Compare commits
2 Commits
tags/BUGZI
...
M15-patch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53866ece4f | ||
|
|
350be55313 |
105
mozilla/java/dom/jni/javaDOMGlobals.h
Normal file
105
mozilla/java/dom/jni/javaDOMGlobals.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
The contents of this file are subject to the Mozilla Public
|
||||
License Version 1.1 (the "License"); you may not use this file
|
||||
except in compliance with the License. You may obtain a copy of
|
||||
the License at http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS
|
||||
IS" basis, WITHOUT WARRANTY OF 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 Sun Microsystems,
|
||||
Inc. Portions created by Sun are
|
||||
Copyright (C) 1999 Sun Microsystems, Inc. All
|
||||
Rights Reserved.
|
||||
|
||||
Contributor(s):
|
||||
*/
|
||||
|
||||
#ifndef __JavaDOMGlobals_h__
|
||||
#define __JavaDOMGlobals_h__
|
||||
|
||||
#include "jni.h"
|
||||
#include "prclist.h"
|
||||
#include "nsError.h"
|
||||
|
||||
#ifdef ERROR
|
||||
#undef ERROR
|
||||
#endif
|
||||
|
||||
class nsISupports;
|
||||
class nsIDOMNode;
|
||||
struct PRLogModuleInfo;
|
||||
struct PRLock;
|
||||
|
||||
class JavaDOMGlobals {
|
||||
|
||||
public:
|
||||
static jclass attrClass;
|
||||
static jclass cDataSectionClass;
|
||||
static jclass commentClass;
|
||||
static jclass documentClass;
|
||||
static jclass documentFragmentClass;
|
||||
static jclass documentTypeClass;
|
||||
static jclass domImplementationClass;
|
||||
static jclass elementClass;
|
||||
static jclass entityClass;
|
||||
static jclass entityReferenceClass;
|
||||
static jclass namedNodeMapClass;
|
||||
static jclass nodeClass;
|
||||
static jclass nodeListClass;
|
||||
static jclass notationClass;
|
||||
static jclass processingInstructionClass;
|
||||
static jclass textClass;
|
||||
|
||||
static jfieldID nodePtrFID;
|
||||
static jfieldID nodeListPtrFID;
|
||||
static jfieldID domImplementationPtrFID;
|
||||
|
||||
static jfieldID nodeTypeAttributeFID;
|
||||
static jfieldID nodeTypeCDataSectionFID;
|
||||
static jfieldID nodeTypeCommentFID;
|
||||
static jfieldID nodeTypeDocumentFragmentFID;
|
||||
static jfieldID nodeTypeDocumentFID;
|
||||
static jfieldID nodeTypeDocumentTypeFID;
|
||||
static jfieldID nodeTypeElementFID;
|
||||
static jfieldID nodeTypeEntityFID;
|
||||
static jfieldID nodeTypeEntityReferenceFID;
|
||||
static jfieldID nodeTypeNotationFID;
|
||||
static jfieldID nodeTypeProcessingInstructionFID;
|
||||
static jfieldID nodeTypeTextFID;
|
||||
|
||||
static jclass domExceptionClass;
|
||||
static jmethodID domExceptionInitMID;
|
||||
static jclass runtimeExceptionClass;
|
||||
static jmethodID runtimeExceptionInitMID;
|
||||
|
||||
static const char* const DOM_EXCEPTION_MESSAGE[];
|
||||
|
||||
typedef enum ExceptionType { EXCEPTION_RUNTIME,
|
||||
EXCEPTION_DOM } ExceptionType;
|
||||
|
||||
static PRLogModuleInfo* log;
|
||||
static PRCList garbage;
|
||||
static PRLock* garbageLock;
|
||||
|
||||
static PRInt32 javaMaxInt;
|
||||
|
||||
static void Initialize(JNIEnv *env);
|
||||
static void Destroy(JNIEnv *env);
|
||||
static jobject CreateNodeSubtype(JNIEnv *env,
|
||||
nsIDOMNode *node);
|
||||
|
||||
static void AddToGarbage(nsISupports* domObject);
|
||||
static void TakeOutGarbage();
|
||||
|
||||
static void ThrowException(JNIEnv *env,
|
||||
const char * message = NULL,
|
||||
nsresult rv = NS_OK,
|
||||
ExceptionType exceptionType = EXCEPTION_RUNTIME);
|
||||
};
|
||||
|
||||
#endif /* __JavaDOMGlobals_h__ */
|
||||
@@ -1,749 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# A. Karl Kornel <karl@kornel.name>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla;
|
||||
|
||||
use strict;
|
||||
|
||||
# We want any compile errors to get to the browser, if possible.
|
||||
BEGIN {
|
||||
# This makes sure we're in a CGI.
|
||||
if ($ENV{SERVER_SOFTWARE} && !$ENV{MOD_PERL}) {
|
||||
require CGI::Carp;
|
||||
CGI::Carp->import('fatalsToBrowser');
|
||||
}
|
||||
}
|
||||
|
||||
use Bugzilla::Config;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Auth;
|
||||
use Bugzilla::Auth::Persist::Cookie;
|
||||
use Bugzilla::CGI;
|
||||
use Bugzilla::DB;
|
||||
use Bugzilla::Install::Localconfig qw(read_localconfig);
|
||||
use Bugzilla::JobQueue;
|
||||
use Bugzilla::Template;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Field;
|
||||
use Bugzilla::Flag;
|
||||
|
||||
use File::Basename;
|
||||
use File::Spec::Functions;
|
||||
use DateTime::TimeZone;
|
||||
use Safe;
|
||||
|
||||
# This creates the request cache for non-mod_perl installations.
|
||||
our $_request_cache = {};
|
||||
|
||||
#####################################################################
|
||||
# Constants
|
||||
#####################################################################
|
||||
|
||||
# Scripts that are not stopped by shutdownhtml being in effect.
|
||||
use constant SHUTDOWNHTML_EXEMPT => [
|
||||
'editparams.cgi',
|
||||
'checksetup.pl',
|
||||
'recode.pl',
|
||||
];
|
||||
|
||||
# Non-cgi scripts that should silently exit.
|
||||
use constant SHUTDOWNHTML_EXIT_SILENTLY => [
|
||||
'whine.pl'
|
||||
];
|
||||
|
||||
#####################################################################
|
||||
# Global Code
|
||||
#####################################################################
|
||||
|
||||
# $::SIG{__DIE__} = i_am_cgi() ? \&CGI::Carp::confess : \&Carp::confess;
|
||||
|
||||
# Note that this is a raw subroutine, not a method, so $class isn't available.
|
||||
sub init_page {
|
||||
(binmode STDOUT, ':utf8') if Bugzilla->params->{'utf8'};
|
||||
|
||||
|
||||
if (${^TAINT}) {
|
||||
# Some environment variables are not taint safe
|
||||
delete @::ENV{'PATH', 'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
|
||||
# Some modules throw undefined errors (notably File::Spec::Win32) if
|
||||
# PATH is undefined.
|
||||
$ENV{'PATH'} = '';
|
||||
}
|
||||
|
||||
# IIS prints out warnings to the webpage, so ignore them, or log them
|
||||
# to a file if the file exists.
|
||||
if ($ENV{SERVER_SOFTWARE} && $ENV{SERVER_SOFTWARE} =~ /microsoft-iis/i) {
|
||||
$SIG{__WARN__} = sub {
|
||||
my ($msg) = @_;
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
if (-w "$datadir/errorlog") {
|
||||
my $warning_log = new IO::File(">>$datadir/errorlog");
|
||||
print $warning_log $msg;
|
||||
$warning_log->close();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
# If Bugzilla is shut down, do not allow anything to run, just display a
|
||||
# message to the user about the downtime and log out. Scripts listed in
|
||||
# SHUTDOWNHTML_EXEMPT are exempt from this message.
|
||||
#
|
||||
# Because this is code which is run live from perl "use" commands of other
|
||||
# scripts, we're skipping this part if we get here during a perl syntax
|
||||
# check -- runtests.pl compiles scripts without running them, so we
|
||||
# need to make sure that this check doesn't apply to 'perl -c' calls.
|
||||
#
|
||||
# This code must go here. It cannot go anywhere in Bugzilla::CGI, because
|
||||
# it uses Template, and that causes various dependency loops.
|
||||
if (!$^C && Bugzilla->params->{"shutdownhtml"}
|
||||
&& lsearch(SHUTDOWNHTML_EXEMPT, basename($0)) == -1)
|
||||
{
|
||||
# Allow non-cgi scripts to exit silently (without displaying any
|
||||
# message), if desired. At this point, no DBI call has been made
|
||||
# yet, and no error will be returned if the DB is inaccessible.
|
||||
if (lsearch(SHUTDOWNHTML_EXIT_SILENTLY, basename($0)) > -1
|
||||
&& !i_am_cgi())
|
||||
{
|
||||
exit;
|
||||
}
|
||||
|
||||
# For security reasons, log out users when Bugzilla is down.
|
||||
# Bugzilla->login() is required to catch the logincookie, if any.
|
||||
my $user;
|
||||
eval { $user = Bugzilla->login(LOGIN_OPTIONAL); };
|
||||
if ($@) {
|
||||
# The DB is not accessible. Use the default user object.
|
||||
$user = Bugzilla->user;
|
||||
$user->{settings} = {};
|
||||
}
|
||||
my $userid = $user->id;
|
||||
Bugzilla->logout();
|
||||
|
||||
my $template = Bugzilla->template;
|
||||
my $vars = {};
|
||||
$vars->{'message'} = 'shutdown';
|
||||
$vars->{'userid'} = $userid;
|
||||
# Generate and return a message about the downtime, appropriately
|
||||
# for if we're a command-line script or a CGI script.
|
||||
my $extension;
|
||||
if (i_am_cgi() && (!Bugzilla->cgi->param('ctype')
|
||||
|| Bugzilla->cgi->param('ctype') eq 'html')) {
|
||||
$extension = 'html';
|
||||
}
|
||||
else {
|
||||
$extension = 'txt';
|
||||
}
|
||||
print Bugzilla->cgi->header() if i_am_cgi();
|
||||
my $t_output;
|
||||
$template->process("global/message.$extension.tmpl", $vars, \$t_output)
|
||||
|| ThrowTemplateError($template->error);
|
||||
print $t_output . "\n";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
init_page() if !$ENV{MOD_PERL};
|
||||
|
||||
#####################################################################
|
||||
# Subroutines and Methods
|
||||
#####################################################################
|
||||
|
||||
sub template {
|
||||
my $class = shift;
|
||||
$class->request_cache->{language} = "";
|
||||
$class->request_cache->{template} ||= Bugzilla::Template->create();
|
||||
return $class->request_cache->{template};
|
||||
}
|
||||
|
||||
sub template_inner {
|
||||
my ($class, $lang) = @_;
|
||||
$lang = defined($lang) ? $lang : ($class->request_cache->{language} || "");
|
||||
$class->request_cache->{language} = $lang;
|
||||
$class->request_cache->{"template_inner_$lang"}
|
||||
||= Bugzilla::Template->create();
|
||||
return $class->request_cache->{"template_inner_$lang"};
|
||||
}
|
||||
|
||||
sub cgi {
|
||||
my $class = shift;
|
||||
$class->request_cache->{cgi} ||= new Bugzilla::CGI();
|
||||
return $class->request_cache->{cgi};
|
||||
}
|
||||
|
||||
sub localconfig {
|
||||
my $class = shift;
|
||||
$class->request_cache->{localconfig} ||= read_localconfig();
|
||||
return $class->request_cache->{localconfig};
|
||||
}
|
||||
|
||||
sub params {
|
||||
my $class = shift;
|
||||
$class->request_cache->{params} ||= Bugzilla::Config::read_param_file();
|
||||
return $class->request_cache->{params};
|
||||
}
|
||||
|
||||
sub user {
|
||||
my $class = shift;
|
||||
$class->request_cache->{user} ||= new Bugzilla::User;
|
||||
return $class->request_cache->{user};
|
||||
}
|
||||
|
||||
sub set_user {
|
||||
my ($class, $user) = @_;
|
||||
$class->request_cache->{user} = $user;
|
||||
}
|
||||
|
||||
sub sudoer {
|
||||
my $class = shift;
|
||||
return $class->request_cache->{sudoer};
|
||||
}
|
||||
|
||||
sub sudo_request {
|
||||
my ($class, $new_user, $new_sudoer) = @_;
|
||||
$class->request_cache->{user} = $new_user;
|
||||
$class->request_cache->{sudoer} = $new_sudoer;
|
||||
# NOTE: If you want to log the start of an sudo session, do it here.
|
||||
}
|
||||
|
||||
sub page_requires_login {
|
||||
return $_[0]->request_cache->{page_requires_login};
|
||||
}
|
||||
|
||||
sub login {
|
||||
my ($class, $type) = @_;
|
||||
|
||||
return $class->user if $class->user->id;
|
||||
|
||||
my $authorizer = new Bugzilla::Auth();
|
||||
$type = LOGIN_REQUIRED if $class->cgi->param('GoAheadAndLogIn');
|
||||
|
||||
if (!defined $type || $type == LOGIN_NORMAL) {
|
||||
$type = $class->params->{'requirelogin'} ? LOGIN_REQUIRED : LOGIN_NORMAL;
|
||||
}
|
||||
|
||||
# Allow templates to know that we're in a page that always requires
|
||||
# login.
|
||||
if ($type == LOGIN_REQUIRED) {
|
||||
$class->request_cache->{page_requires_login} = 1;
|
||||
}
|
||||
|
||||
my $authenticated_user = $authorizer->login($type);
|
||||
|
||||
# At this point, we now know if a real person is logged in.
|
||||
# We must now check to see if an sudo session is in progress.
|
||||
# For a session to be in progress, the following must be true:
|
||||
# 1: There must be a logged in user
|
||||
# 2: That user must be in the 'bz_sudoer' group
|
||||
# 3: There must be a valid value in the 'sudo' cookie
|
||||
# 4: A Bugzilla::User object must exist for the given cookie value
|
||||
# 5: That user must NOT be in the 'bz_sudo_protect' group
|
||||
my $sudo_cookie = $class->cgi->cookie('sudo');
|
||||
detaint_natural($sudo_cookie) if defined($sudo_cookie);
|
||||
my $sudo_target;
|
||||
$sudo_target = new Bugzilla::User($sudo_cookie) if defined($sudo_cookie);
|
||||
if (defined($authenticated_user) &&
|
||||
$authenticated_user->in_group('bz_sudoers') &&
|
||||
defined($sudo_cookie) &&
|
||||
defined($sudo_target) &&
|
||||
!($sudo_target->in_group('bz_sudo_protect'))
|
||||
)
|
||||
{
|
||||
$class->set_user($sudo_target);
|
||||
$class->request_cache->{sudoer} = $authenticated_user;
|
||||
# And make sure that both users have the same Auth object,
|
||||
# since we never call Auth::login for the sudo target.
|
||||
$sudo_target->set_authorizer($authenticated_user->authorizer);
|
||||
|
||||
# NOTE: If you want to do any special logging, do it here.
|
||||
}
|
||||
else {
|
||||
$class->set_user($authenticated_user);
|
||||
}
|
||||
|
||||
# We run after the login has completed since
|
||||
# some of the checks in ssl_require_redirect
|
||||
# look for Bugzilla->user->id to determine
|
||||
# if redirection is required.
|
||||
if (i_am_cgi() && ssl_require_redirect()) {
|
||||
$class->cgi->require_https($class->params->{'sslbase'});
|
||||
}
|
||||
|
||||
return $class->user;
|
||||
}
|
||||
|
||||
sub logout {
|
||||
my ($class, $option) = @_;
|
||||
|
||||
# If we're not logged in, go away
|
||||
return unless $class->user->id;
|
||||
|
||||
$option = LOGOUT_CURRENT unless defined $option;
|
||||
Bugzilla::Auth::Persist::Cookie->logout({type => $option});
|
||||
$class->logout_request() unless $option eq LOGOUT_KEEP_CURRENT;
|
||||
}
|
||||
|
||||
sub logout_user {
|
||||
my ($class, $user) = @_;
|
||||
# When we're logging out another user we leave cookies alone, and
|
||||
# therefore avoid calling Bugzilla->logout() directly.
|
||||
Bugzilla::Auth::Persist::Cookie->logout({user => $user});
|
||||
}
|
||||
|
||||
# just a compatibility front-end to logout_user that gets a user by id
|
||||
sub logout_user_by_id {
|
||||
my ($class, $id) = @_;
|
||||
my $user = new Bugzilla::User($id);
|
||||
$class->logout_user($user);
|
||||
}
|
||||
|
||||
# hack that invalidates credentials for a single request
|
||||
sub logout_request {
|
||||
my $class = shift;
|
||||
delete $class->request_cache->{user};
|
||||
delete $class->request_cache->{sudoer};
|
||||
# We can't delete from $cgi->cookie, so logincookie data will remain
|
||||
# there. Don't rely on it: use Bugzilla->user->login instead!
|
||||
}
|
||||
|
||||
sub job_queue {
|
||||
my $class = shift;
|
||||
$class->request_cache->{job_queue} ||= Bugzilla::JobQueue->new();
|
||||
return $class->request_cache->{job_queue};
|
||||
}
|
||||
|
||||
sub dbh {
|
||||
my $class = shift;
|
||||
# If we're not connected, then we must want the main db
|
||||
$class->request_cache->{dbh} ||= $class->request_cache->{dbh_main}
|
||||
= Bugzilla::DB::connect_main();
|
||||
|
||||
return $class->request_cache->{dbh};
|
||||
}
|
||||
|
||||
sub languages {
|
||||
my $class = shift;
|
||||
return $class->request_cache->{languages}
|
||||
if $class->request_cache->{languages};
|
||||
|
||||
my @files = glob(catdir(bz_locations->{'templatedir'}, '*'));
|
||||
my @languages;
|
||||
foreach my $dir_entry (@files) {
|
||||
# It's a language directory only if it contains "default" or
|
||||
# "custom". This auto-excludes CVS directories as well.
|
||||
next unless (-d catdir($dir_entry, 'default')
|
||||
|| -d catdir($dir_entry, 'custom'));
|
||||
$dir_entry = basename($dir_entry);
|
||||
# Check for language tag format conforming to RFC 1766.
|
||||
next unless $dir_entry =~ /^[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?$/;
|
||||
push(@languages, $dir_entry);
|
||||
}
|
||||
return $class->request_cache->{languages} = \@languages;
|
||||
}
|
||||
|
||||
sub error_mode {
|
||||
my ($class, $newval) = @_;
|
||||
if (defined $newval) {
|
||||
$class->request_cache->{error_mode} = $newval;
|
||||
}
|
||||
return $class->request_cache->{error_mode}
|
||||
|| (i_am_cgi() ? ERROR_MODE_WEBPAGE : ERROR_MODE_DIE);
|
||||
}
|
||||
|
||||
sub usage_mode {
|
||||
my ($class, $newval) = @_;
|
||||
if (defined $newval) {
|
||||
if ($newval == USAGE_MODE_BROWSER) {
|
||||
$class->error_mode(ERROR_MODE_WEBPAGE);
|
||||
}
|
||||
elsif ($newval == USAGE_MODE_CMDLINE) {
|
||||
$class->error_mode(ERROR_MODE_DIE);
|
||||
}
|
||||
elsif ($newval == USAGE_MODE_WEBSERVICE) {
|
||||
$class->error_mode(ERROR_MODE_DIE_SOAP_FAULT);
|
||||
}
|
||||
elsif ($newval == USAGE_MODE_EMAIL) {
|
||||
$class->error_mode(ERROR_MODE_DIE);
|
||||
}
|
||||
else {
|
||||
ThrowCodeError('usage_mode_invalid',
|
||||
{'invalid_usage_mode', $newval});
|
||||
}
|
||||
$class->request_cache->{usage_mode} = $newval;
|
||||
}
|
||||
return $class->request_cache->{usage_mode}
|
||||
|| (i_am_cgi()? USAGE_MODE_BROWSER : USAGE_MODE_CMDLINE);
|
||||
}
|
||||
|
||||
sub installation_mode {
|
||||
my ($class, $newval) = @_;
|
||||
($class->request_cache->{installation_mode} = $newval) if defined $newval;
|
||||
return $class->request_cache->{installation_mode}
|
||||
|| INSTALLATION_MODE_INTERACTIVE;
|
||||
}
|
||||
|
||||
sub installation_answers {
|
||||
my ($class, $filename) = @_;
|
||||
if ($filename) {
|
||||
my $s = new Safe;
|
||||
$s->rdo($filename);
|
||||
|
||||
die "Error reading $filename: $!" if $!;
|
||||
die "Error evaluating $filename: $@" if $@;
|
||||
|
||||
# Now read the param back out from the sandbox
|
||||
$class->request_cache->{installation_answers} = $s->varglob('answer');
|
||||
}
|
||||
return $class->request_cache->{installation_answers} || {};
|
||||
}
|
||||
|
||||
sub switch_to_shadow_db {
|
||||
my $class = shift;
|
||||
|
||||
if (!$class->request_cache->{dbh_shadow}) {
|
||||
if ($class->params->{'shadowdb'}) {
|
||||
$class->request_cache->{dbh_shadow} = Bugzilla::DB::connect_shadow();
|
||||
} else {
|
||||
$class->request_cache->{dbh_shadow} = request_cache()->{dbh_main};
|
||||
}
|
||||
}
|
||||
|
||||
$class->request_cache->{dbh} = $class->request_cache->{dbh_shadow};
|
||||
# we have to return $class->dbh instead of {dbh} as
|
||||
# {dbh_shadow} may be undefined if no shadow DB is used
|
||||
# and no connection to the main DB has been established yet.
|
||||
return $class->dbh;
|
||||
}
|
||||
|
||||
sub switch_to_main_db {
|
||||
my $class = shift;
|
||||
|
||||
$class->request_cache->{dbh} = $class->request_cache->{dbh_main};
|
||||
# We have to return $class->dbh instead of {dbh} as
|
||||
# {dbh_main} may be undefined if no connection to the main DB
|
||||
# has been established yet.
|
||||
return $class->dbh;
|
||||
}
|
||||
|
||||
sub get_fields {
|
||||
my $class = shift;
|
||||
my $criteria = shift;
|
||||
# This function may be called during installation, and Field::match
|
||||
# may fail at that time. so we want to return an empty list in that
|
||||
# case.
|
||||
my $fields = eval { Bugzilla::Field->match($criteria) } || [];
|
||||
return @$fields;
|
||||
}
|
||||
|
||||
sub active_custom_fields {
|
||||
my $class = shift;
|
||||
if (!exists $class->request_cache->{active_custom_fields}) {
|
||||
$class->request_cache->{active_custom_fields} =
|
||||
Bugzilla::Field->match({ custom => 1, obsolete => 0 });
|
||||
}
|
||||
return @{$class->request_cache->{active_custom_fields}};
|
||||
}
|
||||
|
||||
sub has_flags {
|
||||
my $class = shift;
|
||||
|
||||
if (!defined $class->request_cache->{has_flags}) {
|
||||
$class->request_cache->{has_flags} = Bugzilla::Flag::has_flags();
|
||||
}
|
||||
return $class->request_cache->{has_flags};
|
||||
}
|
||||
|
||||
sub hook_args {
|
||||
my ($class, $args) = @_;
|
||||
$class->request_cache->{hook_args} = $args if $args;
|
||||
return $class->request_cache->{hook_args};
|
||||
}
|
||||
|
||||
sub local_timezone {
|
||||
my $class = shift;
|
||||
|
||||
if (!defined $class->request_cache->{local_timezone}) {
|
||||
$class->request_cache->{local_timezone} =
|
||||
DateTime::TimeZone->new(name => 'local');
|
||||
}
|
||||
return $class->request_cache->{local_timezone};
|
||||
}
|
||||
|
||||
sub request_cache {
|
||||
if ($ENV{MOD_PERL}) {
|
||||
require Apache2::RequestUtil;
|
||||
return Apache2::RequestUtil->request->pnotes();
|
||||
}
|
||||
return $_request_cache;
|
||||
}
|
||||
|
||||
# Private methods
|
||||
|
||||
# Per-process cleanup. Note that this is a plain subroutine, not a method,
|
||||
# so we don't have $class available.
|
||||
sub _cleanup {
|
||||
my $main = Bugzilla->request_cache->{dbh_main};
|
||||
my $shadow = Bugzilla->request_cache->{dbh_shadow};
|
||||
foreach my $dbh ($main, $shadow) {
|
||||
next if !$dbh;
|
||||
$dbh->bz_rollback_transaction() if $dbh->bz_in_transaction;
|
||||
$dbh->disconnect;
|
||||
}
|
||||
undef $_request_cache;
|
||||
}
|
||||
|
||||
sub END {
|
||||
# Bugzilla.pm cannot compile in mod_perl.pl if this runs.
|
||||
_cleanup() unless $ENV{MOD_PERL};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla - Semi-persistent collection of various objects used by scripts
|
||||
and modules
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla;
|
||||
|
||||
sub someModulesSub {
|
||||
Bugzilla->dbh->prepare(...);
|
||||
Bugzilla->template->process(...);
|
||||
}
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Several Bugzilla 'things' are used by a variety of modules and scripts. This
|
||||
includes database handles, template objects, and so on.
|
||||
|
||||
This module is a singleton intended as a central place to store these objects.
|
||||
This approach has several advantages:
|
||||
|
||||
=over 4
|
||||
|
||||
=item *
|
||||
|
||||
They're not global variables, so we don't have issues with them staying around
|
||||
with mod_perl
|
||||
|
||||
=item *
|
||||
|
||||
Everything is in one central place, so it's easy to access, modify, and maintain
|
||||
|
||||
=item *
|
||||
|
||||
Code in modules can get access to these objects without having to have them
|
||||
all passed from the caller, and the caller's caller, and....
|
||||
|
||||
=item *
|
||||
|
||||
We can reuse objects across requests using mod_perl where appropriate (eg
|
||||
templates), whilst destroying those which are only valid for a single request
|
||||
(such as the current user)
|
||||
|
||||
=back
|
||||
|
||||
Note that items accessible via this object are demand-loaded when requested.
|
||||
|
||||
For something to be added to this object, it should either be able to benefit
|
||||
from persistence when run under mod_perl (such as the a C<template> object),
|
||||
or should be something which is globally required by a large ammount of code
|
||||
(such as the current C<user> object).
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
Note that all C<Bugzilla> functionality is method based; use C<Bugzilla-E<gt>dbh>
|
||||
rather than C<Bugzilla::dbh>. Nothing cares about this now, but don't rely on
|
||||
that.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<template>
|
||||
|
||||
The current C<Template> object, to be used for output
|
||||
|
||||
=item C<template_inner>
|
||||
|
||||
If you ever need a L<Bugzilla::Template> object while you're already
|
||||
processing a template, use this. Also use it if you want to specify
|
||||
the language to use. If no argument is passed, it uses the last
|
||||
language set. If the argument is "" (empty string), the language is
|
||||
reset to the current one (the one used by Bugzilla->template).
|
||||
|
||||
=item C<cgi>
|
||||
|
||||
The current C<cgi> object. Note that modules should B<not> be using this in
|
||||
general. Not all Bugzilla actions are cgi requests. Its useful as a convenience
|
||||
method for those scripts/templates which are only use via CGI, though.
|
||||
|
||||
=item C<user>
|
||||
|
||||
C<undef> if there is no currently logged in user or if the login code has not
|
||||
yet been run. If an sudo session is in progress, the C<Bugzilla::User>
|
||||
corresponding to the person who is being impersonated. If no session is in
|
||||
progress, the current C<Bugzilla::User>.
|
||||
|
||||
=item C<set_user>
|
||||
|
||||
Allows you to directly set what L</user> will return. You can use this
|
||||
if you want to bypass L</login> for some reason and directly "log in"
|
||||
a specific L<Bugzilla::User>. Be careful with it, though!
|
||||
|
||||
=item C<sudoer>
|
||||
|
||||
C<undef> if there is no currently logged in user, the currently logged in user
|
||||
is not in the I<sudoer> group, or there is no session in progress. If an sudo
|
||||
session is in progress, returns the C<Bugzilla::User> object corresponding to
|
||||
the person who logged in and initiated the session. If no session is in
|
||||
progress, returns the C<Bugzilla::User> object corresponding to the currently
|
||||
logged in user.
|
||||
|
||||
=item C<sudo_request>
|
||||
This begins an sudo session for the current request. It is meant to be
|
||||
used when a session has just started. For normal use, sudo access should
|
||||
normally be set at login time.
|
||||
|
||||
=item C<login>
|
||||
|
||||
Logs in a user, returning a C<Bugzilla::User> object, or C<undef> if there is
|
||||
no logged in user. See L<Bugzilla::Auth|Bugzilla::Auth>, and
|
||||
L<Bugzilla::User|Bugzilla::User>.
|
||||
|
||||
=item C<page_requires_login>
|
||||
|
||||
If the current page always requires the user to log in (for example,
|
||||
C<enter_bug.cgi> or any page called with C<?GoAheadAndLogIn=1>) then
|
||||
this will return something true. Otherwise it will return false. (This is
|
||||
set when you call L</login>.)
|
||||
|
||||
=item C<logout($option)>
|
||||
|
||||
Logs out the current user, which involves invalidating user sessions and
|
||||
cookies. Three options are available from
|
||||
L<Bugzilla::Constants|Bugzilla::Constants>: LOGOUT_CURRENT (the
|
||||
default), LOGOUT_ALL or LOGOUT_KEEP_CURRENT.
|
||||
|
||||
=item C<logout_user($user)>
|
||||
|
||||
Logs out the specified user (invalidating all his sessions), taking a
|
||||
Bugzilla::User instance.
|
||||
|
||||
=item C<logout_by_id($id)>
|
||||
|
||||
Logs out the user with the id specified. This is a compatibility
|
||||
function to be used in callsites where there is only a userid and no
|
||||
Bugzilla::User instance.
|
||||
|
||||
=item C<logout_request>
|
||||
|
||||
Essentially, causes calls to C<Bugzilla-E<gt>user> to return C<undef>. This has the
|
||||
effect of logging out a user for the current request only; cookies and
|
||||
database sessions are left intact.
|
||||
|
||||
=item C<error_mode>
|
||||
|
||||
Call either C<Bugzilla->error_mode(Bugzilla::Constants::ERROR_MODE_DIE)>
|
||||
or C<Bugzilla->error_mode(Bugzilla::Constants::ERROR_MODE_DIE_SOAP_FAULT)> to
|
||||
change this flag's default of C<Bugzilla::Constants::ERROR_MODE_WEBPAGE> and to
|
||||
indicate that errors should be passed to error mode specific error handlers
|
||||
rather than being sent to a browser and finished with an exit().
|
||||
|
||||
This is useful, for example, to keep C<eval> blocks from producing wild HTML
|
||||
on errors, making it easier for you to catch them.
|
||||
(Remember to reset the error mode to its previous value afterwards, though.)
|
||||
|
||||
C<Bugzilla->error_mode> will return the current state of this flag.
|
||||
|
||||
Note that C<Bugzilla->error_mode> is being called by C<Bugzilla->usage_mode> on
|
||||
usage mode changes.
|
||||
|
||||
=item C<usage_mode>
|
||||
|
||||
Call either C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_CMDLINE)>
|
||||
or C<Bugzilla->usage_mode(Bugzilla::Constants::USAGE_MODE_WEBSERVICE)> near the
|
||||
beginning of your script to change this flag's default of
|
||||
C<Bugzilla::Constants::USAGE_MODE_BROWSER> and to indicate that Bugzilla is
|
||||
being called in a non-interactive manner.
|
||||
This influences error handling because on usage mode changes, C<usage_mode>
|
||||
calls C<Bugzilla->error_mode> to set an error mode which makes sense for the
|
||||
usage mode.
|
||||
|
||||
C<Bugzilla->usage_mode> will return the current state of this flag.
|
||||
|
||||
=item C<installation_mode>
|
||||
|
||||
Determines whether or not installation should be silent. See
|
||||
L<Bugzilla::Constants> for the C<INSTALLATION_MODE> constants.
|
||||
|
||||
=item C<installation_answers>
|
||||
|
||||
Returns a hashref representing any "answers" file passed to F<checksetup.pl>,
|
||||
used to automatically answer or skip prompts.
|
||||
|
||||
=item C<dbh>
|
||||
|
||||
The current database handle. See L<DBI>.
|
||||
|
||||
=item C<languages>
|
||||
|
||||
Currently installed languages.
|
||||
Returns a reference to a list of RFC 1766 language tags of installed languages.
|
||||
|
||||
=item C<switch_to_shadow_db>
|
||||
|
||||
Switch from using the main database to using the shadow database.
|
||||
|
||||
=item C<switch_to_main_db>
|
||||
|
||||
Change the database object to refer to the main database.
|
||||
|
||||
=item C<params>
|
||||
|
||||
The current Parameters of Bugzilla, as a hashref. If C<data/params>
|
||||
does not exist, then we return an empty hashref. If C<data/params>
|
||||
is unreadable or is not valid perl, we C<die>.
|
||||
|
||||
=item C<hook_args>
|
||||
|
||||
If you are running inside a code hook (see L<Bugzilla::Hook>) this
|
||||
is how you get the arguments passed to the hook.
|
||||
|
||||
=item C<local_timezone>
|
||||
|
||||
Returns the local timezone of the Bugzilla installation,
|
||||
as a DateTime::TimeZone object. This detection is very time
|
||||
consuming, so we cache this information for future references.
|
||||
|
||||
=item C<job_queue>
|
||||
|
||||
Returns a L<Bugzilla::JobQueue> that you can use for queueing jobs.
|
||||
Will throw an error if job queueing is not correctly configured on
|
||||
this Bugzilla installation.
|
||||
|
||||
=back
|
||||
@@ -1,988 +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 - Bugzilla attachment class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Attachment;
|
||||
|
||||
# Get the attachment with the given ID.
|
||||
my $attachment = new Bugzilla::Attachment($attach_id);
|
||||
|
||||
# Get the attachments with the given IDs.
|
||||
my $attachments = Bugzilla::Attachment->new_from_list($attach_ids);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Attachment.pm represents an attachment 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::Attachment> are listed
|
||||
below.
|
||||
|
||||
=cut
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Flag;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Field;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
use constant DB_TABLE => 'attachments';
|
||||
use constant ID_FIELD => 'attach_id';
|
||||
use constant LIST_ORDER => ID_FIELD;
|
||||
|
||||
sub DB_COLUMNS {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
return qw(
|
||||
attach_id
|
||||
bug_id
|
||||
description
|
||||
filename
|
||||
isobsolete
|
||||
ispatch
|
||||
isprivate
|
||||
isurl
|
||||
mimetype
|
||||
modification_time
|
||||
submitter_id),
|
||||
$dbh->sql_date_format('attachments.creation_ts', '%Y.%m.%d %H:%i') . ' AS creation_ts';
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
=pod
|
||||
|
||||
=head2 Instance Properties
|
||||
|
||||
=over
|
||||
|
||||
=item C<bug_id>
|
||||
|
||||
the ID of the bug to which the attachment is attached
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub bug_id {
|
||||
my $self = shift;
|
||||
return $self->{bug_id};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<bug>
|
||||
|
||||
the bug object to which the attachment is attached
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub bug {
|
||||
my $self = shift;
|
||||
|
||||
require Bugzilla::Bug;
|
||||
$self->{bug} = Bugzilla::Bug->new($self->bug_id);
|
||||
return $self->{bug};
|
||||
}
|
||||
|
||||
=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->{mimetype};
|
||||
}
|
||||
|
||||
=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->{submitter_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->{creation_ts};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<modification_time>
|
||||
|
||||
the date and time on which the attachment was last modified.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub modification_time {
|
||||
my $self = shift;
|
||||
return $self->{modification_time};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<filename>
|
||||
|
||||
the name of the file the attacher attached
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub filename {
|
||||
my $self = shift;
|
||||
return $self->{filename};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<ispatch>
|
||||
|
||||
whether or not the attachment is a patch
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub ispatch {
|
||||
my $self = shift;
|
||||
return $self->{ispatch};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<isurl>
|
||||
|
||||
whether or not the attachment is a URL
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub isurl {
|
||||
my $self = shift;
|
||||
return $self->{isurl};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<isobsolete>
|
||||
|
||||
whether or not the attachment is obsolete
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub isobsolete {
|
||||
my $self = shift;
|
||||
return $self->{isobsolete};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<isprivate>
|
||||
|
||||
whether or not the attachment is private
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub isprivate {
|
||||
my $self = shift;
|
||||
return $self->{isprivate};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<is_viewable>
|
||||
|
||||
Returns 1 if the attachment has a content-type viewable in this browser.
|
||||
Note that we don't use $cgi->Accept()'s ability to check if a content-type
|
||||
matches, because this will return a value even if it's matched by the generic
|
||||
*/* which most browsers add to the end of their Accept: headers.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub is_viewable {
|
||||
my $self = shift;
|
||||
my $contenttype = $self->contenttype;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
# We assume we can view all text and image types.
|
||||
return 1 if ($contenttype =~ /^(text|image)\//);
|
||||
|
||||
# Mozilla can view XUL. Note the trailing slash on the Gecko detection to
|
||||
# avoid sending XUL to Safari.
|
||||
return 1 if (($contenttype =~ /^application\/vnd\.mozilla\./)
|
||||
&& ($cgi->user_agent() =~ /Gecko\//));
|
||||
|
||||
# If it's not one of the above types, we check the Accept: header for any
|
||||
# types mentioned explicitly.
|
||||
my $accept = join(",", $cgi->Accept());
|
||||
return 1 if ($accept =~ /^(.*,)?\Q$contenttype\E(,.*)?$/);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<data>
|
||||
|
||||
the content of the attachment
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub data {
|
||||
my $self = shift;
|
||||
return $self->{data} if exists $self->{data};
|
||||
|
||||
# First try to get the attachment data from the database.
|
||||
($self->{data}) = Bugzilla->dbh->selectrow_array("SELECT thedata
|
||||
FROM attach_data
|
||||
WHERE id = ?",
|
||||
undef,
|
||||
$self->id);
|
||||
|
||||
# If there's no attachment data in the database, the attachment is stored
|
||||
# in a local file, so retrieve it from there.
|
||||
if (length($self->{data}) == 0) {
|
||||
if (open(AH, $self->_get_local_filename())) {
|
||||
local $/;
|
||||
binmode AH;
|
||||
$self->{data} = <AH>;
|
||||
close(AH);
|
||||
}
|
||||
}
|
||||
|
||||
return $self->{data};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<datasize>
|
||||
|
||||
the length (in characters) of the attachment content
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
# datasize is a property of the data itself, and it's unclear whether we should
|
||||
# expose it at all, since you can easily derive it from the data itself: in TT,
|
||||
# attachment.data.size; in Perl, length($attachment->{data}). But perhaps
|
||||
# it makes sense for performance reasons, since accessing the data forces it
|
||||
# to get retrieved from the database/filesystem and loaded into memory,
|
||||
# while datasize avoids loading the attachment into memory, calling SQL's
|
||||
# LENGTH() function or stat()ing the file instead. I've left it in for now.
|
||||
|
||||
sub datasize {
|
||||
my $self = shift;
|
||||
return $self->{datasize} if exists $self->{datasize};
|
||||
|
||||
# If we have already retrieved the data, return its size.
|
||||
return length($self->{data}) if exists $self->{data};
|
||||
|
||||
$self->{datasize} =
|
||||
Bugzilla->dbh->selectrow_array("SELECT LENGTH(thedata)
|
||||
FROM attach_data
|
||||
WHERE id = ?",
|
||||
undef, $self->id) || 0;
|
||||
|
||||
# If there's no attachment data in the database, either the attachment
|
||||
# is stored in a local file, and so retrieve its size from the file,
|
||||
# or the attachment has been deleted.
|
||||
unless ($self->{datasize}) {
|
||||
if (open(AH, $self->_get_local_filename())) {
|
||||
binmode AH;
|
||||
$self->{datasize} = (stat(AH))[7];
|
||||
close(AH);
|
||||
}
|
||||
}
|
||||
|
||||
return $self->{datasize};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<flags>
|
||||
|
||||
flags that have been set on the attachment
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub flags {
|
||||
my $self = shift;
|
||||
return $self->{flags} if exists $self->{flags};
|
||||
|
||||
$self->{flags} = Bugzilla::Flag->match({ 'attach_id' => $self->id });
|
||||
return $self->{flags};
|
||||
}
|
||||
|
||||
=over
|
||||
|
||||
=item C<flag_types>
|
||||
|
||||
Return all flag types available for this attachment as well as flags
|
||||
already set, grouped by flag type.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub flag_types {
|
||||
my $self = shift;
|
||||
return $self->{flag_types} if exists $self->{flag_types};
|
||||
|
||||
my $vars = { target_type => 'attachment',
|
||||
product_id => $self->bug->product_id,
|
||||
component_id => $self->bug->component_id,
|
||||
attach_id => $self->id };
|
||||
|
||||
$self->{flag_types} = Bugzilla::Flag::_flag_types($vars);
|
||||
return $self->{flag_types};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Validators ######
|
||||
###############################
|
||||
|
||||
# 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 $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 $maxsize = Bugzilla->params->{'maxattachmentsize'} * 1024; # Convert from K
|
||||
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, $vars) = @_;
|
||||
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->new_from_list($attach_ids);
|
||||
|
||||
# To avoid $attachment->flags to run SQL queries itself for each
|
||||
# attachment listed here, we collect all the data at once and
|
||||
# populate $attachment->{flags} ourselves.
|
||||
if ($vars->{preload}) {
|
||||
$_->{flags} = [] foreach @$attachments;
|
||||
my %att = map { $_->id => $_ } @$attachments;
|
||||
|
||||
my $flags = Bugzilla::Flag->match({ bug_id => $bug_id,
|
||||
target_type => 'attachment' });
|
||||
|
||||
# Exclude flags for private attachments you cannot see.
|
||||
@$flags = grep {exists $att{$_->attach_id}} @$flags;
|
||||
|
||||
push(@{$att{$_->attach_id}->{flags}}, $_) foreach @$flags;
|
||||
$attachments = [sort {$a->id <=> $b->id} values %att];
|
||||
}
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<validate_is_patch()>
|
||||
|
||||
Description: validates the "patch" flag passed in by CGI.
|
||||
|
||||
Returns: 1 on success.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_is_patch {
|
||||
my ($class, $throw_error) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
# Set the ispatch flag to zero if it is undefined, since the UI uses
|
||||
# an HTML checkbox to represent this flag, and unchecked HTML checkboxes
|
||||
# do not get sent in HTML requests.
|
||||
$cgi->param('ispatch', $cgi->param('ispatch') ? 1 : 0);
|
||||
|
||||
# Set the content type to text/plain if the attachment is a patch.
|
||||
$cgi->param('contenttype', 'text/plain') if $cgi->param('ispatch');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<validate_description()>
|
||||
|
||||
Description: validates the description passed in by CGI.
|
||||
|
||||
Returns: 1 on success.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_description {
|
||||
my ($class, $throw_error) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
$cgi->param('description')
|
||||
|| ($throw_error ? ThrowUserError("missing_attachment_description") : return 0);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<validate_content_type()>
|
||||
|
||||
Description: validates the content type passed in by CGI.
|
||||
|
||||
Returns: 1 on success.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_content_type {
|
||||
my ($class, $throw_error) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
if (!defined $cgi->param('contenttypemethod')) {
|
||||
$throw_error ? ThrowUserError("missing_content_type_method") : return 0;
|
||||
}
|
||||
elsif ($cgi->param('contenttypemethod') eq 'autodetect') {
|
||||
my $contenttype =
|
||||
$cgi->uploadInfo($cgi->param('data'))->{'Content-Type'};
|
||||
# The user asked us to auto-detect the content type, so use the type
|
||||
# specified in the HTTP request headers.
|
||||
if ( !$contenttype ) {
|
||||
$throw_error ? ThrowUserError("missing_content_type") : return 0;
|
||||
}
|
||||
$cgi->param('contenttype', $contenttype);
|
||||
}
|
||||
elsif ($cgi->param('contenttypemethod') eq 'list') {
|
||||
# The user selected a content type from the list, so use their
|
||||
# selection.
|
||||
$cgi->param('contenttype', $cgi->param('contenttypeselection'));
|
||||
}
|
||||
elsif ($cgi->param('contenttypemethod') eq 'manual') {
|
||||
# The user entered a content type manually, so use their entry.
|
||||
$cgi->param('contenttype', $cgi->param('contenttypeentry'));
|
||||
}
|
||||
else {
|
||||
$throw_error ?
|
||||
ThrowCodeError("illegal_content_type_method",
|
||||
{ contenttypemethod => $cgi->param('contenttypemethod') }) :
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( $cgi->param('contenttype') !~
|
||||
/^(application|audio|image|message|model|multipart|text|video)\/.+$/ ) {
|
||||
$throw_error ?
|
||||
ThrowUserError("invalid_content_type",
|
||||
{ contenttype => $cgi->param('contenttype') }) :
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<validate_can_edit($attachment, $product_id)>
|
||||
|
||||
Description: validates if the user is allowed to view and edit the attachment.
|
||||
Only the submitter or someone with editbugs privs can edit it.
|
||||
Only the submitter and users in the insider group can view
|
||||
private attachments.
|
||||
|
||||
Params: $attachment - the attachment object being edited.
|
||||
$product_id - the product ID the attachment belongs to.
|
||||
|
||||
Returns: 1 on success. Else an error is thrown.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_can_edit {
|
||||
my ($attachment, $product_id) = @_;
|
||||
my $user = Bugzilla->user;
|
||||
|
||||
# The submitter can edit their attachments.
|
||||
return 1 if ($attachment->attacher->id == $user->id
|
||||
|| ((!$attachment->isprivate || $user->is_insider)
|
||||
&& $user->in_group('editbugs', $product_id)));
|
||||
|
||||
# If we come here, then this attachment cannot be seen by the user.
|
||||
ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
|
||||
}
|
||||
|
||||
=item C<validate_obsolete($bug)>
|
||||
|
||||
Description: validates if attachments the user wants to mark as obsolete
|
||||
really belong to the given bug and are not already obsolete.
|
||||
Moreover, a user cannot mark an attachment as obsolete if
|
||||
he cannot view it (due to restrictions on it).
|
||||
|
||||
Params: $bug - The bug object obsolete attachments should belong to.
|
||||
|
||||
Returns: 1 on success. Else an error is thrown.
|
||||
|
||||
=cut
|
||||
|
||||
sub validate_obsolete {
|
||||
my ($class, $bug) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
# Make sure the attachment id is valid and the user has permissions to view
|
||||
# the bug to which it is attached. Make sure also that the user can view
|
||||
# the attachment itself.
|
||||
my @obsolete_attachments;
|
||||
foreach my $attachid ($cgi->param('obsolete')) {
|
||||
my $vars = {};
|
||||
$vars->{'attach_id'} = $attachid;
|
||||
|
||||
detaint_natural($attachid)
|
||||
|| ThrowCodeError('invalid_attach_id_to_obsolete', $vars);
|
||||
|
||||
# Make sure the attachment exists in the database.
|
||||
my $attachment = new Bugzilla::Attachment($attachid)
|
||||
|| ThrowUserError('invalid_attach_id', $vars);
|
||||
|
||||
# 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;
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Constructors #####
|
||||
###############################
|
||||
|
||||
=pod
|
||||
|
||||
=item C<create($throw_error, $bug, $user, $timestamp, $hr_vars)>
|
||||
|
||||
Description: inserts an attachment from CGI input for the given bug.
|
||||
|
||||
Params: C<$bug> - Bugzilla::Bug object - the bug for which to insert
|
||||
the attachment.
|
||||
C<$user> - Bugzilla::User object - the user we're inserting an
|
||||
attachment for.
|
||||
C<$timestamp> - scalar - timestamp of the insert as returned
|
||||
by SELECT NOW().
|
||||
C<$hr_vars> - hash reference - reference to a hash of template
|
||||
variables.
|
||||
|
||||
Returns: the ID of the new attachment.
|
||||
|
||||
=cut
|
||||
|
||||
# FIXME: needs to follow the way Object->create() works.
|
||||
sub create {
|
||||
my ($class, $throw_error, $bug, $user, $timestamp, $hr_vars) = @_;
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $attachurl = $cgi->param('attachurl') || '';
|
||||
my $data;
|
||||
my $filename;
|
||||
my $contenttype;
|
||||
my $isurl;
|
||||
$class->validate_is_patch($throw_error) || return;
|
||||
$class->validate_description($throw_error) || return;
|
||||
|
||||
if (Bugzilla->params->{'allow_attach_url'}
|
||||
&& ($attachurl =~ /^(http|https|ftp):\/\/\S+/)
|
||||
&& !defined $cgi->upload('data'))
|
||||
{
|
||||
$filename = '';
|
||||
$data = $attachurl;
|
||||
$isurl = 1;
|
||||
$contenttype = 'text/plain';
|
||||
$cgi->param('ispatch', 0);
|
||||
$cgi->delete('bigfile');
|
||||
}
|
||||
else {
|
||||
$filename = _validate_filename($throw_error) || return;
|
||||
# need to validate content type before data as
|
||||
# we now check the content type for image/bmp in _validate_data()
|
||||
unless ($cgi->param('ispatch')) {
|
||||
$class->validate_content_type($throw_error) || return;
|
||||
|
||||
# Set the ispatch flag to 1 if we're set to autodetect
|
||||
# and the content type is text/x-diff or text/x-patch
|
||||
if ($cgi->param('contenttypemethod') eq 'autodetect'
|
||||
&& $cgi->param('contenttype') =~ m{text/x-(?:diff|patch)})
|
||||
{
|
||||
$cgi->param('ispatch', 1);
|
||||
$cgi->param('contenttype', 'text/plain');
|
||||
}
|
||||
}
|
||||
$data = _validate_data($throw_error, $hr_vars);
|
||||
# If the attachment is stored locally, $data eq ''.
|
||||
# If an error is thrown, $data eq '0'.
|
||||
($data ne '0') || return;
|
||||
$contenttype = $cgi->param('contenttype');
|
||||
|
||||
# These are inserted using placeholders so no need to panic
|
||||
trick_taint($filename);
|
||||
trick_taint($contenttype);
|
||||
$isurl = 0;
|
||||
}
|
||||
|
||||
# Check attachments the user tries to mark as obsolete.
|
||||
my @obsolete_attachments;
|
||||
if ($cgi->param('obsolete')) {
|
||||
@obsolete_attachments = $class->validate_obsolete($bug);
|
||||
}
|
||||
|
||||
# The order of these function calls is important, as Flag::validate
|
||||
# assumes User::match_field has ensured that the
|
||||
# values in the requestee fields are legitimate user email addresses.
|
||||
my $match_status = Bugzilla::User::match_field($cgi, {
|
||||
'^requestee(_type)?-(\d+)$' => { 'type' => 'multi' },
|
||||
}, MATCH_SKIP_CONFIRM);
|
||||
|
||||
$hr_vars->{'match_field'} = 'requestee';
|
||||
if ($match_status == USER_MATCH_FAILED) {
|
||||
$hr_vars->{'message'} = 'user_match_failed';
|
||||
}
|
||||
elsif ($match_status == USER_MATCH_MULTIPLE) {
|
||||
$hr_vars->{'message'} = 'user_match_multiple';
|
||||
}
|
||||
|
||||
# Escape characters in strings that will be used in SQL statements.
|
||||
my $description = $cgi->param('description');
|
||||
trick_taint($description);
|
||||
my $isprivate = $cgi->param('isprivate') ? 1 : 0;
|
||||
|
||||
# Insert the attachment into the database.
|
||||
my $sth = $dbh->do(
|
||||
"INSERT INTO attachments
|
||||
(bug_id, creation_ts, modification_time, filename, description,
|
||||
mimetype, ispatch, isurl, isprivate, submitter_id)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?)", undef, ($bug->bug_id, $timestamp, $timestamp,
|
||||
$filename, $description, $contenttype, $cgi->param('ispatch'),
|
||||
$isurl, $isprivate, $user->id));
|
||||
# Retrieve the ID of the newly created attachment record.
|
||||
my $attachid = $dbh->bz_last_key('attachments', 'attach_id');
|
||||
|
||||
# We only use $data here in this INSERT with a placeholder,
|
||||
# so it's safe.
|
||||
$sth = $dbh->prepare("INSERT INTO attach_data
|
||||
(id, thedata) VALUES ($attachid, ?)");
|
||||
trick_taint($data);
|
||||
$sth->bind_param(1, $data, $dbh->BLOB_TYPE);
|
||||
$sth->execute();
|
||||
|
||||
# If the file is to be stored locally, stream the file from the web server
|
||||
# to the local file without reading it into a local variable.
|
||||
if ($cgi->param('bigfile')) {
|
||||
my $attachdir = bz_locations()->{'attachdir'};
|
||||
my $fh = $cgi->upload('data');
|
||||
my $hash = ($attachid % 100) + 100;
|
||||
$hash =~ s/.*(\d\d)$/group.$1/;
|
||||
mkdir "$attachdir/$hash", 0770;
|
||||
chmod 0770, "$attachdir/$hash";
|
||||
open(AH, ">$attachdir/$hash/attachment.$attachid");
|
||||
binmode AH;
|
||||
my $sizecount = 0;
|
||||
my $limit = (Bugzilla->params->{"maxlocalattachment"} * 1048576);
|
||||
while (<$fh>) {
|
||||
print AH $_;
|
||||
$sizecount += length($_);
|
||||
if ($sizecount > $limit) {
|
||||
close AH;
|
||||
close $fh;
|
||||
unlink "$attachdir/$hash/attachment.$attachid";
|
||||
$throw_error ? ThrowUserError("local_file_too_large") : return;
|
||||
}
|
||||
}
|
||||
close AH;
|
||||
close $fh;
|
||||
}
|
||||
|
||||
# Make existing attachments obsolete.
|
||||
my $fieldid = get_field_id('attachments.isobsolete');
|
||||
|
||||
foreach my $obsolete_attachment (@obsolete_attachments) {
|
||||
# If the obsolete attachment has request flags, cancel them.
|
||||
# This call must be done before updating the 'attachments' table.
|
||||
Bugzilla::Flag->CancelRequests($bug, $obsolete_attachment, $timestamp);
|
||||
|
||||
$dbh->do('UPDATE attachments SET isobsolete = 1, modification_time = ?
|
||||
WHERE attach_id = ?',
|
||||
undef, ($timestamp, $obsolete_attachment->id));
|
||||
|
||||
$dbh->do('INSERT INTO bugs_activity (bug_id, attach_id, who, bug_when,
|
||||
fieldid, removed, added)
|
||||
VALUES (?,?,?,?,?,?,?)',
|
||||
undef, ($bug->bug_id, $obsolete_attachment->id, $user->id,
|
||||
$timestamp, $fieldid, 0, 1));
|
||||
}
|
||||
|
||||
my $attachment = new Bugzilla::Attachment($attachid);
|
||||
|
||||
# 1. Add flags, if any. To avoid dying if something goes wrong
|
||||
# while processing flags, we will eval() flag validation.
|
||||
# This requires errors to die().
|
||||
# XXX: this can go away as soon as flag validation is able to
|
||||
# fail without dying.
|
||||
#
|
||||
# 2. Flag::validate() should not detect any reference to existing flags
|
||||
# when creating a new attachment. Setting the third param to -1 will
|
||||
# force this function to check this point.
|
||||
my $error_mode_cache = Bugzilla->error_mode;
|
||||
Bugzilla->error_mode(ERROR_MODE_DIE);
|
||||
eval {
|
||||
Bugzilla::Flag::validate($bug->bug_id, -1, SKIP_REQUESTEE_ON_ERROR);
|
||||
Bugzilla::Flag->process($bug, $attachment, $timestamp, $hr_vars);
|
||||
};
|
||||
Bugzilla->error_mode($error_mode_cache);
|
||||
if ($@) {
|
||||
$hr_vars->{'message'} = 'flag_creation_failed';
|
||||
$hr_vars->{'flag_creation_error'} = $@;
|
||||
}
|
||||
|
||||
# Return the new attachment object.
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=item C<remove_from_db()>
|
||||
|
||||
Description: removes an attachment from the DB.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
$dbh->do('DELETE FROM flags WHERE attach_id = ?', undef, $self->id);
|
||||
$dbh->do('DELETE FROM attach_data WHERE id = ?', undef, $self->id);
|
||||
$dbh->do('UPDATE attachments SET mimetype = ?, ispatch = ?, isurl = ?, isobsolete = ?
|
||||
WHERE attach_id = ?', undef, ('text/plain', 0, 0, 1, $self->id));
|
||||
$dbh->bz_commit_transaction();
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,295 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): John Keiser <john@johnkeiser.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Attachment::PatchReader;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Attachment;
|
||||
use Bugzilla::Util;
|
||||
|
||||
sub process_diff {
|
||||
my ($attachment, $format, $context) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $lc = Bugzilla->localconfig;
|
||||
my $vars = {};
|
||||
|
||||
my ($reader, $last_reader) = setup_patch_readers(undef, $context);
|
||||
|
||||
if ($format eq 'raw') {
|
||||
require PatchReader::DiffPrinter::raw;
|
||||
$last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
|
||||
# Actually print out the patch.
|
||||
print $cgi->header(-type => 'text/plain',
|
||||
-expires => '+3M');
|
||||
disable_utf8();
|
||||
$reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
|
||||
}
|
||||
else {
|
||||
my @other_patches = ();
|
||||
if ($lc->{interdiffbin} && $lc->{diffpath}) {
|
||||
# Get the list of attachments that the user can view in this bug.
|
||||
my @attachments =
|
||||
@{Bugzilla::Attachment->get_attachments_by_bug($attachment->bug_id)};
|
||||
# Extract patches only.
|
||||
@attachments = grep {$_->ispatch == 1} @attachments;
|
||||
# We want them sorted from newer to older.
|
||||
@attachments = sort { $b->id <=> $a->id } @attachments;
|
||||
|
||||
# Ignore the current patch, but select the one right before it
|
||||
# chronologically.
|
||||
my $select_next_patch = 0;
|
||||
foreach my $attach (@attachments) {
|
||||
if ($attach->id == $attachment->id) {
|
||||
$select_next_patch = 1;
|
||||
}
|
||||
else {
|
||||
push(@other_patches, { 'id' => $attach->id,
|
||||
'desc' => $attach->description,
|
||||
'selected' => $select_next_patch });
|
||||
$select_next_patch = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$vars->{'bugid'} = $attachment->bug_id;
|
||||
$vars->{'attachid'} = $attachment->id;
|
||||
$vars->{'description'} = $attachment->description;
|
||||
$vars->{'other_patches'} = \@other_patches;
|
||||
|
||||
setup_template_patch_reader($last_reader, $format, $context, $vars);
|
||||
# The patch is going to be displayed in a HTML page and if the utf8
|
||||
# param is enabled, we have to encode attachment data as utf8.
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
$attachment->data; # Populate ->{data}
|
||||
utf8::decode($attachment->{data});
|
||||
}
|
||||
$reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
|
||||
}
|
||||
}
|
||||
|
||||
sub process_interdiff {
|
||||
my ($old_attachment, $new_attachment, $format, $context) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $lc = Bugzilla->localconfig;
|
||||
my $vars = {};
|
||||
|
||||
# Encode attachment data as utf8 if it's going to be displayed in a HTML
|
||||
# page using the UTF-8 encoding.
|
||||
if ($format ne 'raw' && Bugzilla->params->{'utf8'}) {
|
||||
$old_attachment->data; # Populate ->{data}
|
||||
utf8::decode($old_attachment->{data});
|
||||
$new_attachment->data; # Populate ->{data}
|
||||
utf8::decode($new_attachment->{data});
|
||||
}
|
||||
|
||||
# Get old patch data.
|
||||
my ($old_filename, $old_file_list) = get_unified_diff($old_attachment, $format);
|
||||
# Get new patch data.
|
||||
my ($new_filename, $new_file_list) = get_unified_diff($new_attachment, $format);
|
||||
|
||||
my $warning = warn_if_interdiff_might_fail($old_file_list, $new_file_list);
|
||||
|
||||
# Send through interdiff, send output directly to template.
|
||||
# Must hack path so that interdiff will work.
|
||||
$ENV{'PATH'} = $lc->{diffpath};
|
||||
open my $interdiff_fh, "$lc->{interdiffbin} $old_filename $new_filename|";
|
||||
binmode $interdiff_fh;
|
||||
my ($reader, $last_reader) = setup_patch_readers("", $context);
|
||||
|
||||
if ($format eq 'raw') {
|
||||
require PatchReader::DiffPrinter::raw;
|
||||
$last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
|
||||
# Actually print out the patch.
|
||||
print $cgi->header(-type => 'text/plain',
|
||||
-expires => '+3M');
|
||||
disable_utf8();
|
||||
}
|
||||
else {
|
||||
# In case the HTML page is displayed with the UTF-8 encoding.
|
||||
binmode $interdiff_fh, ':utf8' if Bugzilla->params->{'utf8'};
|
||||
|
||||
$vars->{'warning'} = $warning if $warning;
|
||||
$vars->{'bugid'} = $new_attachment->bug_id;
|
||||
$vars->{'oldid'} = $old_attachment->id;
|
||||
$vars->{'old_desc'} = $old_attachment->description;
|
||||
$vars->{'newid'} = $new_attachment->id;
|
||||
$vars->{'new_desc'} = $new_attachment->description;
|
||||
|
||||
setup_template_patch_reader($last_reader, $format, $context, $vars);
|
||||
}
|
||||
$reader->iterate_fh($interdiff_fh, 'interdiff #' . $old_attachment->id .
|
||||
' #' . $new_attachment->id);
|
||||
close $interdiff_fh;
|
||||
$ENV{'PATH'} = '';
|
||||
|
||||
# Delete temporary files.
|
||||
unlink($old_filename) or warn "Could not unlink $old_filename: $!";
|
||||
unlink($new_filename) or warn "Could not unlink $new_filename: $!";
|
||||
}
|
||||
|
||||
######################
|
||||
# Internal routines
|
||||
######################
|
||||
|
||||
sub get_unified_diff {
|
||||
my ($attachment, $format) = @_;
|
||||
|
||||
# Bring in the modules we need.
|
||||
require PatchReader::Raw;
|
||||
require PatchReader::FixPatchRoot;
|
||||
require PatchReader::DiffPrinter::raw;
|
||||
require PatchReader::PatchInfoGrabber;
|
||||
require File::Temp;
|
||||
|
||||
$attachment->ispatch
|
||||
|| ThrowCodeError('must_be_patch', { 'attach_id' => $attachment->id });
|
||||
|
||||
# Reads in the patch, converting to unified diff in a temp file.
|
||||
my $reader = new PatchReader::Raw;
|
||||
my $last_reader = $reader;
|
||||
|
||||
# Fixes patch root (makes canonical if possible).
|
||||
if (Bugzilla->params->{'cvsroot'}) {
|
||||
my $fix_patch_root =
|
||||
new PatchReader::FixPatchRoot(Bugzilla->params->{'cvsroot'});
|
||||
$last_reader->sends_data_to($fix_patch_root);
|
||||
$last_reader = $fix_patch_root;
|
||||
}
|
||||
|
||||
# Grabs the patch file info.
|
||||
my $patch_info_grabber = new PatchReader::PatchInfoGrabber();
|
||||
$last_reader->sends_data_to($patch_info_grabber);
|
||||
$last_reader = $patch_info_grabber;
|
||||
|
||||
# Prints out to temporary file.
|
||||
my ($fh, $filename) = File::Temp::tempfile();
|
||||
if ($format ne 'raw' && Bugzilla->params->{'utf8'}) {
|
||||
# The HTML page will be displayed with the UTF-8 encoding.
|
||||
binmode $fh, ':utf8';
|
||||
}
|
||||
my $raw_printer = new PatchReader::DiffPrinter::raw($fh);
|
||||
$last_reader->sends_data_to($raw_printer);
|
||||
$last_reader = $raw_printer;
|
||||
|
||||
# Iterate!
|
||||
$reader->iterate_string($attachment->id, $attachment->data);
|
||||
|
||||
return ($filename, $patch_info_grabber->patch_info()->{files});
|
||||
}
|
||||
|
||||
sub warn_if_interdiff_might_fail {
|
||||
my ($old_file_list, $new_file_list) = @_;
|
||||
|
||||
# Verify that the list of files diffed is the same.
|
||||
my @old_files = sort keys %{$old_file_list};
|
||||
my @new_files = sort keys %{$new_file_list};
|
||||
if (@old_files != @new_files
|
||||
|| join(' ', @old_files) ne join(' ', @new_files))
|
||||
{
|
||||
return 'interdiff1';
|
||||
}
|
||||
|
||||
# Verify that the revisions in the files are the same.
|
||||
foreach my $file (keys %{$old_file_list}) {
|
||||
if ($old_file_list->{$file}{old_revision} ne
|
||||
$new_file_list->{$file}{old_revision})
|
||||
{
|
||||
return 'interdiff2';
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub setup_patch_readers {
|
||||
my ($diff_root, $context) = @_;
|
||||
|
||||
# Parameters:
|
||||
# format=raw|html
|
||||
# context=patch|file|0-n
|
||||
# collapsed=0|1
|
||||
# headers=0|1
|
||||
|
||||
# Define the patch readers.
|
||||
# The reader that reads the patch in (whatever its format).
|
||||
require PatchReader::Raw;
|
||||
my $reader = new PatchReader::Raw;
|
||||
my $last_reader = $reader;
|
||||
# Fix the patch root if we have a cvs root.
|
||||
if (Bugzilla->params->{'cvsroot'}) {
|
||||
require PatchReader::FixPatchRoot;
|
||||
$last_reader->sends_data_to(new PatchReader::FixPatchRoot(Bugzilla->params->{'cvsroot'}));
|
||||
$last_reader->sends_data_to->diff_root($diff_root) if defined($diff_root);
|
||||
$last_reader = $last_reader->sends_data_to;
|
||||
}
|
||||
|
||||
# Add in cvs context if we have the necessary info to do it
|
||||
if ($context ne 'patch' && Bugzilla->localconfig->{cvsbin}
|
||||
&& Bugzilla->params->{'cvsroot_get'})
|
||||
{
|
||||
require PatchReader::AddCVSContext;
|
||||
# We need to set $cvsbin as global, because PatchReader::CVSClient
|
||||
# needs it in order to find 'cvs'.
|
||||
$main::cvsbin = Bugzilla->localconfig->{cvsbin};
|
||||
$last_reader->sends_data_to(
|
||||
new PatchReader::AddCVSContext($context, Bugzilla->params->{'cvsroot_get'}));
|
||||
$last_reader = $last_reader->sends_data_to;
|
||||
}
|
||||
|
||||
return ($reader, $last_reader);
|
||||
}
|
||||
|
||||
sub setup_template_patch_reader {
|
||||
my ($last_reader, $format, $context, $vars) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
require PatchReader::DiffPrinter::template;
|
||||
|
||||
# Define the vars for templates.
|
||||
if (defined $cgi->param('headers')) {
|
||||
$vars->{'headers'} = $cgi->param('headers');
|
||||
}
|
||||
else {
|
||||
$vars->{'headers'} = 1;
|
||||
}
|
||||
|
||||
$vars->{'collapsed'} = $cgi->param('collapsed');
|
||||
$vars->{'context'} = $context;
|
||||
$vars->{'do_context'} = Bugzilla->localconfig->{cvsbin}
|
||||
&& Bugzilla->params->{'cvsroot_get'} && !$vars->{'newid'};
|
||||
|
||||
# Print everything out.
|
||||
print $cgi->header(-type => 'text/html',
|
||||
-expires => '+3M');
|
||||
|
||||
$last_reader->sends_data_to(new PatchReader::DiffPrinter::template($template,
|
||||
"attachment/diff-header.$format.tmpl",
|
||||
"attachment/diff-file.$format.tmpl",
|
||||
"attachment/diff-footer.$format.tmpl",
|
||||
{ %{$vars},
|
||||
bonsai_url => Bugzilla->params->{'bonsai_url'},
|
||||
lxr_url => Bugzilla->params->{'lxr_url'},
|
||||
lxr_root => Bugzilla->params->{'lxr_root'},
|
||||
}));
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
@@ -1,460 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth;
|
||||
|
||||
use strict;
|
||||
use fields qw(
|
||||
_info_getter
|
||||
_verifier
|
||||
_persister
|
||||
);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Auth::Login::Stack;
|
||||
use Bugzilla::Auth::Verify::Stack;
|
||||
use Bugzilla::Auth::Persist::Cookie;
|
||||
|
||||
sub new {
|
||||
my ($class, $params) = @_;
|
||||
my $self = fields::new($class);
|
||||
|
||||
$params ||= {};
|
||||
$params->{Login} ||= Bugzilla->params->{'user_info_class'} . ',Cookie';
|
||||
$params->{Verify} ||= Bugzilla->params->{'user_verify_class'};
|
||||
|
||||
$self->{_info_getter} = new Bugzilla::Auth::Login::Stack($params->{Login});
|
||||
$self->{_verifier} = new Bugzilla::Auth::Verify::Stack($params->{Verify});
|
||||
# If we ever have any other login persistence methods besides cookies,
|
||||
# this could become more configurable.
|
||||
$self->{_persister} = new Bugzilla::Auth::Persist::Cookie();
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub login {
|
||||
my ($self, $type) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Get login info from the cookie, form, environment variables, etc.
|
||||
my $login_info = $self->{_info_getter}->get_login_info();
|
||||
|
||||
if ($login_info->{failure}) {
|
||||
return $self->_handle_login_result($login_info, $type);
|
||||
}
|
||||
|
||||
# Now verify his username and password against the DB, LDAP, etc.
|
||||
if ($self->{_info_getter}->{successful}->requires_verification) {
|
||||
$login_info = $self->{_verifier}->check_credentials($login_info);
|
||||
if ($login_info->{failure}) {
|
||||
return $self->_handle_login_result($login_info, $type);
|
||||
}
|
||||
$login_info =
|
||||
$self->{_verifier}->{successful}->create_or_update_user($login_info);
|
||||
}
|
||||
else {
|
||||
$login_info = $self->{_verifier}->create_or_update_user($login_info);
|
||||
}
|
||||
|
||||
if ($login_info->{failure}) {
|
||||
return $self->_handle_login_result($login_info, $type);
|
||||
}
|
||||
|
||||
# Make sure the user isn't disabled.
|
||||
my $user = $login_info->{user};
|
||||
if ($user->disabledtext) {
|
||||
return $self->_handle_login_result({ failure => AUTH_DISABLED,
|
||||
user => $user }, $type);
|
||||
}
|
||||
$user->set_authorizer($self);
|
||||
|
||||
return $self->_handle_login_result($login_info, $type);
|
||||
}
|
||||
|
||||
sub can_change_password {
|
||||
my ($self) = @_;
|
||||
my $verifier = $self->{_verifier}->{successful};
|
||||
$verifier ||= $self->{_verifier};
|
||||
my $getter = $self->{_info_getter}->{successful};
|
||||
$getter = $self->{_info_getter}
|
||||
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
|
||||
return $verifier->can_change_password &&
|
||||
$getter->user_can_create_account;
|
||||
}
|
||||
|
||||
sub can_login {
|
||||
my ($self) = @_;
|
||||
my $getter = $self->{_info_getter}->{successful};
|
||||
$getter = $self->{_info_getter}
|
||||
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
|
||||
return $getter->can_login;
|
||||
}
|
||||
|
||||
sub can_logout {
|
||||
my ($self) = @_;
|
||||
my $getter = $self->{_info_getter}->{successful};
|
||||
# If there's no successful getter, we're not logged in, so of
|
||||
# course we can't log out!
|
||||
return 0 unless $getter;
|
||||
return $getter->can_logout;
|
||||
}
|
||||
|
||||
sub user_can_create_account {
|
||||
my ($self) = @_;
|
||||
my $verifier = $self->{_verifier}->{successful};
|
||||
$verifier ||= $self->{_verifier};
|
||||
my $getter = $self->{_info_getter}->{successful};
|
||||
$getter = $self->{_info_getter}
|
||||
if (!$getter || $getter->isa('Bugzilla::Auth::Login::Cookie'));
|
||||
return $verifier->user_can_create_account
|
||||
&& $getter->user_can_create_account;
|
||||
}
|
||||
|
||||
sub can_change_email {
|
||||
return $_[0]->user_can_create_account;
|
||||
}
|
||||
|
||||
sub _handle_login_result {
|
||||
my ($self, $result, $login_type) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $user = $result->{user};
|
||||
my $fail_code = $result->{failure};
|
||||
|
||||
if (!$fail_code) {
|
||||
if ($self->{_info_getter}->{successful}->requires_persistence) {
|
||||
$self->{_persister}->persist_login($user);
|
||||
}
|
||||
}
|
||||
elsif ($fail_code == AUTH_ERROR) {
|
||||
ThrowCodeError($result->{error}, $result->{details});
|
||||
}
|
||||
elsif ($fail_code == AUTH_NODATA) {
|
||||
$self->{_info_getter}->fail_nodata($self)
|
||||
if $login_type == LOGIN_REQUIRED;
|
||||
|
||||
# If we're not LOGIN_REQUIRED, 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 or $fail_code == AUTH_NO_SUCH_USER) {
|
||||
ThrowUserError("invalid_username_or_password");
|
||||
}
|
||||
# The account may be disabled
|
||||
elsif ($fail_code == AUTH_DISABLED) {
|
||||
$self->{_persister}->logout();
|
||||
# XXX This is NOT a good way to do this, architecturally.
|
||||
$self->{_persister}->clear_browser_cookies();
|
||||
# and throw a user error
|
||||
ThrowUserError("account_disabled",
|
||||
{'disabled_reason' => $result->{user}->disabledtext});
|
||||
}
|
||||
# If we get here, then we've run out of options, which shouldn't happen.
|
||||
else {
|
||||
ThrowCodeError("authres_unhandled", { value => $fail_code });
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Auth - An object that authenticates the login credentials for
|
||||
a user.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Handles authentication for Bugzilla users.
|
||||
|
||||
Authentication from Bugzilla involves two sets of modules. One set is
|
||||
used to obtain the username/password (from CGI, email, etc), and the
|
||||
other set uses this data to authenticate against the datasource
|
||||
(the Bugzilla DB, LDAP, PAM, etc.).
|
||||
|
||||
Modules for obtaining the username/password are subclasses of
|
||||
L<Bugzilla::Auth::Login>, and modules for authenticating are subclasses
|
||||
of L<Bugzilla::Auth::Verify>.
|
||||
|
||||
=head1 AUTHENTICATION ERROR CODES
|
||||
|
||||
Whenever a method in the C<Bugzilla::Auth> family fails in some way,
|
||||
it will return a hashref containing at least a single key called C<failure>.
|
||||
C<failure> will point to an integer error code, and depending on the error
|
||||
code the hashref may contain more data.
|
||||
|
||||
The error codes are explained here below.
|
||||
|
||||
=head2 C<AUTH_NODATA>
|
||||
|
||||
Insufficient login data was provided by the user. This may happen in several
|
||||
cases, such as cookie authentication when the cookie is not present.
|
||||
|
||||
=head2 C<AUTH_ERROR>
|
||||
|
||||
An error occurred when trying to use the login mechanism.
|
||||
|
||||
The hashref will also contain an C<error> element, which is the name
|
||||
of an error from C<template/en/default/global/code-error.html> --
|
||||
the same type of error that would be thrown by
|
||||
L<Bugzilla::Error::ThrowCodeError>.
|
||||
|
||||
The hashref *may* contain an element called C<details>, which is a hashref
|
||||
that should be passed to L<Bugzilla::Error::ThrowCodeError> as the
|
||||
various fields to be used in the error message.
|
||||
|
||||
=head2 C<AUTH_LOGINFAILED>
|
||||
|
||||
An incorrect username or password was given.
|
||||
|
||||
=head2 C<AUTH_NO_SUCH_USER>
|
||||
|
||||
This is an optional more-specific version of C<AUTH_LOGINFAILED>.
|
||||
Modules should throw this error when they discover that the
|
||||
requested user account actually does not exist, according to them.
|
||||
|
||||
That is, for example, L<Bugzilla::Auth::Verify::LDAP> would throw
|
||||
this if the user didn't exist in LDAP.
|
||||
|
||||
The difference between C<AUTH_NO_SUCH_USER> and C<AUTH_LOGINFAILED>
|
||||
should never be communicated to the user, for security reasons.
|
||||
|
||||
=head2 C<AUTH_DISABLED>
|
||||
|
||||
The user successfully logged in, but their account has been disabled.
|
||||
Usually this is throw only by C<Bugzilla::Auth::login>.
|
||||
|
||||
=head1 LOGIN TYPES
|
||||
|
||||
The C<login> function (below) can do different types of login, depending
|
||||
on what constant you pass into it:
|
||||
|
||||
=head2 C<LOGIN_OPTIONAL>
|
||||
|
||||
A login is never required to access this data. Attempting to login is
|
||||
still useful, because this allows the page to be personalised. Note that
|
||||
an incorrect login will still trigger an error, even though the lack of
|
||||
a login will be OK.
|
||||
|
||||
=head2 C<LOGIN_NORMAL>
|
||||
|
||||
A login may or may not be required, depending on the setting of the
|
||||
I<requirelogin> parameter. This is the default if you don't specify a
|
||||
type.
|
||||
|
||||
=head2 C<LOGIN_REQUIRED>
|
||||
|
||||
A login is always required to access this data.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
These are methods that can be called on a C<Bugzilla::Auth> object
|
||||
itself.
|
||||
|
||||
=head2 Login
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<login($type)>
|
||||
|
||||
Description: Logs a user in. For more details on how this works
|
||||
internally, see the section entitled "STRUCTURE."
|
||||
Params: $type - One of the Login Types from above.
|
||||
Returns: An authenticated C<Bugzilla::User>. Or, if the type was
|
||||
not C<LOGIN_REQUIRED>, then we return an
|
||||
empty C<Bugzilla::User> if no login data was passed in.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Info Methods
|
||||
|
||||
These are methods that give information about the Bugzilla::Auth object.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<can_change_password>
|
||||
|
||||
Description: Tells you whether or not the current login system allows
|
||||
changing passwords.
|
||||
Params: None
|
||||
Returns: C<true> if users and administrators should be allowed to
|
||||
change passwords, C<false> otherwise.
|
||||
|
||||
=item C<can_login>
|
||||
|
||||
Description: Tells you whether or not the current login system allows
|
||||
users to log in through the web interface.
|
||||
Params: None
|
||||
Returns: C<true> if users can log in through the web interface,
|
||||
C<false> otherwise.
|
||||
|
||||
=item C<can_logout>
|
||||
|
||||
Description: Tells you whether or not the current login system allows
|
||||
users to log themselves out.
|
||||
Params: None
|
||||
Returns: C<true> if users can log themselves out, C<false> otherwise.
|
||||
If a user isn't logged in, we always return C<false>.
|
||||
|
||||
=item C<user_can_create_account>
|
||||
|
||||
Description: Tells you whether or not users are allowed to manually create
|
||||
their own accounts, based on the current login system in use.
|
||||
Note that this doesn't check the C<createemailregexp>
|
||||
parameter--you have to do that by yourself in your code.
|
||||
Params: None
|
||||
Returns: C<true> if users are allowed to create new Bugzilla accounts,
|
||||
C<false> otherwise.
|
||||
|
||||
=item C<can_change_email>
|
||||
|
||||
Description: Whether or not the current login system allows users to
|
||||
change their own email address.
|
||||
Params: None
|
||||
Returns: C<true> if users can change their own email address,
|
||||
C<false> otherwise.
|
||||
|
||||
=back
|
||||
|
||||
=head1 STRUCTURE
|
||||
|
||||
This section is mostly interesting to developers who want to implement
|
||||
a new authentication type. It describes the general structure of the
|
||||
Bugzilla::Auth family, and how the C<login> function works.
|
||||
|
||||
A C<Bugzilla::Auth> object is essentially a collection of a few other
|
||||
objects: the "Info Getter," the "Verifier," and the "Persistence
|
||||
Mechanism."
|
||||
|
||||
They are used inside the C<login> function in the following order:
|
||||
|
||||
=head2 The Info Getter
|
||||
|
||||
This is a C<Bugzilla::Auth::Login> object. Basically, it gets the
|
||||
username and password from the user, somehow. Or, it just gets enough
|
||||
information to uniquely identify a user, and passes that on down the line.
|
||||
(For example, a C<user_id> is enough to uniquely identify a user,
|
||||
even without a username and password.)
|
||||
|
||||
Some Info Getters don't require any verification. For example, if we got
|
||||
the C<user_id> from a Cookie, we don't need to check the username and
|
||||
password.
|
||||
|
||||
If an Info Getter returns only a C<user_id> and no username/password,
|
||||
then it MUST NOT require verification. If an Info Getter requires
|
||||
verfication, then it MUST return at least a C<username>.
|
||||
|
||||
=head2 The Verifier
|
||||
|
||||
This verifies that the username and password are valid.
|
||||
|
||||
It's possible that some methods of verification don't require a password.
|
||||
|
||||
=head2 The Persistence Mechanism
|
||||
|
||||
This makes it so that the user doesn't have to log in on every page.
|
||||
Normally this object just sends a cookie to the user's web browser,
|
||||
as that's the most common method of "login persistence."
|
||||
|
||||
=head2 Other Things We Do
|
||||
|
||||
After we verify the username and password, sometimes we automatically
|
||||
create an account in the Bugzilla database, for certain authentication
|
||||
types. We use the "Account Source" to get data about the user, and
|
||||
create them in the database. (Or, if their data has changed since the
|
||||
last time they logged in, their data gets updated.)
|
||||
|
||||
=head2 The C<$login_data> Hash
|
||||
|
||||
All of the C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify>
|
||||
methods take an argument called C<$login_data>. This is basically
|
||||
a hash that becomes more and more populated as we go through the
|
||||
C<login> function.
|
||||
|
||||
All C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify> methods
|
||||
also *return* the C<$login_data> structure, when they succeed. They
|
||||
may have added new data to it.
|
||||
|
||||
For all C<Bugzilla::Auth::Login> and C<Bugzilla::Auth::Verify> methods,
|
||||
the rule is "you must return the same hashref you were passed in." You can
|
||||
modify the hashref all you want, but you can't create a new one. The only
|
||||
time you can return a new one is if you're returning some error code
|
||||
instead of the C<$login_data> structure.
|
||||
|
||||
Each C<Bugzilla::Auth::Login> or C<Bugzilla::Auth::Verify> method
|
||||
explains in its documentation which C<$login_data> elements are
|
||||
required by it, and which are set by it.
|
||||
|
||||
Here are all of the elements that *may* be in C<$login_data>:
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<user_id>
|
||||
|
||||
A Bugzilla C<user_id> that uniquely identifies a user.
|
||||
|
||||
=item C<username>
|
||||
|
||||
The username that was provided by the user.
|
||||
|
||||
=item C<bz_username>
|
||||
|
||||
The username of this user inside of Bugzilla. Sometimes this differs from
|
||||
C<username>.
|
||||
|
||||
=item C<password>
|
||||
|
||||
The password provided by the user.
|
||||
|
||||
=item C<realname>
|
||||
|
||||
The real name of the user.
|
||||
|
||||
=item C<extern_id>
|
||||
|
||||
Some string that uniquely identifies the user in an external account
|
||||
source. If this C<extern_id> already exists in the database with
|
||||
a different username, the username will be *changed* to be the
|
||||
username specified in this C<$login_data>.
|
||||
|
||||
That is, let's my extern_id is C<mkanat>. I already have an account
|
||||
in Bugzilla with the username of C<mkanat@foo.com>. But this time,
|
||||
when I log in, I have an extern_id of C<mkanat> and a C<username>
|
||||
of C<mkanat@bar.org>. So now, Bugzilla will automatically change my
|
||||
username to C<mkanat@bar.org> instead of C<mkanat@foo.com>.
|
||||
|
||||
=item C<user>
|
||||
|
||||
A L<Bugzilla::User> object representing the authenticated user.
|
||||
Note that C<Bugzilla::Auth::login> may modify this object at various points.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Login;
|
||||
|
||||
use strict;
|
||||
use fields qw();
|
||||
|
||||
# Determines whether or not a user can logout. It's really a subroutine,
|
||||
# but we implement it here as a constant. Override it in subclasses if
|
||||
# that particular type of login method cannot log out.
|
||||
use constant can_logout => 1;
|
||||
use constant can_login => 1;
|
||||
use constant requires_persistence => 1;
|
||||
use constant requires_verification => 1;
|
||||
use constant user_can_create_account => 0;
|
||||
|
||||
sub new {
|
||||
my ($class) = @_;
|
||||
my $self = fields::new($class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Auth::Login - Gets username/password data from the user.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla::Auth::Login is used to get information that uniquely identifies
|
||||
a user and allows us to authorize their Bugzilla access.
|
||||
|
||||
It is mostly an abstract class, requiring subclasses to implement
|
||||
most methods.
|
||||
|
||||
Note that callers outside of the C<Bugzilla::Auth> package should never
|
||||
create this object directly. Just create a C<Bugzilla::Auth> object
|
||||
and call C<login> on it.
|
||||
|
||||
=head1 LOGIN METHODS
|
||||
|
||||
These are methods that have to do with getting the actual login data
|
||||
from the user or handling a login somehow.
|
||||
|
||||
These methods are abstract -- they MUST be implemented by a subclass.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<get_login_info()>
|
||||
|
||||
Description: Gets a username/password from the user, or some other
|
||||
information that uniquely identifies them.
|
||||
Params: None
|
||||
Returns: A C<$login_data> hashref. (See L<Bugzilla::Auth> for details.)
|
||||
The hashref MUST contain: C<user_id> *or* C<username>
|
||||
If this is a login method that requires verification,
|
||||
the hashref MUST contain C<password>.
|
||||
The hashref MAY contain C<realname> and C<extern_id>.
|
||||
|
||||
=item C<fail_nodata()>
|
||||
|
||||
Description: This function is called when Bugzilla doesn't get
|
||||
a username/password and the login type is C<LOGIN_REQUIRED>
|
||||
(See L<Bugzilla::Auth> for a description of C<LOGIN_REQUIRED>).
|
||||
That is, this handles C<AUTH_NODATA> in that situation.
|
||||
|
||||
This function MUST stop CGI execution when it is complete.
|
||||
That is, it must call C<exit> or C<ThrowUserError> or some
|
||||
such thing.
|
||||
Params: None
|
||||
Returns: Never Returns.
|
||||
|
||||
=back
|
||||
|
||||
=head1 INFO METHODS
|
||||
|
||||
These are methods that describe the capabilities of this
|
||||
C<Bugzilla::Auth::Login> object. These are all no-parameter
|
||||
methods that return either C<true> or C<false>.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<can_logout>
|
||||
|
||||
Whether or not users can log out if they logged in using this
|
||||
object. Defaults to C<true>.
|
||||
|
||||
=item C<can_login>
|
||||
|
||||
Whether or not users can log in through the web interface using
|
||||
this object. Defaults to C<true>.
|
||||
|
||||
=item C<requires_persistence>
|
||||
|
||||
Whether or not we should send the user a cookie if they logged in with
|
||||
this method. Defaults to C<true>.
|
||||
|
||||
=item C<requires_verification>
|
||||
|
||||
Whether or not we should check the username/password that we
|
||||
got from this login method. Defaults to C<true>.
|
||||
|
||||
=item C<user_can_create_account>
|
||||
|
||||
Whether or not users can create accounts, if this login method is
|
||||
currently being used by the system. Defaults to C<false>.
|
||||
|
||||
=back
|
||||
@@ -1,86 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Dave Miller <justdave@syndicomm.com>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Christian Reis <kiko@async.com.br>
|
||||
# Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Login::CGI;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Login);
|
||||
use constant user_can_create_account => 1;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::WebService::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
sub get_login_info {
|
||||
my ($self) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
|
||||
my $username = trim($cgi->param("Bugzilla_login"));
|
||||
my $password = $cgi->param("Bugzilla_password");
|
||||
|
||||
$cgi->delete('Bugzilla_login', 'Bugzilla_password');
|
||||
|
||||
if (!defined $username || !defined $password) {
|
||||
return { failure => AUTH_NODATA };
|
||||
}
|
||||
|
||||
return { username => $username, password => $password };
|
||||
}
|
||||
|
||||
sub fail_nodata {
|
||||
my ($self) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
if (Bugzilla->error_mode == Bugzilla::Constants::ERROR_MODE_DIE_SOAP_FAULT) {
|
||||
die SOAP::Fault
|
||||
->faultcode(ERROR_AUTH_NODATA)
|
||||
->faultstring('Login Required');
|
||||
}
|
||||
|
||||
# If system is not configured to never require SSL connections
|
||||
# we want to always redirect to SSL since passing usernames and
|
||||
# passwords over an unprotected connection is a bad idea. If we
|
||||
# get here then a login form will be provided to the user so we
|
||||
# want this to be protected if possible.
|
||||
if ($cgi->protocol ne 'https' && Bugzilla->params->{'sslbase'} ne ''
|
||||
&& Bugzilla->params->{'ssl'} ne 'never')
|
||||
{
|
||||
$cgi->require_https(Bugzilla->params->{'sslbase'});
|
||||
}
|
||||
|
||||
print $cgi->header();
|
||||
$template->process("account/auth/login.html.tmpl",
|
||||
{ 'target' => $cgi->url(-relative=>1) })
|
||||
|| ThrowTemplateError($template->error());
|
||||
exit;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,96 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Login::Cookie;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Login);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use List::Util qw(first);
|
||||
|
||||
use constant requires_persistence => 0;
|
||||
use constant requires_verification => 0;
|
||||
use constant can_login => 0;
|
||||
|
||||
# Note that Cookie never consults the Verifier, it always assumes
|
||||
# it has a valid DB account or it fails.
|
||||
sub get_login_info {
|
||||
my ($self) = @_;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $ip_addr = $cgi->remote_addr();
|
||||
my $net_addr = get_netaddr($ip_addr);
|
||||
my $login_cookie = $cgi->cookie("Bugzilla_logincookie");
|
||||
my $user_id = $cgi->cookie("Bugzilla_login");
|
||||
|
||||
# If cookies cannot be found, this could mean that they haven't
|
||||
# been made available yet. In this case, look at Bugzilla_cookie_list.
|
||||
unless ($login_cookie) {
|
||||
my $cookie = first {$_->name eq 'Bugzilla_logincookie'}
|
||||
@{$cgi->{'Bugzilla_cookie_list'}};
|
||||
$login_cookie = $cookie->value if $cookie;
|
||||
}
|
||||
unless ($user_id) {
|
||||
my $cookie = first {$_->name eq 'Bugzilla_login'}
|
||||
@{$cgi->{'Bugzilla_cookie_list'}};
|
||||
$user_id = $cookie->value if $cookie;
|
||||
}
|
||||
|
||||
if ($login_cookie && $user_id) {
|
||||
# Anything goes for these params - they're just strings which
|
||||
# we're going to verify against the db
|
||||
trick_taint($ip_addr);
|
||||
trick_taint($login_cookie);
|
||||
detaint_natural($user_id);
|
||||
|
||||
my $query = "SELECT userid
|
||||
FROM logincookies
|
||||
WHERE logincookies.cookie = ?
|
||||
AND logincookies.userid = ?
|
||||
AND (logincookies.ipaddr = ?";
|
||||
|
||||
# If we have a network block that's allowed to use this cookie,
|
||||
# as opposed to just a single IP.
|
||||
my @params = ($login_cookie, $user_id, $ip_addr);
|
||||
if (defined $net_addr) {
|
||||
trick_taint($net_addr);
|
||||
$query .= " OR logincookies.ipaddr = ?";
|
||||
push(@params, $net_addr);
|
||||
}
|
||||
$query .= ")";
|
||||
|
||||
# If the cookie is valid, return a valid username.
|
||||
if ($dbh->selectrow_array($query, undef, @params)) {
|
||||
# If we logged in successfully, then update the lastused
|
||||
# time on the login cookie
|
||||
$dbh->do("UPDATE logincookies SET lastused = NOW()
|
||||
WHERE cookie = ?", undef, $login_cookie);
|
||||
return { user_id => $user_id };
|
||||
}
|
||||
}
|
||||
|
||||
# Either the he cookie is invalid, or we got no cookie. We don't want
|
||||
# to ever return AUTH_LOGINFAILED, because we don't want Bugzilla to
|
||||
# actually throw an error when it gets a bad cookie. It should just
|
||||
# look like there was no cookie to begin with.
|
||||
return { failure => AUTH_NODATA };
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,52 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Erik Stambaugh <erik@dasbistro.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Login::Env;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Login);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use constant can_logout => 0;
|
||||
use constant can_login => 0;
|
||||
use constant requires_verification => 0;
|
||||
|
||||
sub get_login_info {
|
||||
my ($self) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $env_id = $ENV{Bugzilla->params->{"auth_env_id"}} || '';
|
||||
my $env_email = $ENV{Bugzilla->params->{"auth_env_email"}} || '';
|
||||
my $env_realname = $ENV{Bugzilla->params->{"auth_env_realname"}} || '';
|
||||
|
||||
return { failure => AUTH_NODATA } if !$env_email;
|
||||
|
||||
return { username => $env_email, extern_id => $env_id,
|
||||
realname => $env_realname };
|
||||
}
|
||||
|
||||
sub fail_nodata {
|
||||
ThrowCodeError('env_no_email');
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,95 +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
|
||||
);
|
||||
use Hash::Util qw(lock_keys);
|
||||
use Bugzilla::Hook;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = $class->SUPER::new(@_);
|
||||
my $list = shift;
|
||||
my %methods = map { $_ => "Bugzilla/Auth/Login/$_.pm" } split(',', $list);
|
||||
lock_keys(%methods);
|
||||
Bugzilla::Hook::process('auth-login_methods', { modules => \%methods });
|
||||
|
||||
$self->{_stack} = [];
|
||||
foreach my $login_method (split(',', $list)) {
|
||||
my $module = $methods{$login_method};
|
||||
require $module;
|
||||
$module =~ s|/|::|g;
|
||||
$module =~ s/.pm$//;
|
||||
push(@{$self->{_stack}}, $module->new(@_));
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub get_login_info {
|
||||
my $self = shift;
|
||||
my $result;
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
$result = $object->get_login_info(@_);
|
||||
$self->{successful} = $object;
|
||||
last if !$result->{failure};
|
||||
# So that if none of them succeed, it's undef.
|
||||
$self->{successful} = undef;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub fail_nodata {
|
||||
my $self = shift;
|
||||
# We fail from the bottom of the stack.
|
||||
my @reverse_stack = reverse @{$self->{_stack}};
|
||||
foreach my $object (@reverse_stack) {
|
||||
# We pick the first object that actually has the method
|
||||
# implemented.
|
||||
if ($object->can('fail_nodata')) {
|
||||
$object->fail_nodata(@_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub can_login {
|
||||
my ($self) = @_;
|
||||
# We return true if any method can log in.
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
return 1 if $object->can_login;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub user_can_create_account {
|
||||
my ($self) = @_;
|
||||
# We return true if any method allows users to create accounts.
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
return 1 if $object->user_can_create_account;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,167 +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);
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
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);
|
||||
|
||||
# Issuing a new cookie is a good time to clean up the old
|
||||
# cookies.
|
||||
$dbh->do("DELETE FROM logincookies WHERE lastused < LOCALTIMESTAMP(0) - "
|
||||
. $dbh->sql_interval(MAX_LOGINCOOKIE_AGE, 'DAY'));
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
# Prevent JavaScript from accessing login cookies.
|
||||
my %cookieargs = ('-httponly' => 1);
|
||||
|
||||
# Remember cookie only if admin has told so
|
||||
# or admin didn't forbid it and user told to remember.
|
||||
if ( Bugzilla->params->{'rememberlogin'} eq 'on' ||
|
||||
(Bugzilla->params->{'rememberlogin'} ne 'off' &&
|
||||
$cgi->param('Bugzilla_remember') &&
|
||||
$cgi->param('Bugzilla_remember') eq 'on') )
|
||||
{
|
||||
# Not a session cookie, so set an infinite expiry
|
||||
$cookieargs{'-expires'} = 'Fri, 01-Jan-2038 00:00:00 GMT';
|
||||
}
|
||||
if (Bugzilla->params->{'ssl'} ne 'never'
|
||||
&& Bugzilla->params->{'sslbase'} ne '')
|
||||
{
|
||||
# Bugzilla->login will automatically redirect to https://,
|
||||
# so it's safe to turn on the 'secure' bit.
|
||||
$cookieargs{'-secure'} = 1;
|
||||
}
|
||||
|
||||
$cgi->send_cookie(-name => 'Bugzilla_login',
|
||||
-value => $user->id,
|
||||
%cookieargs);
|
||||
$cgi->send_cookie(-name => 'Bugzilla_logincookie',
|
||||
-value => $login_cookie,
|
||||
%cookieargs);
|
||||
}
|
||||
|
||||
sub logout {
|
||||
my ($self, $param) = @_;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $cgi = Bugzilla->cgi;
|
||||
$param = {} unless $param;
|
||||
my $user = $param->{user} || Bugzilla->user;
|
||||
my $type = $param->{type} || LOGOUT_ALL;
|
||||
|
||||
if ($type == LOGOUT_ALL) {
|
||||
$dbh->do("DELETE FROM logincookies WHERE userid = ?",
|
||||
undef, $user->id);
|
||||
return;
|
||||
}
|
||||
|
||||
# The LOGOUT_*_CURRENT options require the current login cookie.
|
||||
# If a new cookie has been issued during this run, that's the current one.
|
||||
# If not, it's the one we've received.
|
||||
my $cookie = first {$_->name eq 'Bugzilla_logincookie'}
|
||||
@{$cgi->{'Bugzilla_cookie_list'}};
|
||||
my $login_cookie;
|
||||
if ($cookie) {
|
||||
$login_cookie = $cookie->value;
|
||||
}
|
||||
else {
|
||||
$login_cookie = $cgi->cookie("Bugzilla_logincookie");
|
||||
}
|
||||
trick_taint($login_cookie);
|
||||
|
||||
# These queries use both the cookie ID and the user ID as keys. Even
|
||||
# though we know the userid must match, we still check it in the SQL
|
||||
# as a sanity check, since there is no locking here, and if the user
|
||||
# logged out from two machines simultaneously, while someone else
|
||||
# logged in and got the same cookie, we could be logging the other
|
||||
# user out here. Yes, this is very very very unlikely, but why take
|
||||
# chances? - bbaetz
|
||||
if ($type == LOGOUT_KEEP_CURRENT) {
|
||||
$dbh->do("DELETE FROM logincookies WHERE cookie != ? AND userid = ?",
|
||||
undef, $login_cookie, $user->id);
|
||||
} elsif ($type == LOGOUT_CURRENT) {
|
||||
$dbh->do("DELETE FROM logincookies WHERE cookie = ? AND userid = ?",
|
||||
undef, $login_cookie, $user->id);
|
||||
} else {
|
||||
die("Invalid type $type supplied to logout()");
|
||||
}
|
||||
|
||||
if ($type != LOGOUT_KEEP_CURRENT) {
|
||||
clear_browser_cookies();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sub clear_browser_cookies {
|
||||
my $cgi = Bugzilla->cgi;
|
||||
$cgi->remove_cookie('Bugzilla_login');
|
||||
$cgi->remove_cookie('Bugzilla_logincookie');
|
||||
$cgi->remove_cookie('sudo');
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,235 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Verify;
|
||||
|
||||
use strict;
|
||||
use fields qw();
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use constant user_can_create_account => 1;
|
||||
|
||||
sub new {
|
||||
my ($class, $login_type) = @_;
|
||||
my $self = fields::new($class);
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub can_change_password {
|
||||
return $_[0]->can('change_password');
|
||||
}
|
||||
|
||||
sub create_or_update_user {
|
||||
my ($self, $params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $extern_id = $params->{extern_id};
|
||||
my $username = $params->{bz_username} || $params->{username};
|
||||
my $password = $params->{password} || '*';
|
||||
my $real_name = $params->{realname} || '';
|
||||
my $user_id = $params->{user_id};
|
||||
|
||||
# A passed-in user_id always overrides anything else, for determining
|
||||
# what account we should return.
|
||||
if (!$user_id) {
|
||||
my $username_user_id = login_to_id($username || '');
|
||||
my $extern_user_id;
|
||||
if ($extern_id) {
|
||||
trick_taint($extern_id);
|
||||
$extern_user_id = $dbh->selectrow_array('SELECT userid
|
||||
FROM profiles WHERE extern_id = ?', undef, $extern_id);
|
||||
}
|
||||
|
||||
# If we have both a valid extern_id and a valid username, and they are
|
||||
# not the same id, then we have a conflict.
|
||||
if ($username_user_id && $extern_user_id
|
||||
&& $username_user_id ne $extern_user_id)
|
||||
{
|
||||
my $extern_name = Bugzilla::User->new($extern_user_id)->login;
|
||||
return { failure => AUTH_ERROR, error => "extern_id_conflict",
|
||||
details => {extern_id => $extern_id,
|
||||
extern_user => $extern_name,
|
||||
username => $username} };
|
||||
}
|
||||
|
||||
# If we have a valid username, but no valid id,
|
||||
# then we have to create the user. This happens when we're
|
||||
# passed only a username, and that username doesn't exist already.
|
||||
if ($username && !$username_user_id && !$extern_user_id) {
|
||||
validate_email_syntax($username)
|
||||
|| return { failure => AUTH_ERROR,
|
||||
error => 'auth_invalid_email',
|
||||
details => {addr => $username} };
|
||||
# Usually we'd call validate_password, but external authentication
|
||||
# systems might follow different standards than ours. So in this
|
||||
# place here, we call trick_taint without checks.
|
||||
trick_taint($password);
|
||||
|
||||
# XXX Theoretically this could fail with an error, but the fix for
|
||||
# that is too involved to be done right now.
|
||||
my $user = Bugzilla::User->create({
|
||||
login_name => $username,
|
||||
cryptpassword => $password,
|
||||
realname => $real_name});
|
||||
$username_user_id = $user->id;
|
||||
}
|
||||
|
||||
# If we have a valid username id and an extern_id, but no valid
|
||||
# extern_user_id, then we have to set the user's extern_id.
|
||||
if ($extern_id && $username_user_id && !$extern_user_id) {
|
||||
$dbh->do('UPDATE profiles SET extern_id = ? WHERE userid = ?',
|
||||
undef, $extern_id, $username_user_id);
|
||||
}
|
||||
|
||||
# Finally, at this point, one of these will give us a valid user id.
|
||||
$user_id = $extern_user_id || $username_user_id;
|
||||
}
|
||||
|
||||
# If we still don't have a valid user_id, then we weren't passed
|
||||
# enough information in $params, and we should die right here.
|
||||
ThrowCodeError('bad_arg', {argument => 'params', function =>
|
||||
'Bugzilla::Auth::Verify::create_or_update_user'})
|
||||
unless $user_id;
|
||||
|
||||
my $user = new Bugzilla::User($user_id);
|
||||
|
||||
# Now that we have a valid User, we need to see if any data has to be
|
||||
# updated.
|
||||
if ($username && lc($user->login) ne lc($username)) {
|
||||
validate_email_syntax($username)
|
||||
|| return { failure => AUTH_ERROR, error => 'auth_invalid_email',
|
||||
details => {addr => $username} };
|
||||
$user->set_login($username);
|
||||
}
|
||||
if ($real_name && $user->name ne $real_name) {
|
||||
# $real_name is more than likely tainted, but we only use it
|
||||
# in a placeholder and we never use it after this.
|
||||
trick_taint($real_name);
|
||||
$user->set_name($real_name);
|
||||
}
|
||||
$user->update();
|
||||
|
||||
return { user => $user };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Auth::Verify - An object that verifies usernames and passwords.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla::Auth::Verify provides the "Verifier" part of the Bugzilla
|
||||
login process. (For details, see the "STRUCTURE" section of
|
||||
L<Bugzilla::Auth>.)
|
||||
|
||||
It is mostly an abstract class, requiring subclasses to implement
|
||||
most methods.
|
||||
|
||||
Note that callers outside of the C<Bugzilla::Auth> package should never
|
||||
create this object directly. Just create a C<Bugzilla::Auth> object
|
||||
and call C<login> on it.
|
||||
|
||||
=head1 VERIFICATION METHODS
|
||||
|
||||
These are the methods that have to do with the actual verification.
|
||||
|
||||
Subclasses MUST implement these methods.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<check_credentials($login_data)>
|
||||
|
||||
Description: Checks whether or not a username is valid.
|
||||
Params: $login_data - A C<$login_data> hashref, as described in
|
||||
L<Bugzilla::Auth>.
|
||||
This C<$login_data> hashref MUST contain
|
||||
C<username>, and SHOULD also contain
|
||||
C<password>.
|
||||
Returns: A C<$login_data> hashref with C<bz_username> set. This
|
||||
method may also set C<realname>. It must avoid changing
|
||||
anything that is already set.
|
||||
|
||||
=back
|
||||
|
||||
=head1 MODIFICATION METHODS
|
||||
|
||||
These are methods that change data in the actual authentication backend.
|
||||
|
||||
These methods are optional, they do not have to be implemented by
|
||||
subclasses.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<create_or_update_user($login_data)>
|
||||
|
||||
Description: Automatically creates a user account in the database
|
||||
if it doesn't already exist, or updates the account
|
||||
data if C<$login_data> contains newer information.
|
||||
|
||||
Params: $login_data - A C<$login_data> hashref, as described in
|
||||
L<Bugzilla::Auth>.
|
||||
This C<$login_data> hashref MUST contain
|
||||
either C<user_id>, C<bz_username>, or
|
||||
C<username>. If both C<username> and C<bz_username>
|
||||
are specified, C<bz_username> is used as the
|
||||
login name of the user to create in the database.
|
||||
It MAY also contain C<extern_id>, in which
|
||||
case it still MUST contain C<bz_username> or
|
||||
C<username>.
|
||||
It MAY contain C<password> and C<realname>.
|
||||
|
||||
Returns: A hashref with one element, C<user>, which is a
|
||||
L<Bugzilla::User> object. May also return a login error
|
||||
as described in L<Bugzilla::Auth>.
|
||||
|
||||
Note: This method is not abstract, it is actually implemented
|
||||
and creates accounts in the Bugzilla database. Subclasses
|
||||
should probably all call the C<Bugzilla::Auth::Verify>
|
||||
version of this function at the end of their own
|
||||
C<create_or_update_user>.
|
||||
|
||||
=item C<change_password($user, $password)>
|
||||
|
||||
Description: Modifies the user's password in the authentication backend.
|
||||
Params: $user - A L<Bugzilla::User> object representing the user
|
||||
whose password we want to change.
|
||||
$password - The user's new password.
|
||||
Returns: Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=head1 INFO METHODS
|
||||
|
||||
These are methods that describe the capabilities of this object.
|
||||
These are all no-parameter methods that return either C<true> or
|
||||
C<false>.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<user_can_create_account>
|
||||
|
||||
Whether or not users can manually create accounts in this type of
|
||||
account source. Defaults to C<true>.
|
||||
|
||||
=back
|
||||
@@ -1,88 +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 = bz_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");
|
||||
|
||||
# If their old password was using crypt() or some different hash
|
||||
# than we're using now, convert the stored password to using
|
||||
# whatever hashing system we're using now.
|
||||
my $current_algorithm = PASSWORD_DIGEST_ALGORITHM;
|
||||
if ($real_password_crypted !~ /{\Q$current_algorithm\E}$/) {
|
||||
my $new_crypted = bz_crypt($password);
|
||||
$dbh->do('UPDATE profiles SET cryptpassword = ? WHERE userid = ?',
|
||||
undef, $new_crypted, $user_id);
|
||||
}
|
||||
|
||||
return $login_data;
|
||||
}
|
||||
|
||||
sub change_password {
|
||||
my ($self, $user, $password) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $cryptpassword = bz_crypt($password);
|
||||
$dbh->do("UPDATE profiles SET cryptpassword = ? WHERE userid = ?",
|
||||
undef, $cryptpassword, $user->id);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,176 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Dave Miller <justdave@syndicomm.com>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Christian Reis <kiko@async.com.br>
|
||||
# Bradley Baetz <bbaetz@acm.org>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Auth::Verify::LDAP;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Verify);
|
||||
use fields qw(
|
||||
ldap
|
||||
);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use Net::LDAP;
|
||||
|
||||
use constant admin_can_create_account => 0;
|
||||
use constant user_can_create_account => 0;
|
||||
|
||||
sub check_credentials {
|
||||
my ($self, $params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# We need to bind anonymously to the LDAP server. This is
|
||||
# because we need to get the Distinguished Name of the user trying
|
||||
# to log in. Some servers (such as iPlanet) allow you to have unique
|
||||
# uids spread out over a subtree of an area (such as "People"), so
|
||||
# just appending the Base DN to the uid isn't sufficient to get the
|
||||
# user's DN. For servers which don't work this way, there will still
|
||||
# be no harm done.
|
||||
$self->_bind_ldap_anonymously();
|
||||
|
||||
# Now, we verify that the user exists, and get a LDAP Distinguished
|
||||
# Name for the user.
|
||||
my $username = $params->{username};
|
||||
my $dn_result = $self->ldap->search(_bz_search_params($username),
|
||||
attrs => ['dn']);
|
||||
return { failure => AUTH_ERROR, error => "ldap_search_error",
|
||||
details => {errstr => $dn_result->error, username => $username}
|
||||
} if $dn_result->code;
|
||||
|
||||
return { failure => AUTH_NO_SUCH_USER } if !$dn_result->count;
|
||||
|
||||
my $dn = $dn_result->shift_entry->dn;
|
||||
|
||||
# Check the password.
|
||||
my $pw_result = $self->ldap->bind($dn, password => $params->{password});
|
||||
return { failure => AUTH_LOGINFAILED } if $pw_result->code;
|
||||
|
||||
# And now we fill in the user's details.
|
||||
my $detail_result = $self->ldap->search(_bz_search_params($username));
|
||||
return { failure => AUTH_ERROR, error => "ldap_search_error",
|
||||
details => {errstr => $detail_result->error, username => $username}
|
||||
} if $detail_result->code;
|
||||
|
||||
my $user_entry = $detail_result->shift_entry;
|
||||
|
||||
my $mail_attr = Bugzilla->params->{"LDAPmailattribute"};
|
||||
if ($mail_attr) {
|
||||
if (!$user_entry->exists($mail_attr)) {
|
||||
return { failure => AUTH_ERROR,
|
||||
error => "ldap_cannot_retreive_attr",
|
||||
details => {attr => $mail_attr} };
|
||||
}
|
||||
|
||||
my @emails = $user_entry->get_value($mail_attr);
|
||||
|
||||
# Default to the first email address returned.
|
||||
$params->{bz_username} = $emails[0];
|
||||
|
||||
if (@emails > 1) {
|
||||
# Cycle through the adresses and check if they're Bugzilla logins.
|
||||
# Use the first one that returns a valid id.
|
||||
foreach my $email (@emails) {
|
||||
if ( login_to_id($email) ) {
|
||||
$params->{bz_username} = $email;
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
$params->{bz_username} = $username;
|
||||
}
|
||||
|
||||
$params->{realname} ||= $user_entry->get_value("displayName");
|
||||
$params->{realname} ||= $user_entry->get_value("cn");
|
||||
|
||||
$params->{extern_id} = $username;
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
sub _bz_search_params {
|
||||
my ($username) = @_;
|
||||
return (base => Bugzilla->params->{"LDAPBaseDN"},
|
||||
scope => "sub",
|
||||
filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"}
|
||||
. "=$username)"
|
||||
. Bugzilla->params->{"LDAPfilter"} . ')');
|
||||
}
|
||||
|
||||
sub _bind_ldap_anonymously {
|
||||
my ($self) = @_;
|
||||
my $bind_result;
|
||||
if (Bugzilla->params->{"LDAPbinddn"}) {
|
||||
my ($LDAPbinddn,$LDAPbindpass) =
|
||||
split(":",Bugzilla->params->{"LDAPbinddn"});
|
||||
$bind_result =
|
||||
$self->ldap->bind($LDAPbinddn, password => $LDAPbindpass);
|
||||
}
|
||||
else {
|
||||
$bind_result = $self->ldap->bind();
|
||||
}
|
||||
ThrowCodeError("ldap_bind_failed", {errstr => $bind_result->error})
|
||||
if $bind_result->code;
|
||||
}
|
||||
|
||||
# We can't just do this in new(), because we're not allowed to throw any
|
||||
# error from anywhere under Bugzilla::Auth::new -- otherwise we
|
||||
# could create a situation where the admin couldn't get to editparams
|
||||
# to fix his mistake. (Because Bugzilla->login always calls
|
||||
# Bugzilla::Auth->new, and almost every page calls Bugzilla->login.)
|
||||
sub ldap {
|
||||
my ($self) = @_;
|
||||
return $self->{ldap} if $self->{ldap};
|
||||
|
||||
my @servers = split(/[\s,]+/, Bugzilla->params->{"LDAPserver"});
|
||||
ThrowCodeError("ldap_server_not_defined") unless @servers;
|
||||
|
||||
foreach (@servers) {
|
||||
$self->{ldap} = new Net::LDAP(trim($_));
|
||||
last if $self->{ldap};
|
||||
}
|
||||
ThrowCodeError("ldap_connect_failed", { server => join(", ", @servers) })
|
||||
unless $self->{ldap};
|
||||
|
||||
# try to start TLS if needed
|
||||
if (Bugzilla->params->{"LDAPstarttls"}) {
|
||||
my $mesg = $self->{ldap}->start_tls();
|
||||
ThrowCodeError("ldap_start_tls_failed", { error => $mesg->error() })
|
||||
if $mesg->code();
|
||||
}
|
||||
|
||||
return $self->{ldap};
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,64 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Marc Schumann.
|
||||
# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::Auth::Verify::RADIUS;
|
||||
use strict;
|
||||
use base qw(Bugzilla::Auth::Verify);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use Authen::Radius;
|
||||
|
||||
use constant admin_can_create_account => 0;
|
||||
use constant user_can_create_account => 0;
|
||||
|
||||
sub check_credentials {
|
||||
my ($self, $params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $address_suffix = Bugzilla->params->{'RADIUS_email_suffix'};
|
||||
my $username = $params->{username};
|
||||
|
||||
# If we're using RADIUS_email_suffix, we may need to cut it off from
|
||||
# the login name.
|
||||
if ($address_suffix) {
|
||||
$username =~ s/\Q$address_suffix\E$//i;
|
||||
}
|
||||
|
||||
# Create RADIUS object.
|
||||
my $radius =
|
||||
new Authen::Radius(Host => Bugzilla->params->{'RADIUS_server'},
|
||||
Secret => Bugzilla->params->{'RADIUS_secret'})
|
||||
|| return { failure => AUTH_ERROR, error => 'radius_preparation_error',
|
||||
details => {errstr => Authen::Radius::strerror() } };
|
||||
|
||||
# Check the password.
|
||||
$radius->check_pwd($username, $params->{password},
|
||||
Bugzilla->params->{'RADIUS_NAS_IP'} || undef)
|
||||
|| return { failure => AUTH_LOGINFAILED };
|
||||
|
||||
# Build the user account's e-mail address.
|
||||
$params->{bz_username} = $username . $address_suffix;
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,89 +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
|
||||
);
|
||||
use Hash::Util qw(lock_keys);
|
||||
use Bugzilla::Hook;
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $list = shift;
|
||||
my $self = $class->SUPER::new(@_);
|
||||
my %methods = map { $_ => "Bugzilla/Auth/Verify/$_.pm" } split(',', $list);
|
||||
lock_keys(%methods);
|
||||
Bugzilla::Hook::process('auth-verify_methods', { modules => \%methods });
|
||||
|
||||
$self->{_stack} = [];
|
||||
foreach my $verify_method (split(',', $list)) {
|
||||
my $module = $methods{$verify_method};
|
||||
require $module;
|
||||
$module =~ s|/|::|g;
|
||||
$module =~ s/.pm$//;
|
||||
push(@{$self->{_stack}}, $module->new(@_));
|
||||
}
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub can_change_password {
|
||||
my ($self) = @_;
|
||||
# We return true if any method can change passwords.
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
return 1 if $object->can_change_password;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub check_credentials {
|
||||
my $self = shift;
|
||||
my $result;
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
$result = $object->check_credentials(@_);
|
||||
$self->{successful} = $object;
|
||||
last if !$result->{failure};
|
||||
# So that if none of them succeed, it's undef.
|
||||
$self->{successful} = undef;
|
||||
}
|
||||
# Returns the result at the bottom of the stack if they all fail.
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub create_or_update_user {
|
||||
my $self = shift;
|
||||
my $result;
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
$result = $object->create_or_update_user(@_);
|
||||
last if !$result->{failure};
|
||||
}
|
||||
# Returns the result at the bottom of the stack if they all fail.
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub user_can_create_account {
|
||||
my ($self) = @_;
|
||||
# We return true if any method allows the user to create an account.
|
||||
foreach my $object (@{$self->{_stack}}) {
|
||||
return 1 if $object->user_can_create_account;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
1;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,700 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>,
|
||||
# Bryce Nesbitt <bryce-mozilla@nextbus.com>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Alan Raetz <al_raetz@yahoo.com>
|
||||
# Jacob Steenhagen <jake@actex.net>
|
||||
# Matthew Tuck <matty@chariot.net.au>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Byron Jones <bugzilla@glob.com.au>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::BugMail;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Bug;
|
||||
use Bugzilla::Classification;
|
||||
use Bugzilla::Product;
|
||||
use Bugzilla::Component;
|
||||
use Bugzilla::Status;
|
||||
use Bugzilla::Mailer;
|
||||
|
||||
use Date::Parse;
|
||||
use Date::Format;
|
||||
|
||||
use constant FORMAT_TRIPLE => "%19s|%-28s|%-28s";
|
||||
use constant FORMAT_3_SIZE => [19,28,28];
|
||||
use constant FORMAT_DOUBLE => "%19s %-55s";
|
||||
use constant FORMAT_2_SIZE => [19,55];
|
||||
|
||||
use constant BIT_DIRECT => 1;
|
||||
use constant BIT_WATCHING => 2;
|
||||
|
||||
# We need these strings for the X-Bugzilla-Reasons header
|
||||
# Note: this hash uses "," rather than "=>" to avoid auto-quoting of the LHS.
|
||||
use constant REL_NAMES => {
|
||||
REL_ASSIGNEE , "AssignedTo",
|
||||
REL_REPORTER , "Reporter",
|
||||
REL_QA , "QAcontact",
|
||||
REL_CC , "CC",
|
||||
REL_VOTER , "Voter",
|
||||
REL_GLOBAL_WATCHER, "GlobalWatcher"
|
||||
};
|
||||
|
||||
# We use this instead of format because format doesn't deal well with
|
||||
# multi-byte languages.
|
||||
sub multiline_sprintf {
|
||||
my ($format, $args, $sizes) = @_;
|
||||
my @parts;
|
||||
my @my_sizes = @$sizes; # Copy this so we don't modify the input array.
|
||||
foreach my $string (@$args) {
|
||||
my $size = shift @my_sizes;
|
||||
my @pieces = split("\n", wrap_hard($string, $size));
|
||||
push(@parts, \@pieces);
|
||||
}
|
||||
|
||||
my $formatted;
|
||||
while (1) {
|
||||
# Get the first item of each part.
|
||||
my @line = map { shift @$_ } @parts;
|
||||
# If they're all undef, we're done.
|
||||
last if !grep { defined $_ } @line;
|
||||
# Make any single undef item into ''
|
||||
@line = map { defined $_ ? $_ : '' } @line;
|
||||
# And append a formatted line
|
||||
$formatted .= sprintf($format, @line);
|
||||
# Remove trailing spaces, or they become lots of =20's in
|
||||
# quoted-printable emails.
|
||||
$formatted =~ s/\s+$//;
|
||||
$formatted .= "\n";
|
||||
}
|
||||
return $formatted;
|
||||
}
|
||||
|
||||
sub three_columns {
|
||||
return multiline_sprintf(FORMAT_TRIPLE, \@_, FORMAT_3_SIZE);
|
||||
}
|
||||
|
||||
# This is a bit of a hack, basically keeping the old system()
|
||||
# cmd line interface. Should clean this up at some point.
|
||||
#
|
||||
# args: bug_id, and an optional hash ref which may have keys for:
|
||||
# changer, owner, qa, reporter, cc
|
||||
# Optional hash contains values of people which will be forced to those
|
||||
# roles when the email is sent.
|
||||
# All the names are email addresses, not userids
|
||||
# values are scalars, except for cc, which is a list
|
||||
# This hash usually comes from the "mailrecipients" var in a template call.
|
||||
sub Send {
|
||||
my ($id, $forced) = (@_);
|
||||
|
||||
my @headerlist;
|
||||
my %defmailhead;
|
||||
my %fielddescription;
|
||||
|
||||
my $msg = "";
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# XXX - These variables below are useless. We could use field object
|
||||
# methods directly. But we first have to implement a cache in
|
||||
# Bugzilla->get_fields to avoid querying the DB all the time.
|
||||
foreach my $field (Bugzilla->get_fields({obsolete => 0})) {
|
||||
push(@headerlist, $field->name);
|
||||
$defmailhead{$field->name} = $field->in_new_bugmail;
|
||||
$fielddescription{$field->name} = $field->description;
|
||||
}
|
||||
|
||||
my %values = %{$dbh->selectrow_hashref(
|
||||
'SELECT ' . join(',', editable_bug_fields()) . ', reporter,
|
||||
lastdiffed AS start_time, LOCALTIMESTAMP(0) AS end_time
|
||||
FROM bugs WHERE bug_id = ?',
|
||||
undef, $id)};
|
||||
|
||||
my $product = new Bugzilla::Product($values{product_id});
|
||||
$values{product} = $product->name;
|
||||
if (Bugzilla->params->{'useclassification'}) {
|
||||
$values{classification} = Bugzilla::Classification->new($product->classification_id)->name;
|
||||
}
|
||||
my $component = new Bugzilla::Component($values{component_id});
|
||||
$values{component} = $component->name;
|
||||
|
||||
my ($start, $end) = ($values{start_time}, $values{end_time});
|
||||
|
||||
# User IDs of people in various roles. More than one person can 'have' a
|
||||
# role, if the person in that role has changed, or people are watching.
|
||||
my $reporter = $values{'reporter'};
|
||||
my @assignees = ($values{'assigned_to'});
|
||||
my @qa_contacts = ($values{'qa_contact'});
|
||||
|
||||
my $cc_users = $dbh->selectall_arrayref(
|
||||
"SELECT cc.who, profiles.login_name
|
||||
FROM cc
|
||||
INNER JOIN profiles
|
||||
ON cc.who = profiles.userid
|
||||
WHERE bug_id = ?",
|
||||
undef, $id);
|
||||
|
||||
my (@ccs, @cc_login_names);
|
||||
foreach my $cc_user (@$cc_users) {
|
||||
my ($user_id, $user_login) = @$cc_user;
|
||||
push (@ccs, $user_id);
|
||||
push (@cc_login_names, $user_login);
|
||||
}
|
||||
|
||||
# Include the people passed in as being in particular roles.
|
||||
# This can include people who used to hold those roles.
|
||||
# At this point, we don't care if there are duplicates in these arrays.
|
||||
my $changer = $forced->{'changer'};
|
||||
if ($forced->{'owner'}) {
|
||||
push (@assignees, login_to_id($forced->{'owner'}, THROW_ERROR));
|
||||
}
|
||||
|
||||
if ($forced->{'qacontact'}) {
|
||||
push (@qa_contacts, login_to_id($forced->{'qacontact'}, THROW_ERROR));
|
||||
}
|
||||
|
||||
if ($forced->{'cc'}) {
|
||||
foreach my $cc (@{$forced->{'cc'}}) {
|
||||
push(@ccs, login_to_id($cc, THROW_ERROR));
|
||||
}
|
||||
}
|
||||
|
||||
# Convert to names, for later display
|
||||
$values{'changer'} = $changer;
|
||||
# If no changer is specified, then it has no name.
|
||||
if ($changer) {
|
||||
$values{'changername'} = Bugzilla::User->new({name => $changer})->name;
|
||||
}
|
||||
$values{'assigned_to'} = user_id_to_login($values{'assigned_to'});
|
||||
$values{'reporter'} = user_id_to_login($values{'reporter'});
|
||||
if ($values{'qa_contact'}) {
|
||||
$values{'qa_contact'} = user_id_to_login($values{'qa_contact'});
|
||||
}
|
||||
$values{'cc'} = join(', ', @cc_login_names);
|
||||
$values{'estimated_time'} = format_time_decimal($values{'estimated_time'});
|
||||
|
||||
if ($values{'deadline'}) {
|
||||
$values{'deadline'} = time2str("%Y-%m-%d", str2time($values{'deadline'}));
|
||||
}
|
||||
|
||||
my $dependslist = $dbh->selectcol_arrayref(
|
||||
'SELECT dependson FROM dependencies
|
||||
WHERE blocked = ? ORDER BY dependson',
|
||||
undef, ($id));
|
||||
|
||||
$values{'dependson'} = join(",", @$dependslist);
|
||||
|
||||
my $blockedlist = $dbh->selectcol_arrayref(
|
||||
'SELECT blocked FROM dependencies
|
||||
WHERE dependson = ? ORDER BY blocked',
|
||||
undef, ($id));
|
||||
|
||||
$values{'blocked'} = join(",", @$blockedlist);
|
||||
|
||||
my $grouplist = $dbh->selectcol_arrayref(
|
||||
' SELECT name FROM groups
|
||||
INNER JOIN bug_group_map
|
||||
ON groups.id = bug_group_map.group_id
|
||||
AND bug_group_map.bug_id = ?',
|
||||
undef, ($id));
|
||||
|
||||
$values{'bug_group'} = join(', ', @$grouplist);
|
||||
|
||||
my @args = ($id);
|
||||
|
||||
# If lastdiffed is NULL, then we don't limit the search on time.
|
||||
my $when_restriction = '';
|
||||
if ($start) {
|
||||
$when_restriction = ' AND bug_when > ? AND bug_when <= ?';
|
||||
push @args, ($start, $end);
|
||||
}
|
||||
|
||||
my $diffs = $dbh->selectall_arrayref(
|
||||
"SELECT profiles.login_name, profiles.realname, fielddefs.description,
|
||||
bugs_activity.bug_when, bugs_activity.removed,
|
||||
bugs_activity.added, bugs_activity.attach_id, fielddefs.name
|
||||
FROM bugs_activity
|
||||
INNER JOIN fielddefs
|
||||
ON fielddefs.id = bugs_activity.fieldid
|
||||
INNER JOIN profiles
|
||||
ON profiles.userid = bugs_activity.who
|
||||
WHERE bugs_activity.bug_id = ?
|
||||
$when_restriction
|
||||
ORDER BY bugs_activity.bug_when", undef, @args);
|
||||
|
||||
my @new_depbugs;
|
||||
my $difftext = "";
|
||||
my $diffheader = "";
|
||||
my @diffparts;
|
||||
my $lastwho = "";
|
||||
my $fullwho;
|
||||
my @changedfields;
|
||||
foreach my $ref (@$diffs) {
|
||||
my ($who, $whoname, $what, $when, $old, $new, $attachid, $fieldname) = (@$ref);
|
||||
my $diffpart = {};
|
||||
if ($who ne $lastwho) {
|
||||
$lastwho = $who;
|
||||
$fullwho = $whoname ? "$whoname <$who>" : $who;
|
||||
$diffheader = "\n$fullwho changed:\n\n";
|
||||
$diffheader .= three_columns("What ", "Removed", "Added");
|
||||
$diffheader .= ('-' x 76) . "\n";
|
||||
}
|
||||
$what =~ s/^(Attachment )?/Attachment #$attachid / if $attachid;
|
||||
if( $fieldname eq 'estimated_time' ||
|
||||
$fieldname eq 'remaining_time' ) {
|
||||
$old = format_time_decimal($old);
|
||||
$new = format_time_decimal($new);
|
||||
}
|
||||
if ($fieldname eq 'dependson') {
|
||||
push(@new_depbugs, grep {$_ =~ /^\d+$/} split(/[\s,]+/, $new));
|
||||
}
|
||||
if ($attachid) {
|
||||
($diffpart->{'isprivate'}) = $dbh->selectrow_array(
|
||||
'SELECT isprivate FROM attachments WHERE attach_id = ?',
|
||||
undef, ($attachid));
|
||||
}
|
||||
$difftext = three_columns($what, $old, $new);
|
||||
$diffpart->{'header'} = $diffheader;
|
||||
$diffpart->{'fieldname'} = $fieldname;
|
||||
$diffpart->{'text'} = $difftext;
|
||||
push(@diffparts, $diffpart);
|
||||
push(@changedfields, $what);
|
||||
}
|
||||
$values{'changed_fields'} = join(' ', @changedfields);
|
||||
|
||||
my @depbugs;
|
||||
my $deptext = "";
|
||||
# Do not include data about dependent bugs when they have just been added.
|
||||
# Completely skip checking for dependent bugs on bug creation as all
|
||||
# dependencies bugs will just have been added.
|
||||
if ($start) {
|
||||
my $dep_restriction = "";
|
||||
if (scalar @new_depbugs) {
|
||||
$dep_restriction = "AND bugs_activity.bug_id NOT IN (" .
|
||||
join(", ", @new_depbugs) . ")";
|
||||
}
|
||||
|
||||
my $dependency_diffs = $dbh->selectall_arrayref(
|
||||
"SELECT bugs_activity.bug_id, bugs.short_desc, fielddefs.name,
|
||||
bugs_activity.removed, bugs_activity.added
|
||||
FROM bugs_activity
|
||||
INNER JOIN bugs
|
||||
ON bugs.bug_id = bugs_activity.bug_id
|
||||
INNER JOIN dependencies
|
||||
ON bugs_activity.bug_id = dependencies.dependson
|
||||
INNER JOIN fielddefs
|
||||
ON fielddefs.id = bugs_activity.fieldid
|
||||
WHERE dependencies.blocked = ?
|
||||
AND (fielddefs.name = 'bug_status'
|
||||
OR fielddefs.name = 'resolution')
|
||||
$when_restriction
|
||||
$dep_restriction
|
||||
ORDER BY bugs_activity.bug_when, bugs.bug_id", undef, @args);
|
||||
|
||||
my $thisdiff = "";
|
||||
my $lastbug = "";
|
||||
my $interestingchange = 0;
|
||||
foreach my $dependency_diff (@$dependency_diffs) {
|
||||
my ($depbug, $summary, $what, $old, $new) = @$dependency_diff;
|
||||
|
||||
if ($depbug ne $lastbug) {
|
||||
if ($interestingchange) {
|
||||
$deptext .= $thisdiff;
|
||||
}
|
||||
$lastbug = $depbug;
|
||||
my $urlbase = Bugzilla->params->{"urlbase"};
|
||||
$thisdiff =
|
||||
"\nBug $id depends on bug $depbug, which changed state.\n\n" .
|
||||
"Bug $depbug Summary: $summary\n" .
|
||||
"${urlbase}show_bug.cgi?id=$depbug\n\n";
|
||||
$thisdiff .= three_columns("What ", "Old Value", "New Value");
|
||||
$thisdiff .= ('-' x 76) . "\n";
|
||||
$interestingchange = 0;
|
||||
}
|
||||
$thisdiff .= three_columns($fielddescription{$what}, $old, $new);
|
||||
if ($what eq 'bug_status'
|
||||
&& is_open_state($old) ne is_open_state($new))
|
||||
{
|
||||
$interestingchange = 1;
|
||||
}
|
||||
push(@depbugs, $depbug);
|
||||
}
|
||||
|
||||
if ($interestingchange) {
|
||||
$deptext .= $thisdiff;
|
||||
}
|
||||
$deptext = trim($deptext);
|
||||
|
||||
if ($deptext) {
|
||||
my $diffpart = {};
|
||||
$diffpart->{'text'} = "\n" . trim("\n\n" . $deptext);
|
||||
push(@diffparts, $diffpart);
|
||||
}
|
||||
}
|
||||
|
||||
my ($comments, $anyprivate) = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 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;
|
||||
|
||||
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;
|
||||
|
||||
if ($user->can_see_bug($id)) {
|
||||
# Go through each role the user has and see if they want mail in
|
||||
# that role.
|
||||
foreach my $relationship (keys %{$recipients{$user_id}}) {
|
||||
if ($user->wants_bug_mail($id,
|
||||
$relationship,
|
||||
$diffs,
|
||||
$comments,
|
||||
$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 $anyprivate && !$user->is_insider;
|
||||
|
||||
# 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,
|
||||
$anyprivate,
|
||||
! $start,
|
||||
$id,
|
||||
exists $watching{$user_id} ?
|
||||
$watching{$user_id} : undef);
|
||||
}
|
||||
}
|
||||
|
||||
if ($sent_mail) {
|
||||
push(@sent, $user->login);
|
||||
}
|
||||
else {
|
||||
push(@excluded, $user->login);
|
||||
}
|
||||
}
|
||||
|
||||
$dbh->do('UPDATE bugs SET lastdiffed = ? WHERE bug_id = ?',
|
||||
undef, ($end, $id));
|
||||
|
||||
return {'sent' => \@sent, 'excluded' => \@excluded};
|
||||
}
|
||||
|
||||
sub sendMail {
|
||||
my ($user, $hlRef, $relRef, $valueRef, $dmhRef, $fdRef,
|
||||
$diffRef, $newcomments, $anyprivate, $isnew,
|
||||
$id, $watchingRef) = @_;
|
||||
|
||||
my %values = %$valueRef;
|
||||
my @headerlist = @$hlRef;
|
||||
my %mailhead = %$dmhRef;
|
||||
my %fielddescription = %$fdRef;
|
||||
my @diffparts = @$diffRef;
|
||||
|
||||
# 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'))
|
||||
{
|
||||
$add_diff = 1 if $user->is_timetracker;
|
||||
} elsif ($diff->{'isprivate'}
|
||||
&& !$user->is_insider)
|
||||
{
|
||||
$add_diff = 0;
|
||||
} else {
|
||||
$add_diff = 1;
|
||||
}
|
||||
|
||||
if ($add_diff) {
|
||||
if (exists($diff->{'header'}) &&
|
||||
($diffheader ne $diff->{'header'})) {
|
||||
$diffheader = $diff->{'header'};
|
||||
$difftext .= $diffheader;
|
||||
}
|
||||
$difftext .= $diff->{'text'};
|
||||
}
|
||||
}
|
||||
|
||||
if ($difftext eq "" && $newcomments eq "" && !$isnew) {
|
||||
# Whoops, no differences!
|
||||
return 0;
|
||||
}
|
||||
|
||||
# If an attachment was created, then add an URL. (Note: the 'g'lobal
|
||||
# replace should work with comments with multiple attachments.)
|
||||
|
||||
if ( $newcomments =~ /Created an attachment \(/ ) {
|
||||
|
||||
my $showattachurlbase =
|
||||
Bugzilla->params->{'urlbase'} . "attachment.cgi?id=";
|
||||
|
||||
$newcomments =~ s/(Created an attachment \(id=([0-9]+)\))/$1\n --> \(${showattachurlbase}$2\)/g;
|
||||
}
|
||||
|
||||
my $diffs = $difftext;
|
||||
if ($isnew) {
|
||||
my $head = "";
|
||||
foreach my $f (@headerlist) {
|
||||
next unless $mailhead{$f};
|
||||
my $value = $values{$f};
|
||||
# If there isn't anything to show, don't include this header.
|
||||
next unless $value;
|
||||
# Only send estimated_time if it is enabled and the user is in the group.
|
||||
if (($f ne 'estimated_time' && $f ne 'deadline') || $user->is_timetracker) {
|
||||
my $desc = $fielddescription{$f};
|
||||
$head .= multiline_sprintf(FORMAT_DOUBLE, ["$desc:", $value],
|
||||
FORMAT_2_SIZE);
|
||||
}
|
||||
}
|
||||
$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 $vars = {
|
||||
isnew => $isnew,
|
||||
to => $user->email,
|
||||
bugid => $id,
|
||||
alias => Bugzilla->params->{'usebugaliases'} ? $values{'alias'} : "",
|
||||
classification => $values{'classification'},
|
||||
product => $values{'product'},
|
||||
comp => $values{'component'},
|
||||
keywords => $values{'keywords'},
|
||||
severity => $values{'bug_severity'},
|
||||
status => $values{'bug_status'},
|
||||
priority => $values{'priority'},
|
||||
assignedto => $values{'assigned_to'},
|
||||
assignedtoname => Bugzilla::User->new({name => $values{'assigned_to'}})->name,
|
||||
targetmilestone => $values{'target_milestone'},
|
||||
changedfields => $values{'changed_fields'},
|
||||
summary => $values{'short_desc'},
|
||||
reasons => \@reasons,
|
||||
reasons_watch => \@reasons_watch,
|
||||
reasonsheader => join(" ", @headerrel),
|
||||
reasonswatchheader => join(" ", @watchingrel),
|
||||
changer => $values{'changer'},
|
||||
changername => $values{'changername'},
|
||||
reporter => $values{'reporter'},
|
||||
reportername => Bugzilla::User->new({name => $values{'reporter'}})->name,
|
||||
diffs => $diffs,
|
||||
new_comments => $newcomments,
|
||||
threadingmarker => build_thread_marker($id, $user->id, $isnew),
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
foreach my $comment (@$comments) {
|
||||
$comment->{count} = $count++;
|
||||
}
|
||||
|
||||
if (Bugzilla->params->{'insidergroup'}) {
|
||||
$anyprivate = 1 if scalar(grep {$_->{'isprivate'} > 0} @$comments);
|
||||
}
|
||||
|
||||
return ($comments, $anyprivate);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,479 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Byron Jones <bugzilla@glob.com.au>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::CGI;
|
||||
|
||||
BEGIN {
|
||||
if ($^O =~ /MSWin32/i) {
|
||||
# Help CGI find the correct temp directory as the default list
|
||||
# isn't Windows friendly (Bug 248988)
|
||||
$ENV{'TMPDIR'} = $ENV{'TEMP'} || $ENV{'TMP'} || "$ENV{'WINDIR'}\\TEMP";
|
||||
}
|
||||
}
|
||||
|
||||
use CGI qw(-no_xhtml -oldstyle_urls :private_tempfiles :unique_headers SERVER_PUSH);
|
||||
|
||||
use base qw(CGI);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
|
||||
# We need to disable output buffering - see bug 179174
|
||||
$| = 1;
|
||||
|
||||
# Ignore SIGTERM and SIGPIPE - this prevents DB corruption. If the user closes
|
||||
# their browser window while a script is running, the web server sends these
|
||||
# signals, and we don't want to die half way through a write.
|
||||
$::SIG{TERM} = 'IGNORE';
|
||||
$::SIG{PIPE} = 'IGNORE';
|
||||
|
||||
# CGI.pm uses AUTOLOAD, but explicitly defines a DESTROY sub.
|
||||
# We need to do so, too, otherwise perl dies when the object is destroyed
|
||||
# and we don't have a DESTROY method (because CGI.pm's AUTOLOAD will |die|
|
||||
# on getting an unknown sub to try to call)
|
||||
sub DESTROY {
|
||||
my $self = shift;
|
||||
$self->SUPER::DESTROY(@_);
|
||||
};
|
||||
|
||||
sub new {
|
||||
my ($invocant, @args) = @_;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
|
||||
my $self = $class->SUPER::new(@args);
|
||||
|
||||
# Make sure our outgoing cookie list is empty on each invocation
|
||||
$self->{Bugzilla_cookie_list} = [];
|
||||
|
||||
# Send appropriate charset
|
||||
$self->charset(Bugzilla->params->{'utf8'} ? 'UTF-8' : '');
|
||||
|
||||
# Redirect to urlbase/sslbase if we are not viewing an attachment.
|
||||
if (use_attachbase() && i_am_cgi()) {
|
||||
my $cgi_file = $self->url('-path_info' => 0, '-query' => 0, '-relative' => 1);
|
||||
$cgi_file =~ s/\?$//;
|
||||
my $urlbase = Bugzilla->params->{'urlbase'};
|
||||
my $sslbase = Bugzilla->params->{'sslbase'};
|
||||
my $path_regexp = $sslbase ? qr/^(\Q$urlbase\E|\Q$sslbase\E)/ : qr/^\Q$urlbase\E/;
|
||||
if ($cgi_file ne 'attachment.cgi' && $self->self_url !~ /$path_regexp/) {
|
||||
$self->redirect_to_urlbase;
|
||||
}
|
||||
}
|
||||
|
||||
# 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;
|
||||
|
||||
# Remove the Boolean Charts for standard query.cgi fields
|
||||
# They are listed in the query URL already
|
||||
next if $key =~ /^(field|type|value)(-\d+){3}$/;
|
||||
|
||||
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');
|
||||
|
||||
# Delete leftovers from the login form
|
||||
$self->delete('Bugzilla_remember', 'GoAheadAndLogIn');
|
||||
|
||||
foreach my $num (1,2) {
|
||||
# If there's no value in the email field, delete the related fields.
|
||||
if (!$self->param("email$num")) {
|
||||
foreach my $field qw(type assigned_to reporter qa_contact
|
||||
cc longdesc)
|
||||
{
|
||||
$self->delete("email$field$num");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# chfieldto is set to "Now" by default in query.cgi. But if none
|
||||
# of the other chfield parameters are set, it's meaningless.
|
||||
if (!defined $self->param('chfieldfrom') && !$self->param('chfield')
|
||||
&& !defined $self->param('chfieldvalue'))
|
||||
{
|
||||
$self->delete('chfieldto');
|
||||
}
|
||||
|
||||
# cmdtype "doit" is the default from query.cgi, but it's only meaningful
|
||||
# if there's a remtype parameter.
|
||||
if (defined $self->param('cmdtype') && $self->param('cmdtype') eq 'doit'
|
||||
&& !defined $self->param('remtype'))
|
||||
{
|
||||
$self->delete('cmdtype');
|
||||
}
|
||||
|
||||
# "Reuse same sort as last time" is actually the default, so we don't
|
||||
# need it in the URL.
|
||||
if ($self->param('order')
|
||||
&& $self->param('order') eq 'Reuse same sort as last time')
|
||||
{
|
||||
$self->delete('order');
|
||||
}
|
||||
|
||||
# And now finally, if query_format is our only parameter, that
|
||||
# really means we have no parameters, so we should delete query_format.
|
||||
if ($self->param('query_format') && scalar($self->param()) == 1) {
|
||||
$self->delete('query_format');
|
||||
}
|
||||
}
|
||||
|
||||
# Overwrite to ensure nph doesn't get set, and unset HEADERS_ONCE
|
||||
sub multipart_init {
|
||||
my $self = shift;
|
||||
|
||||
# Keys are case-insensitive, map to lowercase
|
||||
my %args = @_;
|
||||
my %param;
|
||||
foreach my $key (keys %args) {
|
||||
$param{lc $key} = $args{$key};
|
||||
}
|
||||
|
||||
# Set the MIME boundary and content-type
|
||||
my $boundary = $param{'-boundary'} || '------- =_aaaaaaaaaa0';
|
||||
delete $param{'-boundary'};
|
||||
$self->{'separator'} = "\r\n--$boundary\r\n";
|
||||
$self->{'final_separator'} = "\r\n--$boundary--\r\n";
|
||||
$param{'-type'} = SERVER_PUSH($boundary);
|
||||
|
||||
# Note: CGI.pm::multipart_init up to v3.04 explicitly set nph to 0
|
||||
# CGI.pm::multipart_init v3.05 explicitly sets nph to 1
|
||||
# CGI.pm's header() sets nph according to a param or $CGI::NPH, which
|
||||
# is the desired behaviour.
|
||||
|
||||
return $self->header(
|
||||
%param,
|
||||
) . "WARNING: YOUR BROWSER DOESN'T SUPPORT THIS SERVER-PUSH TECHNOLOGY." . $self->multipart_end;
|
||||
}
|
||||
|
||||
# Have to add the cookies in.
|
||||
sub multipart_start {
|
||||
my $self = shift;
|
||||
|
||||
my %args = @_;
|
||||
|
||||
# CGI.pm::multipart_start doesn't honour its own charset information, so
|
||||
# we do it ourselves here
|
||||
if (defined $self->charset() && defined $args{-type}) {
|
||||
# Remove any existing charset specifier
|
||||
$args{-type} =~ s/;.*$//;
|
||||
# and add the specified one
|
||||
$args{-type} .= '; charset=' . $self->charset();
|
||||
}
|
||||
|
||||
my $headers = $self->SUPER::multipart_start(%args);
|
||||
# Eliminate the one extra CRLF at the end.
|
||||
$headers =~ s/$CGI::CRLF$//;
|
||||
# Add the cookies. We have to do it this way instead of
|
||||
# passing them to multpart_start, because CGI.pm's multipart_start
|
||||
# doesn't understand a '-cookie' argument pointing to an arrayref.
|
||||
foreach my $cookie (@{$self->{Bugzilla_cookie_list}}) {
|
||||
$headers .= "Set-Cookie: ${cookie}${CGI::CRLF}";
|
||||
}
|
||||
$headers .= $CGI::CRLF;
|
||||
return $headers;
|
||||
}
|
||||
|
||||
# Override header so we can add the cookies in
|
||||
sub header {
|
||||
my $self = shift;
|
||||
|
||||
# Add the cookies in if we have any
|
||||
if (scalar(@{$self->{Bugzilla_cookie_list}})) {
|
||||
if (scalar(@_) == 1) {
|
||||
# if there's only one parameter, then it's a Content-Type.
|
||||
# Since we're adding parameters we have to name it.
|
||||
unshift(@_, '-type' => shift(@_));
|
||||
}
|
||||
unshift(@_, '-cookie' => $self->{Bugzilla_cookie_list});
|
||||
}
|
||||
|
||||
return $self->SUPER::header(@_) || "";
|
||||
}
|
||||
|
||||
sub param {
|
||||
my $self = shift;
|
||||
|
||||
# When we are just requesting the value of a parameter...
|
||||
if (scalar(@_) == 1) {
|
||||
my @result = $self->SUPER::param(@_);
|
||||
|
||||
# Also look at the URL parameters, after we look at the POST
|
||||
# parameters. This is to allow things like login-form submissions
|
||||
# with URL parameters in the form's "target" attribute.
|
||||
if (!scalar(@result)
|
||||
&& $self->request_method && $self->request_method eq 'POST')
|
||||
{
|
||||
@result = $self->SUPER::url_param(@_);
|
||||
}
|
||||
|
||||
# Fix UTF-8-ness of input parameters.
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
@result = map { _fix_utf8($_) } @result;
|
||||
}
|
||||
|
||||
return wantarray ? @result : $result[0];
|
||||
}
|
||||
# And for various other functions in CGI.pm, we need to correctly
|
||||
# return the URL parameters in addition to the POST parameters when
|
||||
# asked for the list of parameters.
|
||||
elsif (!scalar(@_) && $self->request_method
|
||||
&& $self->request_method eq 'POST')
|
||||
{
|
||||
my @post_params = $self->SUPER::param;
|
||||
my @url_params = $self->url_param;
|
||||
my %params = map { $_ => 1 } (@post_params, @url_params);
|
||||
return keys %params;
|
||||
}
|
||||
|
||||
return $self->SUPER::param(@_);
|
||||
}
|
||||
|
||||
sub _fix_utf8 {
|
||||
my $input = shift;
|
||||
# The is_utf8 is here in case CGI gets smart about utf8 someday.
|
||||
utf8::decode($input) if defined $input && !utf8::is_utf8($input);
|
||||
return $input;
|
||||
}
|
||||
|
||||
# The various parts of Bugzilla which create cookies don't want to have to
|
||||
# pass them around to all of the callers. Instead, store them locally here,
|
||||
# and then output as required from |header|.
|
||||
sub send_cookie {
|
||||
my $self = shift;
|
||||
|
||||
# Move the param list into a hash for easier handling.
|
||||
my %paramhash;
|
||||
my @paramlist;
|
||||
my ($key, $value);
|
||||
while ($key = shift) {
|
||||
$value = shift;
|
||||
$paramhash{$key} = $value;
|
||||
}
|
||||
|
||||
# Complain if -value is not given or empty (bug 268146).
|
||||
if (!exists($paramhash{'-value'}) || !$paramhash{'-value'}) {
|
||||
ThrowCodeError('cookies_need_value');
|
||||
}
|
||||
|
||||
# Add the default path and the domain in.
|
||||
$paramhash{'-path'} = Bugzilla->params->{'cookiepath'};
|
||||
$paramhash{'-domain'} = Bugzilla->params->{'cookiedomain'}
|
||||
if Bugzilla->params->{'cookiedomain'};
|
||||
|
||||
# Move the param list back into an array for the call to cookie().
|
||||
foreach (keys(%paramhash)) {
|
||||
unshift(@paramlist, $_ => $paramhash{$_});
|
||||
}
|
||||
|
||||
push(@{$self->{'Bugzilla_cookie_list'}}, $self->cookie(@paramlist));
|
||||
}
|
||||
|
||||
# Cookies are removed by setting an expiry date in the past.
|
||||
# This method is a send_cookie wrapper doing exactly this.
|
||||
sub remove_cookie {
|
||||
my $self = shift;
|
||||
my ($cookiename) = (@_);
|
||||
|
||||
# Expire the cookie, giving a non-empty dummy value (bug 268146).
|
||||
$self->send_cookie('-name' => $cookiename,
|
||||
'-expires' => 'Tue, 15-Sep-1998 21:49:00 GMT',
|
||||
'-value' => 'X');
|
||||
}
|
||||
|
||||
# Redirect to https if required
|
||||
sub require_https {
|
||||
my ($self, $url) = @_;
|
||||
# Do not create query string if data submitted via XMLRPC
|
||||
# since we want the data to be resubmitted over POST method.
|
||||
my $query = Bugzilla->usage_mode == USAGE_MODE_WEBSERVICE ? 0 : 1;
|
||||
# XMLRPC clients (SOAP::Lite at least) requires 301 to redirect properly
|
||||
# and do not work with 302.
|
||||
my $status = Bugzilla->usage_mode == USAGE_MODE_WEBSERVICE ? 301 : 302;
|
||||
if (defined $url) {
|
||||
$url .= $self->url('-path_info' => 1, '-query' => $query, '-relative' => 1);
|
||||
} else {
|
||||
$url = $self->self_url;
|
||||
$url =~ s/^http:/https:/i;
|
||||
}
|
||||
print $self->redirect(-location => $url, -status => $status);
|
||||
# When using XML-RPC with mod_perl, we need the headers sent immediately.
|
||||
$self->r->rflush if $ENV{MOD_PERL};
|
||||
exit;
|
||||
}
|
||||
|
||||
# Redirect to the urlbase version of the current URL.
|
||||
sub redirect_to_urlbase {
|
||||
my $self = shift;
|
||||
my $path = $self->url('-path_info' => 1, '-query' => 1, '-relative' => 1);
|
||||
print $self->redirect('-location' => correct_urlbase() . $path);
|
||||
exit;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::CGI - CGI handling for Bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::CGI;
|
||||
|
||||
my $cgi = new Bugzilla::CGI();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This package inherits from the standard CGI module, to provide additional
|
||||
Bugzilla-specific functionality. In general, see L<the CGI.pm docs|CGI> for
|
||||
documention.
|
||||
|
||||
=head1 CHANGES FROM L<CGI.PM|CGI>
|
||||
|
||||
Bugzilla::CGI has some differences from L<CGI.pm|CGI>.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<cgi_error> is automatically checked
|
||||
|
||||
After creating the CGI object, C<Bugzilla::CGI> automatically checks
|
||||
I<cgi_error>, and throws a CodeError if a problem is detected.
|
||||
|
||||
=back
|
||||
|
||||
=head1 ADDITIONAL FUNCTIONS
|
||||
|
||||
I<Bugzilla::CGI> also includes additional functions.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<canonicalise_query(@exclude)>
|
||||
|
||||
This returns a sorted string of the parameters, suitable for use in a url.
|
||||
Values in C<@exclude> are not included in the result.
|
||||
|
||||
=item C<send_cookie>
|
||||
|
||||
This routine is identical to the cookie generation part of CGI.pm's C<cookie>
|
||||
routine, except that it knows about Bugzilla's cookie_path and cookie_domain
|
||||
parameters and takes them into account if necessary.
|
||||
This should be used by all Bugzilla code (instead of C<cookie> or the C<-cookie>
|
||||
argument to C<header>), so that under mod_perl the headers can be sent
|
||||
correctly, using C<print> or the mod_perl APIs as appropriate.
|
||||
|
||||
To remove (expire) a cookie, use C<remove_cookie>.
|
||||
|
||||
=item C<remove_cookie>
|
||||
|
||||
This is a wrapper around send_cookie, setting an expiry date in the past,
|
||||
effectively removing the cookie.
|
||||
|
||||
As its only argument, it takes the name of the cookie to expire.
|
||||
|
||||
=item C<require_https($baseurl)>
|
||||
|
||||
This routine redirects the client to a different location using the https protocol.
|
||||
If the client is using XMLRPC, it will not retain the QUERY_STRING since XMLRPC uses POST.
|
||||
|
||||
It takes an optional argument which will be used as the base URL. If $baseurl
|
||||
is not provided, the current URL is used.
|
||||
|
||||
=item C<redirect_to_urlbase>
|
||||
|
||||
Redirects from the current URL to one prefixed by the urlbase parameter.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<CGI|CGI>, L<CGI::Cookie|CGI::Cookie>
|
||||
@@ -1,445 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Gervase Markham <gerv@gerv.net>
|
||||
# Albert Ting <altlst@sonic.net>
|
||||
# A. Karl Kornel <karl@kornel.name>
|
||||
|
||||
use strict;
|
||||
|
||||
# This module represents a chart.
|
||||
#
|
||||
# Note that it is perfectly legal for the 'lines' member variable of this
|
||||
# class (which is an array of Bugzilla::Series objects) to have empty members
|
||||
# in it. If this is true, the 'labels' array will also have empty members at
|
||||
# the same points.
|
||||
package Bugzilla::Chart;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Series;
|
||||
|
||||
use Date::Format;
|
||||
use Date::Parse;
|
||||
use List::Util qw(max);
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
|
||||
# Create a ref to an empty hash and bless it
|
||||
my $self = {};
|
||||
bless($self, $class);
|
||||
|
||||
if ($#_ == 0) {
|
||||
# Construct from a CGI object.
|
||||
$self->init($_[0]);
|
||||
}
|
||||
else {
|
||||
die("CGI object not passed in - invalid number of args \($#_\)($_)");
|
||||
}
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub init {
|
||||
my $self = shift;
|
||||
my $cgi = shift;
|
||||
|
||||
# The data structure is a list of lists (lines) of Series objects.
|
||||
# There is a separate list for the labels.
|
||||
#
|
||||
# The URL encoding is:
|
||||
# line0=67&line0=73&line1=81&line2=67...
|
||||
# &label0=B+/+R+/+NEW&label1=...
|
||||
# &select0=1&select3=1...
|
||||
# &cumulate=1&datefrom=2002-02-03&dateto=2002-04-04&ctype=html...
|
||||
# >=1&labelgt=Grand+Total
|
||||
foreach my $param ($cgi->param()) {
|
||||
# Store all the lines
|
||||
if ($param =~ /^line(\d+)$/) {
|
||||
foreach my $series_id ($cgi->param($param)) {
|
||||
detaint_natural($series_id)
|
||||
|| ThrowCodeError("invalid_series_id");
|
||||
my $series = new Bugzilla::Series($series_id);
|
||||
push(@{$self->{'lines'}[$1]}, $series) if $series;
|
||||
}
|
||||
}
|
||||
|
||||
# Store all the labels
|
||||
if ($param =~ /^label(\d+)$/) {
|
||||
$self->{'labels'}[$1] = $cgi->param($param);
|
||||
}
|
||||
}
|
||||
|
||||
# Store the miscellaneous metadata
|
||||
$self->{'cumulate'} = $cgi->param('cumulate') ? 1 : 0;
|
||||
$self->{'gt'} = $cgi->param('gt') ? 1 : 0;
|
||||
$self->{'labelgt'} = $cgi->param('labelgt');
|
||||
$self->{'datefrom'} = $cgi->param('datefrom');
|
||||
$self->{'dateto'} = $cgi->param('dateto');
|
||||
|
||||
# If we are cumulating, a grand total makes no sense
|
||||
$self->{'gt'} = 0 if $self->{'cumulate'};
|
||||
|
||||
# Make sure the dates are ones we are able to interpret
|
||||
foreach my $date ('datefrom', 'dateto') {
|
||||
if ($self->{$date}) {
|
||||
$self->{$date} = str2time($self->{$date})
|
||||
|| ThrowUserError("illegal_date", { date => $self->{$date}});
|
||||
}
|
||||
}
|
||||
|
||||
# datefrom can't be after dateto
|
||||
if ($self->{'datefrom'} && $self->{'dateto'} &&
|
||||
$self->{'datefrom'} > $self->{'dateto'})
|
||||
{
|
||||
ThrowUserError("misarranged_dates",
|
||||
{'datefrom' => $cgi->param('datefrom'),
|
||||
'dateto' => $cgi->param('dateto')});
|
||||
}
|
||||
}
|
||||
|
||||
# Alter Chart so that the selected series are added to it.
|
||||
sub add {
|
||||
my $self = shift;
|
||||
my @series_ids = @_;
|
||||
|
||||
# Get the current size of the series; required for adding Grand Total later
|
||||
my $current_size = scalar($self->getSeriesIDs());
|
||||
|
||||
# Count the number of added series
|
||||
my $added = 0;
|
||||
# Create new Series and push them on to the list of lines.
|
||||
# Note that new lines have no label; the display template is responsible
|
||||
# for inventing something sensible.
|
||||
foreach my $series_id (@series_ids) {
|
||||
my $series = new Bugzilla::Series($series_id);
|
||||
if ($series) {
|
||||
push(@{$self->{'lines'}}, [$series]);
|
||||
push(@{$self->{'labels'}}, "");
|
||||
$added++;
|
||||
}
|
||||
}
|
||||
|
||||
# If we are going from < 2 to >= 2 series, add the Grand Total line.
|
||||
if (!$self->{'gt'}) {
|
||||
if ($current_size < 2 &&
|
||||
$current_size + $added >= 2)
|
||||
{
|
||||
$self->{'gt'} = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Alter Chart so that the selections are removed from it.
|
||||
sub remove {
|
||||
my $self = shift;
|
||||
my @line_ids = @_;
|
||||
|
||||
foreach my $line_id (@line_ids) {
|
||||
if ($line_id == 65536) {
|
||||
# Magic value - delete Grand Total.
|
||||
$self->{'gt'} = 0;
|
||||
}
|
||||
else {
|
||||
delete($self->{'lines'}->[$line_id]);
|
||||
delete($self->{'labels'}->[$line_id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Alter Chart so that the selections are summed.
|
||||
sub sum {
|
||||
my $self = shift;
|
||||
my @line_ids = @_;
|
||||
|
||||
# We can't add the Grand Total to things.
|
||||
@line_ids = grep(!/^65536$/, @line_ids);
|
||||
|
||||
# We can't add less than two things.
|
||||
return if scalar(@line_ids) < 2;
|
||||
|
||||
my @series;
|
||||
my $label = "";
|
||||
my $biggestlength = 0;
|
||||
|
||||
# We rescue the Series objects of all the series involved in the sum.
|
||||
foreach my $line_id (@line_ids) {
|
||||
my @line = @{$self->{'lines'}->[$line_id]};
|
||||
|
||||
foreach my $series (@line) {
|
||||
push(@series, $series);
|
||||
}
|
||||
|
||||
# We keep the label that labels the line with the most series.
|
||||
if (scalar(@line) > $biggestlength) {
|
||||
$biggestlength = scalar(@line);
|
||||
$label = $self->{'labels'}->[$line_id];
|
||||
}
|
||||
}
|
||||
|
||||
$self->remove(@line_ids);
|
||||
|
||||
push(@{$self->{'lines'}}, \@series);
|
||||
push(@{$self->{'labels'}}, $label);
|
||||
}
|
||||
|
||||
sub data {
|
||||
my $self = shift;
|
||||
$self->{'_data'} ||= $self->readData();
|
||||
return $self->{'_data'};
|
||||
}
|
||||
|
||||
# Convert the Chart's data into a plottable form in $self->{'_data'}.
|
||||
sub readData {
|
||||
my $self = shift;
|
||||
my @data;
|
||||
my @maxvals;
|
||||
|
||||
# Note: you get a bad image if getSeriesIDs returns nothing
|
||||
# We need to handle errors better.
|
||||
my $series_ids = join(",", $self->getSeriesIDs());
|
||||
|
||||
return [] unless $series_ids;
|
||||
|
||||
# Work out the date boundaries for our data.
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# The date used is the one given if it's in a sensible range; otherwise,
|
||||
# it's the earliest or latest date in the database as appropriate.
|
||||
my $datefrom = $dbh->selectrow_array("SELECT MIN(series_date) " .
|
||||
"FROM series_data " .
|
||||
"WHERE series_id IN ($series_ids)");
|
||||
$datefrom = str2time($datefrom);
|
||||
|
||||
if ($self->{'datefrom'} && $self->{'datefrom'} > $datefrom) {
|
||||
$datefrom = $self->{'datefrom'};
|
||||
}
|
||||
|
||||
my $dateto = $dbh->selectrow_array("SELECT MAX(series_date) " .
|
||||
"FROM series_data " .
|
||||
"WHERE series_id IN ($series_ids)");
|
||||
$dateto = str2time($dateto);
|
||||
|
||||
if ($self->{'dateto'} && $self->{'dateto'} < $dateto) {
|
||||
$dateto = $self->{'dateto'};
|
||||
}
|
||||
|
||||
# Convert UNIX times back to a date format usable for SQL queries.
|
||||
my $sql_from = time2str('%Y-%m-%d', $datefrom);
|
||||
my $sql_to = time2str('%Y-%m-%d', $dateto);
|
||||
|
||||
# Prepare the query which retrieves the data for each series
|
||||
my $query = "SELECT " . $dbh->sql_to_days('series_date') . " - " .
|
||||
$dbh->sql_to_days('?') . ", series_value " .
|
||||
"FROM series_data " .
|
||||
"WHERE series_id = ? " .
|
||||
"AND series_date >= ?";
|
||||
if ($dateto) {
|
||||
$query .= " AND series_date <= ?";
|
||||
}
|
||||
|
||||
my $sth = $dbh->prepare($query);
|
||||
|
||||
my $gt_index = $self->{'gt'} ? scalar(@{$self->{'lines'}}) : undef;
|
||||
my $line_index = 0;
|
||||
|
||||
$maxvals[$gt_index] = 0 if $gt_index;
|
||||
|
||||
my @datediff_total;
|
||||
|
||||
foreach my $line (@{$self->{'lines'}}) {
|
||||
# Even if we end up with no data, we need an empty arrayref to prevent
|
||||
# errors in the PNG-generating code
|
||||
$data[$line_index] = [];
|
||||
$maxvals[$line_index] = 0;
|
||||
|
||||
foreach my $series (@$line) {
|
||||
|
||||
# Get the data for this series and add it on
|
||||
if ($dateto) {
|
||||
$sth->execute($sql_from, $series->{'series_id'}, $sql_from, $sql_to);
|
||||
}
|
||||
else {
|
||||
$sth->execute($sql_from, $series->{'series_id'}, $sql_from);
|
||||
}
|
||||
my $points = $sth->fetchall_arrayref();
|
||||
|
||||
foreach my $point (@$points) {
|
||||
my ($datediff, $value) = @$point;
|
||||
$data[$line_index][$datediff] ||= 0;
|
||||
$data[$line_index][$datediff] += $value;
|
||||
if ($data[$line_index][$datediff] > $maxvals[$line_index]) {
|
||||
$maxvals[$line_index] = $data[$line_index][$datediff];
|
||||
}
|
||||
|
||||
$datediff_total[$datediff] += $value;
|
||||
|
||||
# Add to the grand total, if we are doing that
|
||||
if ($gt_index) {
|
||||
$data[$gt_index][$datediff] ||= 0;
|
||||
$data[$gt_index][$datediff] += $value;
|
||||
if ($data[$gt_index][$datediff] > $maxvals[$gt_index]) {
|
||||
$maxvals[$gt_index] = $data[$gt_index][$datediff];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# We are done with the series making up this line, go to the next one
|
||||
$line_index++;
|
||||
}
|
||||
|
||||
# calculate maximum y value
|
||||
if ($self->{'cumulate'}) {
|
||||
# Make sure we do not try to take the max of an array with undef values
|
||||
my @processed_datediff;
|
||||
while (@datediff_total) {
|
||||
my $datediff = shift @datediff_total;
|
||||
push @processed_datediff, $datediff if defined($datediff);
|
||||
}
|
||||
$self->{'y_max_value'} = max(@processed_datediff);
|
||||
}
|
||||
else {
|
||||
$self->{'y_max_value'} = max(@maxvals);
|
||||
}
|
||||
$self->{'y_max_value'} |= 1; # For log()
|
||||
|
||||
# Align the max y value:
|
||||
# For one- or two-digit numbers, increase y_max_value until divisible by 8
|
||||
# For larger numbers, see the comments below to figure out what's going on
|
||||
if ($self->{'y_max_value'} < 100) {
|
||||
do {
|
||||
++$self->{'y_max_value'};
|
||||
} while ($self->{'y_max_value'} % 8 != 0);
|
||||
}
|
||||
else {
|
||||
# First, get the # of digits in the y_max_value
|
||||
my $num_digits = 1+int(log($self->{'y_max_value'})/log(10));
|
||||
|
||||
# We want to zero out all but the top 2 digits
|
||||
my $mask_length = $num_digits - 2;
|
||||
$self->{'y_max_value'} /= 10**$mask_length;
|
||||
$self->{'y_max_value'} = int($self->{'y_max_value'});
|
||||
$self->{'y_max_value'} *= 10**$mask_length;
|
||||
|
||||
# Add 10^$mask_length to the max value
|
||||
# Continue to increase until it's divisible by 8 * 10^($mask_length-1)
|
||||
# (Throwing in the -1 keeps at least the smallest digit at zero)
|
||||
do {
|
||||
$self->{'y_max_value'} += 10**$mask_length;
|
||||
} while ($self->{'y_max_value'} % (8*(10**($mask_length-1))) != 0);
|
||||
}
|
||||
|
||||
|
||||
# Add the x-axis labels into the data structure
|
||||
my $date_progression = generateDateProgression($datefrom, $dateto);
|
||||
unshift(@data, $date_progression);
|
||||
|
||||
if ($self->{'gt'}) {
|
||||
# Add Grand Total to label list
|
||||
push(@{$self->{'labels'}}, $self->{'labelgt'});
|
||||
|
||||
$data[$gt_index] ||= [];
|
||||
}
|
||||
|
||||
return \@data;
|
||||
}
|
||||
|
||||
# Flatten the data structure into a list of series_ids
|
||||
sub getSeriesIDs {
|
||||
my $self = shift;
|
||||
my @series_ids;
|
||||
|
||||
foreach my $line (@{$self->{'lines'}}) {
|
||||
foreach my $series (@$line) {
|
||||
push(@series_ids, $series->{'series_id'});
|
||||
}
|
||||
}
|
||||
|
||||
return @series_ids;
|
||||
}
|
||||
|
||||
# Class method to get the data necessary to populate the "select series"
|
||||
# widgets on various pages.
|
||||
sub getVisibleSeries {
|
||||
my %cats;
|
||||
|
||||
my $grouplist = Bugzilla->user->groups_as_string;
|
||||
|
||||
# Get all visible series
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $serieses = $dbh->selectall_arrayref("SELECT cc1.name, cc2.name, " .
|
||||
"series.name, series.series_id " .
|
||||
"FROM series " .
|
||||
"INNER JOIN series_categories AS cc1 " .
|
||||
" ON series.category = cc1.id " .
|
||||
"INNER JOIN series_categories AS cc2 " .
|
||||
" ON series.subcategory = cc2.id " .
|
||||
"LEFT JOIN category_group_map AS cgm " .
|
||||
" ON series.category = cgm.category_id " .
|
||||
" AND cgm.group_id NOT IN($grouplist) " .
|
||||
"WHERE creator = " . Bugzilla->user->id . " OR " .
|
||||
" cgm.category_id IS NULL " .
|
||||
$dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
|
||||
'series.name'));
|
||||
foreach my $series (@$serieses) {
|
||||
my ($cat, $subcat, $name, $series_id) = @$series;
|
||||
$cats{$cat}{$subcat}{$name} = $series_id;
|
||||
}
|
||||
|
||||
return \%cats;
|
||||
}
|
||||
|
||||
sub generateDateProgression {
|
||||
my ($datefrom, $dateto) = @_;
|
||||
my @progression;
|
||||
|
||||
$dateto = $dateto || time();
|
||||
my $oneday = 60 * 60 * 24;
|
||||
|
||||
# When the from and to dates are converted by str2time(), you end up with
|
||||
# a time figure representing midnight at the beginning of that day. We
|
||||
# adjust the times by 1/3 and 2/3 of a day respectively to prevent
|
||||
# edge conditions in time2str().
|
||||
$datefrom += $oneday / 3;
|
||||
$dateto += (2 * $oneday) / 3;
|
||||
|
||||
while ($datefrom < $dateto) {
|
||||
push (@progression, time2str("%Y-%m-%d", $datefrom));
|
||||
$datefrom += $oneday;
|
||||
}
|
||||
|
||||
return \@progression;
|
||||
}
|
||||
|
||||
sub dump {
|
||||
my $self = shift;
|
||||
|
||||
# Make sure we've read in our data
|
||||
my $data = $self->data;
|
||||
|
||||
require Data::Dumper;
|
||||
print "<pre>Bugzilla::Chart object:\n";
|
||||
print Data::Dumper::Dumper($self);
|
||||
print "</pre>";
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,216 +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>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Classification;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Product;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
use constant DB_TABLE => 'classifications';
|
||||
use constant LIST_ORDER => 'sortkey, name';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
name
|
||||
description
|
||||
sortkey
|
||||
);
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(
|
||||
name
|
||||
);
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
name
|
||||
description
|
||||
sortkey
|
||||
);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
name => \&_check_name,
|
||||
description => \&_check_description,
|
||||
sortkey => \&_check_sortkey,
|
||||
};
|
||||
|
||||
###############################
|
||||
#### Constructors #####
|
||||
###############################
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
ThrowUserError("classification_not_deletable") if ($self->id == 1);
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
# Reclassify products to the default classification, if needed.
|
||||
$dbh->do("UPDATE products SET classification_id = 1
|
||||
WHERE classification_id = ?", undef, $self->id);
|
||||
|
||||
$dbh->do("DELETE FROM classifications WHERE id = ?", undef, $self->id);
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Validators ####
|
||||
###############################
|
||||
|
||||
sub _check_name {
|
||||
my ($invocant, $name) = @_;
|
||||
|
||||
$name = trim($name);
|
||||
$name || ThrowUserError('classification_not_specified');
|
||||
|
||||
if (length($name) > MAX_CLASSIFICATION_SIZE) {
|
||||
ThrowUserError('classification_name_too_long', {'name' => $name});
|
||||
}
|
||||
|
||||
my $classification = new Bugzilla::Classification({name => $name});
|
||||
if ($classification && (!ref $invocant || $classification->id != $invocant->id)) {
|
||||
ThrowUserError("classification_already_exists", { name => $classification->name });
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_description {
|
||||
my ($invocant, $description) = @_;
|
||||
|
||||
$description = trim($description || '');
|
||||
return $description;
|
||||
}
|
||||
|
||||
sub _check_sortkey {
|
||||
my ($invocant, $sortkey) = @_;
|
||||
|
||||
$sortkey ||= 0;
|
||||
my $stored_sortkey = $sortkey;
|
||||
if (!detaint_natural($sortkey) || $sortkey > MAX_SMALLINT) {
|
||||
ThrowUserError('classification_invalid_sortkey', { 'sortkey' => $stored_sortkey });
|
||||
}
|
||||
return $sortkey;
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
sub set_name { $_[0]->set('name', $_[1]); }
|
||||
sub set_description { $_[0]->set('description', $_[1]); }
|
||||
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
|
||||
|
||||
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 description { return $_[0]->{'description'}; }
|
||||
sub sortkey { return $_[0]->{'sortkey'}; }
|
||||
|
||||
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 $sortkey = $classification->sortkey;
|
||||
my $product_count = $classification->product_count;
|
||||
my $products = $classification->products;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Classification.pm represents a classification 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::Classification> are listed
|
||||
below.
|
||||
|
||||
A Classification is a higher-level grouping of Products.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=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
|
||||
|
||||
=cut
|
||||
@@ -1,647 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Tiago R. Mello <timello@async.com.br>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Akamai Technologies <bugzilla-dev@akamai.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Component;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::FlagType;
|
||||
use Bugzilla::Series;
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
use constant DB_TABLE => 'components';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
name
|
||||
product_id
|
||||
initialowner
|
||||
initialqacontact
|
||||
description
|
||||
);
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(
|
||||
name
|
||||
product
|
||||
initialowner
|
||||
description
|
||||
);
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
name
|
||||
initialowner
|
||||
initialqacontact
|
||||
description
|
||||
);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
product => \&_check_product,
|
||||
initialowner => \&_check_initialowner,
|
||||
initialqacontact => \&_check_initialqacontact,
|
||||
description => \&_check_description,
|
||||
initial_cc => \&_check_cc_list,
|
||||
};
|
||||
|
||||
use constant UPDATE_VALIDATORS => {
|
||||
name => \&_check_name,
|
||||
};
|
||||
|
||||
###############################
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $param = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $product;
|
||||
if (ref $param) {
|
||||
$product = $param->{product};
|
||||
my $name = $param->{name};
|
||||
if (!defined $product) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'product',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
if (!defined $name) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'name',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
|
||||
my $condition = 'product_id = ? AND name = ?';
|
||||
my @values = ($product->id, $name);
|
||||
$param = { condition => $condition, values => \@values };
|
||||
}
|
||||
|
||||
unshift @_, $param;
|
||||
my $component = $class->SUPER::new(@_);
|
||||
# Add the product object as attribute only if the component exists.
|
||||
$component->{product} = $product if ($component && $product);
|
||||
return $component;
|
||||
}
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
$class->check_required_create_fields(@_);
|
||||
my $params = $class->run_create_validators(@_);
|
||||
my $cc_list = delete $params->{initial_cc};
|
||||
|
||||
my $component = $class->insert_create_data($params);
|
||||
|
||||
# We still have to fill the component_cc table.
|
||||
$component->_update_cc_list($cc_list);
|
||||
|
||||
# Create series for the new component.
|
||||
$component->_create_series();
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
return $component;
|
||||
}
|
||||
|
||||
sub run_create_validators {
|
||||
my $class = shift;
|
||||
my $params = $class->SUPER::run_create_validators(@_);
|
||||
|
||||
my $product = delete $params->{product};
|
||||
$params->{product_id} = $product->id;
|
||||
$params->{name} = $class->_check_name($params->{name}, $product);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
my $changes = $self->SUPER::update(@_);
|
||||
|
||||
# Update the component_cc table if necessary.
|
||||
if (defined $self->{cc_ids}) {
|
||||
my $diff = $self->_update_cc_list($self->{cc_ids});
|
||||
$changes->{cc_list} = $diff if defined $diff;
|
||||
}
|
||||
return $changes;
|
||||
}
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
if ($self->bug_count) {
|
||||
if (Bugzilla->params->{'allowbugdeletion'}) {
|
||||
require Bugzilla::Bug;
|
||||
foreach my $bug_id (@{$self->bug_ids}) {
|
||||
# Note: We allow admins to delete bugs even if they can't
|
||||
# see them, as long as they can see the product.
|
||||
my $bug = new Bugzilla::Bug($bug_id);
|
||||
$bug->remove_from_db();
|
||||
}
|
||||
} else {
|
||||
ThrowUserError('component_has_bugs', {nb => $self->bug_count});
|
||||
}
|
||||
}
|
||||
|
||||
$dbh->do('DELETE FROM flaginclusions WHERE component_id = ?',
|
||||
undef, $self->id);
|
||||
$dbh->do('DELETE FROM flagexclusions WHERE component_id = ?',
|
||||
undef, $self->id);
|
||||
$dbh->do('DELETE FROM component_cc WHERE component_id = ?',
|
||||
undef, $self->id);
|
||||
$dbh->do('DELETE FROM components WHERE id = ?', undef, $self->id);
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
}
|
||||
|
||||
################################
|
||||
# Validators
|
||||
################################
|
||||
|
||||
sub _check_name {
|
||||
my ($invocant, $name, $product) = @_;
|
||||
|
||||
$name = trim($name);
|
||||
$name || ThrowUserError('component_blank_name');
|
||||
|
||||
if (length($name) > MAX_COMPONENT_SIZE) {
|
||||
ThrowUserError('component_name_too_long', {'name' => $name});
|
||||
}
|
||||
|
||||
$product = $invocant->product if (ref $invocant);
|
||||
my $component = new Bugzilla::Component({product => $product, name => $name});
|
||||
if ($component && (!ref $invocant || $component->id != $invocant->id)) {
|
||||
ThrowUserError('component_already_exists', { name => $component->name,
|
||||
product => $product });
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_description {
|
||||
my ($invocant, $description) = @_;
|
||||
|
||||
$description = trim($description);
|
||||
$description || ThrowUserError('component_blank_description');
|
||||
return $description;
|
||||
}
|
||||
|
||||
sub _check_initialowner {
|
||||
my ($invocant, $owner) = @_;
|
||||
|
||||
$owner || ThrowUserError('component_need_initialowner');
|
||||
my $owner_id = Bugzilla::User->check($owner)->id;
|
||||
return $owner_id;
|
||||
}
|
||||
|
||||
sub _check_initialqacontact {
|
||||
my ($invocant, $qa_contact) = @_;
|
||||
|
||||
my $qa_contact_id;
|
||||
if (Bugzilla->params->{'useqacontact'}) {
|
||||
$qa_contact_id = Bugzilla::User->check($qa_contact)->id if $qa_contact;
|
||||
}
|
||||
elsif (ref $invocant) {
|
||||
$qa_contact_id = $invocant->{initialqacontact};
|
||||
}
|
||||
return $qa_contact_id;
|
||||
}
|
||||
|
||||
sub _check_product {
|
||||
my ($invocant, $product) = @_;
|
||||
return Bugzilla->user->check_can_admin_product($product->name);
|
||||
}
|
||||
|
||||
sub _check_cc_list {
|
||||
my ($invocant, $cc_list) = @_;
|
||||
|
||||
my %cc_ids;
|
||||
foreach my $cc (@$cc_list) {
|
||||
my $id = login_to_id($cc, THROW_ERROR);
|
||||
$cc_ids{$id} = 1;
|
||||
}
|
||||
return [keys %cc_ids];
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
sub _update_cc_list {
|
||||
my ($self, $cc_list) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $old_cc_list =
|
||||
$dbh->selectcol_arrayref('SELECT user_id FROM component_cc
|
||||
WHERE component_id = ?', undef, $self->id);
|
||||
|
||||
my ($removed, $added) = diff_arrays($old_cc_list, $cc_list);
|
||||
my $diff;
|
||||
if (scalar @$removed || scalar @$added) {
|
||||
$diff = [join(', ', @$removed), join(', ', @$added)];
|
||||
}
|
||||
|
||||
$dbh->do('DELETE FROM component_cc WHERE component_id = ?', undef, $self->id);
|
||||
|
||||
my $sth = $dbh->prepare('INSERT INTO component_cc
|
||||
(user_id, component_id) VALUES (?, ?)');
|
||||
$sth->execute($_, $self->id) foreach (@$cc_list);
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
sub _create_series {
|
||||
my $self = shift;
|
||||
|
||||
# Insert default charting queries for this product.
|
||||
# If they aren't using charting, this won't do any harm.
|
||||
my $prodcomp = "&product=" . url_quote($self->product->name) .
|
||||
"&component=" . url_quote($self->name);
|
||||
|
||||
my $open_query = 'field0-0-0=resolution&type0-0-0=notregexp&value0-0-0=.' .
|
||||
$prodcomp;
|
||||
my $nonopen_query = 'field0-0-0=resolution&type0-0-0=regexp&value0-0-0=.' .
|
||||
$prodcomp;
|
||||
|
||||
my @series = ([get_text('series_all_open'), $open_query],
|
||||
[get_text('series_all_closed'), $nonopen_query]);
|
||||
|
||||
foreach my $sdata (@series) {
|
||||
my $series = new Bugzilla::Series(undef, $self->product->name,
|
||||
$self->name, $sdata->[0],
|
||||
Bugzilla->user->id, 1, $sdata->[1], 1);
|
||||
$series->writeToDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
sub set_name { $_[0]->set('name', $_[1]); }
|
||||
sub set_description { $_[0]->set('description', $_[1]); }
|
||||
sub set_default_assignee {
|
||||
my ($self, $owner) = @_;
|
||||
|
||||
$self->set('initialowner', $owner);
|
||||
# Reset the default owner object.
|
||||
delete $self->{default_assignee};
|
||||
}
|
||||
sub set_default_qa_contact {
|
||||
my ($self, $qa_contact) = @_;
|
||||
|
||||
$self->set('initialqacontact', $qa_contact);
|
||||
# Reset the default QA contact object.
|
||||
delete $self->{default_qa_contact};
|
||||
}
|
||||
sub set_cc_list {
|
||||
my ($self, $cc_list) = @_;
|
||||
|
||||
$self->{cc_ids} = $self->_check_cc_list($cc_list);
|
||||
# Reset the list of CC user objects.
|
||||
delete $self->{initial_cc};
|
||||
}
|
||||
|
||||
sub bug_count {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bug_count'}) {
|
||||
$self->{'bug_count'} = $dbh->selectrow_array(q{
|
||||
SELECT COUNT(*) FROM bugs
|
||||
WHERE component_id = ?}, undef, $self->id) || 0;
|
||||
}
|
||||
return $self->{'bug_count'};
|
||||
}
|
||||
|
||||
sub bug_ids {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bugs_ids'}) {
|
||||
$self->{'bugs_ids'} = $dbh->selectcol_arrayref(q{
|
||||
SELECT bug_id FROM bugs
|
||||
WHERE component_id = ?}, undef, $self->id);
|
||||
}
|
||||
return $self->{'bugs_ids'};
|
||||
}
|
||||
|
||||
sub default_assignee {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'default_assignee'}) {
|
||||
$self->{'default_assignee'} =
|
||||
new Bugzilla::User($self->{'initialowner'});
|
||||
}
|
||||
return $self->{'default_assignee'};
|
||||
}
|
||||
|
||||
sub default_qa_contact {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'default_qa_contact'}) {
|
||||
$self->{'default_qa_contact'} =
|
||||
new Bugzilla::User($self->{'initialqacontact'});
|
||||
}
|
||||
return $self->{'default_qa_contact'};
|
||||
}
|
||||
|
||||
sub flag_types {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'flag_types'}) {
|
||||
$self->{'flag_types'} = {};
|
||||
$self->{'flag_types'}->{'bug'} =
|
||||
Bugzilla::FlagType::match({ 'target_type' => 'bug',
|
||||
'product_id' => $self->product_id,
|
||||
'component_id' => $self->id });
|
||||
|
||||
$self->{'flag_types'}->{'attachment'} =
|
||||
Bugzilla::FlagType::match({ 'target_type' => 'attachment',
|
||||
'product_id' => $self->product_id,
|
||||
'component_id' => $self->id });
|
||||
}
|
||||
return $self->{'flag_types'};
|
||||
}
|
||||
|
||||
sub initial_cc {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'initial_cc'}) {
|
||||
# If set_cc_list() has been called but data are not yet written
|
||||
# into the DB, we want the new values defined by it.
|
||||
my $cc_ids = $self->{cc_ids}
|
||||
|| $dbh->selectcol_arrayref('SELECT user_id FROM component_cc
|
||||
WHERE component_id = ?',
|
||||
undef, $self->id);
|
||||
|
||||
$self->{'initial_cc'} = Bugzilla::User->new_from_list($cc_ids);
|
||||
}
|
||||
return $self->{'initial_cc'};
|
||||
}
|
||||
|
||||
sub product {
|
||||
my $self = shift;
|
||||
if (!defined $self->{'product'}) {
|
||||
require Bugzilla::Product; # We cannot |use| it.
|
||||
$self->{'product'} = new Bugzilla::Product($self->product_id);
|
||||
}
|
||||
return $self->{'product'};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Accessors ####
|
||||
###############################
|
||||
|
||||
sub id { return $_[0]->{'id'}; }
|
||||
sub name { return $_[0]->{'name'}; }
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
sub product_id { return $_[0]->{'product_id'}; }
|
||||
|
||||
###############################
|
||||
#### Subroutines ####
|
||||
###############################
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Component - Bugzilla product component class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Component;
|
||||
|
||||
my $component = new Bugzilla::Component($comp_id);
|
||||
my $component = new Bugzilla::Component({ product => $product, name => $name });
|
||||
|
||||
my $bug_count = $component->bug_count();
|
||||
my $bug_ids = $component->bug_ids();
|
||||
my $id = $component->id;
|
||||
my $name = $component->name;
|
||||
my $description = $component->description;
|
||||
my $product_id = $component->product_id;
|
||||
my $default_assignee = $component->default_assignee;
|
||||
my $default_qa_contact = $component->default_qa_contact;
|
||||
my $initial_cc = $component->initial_cc;
|
||||
my $product = $component->product;
|
||||
my $bug_flag_types = $component->flag_types->{'bug'};
|
||||
my $attach_flag_types = $component->flag_types->{'attachment'};
|
||||
|
||||
my $component = Bugzilla::Component->check({ product => $product, name => $name });
|
||||
|
||||
my $component =
|
||||
Bugzilla::Component->create({ name => $name,
|
||||
product => $product,
|
||||
initialowner => $user_login1,
|
||||
initialqacontact => $user_login2,
|
||||
description => $description});
|
||||
|
||||
$component->set_name($new_name);
|
||||
$component->set_description($new_description);
|
||||
$component->set_default_assignee($new_login_name);
|
||||
$component->set_default_qa_contact($new_login_name);
|
||||
$component->set_cc_list(\@new_login_names);
|
||||
$component->update();
|
||||
|
||||
$component->remove_from_db;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Component.pm represents a Product Component object.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<new($param)>
|
||||
|
||||
Description: The constructor is used to load an existing component
|
||||
by passing a component ID or a hash with the product
|
||||
object the component belongs to and the component name.
|
||||
|
||||
Params: $param - If you pass an integer, the integer is the
|
||||
component ID from the database that we want to
|
||||
read in. If you pass in a hash with the 'name'
|
||||
and 'product' keys, then the value of the name
|
||||
key is the name of a component being in the given
|
||||
product.
|
||||
|
||||
Returns: A Bugzilla::Component object.
|
||||
|
||||
=item C<bug_count()>
|
||||
|
||||
Description: Returns the total of bugs that belong to the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Integer with the number of bugs.
|
||||
|
||||
=item C<bugs_ids()>
|
||||
|
||||
Description: Returns all bug IDs that belong to the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A reference to an array of bug IDs.
|
||||
|
||||
=item C<default_assignee()>
|
||||
|
||||
Description: Returns a user object that represents the default assignee for
|
||||
the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A Bugzilla::User object.
|
||||
|
||||
=item C<default_qa_contact()>
|
||||
|
||||
Description: Returns a user object that represents the default QA contact for
|
||||
the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A Bugzilla::User object.
|
||||
|
||||
=item C<initial_cc>
|
||||
|
||||
Description: Returns a list of user objects representing users being
|
||||
in the initial CC list.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: An arrayref of L<Bugzilla::User> objects.
|
||||
|
||||
=item C<flag_types()>
|
||||
|
||||
Description: Returns all bug and attachment flagtypes available for
|
||||
the component.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Two references to an array of flagtype objects.
|
||||
|
||||
=item C<product()>
|
||||
|
||||
Description: Returns the product the component belongs to.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A Bugzilla::Product object.
|
||||
|
||||
=item C<set_name($new_name)>
|
||||
|
||||
Description: Changes the name of the component.
|
||||
|
||||
Params: $new_name - new name of the component (string). This name
|
||||
must be unique within the product.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_description($new_desc)>
|
||||
|
||||
Description: Changes the description of the component.
|
||||
|
||||
Params: $new_desc - new description of the component (string).
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_default_assignee($new_assignee)>
|
||||
|
||||
Description: Changes the default assignee of the component.
|
||||
|
||||
Params: $new_owner - login name of the new default assignee of
|
||||
the component (string). This user account
|
||||
must already exist.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_default_qa_contact($new_qa_contact)>
|
||||
|
||||
Description: Changes the default QA contact of the component.
|
||||
|
||||
Params: $new_qa_contact - login name of the new QA contact of
|
||||
the component (string). This user
|
||||
account must already exist.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_cc_list(\@cc_list)>
|
||||
|
||||
Description: Changes the list of users being in the CC list by default.
|
||||
|
||||
Params: \@cc_list - list of login names (string). All the user
|
||||
accounts must already exist.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<update()>
|
||||
|
||||
Description: Write changes made to the component into the DB.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A hashref with changes made to the component object.
|
||||
|
||||
=item C<remove_from_db()>
|
||||
|
||||
Description: Deletes the current component from the DB. The object itself
|
||||
is not destroyed.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CLASS METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<create(\%params)>
|
||||
|
||||
Description: Create a new component for the given product.
|
||||
|
||||
Params: The hashref must have the following keys:
|
||||
name - name of the new component (string). This name
|
||||
must be unique within the product.
|
||||
product - a Bugzilla::Product object to which
|
||||
the Component is being added.
|
||||
description - description of the new component (string).
|
||||
initialowner - login name of the default assignee (string).
|
||||
The following keys are optional:
|
||||
initiaqacontact - login name of the default QA contact (string),
|
||||
or an empty string to clear it.
|
||||
initial_cc - an arrayref of login names to add to the
|
||||
CC list by default.
|
||||
|
||||
Returns: A Bugzilla::Component object.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,412 +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 Bugzilla::Hook;
|
||||
use Data::Dumper;
|
||||
use File::Temp;
|
||||
|
||||
# Don't export localvars by default - people should have to explicitly
|
||||
# ask for it, as a (probably futile) attempt to stop code using it
|
||||
# when it shouldn't
|
||||
%Bugzilla::Config::EXPORT_TAGS =
|
||||
(
|
||||
admin => [qw(update_params SetParam write_params)],
|
||||
);
|
||||
Exporter::export_ok_tags('admin');
|
||||
|
||||
use vars qw(@param_list);
|
||||
|
||||
# INITIALISATION CODE
|
||||
# Perl throws a warning if we use bz_locations() directly after do.
|
||||
our %params;
|
||||
# Load in the param definitions
|
||||
sub _load_params {
|
||||
my $panels = param_panels();
|
||||
my %hook_panels;
|
||||
foreach my $panel (keys %$panels) {
|
||||
my $module = $panels->{$panel};
|
||||
eval("require $module") || die $@;
|
||||
my @new_param_list = $module->get_param_list();
|
||||
$hook_panels{lc($panel)} = { params => \@new_param_list };
|
||||
foreach my $item (@new_param_list) {
|
||||
$params{$item->{'name'}} = $item;
|
||||
}
|
||||
push(@param_list, @new_param_list);
|
||||
}
|
||||
# This hook is also called in editparams.cgi. This call here is required
|
||||
# to make SetParam work.
|
||||
Bugzilla::Hook::process('config-modify_panels',
|
||||
{ panels => \%hook_panels });
|
||||
}
|
||||
# END INIT CODE
|
||||
|
||||
# Subroutines go here
|
||||
|
||||
sub param_panels {
|
||||
my $param_panels = {};
|
||||
my $libpath = bz_locations()->{'libpath'};
|
||||
foreach my $item ((glob "$libpath/Bugzilla/Config/*.pm")) {
|
||||
$item =~ m#/([^/]+)\.pm$#;
|
||||
my $module = $1;
|
||||
$param_panels->{$module} = "Bugzilla::Config::$module" unless $module eq 'Common';
|
||||
}
|
||||
# Now check for any hooked params
|
||||
Bugzilla::Hook::process('config-add_panels',
|
||||
{ panel_modules => $param_panels });
|
||||
return $param_panels;
|
||||
}
|
||||
|
||||
sub SetParam {
|
||||
my ($name, $value) = @_;
|
||||
|
||||
_load_params unless %params;
|
||||
die "Unknown param $name" unless (exists $params{$name});
|
||||
|
||||
my $entry = $params{$name};
|
||||
|
||||
# sanity check the value
|
||||
|
||||
# XXX - This runs the checks. Which would be good, except that
|
||||
# check_shadowdb creates the database as a side effect, and so the
|
||||
# checker fails the second time around...
|
||||
if ($name ne 'shadowdb' && exists $entry->{'checker'}) {
|
||||
my $err = $entry->{'checker'}->($value, $entry);
|
||||
die "Param $name is not valid: $err" unless $err eq '';
|
||||
}
|
||||
|
||||
Bugzilla->params->{$name} = $value;
|
||||
}
|
||||
|
||||
sub update_params {
|
||||
my ($params) = @_;
|
||||
my $answer = Bugzilla->installation_answers;
|
||||
|
||||
my $param = read_param_file();
|
||||
|
||||
# If we didn't return any param values, then this is a new installation.
|
||||
my $new_install = !(keys %$param);
|
||||
|
||||
# --- UPDATE OLD PARAMS ---
|
||||
|
||||
# Old Bugzilla versions stored the version number in the params file
|
||||
# We don't want it, so get rid of it
|
||||
delete $param->{'version'};
|
||||
|
||||
# Change from usebrowserinfo to defaultplatform/defaultopsys combo
|
||||
if (exists $param->{'usebrowserinfo'}) {
|
||||
if (!$param->{'usebrowserinfo'}) {
|
||||
if (!exists $param->{'defaultplatform'}) {
|
||||
$param->{'defaultplatform'} = 'Other';
|
||||
}
|
||||
if (!exists $param->{'defaultopsys'}) {
|
||||
$param->{'defaultopsys'} = 'Other';
|
||||
}
|
||||
}
|
||||
delete $param->{'usebrowserinfo'};
|
||||
}
|
||||
|
||||
# Change from a boolean for quips to multi-state
|
||||
if (exists $param->{'usequip'} && !exists $param->{'enablequips'}) {
|
||||
$param->{'enablequips'} = $param->{'usequip'} ? 'on' : 'off';
|
||||
delete $param->{'usequip'};
|
||||
}
|
||||
|
||||
# Change from old product groups to controls for group_control_map
|
||||
# 2002-10-14 bug 147275 bugreport@peshkin.net
|
||||
if (exists $param->{'usebuggroups'} &&
|
||||
!exists $param->{'makeproductgroups'})
|
||||
{
|
||||
$param->{'makeproductgroups'} = $param->{'usebuggroups'};
|
||||
}
|
||||
if (exists $param->{'usebuggroupsentry'}
|
||||
&& !exists $param->{'useentrygroupdefault'}) {
|
||||
$param->{'useentrygroupdefault'} = $param->{'usebuggroupsentry'};
|
||||
}
|
||||
|
||||
# Modularise auth code
|
||||
if (exists $param->{'useLDAP'} && !exists $param->{'loginmethod'}) {
|
||||
$param->{'loginmethod'} = $param->{'useLDAP'} ? "LDAP" : "DB";
|
||||
}
|
||||
|
||||
# set verify method to whatever loginmethod was
|
||||
if (exists $param->{'loginmethod'}
|
||||
&& !exists $param->{'user_verify_class'})
|
||||
{
|
||||
$param->{'user_verify_class'} = $param->{'loginmethod'};
|
||||
delete $param->{'loginmethod'};
|
||||
}
|
||||
|
||||
# Remove quip-display control from parameters
|
||||
# and give it to users via User Settings (Bug 41972)
|
||||
if ( exists $param->{'enablequips'}
|
||||
&& !exists $param->{'quip_list_entry_control'})
|
||||
{
|
||||
my $new_value;
|
||||
($param->{'enablequips'} eq 'on') && do {$new_value = 'open';};
|
||||
($param->{'enablequips'} eq 'approved') && do {$new_value = 'moderated';};
|
||||
($param->{'enablequips'} eq 'frozen') && do {$new_value = 'closed';};
|
||||
($param->{'enablequips'} eq 'off') && do {$new_value = 'closed';};
|
||||
$param->{'quip_list_entry_control'} = $new_value;
|
||||
delete $param->{'enablequips'};
|
||||
}
|
||||
|
||||
# Old mail_delivery_method choices contained no uppercase characters
|
||||
if (exists $param->{'mail_delivery_method'}
|
||||
&& $param->{'mail_delivery_method'} !~ /[A-Z]/) {
|
||||
my $method = $param->{'mail_delivery_method'};
|
||||
my %translation = (
|
||||
'sendmail' => 'Sendmail',
|
||||
'smtp' => 'SMTP',
|
||||
'qmail' => 'Qmail',
|
||||
'testfile' => 'Test',
|
||||
'none' => 'None');
|
||||
$param->{'mail_delivery_method'} = $translation{$method};
|
||||
}
|
||||
|
||||
# --- DEFAULTS FOR NEW PARAMS ---
|
||||
|
||||
_load_params unless %params;
|
||||
foreach my $item (@param_list) {
|
||||
my $name = $item->{'name'};
|
||||
unless (exists $param->{$name}) {
|
||||
print "New parameter: $name\n" unless $new_install;
|
||||
$param->{$name} = $answer->{$name} || $item->{'default'};
|
||||
}
|
||||
}
|
||||
|
||||
$param->{'utf8'} = 1 if $new_install;
|
||||
|
||||
# --- REMOVE OLD PARAMS ---
|
||||
|
||||
my %oldparams;
|
||||
# Remove any old params, put them in old-params.txt
|
||||
foreach my $item (keys %$param) {
|
||||
if (!grep($_ eq $item, map ($_->{'name'}, @param_list))) {
|
||||
$oldparams{$item} = $param->{$item};
|
||||
delete $param->{$item};
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar(keys %oldparams)) {
|
||||
my $op_file = new IO::File('old-params.txt', '>>', 0600)
|
||||
|| die "old-params.txt: $!";
|
||||
|
||||
print "The following parameters are no longer used in Bugzilla,",
|
||||
" and so have been\nmoved from your parameters file into",
|
||||
" old-params.txt:\n";
|
||||
|
||||
local $Data::Dumper::Terse = 1;
|
||||
local $Data::Dumper::Indent = 0;
|
||||
|
||||
my $comma = "";
|
||||
foreach my $item (keys %oldparams) {
|
||||
print $op_file "\n\n$item:\n" . Data::Dumper->Dump([$oldparams{$item}]) . "\n";
|
||||
print "${comma}$item";
|
||||
$comma = ", ";
|
||||
}
|
||||
print "\n";
|
||||
$op_file->close;
|
||||
}
|
||||
|
||||
if (ON_WINDOWS && !-e SENDMAIL_EXE
|
||||
&& $param->{'mail_delivery_method'} eq 'Sendmail')
|
||||
{
|
||||
my $smtp = $answer->{'SMTP_SERVER'};
|
||||
if (!$smtp) {
|
||||
print "\nBugzilla requires an SMTP server to function on",
|
||||
" Windows.\nPlease enter your SMTP server's hostname: ";
|
||||
$smtp = <STDIN>;
|
||||
chomp $smtp;
|
||||
if ($smtp) {
|
||||
$param->{'smtpserver'} = $smtp;
|
||||
}
|
||||
else {
|
||||
print "\nWarning: No SMTP Server provided, defaulting to",
|
||||
" localhost\n";
|
||||
}
|
||||
}
|
||||
|
||||
$param->{'mail_delivery_method'} = 'SMTP';
|
||||
}
|
||||
|
||||
write_params($param);
|
||||
|
||||
# Return deleted params and values so that checksetup.pl has a chance
|
||||
# to convert old params to new data.
|
||||
return %oldparams;
|
||||
}
|
||||
|
||||
sub write_params {
|
||||
my ($param_data) = @_;
|
||||
$param_data ||= Bugzilla->params;
|
||||
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
my $param_file = "$datadir/params";
|
||||
|
||||
local $Data::Dumper::Sortkeys = 1;
|
||||
|
||||
my ($fh, $tmpname) = File::Temp::tempfile('params.XXXXX',
|
||||
DIR => $datadir );
|
||||
|
||||
print $fh (Data::Dumper->Dump([$param_data], ['*param']))
|
||||
|| die "Can't write param file: $!";
|
||||
|
||||
close $fh;
|
||||
|
||||
rename $tmpname, $param_file
|
||||
or die "Can't rename $tmpname to $param_file: $!";
|
||||
|
||||
ChmodDataFile($param_file, 0666);
|
||||
|
||||
# And now we have to reset the params cache so that Bugzilla will re-read
|
||||
# them.
|
||||
delete Bugzilla->request_cache->{params};
|
||||
}
|
||||
|
||||
# Some files in the data directory must be world readable if and only if
|
||||
# we don't have a webserver group. Call this function to do this.
|
||||
# This will become a private function once all the datafile handling stuff
|
||||
# moves into this package
|
||||
|
||||
# This sub is not perldoc'd for that reason - noone should know about it
|
||||
sub ChmodDataFile {
|
||||
my ($file, $mask) = @_;
|
||||
my $perm = 0770;
|
||||
if ((stat(bz_locations()->{'datadir'}))[2] & 0002) {
|
||||
$perm = 0777;
|
||||
}
|
||||
$perm = $perm & $mask;
|
||||
chmod $perm,$file;
|
||||
}
|
||||
|
||||
sub read_param_file {
|
||||
my %params;
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
if (-e "$datadir/params") {
|
||||
# Note that checksetup.pl sets file permissions on '$datadir/params'
|
||||
|
||||
# Using Safe mode is _not_ a guarantee of safety if someone does
|
||||
# manage to write to the file. However, it won't hurt...
|
||||
# See bug 165144 for not needing to eval this at all
|
||||
my $s = new Safe;
|
||||
|
||||
$s->rdo("$datadir/params");
|
||||
die "Error reading $datadir/params: $!" if $!;
|
||||
die "Error evaluating $datadir/params: $@" if $@;
|
||||
|
||||
# Now read the param back out from the sandbox
|
||||
%params = %{$s->varglob('param')};
|
||||
}
|
||||
elsif ($ENV{'SERVER_SOFTWARE'}) {
|
||||
# We're in a CGI, but the params file doesn't exist. We can't
|
||||
# Template Toolkit, or even install_string, since checksetup
|
||||
# might not have thrown an error. Bugzilla::CGI->new
|
||||
# hasn't even been called yet, so we manually use CGI::Carp here
|
||||
# so that the user sees the error.
|
||||
require CGI::Carp;
|
||||
CGI::Carp->import('fatalsToBrowser');
|
||||
die "The $datadir/params file does not exist."
|
||||
. ' You probably need to run checksetup.pl.',
|
||||
}
|
||||
return \%params;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Config - Configuration parameters for Bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
# Administration functions
|
||||
use Bugzilla::Config qw(:admin);
|
||||
|
||||
update_params();
|
||||
SetParam($param, $value);
|
||||
write_params();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This package contains ways to access Bugzilla configuration parameters.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=head2 Parameters
|
||||
|
||||
Parameters can be set, retrieved, and updated.
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<SetParam($name, $value)>
|
||||
|
||||
Sets the param named $name to $value. Values are checked using the checker
|
||||
function for the given param if one exists.
|
||||
|
||||
=item C<update_params()>
|
||||
|
||||
Updates the parameters, by transitioning old params to new formats, setting
|
||||
defaults for new params, and removing obsolete ones. Used by F<checksetup.pl>
|
||||
in the process of an installation or upgrade.
|
||||
|
||||
Prints out information about what it's doing, if it makes any changes.
|
||||
|
||||
May prompt the user for input, if certain required parameters are not
|
||||
specified.
|
||||
|
||||
=item C<write_params($params)>
|
||||
|
||||
Description: Writes the parameters to disk.
|
||||
|
||||
Params: C<$params> (optional) - A hashref to write to the disk
|
||||
instead of C<Bugzilla->params>. Used only by
|
||||
C<update_params>.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=item C<read_param_file()>
|
||||
|
||||
Description: Most callers should never need this. This is used
|
||||
by C<Bugzilla->params> to directly read C<$datadir/params>
|
||||
and load it into memory. Use C<Bugzilla->params> instead.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: A hashref containing the current params in C<$datadir/params>.
|
||||
|
||||
=back
|
||||
@@ -1,63 +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 => 1
|
||||
},
|
||||
|
||||
{
|
||||
name => 'allowuserdeletion',
|
||||
type => 'b',
|
||||
default => 0
|
||||
});
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,98 +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_display',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'attachment_base',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_urlbase
|
||||
},
|
||||
|
||||
{
|
||||
name => 'allow_attachment_deletion',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
{
|
||||
name => 'allow_attach_url',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'maxattachmentsize',
|
||||
type => 't',
|
||||
default => '1000',
|
||||
checker => \&check_maxattachmentsize
|
||||
},
|
||||
|
||||
# The maximum size (in bytes) for patches and non-patch attachments.
|
||||
# The default limit is 1000KB, which is 24KB less than mysql's default
|
||||
# maximum packet size (which determines how much data can be sent in a
|
||||
# single mysql packet and thus how much data can be inserted into the
|
||||
# database) to provide breathing space for the data in other fields of
|
||||
# the attachment record as well as any mysql packet overhead (I don't
|
||||
# know of any, but I suspect there may be some.)
|
||||
|
||||
{
|
||||
name => 'maxlocalattachment',
|
||||
type => 't',
|
||||
default => '0',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'convert_uncompressed_images',
|
||||
type => 'b',
|
||||
default => 0,
|
||||
checker => \&check_image_converter
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,135 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Auth;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::Auth::sortkey = "02";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'auth_env_id',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'auth_env_email',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'auth_env_realname',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
# XXX in the future:
|
||||
#
|
||||
# user_verify_class and user_info_class should have choices gathered from
|
||||
# whatever sits in their respective directories
|
||||
#
|
||||
# rather than comma-separated lists, these two should eventually become
|
||||
# arrays, but that requires alterations to editparams first
|
||||
|
||||
{
|
||||
name => 'user_info_class',
|
||||
type => 's',
|
||||
choices => [ 'CGI', 'Env', 'Env,CGI' ],
|
||||
default => 'CGI',
|
||||
checker => \&check_multi
|
||||
},
|
||||
|
||||
{
|
||||
name => 'user_verify_class',
|
||||
type => 'o',
|
||||
choices => [ 'DB', 'RADIUS', 'LDAP' ],
|
||||
default => 'DB',
|
||||
checker => \&check_user_verify_class
|
||||
},
|
||||
|
||||
{
|
||||
name => 'rememberlogin',
|
||||
type => 's',
|
||||
choices => ['on', 'defaulton', 'defaultoff', 'off'],
|
||||
default => 'on',
|
||||
checker => \&check_multi
|
||||
},
|
||||
|
||||
{
|
||||
name => 'loginnetmask',
|
||||
type => 't',
|
||||
default => '0',
|
||||
checker => \&check_netmask
|
||||
},
|
||||
|
||||
{
|
||||
name => 'requirelogin',
|
||||
type => 'b',
|
||||
default => '0'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'emailregexp',
|
||||
type => 't',
|
||||
default => q:^[\\w\\.\\+\\-=]+@[\\w\\.\\-]+\\.[\\w\\-]+$:,
|
||||
checker => \&check_regexp
|
||||
},
|
||||
|
||||
{
|
||||
name => 'emailregexpdesc',
|
||||
type => 'l',
|
||||
default => 'A legal address must contain exactly one \'@\', and at least ' .
|
||||
'one \'.\' after the @.'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'emailsuffix',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'createemailregexp',
|
||||
type => 't',
|
||||
default => q:.*:,
|
||||
checker => \&check_regexp
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,103 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::BugChange;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
use Bugzilla::Status;
|
||||
|
||||
$Bugzilla::Config::BugChange::sortkey = "03";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
|
||||
# Hardcoded bug statuses which existed before Bugzilla 3.1.
|
||||
my @closed_bug_statuses = ('RESOLVED', 'VERIFIED', 'CLOSED');
|
||||
|
||||
# If we are upgrading from 3.0 or older, bug statuses are not customisable
|
||||
# and bug_status.is_open is not yet defined (hence the eval), so we use
|
||||
# the bug statuses above as they are still hardcoded.
|
||||
eval {
|
||||
my @current_closed_states = map {$_->name} closed_bug_statuses();
|
||||
# If no closed state was found, use the default list above.
|
||||
@closed_bug_statuses = @current_closed_states if scalar(@current_closed_states);
|
||||
};
|
||||
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'duplicate_or_move_bug_status',
|
||||
type => 's',
|
||||
choices => \@closed_bug_statuses,
|
||||
default => $closed_bug_statuses[0],
|
||||
checker => \&check_bug_status
|
||||
},
|
||||
|
||||
{
|
||||
name => 'letsubmitterchoosepriority',
|
||||
type => 'b',
|
||||
default => 1
|
||||
},
|
||||
|
||||
{
|
||||
name => 'letsubmitterchoosemilestone',
|
||||
type => 'b',
|
||||
default => 1
|
||||
},
|
||||
|
||||
{
|
||||
name => 'musthavemilestoneonaccept',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'commentonchange_resolution',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'commentonduplicate',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'noresolveonopenblockers',
|
||||
type => 'b',
|
||||
default => 0,
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,126 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::BugFields;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
use Bugzilla::Field;
|
||||
|
||||
$Bugzilla::Config::BugFields::sortkey = "04";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
|
||||
my @legal_priorities = @{get_legal_field_values('priority')};
|
||||
my @legal_severities = @{get_legal_field_values('bug_severity')};
|
||||
my @legal_platforms = @{get_legal_field_values('rep_platform')};
|
||||
my @legal_OS = @{get_legal_field_values('op_sys')};
|
||||
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'useclassification',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usetargetmilestone',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'useqacontact',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usestatuswhiteboard',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usevotes',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usebugaliases',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'use_see_also',
|
||||
type => 'b',
|
||||
default => 1
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultpriority',
|
||||
type => 's',
|
||||
choices => \@legal_priorities,
|
||||
default => $legal_priorities[-1],
|
||||
checker => \&check_priority
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultseverity',
|
||||
type => 's',
|
||||
choices => \@legal_severities,
|
||||
default => $legal_severities[-1],
|
||||
checker => \&check_severity
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultplatform',
|
||||
type => 's',
|
||||
choices => ['', @legal_platforms],
|
||||
default => '',
|
||||
checker => \&check_platform
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultopsys',
|
||||
type => 's',
|
||||
choices => ['', @legal_OS],
|
||||
default => '',
|
||||
checker => \&check_opsys
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,93 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::BugMove;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::BugMove::sortkey = "05";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'move-enabled',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'move-button-text',
|
||||
type => 't',
|
||||
default => 'Move To Bugscape'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'move-to-url',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'move-to-address',
|
||||
type => 't',
|
||||
default => 'bugzilla-import'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'moved-from-address',
|
||||
type => 't',
|
||||
default => 'bugzilla-admin'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'movers',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'moved-default-product',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'moved-default-component',
|
||||
type => 't',
|
||||
default => ''
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,462 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Common;
|
||||
|
||||
use strict;
|
||||
|
||||
use Socket;
|
||||
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Field;
|
||||
use Bugzilla::Group;
|
||||
use Bugzilla::Status;
|
||||
|
||||
use base qw(Exporter);
|
||||
@Bugzilla::Config::Common::EXPORT =
|
||||
qw(check_multi check_numeric check_regexp check_url check_group
|
||||
check_sslbase check_priority check_severity check_platform
|
||||
check_opsys check_shadowdb check_urlbase check_webdotbase
|
||||
check_netmask check_user_verify_class check_image_converter
|
||||
check_mail_delivery_method check_notification check_utf8
|
||||
check_bug_status check_smtp_auth check_theschwartz_available
|
||||
check_maxattachmentsize
|
||||
);
|
||||
|
||||
# Checking functions for the various values
|
||||
|
||||
sub check_multi {
|
||||
my ($value, $param) = (@_);
|
||||
|
||||
if ($param->{'type'} eq "s") {
|
||||
unless (scalar(grep {$_ eq $value} (@{$param->{'choices'}}))) {
|
||||
return "Invalid choice '$value' for single-select list param '$param->{'name'}'";
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
elsif ($param->{'type'} eq 'm' || $param->{'type'} eq 'o') {
|
||||
foreach my $chkParam (split(',', $value)) {
|
||||
unless (scalar(grep {$_ eq $chkParam} (@{$param->{'choices'}}))) {
|
||||
return "Invalid choice '$chkParam' for multi-select list param '$param->{'name'}'";
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
else {
|
||||
return "Invalid param type '$param->{'type'}' for check_multi(); " .
|
||||
"contact your Bugzilla administrator";
|
||||
}
|
||||
}
|
||||
|
||||
sub check_numeric {
|
||||
my ($value) = (@_);
|
||||
if ($value !~ /^[0-9]+$/) {
|
||||
return "must be a numeric value";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_regexp {
|
||||
my ($value) = (@_);
|
||||
eval { qr/$value/ };
|
||||
return $@;
|
||||
}
|
||||
|
||||
sub check_sslbase {
|
||||
my $url = shift;
|
||||
if ($url ne '') {
|
||||
if ($url !~ m#^https://([^/]+).*/$#) {
|
||||
return "must be a legal URL, that starts with https and ends with a slash.";
|
||||
}
|
||||
my $host = $1;
|
||||
# Fall back to port 443 if for some reason getservbyname() fails.
|
||||
my $port = getservbyname('https', 'tcp') || 443;
|
||||
if ($host =~ /^(.+):(\d+)$/) {
|
||||
$host = $1;
|
||||
$port = $2;
|
||||
}
|
||||
local *SOCK;
|
||||
my $proto = getprotobyname('tcp');
|
||||
socket(SOCK, PF_INET, SOCK_STREAM, $proto);
|
||||
my $iaddr = inet_aton($host) || return "The host $host cannot be resolved";
|
||||
my $sin = sockaddr_in($port, $iaddr);
|
||||
if (!connect(SOCK, $sin)) {
|
||||
return "Failed to connect to $host:$port; unable to enable SSL";
|
||||
}
|
||||
close(SOCK);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_utf8 {
|
||||
my $utf8 = shift;
|
||||
# You cannot turn off the UTF-8 parameter if you've already converted
|
||||
# your tables to utf-8.
|
||||
my $dbh = Bugzilla->dbh;
|
||||
if ($dbh->isa('Bugzilla::DB::Mysql') && $dbh->bz_db_is_utf8 && !$utf8) {
|
||||
return "You cannot disable UTF-8 support, because your MySQL database"
|
||||
. " is encoded in UTF-8";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_priority {
|
||||
my ($value) = (@_);
|
||||
my $legal_priorities = get_legal_field_values('priority');
|
||||
if (lsearch($legal_priorities, $value) < 0) {
|
||||
return "Must be a legal priority value: one of " .
|
||||
join(", ", @$legal_priorities);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_severity {
|
||||
my ($value) = (@_);
|
||||
my $legal_severities = get_legal_field_values('bug_severity');
|
||||
if (lsearch($legal_severities, $value) < 0) {
|
||||
return "Must be a legal severity value: one of " .
|
||||
join(", ", @$legal_severities);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_platform {
|
||||
my ($value) = (@_);
|
||||
my $legal_platforms = get_legal_field_values('rep_platform');
|
||||
if (lsearch(['', @$legal_platforms], $value) < 0) {
|
||||
return "Must be empty or a legal platform value: one of " .
|
||||
join(", ", @$legal_platforms);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_opsys {
|
||||
my ($value) = (@_);
|
||||
my $legal_OS = get_legal_field_values('op_sys');
|
||||
if (lsearch(['', @$legal_OS], $value) < 0) {
|
||||
return "Must be empty or a legal operating system value: one of " .
|
||||
join(", ", @$legal_OS);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_bug_status {
|
||||
my $bug_status = shift;
|
||||
my @closed_bug_statuses = map {$_->name} closed_bug_statuses();
|
||||
if (lsearch(\@closed_bug_statuses, $bug_status) < 0) {
|
||||
return "Must be a valid closed status: one of " . join(', ', @closed_bug_statuses);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_group {
|
||||
my $group_name = shift;
|
||||
return "" unless $group_name;
|
||||
my $group = new Bugzilla::Group({'name' => $group_name});
|
||||
unless (defined $group) {
|
||||
return "Must be an existing group name";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_shadowdb {
|
||||
my ($value) = (@_);
|
||||
$value = trim($value);
|
||||
if ($value eq "") {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!Bugzilla->params->{'shadowdbhost'}) {
|
||||
return "You need to specify a host when using a shadow database";
|
||||
}
|
||||
|
||||
# Can't test existence of this because ConnectToDatabase uses the param,
|
||||
# but we can't set this before testing....
|
||||
# This can really only be fixed after we can use the DBI more openly
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_urlbase {
|
||||
my ($url) = (@_);
|
||||
if ($url && $url !~ m:^http.*/$:) {
|
||||
return "must be a legal URL, that starts with http and ends with a slash.";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_url {
|
||||
my ($url) = (@_);
|
||||
return '' if $url eq ''; # Allow empty URLs
|
||||
if ($url !~ m:/$:) {
|
||||
return 'must be a legal URL, absolute or relative, ending with a slash.';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
sub check_webdotbase {
|
||||
my ($value) = (@_);
|
||||
$value = trim($value);
|
||||
if ($value eq "") {
|
||||
return "";
|
||||
}
|
||||
if($value !~ /^https?:/) {
|
||||
if(! -x $value) {
|
||||
return "The file path \"$value\" is not a valid executable. Please specify the complete file path to 'dot' if you intend to generate graphs locally.";
|
||||
}
|
||||
# Check .htaccess allows access to generated images
|
||||
my $webdotdir = bz_locations()->{'webdotdir'};
|
||||
if(-e "$webdotdir/.htaccess") {
|
||||
open HTACCESS, "$webdotdir/.htaccess";
|
||||
if(! grep(/ \\\.png\$/,<HTACCESS>)) {
|
||||
return "Dependency graph images are not accessible.\nAssuming that you have not modified the file, delete $webdotdir/.htaccess and re-run checksetup.pl to rectify.\n";
|
||||
}
|
||||
close HTACCESS;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_netmask {
|
||||
my ($mask) = @_;
|
||||
my $res = check_numeric($mask);
|
||||
return $res if $res;
|
||||
if ($mask < 0 || $mask > 32) {
|
||||
return "an IPv4 netmask must be between 0 and 32 bits";
|
||||
}
|
||||
# Note that if we changed the netmask from anything apart from 32, then
|
||||
# existing logincookies which aren't for a single IP won't work
|
||||
# any more. We can't know which ones they are, though, so they'll just
|
||||
# take space until they're periodically cleared, later.
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_user_verify_class {
|
||||
# doeditparams traverses the list of params, and for each one it checks,
|
||||
# then updates. This means that if one param checker wants to look at
|
||||
# other params, it must be below that other one. So you can't have two
|
||||
# params mutually dependent on each other.
|
||||
# This means that if someone clears the LDAP config params after setting
|
||||
# the login method as LDAP, we won't notice, but all logins will fail.
|
||||
# So don't do that.
|
||||
|
||||
my ($list, $entry) = @_;
|
||||
$list || return 'You need to specify at least one authentication mechanism';
|
||||
for my $class (split /,\s*/, $list) {
|
||||
my $res = check_multi($class, $entry);
|
||||
return $res if $res;
|
||||
if ($class eq 'RADIUS') {
|
||||
eval "require Authen::Radius";
|
||||
return "Error requiring Authen::Radius: '$@'" if $@;
|
||||
return "RADIUS servername (RADIUS_server) is missing" unless Bugzilla->params->{"RADIUS_server"};
|
||||
return "RADIUS_secret is empty" unless Bugzilla->params->{"RADIUS_secret"};
|
||||
}
|
||||
elsif ($class eq 'LDAP') {
|
||||
eval "require Net::LDAP";
|
||||
return "Error requiring Net::LDAP: '$@'" if $@;
|
||||
return "LDAP servername (LDAPserver) is missing" unless Bugzilla->params->{"LDAPserver"};
|
||||
return "LDAPBaseDN is empty" unless Bugzilla->params->{"LDAPBaseDN"};
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_image_converter {
|
||||
my ($value, $hash) = @_;
|
||||
if ($value == 1){
|
||||
eval "require Image::Magick";
|
||||
return "Error requiring Image::Magick: '$@'" if $@;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_mail_delivery_method {
|
||||
my $check = check_multi(@_);
|
||||
return $check if $check;
|
||||
my $mailer = shift;
|
||||
if ($mailer eq 'sendmail' && $^O =~ /MSWin32/i) {
|
||||
# look for sendmail.exe
|
||||
return "Failed to locate " . SENDMAIL_EXE
|
||||
unless -e SENDMAIL_EXE;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_maxattachmentsize {
|
||||
my $check = check_numeric(@_);
|
||||
return $check if $check;
|
||||
my $size = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
if ($dbh->isa('Bugzilla::DB::Mysql')) {
|
||||
my (undef, $max_packet) = $dbh->selectrow_array(
|
||||
q{SHOW VARIABLES LIKE 'max\_allowed\_packet'});
|
||||
my $byte_size = $size * 1024;
|
||||
if ($max_packet < $byte_size) {
|
||||
return "You asked for a maxattachmentsize of $byte_size bytes,"
|
||||
. " but the max_allowed_packet setting in MySQL currently"
|
||||
. " only allows packets up to $max_packet bytes";
|
||||
}
|
||||
}
|
||||
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_smtp_auth {
|
||||
my $username = shift;
|
||||
if ($username) {
|
||||
eval "require Authen::SASL";
|
||||
return "Error requiring Authen::SASL: '$@'" if $@;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub check_theschwartz_available {
|
||||
if (!eval { require TheSchwartz; require Daemon::Generic; }) {
|
||||
return "Using the job queue requires that you have certain Perl"
|
||||
. " modules installed. See the output of checksetup.pl"
|
||||
. " for more information";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
# OK, here are the parameter definitions themselves.
|
||||
#
|
||||
# Each definition is a hash with keys:
|
||||
#
|
||||
# name - name of the param
|
||||
# desc - description of the param (for editparams.cgi)
|
||||
# type - see below
|
||||
# choices - (optional) see below
|
||||
# default - default value for the param
|
||||
# checker - (optional) checking function for validating parameter entry
|
||||
# It is called with the value of the param as the first arg and a
|
||||
# reference to the param's hash as the second argument
|
||||
#
|
||||
# The type value can be one of the following:
|
||||
#
|
||||
# t -- A short text entry field (suitable for a single line)
|
||||
# p -- A short text entry field (as with type = 't'), but the string is
|
||||
# replaced by asterisks (appropriate for passwords)
|
||||
# l -- A long text field (suitable for many lines)
|
||||
# b -- A boolean value (either 1 or 0)
|
||||
# m -- A list of values, with many selectable (shows up as a select box)
|
||||
# To specify the list of values, make the 'choices' key be an array
|
||||
# reference of the valid choices. The 'default' key should be a string
|
||||
# with a list of selected values (as a comma-separated list), i.e.:
|
||||
# {
|
||||
# name => 'multiselect',
|
||||
# desc => 'A list of options, choose many',
|
||||
# type => 'm',
|
||||
# choices => [ 'a', 'b', 'c', 'd' ],
|
||||
# default => [ 'a', 'd' ],
|
||||
# checker => \&check_multi
|
||||
# }
|
||||
#
|
||||
# Here, 'a' and 'd' are the default options, and the user may pick any
|
||||
# combination of a, b, c, and d as valid options.
|
||||
#
|
||||
# &check_multi should always be used as the param verification function
|
||||
# for list (single and multiple) parameter types.
|
||||
#
|
||||
# o -- A list of values, orderable, and with many selectable (shows up as a
|
||||
# JavaScript-enhanced select box if JavaScript is enabled, and a text
|
||||
# entry field if not)
|
||||
# Set up in the same way as type m.
|
||||
#
|
||||
# s -- A list of values, with one selectable (shows up as a select box)
|
||||
# To specify the list of values, make the 'choices' key be an array
|
||||
# reference of the valid choices. The 'default' key should be one of
|
||||
# those values, i.e.:
|
||||
# {
|
||||
# name => 'singleselect',
|
||||
# desc => 'A list of options, choose one',
|
||||
# type => 's',
|
||||
# choices => [ 'a', 'b', 'c' ],
|
||||
# default => 'b',
|
||||
# checker => \&check_multi
|
||||
# }
|
||||
#
|
||||
# Here, 'b' is the default option, and 'a' and 'c' are other possible
|
||||
# options, but only one at a time!
|
||||
#
|
||||
# &check_multi should always be used as the param verification function
|
||||
# for list (single and multiple) parameter types.
|
||||
|
||||
sub get_param_list {
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Config::Common - Parameter checking functions
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
All parameter checking functions are called with two parameters:
|
||||
|
||||
=head2 Functions
|
||||
|
||||
=over
|
||||
|
||||
=item C<check_multi>
|
||||
|
||||
Checks that a multi-valued parameter (ie types C<s>, C<o> or C<m>) satisfies
|
||||
its contraints.
|
||||
|
||||
=item C<check_numeric>
|
||||
|
||||
Checks that the value is a valid number
|
||||
|
||||
=item C<check_regexp>
|
||||
|
||||
Checks that the value is a valid regexp
|
||||
|
||||
=back
|
||||
@@ -1,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::Core;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::Core::sortkey = "00";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'maintainer',
|
||||
type => 't',
|
||||
default => 'THE MAINTAINER HAS NOT YET BEEN SET'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'urlbase',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_urlbase
|
||||
},
|
||||
|
||||
{
|
||||
name => 'docs_urlbase',
|
||||
type => 't',
|
||||
default => 'docs/%lang%/html/',
|
||||
checker => \&check_url
|
||||
},
|
||||
|
||||
{
|
||||
name => 'sslbase',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_sslbase
|
||||
},
|
||||
|
||||
{
|
||||
name => 'ssl',
|
||||
type => 's',
|
||||
choices => ['never', 'authenticated sessions', 'always'],
|
||||
default => 'never'
|
||||
},
|
||||
|
||||
|
||||
{
|
||||
name => 'cookiedomain',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'cookiepath',
|
||||
type => 't',
|
||||
default => '/'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'utf8',
|
||||
type => 'b',
|
||||
default => '0',
|
||||
checker => \&check_utf8
|
||||
},
|
||||
|
||||
{
|
||||
name => 'shutdownhtml',
|
||||
type => 'l',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'announcehtml',
|
||||
type => 'l',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'proxy_url',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'upgrade_notification',
|
||||
type => 's',
|
||||
choices => ['development_snapshot', 'latest_stable_release',
|
||||
'stable_branch_release', 'disabled'],
|
||||
default => 'latest_stable_release',
|
||||
checker => \&check_notification
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,52 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::DependencyGraph;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::DependencyGraph::sortkey = "06";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'webdotbase',
|
||||
type => 't',
|
||||
default => 'http://www.research.att.com/~north/cgi-bin/webdot.cgi/%urlbase%',
|
||||
checker => \&check_webdotbase
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,108 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::GroupSecurity;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
use Bugzilla::Group;
|
||||
|
||||
$Bugzilla::Config::GroupSecurity::sortkey = "07";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'makeproductgroups',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'useentrygroupdefault',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'chartgroup',
|
||||
type => 's',
|
||||
choices => \&_get_all_group_names,
|
||||
default => 'editbugs',
|
||||
checker => \&check_group
|
||||
},
|
||||
|
||||
{
|
||||
name => 'insidergroup',
|
||||
type => 's',
|
||||
choices => \&_get_all_group_names,
|
||||
default => '',
|
||||
checker => \&check_group
|
||||
},
|
||||
|
||||
{
|
||||
name => 'timetrackinggroup',
|
||||
type => 's',
|
||||
choices => \&_get_all_group_names,
|
||||
default => 'editbugs',
|
||||
checker => \&check_group
|
||||
},
|
||||
|
||||
{
|
||||
name => 'querysharegroup',
|
||||
type => 's',
|
||||
choices => \&_get_all_group_names,
|
||||
default => 'editbugs',
|
||||
checker => \&check_group
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usevisibilitygroups',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'strict_isolation',
|
||||
type => 'b',
|
||||
default => 0
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
sub _get_all_group_names {
|
||||
my @group_names = map {$_->name} Bugzilla::Group->get_all;
|
||||
unshift(@group_names, '');
|
||||
return \@group_names;
|
||||
}
|
||||
1;
|
||||
@@ -1,87 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::LDAP;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::LDAP::sortkey = "09";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'LDAPserver',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPstarttls',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPbinddn',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPBaseDN',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPuidattribute',
|
||||
type => 't',
|
||||
default => 'uid'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPmailattribute',
|
||||
type => 't',
|
||||
default => 'mail'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'LDAPfilter',
|
||||
type => 't',
|
||||
default => '',
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,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::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 => 'use_mailer_queue',
|
||||
type => 'b',
|
||||
default => 0,
|
||||
checker => \&check_theschwartz_available,
|
||||
},
|
||||
|
||||
{
|
||||
name => 'sendmailnow',
|
||||
type => 'b',
|
||||
default => 1
|
||||
},
|
||||
|
||||
{
|
||||
name => 'smtpserver',
|
||||
type => 't',
|
||||
default => 'localhost'
|
||||
},
|
||||
{
|
||||
name => 'smtp_username',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_smtp_auth
|
||||
},
|
||||
{
|
||||
name => 'smtp_password',
|
||||
type => 'p',
|
||||
default => ''
|
||||
},
|
||||
{
|
||||
name => 'smtp_debug',
|
||||
type => 'b',
|
||||
default => 0
|
||||
},
|
||||
{
|
||||
name => 'whinedays',
|
||||
type => 't',
|
||||
default => 7,
|
||||
checker => \&check_numeric
|
||||
},
|
||||
{
|
||||
name => 'globalwatchers',
|
||||
type => 't',
|
||||
default => '',
|
||||
}, );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,75 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::PatchViewer;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::PatchViewer::sortkey = "11";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'cvsroot',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'cvsroot_get',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'bonsai_url',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'lxr_url',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'lxr_root',
|
||||
type => 't',
|
||||
default => '',
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,87 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::Query;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::Query::sortkey = "12";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'quip_list_entry_control',
|
||||
type => 's',
|
||||
choices => ['open', 'moderated', 'closed'],
|
||||
default => 'open',
|
||||
checker => \&check_multi
|
||||
},
|
||||
|
||||
{
|
||||
name => 'mostfreqthreshold',
|
||||
type => 't',
|
||||
default => '2',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'mybugstemplate',
|
||||
type => 't',
|
||||
default => 'buglist.cgi?bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailreporter1=1&emailtype1=exact&email1=%userid%&field0-0-0=bug_status&type0-0-0=notequals&value0-0-0=UNCONFIRMED&field0-0-1=reporter&type0-0-1=equals&value0-0-1=%userid%'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'defaultquery',
|
||||
type => 't',
|
||||
default => 'bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailqa_contact2=1&order=Importance&long_desc_type=substring'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'quicksearch_comment_cutoff',
|
||||
type => 't',
|
||||
default => '4',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'specific_search_allow_empty_words',
|
||||
type => 'b',
|
||||
default => 1
|
||||
}
|
||||
|
||||
);
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Marc Schumann.
|
||||
# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::RADIUS;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::RADIUS::sortkey = "09";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'RADIUS_server',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'RADIUS_secret',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'RADIUS_NAS_IP',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
|
||||
{
|
||||
name => 'RADIUS_email_suffix',
|
||||
type => 't',
|
||||
default => ''
|
||||
},
|
||||
);
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,73 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::ShadowDB;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::ShadowDB::sortkey = "13";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'shadowdbhost',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
{
|
||||
name => 'shadowdbport',
|
||||
type => 't',
|
||||
default => '3306',
|
||||
checker => \&check_numeric,
|
||||
},
|
||||
|
||||
{
|
||||
name => 'shadowdbsock',
|
||||
type => 't',
|
||||
default => '',
|
||||
},
|
||||
|
||||
# This entry must be _after_ the shadowdb{host,port,sock} settings so that
|
||||
# they can be used in the validation here
|
||||
{
|
||||
name => 'shadowdb',
|
||||
type => 't',
|
||||
default => '',
|
||||
checker => \&check_shadowdb
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,71 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joseph Heenan <joseph@heenan.me.uk>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Config::UserMatch;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Config::Common;
|
||||
|
||||
$Bugzilla::Config::UserMatch::sortkey = "14";
|
||||
|
||||
sub get_param_list {
|
||||
my $class = shift;
|
||||
my @param_list = (
|
||||
{
|
||||
name => 'usemenuforusers',
|
||||
type => 'b',
|
||||
default => '0'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usermatchmode',
|
||||
type => 's',
|
||||
choices => ['off', 'wildcard', 'search'],
|
||||
default => 'off'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'maxusermatches',
|
||||
type => 't',
|
||||
default => '1000',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'confirmuniqueusermatch',
|
||||
type => 'b',
|
||||
default => 1,
|
||||
} );
|
||||
return @param_list;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,517 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dawn Endico <endico@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Joe Robins <jmrobins@tgix.com>
|
||||
# Jake <jake@bugzilla.org>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Shane H. W. Travis <travis@sedsystems.ca>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::Constants;
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
|
||||
# For bz_locations
|
||||
use File::Basename;
|
||||
|
||||
@Bugzilla::Constants::EXPORT = qw(
|
||||
BUGZILLA_VERSION
|
||||
|
||||
bz_locations
|
||||
|
||||
IS_NULL
|
||||
NOT_NULL
|
||||
|
||||
CONTROLMAPNA
|
||||
CONTROLMAPSHOWN
|
||||
CONTROLMAPDEFAULT
|
||||
CONTROLMAPMANDATORY
|
||||
|
||||
AUTH_OK
|
||||
AUTH_NODATA
|
||||
AUTH_ERROR
|
||||
AUTH_LOGINFAILED
|
||||
AUTH_DISABLED
|
||||
AUTH_NO_SUCH_USER
|
||||
|
||||
USER_PASSWORD_MIN_LENGTH
|
||||
USER_PASSWORD_MAX_LENGTH
|
||||
|
||||
LOGIN_OPTIONAL
|
||||
LOGIN_NORMAL
|
||||
LOGIN_REQUIRED
|
||||
|
||||
LOGOUT_ALL
|
||||
LOGOUT_CURRENT
|
||||
LOGOUT_KEEP_CURRENT
|
||||
|
||||
GRANT_DIRECT
|
||||
GRANT_REGEXP
|
||||
|
||||
GROUP_MEMBERSHIP
|
||||
GROUP_BLESS
|
||||
GROUP_VISIBLE
|
||||
|
||||
MAILTO_USER
|
||||
MAILTO_GROUP
|
||||
|
||||
DEFAULT_COLUMN_LIST
|
||||
DEFAULT_QUERY_NAME
|
||||
|
||||
QUERY_LIST
|
||||
LIST_OF_BUGS
|
||||
|
||||
COMMENT_COLS
|
||||
MAX_COMMENT_LENGTH
|
||||
|
||||
CMT_NORMAL
|
||||
CMT_DUPE_OF
|
||||
CMT_HAS_DUPE
|
||||
CMT_POPULAR_VOTES
|
||||
CMT_MOVED_TO
|
||||
|
||||
THROW_ERROR
|
||||
|
||||
RELATIONSHIPS
|
||||
REL_ASSIGNEE REL_QA REL_REPORTER REL_CC REL_VOTER REL_GLOBAL_WATCHER
|
||||
REL_ANY
|
||||
|
||||
POS_EVENTS
|
||||
EVT_OTHER EVT_ADDED_REMOVED EVT_COMMENT EVT_ATTACHMENT EVT_ATTACHMENT_DATA
|
||||
EVT_PROJ_MANAGEMENT EVT_OPENED_CLOSED EVT_KEYWORD EVT_CC EVT_DEPEND_BLOCK
|
||||
EVT_BUG_CREATED
|
||||
|
||||
NEG_EVENTS
|
||||
EVT_UNCONFIRMED EVT_CHANGED_BY_ME
|
||||
|
||||
GLOBAL_EVENTS
|
||||
EVT_FLAG_REQUESTED EVT_REQUESTED_FLAG
|
||||
|
||||
FULLTEXT_BUGLIST_LIMIT
|
||||
|
||||
ADMIN_GROUP_NAME
|
||||
PER_PRODUCT_PRIVILEGES
|
||||
|
||||
SENDMAIL_EXE
|
||||
SENDMAIL_PATH
|
||||
|
||||
FIELD_TYPE_UNKNOWN
|
||||
FIELD_TYPE_FREETEXT
|
||||
FIELD_TYPE_SINGLE_SELECT
|
||||
FIELD_TYPE_MULTI_SELECT
|
||||
FIELD_TYPE_TEXTAREA
|
||||
FIELD_TYPE_DATETIME
|
||||
FIELD_TYPE_BUG_ID
|
||||
FIELD_TYPE_BUG_URLS
|
||||
|
||||
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
|
||||
MAX_LOGINCOOKIE_AGE
|
||||
|
||||
SAFE_PROTOCOLS
|
||||
|
||||
MIN_SMALLINT
|
||||
MAX_SMALLINT
|
||||
|
||||
MAX_LEN_QUERY_NAME
|
||||
MAX_CLASSIFICATION_SIZE
|
||||
MAX_PRODUCT_SIZE
|
||||
MAX_MILESTONE_SIZE
|
||||
MAX_COMPONENT_SIZE
|
||||
MAX_FIELD_VALUE_SIZE
|
||||
MAX_FREETEXT_LENGTH
|
||||
MAX_BUG_URL_LENGTH
|
||||
|
||||
PASSWORD_DIGEST_ALGORITHM
|
||||
PASSWORD_SALT_LENGTH
|
||||
);
|
||||
|
||||
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
|
||||
|
||||
# CONSTANTS
|
||||
#
|
||||
# Bugzilla version
|
||||
use constant BUGZILLA_VERSION => "3.4";
|
||||
|
||||
# These are unique values that are unlikely to match a string or a number,
|
||||
# to be used in criteria for match() functions and other things. They start
|
||||
# and end with spaces because most Bugzilla stuff has trim() called on it,
|
||||
# so this is unlikely to match anything we get out of the DB.
|
||||
#
|
||||
# We can't use a reference, because Template Toolkit doesn't work with
|
||||
# them properly (constants.IS_NULL => {} just returns an empty string instead
|
||||
# of the reference).
|
||||
use constant IS_NULL => ' __IS_NULL__ ';
|
||||
use constant NOT_NULL => ' __NOT_NULL__ ';
|
||||
|
||||
#
|
||||
# ControlMap constants for group_control_map.
|
||||
# membercontol:othercontrol => meaning
|
||||
# Na:Na => Bugs in this product may not be restricted to this
|
||||
# group.
|
||||
# Shown:Na => Members of the group may restrict bugs
|
||||
# in this product to this group.
|
||||
# Shown:Shown => Members of the group may restrict bugs
|
||||
# in this product to this group.
|
||||
# Anyone who can enter bugs in this product may initially
|
||||
# restrict bugs in this product to this group.
|
||||
# Shown:Mandatory => Members of the group may restrict bugs
|
||||
# in this product to this group.
|
||||
# Non-members who can enter bug in this product
|
||||
# will be forced to restrict it.
|
||||
# Default:Na => Members of the group may restrict bugs in this
|
||||
# product to this group and do so by default.
|
||||
# Default:Default => Members of the group may restrict bugs in this
|
||||
# product to this group and do so by default and
|
||||
# nonmembers have this option on entry.
|
||||
# Default:Mandatory => Members of the group may restrict bugs in this
|
||||
# product to this group and do so by default.
|
||||
# Non-members who can enter bug in this product
|
||||
# will be forced to restrict it.
|
||||
# Mandatory:Mandatory => Bug will be forced into this group regardless.
|
||||
# All other combinations are illegal.
|
||||
|
||||
use constant CONTROLMAPNA => 0;
|
||||
use constant CONTROLMAPSHOWN => 1;
|
||||
use constant CONTROLMAPDEFAULT => 2;
|
||||
use constant CONTROLMAPMANDATORY => 3;
|
||||
|
||||
# See Bugzilla::Auth for docs on AUTH_*, LOGIN_* and LOGOUT_*
|
||||
|
||||
use constant AUTH_OK => 0;
|
||||
use constant AUTH_NODATA => 1;
|
||||
use constant AUTH_ERROR => 2;
|
||||
use constant AUTH_LOGINFAILED => 3;
|
||||
use constant AUTH_DISABLED => 4;
|
||||
use constant AUTH_NO_SUCH_USER => 5;
|
||||
|
||||
# The minimum and maximum lengths a password must have.
|
||||
use constant USER_PASSWORD_MIN_LENGTH => 3;
|
||||
use constant USER_PASSWORD_MAX_LENGTH => 16;
|
||||
|
||||
use constant LOGIN_OPTIONAL => 0;
|
||||
use constant LOGIN_NORMAL => 1;
|
||||
use constant LOGIN_REQUIRED => 2;
|
||||
|
||||
use constant LOGOUT_ALL => 0;
|
||||
use constant LOGOUT_CURRENT => 1;
|
||||
use constant LOGOUT_KEEP_CURRENT => 2;
|
||||
|
||||
use constant contenttypes =>
|
||||
{
|
||||
"html"=> "text/html" ,
|
||||
"rdf" => "application/rdf+xml" ,
|
||||
"atom"=> "application/atom+xml" ,
|
||||
"xml" => "application/xml" ,
|
||||
"js" => "application/x-javascript" ,
|
||||
"csv" => "text/csv" ,
|
||||
"png" => "image/png" ,
|
||||
"ics" => "text/calendar" ,
|
||||
};
|
||||
|
||||
use constant GRANT_DIRECT => 0;
|
||||
use constant GRANT_REGEXP => 2;
|
||||
|
||||
use constant GROUP_MEMBERSHIP => 0;
|
||||
use constant GROUP_BLESS => 1;
|
||||
use constant GROUP_VISIBLE => 2;
|
||||
|
||||
use constant MAILTO_USER => 0;
|
||||
use constant MAILTO_GROUP => 1;
|
||||
|
||||
# The default list of columns for buglist.cgi
|
||||
use constant DEFAULT_COLUMN_LIST => (
|
||||
"bug_severity", "priority", "op_sys","assigned_to",
|
||||
"bug_status", "resolution", "short_desc"
|
||||
);
|
||||
|
||||
# Used by query.cgi and buglist.cgi as the named-query name
|
||||
# for the default settings.
|
||||
use constant DEFAULT_QUERY_NAME => '(Default query)';
|
||||
|
||||
# The possible types for saved searches.
|
||||
use constant QUERY_LIST => 0;
|
||||
use constant LIST_OF_BUGS => 1;
|
||||
|
||||
# The column length for displayed (and wrapped) bug comments.
|
||||
use constant COMMENT_COLS => 80;
|
||||
# Used in _check_comment(). Gives the max length allowed for a comment.
|
||||
use constant MAX_COMMENT_LENGTH => 65535;
|
||||
|
||||
# The type of bug comments.
|
||||
use constant CMT_NORMAL => 0;
|
||||
use constant CMT_DUPE_OF => 1;
|
||||
use constant CMT_HAS_DUPE => 2;
|
||||
use constant CMT_POPULAR_VOTES => 3;
|
||||
use constant CMT_MOVED_TO => 4;
|
||||
|
||||
# Determine whether a validation routine should return 0 or throw
|
||||
# an error when the validation fails.
|
||||
use constant THROW_ERROR => 1;
|
||||
|
||||
use constant REL_ASSIGNEE => 0;
|
||||
use constant REL_QA => 1;
|
||||
use constant REL_REPORTER => 2;
|
||||
use constant REL_CC => 3;
|
||||
use constant REL_VOTER => 4;
|
||||
use constant REL_GLOBAL_WATCHER => 5;
|
||||
|
||||
use constant RELATIONSHIPS => REL_ASSIGNEE, REL_QA, REL_REPORTER, REL_CC,
|
||||
REL_VOTER, REL_GLOBAL_WATCHER;
|
||||
|
||||
# Used for global events like EVT_FLAG_REQUESTED
|
||||
use constant REL_ANY => 100;
|
||||
|
||||
# There are two sorts of event - positive and negative. Positive events are
|
||||
# those for which the user says "I want mail if this happens." Negative events
|
||||
# are those for which the user says "I don't want mail if this happens."
|
||||
#
|
||||
# Exactly when each event fires is defined in wants_bug_mail() in User.pm; I'm
|
||||
# not commenting them here in case the comments and the code get out of sync.
|
||||
use constant EVT_OTHER => 0;
|
||||
use constant EVT_ADDED_REMOVED => 1;
|
||||
use constant EVT_COMMENT => 2;
|
||||
use constant EVT_ATTACHMENT => 3;
|
||||
use constant EVT_ATTACHMENT_DATA => 4;
|
||||
use constant EVT_PROJ_MANAGEMENT => 5;
|
||||
use constant EVT_OPENED_CLOSED => 6;
|
||||
use constant EVT_KEYWORD => 7;
|
||||
use constant EVT_CC => 8;
|
||||
use constant EVT_DEPEND_BLOCK => 9;
|
||||
use constant EVT_BUG_CREATED => 10;
|
||||
|
||||
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, EVT_BUG_CREATED;
|
||||
|
||||
use constant EVT_UNCONFIRMED => 50;
|
||||
use constant EVT_CHANGED_BY_ME => 51;
|
||||
|
||||
use constant NEG_EVENTS => EVT_UNCONFIRMED, EVT_CHANGED_BY_ME;
|
||||
|
||||
# These are the "global" flags, which aren't tied to a particular relationship.
|
||||
# and so use REL_ANY.
|
||||
use constant EVT_FLAG_REQUESTED => 100; # Flag has been requested of me
|
||||
use constant EVT_REQUESTED_FLAG => 101; # I have requested a flag
|
||||
|
||||
use constant GLOBAL_EVENTS => EVT_FLAG_REQUESTED, EVT_REQUESTED_FLAG;
|
||||
|
||||
# Number of bugs to return in a buglist when performing
|
||||
# a fulltext search.
|
||||
use constant FULLTEXT_BUGLIST_LIMIT => 200;
|
||||
|
||||
# Default administration group name.
|
||||
use constant ADMIN_GROUP_NAME => 'admin';
|
||||
|
||||
# Privileges which can be per-product.
|
||||
use constant PER_PRODUCT_PRIVILEGES => ('editcomponents', 'editbugs', 'canconfirm');
|
||||
|
||||
# Path to sendmail.exe (Windows only)
|
||||
use constant SENDMAIL_EXE => '/usr/lib/sendmail.exe';
|
||||
# Paths to search for the sendmail binary (non-Windows)
|
||||
use constant SENDMAIL_PATH => '/usr/lib:/usr/sbin:/usr/ucblib';
|
||||
|
||||
# Field types. Match values in fielddefs.type column. These are purposely
|
||||
# not named after database column types, since Bugzilla fields comprise not
|
||||
# only storage but also logic. For example, we might add a "user" field type
|
||||
# whose values are stored in an integer column in the database but for which
|
||||
# we do more than we would do for a standard integer type (f.e. we might
|
||||
# display a user picker).
|
||||
|
||||
use constant FIELD_TYPE_UNKNOWN => 0;
|
||||
use constant FIELD_TYPE_FREETEXT => 1;
|
||||
use constant FIELD_TYPE_SINGLE_SELECT => 2;
|
||||
use constant FIELD_TYPE_MULTI_SELECT => 3;
|
||||
use constant FIELD_TYPE_TEXTAREA => 4;
|
||||
use constant FIELD_TYPE_DATETIME => 5;
|
||||
use constant FIELD_TYPE_BUG_ID => 6;
|
||||
use constant FIELD_TYPE_BUG_URLS => 7;
|
||||
|
||||
# The maximum number of days a token will remain valid.
|
||||
use constant MAX_TOKEN_AGE => 3;
|
||||
# How many days a logincookie will remain valid if not used.
|
||||
use constant MAX_LOGINCOOKIE_AGE => 30;
|
||||
|
||||
# Protocols which are considered as safe.
|
||||
use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https',
|
||||
'irc', 'mid', 'news', 'nntp', 'prospero', 'telnet',
|
||||
'view-source', 'wais');
|
||||
|
||||
# Usage modes. Default USAGE_MODE_BROWSER. Use with Bugzilla->usage_mode.
|
||||
use constant USAGE_MODE_BROWSER => 0;
|
||||
use constant USAGE_MODE_CMDLINE => 1;
|
||||
use constant USAGE_MODE_WEBSERVICE => 2;
|
||||
use constant USAGE_MODE_EMAIL => 3;
|
||||
|
||||
# Error modes. Default set by Bugzilla->usage_mode (so ERROR_MODE_WEBPAGE
|
||||
# usually). Use with Bugzilla->error_mode.
|
||||
use constant ERROR_MODE_WEBPAGE => 0;
|
||||
use constant ERROR_MODE_DIE => 1;
|
||||
use constant ERROR_MODE_DIE_SOAP_FAULT => 2;
|
||||
|
||||
# The various modes that checksetup.pl can run in.
|
||||
use constant INSTALLATION_MODE_INTERACTIVE => 0;
|
||||
use constant INSTALLATION_MODE_NON_INTERACTIVE => 1;
|
||||
|
||||
# Data about what we require for different databases.
|
||||
use constant DB_MODULE => {
|
||||
'mysql' => {db => 'Bugzilla::DB::Mysql', db_version => '4.1.2',
|
||||
dbd => {
|
||||
package => 'DBD-mysql',
|
||||
module => 'DBD::mysql',
|
||||
# Disallow development versions
|
||||
blacklist => ['_'],
|
||||
# For UTF-8 support
|
||||
version => '4.00',
|
||||
},
|
||||
name => 'MySQL'},
|
||||
'pg' => {db => 'Bugzilla::DB::Pg', db_version => '8.00.0000',
|
||||
dbd => {
|
||||
package => 'DBD-Pg',
|
||||
module => 'DBD::Pg',
|
||||
version => '1.45',
|
||||
},
|
||||
name => 'PostgreSQL'},
|
||||
'oracle'=> {db => 'Bugzilla::DB::Oracle', db_version => '10.02.0',
|
||||
dbd => {
|
||||
package => 'DBD-Oracle',
|
||||
module => 'DBD::Oracle',
|
||||
version => '1.19',
|
||||
},
|
||||
name => 'Oracle'},
|
||||
};
|
||||
|
||||
# The user who should be considered "root" when we're giving
|
||||
# instructions to Bugzilla administrators.
|
||||
use constant ROOT_USER => $^O =~ /MSWin32/i ? 'Administrator' : 'root';
|
||||
|
||||
# True if we're on Win32.
|
||||
use constant ON_WINDOWS => ($^O =~ /MSWin32/i);
|
||||
|
||||
use constant MIN_SMALLINT => -32768;
|
||||
use constant MAX_SMALLINT => 32767;
|
||||
|
||||
# The longest that a saved search name can be.
|
||||
use constant MAX_LEN_QUERY_NAME => 64;
|
||||
|
||||
# The longest classification name allowed.
|
||||
use constant MAX_CLASSIFICATION_SIZE => 64;
|
||||
|
||||
# The longest product name allowed.
|
||||
use constant MAX_PRODUCT_SIZE => 64;
|
||||
|
||||
# The longest milestone name allowed.
|
||||
use constant MAX_MILESTONE_SIZE => 20;
|
||||
|
||||
# The longest component name allowed.
|
||||
use constant MAX_COMPONENT_SIZE => 64;
|
||||
|
||||
# The maximum length for values of <select> fields.
|
||||
use constant MAX_FIELD_VALUE_SIZE => 64;
|
||||
|
||||
# Maximum length allowed for free text fields.
|
||||
use constant MAX_FREETEXT_LENGTH => 255;
|
||||
|
||||
# The longest a bug URL in a BUG_URLS field can be.
|
||||
use constant MAX_BUG_URL_LENGTH => 255;
|
||||
|
||||
# This is the name of the algorithm used to hash passwords before storing
|
||||
# them in the database. This can be any string that is valid to pass to
|
||||
# Perl's "Digest" module. Note that if you change this, it won't take
|
||||
# effect until a user changes his password.
|
||||
use constant PASSWORD_DIGEST_ALGORITHM => 'SHA-256';
|
||||
# How long of a salt should we use? Note that if you change this, none
|
||||
# of your users will be able to log in until they reset their passwords.
|
||||
use constant PASSWORD_SALT_LENGTH => 8;
|
||||
|
||||
sub bz_locations {
|
||||
# We know that Bugzilla/Constants.pm must be in %INC at this point.
|
||||
# So the only question is, what's the name of the directory
|
||||
# above it? This is the most reliable way to get our current working
|
||||
# directory under both mod_cgi and mod_perl. We call dirname twice
|
||||
# to get the name of the directory above the "Bugzilla/" directory.
|
||||
#
|
||||
# Calling dirname twice like that won't work on VMS or AmigaOS
|
||||
# but I doubt anybody runs Bugzilla on those.
|
||||
#
|
||||
# On mod_cgi this will be a relative path. On mod_perl it will be an
|
||||
# absolute path.
|
||||
my $libpath = dirname(dirname($INC{'Bugzilla/Constants.pm'}));
|
||||
# We have to detaint $libpath, but we can't use Bugzilla::Util here.
|
||||
$libpath =~ /(.*)/;
|
||||
$libpath = $1;
|
||||
|
||||
my ($project, $localconfig, $datadir);
|
||||
if ($ENV{'PROJECT'} && $ENV{'PROJECT'} =~ /^(\w+)$/) {
|
||||
$project = $1;
|
||||
$localconfig = "localconfig.$project";
|
||||
$datadir = "data/$project";
|
||||
} else {
|
||||
$localconfig = "localconfig";
|
||||
$datadir = "data";
|
||||
}
|
||||
|
||||
# We have to return absolute paths for mod_perl.
|
||||
# That means that if you modify these paths, they must be absolute paths.
|
||||
return {
|
||||
'libpath' => $libpath,
|
||||
'ext_libpath' => "$libpath/lib",
|
||||
# If you put the libraries in a different location than the CGIs,
|
||||
# make sure this still points to the CGIs.
|
||||
'cgi_path' => $libpath,
|
||||
'templatedir' => "$libpath/template",
|
||||
'project' => $project,
|
||||
'localconfig' => "$libpath/$localconfig",
|
||||
'datadir' => "$libpath/$datadir",
|
||||
'attachdir' => "$libpath/$datadir/attachments",
|
||||
'skinsdir' => "$libpath/skins",
|
||||
# $webdotdir must be in the web server's tree somewhere. Even if you use a
|
||||
# local dot, we output images to there. Also, if $webdotdir is
|
||||
# not relative to the bugzilla root directory, you'll need to
|
||||
# change showdependencygraph.cgi to set image_url to the correct
|
||||
# location.
|
||||
# The script should really generate these graphs directly...
|
||||
'webdotdir' => "$libpath/$datadir/webdot",
|
||||
'extensionsdir' => "$libpath/extensions",
|
||||
};
|
||||
}
|
||||
|
||||
1;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,650 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Oracle Corporation.
|
||||
# Portions created by Oracle are Copyright (C) 2007 Oracle Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Lance Larsh <lance.larsh@oracle.com>
|
||||
# Xiaoou Wu <xiaoou.wu@oracle.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::DB::Oracle - Bugzilla database compatibility layer for Oracle
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module overrides methods of the Bugzilla::DB module with Oracle
|
||||
specific implementation. It is instantiated by the Bugzilla::DB module
|
||||
and should never be used directly.
|
||||
|
||||
For interface details see L<Bugzilla::DB> and L<DBI>.
|
||||
|
||||
=cut
|
||||
|
||||
package Bugzilla::DB::Oracle;
|
||||
|
||||
use strict;
|
||||
|
||||
use DBD::Oracle;
|
||||
use DBD::Oracle qw(:ora_types);
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
# This module extends the DB interface via inheritance
|
||||
use base qw(Bugzilla::DB);
|
||||
|
||||
#####################################################################
|
||||
# Constants
|
||||
#####################################################################
|
||||
use constant EMPTY_STRING => '__BZ_EMPTY_STR__';
|
||||
use constant ISOLATION_LEVEL => 'READ COMMITTED';
|
||||
use constant BLOB_TYPE => { ora_type => ORA_BLOB };
|
||||
|
||||
sub new {
|
||||
my ($class, $user, $pass, $host, $dbname, $port) = @_;
|
||||
|
||||
# You can never connect to Oracle without a DB name,
|
||||
# and there is no default DB.
|
||||
$dbname ||= Bugzilla->localconfig->{db_name};
|
||||
|
||||
# Set the language enviroment
|
||||
$ENV{'NLS_LANG'} = '.AL32UTF8' if Bugzilla->params->{'utf8'};
|
||||
|
||||
# construct the DSN from the parameters we got
|
||||
my $dsn = "dbi:Oracle:host=$host;sid=$dbname";
|
||||
$dsn .= ";port=$port" if $port;
|
||||
my $attrs = { FetchHashKeyName => 'NAME_lc',
|
||||
LongReadLen => ( Bugzilla->params->{'maxattachmentsize'}
|
||||
|| 1000 ) * 1024,
|
||||
};
|
||||
my $self = $class->db_new($dsn, $user, $pass, $attrs);
|
||||
# Needed by TheSchwartz
|
||||
$self->{private_bz_dsn} = $dsn;
|
||||
|
||||
bless ($self, $class);
|
||||
|
||||
# Set the session's default date format to match MySQL
|
||||
$self->do("ALTER SESSION SET NLS_DATE_FORMAT='YYYY-MM-DD HH24:MI:SS'");
|
||||
$self->do("ALTER SESSION SET NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS'");
|
||||
$self->do("ALTER SESSION SET NLS_LENGTH_SEMANTICS='CHAR'")
|
||||
if Bugzilla->params->{'utf8'};
|
||||
# To allow case insensitive query.
|
||||
$self->do("ALTER SESSION SET NLS_COMP='ANSI'");
|
||||
$self->do("ALTER SESSION SET NLS_SORT='BINARY_AI'");
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub bz_last_key {
|
||||
my ($self, $table, $column) = @_;
|
||||
|
||||
my $seq = $table . "_" . $column . "_SEQ";
|
||||
my ($last_insert_id) = $self->selectrow_array("SELECT $seq.CURRVAL "
|
||||
. " FROM DUAL");
|
||||
return $last_insert_id;
|
||||
}
|
||||
|
||||
sub bz_check_regexp {
|
||||
my ($self, $pattern) = @_;
|
||||
|
||||
eval { $self->do("SELECT 1 FROM DUAL WHERE "
|
||||
. $self->sql_regexp($self->quote("a"), $pattern, 1)) };
|
||||
|
||||
$@ && ThrowUserError('illegal_regexp',
|
||||
{ value => $pattern, dberror => $self->errstr });
|
||||
}
|
||||
|
||||
sub bz_explain {
|
||||
my ($self, $sql) = @_;
|
||||
my $sth = $self->prepare("EXPLAIN PLAN FOR $sql");
|
||||
$sth->execute();
|
||||
my $explain = $self->selectcol_arrayref(
|
||||
"SELECT PLAN_TABLE_OUTPUT FROM TABLE(DBMS_XPLAN.DISPLAY)");
|
||||
return join("\n", @$explain);
|
||||
}
|
||||
|
||||
sub sql_regexp {
|
||||
my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
|
||||
$real_pattern ||= $pattern;
|
||||
|
||||
$self->bz_check_regexp($real_pattern) if !$nocheck;
|
||||
|
||||
return "REGEXP_LIKE($expr, $pattern)";
|
||||
}
|
||||
|
||||
sub sql_not_regexp {
|
||||
my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
|
||||
$real_pattern ||= $pattern;
|
||||
|
||||
$self->bz_check_regexp($real_pattern) if !$nocheck;
|
||||
|
||||
return "NOT REGEXP_LIKE($expr, $pattern)"
|
||||
}
|
||||
|
||||
sub sql_limit {
|
||||
my ($self, $limit, $offset) = @_;
|
||||
|
||||
if(defined $offset) {
|
||||
return "/* LIMIT $limit $offset */";
|
||||
}
|
||||
return "/* LIMIT $limit */";
|
||||
}
|
||||
|
||||
sub sql_string_concat {
|
||||
my ($self, @params) = @_;
|
||||
|
||||
return 'CONCAT(' . join(', ', @params) . ')';
|
||||
}
|
||||
|
||||
sub sql_string_until {
|
||||
my ($self, $string, $substring) = @_;
|
||||
return "SUBSTR($string, 1, "
|
||||
. $self->sql_position($substring, $string)
|
||||
. " - 1)";
|
||||
}
|
||||
|
||||
sub sql_to_days {
|
||||
my ($self, $date) = @_;
|
||||
|
||||
return " TO_CHAR(TO_DATE($date),'J') ";
|
||||
}
|
||||
sub sql_from_days{
|
||||
my ($self, $date) = @_;
|
||||
|
||||
return " TO_DATE($date,'J') ";
|
||||
}
|
||||
sub sql_fulltext_search {
|
||||
my ($self, $column, $text, $label) = @_;
|
||||
$text = $self->quote($text);
|
||||
trick_taint($text);
|
||||
return "CONTAINS($column,$text,$label)", "SCORE($label)";
|
||||
}
|
||||
|
||||
sub sql_date_format {
|
||||
my ($self, $date, $format) = @_;
|
||||
|
||||
$format = "%Y.%m.%d %H:%i:%s" if !$format;
|
||||
|
||||
$format =~ s/\%Y/YYYY/g;
|
||||
$format =~ s/\%y/YY/g;
|
||||
$format =~ s/\%m/MM/g;
|
||||
$format =~ s/\%d/DD/g;
|
||||
$format =~ s/\%a/Dy/g;
|
||||
$format =~ s/\%H/HH24/g;
|
||||
$format =~ s/\%i/MI/g;
|
||||
$format =~ s/\%s/SS/g;
|
||||
|
||||
return "TO_CHAR($date, " . $self->quote($format) . ")";
|
||||
}
|
||||
|
||||
sub sql_interval {
|
||||
my ($self, $interval, $units) = @_;
|
||||
if ($units =~ /YEAR|MONTH/i) {
|
||||
return "NUMTOYMINTERVAL($interval,'$units')";
|
||||
} else{
|
||||
return "NUMTODSINTERVAL($interval,'$units')";
|
||||
}
|
||||
}
|
||||
|
||||
sub sql_position {
|
||||
my ($self, $fragment, $text) = @_;
|
||||
return "INSTR($text, $fragment)";
|
||||
}
|
||||
|
||||
sub sql_in {
|
||||
my ($self, $column_name, $in_list_ref) = @_;
|
||||
my @in_list = @$in_list_ref;
|
||||
return $self->SUPER::sql_in($column_name, $in_list_ref) if $#in_list < 1000;
|
||||
my @in_str;
|
||||
while (@in_list) {
|
||||
my $length = $#in_list + 1;
|
||||
my $splice = $length > 1000 ? 1000 : $length;
|
||||
my @sub_in_list = splice(@in_list, 0, $splice);
|
||||
push(@in_str,
|
||||
$self->SUPER::sql_in($column_name, \@sub_in_list));
|
||||
}
|
||||
return "( " . join(" OR ", @in_str) . " )";
|
||||
}
|
||||
|
||||
sub _bz_add_field_table {
|
||||
my ($self, $name, $schema_ref, $type) = @_;
|
||||
$self->SUPER::_bz_add_field_table($name, $schema_ref);
|
||||
if (defined($type) && $type == FIELD_TYPE_MULTI_SELECT) {
|
||||
my $uk_name = "UK_" . $self->_bz_schema->_hash_identifier($name . '_value');
|
||||
$self->do("ALTER TABLE $name ADD CONSTRAINT $uk_name UNIQUE(value)");
|
||||
}
|
||||
}
|
||||
|
||||
sub bz_drop_table {
|
||||
my ($self, $name) = @_;
|
||||
my $table_exists = $self->bz_table_info($name);
|
||||
if ($table_exists) {
|
||||
$self->_bz_drop_fks($name);
|
||||
$self->SUPER::bz_drop_table($name);
|
||||
}
|
||||
}
|
||||
|
||||
# Dropping all FKs for a specified table.
|
||||
sub _bz_drop_fks {
|
||||
my ($self, $table) = @_;
|
||||
my @columns = $self->_bz_real_schema->get_table_columns($table);
|
||||
foreach my $column (@columns) {
|
||||
$self->bz_drop_fk($table, $column);
|
||||
}
|
||||
}
|
||||
|
||||
sub _fix_empty {
|
||||
my ($string) = @_;
|
||||
$string = '' if $string eq EMPTY_STRING;
|
||||
return $string;
|
||||
}
|
||||
|
||||
sub _fix_arrayref {
|
||||
my ($row) = @_;
|
||||
return undef if !defined $row;
|
||||
foreach my $field (@$row) {
|
||||
$field = _fix_empty($field) if defined $field;
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
sub _fix_hashref {
|
||||
my ($row) = @_;
|
||||
return undef if !defined $row;
|
||||
foreach my $value (values %$row) {
|
||||
$value = _fix_empty($value) if defined $value;
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
|
||||
sub adjust_statement {
|
||||
my ($sql) = @_;
|
||||
|
||||
# We can't just assume any occurrence of "''" in $sql is an empty
|
||||
# string, since "''" can occur inside a string literal as a way of
|
||||
# escaping a single "'" in the literal. Therefore we must be trickier...
|
||||
|
||||
# split the statement into parts by single-quotes. The negative value
|
||||
# at the end to the split operator from dropping trailing empty strings
|
||||
# (e.g., when $sql ends in "''")
|
||||
my @parts = split /'/, $sql, -1;
|
||||
|
||||
if( !(@parts % 2) ) {
|
||||
# Either the string is empty or the quotes are mismatched
|
||||
# Returning input unmodified.
|
||||
return $sql;
|
||||
}
|
||||
|
||||
# We already verified that we have an odd number of parts. If we take
|
||||
# the first part off now, we know we're entering the loop with an even
|
||||
# number of parts
|
||||
my @result;
|
||||
my $part = shift @parts;
|
||||
|
||||
# Oracle requires a FROM clause in all SELECT statements, so append
|
||||
# "FROM dual" to queries without one (e.g., "SELECT NOW()")
|
||||
my $is_select = ($part =~ m/^\s*SELECT\b/io);
|
||||
my $has_from = ($part =~ m/\bFROM\b/io) if $is_select;
|
||||
|
||||
# Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
|
||||
$part =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
|
||||
|
||||
# Oracle use SUBSTR instead of SUBSTRING
|
||||
$part =~ s/\bSUBSTRING\b/SUBSTR/io;
|
||||
|
||||
# Oracle need no 'AS'
|
||||
$part =~ s/\bAS\b//ig;
|
||||
|
||||
# Oracle doesn't have LIMIT, so if we find the LIMIT comment, wrap the
|
||||
# query with "SELECT * FROM (...) WHERE rownum < $limit"
|
||||
my ($limit,$offset) = ($part =~ m{/\* LIMIT (\d*) (\d*) \*/}o);
|
||||
|
||||
push @result, $part;
|
||||
while( @parts ) {
|
||||
my $string = shift @parts;
|
||||
my $nonstring = shift @parts;
|
||||
|
||||
# if the non-string part is zero-length and there are more parts left,
|
||||
# then this is an escaped quote inside a string literal
|
||||
while( !(length $nonstring) && @parts ) {
|
||||
# we know it's safe to remove two parts at a time, since we
|
||||
# entered the loop with an even number of parts
|
||||
$string .= "''" . shift @parts;
|
||||
$nonstring = shift @parts;
|
||||
}
|
||||
|
||||
# Look for a FROM if this is a SELECT and we haven't found one yet
|
||||
$has_from = ($nonstring =~ m/\bFROM\b/io)
|
||||
if ($is_select and !$has_from);
|
||||
|
||||
# Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
|
||||
$nonstring =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
|
||||
|
||||
# Oracle use SUBSTR instead of SUBSTRING
|
||||
$nonstring =~ s/\bSUBSTRING\b/SUBSTR/io;
|
||||
|
||||
# Oracle need no 'AS'
|
||||
$nonstring =~ s/\bAS\b//ig;
|
||||
|
||||
# Look for a LIMIT clause
|
||||
($limit) = ($nonstring =~ m(/\* LIMIT (\d*) \*/)o);
|
||||
|
||||
if(!length($string)){
|
||||
push @result, EMPTY_STRING;
|
||||
push @result, $nonstring;
|
||||
} else {
|
||||
push @result, $string;
|
||||
push @result, $nonstring;
|
||||
}
|
||||
}
|
||||
|
||||
my $new_sql = join "'", @result;
|
||||
|
||||
# Append "FROM dual" if this is a SELECT without a FROM clause
|
||||
$new_sql .= " FROM DUAL" if ($is_select and !$has_from);
|
||||
|
||||
# Wrap the query with a "WHERE rownum <= ..." if we found LIMIT
|
||||
|
||||
if (defined($limit)) {
|
||||
if ($new_sql !~ /\bWHERE\b/) {
|
||||
$new_sql = $new_sql." WHERE 1=1";
|
||||
}
|
||||
my ($before_where, $after_where) = split /\bWHERE\b/i,$new_sql;
|
||||
if (defined($offset)) {
|
||||
if ($new_sql =~ /(.*\s+)FROM(\s+.*)/i) {
|
||||
my ($before_from,$after_from) = ($1,$2);
|
||||
$before_where = "$before_from FROM ($before_from,"
|
||||
. " ROW_NUMBER() OVER (ORDER BY 1) R "
|
||||
. " FROM $after_from ) ";
|
||||
$after_where = " R BETWEEN $offset+1 AND $limit+$offset";
|
||||
}
|
||||
} else {
|
||||
$after_where = " rownum <=$limit AND ".$after_where;
|
||||
}
|
||||
|
||||
$new_sql = $before_where." WHERE ".$after_where;
|
||||
}
|
||||
return $new_sql;
|
||||
}
|
||||
|
||||
sub do {
|
||||
my $self = shift;
|
||||
my $sql = shift;
|
||||
$sql = adjust_statement($sql);
|
||||
unshift @_, $sql;
|
||||
return $self->SUPER::do(@_);
|
||||
}
|
||||
|
||||
sub selectrow_array {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
if ( wantarray ) {
|
||||
my @row = $self->SUPER::selectrow_array(@_);
|
||||
_fix_arrayref(\@row);
|
||||
return @row;
|
||||
} else {
|
||||
my $row = $self->SUPER::selectrow_array(@_);
|
||||
$row = _fix_empty($row) if defined $row;
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
sub selectrow_arrayref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $ref = $self->SUPER::selectrow_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
|
||||
_fix_arrayref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub selectrow_hashref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $ref = $self->SUPER::selectrow_hashref(@_);
|
||||
return undef if !defined $ref;
|
||||
|
||||
_fix_hashref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub selectall_arrayref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $ref = $self->SUPER::selectall_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
|
||||
foreach my $row (@$ref) {
|
||||
if (ref($row) eq 'ARRAY') {
|
||||
_fix_arrayref($row);
|
||||
}
|
||||
elsif (ref($row) eq 'HASH') {
|
||||
_fix_hashref($row);
|
||||
}
|
||||
}
|
||||
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub selectall_hashref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $rows = $self->SUPER::selectall_hashref(@_);
|
||||
return undef if !defined $rows;
|
||||
foreach my $row (values %$rows) {
|
||||
_fix_hashref($row);
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
sub selectcol_arrayref {
|
||||
my $self = shift;
|
||||
my $stmt = shift;
|
||||
my $new_stmt = (ref $stmt) ? $stmt : adjust_statement($stmt);
|
||||
unshift @_, $new_stmt;
|
||||
my $ref = $self->SUPER::selectcol_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
_fix_arrayref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub prepare {
|
||||
my $self = shift;
|
||||
my $sql = shift;
|
||||
my $new_sql = adjust_statement($sql);
|
||||
unshift @_, $new_sql;
|
||||
return bless $self->SUPER::prepare(@_),
|
||||
'Bugzilla::DB::Oracle::st';
|
||||
}
|
||||
|
||||
sub prepare_cached {
|
||||
my $self = shift;
|
||||
my $sql = shift;
|
||||
my $new_sql = adjust_statement($sql);
|
||||
unshift @_, $new_sql;
|
||||
return bless $self->SUPER::prepare_cached(@_),
|
||||
'Bugzilla::DB::Oracle::st';
|
||||
}
|
||||
|
||||
sub quote_identifier {
|
||||
my ($self,$id) = @_;
|
||||
return $id;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Protected "Real Database" Schema Information Methods
|
||||
#####################################################################
|
||||
|
||||
sub bz_table_columns_real {
|
||||
my ($self, $table) = @_;
|
||||
$table = uc($table);
|
||||
my $cols = $self->selectcol_arrayref(
|
||||
"SELECT LOWER(COLUMN_NAME) FROM USER_TAB_COLUMNS WHERE
|
||||
TABLE_NAME = ? ORDER BY COLUMN_NAME", undef, $table);
|
||||
return @$cols;
|
||||
}
|
||||
|
||||
sub bz_table_list_real {
|
||||
my ($self) = @_;
|
||||
my $tables = $self->selectcol_arrayref(
|
||||
"SELECT LOWER(TABLE_NAME) FROM USER_TABLES WHERE
|
||||
TABLE_NAME NOT LIKE ? ORDER BY TABLE_NAME", undef, 'DR$%');
|
||||
return @$tables;
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Custom Database Setup
|
||||
#####################################################################
|
||||
|
||||
sub bz_setup_database {
|
||||
my $self = shift;
|
||||
|
||||
# Create a function that returns SYSDATE to emulate MySQL's "NOW()".
|
||||
# Function NOW() is used widely in Bugzilla SQLs, but Oracle does not
|
||||
# have that function, So we have to create one ourself.
|
||||
$self->do("CREATE OR REPLACE FUNCTION NOW "
|
||||
. " RETURN DATE IS BEGIN RETURN SYSDATE; END;");
|
||||
$self->do("CREATE OR REPLACE FUNCTION CHAR_LENGTH(COLUMN_NAME VARCHAR2)"
|
||||
. " RETURN NUMBER IS BEGIN RETURN LENGTH(COLUMN_NAME); END;");
|
||||
# Create a WORLD_LEXER named BZ_LEX for multilingual fulltext search
|
||||
my $lexer = $self->selectcol_arrayref(
|
||||
"SELECT pre_name FROM CTXSYS.CTX_PREFERENCES WHERE pre_name = ? AND
|
||||
pre_owner = ?",
|
||||
undef,'BZ_LEX',uc(Bugzilla->localconfig->{db_user}));
|
||||
if(!@$lexer) {
|
||||
$self->do("BEGIN CTX_DDL.CREATE_PREFERENCE
|
||||
('BZ_LEX', 'WORLD_LEXER'); END;");
|
||||
}
|
||||
|
||||
$self->SUPER::bz_setup_database(@_);
|
||||
|
||||
my @tables = $self->bz_table_list_real();
|
||||
foreach my $table (@tables) {
|
||||
my @columns = $self->bz_table_columns_real($table);
|
||||
foreach my $column (@columns) {
|
||||
my $def = $self->bz_column_info($table, $column);
|
||||
if ($def->{REFERENCES}) {
|
||||
my $references = $def->{REFERENCES};
|
||||
my $update = $references->{UPDATE} || 'CASCADE';
|
||||
my $to_table = $references->{TABLE};
|
||||
my $to_column = $references->{COLUMN};
|
||||
my $fk_name = $self->_bz_schema->_get_fk_name($table,
|
||||
$column,
|
||||
$references);
|
||||
if ( $update =~ /CASCADE/i ){
|
||||
my $trigger_name = uc($fk_name . "_UC");
|
||||
my $exist_trigger = $self->selectcol_arrayref(
|
||||
"SELECT OBJECT_NAME FROM USER_OBJECTS
|
||||
WHERE OBJECT_NAME = ?", undef, $trigger_name);
|
||||
if(@$exist_trigger) {
|
||||
$self->do("DROP TRIGGER $trigger_name");
|
||||
}
|
||||
|
||||
my $tr_str = "CREATE OR REPLACE TRIGGER $trigger_name"
|
||||
. " AFTER UPDATE OF $to_column ON $to_table "
|
||||
. " REFERENCING "
|
||||
. " NEW AS NEW "
|
||||
. " OLD AS OLD "
|
||||
. " FOR EACH ROW "
|
||||
. " BEGIN "
|
||||
. " UPDATE $table"
|
||||
. " SET $column = :NEW.$to_column"
|
||||
. " WHERE $column = :OLD.$to_column;"
|
||||
. " END $trigger_name;";
|
||||
$self->do($tr_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
package Bugzilla::DB::Oracle::st;
|
||||
use base qw(DBI::st);
|
||||
|
||||
sub fetchrow_arrayref {
|
||||
my $self = shift;
|
||||
my $ref = $self->SUPER::fetchrow_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
Bugzilla::DB::Oracle::_fix_arrayref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub fetchrow_array {
|
||||
my $self = shift;
|
||||
if ( wantarray ) {
|
||||
my @row = $self->SUPER::fetchrow_array(@_);
|
||||
Bugzilla::DB::Oracle::_fix_arrayref(\@row);
|
||||
return @row;
|
||||
} else {
|
||||
my $row = $self->SUPER::fetchrow_array(@_);
|
||||
$row = Bugzilla::DB::Oracle::_fix_empty($row) if defined $row;
|
||||
return $row;
|
||||
}
|
||||
}
|
||||
|
||||
sub fetchrow_hashref {
|
||||
my $self = shift;
|
||||
my $ref = $self->SUPER::fetchrow_hashref(@_);
|
||||
return undef if !defined $ref;
|
||||
Bugzilla::DB::Oracle::_fix_hashref($ref);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub fetchall_arrayref {
|
||||
my $self = shift;
|
||||
my $ref = $self->SUPER::fetchall_arrayref(@_);
|
||||
return undef if !defined $ref;
|
||||
foreach my $row (@$ref) {
|
||||
if (ref($row) eq 'ARRAY') {
|
||||
Bugzilla::DB::Oracle::_fix_arrayref($row);
|
||||
}
|
||||
elsif (ref($row) eq 'HASH') {
|
||||
Bugzilla::DB::Oracle::_fix_hashref($row);
|
||||
}
|
||||
}
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub fetchall_hashref {
|
||||
my $self = shift;
|
||||
my $ref = $self->SUPER::fetchall_hashref(@_);
|
||||
return undef if !defined $ref;
|
||||
foreach my $row (values %$ref) {
|
||||
Bugzilla::DB::Oracle::_fix_hashref($row);
|
||||
}
|
||||
return $ref;
|
||||
}
|
||||
|
||||
sub fetch {
|
||||
my $self = shift;
|
||||
my $row = $self->SUPER::fetch(@_);
|
||||
if ($row) {
|
||||
Bugzilla::DB::Oracle::_fix_arrayref($row);
|
||||
}
|
||||
return $row;
|
||||
}
|
||||
1;
|
||||
@@ -1,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): Dave Miller <davem00@aol.com>
|
||||
# Gayathri Swaminath <gayathrik00@aol.com>
|
||||
# Jeroen Ruigrok van der Werven <asmodai@wxs.nl>
|
||||
# Dave Lawrence <dkl@redhat.com>
|
||||
# Tomas Kopal <Tomas.Kopal@altap.cz>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Lance Larsh <lance.larsh@oracle.com>
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::DB::Pg - Bugzilla database compatibility layer for PostgreSQL
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module overrides methods of the Bugzilla::DB module with PostgreSQL
|
||||
specific implementation. It is instantiated by the Bugzilla::DB module
|
||||
and should never be used directly.
|
||||
|
||||
For interface details see L<Bugzilla::DB> and L<DBI>.
|
||||
|
||||
=cut
|
||||
|
||||
package Bugzilla::DB::Pg;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use DBD::Pg;
|
||||
|
||||
# This module extends the DB interface via inheritance
|
||||
use base qw(Bugzilla::DB);
|
||||
|
||||
use constant BLOB_TYPE => { pg_type => DBD::Pg::PG_BYTEA };
|
||||
|
||||
sub new {
|
||||
my ($class, $user, $pass, $host, $dbname, $port) = @_;
|
||||
|
||||
# The default database name for PostgreSQL. We have
|
||||
# to connect to SOME database, even if we have
|
||||
# no $dbname parameter.
|
||||
$dbname ||= 'template1';
|
||||
|
||||
# construct the DSN from the parameters we got
|
||||
my $dsn = "dbi:Pg:dbname=$dbname";
|
||||
$dsn .= ";host=$host" if $host;
|
||||
$dsn .= ";port=$port" if $port;
|
||||
|
||||
# This stops Pg from printing out lots of "NOTICE" messages when
|
||||
# creating tables.
|
||||
$dsn .= ";options='-c client_min_messages=warning'";
|
||||
|
||||
my $attrs = { pg_enable_utf8 => Bugzilla->params->{'utf8'} };
|
||||
|
||||
my $self = $class->db_new($dsn, $user, $pass, $attrs);
|
||||
|
||||
# all class local variables stored in DBI derived class needs to have
|
||||
# a prefix 'private_'. See DBI documentation.
|
||||
$self->{private_bz_tables_locked} = "";
|
||||
# Needed by TheSchwartz
|
||||
$self->{private_bz_dsn} = $dsn;
|
||||
|
||||
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, $nocheck, $real_pattern) = @_;
|
||||
$real_pattern ||= $pattern;
|
||||
|
||||
$self->bz_check_regexp($real_pattern) if !$nocheck;
|
||||
|
||||
return "$expr ~* $pattern";
|
||||
}
|
||||
|
||||
sub sql_not_regexp {
|
||||
my ($self, $expr, $pattern, $nocheck, $real_pattern) = @_;
|
||||
$real_pattern ||= $pattern;
|
||||
|
||||
$self->bz_check_regexp($real_pattern) if !$nocheck;
|
||||
|
||||
return "$expr !~* $pattern"
|
||||
}
|
||||
|
||||
sub sql_limit {
|
||||
my ($self, $limit, $offset) = @_;
|
||||
|
||||
if (defined($offset)) {
|
||||
return "LIMIT $limit OFFSET $offset";
|
||||
} else {
|
||||
return "LIMIT $limit";
|
||||
}
|
||||
}
|
||||
|
||||
sub sql_from_days {
|
||||
my ($self, $days) = @_;
|
||||
|
||||
return "TO_TIMESTAMP(${days}::int, 'J')::date";
|
||||
}
|
||||
|
||||
sub sql_to_days {
|
||||
my ($self, $date) = @_;
|
||||
|
||||
return "TO_CHAR(${date}::date, 'J')::int";
|
||||
}
|
||||
|
||||
sub sql_date_format {
|
||||
my ($self, $date, $format) = @_;
|
||||
|
||||
$format = "%Y.%m.%d %H:%i:%s" if !$format;
|
||||
|
||||
$format =~ s/\%Y/YYYY/g;
|
||||
$format =~ s/\%y/YY/g;
|
||||
$format =~ s/\%m/MM/g;
|
||||
$format =~ s/\%d/DD/g;
|
||||
$format =~ s/\%a/Dy/g;
|
||||
$format =~ s/\%H/HH24/g;
|
||||
$format =~ s/\%i/MI/g;
|
||||
$format =~ s/\%s/SS/g;
|
||||
|
||||
return "TO_CHAR($date, " . $self->quote($format) . ")";
|
||||
}
|
||||
|
||||
sub sql_interval {
|
||||
my ($self, $interval, $units) = @_;
|
||||
|
||||
return "$interval * INTERVAL '1 $units'";
|
||||
}
|
||||
|
||||
sub sql_string_concat {
|
||||
my ($self, @params) = @_;
|
||||
|
||||
# Postgres 7.3 does not support concatenating of different types, so we
|
||||
# need to cast both parameters to text. Version 7.4 seems to handle this
|
||||
# properly, so when we stop support 7.3, this can be removed.
|
||||
return '(CAST(' . join(' AS text) || CAST(', @params) . ' AS text))';
|
||||
}
|
||||
|
||||
# Tell us whether or not a particular sequence exists in the DB.
|
||||
sub bz_sequence_exists {
|
||||
my ($self, $seq_name) = @_;
|
||||
my $exists = $self->selectrow_array(
|
||||
'SELECT 1 FROM pg_statio_user_sequences WHERE relname = ?',
|
||||
undef, $seq_name);
|
||||
return $exists || 0;
|
||||
}
|
||||
|
||||
sub bz_explain {
|
||||
my ($self, $sql) = @_;
|
||||
my $explain = $self->selectcol_arrayref("EXPLAIN ANALYZE $sql");
|
||||
return join("\n", @$explain);
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Custom Database Setup
|
||||
#####################################################################
|
||||
|
||||
sub bz_setup_database {
|
||||
my $self = shift;
|
||||
$self->SUPER::bz_setup_database(@_);
|
||||
|
||||
# PostgreSQL doesn't like having *any* index on the thetext
|
||||
# field, because it can't have index data longer than 2770
|
||||
# characters on that field.
|
||||
$self->bz_drop_index('longdescs', 'longdescs_thetext_idx');
|
||||
# Same for all the comments fields in the fulltext table.
|
||||
$self->bz_drop_index('bugs_fulltext', 'bugs_fulltext_comments_idx');
|
||||
$self->bz_drop_index('bugs_fulltext',
|
||||
'bugs_fulltext_comments_noprivate_idx');
|
||||
|
||||
# PostgreSQL also wants an index for calling LOWER on
|
||||
# login_name, which we do with sql_istrcmp all over the place.
|
||||
$self->bz_add_index('profiles', 'profiles_login_name_lower_idx',
|
||||
{FIELDS => ['LOWER(login_name)'], TYPE => 'UNIQUE'});
|
||||
|
||||
# Now that Bugzilla::Object uses sql_istrcmp, other tables
|
||||
# also need a LOWER() index.
|
||||
_fix_case_differences('fielddefs', 'name');
|
||||
$self->bz_add_index('fielddefs', 'fielddefs_name_lower_idx',
|
||||
{FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
|
||||
_fix_case_differences('keyworddefs', 'name');
|
||||
$self->bz_add_index('keyworddefs', 'keyworddefs_name_lower_idx',
|
||||
{FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
|
||||
_fix_case_differences('products', 'name');
|
||||
$self->bz_add_index('products', 'products_name_lower_idx',
|
||||
{FIELDS => ['LOWER(name)'], TYPE => 'UNIQUE'});
|
||||
|
||||
# bz_rename_column didn't correctly rename the sequence.
|
||||
if ($self->bz_column_info('fielddefs', 'id')
|
||||
&& $self->bz_sequence_exists('fielddefs_fieldid_seq'))
|
||||
{
|
||||
print "Fixing fielddefs_fieldid_seq sequence...\n";
|
||||
$self->do("ALTER TABLE fielddefs_fieldid_seq RENAME TO fielddefs_id_seq");
|
||||
$self->do("ALTER TABLE fielddefs ALTER COLUMN id
|
||||
SET DEFAULT NEXTVAL('fielddefs_id_seq')");
|
||||
}
|
||||
}
|
||||
|
||||
# Renames things that differ only in case.
|
||||
sub _fix_case_differences {
|
||||
my ($table, $field) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $duplicates = $dbh->selectcol_arrayref(
|
||||
"SELECT DISTINCT LOWER($field) FROM $table
|
||||
GROUP BY LOWER($field) HAVING COUNT(LOWER($field)) > 1");
|
||||
|
||||
foreach my $name (@$duplicates) {
|
||||
my $dups = $dbh->selectcol_arrayref(
|
||||
"SELECT $field FROM $table WHERE LOWER($field) = ?",
|
||||
undef, $name);
|
||||
my $primary = shift @$dups;
|
||||
foreach my $dup (@$dups) {
|
||||
my $new_name = "${dup}_";
|
||||
# Make sure the new name isn't *also* a duplicate.
|
||||
while (1) {
|
||||
last if (!$dbh->selectrow_array(
|
||||
"SELECT 1 FROM $table WHERE LOWER($field) = ?",
|
||||
undef, lc($new_name)));
|
||||
$new_name .= "_";
|
||||
}
|
||||
print "$table '$primary' and '$dup' have names that differ",
|
||||
" only in case.\nRenaming '$dup' to '$new_name'...\n";
|
||||
$dbh->do("UPDATE $table SET $field = ? WHERE $field = ?",
|
||||
undef, $new_name, $dup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#####################################################################
|
||||
# Custom Schema Information Functions
|
||||
#####################################################################
|
||||
|
||||
# Pg includes the PostgreSQL system tables in table_list_real, so
|
||||
# we need to remove those.
|
||||
sub bz_table_list_real {
|
||||
my $self = shift;
|
||||
|
||||
my @full_table_list = $self->SUPER::bz_table_list_real(@_);
|
||||
# All PostgreSQL system tables start with "pg_" or "sql_"
|
||||
my @table_list = grep(!/(^pg_)|(^sql_)/, @full_table_list);
|
||||
return @table_list;
|
||||
}
|
||||
|
||||
1;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,369 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Andrew Dunstan <andrew@dunslane.net>,
|
||||
# Edward J. Sabol <edwardjsabol@iname.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::DB::Schema::Mysql;
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# DB::Schema implementation for MySQL
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
use strict;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use base qw(Bugzilla::DB::Schema);
|
||||
|
||||
# This is for column_info_to_column, to know when a tinyint is a
|
||||
# boolean and when it's really a tinyint. This only has to be accurate
|
||||
# up to and through 2.19.3, because that's the only time we need
|
||||
# column_info_to_column.
|
||||
#
|
||||
# This is basically a hash of tables/columns, with one entry for each column
|
||||
# that should be interpreted as a BOOLEAN instead of as an INT1 when
|
||||
# reading in the Schema from the disk. The values are discarded; I just
|
||||
# used "1" for simplicity.
|
||||
use constant BOOLEAN_MAP => {
|
||||
bugs => {everconfirmed => 1, reporter_accessible => 1,
|
||||
cclist_accessible => 1, qacontact_accessible => 1,
|
||||
assignee_accessible => 1},
|
||||
longdescs => {isprivate => 1, already_wrapped => 1},
|
||||
attachments => {ispatch => 1, isobsolete => 1, isprivate => 1},
|
||||
flags => {is_active => 1},
|
||||
flagtypes => {is_active => 1, is_requestable => 1,
|
||||
is_requesteeble => 1, is_multiplicable => 1},
|
||||
fielddefs => {mailhead => 1, obsolete => 1},
|
||||
bug_status => {isactive => 1},
|
||||
resolution => {isactive => 1},
|
||||
bug_severity => {isactive => 1},
|
||||
priority => {isactive => 1},
|
||||
rep_platform => {isactive => 1},
|
||||
op_sys => {isactive => 1},
|
||||
profiles => {mybugslink => 1, newemailtech => 1},
|
||||
namedqueries => {linkinfooter => 1, watchfordiffs => 1},
|
||||
groups => {isbuggroup => 1, isactive => 1},
|
||||
group_control_map => {entry => 1, membercontrol => 1, othercontrol => 1,
|
||||
canedit => 1},
|
||||
group_group_map => {isbless => 1},
|
||||
user_group_map => {isbless => 1, isderived => 1},
|
||||
products => {disallownew => 1},
|
||||
series => {public => 1},
|
||||
whine_queries => {onemailperbug => 1},
|
||||
quips => {approved => 1},
|
||||
setting => {is_enabled => 1}
|
||||
};
|
||||
|
||||
# Maps the db_specific hash backwards, for use in column_info_to_column.
|
||||
use constant REVERSE_MAPPING => {
|
||||
# Boolean and the SERIAL fields are handled in column_info_to_column,
|
||||
# and so don't have an entry here.
|
||||
TINYINT => 'INT1',
|
||||
SMALLINT => 'INT2',
|
||||
MEDIUMINT => 'INT3',
|
||||
INTEGER => 'INT4',
|
||||
|
||||
# All the other types have the same name in their abstract version
|
||||
# as in their db-specific version, so no reverse mapping is needed.
|
||||
};
|
||||
|
||||
use constant MYISAM_TABLES => qw(bugs_fulltext);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
sub _initialize {
|
||||
|
||||
my $self = shift;
|
||||
|
||||
$self = $self->SUPER::_initialize(@_);
|
||||
|
||||
$self->{db_specific} = {
|
||||
|
||||
BOOLEAN => 'tinyint',
|
||||
FALSE => '0',
|
||||
TRUE => '1',
|
||||
|
||||
INT1 => 'tinyint',
|
||||
INT2 => 'smallint',
|
||||
INT3 => 'mediumint',
|
||||
INT4 => 'integer',
|
||||
|
||||
SMALLSERIAL => 'smallint auto_increment',
|
||||
MEDIUMSERIAL => 'mediumint auto_increment',
|
||||
INTSERIAL => 'integer auto_increment',
|
||||
|
||||
TINYTEXT => 'tinytext',
|
||||
MEDIUMTEXT => 'mediumtext',
|
||||
LONGTEXT => 'mediumtext',
|
||||
|
||||
LONGBLOB => 'longblob',
|
||||
|
||||
DATETIME => 'datetime',
|
||||
|
||||
};
|
||||
|
||||
$self->_adjust_schema;
|
||||
|
||||
return $self;
|
||||
|
||||
} #eosub--_initialize
|
||||
#------------------------------------------------------------------------------
|
||||
sub _get_create_table_ddl {
|
||||
# Extend superclass method to specify the MYISAM storage engine.
|
||||
# Returns a "create table" SQL statement.
|
||||
|
||||
my($self, $table) = @_;
|
||||
|
||||
my $charset = Bugzilla->dbh->bz_db_is_utf8 ? "CHARACTER SET utf8" : '';
|
||||
my $type = grep($_ eq $table, MYISAM_TABLES) ? 'MYISAM' : 'InnoDB';
|
||||
return($self->SUPER::_get_create_table_ddl($table)
|
||||
. " ENGINE = $type $charset");
|
||||
|
||||
} #eosub--_get_create_table_ddl
|
||||
#------------------------------------------------------------------------------
|
||||
sub _get_create_index_ddl {
|
||||
# Extend superclass method to create FULLTEXT indexes on text fields.
|
||||
# Returns a "create index" SQL statement.
|
||||
|
||||
my($self, $table_name, $index_name, $index_fields, $index_type) = @_;
|
||||
|
||||
my $sql = "CREATE ";
|
||||
$sql .= "$index_type " if ($index_type eq 'UNIQUE'
|
||||
|| $index_type eq 'FULLTEXT');
|
||||
$sql .= "INDEX \`$index_name\` ON $table_name \(" .
|
||||
join(", ", @$index_fields) . "\)";
|
||||
|
||||
return($sql);
|
||||
|
||||
} #eosub--_get_create_index_ddl
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
sub get_create_database_sql {
|
||||
my ($self, $name) = @_;
|
||||
# We only create as utf8 if we have no params (meaning we're doing
|
||||
# a new installation) or if the utf8 param is on.
|
||||
my $create_utf8 = Bugzilla->params->{'utf8'}
|
||||
|| !defined Bugzilla->params->{'utf8'};
|
||||
my $charset = $create_utf8 ? "CHARACTER SET utf8" : '';
|
||||
return ("CREATE DATABASE $name $charset");
|
||||
}
|
||||
|
||||
# MySQL has a simpler ALTER TABLE syntax than ANSI.
|
||||
sub get_alter_column_ddl {
|
||||
my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
|
||||
my $old_def = $self->get_column($table, $column);
|
||||
my %new_def_copy = %$new_def;
|
||||
if ($old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
|
||||
# If a column stays a primary key do NOT specify PRIMARY KEY in the
|
||||
# ALTER TABLE statement. This avoids a MySQL error that two primary
|
||||
# keys are not allowed.
|
||||
delete $new_def_copy{PRIMARYKEY};
|
||||
}
|
||||
|
||||
my $new_ddl = $self->get_type_ddl(\%new_def_copy);
|
||||
my @statements;
|
||||
|
||||
push(@statements, "UPDATE $table SET $column = $set_nulls_to
|
||||
WHERE $column IS NULL") if defined $set_nulls_to;
|
||||
push(@statements, "ALTER TABLE $table CHANGE COLUMN
|
||||
$column $column $new_ddl");
|
||||
if ($old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY}) {
|
||||
# Dropping a PRIMARY KEY needs an explicit DROP PRIMARY KEY
|
||||
push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
|
||||
}
|
||||
|
||||
return @statements;
|
||||
}
|
||||
|
||||
sub get_drop_fk_sql {
|
||||
my ($self, $table, $column, $references) = @_;
|
||||
my $fk_name = $self->_get_fk_name($table, $column, $references);
|
||||
my @sql = ("ALTER TABLE $table DROP FOREIGN KEY $fk_name");
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# MySQL requires, and will create, an index on any column with
|
||||
# an FK. It will name it after the fk, which we never do.
|
||||
# So if there's an index named after the fk, we also have to delete it.
|
||||
if ($dbh->bz_index_info_real($table, $fk_name)) {
|
||||
push(@sql, $self->get_drop_index_ddl($table, $fk_name));
|
||||
}
|
||||
|
||||
return @sql;
|
||||
}
|
||||
|
||||
sub get_drop_index_ddl {
|
||||
my ($self, $table, $name) = @_;
|
||||
return ("DROP INDEX \`$name\` ON $table");
|
||||
}
|
||||
|
||||
# A special function for MySQL, for renaming a lot of indexes.
|
||||
# Index renames is a hash, where the key is a string - the
|
||||
# old names of the index, and the value is a hash - the index
|
||||
# definition that we're renaming to, with an extra key of "NAME"
|
||||
# that contains the new index name.
|
||||
# The indexes in %indexes must be in hashref format.
|
||||
sub get_rename_indexes_ddl {
|
||||
my ($self, $table, %indexes) = @_;
|
||||
my @keys = keys %indexes or return ();
|
||||
|
||||
my $sql = "ALTER TABLE $table ";
|
||||
|
||||
foreach my $old_name (@keys) {
|
||||
my $name = $indexes{$old_name}->{NAME};
|
||||
my $type = $indexes{$old_name}->{TYPE};
|
||||
$type ||= 'INDEX';
|
||||
my $fields = join(',', @{$indexes{$old_name}->{FIELDS}});
|
||||
# $old_name needs to be escaped, sometimes, because it was
|
||||
# a reserved word.
|
||||
$old_name = '`' . $old_name . '`';
|
||||
$sql .= " ADD $type $name ($fields), DROP INDEX $old_name,";
|
||||
}
|
||||
# Remove the last comma.
|
||||
chop($sql);
|
||||
return ($sql);
|
||||
}
|
||||
|
||||
# Converts a DBI column_info output to an abstract column definition.
|
||||
# Expects to only be called by Bugzila::DB::Mysql::_bz_build_schema_from_disk,
|
||||
# although there's a chance that it will also work properly if called
|
||||
# elsewhere.
|
||||
sub column_info_to_column {
|
||||
my ($self, $column_info) = @_;
|
||||
|
||||
# Unfortunately, we have to break Schema's normal "no database"
|
||||
# barrier a few times in this function.
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $table = $column_info->{TABLE_NAME};
|
||||
my $col_name = $column_info->{COLUMN_NAME};
|
||||
|
||||
my $column = {};
|
||||
|
||||
($column->{NOTNULL} = 1) if $column_info->{NULLABLE} == 0;
|
||||
|
||||
if ($column_info->{mysql_is_pri_key}) {
|
||||
# In MySQL, if a table has no PK, but it has a UNIQUE index,
|
||||
# that index will show up as the PK. So we have to eliminate
|
||||
# that possibility.
|
||||
# Unfortunately, the only way to definitely solve this is
|
||||
# to break Schema's standard of not touching the live database
|
||||
# and check if the index called PRIMARY is on that field.
|
||||
my $pri_index = $dbh->bz_index_info_real($table, 'PRIMARY');
|
||||
if ( $pri_index && grep($_ eq $col_name, @{$pri_index->{FIELDS}}) ) {
|
||||
$column->{PRIMARYKEY} = 1;
|
||||
}
|
||||
}
|
||||
|
||||
# MySQL frequently defines a default for a field even when we
|
||||
# didn't explicitly set one. So we have to have some special
|
||||
# hacks to determine whether or not we should actually put
|
||||
# a default in the abstract schema for this field.
|
||||
if (defined $column_info->{COLUMN_DEF}) {
|
||||
# The defaults that MySQL inputs automatically are usually
|
||||
# something that would be considered "false" by perl, either
|
||||
# a 0 or an empty string. (Except for datetime and decimal
|
||||
# fields, which have their own special auto-defaults.)
|
||||
#
|
||||
# Here's how we handle this: If it exists in the schema
|
||||
# without a default, then we don't use the default. If it
|
||||
# doesn't exist in the schema, then we're either going to
|
||||
# be dropping it soon, or it's a custom end-user column, in which
|
||||
# case having a bogus default won't harm anything.
|
||||
my $schema_column = $self->get_column($table, $col_name);
|
||||
unless ( (!$column_info->{COLUMN_DEF}
|
||||
|| $column_info->{COLUMN_DEF} eq '0000-00-00 00:00:00'
|
||||
|| $column_info->{COLUMN_DEF} eq '0.00')
|
||||
&& $schema_column
|
||||
&& !exists $schema_column->{DEFAULT}) {
|
||||
|
||||
my $default = $column_info->{COLUMN_DEF};
|
||||
# Schema uses '0' for the defaults for decimal fields.
|
||||
$default = 0 if $default =~ /^0\.0+$/;
|
||||
# If we're not a number, we're a string and need to be
|
||||
# quoted.
|
||||
$default = $dbh->quote($default) if !($default =~ /^(-)?(\d+)(.\d+)?$/);
|
||||
$column->{DEFAULT} = $default;
|
||||
}
|
||||
}
|
||||
|
||||
my $type = $column_info->{TYPE_NAME};
|
||||
|
||||
# Certain types of columns need the size/precision appended.
|
||||
if ($type =~ /CHAR$/ || $type eq 'DECIMAL') {
|
||||
# This is nicely lowercase and has the size/precision appended.
|
||||
$type = $column_info->{mysql_type_name};
|
||||
}
|
||||
|
||||
# If we're a tinyint, we could be either a BOOLEAN or an INT1.
|
||||
# Only the BOOLEAN_MAP knows the difference.
|
||||
elsif ($type eq 'TINYINT' && exists BOOLEAN_MAP->{$table}
|
||||
&& exists BOOLEAN_MAP->{$table}->{$col_name}) {
|
||||
$type = 'BOOLEAN';
|
||||
if (exists $column->{DEFAULT}) {
|
||||
$column->{DEFAULT} = $column->{DEFAULT} ? 'TRUE' : 'FALSE';
|
||||
}
|
||||
}
|
||||
|
||||
# We also need to check if we're an auto_increment field.
|
||||
elsif ($type =~ /INT/) {
|
||||
# Unfortunately, the only way to do this in DBI is to query the
|
||||
# database, so we have to break the rule here that Schema normally
|
||||
# doesn't touch the live DB.
|
||||
my $ref_sth = $dbh->prepare(
|
||||
"SELECT $col_name FROM $table LIMIT 1");
|
||||
$ref_sth->execute;
|
||||
if ($ref_sth->{mysql_is_auto_increment}->[0]) {
|
||||
if ($type eq 'MEDIUMINT') {
|
||||
$type = 'MEDIUMSERIAL';
|
||||
}
|
||||
elsif ($type eq 'SMALLINT') {
|
||||
$type = 'SMALLSERIAL';
|
||||
}
|
||||
else {
|
||||
$type = 'INTSERIAL';
|
||||
}
|
||||
}
|
||||
$ref_sth->finish;
|
||||
|
||||
}
|
||||
|
||||
# For all other db-specific types, check if they exist in
|
||||
# REVERSE_MAPPING and use the type found there.
|
||||
if (exists REVERSE_MAPPING->{$type}) {
|
||||
$type = REVERSE_MAPPING->{$type};
|
||||
}
|
||||
|
||||
$column->{TYPE} = $type;
|
||||
|
||||
#print "$table.$col_name: " . Data::Dumper->Dump([$column]) . "\n";
|
||||
|
||||
return $column;
|
||||
}
|
||||
|
||||
sub get_rename_column_ddl {
|
||||
my ($self, $table, $old_name, $new_name) = @_;
|
||||
my $def = $self->get_type_ddl($self->get_column($table, $old_name));
|
||||
# MySQL doesn't like having the PRIMARY KEY statement in a rename.
|
||||
$def =~ s/PRIMARY KEY//i;
|
||||
return ("ALTER TABLE $table CHANGE COLUMN $old_name $new_name $def");
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,403 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Oracle Corporation.
|
||||
# Portions created by Oracle are Copyright (C) 2007 Oracle Corporation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Lance Larsh <lance.larsh@oracle.com>
|
||||
# Xiaoou Wu <xiaoou.wu@oracle.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::DB::Schema::Oracle;
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# DB::Schema implementation for Oracle
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Bugzilla::DB::Schema);
|
||||
use Carp qw(confess);
|
||||
use Bugzilla::Util;
|
||||
|
||||
use constant ADD_COLUMN => 'ADD';
|
||||
# Whether this is true or not, this is what it needs to be in order for
|
||||
# hash_identifier to maintain backwards compatibility with versions before
|
||||
# 3.2rc2.
|
||||
use constant MAX_IDENTIFIER_LEN => 27;
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
sub _initialize {
|
||||
|
||||
my $self = shift;
|
||||
|
||||
$self = $self->SUPER::_initialize(@_);
|
||||
|
||||
$self->{db_specific} = {
|
||||
|
||||
BOOLEAN => 'integer',
|
||||
FALSE => '0',
|
||||
TRUE => '1',
|
||||
|
||||
INT1 => 'integer',
|
||||
INT2 => 'integer',
|
||||
INT3 => 'integer',
|
||||
INT4 => 'integer',
|
||||
|
||||
SMALLSERIAL => 'integer',
|
||||
MEDIUMSERIAL => 'integer',
|
||||
INTSERIAL => 'integer',
|
||||
|
||||
TINYTEXT => 'varchar(255)',
|
||||
MEDIUMTEXT => 'varchar(4000)',
|
||||
LONGTEXT => 'clob',
|
||||
|
||||
LONGBLOB => 'blob',
|
||||
|
||||
DATETIME => 'date',
|
||||
|
||||
};
|
||||
|
||||
$self->_adjust_schema;
|
||||
|
||||
return $self;
|
||||
|
||||
} #eosub--_initialize
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
sub get_table_ddl {
|
||||
my $self = shift;
|
||||
my $table = shift;
|
||||
unshift @_, $table;
|
||||
my @ddl = $self->SUPER::get_table_ddl(@_);
|
||||
|
||||
my @fields = @{ $self->{abstract_schema}{$table}{FIELDS} || [] };
|
||||
while (@fields) {
|
||||
my $field_name = shift @fields;
|
||||
my $field_info = shift @fields;
|
||||
# Create triggers to deal with empty string.
|
||||
if ( $field_info->{TYPE} =~ /varchar|TEXT/i
|
||||
&& $field_info->{NOTNULL} ) {
|
||||
push (@ddl, _get_notnull_trigger_ddl($table, $field_name));
|
||||
}
|
||||
# Create sequences and triggers to emulate SERIAL datatypes.
|
||||
if ( $field_info->{TYPE} =~ /SERIAL/i ) {
|
||||
push (@ddl, $self->_get_create_seq_ddl($table, $field_name));
|
||||
}
|
||||
}
|
||||
return @ddl;
|
||||
|
||||
} #eosub--get_table_ddl
|
||||
|
||||
# Extend superclass method to create Oracle Text indexes if index type
|
||||
# is FULLTEXT from schema. Returns a "create index" SQL statement.
|
||||
sub _get_create_index_ddl {
|
||||
|
||||
my ($self, $table_name, $index_name, $index_fields, $index_type) = @_;
|
||||
$index_name = "idx_" . $self->_hash_identifier($index_name);
|
||||
if ($index_type eq 'FULLTEXT') {
|
||||
my $sql = "CREATE INDEX $index_name ON $table_name ("
|
||||
. join(',',@$index_fields)
|
||||
. ") INDEXTYPE IS CTXSYS.CONTEXT "
|
||||
. " PARAMETERS('LEXER BZ_LEX SYNC(ON COMMIT)')" ;
|
||||
return $sql;
|
||||
}
|
||||
|
||||
return($self->SUPER::_get_create_index_ddl($table_name, $index_name,
|
||||
$index_fields, $index_type));
|
||||
|
||||
}
|
||||
|
||||
sub get_drop_index_ddl {
|
||||
my $self = shift;
|
||||
my ($table, $name) = @_;
|
||||
|
||||
$name = 'idx_' . $self->_hash_identifier($name);
|
||||
return $self->SUPER::get_drop_index_ddl($table, $name);
|
||||
}
|
||||
|
||||
# Oracle supports the use of FOREIGN KEY integrity constraints
|
||||
# to define the referential integrity actions, including:
|
||||
# - Update and delete No Action (default)
|
||||
# - Delete CASCADE
|
||||
# - Delete SET NULL
|
||||
sub get_fk_ddl {
|
||||
my ($self, $table, $column, $references) = @_;
|
||||
return "" if !$references;
|
||||
|
||||
my $update = $references->{UPDATE} || 'CASCADE';
|
||||
my $delete = $references->{DELETE};
|
||||
my $to_table = $references->{TABLE} || confess "No table in reference";
|
||||
my $to_column = $references->{COLUMN} || confess "No column in reference";
|
||||
my $fk_name = $self->_get_fk_name($table, $column, $references);
|
||||
|
||||
my $fk_string = "\n CONSTRAINT $fk_name FOREIGN KEY ($column)\n"
|
||||
. " REFERENCES $to_table($to_column)\n";
|
||||
|
||||
$fk_string = $fk_string . " ON DELETE $delete" if $delete;
|
||||
|
||||
if ( $update =~ /CASCADE/i ){
|
||||
my $tr_str = "CREATE OR REPLACE TRIGGER ${fk_name}_UC"
|
||||
. " AFTER UPDATE OF $to_column ON $to_table "
|
||||
. " REFERENCING "
|
||||
. " NEW AS NEW "
|
||||
. " OLD AS OLD "
|
||||
. " FOR EACH ROW "
|
||||
. " BEGIN "
|
||||
. " UPDATE $table"
|
||||
. " SET $column = :NEW.$to_column"
|
||||
. " WHERE $column = :OLD.$to_column;"
|
||||
. " END ${fk_name}_UC;";
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->do($tr_str);
|
||||
}
|
||||
|
||||
return $fk_string;
|
||||
}
|
||||
|
||||
sub get_drop_fk_sql {
|
||||
my $self = shift;
|
||||
my ($table, $column, $references) = @_;
|
||||
my $fk_name = $self->_get_fk_name(@_);
|
||||
my @sql;
|
||||
if (!$references->{UPDATE} || $references->{UPDATE} =~ /CASCADE/i) {
|
||||
push(@sql, "DROP TRIGGER ${fk_name}_uc");
|
||||
}
|
||||
push(@sql, $self->SUPER::get_drop_fk_sql(@_));
|
||||
return @sql;
|
||||
}
|
||||
|
||||
sub _get_fk_name {
|
||||
my ($self, $table, $column, $references) = @_;
|
||||
my $to_table = $references->{TABLE};
|
||||
my $to_column = $references->{COLUMN};
|
||||
my $fk_name = "${table}_${column}_${to_table}_${to_column}";
|
||||
$fk_name = "fk_" . $self->_hash_identifier($fk_name);
|
||||
|
||||
return $fk_name;
|
||||
}
|
||||
|
||||
sub get_alter_column_ddl {
|
||||
my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
|
||||
|
||||
my @statements;
|
||||
my $old_def = $self->get_column_abstract($table, $column);
|
||||
my $specific = $self->{db_specific};
|
||||
|
||||
# If the types have changed, we have to deal with that.
|
||||
if (uc(trim($old_def->{TYPE})) ne uc(trim($new_def->{TYPE}))) {
|
||||
push(@statements, $self->_get_alter_type_sql($table, $column,
|
||||
$new_def, $old_def));
|
||||
}
|
||||
|
||||
my $default = $new_def->{DEFAULT};
|
||||
my $default_old = $old_def->{DEFAULT};
|
||||
# This first condition prevents "uninitialized value" errors.
|
||||
if (!defined $default && !defined $default_old) {
|
||||
# Do Nothing
|
||||
}
|
||||
# If we went from having a default to not having one
|
||||
elsif (!defined $default && defined $default_old) {
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column"
|
||||
. " DEFAULT NULL");
|
||||
}
|
||||
# If we went from no default to a default, or we changed the default.
|
||||
elsif ( (defined $default && !defined $default_old) ||
|
||||
($default ne $default_old) )
|
||||
{
|
||||
$default = $specific->{$default} if exists $specific->{$default};
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column "
|
||||
. " DEFAULT $default");
|
||||
}
|
||||
|
||||
# If we went from NULL to NOT NULL.
|
||||
if (!$old_def->{NOTNULL} && $new_def->{NOTNULL}) {
|
||||
my $setdefault;
|
||||
# Handle any fields that were NULL before, if we have a default,
|
||||
$setdefault = $new_def->{DEFAULT} if exists $new_def->{DEFAULT};
|
||||
# But if we have a set_nulls_to, that overrides the DEFAULT
|
||||
# (although nobody would usually specify both a default and
|
||||
# a set_nulls_to.)
|
||||
$setdefault = $set_nulls_to if defined $set_nulls_to;
|
||||
if (defined $setdefault) {
|
||||
push(@statements, "UPDATE $table SET $column = $setdefault"
|
||||
. " WHERE $column IS NULL");
|
||||
}
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column"
|
||||
. " NOT NULL");
|
||||
push (@statements, _get_notnull_trigger_ddl($table, $column))
|
||||
if $old_def->{TYPE} =~ /varchar|text/i
|
||||
&& $new_def->{TYPE} =~ /varchar|text/i;
|
||||
}
|
||||
# If we went from NOT NULL to NULL
|
||||
elsif ($old_def->{NOTNULL} && !$new_def->{NOTNULL}) {
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column"
|
||||
. " NULL");
|
||||
push(@statements, "DROP TRIGGER ${table}_${column}")
|
||||
if $new_def->{TYPE} =~ /varchar|text/i
|
||||
&& $old_def->{TYPE} =~ /varchar|text/i;
|
||||
}
|
||||
|
||||
# If we went from not being a PRIMARY KEY to being a PRIMARY KEY.
|
||||
if (!$old_def->{PRIMARYKEY} && $new_def->{PRIMARYKEY}) {
|
||||
push(@statements, "ALTER TABLE $table ADD PRIMARY KEY ($column)");
|
||||
}
|
||||
# If we went from being a PK to not being a PK
|
||||
elsif ( $old_def->{PRIMARYKEY} && !$new_def->{PRIMARYKEY} ) {
|
||||
push(@statements, "ALTER TABLE $table DROP PRIMARY KEY");
|
||||
}
|
||||
|
||||
return @statements;
|
||||
}
|
||||
|
||||
sub _get_alter_type_sql {
|
||||
my ($self, $table, $column, $new_def, $old_def) = @_;
|
||||
my @statements;
|
||||
|
||||
my $type = $new_def->{TYPE};
|
||||
$type = $self->{db_specific}->{$type}
|
||||
if exists $self->{db_specific}->{$type};
|
||||
|
||||
if ($type =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
|
||||
die("You cannot specify a DEFAULT on a SERIAL-type column.")
|
||||
if $new_def->{DEFAULT};
|
||||
}
|
||||
|
||||
if ( ($old_def->{TYPE} =~ /LONGTEXT/i && $new_def->{TYPE} !~ /LONGTEXT/i)
|
||||
|| ($old_def->{TYPE} !~ /LONGTEXT/i && $new_def->{TYPE} =~ /LONGTEXT/i)
|
||||
) {
|
||||
# LONG to VARCHAR or VARCHAR to LONG is not allowed in Oracle,
|
||||
# just a way to work around.
|
||||
# Determine whether column_temp is already exist.
|
||||
my $dbh=Bugzilla->dbh;
|
||||
my $column_exist = $dbh->selectcol_arrayref(
|
||||
"SELECT CNAME FROM COL WHERE TNAME = UPPER(?) AND
|
||||
CNAME = UPPER(?)", undef,$table,$column . "_temp");
|
||||
if(!@$column_exist) {
|
||||
push(@statements,
|
||||
"ALTER TABLE $table ADD ${column}_temp $type");
|
||||
}
|
||||
push(@statements, "UPDATE $table SET ${column}_temp = $column");
|
||||
push(@statements, "COMMIT");
|
||||
push(@statements, "ALTER TABLE $table DROP COLUMN $column");
|
||||
push(@statements,
|
||||
"ALTER TABLE $table RENAME COLUMN ${column}_temp TO $column");
|
||||
} else {
|
||||
push(@statements, "ALTER TABLE $table MODIFY $column $type");
|
||||
}
|
||||
|
||||
if ($new_def->{TYPE} =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
|
||||
push(@statements, _get_create_seq_ddl($table, $column));
|
||||
}
|
||||
|
||||
# If this column is no longer SERIAL, we need to drop the sequence
|
||||
# that went along with it.
|
||||
if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
|
||||
push(@statements, "DROP SEQUENCE ${table}_${column}_SEQ");
|
||||
push(@statements, "DROP TRIGGER ${table}_${column}_TR");
|
||||
}
|
||||
|
||||
# If this column is changed to type TEXT/VARCHAR, we need to deal with
|
||||
# empty string.
|
||||
if ( $old_def->{TYPE} !~ /varchar|text/i
|
||||
&& $new_def->{TYPE} =~ /varchar|text/i
|
||||
&& $new_def->{NOTNULL} )
|
||||
{
|
||||
push (@statements, _get_notnull_trigger_ddl($table, $column));
|
||||
}
|
||||
# If this column is no longer TEXT/VARCHAR, we need to drop the trigger
|
||||
# that went along with it.
|
||||
if ( $old_def->{TYPE} =~ /varchar|text/i
|
||||
&& $old_def->{NOTNULL}
|
||||
&& $new_def->{TYPE} !~ /varchar|text/i )
|
||||
{
|
||||
push(@statements, "DROP TRIGGER ${table}_${column}");
|
||||
}
|
||||
return @statements;
|
||||
}
|
||||
|
||||
sub get_rename_column_ddl {
|
||||
my ($self, $table, $old_name, $new_name) = @_;
|
||||
if (lc($old_name) eq lc($new_name)) {
|
||||
# if the only change is a case change, return an empty list.
|
||||
return ();
|
||||
}
|
||||
my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
|
||||
my $def = $self->get_column_abstract($table, $old_name);
|
||||
if ($def->{TYPE} =~ /SERIAL/i) {
|
||||
# We have to rename the series also, and fix the default of the series.
|
||||
push(@sql, "RENAME ${table}_${old_name}_SEQ TO
|
||||
${table}_${new_name}_seq");
|
||||
my $serial_sql =
|
||||
"CREATE OR REPLACE TRIGGER ${table}_${new_name}_TR "
|
||||
. " BEFORE INSERT ON ${table} "
|
||||
. " FOR EACH ROW "
|
||||
. " BEGIN "
|
||||
. " SELECT ${table}_${new_name}_SEQ.NEXTVAL "
|
||||
. " INTO :NEW.${new_name} FROM DUAL; "
|
||||
. " END;";
|
||||
push(@sql, $serial_sql);
|
||||
push(@sql, "DROP TRIGGER ${table}_${old_name}_TR");
|
||||
}
|
||||
if ($def->{TYPE} =~ /varchar|text/i && $def->{NOTNULL} ) {
|
||||
push(@sql, _get_notnull_trigger_ddl($table,$new_name));
|
||||
push(@sql, "DROP TRIGGER ${table}_${old_name}");
|
||||
}
|
||||
return @sql;
|
||||
}
|
||||
|
||||
sub _get_notnull_trigger_ddl {
|
||||
my ($table, $column) = @_;
|
||||
|
||||
my $notnull_sql = "CREATE OR REPLACE TRIGGER "
|
||||
. " ${table}_${column}"
|
||||
. " BEFORE INSERT OR UPDATE ON ". $table
|
||||
. " FOR EACH ROW"
|
||||
. " BEGIN "
|
||||
. " IF :NEW.". $column ." IS NULL THEN "
|
||||
. " SELECT '" . Bugzilla::DB::Oracle->EMPTY_STRING
|
||||
. "' INTO :NEW.". $column ." FROM DUAL; "
|
||||
. " END IF; "
|
||||
. " END ".$table.";";
|
||||
return $notnull_sql;
|
||||
}
|
||||
|
||||
sub _get_create_seq_ddl {
|
||||
my ($self, $table, $column, $start_with) = @_;
|
||||
$start_with ||= 1;
|
||||
my @ddl;
|
||||
my $seq_name = "${table}_${column}_SEQ";
|
||||
my $seq_sql = "CREATE SEQUENCE $seq_name "
|
||||
. " INCREMENT BY 1 "
|
||||
. " START WITH $start_with "
|
||||
. " NOMAXVALUE "
|
||||
. " NOCYCLE "
|
||||
. " NOCACHE";
|
||||
my $serial_sql = "CREATE OR REPLACE TRIGGER ${table}_${column}_TR "
|
||||
. " BEFORE INSERT ON ${table} "
|
||||
. " FOR EACH ROW "
|
||||
. " BEGIN "
|
||||
. " SELECT ${seq_name}.NEXTVAL "
|
||||
. " INTO :NEW.${column} FROM DUAL; "
|
||||
. " END;";
|
||||
push (@ddl, $seq_sql);
|
||||
push (@ddl, $serial_sql);
|
||||
|
||||
return @ddl;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,166 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Andrew Dunstan <andrew@dunslane.net>,
|
||||
# Edward J. Sabol <edwardjsabol@iname.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::DB::Schema::Pg;
|
||||
|
||||
###############################################################################
|
||||
#
|
||||
# DB::Schema implementation for PostgreSQL
|
||||
#
|
||||
###############################################################################
|
||||
|
||||
use strict;
|
||||
use base qw(Bugzilla::DB::Schema);
|
||||
use Storable qw(dclone);
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
sub _initialize {
|
||||
|
||||
my $self = shift;
|
||||
|
||||
$self = $self->SUPER::_initialize(@_);
|
||||
|
||||
# Remove FULLTEXT index types from the schemas.
|
||||
foreach my $table (keys %{ $self->{schema} }) {
|
||||
if ($self->{schema}{$table}{INDEXES}) {
|
||||
foreach my $index (@{ $self->{schema}{$table}{INDEXES} }) {
|
||||
if (ref($index) eq 'HASH') {
|
||||
delete($index->{TYPE}) if (exists $index->{TYPE}
|
||||
&& $index->{TYPE} eq 'FULLTEXT');
|
||||
}
|
||||
}
|
||||
foreach my $index (@{ $self->{abstract_schema}{$table}{INDEXES} }) {
|
||||
if (ref($index) eq 'HASH') {
|
||||
delete($index->{TYPE}) if (exists $index->{TYPE}
|
||||
&& $index->{TYPE} eq 'FULLTEXT');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$self->{db_specific} = {
|
||||
|
||||
BOOLEAN => 'smallint',
|
||||
FALSE => '0',
|
||||
TRUE => '1',
|
||||
|
||||
INT1 => 'integer',
|
||||
INT2 => 'integer',
|
||||
INT3 => 'integer',
|
||||
INT4 => 'integer',
|
||||
|
||||
SMALLSERIAL => 'serial unique',
|
||||
MEDIUMSERIAL => 'serial unique',
|
||||
INTSERIAL => 'serial unique',
|
||||
|
||||
TINYTEXT => 'varchar(255)',
|
||||
MEDIUMTEXT => 'text',
|
||||
LONGTEXT => 'text',
|
||||
|
||||
LONGBLOB => 'bytea',
|
||||
|
||||
DATETIME => 'timestamp(0) without time zone',
|
||||
|
||||
};
|
||||
|
||||
$self->_adjust_schema;
|
||||
|
||||
return $self;
|
||||
|
||||
} #eosub--_initialize
|
||||
#--------------------------------------------------------------------
|
||||
|
||||
sub get_rename_column_ddl {
|
||||
my ($self, $table, $old_name, $new_name) = @_;
|
||||
if (lc($old_name) eq lc($new_name)) {
|
||||
# if the only change is a case change, return an empty list, since Pg
|
||||
# is case-insensitive and will return an error about a duplicate name
|
||||
return ();
|
||||
}
|
||||
my @sql = ("ALTER TABLE $table RENAME COLUMN $old_name TO $new_name");
|
||||
my $def = $self->get_column_abstract($table, $old_name);
|
||||
if ($def->{TYPE} =~ /SERIAL/i) {
|
||||
# We have to rename the series also, and fix the default of the series.
|
||||
push(@sql, "ALTER TABLE ${table}_${old_name}_seq
|
||||
RENAME TO ${table}_${new_name}_seq");
|
||||
push(@sql, "ALTER TABLE $table ALTER COLUMN $new_name
|
||||
SET DEFAULT NEXTVAL('${table}_${new_name}_seq')");
|
||||
}
|
||||
return @sql;
|
||||
}
|
||||
|
||||
sub get_rename_table_sql {
|
||||
my ($self, $old_name, $new_name) = @_;
|
||||
if (lc($old_name) eq lc($new_name)) {
|
||||
# if the only change is a case change, return an empty list, since Pg
|
||||
# is case-insensitive and will return an error about a duplicate name
|
||||
return ();
|
||||
}
|
||||
return ("ALTER TABLE $old_name RENAME TO $new_name");
|
||||
}
|
||||
|
||||
sub _get_alter_type_sql {
|
||||
my ($self, $table, $column, $new_def, $old_def) = @_;
|
||||
my @statements;
|
||||
|
||||
my $type = $new_def->{TYPE};
|
||||
$type = $self->{db_specific}->{$type}
|
||||
if exists $self->{db_specific}->{$type};
|
||||
|
||||
if ($type =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
|
||||
die("You cannot specify a DEFAULT on a SERIAL-type column.")
|
||||
if $new_def->{DEFAULT};
|
||||
$type =~ s/serial/integer/i;
|
||||
}
|
||||
|
||||
# On Pg, you don't need UNIQUE if you're a PK--it creates
|
||||
# two identical indexes otherwise.
|
||||
$type =~ s/unique//i if $new_def->{PRIMARYKEY};
|
||||
|
||||
push(@statements, "ALTER TABLE $table ALTER COLUMN $column
|
||||
TYPE $type");
|
||||
|
||||
if ($new_def->{TYPE} =~ /serial/i && $old_def->{TYPE} !~ /serial/i) {
|
||||
push(@statements, "CREATE SEQUENCE ${table}_${column}_seq");
|
||||
push(@statements, "SELECT setval('${table}_${column}_seq',
|
||||
MAX($table.$column))
|
||||
FROM $table");
|
||||
push(@statements, "ALTER TABLE $table ALTER COLUMN $column
|
||||
SET DEFAULT nextval('${table}_${column}_seq')");
|
||||
}
|
||||
|
||||
# If this column is no longer SERIAL, we need to drop the sequence
|
||||
# that went along with it.
|
||||
if ($old_def->{TYPE} =~ /serial/i && $new_def->{TYPE} !~ /serial/i) {
|
||||
push(@statements, "ALTER TABLE $table ALTER COLUMN $column
|
||||
DROP DEFAULT");
|
||||
# XXX Pg actually won't let us drop the sequence, even though it's
|
||||
# no longer in use. So we harmlessly leave behind a sequence
|
||||
# that does nothing.
|
||||
#push(@statements, "DROP SEQUENCE ${table}_${column}_seq");
|
||||
}
|
||||
|
||||
return @statements;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,234 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@acm.org>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
package Bugzilla::Error;
|
||||
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
|
||||
@Bugzilla::Error::EXPORT = qw(ThrowCodeError ThrowTemplateError ThrowUserError);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::WebService::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Date::Format;
|
||||
|
||||
# We cannot use $^S to detect if we are in an eval(), because mod_perl
|
||||
# already eval'uates everything, so $^S = 1 in all cases under mod_perl!
|
||||
sub _in_eval {
|
||||
my $in_eval = 0;
|
||||
for (my $stack = 1; my $sub = (caller($stack))[3]; $stack++) {
|
||||
last if $sub =~ /^ModPerl/;
|
||||
$in_eval = 1 if $sub =~ /^\(eval\)/;
|
||||
}
|
||||
return $in_eval;
|
||||
}
|
||||
|
||||
sub _throw_error {
|
||||
my ($name, $error, $vars) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$vars ||= {};
|
||||
|
||||
$vars->{error} = $error;
|
||||
|
||||
# Make sure any transaction is rolled back (if supported).
|
||||
# If we are within an eval(), do not roll back transactions as we are
|
||||
# eval'uating some test on purpose.
|
||||
$dbh->bz_rollback_transaction() if ($dbh->bz_in_transaction() && !_in_eval());
|
||||
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
# If a writable $datadir/errorlog exists, log error details there.
|
||||
if (-w "$datadir/errorlog") {
|
||||
require Data::Dumper;
|
||||
my $mesg = "";
|
||||
for (1..75) { $mesg .= "-"; };
|
||||
$mesg .= "\n[$$] " . time2str("%D %H:%M:%S ", time());
|
||||
$mesg .= "$name $error ";
|
||||
$mesg .= "$ENV{REMOTE_ADDR} " if $ENV{REMOTE_ADDR};
|
||||
$mesg .= Bugzilla->user->login;
|
||||
$mesg .= (' actually ' . Bugzilla->sudoer->login) if Bugzilla->sudoer;
|
||||
$mesg .= "\n";
|
||||
my %params = Bugzilla->cgi->Vars;
|
||||
$Data::Dumper::Useqq = 1;
|
||||
for my $param (sort keys %params) {
|
||||
my $val = $params{$param};
|
||||
# obscure passwords
|
||||
$val = "*****" if $param =~ /password/i;
|
||||
# limit line length
|
||||
$val =~ s/^(.{512}).*$/$1\[CHOP\]/;
|
||||
$mesg .= "[$$] " . Data::Dumper->Dump([$val],["param($param)"]);
|
||||
}
|
||||
for my $var (sort keys %ENV) {
|
||||
my $val = $ENV{$var};
|
||||
$val = "*****" if $val =~ /password|http_pass/i;
|
||||
$mesg .= "[$$] " . Data::Dumper->Dump([$val],["env($var)"]);
|
||||
}
|
||||
open(ERRORLOGFID, ">>$datadir/errorlog");
|
||||
print ERRORLOGFID "$mesg\n";
|
||||
close ERRORLOGFID;
|
||||
}
|
||||
|
||||
my $template = Bugzilla->template;
|
||||
if (Bugzilla->error_mode == ERROR_MODE_WEBPAGE) {
|
||||
print Bugzilla->cgi->header();
|
||||
$template->process($name, $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
}
|
||||
else {
|
||||
my $message;
|
||||
$template->process($name, $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
if (Bugzilla->error_mode == ERROR_MODE_DIE) {
|
||||
die("$message\n");
|
||||
}
|
||||
elsif (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) {
|
||||
# Clone the hash so we aren't modifying the constant.
|
||||
my %error_map = %{ WS_ERROR_CODE() };
|
||||
require Bugzilla::Hook;
|
||||
Bugzilla::Hook::process('webservice-error_codes',
|
||||
{ error_map => \%error_map });
|
||||
my $code = $error_map{$error};
|
||||
if (!$code) {
|
||||
$code = ERROR_UNKNOWN_FATAL if $name =~ /code/i;
|
||||
$code = ERROR_UNKNOWN_TRANSIENT if $name =~ /user/i;
|
||||
}
|
||||
die SOAP::Fault->faultcode($code)->faultstring($message);
|
||||
}
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
sub ThrowUserError {
|
||||
_throw_error("global/user-error.html.tmpl", @_);
|
||||
}
|
||||
|
||||
sub ThrowCodeError {
|
||||
_throw_error("global/code-error.html.tmpl", @_);
|
||||
}
|
||||
|
||||
sub ThrowTemplateError {
|
||||
my ($template_err) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Make sure the transaction is rolled back (if supported).
|
||||
$dbh->bz_rollback_transaction() if $dbh->bz_in_transaction();
|
||||
|
||||
my $vars = {};
|
||||
if (Bugzilla->error_mode == ERROR_MODE_DIE) {
|
||||
die("error: template error: $template_err");
|
||||
}
|
||||
|
||||
$vars->{'template_error_msg'} = $template_err;
|
||||
$vars->{'error'} = "template_error";
|
||||
|
||||
my $template = Bugzilla->template;
|
||||
|
||||
# Try a template first; but if this one fails too, fall back
|
||||
# on plain old print statements.
|
||||
if (!$template->process("global/code-error.html.tmpl", $vars)) {
|
||||
my $maintainer = Bugzilla->params->{'maintainer'};
|
||||
my $error = html_quote($vars->{'template_error_msg'});
|
||||
my $error2 = html_quote($template->error());
|
||||
print <<END;
|
||||
<tt>
|
||||
<p>
|
||||
Bugzilla has suffered an internal error. Please save this page and
|
||||
send it to $maintainer with details of what you were doing at the
|
||||
time this message appeared.
|
||||
</p>
|
||||
<script type="text/javascript"> <!--
|
||||
document.write("<p>URL: " +
|
||||
document.location.href.replace(/&/g,"&")
|
||||
.replace(/</g,"<")
|
||||
.replace(/>/g,">") + "</p>");
|
||||
// -->
|
||||
</script>
|
||||
<p>Template->process() failed twice.<br>
|
||||
First error: $error<br>
|
||||
Second error: $error2</p>
|
||||
</tt>
|
||||
END
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Error - Error handling utilities for Bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Error;
|
||||
|
||||
ThrowUserError("error_tag",
|
||||
{ foo => 'bar' });
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Various places throughout the Bugzilla codebase need to report errors to the
|
||||
user. The C<Throw*Error> family of functions allow this to be done in a
|
||||
generic and localizable manner.
|
||||
|
||||
These functions automatically unlock the database tables, if there were any
|
||||
locked. They will also roll back the transaction, if it is supported by
|
||||
the underlying DB.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<ThrowUserError>
|
||||
|
||||
This function takes an error tag as the first argument, and an optional hashref
|
||||
of variables as a second argument. These are used by the
|
||||
I<global/user-error.html.tmpl> template to format the error, using the passed
|
||||
in variables as required.
|
||||
|
||||
=item C<ThrowCodeError>
|
||||
|
||||
This function is used when an internal check detects an error of some sort.
|
||||
This usually indicates a bug in Bugzilla, although it can occur if the user
|
||||
manually constructs urls without correct parameters.
|
||||
|
||||
This function's behaviour is similar to C<ThrowUserError>, except that the
|
||||
template used to display errors is I<global/code-error.html.tmpl>. In addition
|
||||
if the hashref used as the optional second argument contains a key I<variables>
|
||||
then the contents of the hashref (which is expected to be another hashref) will
|
||||
be displayed after the error message, as a debugging aid.
|
||||
|
||||
=item C<ThrowTemplateError>
|
||||
|
||||
This function should only be called if a C<template-<gt>process()> fails.
|
||||
It tries another template first, because often one template being
|
||||
broken or missing doesn't mean that they all are. But it falls back to
|
||||
a print statement as a last-ditch error.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla|Bugzilla>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,445 +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 NASA.
|
||||
# Portions created by NASA are Copyright (C) 2006 San Jose State
|
||||
# University Foundation. All Rights Reserved.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Field::Choice;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Config qw(SetParam write_params);
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Field;
|
||||
use Bugzilla::Util qw(trim detaint_natural);
|
||||
|
||||
use Scalar::Util qw(blessed);
|
||||
|
||||
##################
|
||||
# Initialization #
|
||||
##################
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
value
|
||||
sortkey
|
||||
visibility_value_id
|
||||
);
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
value
|
||||
sortkey
|
||||
visibility_value_id
|
||||
);
|
||||
|
||||
use constant NAME_FIELD => 'value';
|
||||
use constant LIST_ORDER => 'sortkey, value';
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(value);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
value => \&_check_value,
|
||||
sortkey => \&_check_sortkey,
|
||||
visibility_value_id => \&_check_visibility_value_id,
|
||||
};
|
||||
|
||||
use constant CLASS_MAP => {
|
||||
bug_status => 'Bugzilla::Status',
|
||||
product => 'Bugzilla::Product',
|
||||
};
|
||||
|
||||
use constant DEFAULT_MAP => {
|
||||
op_sys => 'defaultopsys',
|
||||
rep_platform => 'defaultplatform',
|
||||
priority => 'defaultpriority',
|
||||
bug_severity => 'defaultseverity',
|
||||
};
|
||||
|
||||
#################
|
||||
# Class Factory #
|
||||
#################
|
||||
|
||||
# Bugzilla::Field::Choice is actually an abstract base class. Every field
|
||||
# type has its own dynamically-generated class for its values. This allows
|
||||
# certain fields to have special types, like how bug_status's values
|
||||
# are Bugzilla::Status objects.
|
||||
|
||||
sub type {
|
||||
my ($class, $field) = @_;
|
||||
my $field_obj = blessed $field ? $field : Bugzilla::Field->check($field);
|
||||
my $field_name = $field_obj->name;
|
||||
|
||||
if ($class->CLASS_MAP->{$field_name}) {
|
||||
return $class->CLASS_MAP->{$field_name};
|
||||
}
|
||||
|
||||
# For generic classes, we use a lowercase class name, so as
|
||||
# not to interfere with any real subclasses we might make some day.
|
||||
my $package = "Bugzilla::Field::Choice::$field_name";
|
||||
Bugzilla->request_cache->{"field_$package"} = $field_obj;
|
||||
|
||||
# This package only needs to be created once. We check if the DB_TABLE
|
||||
# glob for this package already exists, which tells us whether or not
|
||||
# we need to create the package (this works even under mod_perl, where
|
||||
# this package definition will persist across requests)).
|
||||
if (!defined *{"${package}::DB_TABLE"}) {
|
||||
eval <<EOC;
|
||||
package $package;
|
||||
use base qw(Bugzilla::Field::Choice);
|
||||
use constant DB_TABLE => '$field_name';
|
||||
EOC
|
||||
}
|
||||
|
||||
return $package;
|
||||
}
|
||||
|
||||
################
|
||||
# Constructors #
|
||||
################
|
||||
|
||||
# We just make new() enforce this, which should give developers
|
||||
# the understanding that you can't use Bugzilla::Field::Choice
|
||||
# without calling type().
|
||||
sub new {
|
||||
my $class = shift;
|
||||
if ($class eq 'Bugzilla::Field::Choice') {
|
||||
ThrowCodeError('field_choice_must_use_type');
|
||||
}
|
||||
$class->SUPER::new(@_);
|
||||
}
|
||||
|
||||
#########################
|
||||
# Database Manipulation #
|
||||
#########################
|
||||
|
||||
# Our subclasses can take more arguments than we normally accept.
|
||||
# So, we override create() to remove arguments that aren't valid
|
||||
# columns. (Normally Bugzilla::Object dies if you pass arguments
|
||||
# that aren't valid columns.)
|
||||
sub create {
|
||||
my $class = shift;
|
||||
my ($params) = @_;
|
||||
foreach my $key (keys %$params) {
|
||||
if (!grep {$_ eq $key} $class->DB_COLUMNS) {
|
||||
delete $params->{$key};
|
||||
}
|
||||
}
|
||||
return $class->SUPER::create(@_);
|
||||
}
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $fname = $self->field->name;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
my ($changes, $old_self) = $self->SUPER::update(@_);
|
||||
if (exists $changes->{value}) {
|
||||
my ($old, $new) = @{ $changes->{value} };
|
||||
if ($self->field->type == FIELD_TYPE_MULTI_SELECT) {
|
||||
$dbh->do("UPDATE bug_$fname SET value = ? WHERE value = ?",
|
||||
undef, $new, $old);
|
||||
}
|
||||
else {
|
||||
$dbh->do("UPDATE bugs SET $fname = ? WHERE $fname = ?",
|
||||
undef, $new, $old);
|
||||
}
|
||||
|
||||
if ($old_self->is_default) {
|
||||
my $param = $self->DEFAULT_MAP->{$self->field->name};
|
||||
SetParam($param, $self->name);
|
||||
write_params();
|
||||
}
|
||||
}
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
return wantarray ? ($changes, $old_self) : $changes;
|
||||
}
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
if ($self->is_default) {
|
||||
ThrowUserError('fieldvalue_is_default',
|
||||
{ field => $self->field, value => $self,
|
||||
param_name => $self->DEFAULT_MAP->{$self->field->name},
|
||||
});
|
||||
}
|
||||
if ($self->is_static) {
|
||||
ThrowUserError('fieldvalue_not_deletable',
|
||||
{ field => $self->field, value => $self });
|
||||
}
|
||||
if ($self->bug_count) {
|
||||
ThrowUserError("fieldvalue_still_has_bugs",
|
||||
{ field => $self->field, value => $self });
|
||||
}
|
||||
$self->_check_if_controller();
|
||||
$self->SUPER::remove_from_db();
|
||||
}
|
||||
|
||||
# Factored out to make life easier for subclasses.
|
||||
sub _check_if_controller {
|
||||
my $self = shift;
|
||||
my $vis_fields = $self->controls_visibility_of_fields;
|
||||
my $values = $self->controlled_values;
|
||||
if (@$vis_fields || scalar(keys %$values)) {
|
||||
ThrowUserError('fieldvalue_is_controller',
|
||||
{ value => $self, fields => [map($_->name, @$vis_fields)],
|
||||
vals => $values });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#############
|
||||
# Accessors #
|
||||
#############
|
||||
|
||||
sub sortkey { return $_[0]->{'sortkey'}; }
|
||||
|
||||
sub bug_count {
|
||||
my $self = shift;
|
||||
return $self->{bug_count} if defined $self->{bug_count};
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $fname = $self->field->name;
|
||||
my $count;
|
||||
if ($self->field->type == FIELD_TYPE_MULTI_SELECT) {
|
||||
$count = $dbh->selectrow_array("SELECT COUNT(*) FROM bug_$fname
|
||||
WHERE value = ?", undef, $self->name);
|
||||
}
|
||||
else {
|
||||
$count = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs
|
||||
WHERE $fname = ?",
|
||||
undef, $self->name);
|
||||
}
|
||||
$self->{bug_count} = $count;
|
||||
return $count;
|
||||
}
|
||||
|
||||
sub field {
|
||||
my $invocant = shift;
|
||||
my $class = ref $invocant || $invocant;
|
||||
my $cache = Bugzilla->request_cache;
|
||||
# This is just to make life easier for subclasses. Our auto-generated
|
||||
# subclasses from type() already have this set.
|
||||
$cache->{"field_$class"} ||=
|
||||
new Bugzilla::Field({ name => $class->DB_TABLE });
|
||||
return $cache->{"field_$class"};
|
||||
}
|
||||
|
||||
sub is_default {
|
||||
my $self = shift;
|
||||
my $name = $self->DEFAULT_MAP->{$self->field->name};
|
||||
# If it doesn't exist in DEFAULT_MAP, then there is no parameter
|
||||
# related to this field.
|
||||
return 0 unless $name;
|
||||
return ($self->name eq Bugzilla->params->{$name}) ? 1 : 0;
|
||||
}
|
||||
|
||||
sub is_static {
|
||||
my $self = shift;
|
||||
# If we need to special-case Resolution for *anything* else, it should
|
||||
# get its own subclass.
|
||||
if ($self->field->name eq 'resolution') {
|
||||
return grep($_ eq $self->name, ('', 'FIXED', 'MOVED', 'DUPLICATE'))
|
||||
? 1 : 0;
|
||||
}
|
||||
elsif ($self->field->custom) {
|
||||
return $self->name eq '---' ? 1 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub controls_visibility_of_fields {
|
||||
my $self = shift;
|
||||
$self->{controls_visibility_of_fields} ||= Bugzilla::Field->match(
|
||||
{ visibility_field_id => $self->field->id,
|
||||
visibility_value_id => $self->id });
|
||||
return $self->{controls_visibility_of_fields};
|
||||
}
|
||||
|
||||
sub visibility_value {
|
||||
my $self = shift;
|
||||
if ($self->{visibility_value_id}) {
|
||||
$self->{visibility_value} ||=
|
||||
Bugzilla::Field::Choice->type($self->field->value_field)->new(
|
||||
$self->{visibility_value_id});
|
||||
}
|
||||
return $self->{visibility_value};
|
||||
}
|
||||
|
||||
sub controlled_values {
|
||||
my $self = shift;
|
||||
return $self->{controlled_values} if defined $self->{controlled_values};
|
||||
my $fields = $self->field->controls_values_of;
|
||||
my %controlled_values;
|
||||
foreach my $field (@$fields) {
|
||||
$controlled_values{$field->name} =
|
||||
Bugzilla::Field::Choice->type($field)
|
||||
->match({ visibility_value_id => $self->id });
|
||||
}
|
||||
$self->{controlled_values} = \%controlled_values;
|
||||
return $self->{controlled_values};
|
||||
}
|
||||
|
||||
############
|
||||
# Mutators #
|
||||
############
|
||||
|
||||
sub set_name { $_[0]->set('value', $_[1]); }
|
||||
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
|
||||
sub set_visibility_value {
|
||||
my ($self, $value) = @_;
|
||||
$self->set('visibility_value_id', $value);
|
||||
delete $self->{visibility_value};
|
||||
}
|
||||
|
||||
##############
|
||||
# Validators #
|
||||
##############
|
||||
|
||||
sub _check_value {
|
||||
my ($invocant, $value) = @_;
|
||||
|
||||
my $field = $invocant->field;
|
||||
|
||||
$value = trim($value);
|
||||
|
||||
# Make sure people don't rename static values
|
||||
if (blessed($invocant) && $value ne $invocant->name
|
||||
&& $invocant->is_static)
|
||||
{
|
||||
ThrowUserError('fieldvalue_not_editable',
|
||||
{ field => $field, old_value => $invocant });
|
||||
}
|
||||
|
||||
ThrowUserError('fieldvalue_undefined') if !defined $value || $value eq "";
|
||||
ThrowUserError('fieldvalue_name_too_long', { value => $value })
|
||||
if length($value) > MAX_FIELD_VALUE_SIZE;
|
||||
|
||||
my $exists = $invocant->type($field)->new({ name => $value });
|
||||
if ($exists && (!blessed($invocant) || $invocant->id != $exists->id)) {
|
||||
ThrowUserError('fieldvalue_already_exists',
|
||||
{ field => $field, value => $exists });
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
sub _check_sortkey {
|
||||
my ($invocant, $value) = @_;
|
||||
$value = trim($value);
|
||||
return 0 if !$value;
|
||||
# Store for the error message in case detaint_natural clears it.
|
||||
my $orig_value = $value;
|
||||
detaint_natural($value)
|
||||
|| ThrowUserError('fieldvalue_sortkey_invalid',
|
||||
{ sortkey => $orig_value,
|
||||
field => $invocant->field });
|
||||
return $value;
|
||||
}
|
||||
|
||||
sub _check_visibility_value_id {
|
||||
my ($invocant, $value_id) = @_;
|
||||
$value_id = trim($value_id);
|
||||
my $field = $invocant->field->value_field;
|
||||
return undef if !$field || !$value_id;
|
||||
my $value_obj = Bugzilla::Field::Choice->type($field)
|
||||
->check({ id => $value_id });
|
||||
return $value_obj->id;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Field::Choice - A legal value for a <select>-type field.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $field = new Bugzilla::Field({name => 'bug_status'});
|
||||
|
||||
my $choice = new Bugzilla::Field::Choice->type($field)->new(1);
|
||||
|
||||
my $choices = Bugzilla::Field::Choice->type($field)->new_from_list([1,2,3]);
|
||||
my $choices = Bugzilla::Field::Choice->type($field)->get_all();
|
||||
my $choices = Bugzilla::Field::Choice->type($field->match({ sortkey => 10 });
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is an implementation of L<Bugzilla::Object>, but with a twist.
|
||||
You can't call any class methods (such as C<new>, C<create>, etc.)
|
||||
directly on C<Bugzilla::Field::Choice> itself. Instead, you have to
|
||||
call C<Bugzilla::Field::Choice-E<gt>type($field)> to get the class
|
||||
you're going to instantiate, and then you call the methods on that.
|
||||
|
||||
We do that because each field has its own database table for its values, so
|
||||
each value type needs its own class.
|
||||
|
||||
See the L</SYNOPSIS> for examples of how this works.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 Class Factory
|
||||
|
||||
In object-oriented design, a "class factory" is a method that picks
|
||||
and returns the right class for you, based on an argument that you pass.
|
||||
|
||||
=over
|
||||
|
||||
=item C<type>
|
||||
|
||||
Takes a single argument, which is either the name of a field from the
|
||||
C<fielddefs> table, or a L<Bugzilla::Field> object representing a field.
|
||||
|
||||
Returns an appropriate subclass of C<Bugzilla::Field::Choice> that you
|
||||
can now call class methods on (like C<new>, C<create>, C<match>, etc.)
|
||||
|
||||
B<NOTE>: YOU CANNOT CALL CLASS METHODS ON C<Bugzilla::Field::Choice>. You
|
||||
must call C<type> to get a class you can call methods on.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Accessors
|
||||
|
||||
These are in addition to the standard L<Bugzilla::Object> accessors.
|
||||
|
||||
=over
|
||||
|
||||
=item C<sortkey>
|
||||
|
||||
The key that determines the sort order of this item.
|
||||
|
||||
=item C<field>
|
||||
|
||||
The L<Bugzilla::Field> object that this field value belongs to.
|
||||
|
||||
=item C<controlled_values>
|
||||
|
||||
Tells you which values in B<other> fields appear (become visible) when this
|
||||
value is set in its field.
|
||||
|
||||
Returns a hashref of arrayrefs. The hash keys are the names of fields,
|
||||
and the values are arrays of C<Bugzilla::Field::Choice> objects,
|
||||
representing values that this value controls the visibility of, for
|
||||
that field.
|
||||
|
||||
=back
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,482 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Myk Melez <myk@mozilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::FlagType;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::FlagType - A module to deal with Bugzilla flag types.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
FlagType.pm provides an interface to flag types as stored in Bugzilla.
|
||||
See below for more information.
|
||||
|
||||
=head1 NOTES
|
||||
|
||||
=over
|
||||
|
||||
=item *
|
||||
|
||||
Use of private functions/variables outside this module may lead to
|
||||
unexpected results after an upgrade. Please avoid using private
|
||||
functions in other files/modules. Private functions are functions
|
||||
whose names start with _ or are specifically noted as being private.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Group;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
=begin private
|
||||
|
||||
=head1 PRIVATE VARIABLES/CONSTANTS
|
||||
|
||||
=over
|
||||
|
||||
=item C<DB_COLUMNS>
|
||||
|
||||
basic sets of columns and tables for getting flag types from the
|
||||
database.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
flagtypes.id
|
||||
flagtypes.name
|
||||
flagtypes.description
|
||||
flagtypes.cc_list
|
||||
flagtypes.target_type
|
||||
flagtypes.sortkey
|
||||
flagtypes.is_active
|
||||
flagtypes.is_requestable
|
||||
flagtypes.is_requesteeble
|
||||
flagtypes.is_multiplicable
|
||||
flagtypes.grant_group_id
|
||||
flagtypes.request_group_id
|
||||
);
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<DB_TABLE>
|
||||
|
||||
Which database(s) is the data coming from?
|
||||
|
||||
Note: when adding tables to DB_TABLE, make sure to include the separator
|
||||
(i.e. words like "LEFT OUTER JOIN") before the table name, since tables take
|
||||
multiple separators based on the join type, and therefore it is not possible
|
||||
to join them later using a single known separator.
|
||||
|
||||
=back
|
||||
|
||||
=end private
|
||||
|
||||
=cut
|
||||
|
||||
use constant DB_TABLE => 'flagtypes';
|
||||
use constant LIST_ORDER => 'flagtypes.sortkey, flagtypes.name';
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
=head2 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<id>
|
||||
|
||||
Returns the ID of the flagtype.
|
||||
|
||||
=item C<name>
|
||||
|
||||
Returns the name of the flagtype.
|
||||
|
||||
=item C<description>
|
||||
|
||||
Returns the description of the flagtype.
|
||||
|
||||
=item C<cc_list>
|
||||
|
||||
Returns the concatenated CC list for the flagtype, as a single string.
|
||||
|
||||
=item C<target_type>
|
||||
|
||||
Returns whether the flagtype applies to bugs or attachments.
|
||||
|
||||
=item C<is_active>
|
||||
|
||||
Returns whether the flagtype is active or disabled. Flags being
|
||||
in a disabled flagtype are not deleted. It only prevents you from
|
||||
adding new flags to it.
|
||||
|
||||
=item C<is_requestable>
|
||||
|
||||
Returns whether you can request for the given flagtype
|
||||
(i.e. whether the '?' flag is available or not).
|
||||
|
||||
=item C<is_requesteeble>
|
||||
|
||||
Returns whether you can ask someone specifically or not.
|
||||
|
||||
=item C<is_multiplicable>
|
||||
|
||||
Returns whether you can have more than one flag for the given
|
||||
flagtype in a given bug/attachment.
|
||||
|
||||
=item C<sortkey>
|
||||
|
||||
Returns the sortkey of the flagtype.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub id { return $_[0]->{'id'}; }
|
||||
sub name { return $_[0]->{'name'}; }
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
sub cc_list { return $_[0]->{'cc_list'}; }
|
||||
sub target_type { return $_[0]->{'target_type'} eq 'b' ? 'bug' : 'attachment'; }
|
||||
sub is_active { return $_[0]->{'is_active'}; }
|
||||
sub is_requestable { return $_[0]->{'is_requestable'}; }
|
||||
sub is_requesteeble { return $_[0]->{'is_requesteeble'}; }
|
||||
sub is_multiplicable { return $_[0]->{'is_multiplicable'}; }
|
||||
sub sortkey { return $_[0]->{'sortkey'}; }
|
||||
sub request_group_id { return $_[0]->{'request_group_id'}; }
|
||||
sub grant_group_id { return $_[0]->{'grant_group_id'}; }
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<grant_list>
|
||||
|
||||
Returns a reference to an array of users who have permission to grant this flag type.
|
||||
The arrays are populated with hashrefs containing the login, identity and visibility of users.
|
||||
|
||||
=item C<grant_group>
|
||||
|
||||
Returns the group (as a Bugzilla::Group object) in which a user
|
||||
must be in order to grant or deny a request.
|
||||
|
||||
=item C<request_group>
|
||||
|
||||
Returns the group (as a Bugzilla::Group object) in which a user
|
||||
must be in order to request or clear a flag.
|
||||
|
||||
=item C<flag_count>
|
||||
|
||||
Returns the number of flags belonging to the flagtype.
|
||||
|
||||
=item C<inclusions>
|
||||
|
||||
Return a hash of product/component IDs and names
|
||||
explicitly associated with the flagtype.
|
||||
|
||||
=item C<exclusions>
|
||||
|
||||
Return a hash of product/component IDs and names
|
||||
explicitly excluded from the flagtype.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub grant_list {
|
||||
my $self = shift;
|
||||
my @custusers;
|
||||
my @allusers = @{Bugzilla->user->get_userlist};
|
||||
foreach my $user (@allusers) {
|
||||
my $user_obj = new Bugzilla::User({name => $user->{login}});
|
||||
push(@custusers, $user) if $user_obj->can_set_flag($self);
|
||||
}
|
||||
return \@custusers;
|
||||
}
|
||||
|
||||
sub grant_group {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'grant_group'} && $self->{'grant_group_id'}) {
|
||||
$self->{'grant_group'} = new Bugzilla::Group($self->{'grant_group_id'});
|
||||
}
|
||||
return $self->{'grant_group'};
|
||||
}
|
||||
|
||||
sub request_group {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'request_group'} && $self->{'request_group_id'}) {
|
||||
$self->{'request_group'} = new Bugzilla::Group($self->{'request_group_id'});
|
||||
}
|
||||
return $self->{'request_group'};
|
||||
}
|
||||
|
||||
sub flag_count {
|
||||
my $self = shift;
|
||||
|
||||
if (!defined $self->{'flag_count'}) {
|
||||
$self->{'flag_count'} =
|
||||
Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM flags
|
||||
WHERE type_id = ?', undef, $self->{'id'});
|
||||
}
|
||||
return $self->{'flag_count'};
|
||||
}
|
||||
|
||||
sub inclusions {
|
||||
my $self = shift;
|
||||
|
||||
$self->{'inclusions'} ||= get_clusions($self->id, 'in');
|
||||
return $self->{'inclusions'};
|
||||
}
|
||||
|
||||
sub exclusions {
|
||||
my $self = shift;
|
||||
|
||||
$self->{'exclusions'} ||= get_clusions($self->id, 'ex');
|
||||
return $self->{'exclusions'};
|
||||
}
|
||||
|
||||
######################################################################
|
||||
# Public Functions
|
||||
######################################################################
|
||||
|
||||
=pod
|
||||
|
||||
=head1 PUBLIC FUNCTIONS/METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_clusions($id, $type)>
|
||||
|
||||
Return a hash of product/component IDs and names
|
||||
associated with the flagtype:
|
||||
$clusions{'product_name:component_name'} = "product_ID:component_ID"
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub get_clusions {
|
||||
my ($id, $type) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $list =
|
||||
$dbh->selectall_arrayref("SELECT products.id, products.name, " .
|
||||
" components.id, components.name " .
|
||||
"FROM flagtypes, flag${type}clusions " .
|
||||
"LEFT OUTER JOIN products " .
|
||||
" ON flag${type}clusions.product_id = products.id " .
|
||||
"LEFT OUTER JOIN components " .
|
||||
" ON flag${type}clusions.component_id = components.id " .
|
||||
"WHERE flagtypes.id = ? " .
|
||||
" AND flag${type}clusions.type_id = flagtypes.id",
|
||||
undef, $id);
|
||||
my %clusions;
|
||||
foreach my $data (@$list) {
|
||||
my ($product_id, $product_name, $component_id, $component_name) = @$data;
|
||||
$product_id ||= 0;
|
||||
$product_name ||= "__Any__";
|
||||
$component_id ||= 0;
|
||||
$component_name ||= "__Any__";
|
||||
$clusions{"$product_name:$component_name"} = "$product_id:$component_id";
|
||||
}
|
||||
return \%clusions;
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<match($criteria)>
|
||||
|
||||
Queries the database for flag types matching the given criteria
|
||||
and returns a list of matching flagtype objects.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub match {
|
||||
my ($criteria) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Depending on the criteria, we may have to append additional tables.
|
||||
my $tables = [DB_TABLE];
|
||||
my @criteria = sqlify_criteria($criteria, $tables);
|
||||
$tables = join(' ', @$tables);
|
||||
$criteria = join(' AND ', @criteria);
|
||||
|
||||
my $flagtype_ids = $dbh->selectcol_arrayref("SELECT id FROM $tables WHERE $criteria");
|
||||
|
||||
return Bugzilla::FlagType->new_from_list($flagtype_ids);
|
||||
}
|
||||
|
||||
=pod
|
||||
|
||||
=over
|
||||
|
||||
=item C<count($criteria)>
|
||||
|
||||
Returns the total number of flag types matching the given criteria.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub count {
|
||||
my ($criteria) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Depending on the criteria, we may have to append additional tables.
|
||||
my $tables = [DB_TABLE];
|
||||
my @criteria = sqlify_criteria($criteria, $tables);
|
||||
$tables = join(' ', @$tables);
|
||||
$criteria = join(' AND ', @criteria);
|
||||
|
||||
my $count = $dbh->selectrow_array("SELECT COUNT(flagtypes.id)
|
||||
FROM $tables WHERE $criteria");
|
||||
return $count;
|
||||
}
|
||||
|
||||
######################################################################
|
||||
# Private Functions
|
||||
######################################################################
|
||||
|
||||
=begin private
|
||||
|
||||
=head1 PRIVATE FUNCTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item C<sqlify_criteria($criteria, $tables)>
|
||||
|
||||
Converts a hash of criteria into a list of SQL criteria.
|
||||
$criteria is a reference to the criteria (field => value),
|
||||
$tables is a reference to an array of tables being accessed
|
||||
by the query.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
|
||||
sub sqlify_criteria {
|
||||
my ($criteria, $tables) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# the generated list of SQL criteria; "1=1" is a clever way of making sure
|
||||
# there's something in the list so calling code doesn't have to check list
|
||||
# size before building a WHERE clause out of it
|
||||
my @criteria = ("1=1");
|
||||
|
||||
if ($criteria->{name}) {
|
||||
my $name = $dbh->quote($criteria->{name});
|
||||
trick_taint($name); # Detaint data as we have quoted it.
|
||||
push(@criteria, "flagtypes.name = $name");
|
||||
}
|
||||
if ($criteria->{target_type}) {
|
||||
# The target type is stored in the database as a one-character string
|
||||
# ("a" for attachment and "b" for bug), but this function takes complete
|
||||
# names ("attachment" and "bug") for clarity, so we must convert them.
|
||||
my $target_type = $criteria->{target_type} eq 'bug'? 'b' : 'a';
|
||||
push(@criteria, "flagtypes.target_type = '$target_type'");
|
||||
}
|
||||
if (exists($criteria->{is_active})) {
|
||||
my $is_active = $criteria->{is_active} ? "1" : "0";
|
||||
push(@criteria, "flagtypes.is_active = $is_active");
|
||||
}
|
||||
if ($criteria->{product_id} && $criteria->{'component_id'}) {
|
||||
my $product_id = $criteria->{product_id};
|
||||
my $component_id = $criteria->{component_id};
|
||||
|
||||
# Add inclusions to the query, which simply involves joining the table
|
||||
# by flag type ID and target product/component.
|
||||
push(@$tables, "INNER JOIN flaginclusions AS i ON flagtypes.id = i.type_id");
|
||||
push(@criteria, "(i.product_id = $product_id OR i.product_id IS NULL)");
|
||||
push(@criteria, "(i.component_id = $component_id OR i.component_id IS NULL)");
|
||||
|
||||
# Add exclusions to the query, which is more complicated. First of all,
|
||||
# we do a LEFT JOIN so we don't miss flag types with no exclusions.
|
||||
# Then, as with inclusions, we join on flag type ID and target product/
|
||||
# component. However, since we want flag types that *aren't* on the
|
||||
# exclusions list, we add a WHERE criteria to use only records with
|
||||
# NULL exclusion type, i.e. without any exclusions.
|
||||
my $join_clause = "flagtypes.id = e.type_id " .
|
||||
"AND (e.product_id = $product_id OR e.product_id IS NULL) " .
|
||||
"AND (e.component_id = $component_id OR e.component_id IS NULL)";
|
||||
push(@$tables, "LEFT JOIN flagexclusions AS e ON ($join_clause)");
|
||||
push(@criteria, "e.type_id IS NULL");
|
||||
}
|
||||
if ($criteria->{group}) {
|
||||
my $gid = $criteria->{group};
|
||||
detaint_natural($gid);
|
||||
push(@criteria, "(flagtypes.grant_group_id = $gid " .
|
||||
" OR flagtypes.request_group_id = $gid)");
|
||||
}
|
||||
|
||||
return @criteria;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=end private
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
=over
|
||||
|
||||
=item B<Bugzilla::Flags>
|
||||
|
||||
=back
|
||||
|
||||
=head1 CONTRIBUTORS
|
||||
|
||||
=over
|
||||
|
||||
=item Myk Melez <myk@mozilla.org>
|
||||
|
||||
=item Kevin Benton <kevin.benton@amd.com>
|
||||
|
||||
=item Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,432 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Joel Peshkin <bugreport@peshkin.net>
|
||||
# Erik Stambaugh <erik@dasbistro.com>
|
||||
# Tiago R. Mello <timello@async.com.br>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Group;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Config qw(:admin);
|
||||
|
||||
###############################
|
||||
##### Module Initialization ###
|
||||
###############################
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
groups.id
|
||||
groups.name
|
||||
groups.description
|
||||
groups.isbuggroup
|
||||
groups.userregexp
|
||||
groups.isactive
|
||||
groups.icon_url
|
||||
);
|
||||
|
||||
use constant DB_TABLE => 'groups';
|
||||
|
||||
use constant LIST_ORDER => 'isbuggroup, name';
|
||||
|
||||
use constant VALIDATORS => {
|
||||
name => \&_check_name,
|
||||
description => \&_check_description,
|
||||
userregexp => \&_check_user_regexp,
|
||||
isactive => \&_check_is_active,
|
||||
isbuggroup => \&_check_is_bug_group,
|
||||
icon_url => \&_check_icon_url,
|
||||
};
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(name description isbuggroup);
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
name
|
||||
description
|
||||
userregexp
|
||||
isactive
|
||||
icon_url
|
||||
);
|
||||
|
||||
# Parameters that are lists of groups.
|
||||
use constant GROUP_PARAMS => qw(chartgroup insidergroup timetrackinggroup
|
||||
querysharegroup);
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
sub is_bug_group { return $_[0]->{'isbuggroup'}; }
|
||||
sub user_regexp { return $_[0]->{'userregexp'}; }
|
||||
sub is_active { return $_[0]->{'isactive'}; }
|
||||
sub icon_url { return $_[0]->{'icon_url'}; }
|
||||
|
||||
sub members_direct {
|
||||
my ($self) = @_;
|
||||
return $self->{members_direct} if defined $self->{members_direct};
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $user_ids = $dbh->selectcol_arrayref(
|
||||
"SELECT user_group_map.user_id
|
||||
FROM user_group_map
|
||||
WHERE user_group_map.group_id = ?
|
||||
AND grant_type = " . GRANT_DIRECT . "
|
||||
AND isbless = 0", undef, $self->id);
|
||||
require Bugzilla::User;
|
||||
$self->{members_direct} = Bugzilla::User->new_from_list($user_ids);
|
||||
return $self->{members_direct};
|
||||
}
|
||||
|
||||
sub grant_direct {
|
||||
my ($self, $type) = @_;
|
||||
$self->{grant_direct} ||= {};
|
||||
return $self->{grant_direct}->{$type}
|
||||
if defined $self->{grant_direct}->{$type};
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $ids = $dbh->selectcol_arrayref(
|
||||
"SELECT member_id FROM group_group_map
|
||||
WHERE grantor_id = ? AND grant_type = $type",
|
||||
undef, $self->id) || [];
|
||||
|
||||
$self->{grant_direct}->{$type} = $self->new_from_list($ids);
|
||||
return $self->{grant_direct}->{$type};
|
||||
}
|
||||
|
||||
sub granted_by_direct {
|
||||
my ($self, $type) = @_;
|
||||
$self->{granted_by_direct} ||= {};
|
||||
return $self->{granted_by_direct}->{$type}
|
||||
if defined $self->{granted_by_direct}->{$type};
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $ids = $dbh->selectcol_arrayref(
|
||||
"SELECT grantor_id FROM group_group_map
|
||||
WHERE member_id = ? AND grant_type = $type",
|
||||
undef, $self->id) || [];
|
||||
|
||||
$self->{granted_by_direct}->{$type} = $self->new_from_list($ids);
|
||||
return $self->{granted_by_direct}->{$type};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
sub set_description { $_[0]->set('description', $_[1]); }
|
||||
sub set_is_active { $_[0]->set('isactive', $_[1]); }
|
||||
sub set_name { $_[0]->set('name', $_[1]); }
|
||||
sub set_user_regexp { $_[0]->set('userregexp', $_[1]); }
|
||||
sub set_icon_url { $_[0]->set('icon_url', $_[1]); }
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
my $changes = $self->SUPER::update(@_);
|
||||
|
||||
if (exists $changes->{name}) {
|
||||
my ($old_name, $new_name) = @{$changes->{name}};
|
||||
my $update_params;
|
||||
foreach my $group (GROUP_PARAMS) {
|
||||
if ($old_name eq Bugzilla->params->{$group}) {
|
||||
SetParam($group, $new_name);
|
||||
$update_params = 1;
|
||||
}
|
||||
}
|
||||
write_params() if $update_params;
|
||||
}
|
||||
|
||||
# If we've changed this group to be active, fix any Mandatory groups.
|
||||
$self->_enforce_mandatory if (exists $changes->{isactive}
|
||||
&& $changes->{isactive}->[1]);
|
||||
|
||||
$self->_rederive_regexp() if exists $changes->{userregexp};
|
||||
return $changes;
|
||||
}
|
||||
|
||||
# Add missing entries in bug_group_map for bugs created while
|
||||
# a mandatory group was disabled and which is now enabled again.
|
||||
sub _enforce_mandatory {
|
||||
my ($self) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $gid = $self->id;
|
||||
|
||||
my $bug_ids =
|
||||
$dbh->selectcol_arrayref('SELECT bugs.bug_id
|
||||
FROM bugs
|
||||
INNER JOIN group_control_map
|
||||
ON group_control_map.product_id = bugs.product_id
|
||||
LEFT JOIN bug_group_map
|
||||
ON bug_group_map.bug_id = bugs.bug_id
|
||||
AND bug_group_map.group_id = group_control_map.group_id
|
||||
WHERE group_control_map.group_id = ?
|
||||
AND group_control_map.membercontrol = ?
|
||||
AND bug_group_map.group_id IS NULL',
|
||||
undef, ($gid, CONTROLMAPMANDATORY));
|
||||
|
||||
my $sth = $dbh->prepare('INSERT INTO bug_group_map (bug_id, group_id) VALUES (?, ?)');
|
||||
foreach my $bug_id (@$bug_ids) {
|
||||
$sth->execute($bug_id, $gid);
|
||||
}
|
||||
}
|
||||
|
||||
sub is_active_bug_group {
|
||||
my $self = shift;
|
||||
return $self->is_active && $self->is_bug_group;
|
||||
}
|
||||
|
||||
sub _rederive_regexp {
|
||||
my ($self) = @_;
|
||||
|
||||
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($self->id, GRANT_REGEXP);
|
||||
my $regexp = $self->user_regexp;
|
||||
while (my ($uid, $login, $present) = $sth->fetchrow_array) {
|
||||
if ($regexp ne '' and $login =~ /$regexp/i) {
|
||||
$sthadd->execute($uid, $self->id, GRANT_REGEXP) unless $present;
|
||||
} else {
|
||||
$sthdel->execute($uid, $self->id, GRANT_REGEXP) if $present;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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};
|
||||
}
|
||||
|
||||
sub flatten_group_membership {
|
||||
my ($self, @groups) = @_;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth;
|
||||
my @groupidstocheck = @groups;
|
||||
my %groupidschecked = ();
|
||||
$sth = $dbh->prepare("SELECT member_id FROM group_group_map
|
||||
WHERE grantor_id = ?
|
||||
AND grant_type = " . GROUP_MEMBERSHIP);
|
||||
while (my $node = shift @groupidstocheck) {
|
||||
$sth->execute($node);
|
||||
my $member;
|
||||
while (($member) = $sth->fetchrow_array) {
|
||||
if (!$groupidschecked{$member}) {
|
||||
$groupidschecked{$member} = 1;
|
||||
push @groupidstocheck, $member;
|
||||
push @groups, $member unless grep $_ == $member, @groups;
|
||||
}
|
||||
}
|
||||
}
|
||||
return \@groups;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
################################
|
||||
##### 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;
|
||||
}
|
||||
|
||||
###############################
|
||||
### Validators ###
|
||||
###############################
|
||||
|
||||
sub _check_name {
|
||||
my ($invocant, $name) = @_;
|
||||
$name = trim($name);
|
||||
$name || ThrowUserError("empty_group_name");
|
||||
# If we're creating a Group or changing the name...
|
||||
if (!ref($invocant) || $invocant->name ne $name) {
|
||||
my $exists = new Bugzilla::Group({name => $name });
|
||||
ThrowUserError("group_exists", { name => $name }) if $exists;
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_description {
|
||||
my ($invocant, $desc) = @_;
|
||||
$desc = trim($desc);
|
||||
$desc || ThrowUserError("empty_group_description");
|
||||
return $desc;
|
||||
}
|
||||
|
||||
sub _check_user_regexp {
|
||||
my ($invocant, $regex) = @_;
|
||||
$regex = trim($regex) || '';
|
||||
ThrowUserError("invalid_regexp") unless (eval {qr/$regex/});
|
||||
return $regex;
|
||||
}
|
||||
|
||||
sub _check_is_active { return $_[1] ? 1 : 0; }
|
||||
sub _check_is_bug_group {
|
||||
return $_[1] ? 1 : 0;
|
||||
}
|
||||
|
||||
sub _check_icon_url { return $_[1] ? clean_text($_[1]) : undef; }
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Group - Bugzilla group class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Group;
|
||||
|
||||
my $group = new Bugzilla::Group(1);
|
||||
my $group = new Bugzilla::Group({name => 'AcmeGroup'});
|
||||
|
||||
my $id = $group->id;
|
||||
my $name = $group->name;
|
||||
my $description = $group->description;
|
||||
my $user_reg_exp = $group->user_reg_exp;
|
||||
my $is_active = $group->is_active;
|
||||
my $icon_url = $group->icon_url;
|
||||
my $is_active_bug_group = $group->is_active_bug_group;
|
||||
|
||||
my $group_id = Bugzilla::Group::ValidateGroupName('admin', @users);
|
||||
my @groups = Bugzilla::Group->get_all;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Group.pm represents a Bugzilla Group object. It is an implementation
|
||||
of L<Bugzilla::Object>, and thus has all the methods that L<Bugzilla::Object>
|
||||
provides, in addition to any methods documented below.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<create>
|
||||
|
||||
Note that in addition to what L<Bugzilla::Object/create($params)>
|
||||
normally does, this function also makes the new group be inherited
|
||||
by the C<admin> group. That is, the C<admin> group will automatically
|
||||
be a member of this group.
|
||||
|
||||
=item C<ValidateGroupName($name, @users)>
|
||||
|
||||
Description: ValidateGroupName checks to see if ANY of the users
|
||||
in the provided list of user objects can see the
|
||||
named group.
|
||||
|
||||
Params: $name - String with the group name.
|
||||
@users - An array with Bugzilla::User objects.
|
||||
|
||||
Returns: It returns the group id if successful
|
||||
and undef otherwise.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<members_non_inherited>
|
||||
|
||||
Returns an arrayref of L<Bugzilla::User> objects representing people who are
|
||||
"directly" in this group, meaning that they're in it because they match
|
||||
the group regular expression, or they have been actually added to the
|
||||
group manually.
|
||||
|
||||
=item C<flatten_group_membership>
|
||||
|
||||
Accepts a list of groups and returns a list of all the groups whose members
|
||||
inherit membership in any group on the list. So, we can determine if a user
|
||||
is in any of the groups input to flatten_group_membership by querying the
|
||||
user_group_map for any user with DIRECT or REGEXP membership IN() the list
|
||||
of groups returned.
|
||||
|
||||
=back
|
||||
@@ -1,543 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Zach Lipton <zach@zachlipton.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Hook;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use strict;
|
||||
|
||||
sub process {
|
||||
my ($name, $args) = @_;
|
||||
|
||||
# get a list of all extensions
|
||||
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
|
||||
|
||||
# check each extension to see if it uses the hook
|
||||
# if so, invoke the extension source file:
|
||||
foreach my $extension (@extensions) {
|
||||
# all of these variables come directly from code or directory names.
|
||||
# If there's malicious data here, we have much bigger issues to
|
||||
# worry about, so we can safely detaint them:
|
||||
trick_taint($extension);
|
||||
# Skip CVS directories and any hidden files/dirs.
|
||||
next if $extension =~ m{/CVS$} || $extension =~ m{/\.[^/]+$};
|
||||
next if -e "$extension/disabled";
|
||||
if (-e $extension.'/code/'.$name.'.pl') {
|
||||
Bugzilla->hook_args($args);
|
||||
# Allow extensions to load their own libraries.
|
||||
local @INC = ("$extension/lib", @INC);
|
||||
do($extension.'/code/'.$name.'.pl');
|
||||
ThrowCodeError('extension_invalid',
|
||||
{ errstr => $@, name => $name, extension => $extension }) if $@;
|
||||
# Flush stored data.
|
||||
Bugzilla->hook_args({});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub enabled_plugins {
|
||||
my $extdir = bz_locations()->{'extensionsdir'};
|
||||
my @extensions = glob("$extdir/*");
|
||||
my %enabled;
|
||||
foreach my $extension (@extensions) {
|
||||
trick_taint($extension);
|
||||
my $extname = $extension;
|
||||
$extname =~ s{^\Q$extdir\E/}{};
|
||||
next if $extname eq 'CVS' || $extname =~ /^\./;
|
||||
next if -e "$extension/disabled";
|
||||
# Allow extensions to load their own libraries.
|
||||
local @INC = ("$extension/lib", @INC);
|
||||
$enabled{$extname} = do("$extension/info.pl");
|
||||
ThrowCodeError('extension_invalid',
|
||||
{ errstr => $@, name => 'version',
|
||||
extension => $extension }) if $@;
|
||||
|
||||
}
|
||||
|
||||
return \%enabled;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Hook - Extendable extension hooks for Bugzilla code
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Hook;
|
||||
|
||||
Bugzilla::Hook::process("hookname", { arg => $value, arg2 => $value2 });
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla allows extension modules to drop in and add routines at
|
||||
arbitrary points in Bugzilla code. These points are referred to as
|
||||
hooks. When a piece of standard Bugzilla code wants to allow an extension
|
||||
to perform additional functions, it uses Bugzilla::Hook's L</process>
|
||||
subroutine to invoke any extension code if installed.
|
||||
|
||||
There is a sample extension in F<extensions/example/> that demonstrates
|
||||
most of the things described in this document, as well as many of the
|
||||
hooks available.
|
||||
|
||||
=head2 How Hooks Work
|
||||
|
||||
When a hook named C<HOOK_NAME> is run, Bugzilla will attempt to invoke any
|
||||
source files named F<extensions/*/code/HOOK_NAME.pl>.
|
||||
|
||||
So, for example, if your extension is called "testopia", and you
|
||||
want to have code run during the L</install-update_db> hook, you
|
||||
would have a file called F<extensions/testopia/code/install-update_db.pl>
|
||||
that contained perl code to run during that hook.
|
||||
|
||||
=head2 Arguments Passed to Hooks
|
||||
|
||||
Some L<hooks|/HOOKS> have params that are passed to them.
|
||||
|
||||
These params are accessible through L<Bugzilla/hook_args>.
|
||||
That returns a hashref. Very frequently, if you want your
|
||||
hook to do anything, you have to modify these variables.
|
||||
|
||||
=head2 Versioning Extensions
|
||||
|
||||
Every extension must have a file in its root called F<info.pl>.
|
||||
This file must return a hash when called with C<do>.
|
||||
The hash must contain a 'version' key with the current version of the
|
||||
extension. Extension authors can also add any extra infomration to this hash if
|
||||
required, by adding a new key beginning with x_ which will not be used the
|
||||
core Bugzilla code.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<process>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Invoke any code hooks with a matching name from any installed extensions.
|
||||
|
||||
See C<customization.xml> in the Bugzilla Guide for more information on
|
||||
Bugzilla's extension mechanism.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$name> - The name of the hook to invoke.
|
||||
|
||||
=item C<$args> - A hashref. The named args to pass to the hook.
|
||||
They will be accessible to the hook via L<Bugzilla/hook_args>.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head1 HOOKS
|
||||
|
||||
This describes what hooks exist in Bugzilla currently. They are mostly
|
||||
in alphabetical order, but some related hooks are near each other instead
|
||||
of being alphabetical.
|
||||
|
||||
=head2 auth-login_methods
|
||||
|
||||
This allows you to add new login types to Bugzilla.
|
||||
(See L<Bugzilla::Auth::Login>.)
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<modules>
|
||||
|
||||
This is a hash--a mapping from login-type "names" to the actual module on
|
||||
disk. The keys will be all the values that were passed to
|
||||
L<Bugzilla::Auth/login> for the C<Login> parameter. The values are the
|
||||
actual path to the module on disk. (For example, if the key is C<DB>, the
|
||||
value is F<Bugzilla/Auth/Login/DB.pm>.)
|
||||
|
||||
For your extension, the path will start with
|
||||
F<extensions/yourextension/lib/>. (See the code in the example extension.)
|
||||
|
||||
If your login type is in the hash as a key, you should set that key to the
|
||||
right path to your module. That module's C<new> method will be called,
|
||||
probably with empty parameters. If your login type is I<not> in the hash,
|
||||
you should not set it.
|
||||
|
||||
You will be prevented from adding new keys to the hash, so make sure your
|
||||
key is in there before you modify it. (In other words, you can't add in
|
||||
login methods that weren't passed to L<Bugzilla::Auth/login>.)
|
||||
|
||||
=back
|
||||
|
||||
=head2 auth-verify_methods
|
||||
|
||||
This works just like L</auth-login_methods> except it's for
|
||||
login verification methods (See L<Bugzilla::Auth::Verify>.) It also
|
||||
takes a C<modules> parameter, just like L</auth-login_methods>.
|
||||
|
||||
=head2 bug-columns
|
||||
|
||||
This allows you to add new fields that will show up in every L<Bugzilla::Bug>
|
||||
object. Note that you will also need to use the L</bug-fields> hook in
|
||||
conjunction with this hook to make this work.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<columns> - An arrayref containing an array of column names. Push
|
||||
your column name(s) onto the array.
|
||||
|
||||
=back
|
||||
|
||||
=head2 bug-end_of_create
|
||||
|
||||
This happens at the end of L<Bugzilla::Bug/create>, after all other changes are
|
||||
made to the database. This occurs inside a database transaction.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<bug> - The changed bug object, with all fields set to their updated
|
||||
values.
|
||||
|
||||
=item C<timestamp> - The timestamp used for all updates in this transaction.
|
||||
|
||||
=back
|
||||
|
||||
=head2 bug-end_of_update
|
||||
|
||||
This happens at the end of L<Bugzilla::Bug/update>, after all other changes are
|
||||
made to the database. This generally occurs inside a database transaction.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<bug> - The changed bug object, with all fields set to their updated
|
||||
values.
|
||||
|
||||
=item C<timestamp> - The timestamp used for all updates in this transaction.
|
||||
|
||||
=item C<changes> - The hash of changed fields.
|
||||
C<$changes-E<gt>{field} = [old, new]>
|
||||
|
||||
=back
|
||||
|
||||
=head2 bug-fields
|
||||
|
||||
Allows the addition of database fields from the bugs table to the standard
|
||||
list of allowable fields in a L<Bugzilla::Bug> object, so that
|
||||
you can call the field as a method.
|
||||
|
||||
Note: You should add here the names of any fields you added in L</bug-columns>.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<columns> - A arrayref containing an array of column names. Push
|
||||
your column name(s) onto the array.
|
||||
|
||||
=back
|
||||
|
||||
=head2 buglist-columns
|
||||
|
||||
This happens in buglist.cgi after the standard columns have been defined and
|
||||
right before the display column determination. It gives you the opportunity
|
||||
to add additional display columns.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<columns> - A hashref, where the keys are unique string identifiers
|
||||
for the column being defined and the values are hashrefs with the
|
||||
following fields:
|
||||
|
||||
=over
|
||||
|
||||
=item C<name> - The name of the column in the database.
|
||||
|
||||
=item C<title> - The title of the column as displayed to users.
|
||||
|
||||
=back
|
||||
|
||||
The definition is structured as:
|
||||
|
||||
$columns->{$id} = { name => $name, title => $title };
|
||||
|
||||
=back
|
||||
|
||||
=head2 colchange-columns
|
||||
|
||||
This happens in F<colchange.cgi> right after the list of possible display
|
||||
columns have been defined and gives you the opportunity to add additional
|
||||
display columns to the list of selectable columns.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<columns> - An arrayref containing an array of column IDs. Any IDs
|
||||
added by this hook must have been defined in the the buglist-columns hook.
|
||||
See L</buglist-columns>.
|
||||
|
||||
=back
|
||||
|
||||
=head2 config-add_panels
|
||||
|
||||
If you want to add new panels to the Parameters administrative interface,
|
||||
this is where you do it.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<panel_modules>
|
||||
|
||||
A hashref, where the keys are the "name" of the module and the value
|
||||
is the Perl module containing that config module. For example, if
|
||||
the name is C<Auth>, the value would be C<Bugzilla::Config::Auth>.
|
||||
|
||||
For your extension, the Perl module name must start with
|
||||
C<extensions::yourextension::lib>. (See the code in the example
|
||||
extension.)
|
||||
|
||||
=back
|
||||
|
||||
=head2 config-modify_panels
|
||||
|
||||
This is how you modify already-existing panels in the Parameters
|
||||
administrative interface. For example, if you wanted to add a new
|
||||
Auth method (modifying Bugzilla::Config::Auth) this is how you'd
|
||||
do it.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<panels>
|
||||
|
||||
A hashref, where the keys are lower-case panel "names" (like C<auth>,
|
||||
C<admin>, etc.) and the values are hashrefs. The hashref contains a
|
||||
single key, C<params>. C<params> is an arrayref--the return value from
|
||||
C<get_param_list> for that module. You can modify C<params> and
|
||||
your changes will be reflected in the interface.
|
||||
|
||||
Adding new keys to C<panels> will have no effect. You should use
|
||||
L</config-add_panels> if you want to add new panels.
|
||||
|
||||
=back
|
||||
|
||||
=head2 enter_bug-entrydefaultvars
|
||||
|
||||
This happens right before the template is loaded on enter_bug.cgi.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<vars> - A hashref. The variables that will be passed into the template.
|
||||
|
||||
=back
|
||||
|
||||
=head2 flag-end_of_update
|
||||
|
||||
This happens at the end of L<Bugzilla::Flag/process>, after all other changes
|
||||
are made to the database and after emails are sent. It gives you a before/after
|
||||
snapshot of flags so you can react to specific flag changes.
|
||||
This generally occurs inside a database transaction.
|
||||
|
||||
Note that the interface to this hook is B<UNSTABLE> and it may change in the
|
||||
future.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<bug> - The changed bug object.
|
||||
|
||||
=item C<timestamp> - The timestamp used for all updates in this transaction.
|
||||
|
||||
=item C<old_flags> - The snapshot of flag summaries from before the change.
|
||||
|
||||
=item C<new_flags> - The snapshot of flag summaries after the change. Call
|
||||
C<my ($removed, $added) = diff_arrays(old_flags, new_flags)> to get the list of
|
||||
changed flags, and search for a specific condition like C<added eq 'review-'>.
|
||||
|
||||
=back
|
||||
|
||||
=head2 install-before_final_checks
|
||||
|
||||
Allows execution of custom code before the final checks are done in
|
||||
checksetup.pl.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<silent>
|
||||
|
||||
A flag that indicates whether or not checksetup is running in silent mode.
|
||||
|
||||
=back
|
||||
|
||||
=head2 install-requirements
|
||||
|
||||
Because of the way Bugzilla installation works, there can't be a normal
|
||||
hook during the time that F<checksetup.pl> checks what modules are
|
||||
installed. (C<Bugzilla::Hook> needs to have those modules installed--it's
|
||||
a chicken-and-egg problem.)
|
||||
|
||||
So instead of the way hooks normally work, this hook just looks for two
|
||||
subroutines (or constants, since all constants are just subroutines) in
|
||||
your file, called C<OPTIONAL_MODULES> and C<REQUIRED_MODULES>,
|
||||
which should return arrayrefs in the same format as C<OPTIONAL_MODULES> and
|
||||
C<REQUIRED_MODULES> in L<Bugzilla::Install::Requirements>.
|
||||
|
||||
These subroutines will be passed an arrayref that contains the current
|
||||
Bugzilla requirements of the same type, in case you want to modify
|
||||
Bugzilla's requirements somehow. (Probably the most common would be to
|
||||
alter a version number or the "feature" element of C<OPTIONAL_MODULES>.)
|
||||
|
||||
F<checksetup.pl> will add these requirements to its own.
|
||||
|
||||
Please remember--if you put something in C<REQUIRED_MODULES>, then
|
||||
F<checksetup.pl> B<cannot complete> unless the user has that module
|
||||
installed! So use C<OPTIONAL_MODULES> whenever you can.
|
||||
|
||||
=head2 install-update_db
|
||||
|
||||
This happens at the very end of all the tables being updated
|
||||
during an installation or upgrade. If you need to modify your custom
|
||||
schema, do it here. No params are passed.
|
||||
|
||||
=head2 db_schema-abstract_schema
|
||||
|
||||
This allows you to add tables to Bugzilla. Note that we recommend that you
|
||||
prefix the names of your tables with some word, so that they don't conflict
|
||||
with any future Bugzilla tables.
|
||||
|
||||
If you wish to add new I<columns> to existing Bugzilla tables, do that
|
||||
in L</install-update_db>.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<schema> - A hashref, in the format of
|
||||
L<Bugzilla::DB::Schema/ABSTRACT_SCHEMA>. Add new hash keys to make new table
|
||||
definitions. F<checksetup.pl> will automatically add these tables to the
|
||||
database when run.
|
||||
|
||||
=back
|
||||
|
||||
=head2 mailer-before_send
|
||||
|
||||
Called right before L<Bugzilla::Mailer> sends a message to the MTA.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<email> - The C<Email::MIME> object that's about to be sent.
|
||||
|
||||
=back
|
||||
|
||||
=head2 product-confirm_delete
|
||||
|
||||
Called before displaying the confirmation message when deleting a product.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<vars> - The template vars hashref.
|
||||
|
||||
=back
|
||||
|
||||
=head2 webservice
|
||||
|
||||
This hook allows you to add your own modules to the WebService. (See
|
||||
L<Bugzilla::WebService>.)
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<dispatch>
|
||||
|
||||
A hashref that you can specify the names of your modules and what Perl
|
||||
module handles the functions for that module. (This is actually sent to
|
||||
L<SOAP::Lite/dispatch_with>. You can see how that's used in F<xmlrpc.cgi>.)
|
||||
|
||||
The Perl module name must start with C<extensions::yourextension::lib::>
|
||||
(replace C<yourextension> with the name of your extension). The C<package>
|
||||
declaration inside that module must also start with
|
||||
C<extensions::yourextension::lib::> in that module's code.
|
||||
|
||||
Example:
|
||||
|
||||
$dispatch->{Example} = "extensions::example::lib::Example";
|
||||
|
||||
And then you'd have a module F<extensions/example/lib/Example.pm>
|
||||
|
||||
It's recommended that all the keys you put in C<dispatch> start with the
|
||||
name of your extension, so that you don't conflict with the standard Bugzilla
|
||||
WebService functions (and so that you also don't conflict with other
|
||||
plugins).
|
||||
|
||||
=back
|
||||
|
||||
=head2 webservice-error_codes
|
||||
|
||||
If your webservice extension throws custom errors, you can set numeric
|
||||
codes for those errors here.
|
||||
|
||||
Extensions should use error codes above 10000, unless they are re-using
|
||||
an already-existing error code.
|
||||
|
||||
Params:
|
||||
|
||||
=over
|
||||
|
||||
=item C<error_map>
|
||||
|
||||
A hash that maps the names of errors (like C<invalid_param>) to numbers.
|
||||
See L<Bugzilla::WebService::Constants/WS_ERROR_CODE> for an example.
|
||||
|
||||
=back
|
||||
@@ -1,454 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Install;
|
||||
|
||||
# Functions in this this package can assume that the database
|
||||
# has been set up, params are available, localconfig is
|
||||
# available, and any module can be used.
|
||||
#
|
||||
# If you want to write an installation function that can't
|
||||
# make those assumptions, then it should go into one of the
|
||||
# packages under the Bugzilla::Install namespace.
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Group;
|
||||
use Bugzilla::Product;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::User::Setting;
|
||||
use Bugzilla::Util qw(get_text);
|
||||
use Bugzilla::Version;
|
||||
|
||||
sub SETTINGS {
|
||||
return {
|
||||
# 2005-03-03 travis@sedsystems.ca -- Bug 41972
|
||||
display_quips => { options => ["on", "off"], default => "on" },
|
||||
# 2005-03-10 travis@sedsystems.ca -- Bug 199048
|
||||
comment_sort_order => { options => ["oldest_to_newest", "newest_to_oldest",
|
||||
"newest_to_oldest_desc_first"],
|
||||
default => "oldest_to_newest" },
|
||||
# 2005-05-12 bugzilla@glob.com.au -- Bug 63536
|
||||
post_bug_submit_action => { options => ["next_bug", "same_bug", "nothing"],
|
||||
default => "next_bug" },
|
||||
# 2005-06-29 wurblzap@gmail.com -- Bug 257767
|
||||
csv_colsepchar => { options => [',',';'], default => ',' },
|
||||
# 2005-10-26 wurblzap@gmail.com -- Bug 291459
|
||||
zoom_textareas => { options => ["on", "off"], default => "on" },
|
||||
# 2005-10-21 LpSolit@gmail.com -- Bug 313020
|
||||
per_bug_queries => { options => ['on', 'off'], default => 'off' },
|
||||
# 2006-05-01 olav@bkor.dhs.org -- Bug 7710
|
||||
state_addselfcc => { options => ['always', 'never', 'cc_unless_role'],
|
||||
default => 'cc_unless_role' },
|
||||
# 2006-08-04 wurblzap@gmail.com -- Bug 322693
|
||||
skin => { subclass => 'Skin', default => 'Dusk' },
|
||||
# 2006-12-10 LpSolit@gmail.com -- Bug 297186
|
||||
lang => { subclass => 'Lang',
|
||||
default => ${Bugzilla->languages}[0] },
|
||||
# 2007-07-02 altlist@gmail.com -- Bug 225731
|
||||
quote_replies => { options => ['quoted_reply', 'simple_reply', 'off'],
|
||||
default => "quoted_reply" },
|
||||
# 2008-08-27 LpSolit@gmail.com -- Bug 182238
|
||||
timezone => { subclass => 'Timezone', default => 'local' },
|
||||
}
|
||||
};
|
||||
|
||||
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::Group->flatten_group_membership($admin_group->id);
|
||||
my $admin_group_ids = join(',', @$admin_inheritors);
|
||||
|
||||
my ($admin_count) = $dbh->selectrow_array(
|
||||
"SELECT COUNT(*) FROM user_group_map
|
||||
WHERE group_id IN ($admin_group_ids)");
|
||||
|
||||
return if $admin_count;
|
||||
|
||||
my %answer = %{Bugzilla->installation_answers};
|
||||
my $login = $answer{'ADMIN_EMAIL'};
|
||||
my $password = $answer{'ADMIN_PASSWORD'};
|
||||
my $full_name = $answer{'ADMIN_REALNAME'};
|
||||
|
||||
if (!$login || !$password || !$full_name) {
|
||||
print "\n" . get_text('install_admin_setup') . "\n\n";
|
||||
}
|
||||
|
||||
while (!$login) {
|
||||
print get_text('install_admin_get_email') . ' ';
|
||||
$login = <STDIN>;
|
||||
chomp $login;
|
||||
eval { Bugzilla::User->check_login_name_for_creation($login); };
|
||||
if ($@) {
|
||||
print $@ . "\n";
|
||||
undef $login;
|
||||
}
|
||||
}
|
||||
|
||||
while (!defined $full_name) {
|
||||
print get_text('install_admin_get_name') . ' ';
|
||||
$full_name = <STDIN>;
|
||||
chomp($full_name);
|
||||
}
|
||||
|
||||
if (!$password) {
|
||||
$password = _prompt_for_password(
|
||||
get_text('install_admin_get_password'));
|
||||
}
|
||||
|
||||
my $admin = Bugzilla::User->create({ login_name => $login,
|
||||
realname => $full_name,
|
||||
cryptpassword => $password });
|
||||
make_admin($admin);
|
||||
}
|
||||
|
||||
sub make_admin {
|
||||
my ($user) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$user = ref($user) ? $user
|
||||
: new Bugzilla::User(login_to_id($user, THROW_ERROR));
|
||||
|
||||
my $admin_group = new Bugzilla::Group({ name => 'admin' });
|
||||
|
||||
# Admins get explicit membership and bless capability for the admin group
|
||||
$dbh->selectrow_array("SELECT id FROM groups WHERE name = 'admin'");
|
||||
|
||||
my $group_insert = $dbh->prepare(
|
||||
'INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
|
||||
VALUES (?, ?, ?, ?)');
|
||||
# These are run in an eval so that we can ignore the error of somebody
|
||||
# already being granted these things.
|
||||
eval {
|
||||
$group_insert->execute($user->id, $admin_group->id, 0, GRANT_DIRECT);
|
||||
};
|
||||
eval {
|
||||
$group_insert->execute($user->id, $admin_group->id, 1, GRANT_DIRECT);
|
||||
};
|
||||
|
||||
# Admins should also have editusers directly, even though they'll usually
|
||||
# inherit it. People could have changed their inheritance structure.
|
||||
my $editusers = new Bugzilla::Group({ name => 'editusers' });
|
||||
eval {
|
||||
$group_insert->execute($user->id, $editusers->id, 0, GRANT_DIRECT);
|
||||
};
|
||||
|
||||
print "\n", get_text('install_admin_created', { user => $user }), "\n";
|
||||
}
|
||||
|
||||
sub _prompt_for_password {
|
||||
my $prompt = shift;
|
||||
|
||||
my $password;
|
||||
while (!$password) {
|
||||
# trap a few interrupts so we can fix the echo if we get aborted.
|
||||
local $SIG{HUP} = \&_password_prompt_exit;
|
||||
local $SIG{INT} = \&_password_prompt_exit;
|
||||
local $SIG{QUIT} = \&_password_prompt_exit;
|
||||
local $SIG{TERM} = \&_password_prompt_exit;
|
||||
|
||||
system("stty","-echo") unless ON_WINDOWS; # disable input echoing
|
||||
|
||||
print $prompt, ' ';
|
||||
$password = <STDIN>;
|
||||
chomp $password;
|
||||
print "\n", get_text('install_confirm_password'), ' ';
|
||||
my $pass2 = <STDIN>;
|
||||
chomp $pass2;
|
||||
eval { validate_password($password, $pass2); };
|
||||
if ($@) {
|
||||
print "\n$@\n";
|
||||
undef $password;
|
||||
}
|
||||
system("stty","echo") unless ON_WINDOWS;
|
||||
}
|
||||
return $password;
|
||||
}
|
||||
|
||||
# This is just in case we get interrupted while getting a password.
|
||||
sub _password_prompt_exit {
|
||||
# re-enable input echoing
|
||||
system("stty","echo") unless ON_WINDOWS;
|
||||
exit 1;
|
||||
}
|
||||
|
||||
sub reset_password {
|
||||
my $login = shift;
|
||||
my $user = Bugzilla::User->check($login);
|
||||
my $prompt = "\n" . get_text('install_reset_password', { user => $user });
|
||||
my $password = _prompt_for_password($prompt);
|
||||
$user->set_password($password);
|
||||
$user->update();
|
||||
print "\n", get_text('install_reset_password_done'), "\n";
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install - Functions and variables having to do with
|
||||
installation.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Install;
|
||||
Bugzilla::Install::update_settings();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module is used primarily by L<checksetup.pl> during installation.
|
||||
This module contains functions that deal with general installation
|
||||
issues after the database is completely set up and configured.
|
||||
|
||||
=head1 CONSTANTS
|
||||
|
||||
=over
|
||||
|
||||
=item C<SETTINGS>
|
||||
|
||||
Contains information about Settings, used by L</update_settings()>.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<update_settings()>
|
||||
|
||||
Description: Adds and updates Settings for users.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: nothing.
|
||||
|
||||
=item C<create_default_product()>
|
||||
|
||||
Description: Creates the default product and classification if
|
||||
they don't exist.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
@@ -1,251 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Everything Solved, Inc.
|
||||
# Portions created by Everything Solved are Copyright (C) 2007
|
||||
# Everything Solved, Inc. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Install::CPAN;
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
our @EXPORT = qw(set_cpan_config install_module BZ_LIB);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Install::Util qw(bin_loc install_string);
|
||||
|
||||
use CPAN;
|
||||
use Cwd qw(abs_path);
|
||||
use File::Path qw(rmtree);
|
||||
use List::Util qw(shuffle);
|
||||
|
||||
# We need the absolute path of ext_libpath, because CPAN chdirs around
|
||||
# and so we can't use a relative directory.
|
||||
#
|
||||
# We need it often enough (and at compile time, in install-module.pl) so
|
||||
# we make it a constant.
|
||||
use constant BZ_LIB => abs_path(bz_locations()->{ext_libpath});
|
||||
|
||||
# CPAN requires nearly all of its parameters to be set, or it will start
|
||||
# asking questions to the user. We want to avoid that, so we have
|
||||
# defaults here for most of the required parameters we know about, in case
|
||||
# any of them aren't set. The rest are handled by set_cpan_defaults().
|
||||
use constant CPAN_DEFAULTS => {
|
||||
auto_commit => 0,
|
||||
# We always force builds, so there's no reason to cache them.
|
||||
build_cache => 0,
|
||||
cache_metadata => 1,
|
||||
index_expire => 1,
|
||||
scan_cache => 'atstart',
|
||||
|
||||
inhibit_startup_message => 1,
|
||||
mbuild_install_build_command => './Build',
|
||||
|
||||
curl => bin_loc('curl'),
|
||||
gzip => bin_loc('gzip'),
|
||||
links => bin_loc('links'),
|
||||
lynx => bin_loc('lynx'),
|
||||
make => bin_loc('make'),
|
||||
pager => bin_loc('less'),
|
||||
tar => bin_loc('tar'),
|
||||
unzip => bin_loc('unzip'),
|
||||
wget => bin_loc('wget'),
|
||||
|
||||
urllist => [shuffle qw(
|
||||
http://cpan.pair.com/
|
||||
http://mirror.hiwaay.net/CPAN/
|
||||
ftp://ftp.dc.aleron.net/pub/CPAN/
|
||||
http://perl.secsup.org/
|
||||
http://mirrors.kernel.org/cpan/)],
|
||||
};
|
||||
|
||||
sub install_module {
|
||||
my ($name, $notest) = @_;
|
||||
my $bzlib = BZ_LIB;
|
||||
|
||||
# Certain modules require special stuff in order to not prompt us.
|
||||
my $original_makepl = $CPAN::Config->{makepl_arg};
|
||||
# This one's a regex in case we're doing Template::Plugin::GD and it
|
||||
# pulls in Template-Toolkit as a dependency.
|
||||
if ($name =~ /^Template/) {
|
||||
$CPAN::Config->{makepl_arg} .= " TT_ACCEPT=y TT_EXTRAS=n";
|
||||
}
|
||||
elsif ($name eq 'XML::Twig') {
|
||||
$CPAN::Config->{makepl_arg} = "-n $original_makepl";
|
||||
}
|
||||
elsif ($name eq 'Net::LDAP') {
|
||||
$CPAN::Config->{makepl_arg} .= " --skipdeps";
|
||||
}
|
||||
elsif ($name eq 'SOAP::Lite') {
|
||||
$CPAN::Config->{makepl_arg} .= " --noprompt";
|
||||
}
|
||||
|
||||
my $module = CPAN::Shell->expand('Module', $name);
|
||||
print install_string('install_module',
|
||||
{ module => $name, version => $module->cpan_version }) . "\n";
|
||||
if ($notest) {
|
||||
CPAN::Shell->notest('install', $name);
|
||||
}
|
||||
else {
|
||||
CPAN::Shell->force('install', $name);
|
||||
}
|
||||
|
||||
# If it installed any binaries in the Bugzilla directory, delete them.
|
||||
if (-d "$bzlib/bin") {
|
||||
File::Path::rmtree("$bzlib/bin");
|
||||
}
|
||||
|
||||
$CPAN::Config->{makepl_arg} = $original_makepl;
|
||||
}
|
||||
|
||||
sub set_cpan_config {
|
||||
my $do_global = shift;
|
||||
my $bzlib = BZ_LIB;
|
||||
|
||||
# We set defaults before we do anything, otherwise CPAN will
|
||||
# start asking us questions as soon as we load its configuration.
|
||||
eval { require CPAN::Config; };
|
||||
_set_cpan_defaults();
|
||||
|
||||
# Calling a senseless autoload that does nothing makes us
|
||||
# automatically load any existing configuration.
|
||||
# We want to avoid the "invalid command" message.
|
||||
open(my $saveout, ">&STDOUT");
|
||||
open(STDOUT, '>/dev/null');
|
||||
eval { CPAN->ignore_this_error_message_from_bugzilla; };
|
||||
undef $@;
|
||||
close(STDOUT);
|
||||
open(STDOUT, '>&', $saveout);
|
||||
|
||||
my $dir = $CPAN::Config->{cpan_home};
|
||||
if (!defined $dir || !-w $dir) {
|
||||
# If we can't use the standard CPAN build dir, we try to make one.
|
||||
$dir = "$ENV{HOME}/.cpan";
|
||||
mkdir $dir;
|
||||
|
||||
# If we can't make one, we finally try to use the Bugzilla directory.
|
||||
if (!-w $dir) {
|
||||
print "WARNING: Using the Bugzilla directory as the CPAN home.\n";
|
||||
$dir = "$bzlib/.cpan";
|
||||
}
|
||||
}
|
||||
$CPAN::Config->{cpan_home} = $dir;
|
||||
$CPAN::Config->{build_dir} = "$dir/build";
|
||||
# We always force builds, so there's no reason to cache them.
|
||||
$CPAN::Config->{keep_source_where} = "$dir/source";
|
||||
# This is set both here and in defaults so that it's always true.
|
||||
$CPAN::Config->{inhibit_startup_message} = 1;
|
||||
# Automatically install dependencies.
|
||||
$CPAN::Config->{prerequisites_policy} = 'follow';
|
||||
|
||||
# Unless specified, we install the modules into the Bugzilla directory.
|
||||
if (!$do_global) {
|
||||
$CPAN::Config->{makepl_arg} .= " LIB=\"$bzlib\""
|
||||
. " INSTALLMAN1DIR=\"$bzlib/man/man1\""
|
||||
. " INSTALLMAN3DIR=\"$bzlib/man/man3\""
|
||||
# The bindirs are here because otherwise we'll try to write to
|
||||
# the system binary dirs, and that will cause CPAN to die.
|
||||
. " INSTALLBIN=\"$bzlib/bin\""
|
||||
. " INSTALLSCRIPT=\"$bzlib/bin\""
|
||||
# INSTALLDIRS=perl is set because that makes sure that MakeMaker
|
||||
# always uses the directories we've specified here.
|
||||
. " INSTALLDIRS=perl";
|
||||
$CPAN::Config->{mbuild_arg} = "--install_base \"$bzlib\"";
|
||||
|
||||
# When we're not root, sometimes newer versions of CPAN will
|
||||
# try to read/modify things that belong to root, unless we set
|
||||
# certain config variables.
|
||||
$CPAN::Config->{histfile} = "$dir/histfile";
|
||||
$CPAN::Config->{use_sqlite} = 0;
|
||||
$CPAN::Config->{prefs_dir} = "$dir/prefs";
|
||||
|
||||
# Unless we actually set PERL5LIB, some modules can't install
|
||||
# themselves, like DBD::mysql, DBD::Pg, and XML::Twig.
|
||||
my $current_lib = $ENV{PERL5LIB} ? $ENV{PERL5LIB} . ':' : '';
|
||||
$ENV{PERL5LIB} = $current_lib . $bzlib;
|
||||
}
|
||||
}
|
||||
|
||||
sub _set_cpan_defaults {
|
||||
# If CPAN hasn't been configured, we try to use some reasonable defaults.
|
||||
foreach my $key (keys %{CPAN_DEFAULTS()}) {
|
||||
$CPAN::Config->{$key} = CPAN_DEFAULTS->{$key}
|
||||
if !defined $CPAN::Config->{$key};
|
||||
}
|
||||
|
||||
my @missing;
|
||||
# In newer CPANs, this is in HandleConfig. In older CPANs, it's in
|
||||
# Config.
|
||||
if (eval { require CPAN::HandleConfig }) {
|
||||
@missing = CPAN::HandleConfig->missing_config_data;
|
||||
}
|
||||
else {
|
||||
@missing = CPAN::Config->missing_config_data;
|
||||
}
|
||||
|
||||
foreach my $key (@missing) {
|
||||
$CPAN::Config->{$key} = '';
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install::CPAN - Routines to install Perl modules from CPAN.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Install::CPAN;
|
||||
|
||||
set_cpan_config();
|
||||
install_module('Module::Name', 1);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is primarily used by L<install-module> to do the "hard work" of
|
||||
installing CPAN modules.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<set_cpan_config>
|
||||
|
||||
Sets up the configuration of CPAN for this session. Must be called
|
||||
before L</install_module>. Takes one boolean parameter. If true,
|
||||
L</install_module> will install modules globally instead of to the
|
||||
local F<lib/> directory. On most systems, you have to be root to do that.
|
||||
|
||||
=item C<install_module>
|
||||
|
||||
Installs a module from CPAN. Takes two arguments:
|
||||
|
||||
=over
|
||||
|
||||
=item C<$name> - The name of the module, just like you'd pass to the
|
||||
C<install> command in the CPAN shell.
|
||||
|
||||
=item C<$notest> - If true, we skip running tests on this module. This
|
||||
can greatly speed up the installation time.
|
||||
|
||||
=back
|
||||
|
||||
Note that calling this function prints a B<lot> of information to
|
||||
STDOUT and STDERR.
|
||||
|
||||
=back
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,694 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Bill Barry <after.fallout@gmail.com>
|
||||
|
||||
package Bugzilla::Install::Filesystem;
|
||||
|
||||
# NOTE: This package may "use" any modules that it likes,
|
||||
# and localconfig is available. However, all functions in this
|
||||
# package should assume that:
|
||||
#
|
||||
# * Templates are not available.
|
||||
# * Files do not have the correct permissions.
|
||||
# * The database does not exist.
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Install::Localconfig;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use File::Find;
|
||||
use File::Path;
|
||||
use File::Basename;
|
||||
use IO::File;
|
||||
use POSIX ();
|
||||
|
||||
use base qw(Exporter);
|
||||
our @EXPORT = qw(
|
||||
update_filesystem
|
||||
create_htaccess
|
||||
fix_all_file_permissions
|
||||
);
|
||||
|
||||
# This looks like a constant because it effectively is, but
|
||||
# it has to call other subroutines and read the current filesystem,
|
||||
# so it's defined as a sub. This is not exported, so it doesn't have
|
||||
# a perldoc. However, look at the various hashes defined inside this
|
||||
# function to understand what it returns. (There are comments throughout.)
|
||||
#
|
||||
# The rationale for the file permissions is that the web server generally
|
||||
# runs as apache, so the cgi scripts should not be writable for apache,
|
||||
# otherwise someone may find it possible to change the cgis when exploiting
|
||||
# some security flaw somewhere (not necessarily in Bugzilla!)
|
||||
sub FILESYSTEM {
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
my $attachdir = bz_locations()->{'attachdir'};
|
||||
my $extensionsdir = bz_locations()->{'extensionsdir'};
|
||||
my $webdotdir = bz_locations()->{'webdotdir'};
|
||||
my $templatedir = bz_locations()->{'templatedir'};
|
||||
my $libdir = bz_locations()->{'libpath'};
|
||||
my $extlib = bz_locations()->{'ext_libpath'};
|
||||
my $skinsdir = bz_locations()->{'skinsdir'};
|
||||
my $localconfig = bz_locations()->{'localconfig'};
|
||||
|
||||
my $ws_group = Bugzilla->localconfig->{'webservergroup'};
|
||||
|
||||
# The set of permissions that we use:
|
||||
|
||||
# FILES
|
||||
# Executable by the web server
|
||||
my $ws_executable = $ws_group ? 0750 : 0755;
|
||||
# Executable by the owner only.
|
||||
my $owner_executable = 0700;
|
||||
# Readable by the web server.
|
||||
my $ws_readable = $ws_group ? 0640 : 0644;
|
||||
# Readable by the owner only.
|
||||
my $owner_readable = 0600;
|
||||
# Writeable by the web server.
|
||||
my $ws_writeable = $ws_group ? 0660 : 0666;
|
||||
|
||||
# DIRECTORIES
|
||||
# Readable by the web server.
|
||||
my $ws_dir_readable = $ws_group ? 0750 : 0755;
|
||||
# Readable only by the owner.
|
||||
my $owner_dir_readable = 0700;
|
||||
# Writeable by the web server.
|
||||
my $ws_dir_writeable = $ws_group ? 0770 : 01777;
|
||||
# The web server can overwrite files owned by other users,
|
||||
# in this directory.
|
||||
my $ws_dir_full_control = $ws_group ? 0770 : 0777;
|
||||
|
||||
# Note: When being processed by checksetup, these have their permissions
|
||||
# set in this order: %all_dirs, %recurse_dirs, %all_files.
|
||||
#
|
||||
# Each is processed in alphabetical order of keys, so shorter keys
|
||||
# will have their permissions set before longer keys (thus setting
|
||||
# the permissions on parent directories before setting permissions
|
||||
# on their children).
|
||||
|
||||
# --- FILE PERMISSIONS (Non-created files) --- #
|
||||
my %files = (
|
||||
'*' => { perms => $ws_readable },
|
||||
'*.cgi' => { perms => $ws_executable },
|
||||
'whineatnews.pl' => { perms => $ws_executable },
|
||||
'collectstats.pl' => { perms => $ws_executable },
|
||||
'checksetup.pl' => { perms => $owner_executable },
|
||||
'importxml.pl' => { perms => $ws_executable },
|
||||
'runtests.pl' => { perms => $owner_executable },
|
||||
'testserver.pl' => { perms => $ws_executable },
|
||||
'whine.pl' => { perms => $ws_executable },
|
||||
'customfield.pl' => { perms => $owner_executable },
|
||||
'email_in.pl' => { perms => $ws_executable },
|
||||
'sanitycheck.pl' => { perms => $ws_executable },
|
||||
'jobqueue.pl' => { perms => $owner_executable },
|
||||
'install-module.pl' => { perms => $owner_executable },
|
||||
|
||||
"$localconfig.old" => { perms => $owner_readable },
|
||||
|
||||
'docs/makedocs.pl' => { perms => $owner_executable },
|
||||
'docs/style.css' => { perms => $ws_readable },
|
||||
'docs/*/rel_notes.txt' => { perms => $ws_readable },
|
||||
'docs/*/README.docs' => { perms => $owner_readable },
|
||||
"$datadir/bugzilla-update.xml" => { perms => $ws_writeable },
|
||||
"$datadir/params" => { perms => $ws_writeable },
|
||||
"$datadir/mailer.testfile" => { perms => $ws_writeable },
|
||||
);
|
||||
|
||||
# Directories that we want to set the perms on, but not
|
||||
# recurse through. These are directories we didn't create
|
||||
# in checkesetup.pl.
|
||||
my %non_recurse_dirs = (
|
||||
'.' => $ws_dir_readable,
|
||||
docs => $ws_dir_readable,
|
||||
);
|
||||
|
||||
# This sets the permissions for each item inside each of these
|
||||
# directories, including the directory itself.
|
||||
# 'CVS' directories are special, though, and are never readable by
|
||||
# the webserver.
|
||||
my %recurse_dirs = (
|
||||
# Writeable directories
|
||||
"$datadir/template" => { files => $ws_readable,
|
||||
dirs => $ws_dir_full_control },
|
||||
$attachdir => { files => $ws_writeable,
|
||||
dirs => $ws_dir_writeable },
|
||||
$webdotdir => { files => $ws_writeable,
|
||||
dirs => $ws_dir_writeable },
|
||||
graphs => { files => $ws_writeable,
|
||||
dirs => $ws_dir_writeable },
|
||||
|
||||
# Readable directories
|
||||
"$datadir/mining" => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
"$datadir/duplicates" => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
"$libdir/Bugzilla" => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
$extlib => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
$templatedir => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
$extensionsdir => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
images => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
css => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
js => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
$skinsdir => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
t => { files => $owner_readable,
|
||||
dirs => $owner_dir_readable },
|
||||
'docs/*/html' => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
'docs/*/pdf' => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
'docs/*/txt' => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
'docs/*/images' => { files => $ws_readable,
|
||||
dirs => $ws_dir_readable },
|
||||
'docs/lib' => { files => $owner_readable,
|
||||
dirs => $owner_dir_readable },
|
||||
'docs/*/xml' => { files => $owner_readable,
|
||||
dirs => $owner_dir_readable },
|
||||
);
|
||||
|
||||
# --- FILES TO CREATE --- #
|
||||
|
||||
# The name of each directory that we should actually *create*,
|
||||
# pointing at its default permissions.
|
||||
my %create_dirs = (
|
||||
$datadir => $ws_dir_full_control,
|
||||
"$datadir/mining" => $ws_dir_readable,
|
||||
"$datadir/duplicates" => $ws_dir_readable,
|
||||
$attachdir => $ws_dir_writeable,
|
||||
$extensionsdir => $ws_dir_readable,
|
||||
graphs => $ws_dir_writeable,
|
||||
$webdotdir => $ws_dir_writeable,
|
||||
"$skinsdir/custom" => $ws_dir_readable,
|
||||
"$skinsdir/contrib" => $ws_dir_readable,
|
||||
);
|
||||
|
||||
# The name of each file, pointing at its default permissions and
|
||||
# default contents.
|
||||
my %create_files = ();
|
||||
|
||||
# Each standard stylesheet has an associated custom stylesheet that
|
||||
# we create. Also, we create placeholders for standard stylesheets
|
||||
# for contrib skins which don't provide them themselves.
|
||||
foreach my $skin_dir ("$skinsdir/custom", <$skinsdir/contrib/*>) {
|
||||
next if basename($skin_dir) =~ /^cvs$/i;
|
||||
$create_dirs{"$skin_dir/yui"} = $ws_dir_readable;
|
||||
foreach my $base_css (<$skinsdir/standard/*.css>) {
|
||||
_add_custom_css($skin_dir, basename($base_css), \%create_files, $ws_readable);
|
||||
}
|
||||
foreach my $dir_css (<$skinsdir/standard/*/*.css>) {
|
||||
$dir_css =~ s{.+?([^/]+/[^/]+)$}{$1};
|
||||
_add_custom_css($skin_dir, $dir_css, \%create_files, $ws_readable);
|
||||
}
|
||||
}
|
||||
|
||||
# Because checksetup controls the creation of index.html separately
|
||||
# from all other files, it gets its very own hash.
|
||||
my %index_html = (
|
||||
'index.html' => { perms => $ws_readable, contents => <<EOT
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Refresh" content="0; URL=index.cgi">
|
||||
</head>
|
||||
<body>
|
||||
<h1>I think you are looking for <a href="index.cgi">index.cgi</a></h1>
|
||||
</body>
|
||||
</html>
|
||||
EOT
|
||||
}
|
||||
);
|
||||
|
||||
# Because checksetup controls the .htaccess creation separately
|
||||
# by a localconfig variable, these go in a separate variable from
|
||||
# %create_files.
|
||||
my $ht_default_deny = <<EOT;
|
||||
# nothing in this directory is retrievable unless overridden by an .htaccess
|
||||
# in a subdirectory
|
||||
deny from all
|
||||
EOT
|
||||
|
||||
my %htaccess = (
|
||||
"$attachdir/.htaccess" => { perms => $ws_readable,
|
||||
contents => $ht_default_deny },
|
||||
"$libdir/Bugzilla/.htaccess" => { perms => $ws_readable,
|
||||
contents => $ht_default_deny },
|
||||
"$extlib/.htaccess" => { perms => $ws_readable,
|
||||
contents => $ht_default_deny },
|
||||
"$templatedir/.htaccess" => { perms => $ws_readable,
|
||||
contents => $ht_default_deny },
|
||||
|
||||
'.htaccess' => { perms => $ws_readable, contents => <<EOT
|
||||
# Don't allow people to retrieve non-cgi executable files or our private data
|
||||
<FilesMatch ^(.*\\.pm|.*\\.pl|.*localconfig.*)\$>
|
||||
deny from all
|
||||
</FilesMatch>
|
||||
EOT
|
||||
},
|
||||
|
||||
"$webdotdir/.htaccess" => { perms => $ws_readable, contents => <<EOT
|
||||
# Restrict access to .dot files to the public webdot server at research.att.com
|
||||
# if research.att.com ever changes their IP, or if you use a different
|
||||
# webdot server, you'll need to edit this
|
||||
<FilesMatch \\.dot\$>
|
||||
Allow from 192.20.225.0/24
|
||||
Deny from all
|
||||
</FilesMatch>
|
||||
|
||||
# Allow access to .png files created by a local copy of 'dot'
|
||||
<FilesMatch \\.png\$>
|
||||
Allow from all
|
||||
</FilesMatch>
|
||||
|
||||
# And no directory listings, either.
|
||||
Deny from all
|
||||
EOT
|
||||
},
|
||||
|
||||
# Even though $datadir may not (and should not) be accessible from the
|
||||
# web server, we can't know for sure, so create the .htaccess anyway.
|
||||
# It's harmless if it isn't accessible...
|
||||
"$datadir/.htaccess" => { perms => $ws_readable, contents => <<EOT
|
||||
# Nothing in this directory is retrievable unless overridden by an .htaccess
|
||||
# in a subdirectory.
|
||||
deny from all
|
||||
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";
|
||||
}
|
||||
|
||||
if (-e "$datadir/duplicates.rdf") {
|
||||
print "Removing duplicates.rdf...\n";
|
||||
unlink "$datadir/duplicates.rdf";
|
||||
unlink "$datadir/duplicates-old.rdf";
|
||||
}
|
||||
}
|
||||
|
||||
# A simple helper for creating "empty" CSS files.
|
||||
sub _add_custom_css {
|
||||
my ($skin_dir, $path, $create_files, $perms) = @_;
|
||||
$create_files->{"$skin_dir/$path"} = { perms => $perms, contents => <<EOT
|
||||
/*
|
||||
* Custom rules for $path.
|
||||
* The rules you put here override rules in that stylesheet.
|
||||
*/
|
||||
EOT
|
||||
};
|
||||
}
|
||||
|
||||
sub create_htaccess {
|
||||
_create_files(%{FILESYSTEM()->{htaccess}});
|
||||
|
||||
# Repair old .htaccess files
|
||||
my $htaccess = new IO::File('.htaccess', 'r') || die ".htaccess: $!";
|
||||
my $old_data;
|
||||
{ local $/; $old_data = <$htaccess>; }
|
||||
$htaccess->close;
|
||||
|
||||
my $repaired = 0;
|
||||
if ($old_data =~ s/\|localconfig\|/\|.*localconfig.*\|/) {
|
||||
$repaired = 1;
|
||||
}
|
||||
if ($old_data !~ /\(\.\*\\\.pm\|/) {
|
||||
$old_data =~ s/\(/(.*\\.pm\|/;
|
||||
$repaired = 1;
|
||||
}
|
||||
if ($repaired) {
|
||||
print "Repairing .htaccess...\n";
|
||||
$htaccess = new IO::File('.htaccess', 'w') || die $!;
|
||||
print $htaccess $old_data;
|
||||
$htaccess->close;
|
||||
}
|
||||
|
||||
|
||||
my $webdot_dir = bz_locations()->{'webdotdir'};
|
||||
# The public webdot IP address changed.
|
||||
my $webdot = new IO::File("$webdot_dir/.htaccess", 'r')
|
||||
|| die "$webdot_dir/.htaccess: $!";
|
||||
my $webdot_data;
|
||||
{ local $/; $webdot_data = <$webdot>; }
|
||||
$webdot->close;
|
||||
if ($webdot_data =~ /192\.20\.225\.10/) {
|
||||
print "Repairing $webdot_dir/.htaccess...\n";
|
||||
$webdot_data =~ s/192\.20\.225\.10/192.20.225.0\/24/g;
|
||||
$webdot = new IO::File("$webdot_dir/.htaccess", 'w') || die $!;
|
||||
print $webdot $webdot_data;
|
||||
$webdot->close;
|
||||
}
|
||||
}
|
||||
|
||||
# A helper for the above functions.
|
||||
sub _create_files {
|
||||
my (%files) = @_;
|
||||
|
||||
# It's not necessary to sort these, but it does make the
|
||||
# output of checksetup.pl look a bit nicer.
|
||||
foreach my $file (sort keys %files) {
|
||||
unless (-e $file) {
|
||||
print "Creating $file...\n";
|
||||
my $info = $files{$file};
|
||||
my $fh = new IO::File($file, O_WRONLY | O_CREAT, $info->{perms})
|
||||
|| die $!;
|
||||
print $fh $info->{contents} if $info->{contents};
|
||||
$fh->close;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# If you ran a REALLY old version of Bugzilla, your chart files are in the
|
||||
# wrong format. This code is a little messy, because it's very old, and
|
||||
# when moving it into this module, I couldn't test it so I left it almost
|
||||
# completely alone.
|
||||
sub _update_old_charts {
|
||||
my ($datadir) = @_;
|
||||
print "Updating old chart storage format...\n";
|
||||
foreach my $in_file (glob("$datadir/mining/*")) {
|
||||
# Don't try and upgrade image or db files!
|
||||
next if (($in_file =~ /\.gif$/i) ||
|
||||
($in_file =~ /\.png$/i) ||
|
||||
($in_file =~ /\.db$/i) ||
|
||||
($in_file =~ /\.orig$/i));
|
||||
|
||||
rename("$in_file", "$in_file.orig") or next;
|
||||
open(IN, "$in_file.orig") or next;
|
||||
open(OUT, '>', $in_file) or next;
|
||||
|
||||
# Fields in the header
|
||||
my @declared_fields;
|
||||
|
||||
# Fields we changed to half way through by mistake
|
||||
# This list comes from an old version of collectstats.pl
|
||||
# This part is only for people who ran later versions of 2.11 (devel)
|
||||
my @intermediate_fields = qw(DATE UNCONFIRMED NEW ASSIGNED REOPENED
|
||||
RESOLVED VERIFIED CLOSED);
|
||||
|
||||
# Fields we actually want (matches the current collectstats.pl)
|
||||
my @out_fields = qw(DATE NEW ASSIGNED REOPENED UNCONFIRMED RESOLVED
|
||||
VERIFIED CLOSED FIXED INVALID WONTFIX LATER REMIND
|
||||
DUPLICATE WORKSFORME MOVED);
|
||||
|
||||
while (<IN>) {
|
||||
if (/^# fields?: (.*)\s$/) {
|
||||
@declared_fields = map uc, (split /\||\r/, $1);
|
||||
print OUT "# fields: ", join('|', @out_fields), "\n";
|
||||
}
|
||||
elsif (/^(\d+\|.*)/) {
|
||||
my @data = split(/\||\r/, $1);
|
||||
my %data;
|
||||
if (@data == @declared_fields) {
|
||||
# old format
|
||||
for my $i (0 .. $#declared_fields) {
|
||||
$data{$declared_fields[$i]} = $data[$i];
|
||||
}
|
||||
}
|
||||
elsif (@data == @intermediate_fields) {
|
||||
# Must have changed over at this point
|
||||
for my $i (0 .. $#intermediate_fields) {
|
||||
$data{$intermediate_fields[$i]} = $data[$i];
|
||||
}
|
||||
}
|
||||
elsif (@data == @out_fields) {
|
||||
# This line's fine - it has the right number of entries
|
||||
for my $i (0 .. $#out_fields) {
|
||||
$data{$out_fields[$i]} = $data[$i];
|
||||
}
|
||||
}
|
||||
else {
|
||||
print "Oh dear, input line $. of $in_file had " .
|
||||
scalar(@data) . " fields\nThis was unexpected.",
|
||||
" You may want to check your data files.\n";
|
||||
}
|
||||
|
||||
print OUT join('|',
|
||||
map { defined ($data{$_}) ? ($data{$_}) : "" } @out_fields),
|
||||
"\n";
|
||||
}
|
||||
else {
|
||||
print OUT;
|
||||
}
|
||||
}
|
||||
|
||||
close(IN);
|
||||
close(OUT);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
sub fix_all_file_permissions {
|
||||
my ($output) = @_;
|
||||
|
||||
my $ws_group = Bugzilla->localconfig->{'webservergroup'};
|
||||
my $group_id = _check_web_server_group($ws_group, $output);
|
||||
|
||||
return if ON_WINDOWS;
|
||||
|
||||
my $fs = FILESYSTEM();
|
||||
my %files = %{$fs->{all_files}};
|
||||
my %dirs = %{$fs->{all_dirs}};
|
||||
my %recurse_dirs = %{$fs->{recurse_dirs}};
|
||||
|
||||
print get_text('install_file_perms_fix') . "\n" if $output;
|
||||
|
||||
my $owner_id = POSIX::getuid();
|
||||
$group_id = POSIX::getgid() unless defined $group_id;
|
||||
|
||||
foreach my $dir (sort keys %dirs) {
|
||||
next unless -d $dir;
|
||||
_fix_perms($dir, $owner_id, $group_id, $dirs{$dir});
|
||||
}
|
||||
|
||||
foreach my $dir (sort keys %recurse_dirs) {
|
||||
next unless -d $dir;
|
||||
# Set permissions on the directory itself.
|
||||
my $perms = $recurse_dirs{$dir};
|
||||
_fix_perms($dir, $owner_id, $group_id, $perms->{dirs});
|
||||
# Now recurse through the directory and set the correct permissions
|
||||
# on subdirectories and files.
|
||||
find({ no_chdir => 1, wanted => sub {
|
||||
my $name = $File::Find::name;
|
||||
if (-d $name) {
|
||||
_fix_perms($name, $owner_id, $group_id, $perms->{dirs});
|
||||
}
|
||||
else {
|
||||
_fix_perms($name, $owner_id, $group_id, $perms->{files});
|
||||
}
|
||||
}}, $dir);
|
||||
}
|
||||
|
||||
foreach my $file (sort keys %files) {
|
||||
# %files supports globs
|
||||
foreach my $filename (glob $file) {
|
||||
# Don't touch directories.
|
||||
next if -d $filename || !-e $filename;
|
||||
_fix_perms($filename, $owner_id, $group_id,
|
||||
$files{$file}->{perms});
|
||||
}
|
||||
}
|
||||
|
||||
_fix_cvs_dirs($owner_id, '.');
|
||||
}
|
||||
|
||||
# A helper for fix_all_file_permissions
|
||||
sub _fix_cvs_dirs {
|
||||
my ($owner_id, $dir) = @_;
|
||||
my $owner_gid = POSIX::getgid();
|
||||
find({ no_chdir => 1, wanted => sub {
|
||||
my $name = $File::Find::name;
|
||||
if ($File::Find::dir =~ /\/CVS/ || $_ eq '.cvsignore'
|
||||
|| (-d $name && $_ eq 'CVS')) {
|
||||
_fix_perms($name, $owner_id, $owner_gid, 0700);
|
||||
}
|
||||
}}, $dir);
|
||||
}
|
||||
|
||||
sub _fix_perms {
|
||||
my ($name, $owner, $group, $perms) = @_;
|
||||
#printf ("Changing $name to %o\n", $perms);
|
||||
chown $owner, $group, $name
|
||||
|| warn "Failed to change ownership of $name: $!";
|
||||
chmod $perms, $name
|
||||
|| warn "Failed to change permissions of $name: $!";
|
||||
}
|
||||
|
||||
sub _check_web_server_group {
|
||||
my ($group, $output) = @_;
|
||||
|
||||
my $filename = bz_locations()->{'localconfig'};
|
||||
my $group_id;
|
||||
|
||||
# If we are on Windows, webservergroup does nothing
|
||||
if (ON_WINDOWS && $group && $output) {
|
||||
print "\n\n" . get_text('install_webservergroup_windows') . "\n\n";
|
||||
}
|
||||
|
||||
# If we're not on Windows, make sure that webservergroup isn't
|
||||
# empty.
|
||||
elsif (!ON_WINDOWS && !$group && $output) {
|
||||
print "\n\n" . get_text('install_webservergroup_empty') . "\n\n";
|
||||
}
|
||||
|
||||
# If we're not on Windows, make sure we are actually a member of
|
||||
# the webservergroup.
|
||||
elsif (!ON_WINDOWS && $group) {
|
||||
$group_id = getgrnam($group);
|
||||
ThrowCodeError('invalid_webservergroup', { group => $group })
|
||||
unless defined $group_id;
|
||||
|
||||
# If on unix, see if we need to print a warning about a webservergroup
|
||||
# that we can't chgrp to
|
||||
if ($output && $< != 0 && !grep($_ eq $group_id, split(" ", $)))) {
|
||||
print "\n\n" . get_text('install_webservergroup_not_in') . "\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $group_id;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install::Filesystem - Fix up the filesystem during
|
||||
installation.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module is used primarily by L<checksetup.pl> to modify the
|
||||
filesystem during installation, including creating the data/ directory.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<update_filesystem({ index_html => 0 })>
|
||||
|
||||
Description: Creates all the directories and files that Bugzilla
|
||||
needs to function but doesn't ship with. Also does
|
||||
any updates to these files as necessary during an
|
||||
upgrade.
|
||||
|
||||
Params: C<index_html> - Whether or not we should create
|
||||
the F<index.html> file.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=item C<create_htaccess()>
|
||||
|
||||
Description: Creates all of the .htaccess files for Apache,
|
||||
in the various Bugzilla directories. Also updates
|
||||
the .htaccess files if they need updating.
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=item C<fix_all_file_permissions($output)>
|
||||
|
||||
Description: Sets all the file permissions on all of Bugzilla's files
|
||||
to what they should be. Note that permissions are different
|
||||
depending on whether or not C<$webservergroup> is set
|
||||
in F<localconfig>.
|
||||
|
||||
Params: C<$output> - C<true> if you want this function to print
|
||||
out information about what it's doing.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
@@ -1,484 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Everything Solved.
|
||||
# Portions created by Everything Solved are Copyright (C) 2006
|
||||
# Everything Solved. All Rights Reserved.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Install::Localconfig;
|
||||
|
||||
# NOTE: This package may "use" any modules that it likes. However,
|
||||
# all functions in this package should assume that:
|
||||
#
|
||||
# * The data/ directory does not exist.
|
||||
# * Templates are not available.
|
||||
# * Files do not have the correct permissions
|
||||
# * The database is not up to date
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Install::Util qw(bin_loc);
|
||||
use Bugzilla::Util qw(generate_random_password);
|
||||
|
||||
use Data::Dumper;
|
||||
use File::Basename qw(dirname);
|
||||
use IO::File;
|
||||
use Safe;
|
||||
|
||||
use base qw(Exporter);
|
||||
|
||||
our @EXPORT_OK = qw(
|
||||
read_localconfig
|
||||
update_localconfig
|
||||
);
|
||||
|
||||
use constant LOCALCONFIG_VARS => (
|
||||
{
|
||||
name => 'create_htaccess',
|
||||
default => 1,
|
||||
desc => <<EOT
|
||||
# If you are using Apache as your web server, Bugzilla can create .htaccess
|
||||
# files for you that will instruct Apache not to serve files that shouldn't
|
||||
# be accessed from the web browser (like your local configuration data and non-cgi
|
||||
# executable files). For this to work, the directory your Bugzilla
|
||||
# installation is in must be within the jurisdiction of a <Directory> block
|
||||
# in the httpd.conf file that has 'AllowOverride Limit' in it. If it has
|
||||
# 'AllowOverride All' or other options with Limit, that's fine.
|
||||
# (Older Apache installations may use an access.conf file to store these
|
||||
# <Directory> blocks.)
|
||||
# If this is set to 1, Bugzilla will create these files if they don't exist.
|
||||
# If this is set to 0, Bugzilla will not create these files.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'webservergroup',
|
||||
default => ON_WINDOWS ? '' : 'apache',
|
||||
desc => q{# This is the group your web server runs as.
|
||||
# If you have a Windows box, ignore this setting.
|
||||
# If you do not have access to the group your web server runs under,
|
||||
# set this to "". If you do set this to "", then your Bugzilla installation
|
||||
# will be _VERY_ insecure, because some files will be world readable/writable,
|
||||
# and so anyone who can get local access to your machine can do whatever they
|
||||
# want. You should only have this set to "" if this is a testing installation
|
||||
# and you cannot set this up any other way. YOU HAVE BEEN WARNED!
|
||||
# If you set this to anything other than "", you will need to run checksetup.pl
|
||||
# as} . ROOT_USER . qq{, or as a user who is a member of the specified group.\n}
|
||||
},
|
||||
{
|
||||
name => 'db_driver',
|
||||
default => 'mysql',
|
||||
desc => <<EOT
|
||||
# What SQL database to use. Default is mysql. List of supported databases
|
||||
# can be obtained by listing Bugzilla/DB directory - every module corresponds
|
||||
# to one supported database and the name corresponds to a driver name.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'db_host',
|
||||
default => 'localhost',
|
||||
desc =>
|
||||
"# The DNS name of the host that the database server runs on.\n"
|
||||
},
|
||||
{
|
||||
name => 'db_name',
|
||||
default => 'bugs',
|
||||
desc => "# The name of the database\n"
|
||||
},
|
||||
{
|
||||
name => 'db_user',
|
||||
default => 'bugs',
|
||||
desc => "# Who we connect to the database as.\n"
|
||||
},
|
||||
{
|
||||
name => 'db_pass',
|
||||
default => '',
|
||||
desc => <<EOT
|
||||
# Enter your database password here. It's normally advisable to specify
|
||||
# a password for your bugzilla database user.
|
||||
# If you use apostrophe (') or a backslash (\\) in your password, you'll
|
||||
# need to escape it by preceding it with a '\\' character. (\\') or (\\)
|
||||
# (Far simpler just not to use those characters.)
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'db_port',
|
||||
default => 0,
|
||||
desc => <<EOT
|
||||
# Sometimes the database server is running on a non-standard port. If that's
|
||||
# the case for your database server, set this to the port number that your
|
||||
# database server is running on. Setting this to 0 means "use the default
|
||||
# port for my database server."
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'db_sock',
|
||||
default => '',
|
||||
desc => <<EOT
|
||||
# MySQL Only: Enter a path to the unix socket for MySQL. If this is
|
||||
# blank, then MySQL's compiled-in default will be used. You probably
|
||||
# want that.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'db_check',
|
||||
default => 1,
|
||||
desc => <<EOT
|
||||
# Should checksetup.pl try to verify that your database setup is correct?
|
||||
# (with some combinations of database servers/Perl modules/moonphase this
|
||||
# doesn't work)
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'index_html',
|
||||
default => 0,
|
||||
desc => <<EOT
|
||||
# With the introduction of a configurable index page using the
|
||||
# template toolkit, Bugzilla's main index page is now index.cgi.
|
||||
# Most web servers will allow you to use index.cgi as a directory
|
||||
# index, and many come preconfigured that way, but if yours doesn't
|
||||
# then you'll need an index.html file that provides redirection
|
||||
# to index.cgi. Setting \$index_html to 1 below will allow
|
||||
# checksetup.pl to create one for you if it doesn't exist.
|
||||
# NOTE: checksetup.pl will not replace an existing file, so if you
|
||||
# wish to have checksetup.pl create one for you, you must
|
||||
# make sure that index.html doesn't already exist
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'cvsbin',
|
||||
default => \&_get_default_cvsbin,
|
||||
desc => <<EOT
|
||||
# For some optional functions of Bugzilla (such as the pretty-print patch
|
||||
# viewer), we need the cvs binary to access files and revisions.
|
||||
# Because it's possible that this program is not in your path, you can specify
|
||||
# its location here. Please specify the full path to the executable.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'interdiffbin',
|
||||
default => \&_get_default_interdiffbin,
|
||||
desc => <<EOT
|
||||
# For some optional functions of Bugzilla (such as the pretty-print patch
|
||||
# viewer), we need the interdiff binary to make diffs between two patches.
|
||||
# Because it's possible that this program is not in your path, you can specify
|
||||
# its location here. Please specify the full path to the executable.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'diffpath',
|
||||
default => \&_get_default_diffpath,
|
||||
desc => <<EOT
|
||||
# The interdiff feature needs diff, so we have to have that path.
|
||||
# Please specify the directory name only; do not use trailing slash.
|
||||
EOT
|
||||
},
|
||||
{
|
||||
name => 'site_wide_secret',
|
||||
default => sub { generate_random_password(256) },
|
||||
desc => <<EOT
|
||||
# This secret key is used by your installation for the creation and
|
||||
# validation of encrypted tokens to prevent unsolicited changes,
|
||||
# such as bug changes. A random string is generated by default.
|
||||
# It's very important that this key is kept secret. It also must be
|
||||
# very long.
|
||||
EOT
|
||||
},
|
||||
);
|
||||
|
||||
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 @read_symbols;
|
||||
if ($include_deprecated) {
|
||||
# First we have to get the whole symbol table
|
||||
my $safe_root = $s->root;
|
||||
my %safe_package;
|
||||
{ no strict 'refs'; %safe_package = %{$safe_root . "::"}; }
|
||||
# And now we read the contents of every var in the symbol table.
|
||||
# However:
|
||||
# * We only include symbols that start with an alphanumeric
|
||||
# character. This excludes symbols like "_<./localconfig"
|
||||
# that show up in some perls.
|
||||
# * We ignore the INC symbol, which exists in every package.
|
||||
# * Perl 5.10 imports a lot of random symbols that all
|
||||
# contain "::", and we want to ignore those.
|
||||
@read_symbols = grep { /^[A-Za-z0-1]/ and !/^INC$/ and !/::/ }
|
||||
(keys %safe_package);
|
||||
}
|
||||
else {
|
||||
@read_symbols = map($_->{name}, LOCALCONFIG_VARS);
|
||||
}
|
||||
foreach my $var (@read_symbols) {
|
||||
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};
|
||||
}
|
||||
}
|
||||
|
||||
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 @old_vars;
|
||||
foreach my $var (keys %$localconfig) {
|
||||
push(@old_vars, $var) if !grep($_->{name} eq $var, LOCALCONFIG_VARS);
|
||||
}
|
||||
|
||||
my $filename = bz_locations->{'localconfig'};
|
||||
|
||||
# Move any custom or old variables into a separate file.
|
||||
if (scalar @old_vars) {
|
||||
my $filename_old = "$filename.old";
|
||||
open(my $old_file, ">>$filename_old") || die "$filename_old: $!";
|
||||
local $Data::Dumper::Purity = 1;
|
||||
foreach my $var (@old_vars) {
|
||||
print $old_file Data::Dumper->Dump([$localconfig->{$var}],
|
||||
["*$var"]) . "\n\n";
|
||||
}
|
||||
close $old_file;
|
||||
my $oldstuff = join(', ', @old_vars);
|
||||
print <<EOT
|
||||
|
||||
The following variables are no longer used in $filename, and
|
||||
have been moved to $filename_old: $oldstuff
|
||||
|
||||
EOT
|
||||
}
|
||||
|
||||
# Re-write localconfig
|
||||
open(my $fh, ">$filename") || die "$filename: $!";
|
||||
foreach my $var (LOCALCONFIG_VARS) {
|
||||
print $fh "\n", $var->{desc},
|
||||
Data::Dumper->Dump([$localconfig->{$var->{name}}],
|
||||
["*$var->{name}"]);
|
||||
}
|
||||
|
||||
if (@new_vars) {
|
||||
my $newstuff = join(', ', @new_vars);
|
||||
print <<EOT;
|
||||
|
||||
This version of Bugzilla contains some variables that you may want to
|
||||
change and adapt to your local settings. Please edit the file
|
||||
$filename and rerun checksetup.pl.
|
||||
|
||||
The following variables are new to $filename since you last ran
|
||||
checksetup.pl: $newstuff
|
||||
|
||||
EOT
|
||||
exit;
|
||||
}
|
||||
|
||||
# Reset the cache for Bugzilla->localconfig so that it will be re-read
|
||||
delete Bugzilla->request_cache->{localconfig};
|
||||
|
||||
return { old_vars => \@old_vars, new_vars => \@new_vars };
|
||||
}
|
||||
|
||||
sub _get_default_cvsbin { return bin_loc('cvs') }
|
||||
sub _get_default_interdiffbin { return bin_loc('interdiff') }
|
||||
sub _get_default_diffpath {
|
||||
my $diff_bin = bin_loc('diff');
|
||||
return dirname($diff_bin);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install::Localconfig - Functions and variables dealing
|
||||
with the manipulation and creation of the F<localconfig> file.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Install::Requirements qw(update_localconfig);
|
||||
update_localconfig({ output => 1 });
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module is used primarily by L<checksetup.pl> to create and
|
||||
modify the localconfig file. Most scripts should use L<Bugzilla/localconfig>
|
||||
to access localconfig variables.
|
||||
|
||||
=head1 CONSTANTS
|
||||
|
||||
=over
|
||||
|
||||
=item C<LOCALCONFIG_VARS>
|
||||
|
||||
An array of hashrefs. These hashrefs contain three keys:
|
||||
|
||||
name - The name of the variable.
|
||||
default - The default value for the variable. Should always be
|
||||
something that can fit in a scalar.
|
||||
desc - Additional text to put in localconfig before the variable
|
||||
definition. Must end in a newline. Each line should start
|
||||
with "#" unless you have some REALLY good reason not
|
||||
to do that.
|
||||
|
||||
=item C<OLD_LOCALCONFIG_VARS>
|
||||
|
||||
An array of names of variables. If C<update_localconfig> finds these
|
||||
variables defined in localconfig, it will print out a warning.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<read_localconfig>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Reads the localconfig file and returns all valid values in a hashref.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$include_deprecated>
|
||||
|
||||
C<true> if you want the returned hashref to include *any* variable
|
||||
currently defined in localconfig, even if it doesn't exist in
|
||||
C<LOCALCONFIG_VARS>. Generally this is is only for use
|
||||
by L</update_localconfig>.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hashref of the localconfig variables. If an array is defined in
|
||||
localconfig, 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>, unless
|
||||
C<$include_deprecated> is true.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=item C<update_localconfig>
|
||||
|
||||
Description: Adds any new variables to localconfig that aren't
|
||||
currently defined there. Also optionally prints out
|
||||
a message about vars that *should* be there and aren't.
|
||||
Exits the program if it adds any new vars.
|
||||
|
||||
Params: C<$output> - C<true> if the function should display informational
|
||||
output and warnings. It will always display errors or
|
||||
any message which would cause program execution to halt.
|
||||
|
||||
Returns: A hashref, with C<old_vals> being an array of names of variables
|
||||
that were removed, and C<new_vals> being an array of names
|
||||
of variables that were added to localconfig.
|
||||
|
||||
=back
|
||||
@@ -1,682 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::Install::Requirements;
|
||||
|
||||
# NOTE: This package MUST NOT "use" any Bugzilla modules other than
|
||||
# Bugzilla::Constants, anywhere. We may "use" standard perl modules.
|
||||
#
|
||||
# Subroutines may "require" and "import" from modules, but they
|
||||
# MUST NOT "use."
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Install::Util qw(vers_cmp install_string);
|
||||
use List::Util qw(max);
|
||||
use Safe;
|
||||
|
||||
use base qw(Exporter);
|
||||
our @EXPORT = qw(
|
||||
REQUIRED_MODULES
|
||||
OPTIONAL_MODULES
|
||||
|
||||
check_requirements
|
||||
check_graphviz
|
||||
have_vers
|
||||
install_command
|
||||
);
|
||||
|
||||
# This is how many *'s are in the top of each "box" message printed
|
||||
# by checksetup.pl.
|
||||
use constant TABLE_WIDTH => 71;
|
||||
|
||||
# The below two constants are subroutines so that they can implement
|
||||
# a hook. Other than that they are actually constants.
|
||||
|
||||
# "package" is the perl package we're checking for. "module" is the name
|
||||
# of the actual module we load with "require" to see if the package is
|
||||
# installed or not. "version" is the version we need, or 0 if we'll accept
|
||||
# any version.
|
||||
#
|
||||
# "blacklist" is an arrayref of regular expressions that describe versions that
|
||||
# are 'blacklisted'--that is, even if the version is high enough, Bugzilla
|
||||
# will refuse to say that it's OK to run with that version.
|
||||
sub REQUIRED_MODULES {
|
||||
my $perl_ver = sprintf('%vd', $^V);
|
||||
my @modules = (
|
||||
{
|
||||
package => 'CGI.pm',
|
||||
module => 'CGI',
|
||||
# Perl 5.10 requires CGI 3.33 due to a taint issue when
|
||||
# uploading attachments, see bug 416382.
|
||||
# Require CGI 3.21 for -httponly support, see bug 368502.
|
||||
version => (vers_cmp($perl_ver, '5.10') > -1) ? '3.33' : '3.21'
|
||||
},
|
||||
{
|
||||
package => 'Digest-SHA',
|
||||
module => 'Digest::SHA',
|
||||
version => 0
|
||||
},
|
||||
{
|
||||
package => 'TimeDate',
|
||||
module => 'Date::Format',
|
||||
version => '2.21'
|
||||
},
|
||||
# 0.28 fixed some important bugs in DateTime.
|
||||
{
|
||||
package => 'DateTime',
|
||||
module => 'DateTime',
|
||||
version => '0.28'
|
||||
},
|
||||
# 0.79 is required to work on Windows Vista and Windows Server 2008.
|
||||
# As correctly detecting the flavor of Windows is not easy,
|
||||
# we require this version for all Windows installations.
|
||||
# 0.71 fixes a major bug affecting all platforms.
|
||||
{
|
||||
package => 'DateTime-TimeZone',
|
||||
module => 'DateTime::TimeZone',
|
||||
version => ON_WINDOWS ? '0.79' : '0.71'
|
||||
},
|
||||
{
|
||||
package => 'PathTools',
|
||||
module => 'File::Spec',
|
||||
version => '0.84'
|
||||
},
|
||||
{
|
||||
package => 'DBI',
|
||||
module => 'DBI',
|
||||
version => '1.41'
|
||||
},
|
||||
# 2.22 fixes various problems related to UTF8 strings in hash keys,
|
||||
# as well as line endings on Windows.
|
||||
{
|
||||
package => 'Template-Toolkit',
|
||||
module => 'Template',
|
||||
version => '2.22'
|
||||
},
|
||||
{
|
||||
package => 'Email-Send',
|
||||
module => 'Email::Send',
|
||||
version => ON_WINDOWS ? '2.16' : '2.00',
|
||||
blacklist => ['^2\.196$']
|
||||
},
|
||||
{
|
||||
package => 'Email-MIME',
|
||||
module => 'Email::MIME',
|
||||
version => '1.861'
|
||||
},
|
||||
{
|
||||
package => 'Email-MIME-Encodings',
|
||||
module => 'Email::MIME::Encodings',
|
||||
# Fixes bug 486206
|
||||
version => '1.313',
|
||||
},
|
||||
{
|
||||
package => 'Email-MIME-Modifier',
|
||||
module => 'Email::MIME::Modifier',
|
||||
version => '1.442'
|
||||
},
|
||||
{
|
||||
package => 'URI',
|
||||
module => 'URI',
|
||||
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 => 'Chart',
|
||||
module => 'Chart::Base',
|
||||
version => '1.0',
|
||||
feature => 'New Charts, Old Charts'
|
||||
},
|
||||
{
|
||||
package => 'Template-GD',
|
||||
# This module tells us whether or not Template-GD is installed
|
||||
# on Template-Toolkits after 2.14, and still works with 2.14 and lower.
|
||||
module => 'Template::Plugin::GD::Image',
|
||||
version => 0,
|
||||
feature => 'Graphical Reports'
|
||||
},
|
||||
{
|
||||
package => 'GDTextUtil',
|
||||
module => 'GD::Text',
|
||||
version => 0,
|
||||
feature => 'Graphical Reports'
|
||||
},
|
||||
{
|
||||
package => 'GDGraph',
|
||||
module => 'GD::Graph',
|
||||
version => 0,
|
||||
feature => 'Graphical Reports'
|
||||
},
|
||||
{
|
||||
package => 'XML-Twig',
|
||||
module => 'XML::Twig',
|
||||
version => 0,
|
||||
feature => 'Move Bugs Between Installations'
|
||||
},
|
||||
{
|
||||
package => 'MIME-tools',
|
||||
# MIME::Parser is packaged as MIME::Tools on ActiveState Perl
|
||||
module => ON_WINDOWS ? 'MIME::Tools' : 'MIME::Parser',
|
||||
version => '5.406',
|
||||
feature => 'Move Bugs Between Installations'
|
||||
},
|
||||
{
|
||||
package => 'libwww-perl',
|
||||
module => 'LWP::UserAgent',
|
||||
version => 0,
|
||||
feature => 'Automatic Update Notifications'
|
||||
},
|
||||
{
|
||||
package => 'PatchReader',
|
||||
module => 'PatchReader',
|
||||
version => '0.9.4',
|
||||
feature => 'Patch Viewer'
|
||||
},
|
||||
{
|
||||
package => 'PerlMagick',
|
||||
module => 'Image::Magick',
|
||||
version => 0,
|
||||
feature => 'Optionally Convert BMP Attachments to PNGs'
|
||||
},
|
||||
{
|
||||
package => 'perl-ldap',
|
||||
module => 'Net::LDAP',
|
||||
version => 0,
|
||||
feature => 'LDAP Authentication'
|
||||
},
|
||||
{
|
||||
package => 'Authen-SASL',
|
||||
module => 'Authen::SASL',
|
||||
version => 0,
|
||||
feature => 'SMTP Authentication'
|
||||
},
|
||||
{
|
||||
package => 'RadiusPerl',
|
||||
module => 'Authen::Radius',
|
||||
version => 0,
|
||||
feature => 'RADIUS Authentication'
|
||||
},
|
||||
{
|
||||
package => 'SOAP-Lite',
|
||||
module => 'SOAP::Lite',
|
||||
version => 0,
|
||||
# These versions (0.70 -> 0.710.05) are affected by bug 468009
|
||||
blacklist => ['^0\.70', '^0\.710?\.0[1-5]$'],
|
||||
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'
|
||||
},
|
||||
|
||||
# Mail Queueing
|
||||
{
|
||||
package => 'TheSchwartz',
|
||||
module => 'TheSchwartz',
|
||||
version => 0,
|
||||
feature => 'Mail Queueing',
|
||||
},
|
||||
{
|
||||
package => 'Daemon-Generic',
|
||||
module => 'Daemon::Generic',
|
||||
version => 0,
|
||||
feature => 'Mail Queueing',
|
||||
},
|
||||
|
||||
# mod_perl
|
||||
{
|
||||
package => 'mod_perl',
|
||||
module => 'mod_perl2',
|
||||
version => '1.999022',
|
||||
feature => 'mod_perl'
|
||||
},
|
||||
);
|
||||
|
||||
my $all_modules = _get_extension_requirements(
|
||||
'OPTIONAL_MODULES', \@modules);
|
||||
return $all_modules;
|
||||
};
|
||||
|
||||
# This implements the install-requirements hook described in Bugzilla::Hook.
|
||||
sub _get_extension_requirements {
|
||||
my ($function, $base_modules) = @_;
|
||||
my @all_modules;
|
||||
# get a list of all extensions
|
||||
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
|
||||
foreach my $extension (@extensions) {
|
||||
my $file = "$extension/code/install-requirements.pl";
|
||||
if (-e $file) {
|
||||
my $safe = new Safe;
|
||||
# This is a very liberal Safe.
|
||||
$safe->permit(qw(:browse require entereval caller));
|
||||
$safe->rdo($file);
|
||||
if ($@) {
|
||||
warn $@;
|
||||
next;
|
||||
}
|
||||
my $modules = eval { &{$safe->varglob($function)}($base_modules) };
|
||||
next unless $modules;
|
||||
push(@all_modules, @$modules);
|
||||
}
|
||||
}
|
||||
|
||||
unshift(@all_modules, @$base_modules);
|
||||
return \@all_modules;
|
||||
};
|
||||
|
||||
sub check_requirements {
|
||||
my ($output) = @_;
|
||||
|
||||
print "\n", install_string('checking_modules'), "\n" if $output;
|
||||
my $root = ROOT_USER;
|
||||
my $missing = _check_missing(REQUIRED_MODULES, $output);
|
||||
|
||||
print "\n", install_string('checking_dbd'), "\n" if $output;
|
||||
my $have_one_dbd = 0;
|
||||
my $db_modules = DB_MODULE;
|
||||
foreach my $db (keys %$db_modules) {
|
||||
my $dbd = $db_modules->{$db}->{dbd};
|
||||
$have_one_dbd = 1 if have_vers($dbd, $output);
|
||||
}
|
||||
|
||||
print "\n", install_string('checking_optional'), "\n" if $output;
|
||||
my $missing_optional = _check_missing(OPTIONAL_MODULES, $output);
|
||||
|
||||
# If we're running on Windows, reset the input line terminator so that
|
||||
# console input works properly - loading CGI tends to mess it up
|
||||
$/ = "\015\012" if ON_WINDOWS;
|
||||
|
||||
my $pass = !scalar(@$missing) && $have_one_dbd;
|
||||
return {
|
||||
pass => $pass,
|
||||
one_dbd => $have_one_dbd,
|
||||
missing => $missing,
|
||||
optional => $missing_optional,
|
||||
any_missing => !$pass || scalar(@$missing_optional),
|
||||
};
|
||||
}
|
||||
|
||||
# A helper for check_requirements
|
||||
sub _check_missing {
|
||||
my ($modules, $output) = @_;
|
||||
|
||||
my @missing;
|
||||
foreach my $module (@$modules) {
|
||||
unless (have_vers($module, $output)) {
|
||||
push(@missing, $module);
|
||||
}
|
||||
}
|
||||
|
||||
return \@missing;
|
||||
}
|
||||
|
||||
# Returns the build ID of ActivePerl. If several versions of
|
||||
# ActivePerl are installed, it won't be able to know which one
|
||||
# you are currently running. But that's our best guess.
|
||||
sub _get_activestate_build_id {
|
||||
eval 'use Win32::TieRegistry';
|
||||
return 0 if $@;
|
||||
my $key = Win32::TieRegistry->new('LMachine\Software\ActiveState\ActivePerl')
|
||||
or return 0;
|
||||
return $key->GetValue("CurrentVersion");
|
||||
}
|
||||
|
||||
sub print_module_instructions {
|
||||
my ($check_results, $output) = @_;
|
||||
|
||||
# First we print the long explanatory messages.
|
||||
|
||||
if (scalar @{$check_results->{missing}}) {
|
||||
print install_string('modules_message_required');
|
||||
}
|
||||
|
||||
if (!$check_results->{one_dbd}) {
|
||||
print install_string('modules_message_db');
|
||||
}
|
||||
|
||||
if (my @missing = @{$check_results->{optional}} and $output) {
|
||||
print install_string('modules_message_optional');
|
||||
# Now we have to determine how large the table cols will be.
|
||||
my $longest_name = max(map(length($_->{package}), @missing));
|
||||
|
||||
# The first column header is at least 11 characters long.
|
||||
$longest_name = 11 if $longest_name < 11;
|
||||
|
||||
# The table is TABLE_WIDTH characters long. There are seven mandatory
|
||||
# characters (* and space) in the string. So, we have a total
|
||||
# of TABLE_WIDTH - 7 characters to work with.
|
||||
my $remaining_space = (TABLE_WIDTH - 7) - $longest_name;
|
||||
print '*' x TABLE_WIDTH . "\n";
|
||||
printf "* \%${longest_name}s * %-${remaining_space}s *\n",
|
||||
'MODULE NAME', 'ENABLES FEATURE(S)';
|
||||
print '*' x TABLE_WIDTH . "\n";
|
||||
foreach my $package (@missing) {
|
||||
printf "* \%${longest_name}s * %-${remaining_space}s *\n",
|
||||
$package->{package}, $package->{feature};
|
||||
}
|
||||
}
|
||||
|
||||
# We only print the PPM repository note if we have to.
|
||||
if ((!$output && @{$check_results->{missing}})
|
||||
|| ($output && $check_results->{any_missing}))
|
||||
{
|
||||
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';
|
||||
# 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/';
|
||||
}
|
||||
print install_string('ppm_repo_add',
|
||||
{ theory_url => $url_to_theory58S });
|
||||
# ActivePerls older than revision 819 require an additional command.
|
||||
if (_get_activestate_build_id() < 819) {
|
||||
print install_string('ppm_repo_up');
|
||||
}
|
||||
}
|
||||
|
||||
# If any output was required, we want to close the "table"
|
||||
print "*" x TABLE_WIDTH . "\n";
|
||||
}
|
||||
|
||||
# And now we print the actual installation commands.
|
||||
|
||||
if (my @missing = @{$check_results->{optional}} and $output) {
|
||||
print install_string('commands_optional') . "\n\n";
|
||||
foreach my $module (@missing) {
|
||||
my $command = install_command($module);
|
||||
printf "%15s: $command\n", $module->{package};
|
||||
}
|
||||
print "\n";
|
||||
}
|
||||
|
||||
if (!$check_results->{one_dbd}) {
|
||||
print install_string('commands_dbd') . "\n";
|
||||
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 "\n";
|
||||
}
|
||||
|
||||
if (my @missing = @{$check_results->{missing}}) {
|
||||
print install_string('commands_required') . "\n";
|
||||
foreach my $package (@missing) {
|
||||
my $command = install_command($package);
|
||||
print " $command\n";
|
||||
}
|
||||
}
|
||||
|
||||
if ($output && $check_results->{any_missing} && !ON_WINDOWS) {
|
||||
print install_string('install_all', { perl => $^X });
|
||||
}
|
||||
}
|
||||
|
||||
sub check_graphviz {
|
||||
my ($output) = @_;
|
||||
|
||||
return 1 if (Bugzilla->params->{'webdotbase'} =~ /^https?:/);
|
||||
|
||||
printf("Checking for %15s %-9s ", "GraphViz", "(any)") if $output;
|
||||
|
||||
my $return = 0;
|
||||
if(-x Bugzilla->params->{'webdotbase'}) {
|
||||
print "ok: found\n" if $output;
|
||||
$return = 1;
|
||||
} else {
|
||||
print "not a valid executable: " . Bugzilla->params->{'webdotbase'} . "\n";
|
||||
}
|
||||
|
||||
my $webdotdir = bz_locations()->{'webdotdir'};
|
||||
# Check .htaccess allows access to generated images
|
||||
if (-e "$webdotdir/.htaccess") {
|
||||
my $htaccess = new IO::File("$webdotdir/.htaccess", 'r')
|
||||
|| die "$webdotdir/.htaccess: " . $!;
|
||||
if (!grep(/png/, $htaccess->getlines)) {
|
||||
print "Dependency graph images are not accessible.\n";
|
||||
print "delete $webdotdir/.htaccess and re-run checksetup.pl to fix.\n";
|
||||
}
|
||||
$htaccess->close;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
# This was originally clipped from the libnet Makefile.PL, adapted here to
|
||||
# use the below vers_cmp routine for accurate version checking.
|
||||
sub have_vers {
|
||||
my ($params, $output) = @_;
|
||||
my $module = $params->{module};
|
||||
my $package = $params->{package};
|
||||
if (!$package) {
|
||||
$package = $module;
|
||||
$package =~ s/::/-/g;
|
||||
}
|
||||
my $wanted = $params->{version};
|
||||
|
||||
eval "require $module;";
|
||||
|
||||
# VERSION is provided by UNIVERSAL::
|
||||
my $vnum = eval { $module->VERSION } || -1;
|
||||
|
||||
# CGI's versioning scheme went 2.75, 2.751, 2.752, 2.753, 2.76
|
||||
# That breaks the standard version tests, so we need to manually correct
|
||||
# the version
|
||||
if ($module eq 'CGI' && $vnum =~ /(2\.7\d)(\d+)/) {
|
||||
$vnum = $1 . "." . $2;
|
||||
}
|
||||
|
||||
my $vstr;
|
||||
if ($vnum eq "-1") { # string compare just in case it's non-numeric
|
||||
$vstr = install_string('module_not_found');
|
||||
}
|
||||
elsif (vers_cmp($vnum,"0") > -1) {
|
||||
$vstr = install_string('module_found', { ver => $vnum });
|
||||
}
|
||||
else {
|
||||
$vstr = install_string('module_unknown_version');
|
||||
}
|
||||
|
||||
my $vok = (vers_cmp($vnum,$wanted) > -1);
|
||||
my $blacklisted;
|
||||
if ($vok && $params->{blacklist}) {
|
||||
$blacklisted = grep($vnum =~ /$_/, @{$params->{blacklist}});
|
||||
$vok = 0 if $blacklisted;
|
||||
}
|
||||
|
||||
if ($output) {
|
||||
my $ok = $vok ? install_string('module_ok') : '';
|
||||
my $black_string = $blacklisted ? install_string('blacklisted') : '';
|
||||
my $want_string = $wanted ? "v$wanted" : install_string('any');
|
||||
|
||||
$ok = "$ok:" if $ok;
|
||||
printf "%s %19s %-9s $ok $vstr $black_string\n",
|
||||
install_string('checking_for'), $package, "($want_string)";
|
||||
}
|
||||
|
||||
return $vok ? 1 : 0;
|
||||
}
|
||||
|
||||
sub install_command {
|
||||
my $module = shift;
|
||||
my ($command, $package);
|
||||
|
||||
if (ON_WINDOWS) {
|
||||
$command = 'ppm install %s';
|
||||
$package = $module->{package};
|
||||
}
|
||||
else {
|
||||
$command = "$^X install-module.pl \%s";
|
||||
# Non-Windows installations need to use module names, because
|
||||
# CPAN doesn't understand package names.
|
||||
$package = $module->{module};
|
||||
}
|
||||
return sprintf $command, $package;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install::Requirements - Functions and variables dealing
|
||||
with Bugzilla's perl-module requirements.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module is used primarily by C<checksetup.pl> to determine whether
|
||||
or not all of Bugzilla's prerequisites are installed. (That is, all the
|
||||
perl modules it requires.)
|
||||
|
||||
=head1 CONSTANTS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<REQUIRED_MODULES>
|
||||
|
||||
An arrayref of hashrefs that describes the perl modules required by
|
||||
Bugzilla. The hashes have two keys, C<name> and C<version>, which
|
||||
represent the name of the module and the version that we require.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<check_requirements>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This checks what optional or required perl modules are installed, like
|
||||
C<checksetup.pl> does.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$output> - C<true> if you want the function to print out information
|
||||
about what it's doing, and the versions of everything installed.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hashref containing these values:
|
||||
|
||||
=over
|
||||
|
||||
=item C<pass> - Whether or not we have all the mandatory requirements.
|
||||
|
||||
=item C<missing> - An arrayref containing any required modules that
|
||||
are not installed or that are not up-to-date. Each item in the array is
|
||||
a hashref in the format of items from L</REQUIRED_MODULES>.
|
||||
|
||||
=item C<optional> - The same as C<missing>, but for optional modules.
|
||||
|
||||
=item C<have_one_dbd> - True if at least one C<DBD::> module is installed.
|
||||
|
||||
=item C<any_missing> - True if there are any missing modules, even optional
|
||||
modules.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<check_graphviz($output)>
|
||||
|
||||
Description: Checks if the graphviz binary specified in the
|
||||
C<webdotbase> parameter is a valid binary, or a valid URL.
|
||||
|
||||
Params: C<$output> - C<$true> if you want the function to
|
||||
print out information about what it's doing.
|
||||
|
||||
Returns: C<1> if the check was successful, C<0> otherwise.
|
||||
|
||||
=item C<have_vers($module, $output)>
|
||||
|
||||
Description: Tells you whether or not you have the appropriate
|
||||
version of the module requested. It also prints
|
||||
out a message to the user explaining the check
|
||||
and the result.
|
||||
|
||||
Params: C<$module> - A hashref, in the format of an item from
|
||||
L</REQUIRED_MODULES>.
|
||||
C<$output> - Set to true if you want this function to
|
||||
print information to STDOUT about what it's
|
||||
doing.
|
||||
|
||||
Returns: C<1> if you have the module installed and you have the
|
||||
appropriate version. C<0> otherwise.
|
||||
|
||||
=item C<install_command($module)>
|
||||
|
||||
Description: Prints out the appropriate command to install the
|
||||
module specified, depending on whether you're
|
||||
on Windows or Linux.
|
||||
|
||||
Params: C<$module> - A hashref, in the format of an item from
|
||||
L</REQUIRED_MODULES>.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
@@ -1,550 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Everything Solved.
|
||||
# Portions created by Everything Solved are Copyright (C) 2006
|
||||
# Everything Solved. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Install::Util;
|
||||
|
||||
# The difference between this module and Bugzilla::Util is that this
|
||||
# module may require *only* Bugzilla::Constants and built-in
|
||||
# perl modules.
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
|
||||
use File::Basename;
|
||||
use POSIX qw(setlocale LC_CTYPE);
|
||||
use Safe;
|
||||
use Scalar::Util qw(tainted);
|
||||
|
||||
use base qw(Exporter);
|
||||
our @EXPORT_OK = qw(
|
||||
bin_loc
|
||||
get_version_and_os
|
||||
indicate_progress
|
||||
install_string
|
||||
include_languages
|
||||
template_include_path
|
||||
vers_cmp
|
||||
get_console_locale
|
||||
);
|
||||
|
||||
sub bin_loc {
|
||||
my ($bin) = @_;
|
||||
return '' if ON_WINDOWS;
|
||||
# Don't print any errors from "which"
|
||||
open(my $saveerr, ">&STDERR");
|
||||
open(STDERR, '>/dev/null');
|
||||
my $loc = `which $bin`;
|
||||
close(STDERR);
|
||||
open(STDERR, ">&", $saveerr);
|
||||
my $exit_code = $? >> 8; # See the perlvar manpage.
|
||||
return '' if $exit_code > 0;
|
||||
chomp($loc);
|
||||
return $loc;
|
||||
}
|
||||
|
||||
sub get_version_and_os {
|
||||
# Display version information
|
||||
my @os_details = POSIX::uname;
|
||||
# 0 is the name of the OS, 2 is the major version,
|
||||
my $os_name = $os_details[0] . ' ' . $os_details[2];
|
||||
if (ON_WINDOWS) {
|
||||
require Win32;
|
||||
$os_name = Win32::GetOSName();
|
||||
}
|
||||
# $os_details[3] is the minor version.
|
||||
return { bz_ver => BUGZILLA_VERSION,
|
||||
perl_ver => sprintf('%vd', $^V),
|
||||
os_name => $os_name,
|
||||
os_ver => $os_details[3] };
|
||||
}
|
||||
|
||||
sub indicate_progress {
|
||||
my ($params) = @_;
|
||||
my $current = $params->{current};
|
||||
my $total = $params->{total};
|
||||
my $every = $params->{every} || 1;
|
||||
|
||||
print "." if !($current % $every);
|
||||
if ($current == $total || $current % ($every * 60) == 0) {
|
||||
print "$current/$total (" . int($current * 100 / $total) . "%)\n";
|
||||
}
|
||||
}
|
||||
|
||||
sub install_string {
|
||||
my ($string_id, $vars) = @_;
|
||||
_cache()->{template_include_path} ||= template_include_path();
|
||||
my $path = _cache()->{template_include_path};
|
||||
|
||||
my $string_template;
|
||||
# Find the first template that defines this string.
|
||||
foreach my $dir (@$path) {
|
||||
my $base = "$dir/setup/strings";
|
||||
$string_template = _get_string_from_file($string_id, "$base.txt.pl")
|
||||
if !defined $string_template;
|
||||
last if defined $string_template;
|
||||
}
|
||||
|
||||
die "No language defines the string '$string_id'"
|
||||
if !defined $string_template;
|
||||
|
||||
$vars ||= {};
|
||||
my @replace_keys = keys %$vars;
|
||||
foreach my $key (@replace_keys) {
|
||||
my $replacement = $vars->{$key};
|
||||
die "'$key' in '$string_id' is tainted: '$replacement'"
|
||||
if tainted($replacement);
|
||||
# We don't want people to start getting clever and inserting
|
||||
# ##variable## into their values. So we check if any other
|
||||
# key is listed in the *replacement* string, before doing
|
||||
# the replacement. This is mostly to protect programmers from
|
||||
# making mistakes.
|
||||
if (grep($replacement =~ /##$key##/, @replace_keys)) {
|
||||
die "Unsafe replacement for '$key' in '$string_id': '$replacement'";
|
||||
}
|
||||
$string_template =~ s/\Q##$key##\E/$replacement/g;
|
||||
}
|
||||
|
||||
return $string_template;
|
||||
}
|
||||
|
||||
sub include_languages {
|
||||
my ($params) = @_;
|
||||
$params ||= {};
|
||||
|
||||
# Basically, the way this works is that we have a list of languages
|
||||
# that we *want*, and a list of languages that Bugzilla actually
|
||||
# supports. The caller tells us what languages they want, by setting
|
||||
# $ENV{HTTP_ACCEPT_LANGUAGE} or $params->{only_language}. The languages
|
||||
# we support are those specified in $params->{use_languages}. Otherwise
|
||||
# we support every language installed in the template/ directory.
|
||||
|
||||
my @wanted;
|
||||
if ($params->{only_language}) {
|
||||
@wanted = ($params->{only_language});
|
||||
}
|
||||
else {
|
||||
@wanted = _sort_accept_language($ENV{'HTTP_ACCEPT_LANGUAGE'} || '');
|
||||
}
|
||||
|
||||
my @supported;
|
||||
if (defined $params->{use_languages}) {
|
||||
@supported = @{$params->{use_languages}};
|
||||
}
|
||||
else {
|
||||
my @dirs = glob(bz_locations()->{'templatedir'} . "/*");
|
||||
@dirs = map(basename($_), @dirs);
|
||||
@supported = grep($_ ne 'CVS', @dirs);
|
||||
}
|
||||
|
||||
my @usedlanguages;
|
||||
foreach my $wanted (@wanted) {
|
||||
# If we support the language we want, or *any version* of
|
||||
# the language we want, it gets pushed into @usedlanguages.
|
||||
#
|
||||
# Per RFC 1766 and RFC 2616, things like 'en' match 'en-us' and
|
||||
# 'en-uk', but not the other way around. (This is unfortunately
|
||||
# not very clearly stated in those RFC; see comment just over 14.5
|
||||
# in http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4)
|
||||
if(my @found = grep /^\Q$wanted\E(-.+)?$/i, @supported) {
|
||||
push (@usedlanguages, @found);
|
||||
}
|
||||
}
|
||||
|
||||
# We always include English at the bottom if it's not there, even if
|
||||
# somebody removed it from use_languages.
|
||||
if (!grep($_ eq 'en', @usedlanguages)) {
|
||||
push(@usedlanguages, 'en');
|
||||
}
|
||||
|
||||
return @usedlanguages;
|
||||
}
|
||||
|
||||
sub template_include_path {
|
||||
my @usedlanguages = include_languages(@_);
|
||||
# Now, we add template directories in the order they will be searched:
|
||||
|
||||
# First, we add extension template directories, because extension templates
|
||||
# override standard templates. Extensions may be localized in the same way
|
||||
# that Bugzilla templates are localized.
|
||||
my @include_path;
|
||||
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
|
||||
foreach my $extension (@extensions) {
|
||||
foreach my $lang (@usedlanguages) {
|
||||
_add_language_set(\@include_path, $lang, "$extension/template");
|
||||
}
|
||||
}
|
||||
|
||||
# Then, we add normal template directories, sorted by language.
|
||||
foreach my $lang (@usedlanguages) {
|
||||
_add_language_set(\@include_path, $lang);
|
||||
}
|
||||
|
||||
return \@include_path;
|
||||
}
|
||||
|
||||
# This is taken straight from Sort::Versions 1.5, which is not included
|
||||
# with perl by default.
|
||||
sub vers_cmp {
|
||||
my ($a, $b) = @_;
|
||||
|
||||
# Remove leading zeroes - Bug 344661
|
||||
$a =~ s/^0*(\d.+)/$1/;
|
||||
$b =~ s/^0*(\d.+)/$1/;
|
||||
|
||||
my @A = ($a =~ /([-.]|\d+|[^-.\d]+)/g);
|
||||
my @B = ($b =~ /([-.]|\d+|[^-.\d]+)/g);
|
||||
|
||||
my ($A, $B);
|
||||
while (@A and @B) {
|
||||
$A = shift @A;
|
||||
$B = shift @B;
|
||||
if ($A eq '-' and $B eq '-') {
|
||||
next;
|
||||
} elsif ( $A eq '-' ) {
|
||||
return -1;
|
||||
} elsif ( $B eq '-') {
|
||||
return 1;
|
||||
} elsif ($A eq '.' and $B eq '.') {
|
||||
next;
|
||||
} elsif ( $A eq '.' ) {
|
||||
return -1;
|
||||
} elsif ( $B eq '.' ) {
|
||||
return 1;
|
||||
} elsif ($A =~ /^\d+$/ and $B =~ /^\d+$/) {
|
||||
if ($A =~ /^0/ || $B =~ /^0/) {
|
||||
return $A cmp $B if $A cmp $B;
|
||||
} else {
|
||||
return $A <=> $B if $A <=> $B;
|
||||
}
|
||||
} else {
|
||||
$A = uc $A;
|
||||
$B = uc $B;
|
||||
return $A cmp $B if $A cmp $B;
|
||||
}
|
||||
}
|
||||
@A <=> @B;
|
||||
}
|
||||
|
||||
######################
|
||||
# Helper Subroutines #
|
||||
######################
|
||||
|
||||
# Used by install_string
|
||||
sub _get_string_from_file {
|
||||
my ($string_id, $file) = @_;
|
||||
|
||||
return undef if !-e $file;
|
||||
my $safe = new Safe;
|
||||
$safe->rdo($file);
|
||||
my %strings = %{$safe->varglob('strings')};
|
||||
return $strings{$string_id};
|
||||
}
|
||||
|
||||
# Used by template_include_path.
|
||||
sub _add_language_set {
|
||||
my ($array, $lang, $templatedir) = @_;
|
||||
|
||||
$templatedir ||= bz_locations()->{'templatedir'};
|
||||
my @add = ("$templatedir/$lang/custom", "$templatedir/$lang/default");
|
||||
|
||||
my $project = bz_locations->{'project'};
|
||||
unshift(@add, "$templatedir/$lang/$project") if $project;
|
||||
|
||||
foreach my $dir (@add) {
|
||||
if (-d $dir) {
|
||||
trick_taint($dir);
|
||||
push(@$array, $dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Make an ordered list out of a HTTP Accept-Language header (see RFC 2616, 14.4)
|
||||
# We ignore '*' and <language-range>;q=0
|
||||
# For languages with the same priority q the order remains unchanged.
|
||||
sub _sort_accept_language {
|
||||
sub sortQvalue { $b->{'qvalue'} <=> $a->{'qvalue'} }
|
||||
my $accept_language = $_[0];
|
||||
|
||||
# clean up string.
|
||||
$accept_language =~ s/[^A-Za-z;q=0-9\.\-,]//g;
|
||||
my @qlanguages;
|
||||
my @languages;
|
||||
foreach(split /,/, $accept_language) {
|
||||
if (m/([A-Za-z\-]+)(?:;q=(\d(?:\.\d+)))?/) {
|
||||
my $lang = $1;
|
||||
my $qvalue = $2;
|
||||
$qvalue = 1 if not defined $qvalue;
|
||||
next if $qvalue == 0;
|
||||
$qvalue = 1 if $qvalue > 1;
|
||||
push(@qlanguages, {'qvalue' => $qvalue, 'language' => $lang});
|
||||
}
|
||||
}
|
||||
|
||||
return map($_->{'language'}, (sort sortQvalue @qlanguages));
|
||||
}
|
||||
|
||||
sub get_console_locale {
|
||||
require Locale::Language;
|
||||
my $locale = setlocale(LC_CTYPE);
|
||||
my $language;
|
||||
# Some distros set e.g. LC_CTYPE = fr_CH.UTF-8. We clean it up.
|
||||
if ($locale =~ /^([^\.]+)/) {
|
||||
$locale = $1;
|
||||
}
|
||||
$locale =~ s/_/-/;
|
||||
# It's pretty sure that there is no language pack of the form fr-CH
|
||||
# installed, so we also include fr as a wanted language.
|
||||
if ($locale =~ /^(\S+)\-/) {
|
||||
$language = $1;
|
||||
$locale .= ",$language";
|
||||
}
|
||||
else {
|
||||
$language = $locale;
|
||||
}
|
||||
|
||||
# Some OSs or distributions may have setlocale return a string of the form
|
||||
# German_Germany.1252 (this example taken from a Windows XP system), which
|
||||
# is unsuitable for our needs because Bugzilla works on language codes.
|
||||
# We try and convert them here.
|
||||
if ($language = Locale::Language::language2code($language)) {
|
||||
$locale .= ",$language";
|
||||
}
|
||||
|
||||
return $locale;
|
||||
}
|
||||
|
||||
|
||||
# This is like request_cache, but it's used only by installation code
|
||||
# for setup.cgi and things like that.
|
||||
our $_cache = {};
|
||||
sub _cache {
|
||||
if ($ENV{MOD_PERL}) {
|
||||
require Apache2::RequestUtil;
|
||||
return Apache2::RequestUtil->request->pnotes();
|
||||
}
|
||||
return $_cache;
|
||||
}
|
||||
|
||||
###############################
|
||||
# Copied from Bugzilla::Util #
|
||||
##############################
|
||||
|
||||
sub trick_taint {
|
||||
require Carp;
|
||||
Carp::confess("Undef to trick_taint") unless defined $_[0];
|
||||
my $match = $_[0] =~ /^(.*)$/s;
|
||||
$_[0] = $match ? $1 : undef;
|
||||
return (defined($_[0]));
|
||||
}
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Install::Util - Utility functions that are useful both during
|
||||
installation and afterwards.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains various subroutines that are used primarily
|
||||
during installation. However, these subroutines can also be useful to
|
||||
non-installation code, so they have been split out into this module.
|
||||
|
||||
The difference between this module and L<Bugzilla::Util> is that this
|
||||
module is safe to C<use> anywhere in Bugzilla, even during installation,
|
||||
because it depends only on L<Bugzilla::Constants> and built-in perl modules.
|
||||
|
||||
None of the subroutines are exported by default--you must explicitly
|
||||
export them.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<bin_loc>
|
||||
|
||||
On *nix systems, given the name of a binary, returns the path to that
|
||||
binary, if the binary is in the C<PATH>.
|
||||
|
||||
=item C<get_version_and_os>
|
||||
|
||||
Returns a hash containing information about what version of Bugzilla we're
|
||||
running, what perl version we're using, and what OS we're running on.
|
||||
|
||||
=item C<get_console_locale>
|
||||
|
||||
Returns the language to use based on the LC_CTYPE value returned by the OS.
|
||||
If LC_CTYPE is of the form fr-CH, then fr is appended to the list.
|
||||
|
||||
=item C<indicate_progress>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This prints out lines of dots as a long update is going on, to let the user
|
||||
know where we are and that we're not frozen. A new line of dots will start
|
||||
every 60 dots.
|
||||
|
||||
Sample usage: C<indicate_progress({ total =E<gt> $total, current =E<gt>
|
||||
$count, every =E<gt> 1 })>
|
||||
|
||||
=item B<Sample Output>
|
||||
|
||||
Here's some sample output with C<total = 1000> and C<every = 10>:
|
||||
|
||||
............................................................600/1000 (60%)
|
||||
........................................
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<total> - The total number of items we're processing.
|
||||
|
||||
=item C<current> - The number of the current item we're processing.
|
||||
|
||||
=item C<every> - How often the function should print out a dot.
|
||||
For example, if this is 10, the function will print out a dot every
|
||||
ten items. Defaults to 1 if not specified.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>: nothing
|
||||
|
||||
=back
|
||||
|
||||
=item C<install_string>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This is a very simple method of templating strings for installation.
|
||||
It should only be used by code that has to run before the Template Toolkit
|
||||
can be used. (See the comments at the top of the various L<Bugzilla::Install>
|
||||
modules to find out when it's safe to use Template Toolkit.)
|
||||
|
||||
It pulls strings out of the F<strings.txt.pl> "template" and replaces
|
||||
any variable surrounded by double-hashes (##) with a value you specify.
|
||||
|
||||
This allows for localization of strings used during installation.
|
||||
|
||||
=item B<Example>
|
||||
|
||||
Let's say your template string looks like this:
|
||||
|
||||
The ##animal## jumped over the ##plant##.
|
||||
|
||||
Let's say that string is called 'animal_jump_plant'. So you call the function
|
||||
like this:
|
||||
|
||||
install_string('animal_jump_plant', { animal => 'fox', plant => 'tree' });
|
||||
|
||||
That will output this:
|
||||
|
||||
The fox jumped over the tree.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$string_id> - The name of the string from F<strings.txt.pl>.
|
||||
|
||||
=item C<$vars> - A hashref containing the replacement values for variables
|
||||
inside of the string.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>: The appropriate string, with variables replaced.
|
||||
|
||||
=back
|
||||
|
||||
=item C<template_include_path>
|
||||
|
||||
Used by L<Bugzilla::Template> and L</install_string> to determine the
|
||||
directories where templates are installed. Templates can be installed
|
||||
in many places. They're listed here in the basic order that they're
|
||||
searched:
|
||||
|
||||
=over
|
||||
|
||||
=item extensions/C<$extension>/template/C<$language>/C<$project>
|
||||
|
||||
=item extensions/C<$extension>/template/C<$language>/custom
|
||||
|
||||
=item extensions/C<$extension>/template/C<$language>/default
|
||||
|
||||
=item template/C<$language>/C<$project>
|
||||
|
||||
=item template/C<$language>/custom
|
||||
|
||||
=item template/C<$language>/default
|
||||
|
||||
=back
|
||||
|
||||
C<$project> has to do with installations that are using the C<$ENV{PROJECT}>
|
||||
variable to have different "views" on a single Bugzilla.
|
||||
|
||||
The F<default> directory includes templates shipped with Bugzilla.
|
||||
|
||||
The F<custom> directory is a directory for local installations to override
|
||||
the F<default> templates. Any individual template in F<custom> will
|
||||
override a template of the same name and path in F<default>.
|
||||
|
||||
C<$language> is a language code, C<en> being the default language shipped
|
||||
with Bugzilla. Localizers ship other languages.
|
||||
|
||||
C<$extension> is the name of any directory in the F<extensions/> directory.
|
||||
Each extension has its own directory.
|
||||
|
||||
Note that languages are sorted by the user's preference (as specified
|
||||
in their browser, usually), and extensions are sorted alphabetically.
|
||||
|
||||
=item C<include_languages>
|
||||
|
||||
Used by L<Bugzilla::Template> to determine the languages' list which
|
||||
are compiled with the browser's I<Accept-Language> and the languages
|
||||
of installed templates.
|
||||
|
||||
=item C<vers_cmp>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This is a comparison function, like you would use in C<sort>, except that
|
||||
it compares two version numbers. So, for example, 2.10 would be greater
|
||||
than 2.2.
|
||||
|
||||
It's based on versioncmp from L<Sort::Versions>, with some Bugzilla-specific
|
||||
fixes.
|
||||
|
||||
=item B<Params>: C<$a> and C<$b> - The versions you want to compare.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
C<-1> if C<$a> is less than C<$b>, C<0> if they are equal, or C<1> if C<$a>
|
||||
is greater than C<$b>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
@@ -1,57 +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 Mozilla Corporation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2008
|
||||
# Mozilla Corporation. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Mark Smith <mark@mozilla.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Job::Mailer;
|
||||
use strict;
|
||||
use Bugzilla::Mailer;
|
||||
BEGIN { eval "use base qw(TheSchwartz::Worker)"; }
|
||||
|
||||
# The longest we expect a job to possibly take, in seconds.
|
||||
use constant grab_for => 300;
|
||||
# We don't want email to fail permanently very easily. Retry for 30 days.
|
||||
use constant max_retries => 725;
|
||||
|
||||
# The first few retries happen quickly, but after that we wait an hour for
|
||||
# each retry.
|
||||
sub retry_delay {
|
||||
my $num_retries = shift;
|
||||
if ($num_retries < 5) {
|
||||
return (10, 30, 60, 300, 600)[$num_retries];
|
||||
}
|
||||
# One hour
|
||||
return 60*60;
|
||||
}
|
||||
|
||||
sub work {
|
||||
my ($class, $job) = @_;
|
||||
my $msg = $job->arg->{msg};
|
||||
my $success = eval { MessageToMTA($msg, 1); 1; };
|
||||
if (!$success) {
|
||||
$job->failed($@);
|
||||
undef $@;
|
||||
}
|
||||
else {
|
||||
$job->completed;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,108 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Mozilla Corporation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2008
|
||||
# Mozilla Corporation. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Mark Smith <mark@mozilla.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::JobQueue;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Install::Util qw(install_string);
|
||||
BEGIN { eval "use base qw(TheSchwartz)"; }
|
||||
|
||||
# This maps job names for Bugzilla::JobQueue to the appropriate modules.
|
||||
# If you add new types of jobs, you should add a mapping here.
|
||||
use constant JOB_MAP => {
|
||||
send_mail => 'Bugzilla::Job::Mailer',
|
||||
};
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
|
||||
if (!eval { require TheSchwartz; }) {
|
||||
ThrowCodeError('jobqueue_not_configured');
|
||||
}
|
||||
|
||||
my $lc = Bugzilla->localconfig;
|
||||
my $self = $class->SUPER::new(
|
||||
databases => [{
|
||||
dsn => Bugzilla->dbh->{private_bz_dsn},
|
||||
user => $lc->{db_user},
|
||||
pass => $lc->{db_pass},
|
||||
prefix => 'ts_',
|
||||
}],
|
||||
);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
# A way to get access to the underlying databases directly.
|
||||
sub bz_databases {
|
||||
my $self = shift;
|
||||
my @hashes = keys %{ $self->{databases} };
|
||||
return map { $self->driver_for($_) } @hashes;
|
||||
}
|
||||
|
||||
# inserts a job into the queue to be processed and returns immediately
|
||||
sub insert {
|
||||
my $self = shift;
|
||||
my $job = shift;
|
||||
|
||||
my $mapped_job = JOB_MAP->{$job};
|
||||
ThrowCodeError('jobqueue_no_job_mapping', { job => $job })
|
||||
if !$mapped_job;
|
||||
unshift(@_, $mapped_job);
|
||||
|
||||
my $retval = $self->SUPER::insert(@_);
|
||||
# XXX Need to get an error message here if insert fails, but
|
||||
# I don't see any way to do that in TheSchwartz.
|
||||
ThrowCodeError('jobqueue_insert_failed', { job => $job, errmsg => $@ })
|
||||
if !$retval;
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::JobQueue - Interface between Bugzilla and TheSchwartz.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla;
|
||||
|
||||
my $obj = Bugzilla->job_queue();
|
||||
$obj->insert('send_mail', { msg => $message });
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Certain tasks should be done asyncronously. The job queue system allows
|
||||
Bugzilla to use some sort of service to schedule jobs to happen asyncronously.
|
||||
|
||||
=head2 Inserting a Job
|
||||
|
||||
See the synopsis above for an easy to follow example on how to insert a
|
||||
job into the queue. Give it a name and some arguments and the job will
|
||||
be sent away to be done later.
|
||||
@@ -1,99 +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 Mozilla Corporation.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2008
|
||||
# Mozilla Corporation. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Mark Smith <mark@mozilla.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
# XXX In order to support Windows, we have to make gd_redirect_output
|
||||
# use Log4Perl or something instead of calling "logger". We probably
|
||||
# also need to use Win32::Daemon or something like that to daemonize.
|
||||
|
||||
package Bugzilla::JobQueue::Runner;
|
||||
|
||||
use strict;
|
||||
use File::Basename;
|
||||
use Pod::Usage;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::JobQueue;
|
||||
use Bugzilla::Util qw(get_text);
|
||||
BEGIN { eval "use base qw(Daemon::Generic)"; }
|
||||
|
||||
# Required because of a bug in Daemon::Generic where it won't use the
|
||||
# "version" key from DAEMON_CONFIG.
|
||||
our $VERSION = BUGZILLA_VERSION;
|
||||
|
||||
use constant DAEMON_CONFIG => (
|
||||
progname => basename($0),
|
||||
pidfile => bz_locations()->{datadir} . '/' . basename($0) . '.pid',
|
||||
version => BUGZILLA_VERSION,
|
||||
);
|
||||
|
||||
sub gd_preconfig {
|
||||
return DAEMON_CONFIG;
|
||||
}
|
||||
|
||||
sub gd_usage {
|
||||
pod2usage({ -verbose => 0, -exitval => 'NOEXIT' });
|
||||
return 0
|
||||
}
|
||||
|
||||
sub gd_check {
|
||||
my $self = shift;
|
||||
|
||||
# Get a count of all the jobs currently in the queue.
|
||||
my $jq = Bugzilla->job_queue();
|
||||
my @dbs = $jq->bz_databases();
|
||||
my $count = 0;
|
||||
foreach my $driver (@dbs) {
|
||||
$count += $driver->select_one('SELECT COUNT(*) FROM ts_job', []);
|
||||
}
|
||||
print get_text('job_queue_depth', { count => $count }) . "\n";
|
||||
}
|
||||
|
||||
sub gd_run {
|
||||
my $self = shift;
|
||||
|
||||
my $jq = Bugzilla->job_queue();
|
||||
$jq->set_verbose($self->{debug});
|
||||
foreach my $module (values %{ Bugzilla::JobQueue::JOB_MAP() }) {
|
||||
eval "use $module";
|
||||
$jq->can_do($module);
|
||||
}
|
||||
$jq->work;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::JobQueue::Runner - A class representing the daemon that runs the
|
||||
job queue.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::JobQueue::Runner;
|
||||
Bugzilla::JobQueue::Runner->new();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is a subclass of L<Daemon::Generic> that is used by L<jobqueue>
|
||||
to run the Bugzilla job queue.
|
||||
@@ -1,189 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Keyword;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
keyworddefs.id
|
||||
keyworddefs.name
|
||||
keyworddefs.description
|
||||
);
|
||||
|
||||
use constant DB_TABLE => 'keyworddefs';
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(name description);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
name => \&_check_name,
|
||||
description => \&_check_description,
|
||||
};
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
name
|
||||
description
|
||||
);
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
sub description { return $_[0]->{'description'}; }
|
||||
|
||||
sub bug_count {
|
||||
my ($self) = @_;
|
||||
return $self->{'bug_count'} if defined $self->{'bug_count'};
|
||||
($self->{'bug_count'}) =
|
||||
Bugzilla->dbh->selectrow_array(
|
||||
'SELECT COUNT(*) FROM keywords WHERE keywordid = ?',
|
||||
undef, $self->id);
|
||||
return $self->{'bug_count'};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Mutators #####
|
||||
###############################
|
||||
|
||||
sub set_name { $_[0]->set('name', $_[1]); }
|
||||
sub set_description { $_[0]->set('description', $_[1]); }
|
||||
|
||||
###############################
|
||||
#### Subroutines ######
|
||||
###############################
|
||||
|
||||
sub keyword_count {
|
||||
my ($count) =
|
||||
Bugzilla->dbh->selectrow_array('SELECT COUNT(*) FROM keyworddefs');
|
||||
return $count;
|
||||
}
|
||||
|
||||
sub get_all_with_bug_count {
|
||||
my $class = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $keywords =
|
||||
$dbh->selectall_arrayref('SELECT ' . join(', ', DB_COLUMNS) . ',
|
||||
COUNT(keywords.bug_id) AS bug_count
|
||||
FROM keyworddefs
|
||||
LEFT JOIN keywords
|
||||
ON keyworddefs.id = keywords.keywordid ' .
|
||||
$dbh->sql_group_by('keyworddefs.id',
|
||||
'keyworddefs.name,
|
||||
keyworddefs.description') . '
|
||||
ORDER BY keyworddefs.name', {'Slice' => {}});
|
||||
if (!$keywords) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach my $keyword (@$keywords) {
|
||||
bless($keyword, $class);
|
||||
}
|
||||
return $keywords;
|
||||
}
|
||||
|
||||
###############################
|
||||
### Validators ###
|
||||
###############################
|
||||
|
||||
sub _check_name {
|
||||
my ($self, $name) = @_;
|
||||
|
||||
$name = trim($name);
|
||||
$name eq "" && ThrowUserError("keyword_blank_name");
|
||||
if ($name =~ /[\s,]/) {
|
||||
ThrowUserError("keyword_invalid_name");
|
||||
}
|
||||
|
||||
# We only want to validate the non-existence of the name if
|
||||
# we're creating a new Keyword or actually renaming the keyword.
|
||||
if (!ref($self) || $self->name ne $name) {
|
||||
my $keyword = new Bugzilla::Keyword({ name => $name });
|
||||
ThrowUserError("keyword_already_exists", { name => $name }) if $keyword;
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_description {
|
||||
my ($self, $desc) = @_;
|
||||
$desc = trim($desc);
|
||||
$desc eq '' && ThrowUserError("keyword_blank_description");
|
||||
return $desc;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Keyword - A Keyword that can be added to a bug.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Keyword;
|
||||
|
||||
my $count = Bugzilla::Keyword::keyword_count;
|
||||
|
||||
my $description = $keyword->description;
|
||||
|
||||
my $keywords = Bugzilla::Keyword->get_all_with_bug_count();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla::Keyword represents a keyword that can be added to a bug.
|
||||
|
||||
This implements all standard C<Bugzilla::Object> methods. See
|
||||
L<Bugzilla::Object> for more details.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
This is only a list of subroutines specific to C<Bugzilla::Keyword>.
|
||||
See L<Bugzilla::Object> for more subroutines that this object
|
||||
implements.
|
||||
|
||||
=over
|
||||
|
||||
=item C<keyword_count()>
|
||||
|
||||
Description: A utility function to get the total number
|
||||
of keywords defined. Mostly used to see
|
||||
if there are any keywords defined at all.
|
||||
Params: none
|
||||
Returns: An integer, the count of keywords.
|
||||
|
||||
=item C<get_all_with_bug_count()>
|
||||
|
||||
Description: Returns all defined keywords. This is an efficient way
|
||||
to get the associated bug counts, as only one SQL query
|
||||
is executed with this method, instead of one per keyword
|
||||
when calling get_all and then bug_count.
|
||||
Params: none
|
||||
Returns: A reference to an array of Keyword objects, or an empty
|
||||
arrayref if there are no keywords.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,224 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>,
|
||||
# Bryce Nesbitt <bryce-mozilla@nextbus.com>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Alan Raetz <al_raetz@yahoo.com>
|
||||
# Jacob Steenhagen <jake@actex.net>
|
||||
# Matthew Tuck <matty@chariot.net.au>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# J. Paul Reed <preed@sigkill.com>
|
||||
# Gervase Markham <gerv@gerv.net>
|
||||
# Byron Jones <bugzilla@glob.com.au>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::Mailer;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Exporter);
|
||||
@Bugzilla::Mailer::EXPORT = qw(MessageToMTA build_thread_marker);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Hook;
|
||||
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, $send_now) = (@_);
|
||||
my $method = Bugzilla->params->{'mail_delivery_method'};
|
||||
return if $method eq 'None';
|
||||
|
||||
if (Bugzilla->params->{'use_mailer_queue'} and !$send_now) {
|
||||
Bugzilla->job_queue->insert('send_mail', { msg => $msg });
|
||||
return;
|
||||
}
|
||||
|
||||
my $email;
|
||||
if (ref $msg) {
|
||||
$email = $msg;
|
||||
}
|
||||
else {
|
||||
# RFC 2822 requires us to have CRLF for our line endings and
|
||||
# Email::MIME doesn't do this for us. We use \015 (CR) and \012 (LF)
|
||||
# directly because Perl translates "\n" depending on what platform
|
||||
# you're running on. See http://perldoc.perl.org/perlport.html#Newlines
|
||||
# We check for multiple CRs because of this Template-Toolkit bug:
|
||||
# https://rt.cpan.org/Ticket/Display.html?id=43345
|
||||
$msg =~ s/(?:\015+)?\012/\015\012/msg;
|
||||
$email = new Email::MIME($msg);
|
||||
}
|
||||
|
||||
# We add this header to uniquely identify all email that we
|
||||
# send as coming from this Bugzilla installation.
|
||||
#
|
||||
# We don't use correct_urlbase, because we want this URL to
|
||||
# *always* be the same for this Bugzilla, in every email,
|
||||
# and some emails we send when we're logged out (in which case
|
||||
# some emails might get urlbase while the logged-in emails might
|
||||
# get sslbase). Also, we want this to stay the same even if
|
||||
# the admin changes the "ssl" parameter.
|
||||
$email->header_set('X-Bugzilla-URL', Bugzilla->params->{'urlbase'});
|
||||
|
||||
# We add this header to mark the mail as "auto-generated" and
|
||||
# thus to hopefully avoid auto replies.
|
||||
$email->header_set('Auto-Submitted', 'auto-generated');
|
||||
|
||||
$email->walk_parts(sub {
|
||||
my ($part) = @_;
|
||||
return if $part->parts > 1; # Top-level
|
||||
my $content_type = $part->content_type || '';
|
||||
if ($content_type !~ /;/) {
|
||||
my $body = $part->body;
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
$part->charset_set('UTF-8');
|
||||
# encoding_set works only with bytes, not with utf8 strings.
|
||||
my $raw = $part->body_raw;
|
||||
if (utf8::is_utf8($raw)) {
|
||||
utf8::encode($raw);
|
||||
$part->body_set($raw);
|
||||
}
|
||||
}
|
||||
$part->encoding_set('quoted-printable') if !is_7bit_clean($body);
|
||||
}
|
||||
});
|
||||
|
||||
# MIME-Version must be set otherwise some mailsystems ignore the charset
|
||||
$email->header_set('MIME-Version', '1.0') if !$email->header('MIME-Version');
|
||||
|
||||
# Encode the headers correctly in quoted-printable
|
||||
foreach my $header ($email->header_names) {
|
||||
my @values = $email->header($header);
|
||||
# We don't recode headers that happen multiple times.
|
||||
next if scalar(@values) > 1;
|
||||
if (my $value = $values[0]) {
|
||||
if (Bugzilla->params->{'utf8'} && !utf8::is_utf8($value)) {
|
||||
utf8::decode($value);
|
||||
}
|
||||
|
||||
# avoid excessive line wrapping done by Encode.
|
||||
local $Encode::Encoding{'MIME-Q'}->{'bpl'} = 998;
|
||||
|
||||
my $encoded = encode('MIME-Q', $value);
|
||||
$email->header_set($header, $encoded);
|
||||
}
|
||||
}
|
||||
|
||||
my $from = $email->header('From');
|
||||
|
||||
my ($hostname, @args);
|
||||
if ($method eq "Sendmail") {
|
||||
if (ON_WINDOWS) {
|
||||
$Email::Send::Sendmail::SENDMAIL = SENDMAIL_EXE;
|
||||
}
|
||||
push @args, "-i";
|
||||
# We want to make sure that we pass *only* an email address.
|
||||
if ($from) {
|
||||
my ($email_obj) = Email::Address->parse($from);
|
||||
if ($email_obj) {
|
||||
my $from_email = $email_obj->address;
|
||||
push(@args, "-f$from_email") if $from_email;
|
||||
}
|
||||
}
|
||||
push(@args, "-ODeliveryMode=deferred")
|
||||
if !Bugzilla->params->{"sendmailnow"};
|
||||
}
|
||||
else {
|
||||
# Sendmail will automatically append our hostname to the From
|
||||
# address, but other mailers won't.
|
||||
my $urlbase = Bugzilla->params->{'urlbase'};
|
||||
$urlbase =~ m|//([^:/]+)[:/]?|;
|
||||
$hostname = $1;
|
||||
$from .= "\@$hostname" if $from !~ /@/;
|
||||
$email->header_set('From', $from);
|
||||
|
||||
# Sendmail adds a Date: header also, but others may not.
|
||||
if (!defined $email->header('Date')) {
|
||||
$email->header_set('Date', time2str("%a, %e %b %Y %T %z", time()));
|
||||
}
|
||||
}
|
||||
|
||||
if ($method eq "SMTP") {
|
||||
push @args, Host => Bugzilla->params->{"smtpserver"},
|
||||
username => Bugzilla->params->{"smtp_username"},
|
||||
password => Bugzilla->params->{"smtp_password"},
|
||||
Hello => $hostname,
|
||||
Debug => Bugzilla->params->{'smtp_debug'};
|
||||
}
|
||||
|
||||
Bugzilla::Hook::process('mailer-before_send', { email => $email });
|
||||
|
||||
if ($method eq "Test") {
|
||||
my $filename = bz_locations()->{'datadir'} . '/mailer.testfile';
|
||||
open TESTFILE, '>>', $filename;
|
||||
# From - <date> is required to be a valid mbox file.
|
||||
print TESTFILE "\n\nFrom - " . $email->header('Date') . "\n" . $email->as_string;
|
||||
close TESTFILE;
|
||||
}
|
||||
else {
|
||||
# This is useful for both Sendmail and Qmail, so we put it out here.
|
||||
local $ENV{PATH} = SENDMAIL_PATH;
|
||||
my $mailer = Email::Send->new({ mailer => $method,
|
||||
mailer_args => \@args });
|
||||
my $retval = $mailer->send($email);
|
||||
ThrowCodeError('mail_send_error', { msg => $retval, mail => $email })
|
||||
if !$retval;
|
||||
}
|
||||
}
|
||||
|
||||
# Builds header suitable for use as a threading marker in email notifications
|
||||
sub build_thread_marker {
|
||||
my ($bug_id, $user_id, $is_new) = @_;
|
||||
|
||||
if (!defined $user_id) {
|
||||
$user_id = Bugzilla->user->id;
|
||||
}
|
||||
|
||||
my $sitespec = '@' . Bugzilla->params->{'urlbase'};
|
||||
$sitespec =~ s/:\/\//\./; # Make the protocol look like part of the domain
|
||||
$sitespec =~ s/^([^:\/]+):(\d+)/$1/; # Remove a port number, to relocate
|
||||
if ($2) {
|
||||
$sitespec = "-$2$sitespec"; # Put the port number back in, before the '@'
|
||||
}
|
||||
|
||||
my $threadingmarker;
|
||||
if ($is_new) {
|
||||
$threadingmarker = "Message-ID: <bug-$bug_id-$user_id$sitespec>";
|
||||
}
|
||||
else {
|
||||
$threadingmarker = "In-Reply-To: <bug-$bug_id-$user_id$sitespec>" .
|
||||
"\nReferences: <bug-$bug_id-$user_id$sitespec>";
|
||||
}
|
||||
|
||||
return $threadingmarker;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,375 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Tiago R. Mello <timello@async.com.br>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Milestone;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
################################
|
||||
##### Initialization #####
|
||||
################################
|
||||
|
||||
use constant DEFAULT_SORTKEY => 0;
|
||||
|
||||
use constant DB_TABLE => 'milestones';
|
||||
use constant NAME_FIELD => 'value';
|
||||
use constant LIST_ORDER => 'sortkey, value';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
value
|
||||
product_id
|
||||
sortkey
|
||||
);
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(
|
||||
name
|
||||
product
|
||||
);
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(
|
||||
value
|
||||
sortkey
|
||||
);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
product => \&_check_product,
|
||||
sortkey => \&_check_sortkey,
|
||||
};
|
||||
|
||||
use constant UPDATE_VALIDATORS => {
|
||||
value => \&_check_value,
|
||||
};
|
||||
|
||||
################################
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $param = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $product;
|
||||
if (ref $param) {
|
||||
$product = $param->{product};
|
||||
my $name = $param->{name};
|
||||
if (!defined $product) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'product',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
if (!defined $name) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'name',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
|
||||
my $condition = 'product_id = ? AND value = ?';
|
||||
my @values = ($product->id, $name);
|
||||
$param = { condition => $condition, values => \@values };
|
||||
}
|
||||
|
||||
unshift @_, $param;
|
||||
return $class->SUPER::new(@_);
|
||||
}
|
||||
|
||||
sub run_create_validators {
|
||||
my $class = shift;
|
||||
my $params = $class->SUPER::run_create_validators(@_);
|
||||
|
||||
my $product = delete $params->{product};
|
||||
$params->{product_id} = $product->id;
|
||||
$params->{value} = $class->_check_value($params->{name}, $product);
|
||||
delete $params->{name};
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
my $changes = $self->SUPER::update(@_);
|
||||
|
||||
if (exists $changes->{value}) {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
# The milestone value is stored in the bugs table instead of its ID.
|
||||
$dbh->do('UPDATE bugs SET target_milestone = ?
|
||||
WHERE target_milestone = ? AND product_id = ?',
|
||||
undef, ($self->name, $changes->{value}->[0], $self->product_id));
|
||||
|
||||
# The default milestone also stores the value instead of the ID.
|
||||
$dbh->do('UPDATE products SET defaultmilestone = ?
|
||||
WHERE id = ? AND defaultmilestone = ?',
|
||||
undef, ($self->name, $self->product_id, $changes->{value}->[0]));
|
||||
}
|
||||
return $changes;
|
||||
}
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# The default milestone cannot be deleted.
|
||||
if ($self->name eq $self->product->default_milestone) {
|
||||
ThrowUserError('milestone_is_default', { milestone => $self });
|
||||
}
|
||||
|
||||
if ($self->bug_count) {
|
||||
# We don't want to delete bugs when deleting a milestone.
|
||||
# Bugs concerned are reassigned to the default milestone.
|
||||
my $bug_ids =
|
||||
$dbh->selectcol_arrayref('SELECT bug_id FROM bugs
|
||||
WHERE product_id = ? AND target_milestone = ?',
|
||||
undef, ($self->product->id, $self->name));
|
||||
|
||||
my $timestamp = $dbh->selectrow_array('SELECT NOW()');
|
||||
|
||||
$dbh->do('UPDATE bugs SET target_milestone = ?, delta_ts = ?
|
||||
WHERE ' . $dbh->sql_in('bug_id', $bug_ids),
|
||||
undef, ($self->product->default_milestone, $timestamp));
|
||||
|
||||
require Bugzilla::Bug;
|
||||
import Bugzilla::Bug qw(LogActivityEntry);
|
||||
foreach my $bug_id (@$bug_ids) {
|
||||
LogActivityEntry($bug_id, 'target_milestone',
|
||||
$self->name,
|
||||
$self->product->default_milestone,
|
||||
Bugzilla->user->id, $timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
$dbh->do('DELETE FROM milestones WHERE id = ?', undef, $self->id);
|
||||
}
|
||||
|
||||
################################
|
||||
# Validators
|
||||
################################
|
||||
|
||||
sub _check_value {
|
||||
my ($invocant, $name, $product) = @_;
|
||||
|
||||
$name = trim($name);
|
||||
$name || ThrowUserError('milestone_blank_name');
|
||||
if (length($name) > MAX_MILESTONE_SIZE) {
|
||||
ThrowUserError('milestone_name_too_long', {name => $name});
|
||||
}
|
||||
|
||||
$product = $invocant->product if (ref $invocant);
|
||||
my $milestone = new Bugzilla::Milestone({product => $product, name => $name});
|
||||
if ($milestone && (!ref $invocant || $milestone->id != $invocant->id)) {
|
||||
ThrowUserError('milestone_already_exists', { name => $milestone->name,
|
||||
product => $product->name });
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_sortkey {
|
||||
my ($invocant, $sortkey) = @_;
|
||||
|
||||
# Keep a copy in case detaint_signed() clears the sortkey
|
||||
my $stored_sortkey = $sortkey;
|
||||
|
||||
if (!detaint_signed($sortkey) || $sortkey < MIN_SMALLINT || $sortkey > MAX_SMALLINT) {
|
||||
ThrowUserError('milestone_sortkey_invalid', {sortkey => $stored_sortkey});
|
||||
}
|
||||
return $sortkey;
|
||||
}
|
||||
|
||||
sub _check_product {
|
||||
my ($invocant, $product) = @_;
|
||||
return Bugzilla->user->check_can_admin_product($product->name);
|
||||
}
|
||||
|
||||
################################
|
||||
# Methods
|
||||
################################
|
||||
|
||||
sub set_name { $_[0]->set('value', $_[1]); }
|
||||
sub set_sortkey { $_[0]->set('sortkey', $_[1]); }
|
||||
|
||||
sub bug_count {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bug_count'}) {
|
||||
$self->{'bug_count'} = $dbh->selectrow_array(q{
|
||||
SELECT COUNT(*) FROM bugs
|
||||
WHERE product_id = ? AND target_milestone = ?},
|
||||
undef, $self->product_id, $self->name) || 0;
|
||||
}
|
||||
return $self->{'bug_count'};
|
||||
}
|
||||
|
||||
################################
|
||||
##### Accessors ######
|
||||
################################
|
||||
|
||||
sub name { return $_[0]->{'value'}; }
|
||||
sub product_id { return $_[0]->{'product_id'}; }
|
||||
sub sortkey { return $_[0]->{'sortkey'}; }
|
||||
|
||||
sub product {
|
||||
my $self = shift;
|
||||
|
||||
require Bugzilla::Product;
|
||||
$self->{'product'} ||= new Bugzilla::Product($self->product_id);
|
||||
return $self->{'product'};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Milestone - Bugzilla product milestone class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Milestone;
|
||||
|
||||
my $milestone = new Bugzilla::Milestone({ name => $name, product => $product });
|
||||
|
||||
my $name = $milestone->name;
|
||||
my $product_id = $milestone->product_id;
|
||||
my $product = $milestone->product;
|
||||
my $sortkey = $milestone->sortkey;
|
||||
|
||||
my $milestone = Bugzilla::Milestone->create(
|
||||
{ name => $name, product => $product, sortkey => $sortkey });
|
||||
|
||||
$milestone->set_name($new_name);
|
||||
$milestone->set_sortkey($new_sortkey);
|
||||
$milestone->update();
|
||||
|
||||
$milestone->remove_from_db;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Milestone.pm represents a Product Milestone object.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<new({name => $name, product => $product})>
|
||||
|
||||
Description: The constructor is used to load an existing milestone
|
||||
by passing a product object and a milestone name.
|
||||
|
||||
Params: $product - a Bugzilla::Product object.
|
||||
$name - the name of a milestone (string).
|
||||
|
||||
Returns: A Bugzilla::Milestone object.
|
||||
|
||||
=item C<name()>
|
||||
|
||||
Description: Name (value) of the milestone.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: The name of the milestone.
|
||||
|
||||
=item C<product_id()>
|
||||
|
||||
Description: ID of the product the milestone belongs to.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: The ID of a product.
|
||||
|
||||
=item C<product()>
|
||||
|
||||
Description: The product object of the product the milestone belongs to.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A Bugzilla::Product object.
|
||||
|
||||
=item C<sortkey()>
|
||||
|
||||
Description: Sortkey of the milestone.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: The sortkey of the milestone.
|
||||
|
||||
=item C<bug_count()>
|
||||
|
||||
Description: Returns the total of bugs that belong to the milestone.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Integer with the number of bugs.
|
||||
|
||||
=item C<set_name($new_name)>
|
||||
|
||||
Description: Changes the name of the milestone.
|
||||
|
||||
Params: $new_name - new name of the milestone (string). This name
|
||||
must be unique within the product.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<set_sortkey($new_sortkey)>
|
||||
|
||||
Description: Changes the sortkey of the milestone.
|
||||
|
||||
Params: $new_sortkey - new sortkey of the milestone (signed integer).
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<update()>
|
||||
|
||||
Description: Writes the new name and/or the new sortkey into the DB.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A hashref with changes made to the milestone object.
|
||||
|
||||
=item C<remove_from_db()>
|
||||
|
||||
Description: Deletes the current milestone from the DB. The object itself
|
||||
is not destroyed.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CLASS METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<create({name => $name, product => $product, sortkey => $sortkey})>
|
||||
|
||||
Description: Create a new milestone for the given product.
|
||||
|
||||
Params: $name - name of the new milestone (string). This name
|
||||
must be unique within the product.
|
||||
$product - a Bugzilla::Product object.
|
||||
$sortkey - the sortkey of the new milestone (signed integer)
|
||||
|
||||
Returns: A Bugzilla::Milestone object.
|
||||
|
||||
=back
|
||||
@@ -1,894 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Everything Solved.
|
||||
# Portions created by Everything Solved are Copyright (C) 2006
|
||||
# Everything Solved. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Object;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
use Date::Parse;
|
||||
|
||||
use constant NAME_FIELD => 'name';
|
||||
use constant ID_FIELD => 'id';
|
||||
use constant LIST_ORDER => NAME_FIELD;
|
||||
|
||||
use constant UPDATE_VALIDATORS => {};
|
||||
use constant NUMERIC_COLUMNS => ();
|
||||
use constant DATE_COLUMNS => ();
|
||||
|
||||
###############################
|
||||
#### Initialization ####
|
||||
###############################
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my $object = $class->_init(@_);
|
||||
bless($object, $class) if $object;
|
||||
return $object;
|
||||
}
|
||||
|
||||
|
||||
# Note: Because this uses sql_istrcmp, if you make a new object use
|
||||
# Bugzilla::Object, make sure that you modify bz_setup_database
|
||||
# in Bugzilla::DB::Pg appropriately, to add the right LOWER
|
||||
# index. You can see examples already there.
|
||||
sub _init {
|
||||
my $class = shift;
|
||||
my ($param) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $columns = join(',', $class->DB_COLUMNS);
|
||||
my $table = $class->DB_TABLE;
|
||||
my $name_field = $class->NAME_FIELD;
|
||||
my $id_field = $class->ID_FIELD;
|
||||
|
||||
my $id = $param;
|
||||
if (ref $param eq 'HASH') {
|
||||
$id = $param->{id};
|
||||
}
|
||||
my $object;
|
||||
|
||||
if (defined $id) {
|
||||
# We special-case if somebody specifies an ID, so that we can
|
||||
# validate it as numeric.
|
||||
detaint_natural($id)
|
||||
|| ThrowCodeError('param_must_be_numeric',
|
||||
{function => $class . '::_init'});
|
||||
|
||||
$object = $dbh->selectrow_hashref(qq{
|
||||
SELECT $columns FROM $table
|
||||
WHERE $id_field = ?}, undef, $id);
|
||||
} else {
|
||||
unless (defined $param->{name} || (defined $param->{'condition'}
|
||||
&& defined $param->{'values'}))
|
||||
{
|
||||
ThrowCodeError('bad_arg', { argument => 'param',
|
||||
function => $class . '::new' });
|
||||
}
|
||||
|
||||
my ($condition, @values);
|
||||
if (defined $param->{name}) {
|
||||
$condition = $dbh->sql_istrcmp($name_field, '?');
|
||||
push(@values, $param->{name});
|
||||
}
|
||||
elsif (defined $param->{'condition'} && defined $param->{'values'}) {
|
||||
caller->isa('Bugzilla::Object')
|
||||
|| ThrowCodeError('protection_violation',
|
||||
{ caller => caller,
|
||||
function => $class . '::new',
|
||||
argument => 'condition/values' });
|
||||
$condition = $param->{'condition'};
|
||||
push(@values, @{$param->{'values'}});
|
||||
}
|
||||
|
||||
map { trick_taint($_) } @values;
|
||||
$object = $dbh->selectrow_hashref(
|
||||
"SELECT $columns FROM $table WHERE $condition", undef, @values);
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
sub check {
|
||||
my ($invocant, $param) = @_;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
# If we were just passed a name, then just use the name.
|
||||
if (!ref $param) {
|
||||
$param = { name => $param };
|
||||
}
|
||||
# Don't allow empty names or ids.
|
||||
my $check_param = exists $param->{id} ? $param->{id} : $param->{name};
|
||||
$check_param = trim($check_param);
|
||||
$check_param || ThrowUserError('object_not_specified', { class => $class });
|
||||
my $obj = $class->new($param)
|
||||
|| ThrowUserError('object_does_not_exist', {%$param, class => $class});
|
||||
return $obj;
|
||||
}
|
||||
|
||||
sub new_from_list {
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my ($id_list) = @_;
|
||||
my $id_field = $class->ID_FIELD;
|
||||
|
||||
my @detainted_ids;
|
||||
foreach my $id (@$id_list) {
|
||||
detaint_natural($id) ||
|
||||
ThrowCodeError('param_must_be_numeric',
|
||||
{function => $class . '::new_from_list'});
|
||||
push(@detainted_ids, $id);
|
||||
}
|
||||
# We don't do $invocant->match because some classes have
|
||||
# their own implementation of match which is not compatible
|
||||
# with this one. However, match() still needs to have the right $invocant
|
||||
# in order to do $class->DB_TABLE and so on.
|
||||
return match($invocant, { $id_field => \@detainted_ids });
|
||||
}
|
||||
|
||||
# Note: Future extensions to this could be:
|
||||
# * Add a MATCH_JOIN constant so that we can join against
|
||||
# certain other tables for the WHERE criteria.
|
||||
sub match {
|
||||
my ($invocant, $criteria) = @_;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
return [$class->get_all] if !$criteria;
|
||||
|
||||
my (@terms, @values, $postamble);
|
||||
foreach my $field (keys %$criteria) {
|
||||
my $value = $criteria->{$field};
|
||||
|
||||
# allow for LIMIT and OFFSET expressions via the criteria.
|
||||
next if $field eq 'OFFSET';
|
||||
if ( $field eq 'LIMIT' ) {
|
||||
next unless defined $value;
|
||||
$postamble = $dbh->sql_limit( $value, $criteria->{OFFSET} );
|
||||
next;
|
||||
}
|
||||
elsif ( $field eq 'WHERE' ) {
|
||||
# the WHERE value is a hashref where the keys are
|
||||
# "column_name operator ?" and values are the placeholder's
|
||||
# value (either a scalar or an array of values).
|
||||
foreach my $k (keys %$value) {
|
||||
push(@terms, $k);
|
||||
my @this_value = ref($value->{$k}) ? @{ $value->{$k} }
|
||||
: ($value->{$k});
|
||||
push(@values, @this_value);
|
||||
}
|
||||
next;
|
||||
}
|
||||
|
||||
if (ref $value eq 'ARRAY') {
|
||||
# IN () is invalid SQL, and if we have an empty list
|
||||
# to match against, we're just returning an empty
|
||||
# array anyhow.
|
||||
return [] if !scalar @$value;
|
||||
|
||||
my @qmarks = ("?") x @$value;
|
||||
push(@terms, $dbh->sql_in($field, \@qmarks));
|
||||
push(@values, @$value);
|
||||
}
|
||||
elsif ($value eq NOT_NULL) {
|
||||
push(@terms, "$field IS NOT NULL");
|
||||
}
|
||||
elsif ($value eq IS_NULL) {
|
||||
push(@terms, "$field IS NULL");
|
||||
}
|
||||
else {
|
||||
push(@terms, "$field = ?");
|
||||
push(@values, $value);
|
||||
}
|
||||
}
|
||||
|
||||
my $where = join(' AND ', @terms) if scalar @terms;
|
||||
return $class->_do_list_select($where, \@values, $postamble);
|
||||
}
|
||||
|
||||
sub _do_list_select {
|
||||
my ($class, $where, $values, $postamble) = @_;
|
||||
my $table = $class->DB_TABLE;
|
||||
my $cols = join(',', $class->DB_COLUMNS);
|
||||
my $order = $class->LIST_ORDER;
|
||||
|
||||
my $sql = "SELECT $cols FROM $table";
|
||||
if (defined $where) {
|
||||
$sql .= " WHERE $where ";
|
||||
}
|
||||
$sql .= " ORDER BY $order";
|
||||
|
||||
$sql .= " $postamble" if $postamble;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $objects = $dbh->selectall_arrayref($sql, {Slice=>{}}, @$values);
|
||||
bless ($_, $class) foreach @$objects;
|
||||
return $objects
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Accessors ######
|
||||
###############################
|
||||
|
||||
sub id { return $_[0]->{$_[0]->ID_FIELD}; }
|
||||
sub name { return $_[0]->{$_[0]->NAME_FIELD}; }
|
||||
|
||||
###############################
|
||||
#### Methods ####
|
||||
###############################
|
||||
|
||||
sub set {
|
||||
my ($self, $field, $value) = @_;
|
||||
|
||||
# This method is protected. It's used to help implement set_ functions.
|
||||
caller->isa('Bugzilla::Object')
|
||||
|| ThrowCodeError('protection_violation',
|
||||
{ caller => caller,
|
||||
superclass => __PACKAGE__,
|
||||
function => 'Bugzilla::Object->set' });
|
||||
|
||||
my %validators = (%{$self->VALIDATORS}, %{$self->UPDATE_VALIDATORS});
|
||||
if (exists $validators{$field}) {
|
||||
my $validator = $validators{$field};
|
||||
$value = $self->$validator($value, $field);
|
||||
trick_taint($value) if (defined $value && !ref($value));
|
||||
|
||||
if ($self->can('_set_global_validator')) {
|
||||
$self->_set_global_validator($value, $field);
|
||||
}
|
||||
}
|
||||
|
||||
$self->{$field} = $value;
|
||||
}
|
||||
|
||||
sub set_all {
|
||||
my ($self, $params) = @_;
|
||||
foreach my $key (keys %$params) {
|
||||
my $method = "set_$key";
|
||||
$self->$method($params->{$key});
|
||||
}
|
||||
}
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $table = $self->DB_TABLE;
|
||||
my $id_field = $self->ID_FIELD;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
my $old_self = $self->new($self->id);
|
||||
|
||||
my %numeric = map { $_ => 1 } $self->NUMERIC_COLUMNS;
|
||||
my %date = map { $_ => 1 } $self->DATE_COLUMNS;
|
||||
my (@update_columns, @values, %changes);
|
||||
foreach my $column ($self->UPDATE_COLUMNS) {
|
||||
my ($old, $new) = ($old_self->{$column}, $self->{$column});
|
||||
# This has to be written this way in order to allow us to set a field
|
||||
# from undef or to undef, and avoid warnings about comparing an undef
|
||||
# with the "eq" operator.
|
||||
if (!defined $new || !defined $old) {
|
||||
next if !defined $new && !defined $old;
|
||||
}
|
||||
elsif ( ($numeric{$column} && $old == $new)
|
||||
|| ($date{$column} && str2time($old) == str2time($new))
|
||||
|| $old eq $new ) {
|
||||
next;
|
||||
}
|
||||
|
||||
trick_taint($new) if defined $new;
|
||||
push(@values, $new);
|
||||
push(@update_columns, $column);
|
||||
# We don't use $new because we don't want to detaint this for
|
||||
# the caller.
|
||||
$changes{$column} = [$old, $self->{$column}];
|
||||
}
|
||||
|
||||
my $columns = join(', ', map {"$_ = ?"} @update_columns);
|
||||
|
||||
$dbh->do("UPDATE $table SET $columns WHERE $id_field = ?", undef,
|
||||
@values, $self->id) if @values;
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
if (wantarray) {
|
||||
return (\%changes, $old_self);
|
||||
}
|
||||
|
||||
return \%changes;
|
||||
}
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $table = $self->DB_TABLE;
|
||||
my $id_field = $self->ID_FIELD;
|
||||
Bugzilla->dbh->do("DELETE FROM $table WHERE $id_field = ?",
|
||||
undef, $self->id);
|
||||
undef $self;
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Subroutines ######
|
||||
###############################
|
||||
|
||||
sub create {
|
||||
my ($class, $params) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
$class->check_required_create_fields($params);
|
||||
my $field_values = $class->run_create_validators($params);
|
||||
my $object = $class->insert_create_data($field_values);
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
sub check_required_create_fields {
|
||||
my ($class, $params) = @_;
|
||||
|
||||
foreach my $field ($class->REQUIRED_CREATE_FIELDS) {
|
||||
ThrowCodeError('param_required',
|
||||
{ function => "${class}->create", param => $field })
|
||||
if !exists $params->{$field};
|
||||
}
|
||||
}
|
||||
|
||||
sub run_create_validators {
|
||||
my ($class, $params) = @_;
|
||||
|
||||
my $validators = $class->VALIDATORS;
|
||||
|
||||
my %field_values;
|
||||
# We do the sort just to make sure that validation always
|
||||
# happens in a consistent order.
|
||||
foreach my $field (sort keys %$params) {
|
||||
my $value;
|
||||
if (exists $validators->{$field}) {
|
||||
my $validator = $validators->{$field};
|
||||
$value = $class->$validator($params->{$field}, $field);
|
||||
}
|
||||
else {
|
||||
$value = $params->{$field};
|
||||
}
|
||||
# We want people to be able to explicitly set fields to NULL,
|
||||
# and that means they can be set to undef.
|
||||
trick_taint($value) if defined $value && !ref($value);
|
||||
$field_values{$field} = $value;
|
||||
}
|
||||
|
||||
return \%field_values;
|
||||
}
|
||||
|
||||
sub insert_create_data {
|
||||
my ($class, $field_values) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my (@field_names, @values);
|
||||
while (my ($field, $value) = each %$field_values) {
|
||||
push(@field_names, $field);
|
||||
push(@values, $value);
|
||||
}
|
||||
|
||||
my $qmarks = '?,' x @field_names;
|
||||
chop($qmarks);
|
||||
my $table = $class->DB_TABLE;
|
||||
$dbh->do("INSERT INTO $table (" . join(', ', @field_names)
|
||||
. ") VALUES ($qmarks)", undef, @values);
|
||||
my $id = $dbh->bz_last_key($table, $class->ID_FIELD);
|
||||
return $class->new($id);
|
||||
}
|
||||
|
||||
sub get_all {
|
||||
my $class = shift;
|
||||
return @{$class->_do_list_select()};
|
||||
}
|
||||
|
||||
###############################
|
||||
#### Validators ######
|
||||
###############################
|
||||
|
||||
sub check_boolean { return $_[1] ? 1 : 0 }
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Object - A base class for objects in Bugzilla.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $object = new Bugzilla::Object(1);
|
||||
my $object = new Bugzilla::Object({name => 'TestProduct'});
|
||||
|
||||
my $id = $object->id;
|
||||
my $name = $object->name;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Bugzilla::Object is a base class for Bugzilla objects. You never actually
|
||||
create a Bugzilla::Object directly, you only make subclasses of it.
|
||||
|
||||
Basically, Bugzilla::Object exists to allow developers to create objects
|
||||
more easily. All you have to do is define C<DB_TABLE>, C<DB_COLUMNS>,
|
||||
and sometimes C<LIST_ORDER> and you have a whole new object.
|
||||
|
||||
You should also define accessors for any columns other than C<name>
|
||||
or C<id>.
|
||||
|
||||
=head1 CONSTANTS
|
||||
|
||||
Frequently, these will be the only things you have to define in your
|
||||
subclass in order to have a fully-functioning object. C<DB_TABLE>
|
||||
and C<DB_COLUMNS> are required.
|
||||
|
||||
=over
|
||||
|
||||
=item C<DB_TABLE>
|
||||
|
||||
The name of the table that these objects are stored in. For example,
|
||||
for C<Bugzilla::Keyword> this would be C<keyworddefs>.
|
||||
|
||||
=item C<DB_COLUMNS>
|
||||
|
||||
The names of the columns that you want to read out of the database
|
||||
and into this object. This should be an array.
|
||||
|
||||
=item C<NAME_FIELD>
|
||||
|
||||
The name of the column that should be considered to be the unique
|
||||
"name" of this object. The 'name' is a B<string> that uniquely identifies
|
||||
this Object in the database. Defaults to 'name'. When you specify
|
||||
C<{name => $name}> to C<new()>, this is the column that will be
|
||||
matched against in the DB.
|
||||
|
||||
=item C<ID_FIELD>
|
||||
|
||||
The name of the column that represents the unique B<integer> ID
|
||||
of this object in the database. Defaults to 'id'.
|
||||
|
||||
=item C<LIST_ORDER>
|
||||
|
||||
The order that C<new_from_list> and C<get_all> should return objects
|
||||
in. This should be the name of a database column. Defaults to
|
||||
L</NAME_FIELD>.
|
||||
|
||||
=item C<REQUIRED_CREATE_FIELDS>
|
||||
|
||||
The list of fields that B<must> be specified when the user calls
|
||||
C<create()>. This should be an array.
|
||||
|
||||
=item C<VALIDATORS>
|
||||
|
||||
A hashref that points to a function that will validate each param to
|
||||
L</create>.
|
||||
|
||||
Validators are called both by L</create> and L</set>. When
|
||||
they are called by L</create>, the first argument will be the name
|
||||
of the class (what we normally call C<$class>).
|
||||
|
||||
When they are called by L</set>, the first argument will be
|
||||
a reference to the current object (what we normally call C<$self>).
|
||||
|
||||
The second argument will be the value passed to L</create> or
|
||||
L</set>for that field.
|
||||
|
||||
The third argument will be the name of the field being validated.
|
||||
This may be required by validators which validate several distinct fields.
|
||||
|
||||
These functions should call L<Bugzilla::Error/ThrowUserError> if they fail.
|
||||
|
||||
The validator must return the validated value.
|
||||
|
||||
=item C<UPDATE_VALIDATORS>
|
||||
|
||||
This is just like L</VALIDATORS>, but these validators are called only
|
||||
when updating an object, not when creating it. Any validator that appears
|
||||
here must not appear in L</VALIDATORS>.
|
||||
|
||||
L<Bugzilla::Bug> has good examples in its code of when to use this.
|
||||
|
||||
=item C<UPDATE_COLUMNS>
|
||||
|
||||
A list of columns to update when L</update> is called.
|
||||
If a field can't be changed, it shouldn't be listed here. (For example,
|
||||
the L</ID_FIELD> usually can't be updated.)
|
||||
|
||||
=item C<NUMERIC_COLUMNS>
|
||||
|
||||
When L</update> is called, it compares each column in the object to its
|
||||
current value in the database. It only updates columns that have changed.
|
||||
|
||||
Any column listed in NUMERIC_COLUMNS is treated as a number, not as a string,
|
||||
during these comparisons.
|
||||
|
||||
=item C<DATE_COLUMNS>
|
||||
|
||||
This is much like L</NUMERIC_COLUMNS>, except that it treats strings as
|
||||
dates when being compared. So, for example, C<2007-01-01> would be
|
||||
equal to C<2007-01-01 00:00:00>.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 Constructors
|
||||
|
||||
=over
|
||||
|
||||
=item C<new>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
The constructor is used to load an existing object from the database,
|
||||
by id or by name.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
If you pass an integer, the integer is the id of the object,
|
||||
from the database, that we want to read in. (id is defined
|
||||
as the value in the L</ID_FIELD> column).
|
||||
|
||||
If you pass in a hashref, you can pass a C<name> key. The
|
||||
value of the C<name> key is the case-insensitive name of the object
|
||||
(from L</NAME_FIELD>) in the DB. You can also pass in an C<id> key
|
||||
which will be interpreted as the id of the object you want (overriding the
|
||||
C<name> key).
|
||||
|
||||
B<Additional Parameters Available for Subclasses>
|
||||
|
||||
If you are a subclass of C<Bugzilla::Object>, you can pass
|
||||
C<condition> and C<values> as hash keys, instead of the above.
|
||||
|
||||
C<condition> is a set of SQL conditions for the WHERE clause, which contain
|
||||
placeholders.
|
||||
|
||||
C<values> is a reference to an array. The array contains the values
|
||||
for each placeholder in C<condition>, in order.
|
||||
|
||||
This is to allow subclasses to have complex parameters, and then to
|
||||
translate those parameters into C<condition> and C<values> when they
|
||||
call C<$self->SUPER::new> (which is this function, usually).
|
||||
|
||||
If you try to call C<new> outside of a subclass with the C<condition>
|
||||
and C<values> parameters, Bugzilla will throw an error. These parameters
|
||||
are intended B<only> for use by subclasses.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A fully-initialized object, or C<undef> if there is no object in the
|
||||
database matching the parameters you passed in.
|
||||
|
||||
=back
|
||||
|
||||
=item C<check>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Checks if there is an object in the database with the specified name, and
|
||||
throws an error if you specified an empty name, or if there is no object
|
||||
in the database with that name.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
The parameters are the same as for L</new>, except that if you don't pass
|
||||
a hashref, the single argument is the I<name> of the object, not the id.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A fully initialized object, guaranteed.
|
||||
|
||||
=item B<Notes For Implementors>
|
||||
|
||||
If you implement this in your subclass, make sure that you also update
|
||||
the C<object_name> block at the bottom of the F<global/user-error.html.tmpl>
|
||||
template.
|
||||
|
||||
=back
|
||||
|
||||
=item C<new_from_list(\@id_list)>
|
||||
|
||||
Description: Creates an array of objects, given an array of ids.
|
||||
|
||||
Params: \@id_list - A reference to an array of numbers, database ids.
|
||||
If any of these are not numeric, the function
|
||||
will throw an error. If any of these are not
|
||||
valid ids in the database, they will simply
|
||||
be skipped.
|
||||
|
||||
Returns: A reference to an array of objects.
|
||||
|
||||
=item C<match>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Gets a list of objects from the database based on certain criteria.
|
||||
|
||||
Basically, a simple way of doing a sort of "SELECT" statement (like SQL)
|
||||
to get objects.
|
||||
|
||||
All criteria are joined by C<AND>, so adding more criteria will give you
|
||||
a smaller set of results, not a larger set.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
A hashref, where the keys are column names of the table, pointing to the
|
||||
value that you want to match against for that column.
|
||||
|
||||
There are two special values, the constants C<NULL> and C<NOT_NULL>,
|
||||
which means "give me objects where this field is NULL or NOT NULL,
|
||||
respectively."
|
||||
|
||||
In addition to the column keys, there are a few special keys that
|
||||
can be used to rig the underlying database queries. These are
|
||||
C<LIMIT>, C<OFFSET>, and C<WHERE>.
|
||||
|
||||
The value for the C<LIMIT> key is expected to be an integer defining
|
||||
the number of objects to return, while the value for C<OFFSET> defines
|
||||
the position, relative to the number of objects the query would normally
|
||||
return, at which to begin the result set. If C<OFFSET> is defined without
|
||||
a corresponding C<LIMIT> it is silently ignored.
|
||||
|
||||
The C<WHERE> key provides a mechanism for adding arbitrary WHERE
|
||||
clauses to the underlying query. Its value is expected to a hash
|
||||
reference whose keys are the columns, operators and placeholders, and the
|
||||
values are the placeholders' bind value. For example:
|
||||
|
||||
WHERE => { 'some_column >= ?' => $some_value }
|
||||
|
||||
would constrain the query to only those objects in the table whose
|
||||
'some_column' column has a value greater than or equal to $some_value.
|
||||
|
||||
If you don't specify any criteria, calling this function is the same
|
||||
as doing C<[$class-E<gt>get_all]>.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
An arrayref of objects, or an empty arrayref if there are no matches.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 Database Manipulation
|
||||
|
||||
=over
|
||||
|
||||
=item C<create>
|
||||
|
||||
Description: Creates a new item in the database.
|
||||
Throws a User Error if any of the passed-in params
|
||||
are invalid.
|
||||
|
||||
Params: C<$params> - hashref - A value to put in each database
|
||||
field for this object. Certain values must be set (the
|
||||
ones specified in L</REQUIRED_CREATE_FIELDS>), and
|
||||
the function will throw a Code Error if you don't set
|
||||
them.
|
||||
|
||||
Returns: The Object just created in the database.
|
||||
|
||||
Notes: In order for this function to work in your subclass,
|
||||
your subclass's L</ID_FIELD> must be of C<SERIAL>
|
||||
type in the database. Your subclass also must
|
||||
define L</REQUIRED_CREATE_FIELDS> and L</VALIDATORS>.
|
||||
|
||||
Subclass Implementors: This function basically just
|
||||
calls L</check_required_create_fields>, then
|
||||
L</run_create_validators>, and then finally
|
||||
L</insert_create_data>. So if you have a complex system that
|
||||
you need to implement, you can do it by calling these
|
||||
three functions instead of C<SUPER::create>.
|
||||
|
||||
=item C<check_required_create_fields>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Part of L</create>. Throws an error if any of the L</REQUIRED_CREATE_FIELDS>
|
||||
have not been specified in C<$params>
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<$params> - The same as C<$params> from L</create>.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=back
|
||||
|
||||
=item C<run_create_validators>
|
||||
|
||||
Description: Runs the validation of input parameters for L</create>.
|
||||
This subroutine exists so that it can be overridden
|
||||
by subclasses who need to do special validations
|
||||
of their input parameters. This method is B<only> called
|
||||
by L</create>.
|
||||
|
||||
Params: The same as L</create>.
|
||||
|
||||
Returns: A hash, in a similar format as C<$params>, except that
|
||||
these are the values to be inserted into the database,
|
||||
not the values that were input to L</create>.
|
||||
|
||||
=item C<insert_create_data>
|
||||
|
||||
Part of L</create>.
|
||||
|
||||
Takes the return value from L</run_create_validators> and inserts the
|
||||
data into the database. Returns a newly created object.
|
||||
|
||||
=item C<update>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Saves the values currently in this object to the database.
|
||||
Only the fields specified in L</UPDATE_COLUMNS> will be
|
||||
updated, and they will only be updated if their values have changed.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
B<In scalar context:>
|
||||
|
||||
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.
|
||||
|
||||
B<In array context:>
|
||||
|
||||
Returns a list, where the first item is the above hashref. The second item
|
||||
is the object as it was in the database before update() was called. (This
|
||||
is mostly useful to subclasses of C<Bugzilla::Object> that are implementing
|
||||
C<update>.)
|
||||
|
||||
=back
|
||||
|
||||
=item C<remove_from_db>
|
||||
|
||||
Removes this object from the database. Will throw an error if you can't
|
||||
remove it for some reason. The object will then be destroyed, as it is
|
||||
not safe to use the object after it has been removed from the database.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Mutators
|
||||
|
||||
These are used for updating the values in objects, before calling
|
||||
C<update>.
|
||||
|
||||
=over
|
||||
|
||||
=item C<set>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Sets a certain hash member of this class to a certain value.
|
||||
Used for updating fields. Calls the validator for this field,
|
||||
if it exists. Subclasses should use this function
|
||||
to implement the various C<set_> mutators for their different
|
||||
fields.
|
||||
|
||||
If your class defines a method called C<_set_global_validator>,
|
||||
C<set> will call it with C<($value, $field)> as arguments, after running
|
||||
the validator for this particular field. C<_set_global_validator> does not
|
||||
return anything.
|
||||
|
||||
See L</VALIDATORS> for more information.
|
||||
|
||||
B<NOTE>: This function is intended only for use by subclasses. If
|
||||
you call it from anywhere else, it will throw a C<CodeError>.
|
||||
|
||||
=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
|
||||
|
||||
|
||||
=item C<set_all>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
This is a convenience function which is simpler than calling many different
|
||||
C<set_> functions in a row. You pass a hashref of parameters and it calls
|
||||
C<set_$key($value)> for every item in the hashref.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
Takes a hashref of the fields that need to be set, pointing to the value
|
||||
that should be passed to the C<set_> function that is called.
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=back
|
||||
|
||||
=head2 Simple Validators
|
||||
|
||||
You can use these in your subclass L</VALIDATORS> or L</UPDATE_VALIDATORS>.
|
||||
Note that you have to reference them like C<\&Bugzilla::Object::check_boolean>,
|
||||
you can't just write C<\&check_boolean>.
|
||||
|
||||
=over
|
||||
|
||||
=item C<check_boolean>
|
||||
|
||||
Returns C<1> if the passed-in value is true, C<0> otherwise.
|
||||
|
||||
=back
|
||||
|
||||
=head1 CLASS FUNCTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_all>
|
||||
|
||||
Description: Returns all objects in this table from the database.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A list of objects, or an empty list if there are none.
|
||||
|
||||
Notes: Note that you must call this as C<$class->get_all>. For
|
||||
example, C<Bugzilla::Keyword->get_all>.
|
||||
C<Bugzilla::Keyword::get_all> will not work.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,530 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): C. Begle
|
||||
# Jesse Ruderman
|
||||
# Andreas Franke <afranke@mathweb.org>
|
||||
# Stephen Lee <slee@uk.bnsmc.com>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::Search::Quicksearch;
|
||||
|
||||
# Make it harder for us to do dangerous things in Perl.
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Keyword;
|
||||
use Bugzilla::Status;
|
||||
use Bugzilla::Field;
|
||||
use Bugzilla::Util;
|
||||
|
||||
use base qw(Exporter);
|
||||
@Bugzilla::Search::Quicksearch::EXPORT = qw(quicksearch);
|
||||
|
||||
# Word renamings
|
||||
use constant MAPPINGS => {
|
||||
# Status, Resolution, Platform, OS, Priority, Severity
|
||||
"status" => "bug_status",
|
||||
"resolution" => "resolution", # no change
|
||||
"platform" => "rep_platform",
|
||||
"os" => "op_sys",
|
||||
"opsys" => "op_sys",
|
||||
"priority" => "priority", # no change
|
||||
"pri" => "priority",
|
||||
"severity" => "bug_severity",
|
||||
"sev" => "bug_severity",
|
||||
# People: AssignedTo, Reporter, QA Contact, CC, Added comment (?)
|
||||
"owner" => "assigned_to", # deprecated since bug 76507
|
||||
"assignee" => "assigned_to",
|
||||
"assignedto" => "assigned_to",
|
||||
"reporter" => "reporter", # no change
|
||||
"rep" => "reporter",
|
||||
"qa" => "qa_contact",
|
||||
"qacontact" => "qa_contact",
|
||||
"cc" => "cc", # no change
|
||||
# Product, Version, Component, Target Milestone
|
||||
"product" => "product", # no change
|
||||
"prod" => "product",
|
||||
"version" => "version", # no change
|
||||
"ver" => "version",
|
||||
"component" => "component", # no change
|
||||
"comp" => "component",
|
||||
"milestone" => "target_milestone",
|
||||
"target" => "target_milestone",
|
||||
"targetmilestone" => "target_milestone",
|
||||
# Summary, Description, URL, Status whiteboard, Keywords
|
||||
"summary" => "short_desc",
|
||||
"shortdesc" => "short_desc",
|
||||
"desc" => "longdesc",
|
||||
"description" => "longdesc",
|
||||
#"comment" => "longdesc", # ???
|
||||
# reserve "comment" for "added comment" email search?
|
||||
"longdesc" => "longdesc",
|
||||
"url" => "bug_file_loc",
|
||||
"whiteboard" => "status_whiteboard",
|
||||
"statuswhiteboard" => "status_whiteboard",
|
||||
"sw" => "status_whiteboard",
|
||||
"keywords" => "keywords", # no change
|
||||
"kw" => "keywords",
|
||||
"group" => "bug_group",
|
||||
"flag" => "flagtypes.name",
|
||||
"requestee" => "requestees.login_name",
|
||||
"req" => "requestees.login_name",
|
||||
"setter" => "setters.login_name",
|
||||
"set" => "setters.login_name",
|
||||
# Attachments
|
||||
"attachment" => "attachments.description",
|
||||
"attachmentdesc" => "attachments.description",
|
||||
"attachdesc" => "attachments.description",
|
||||
"attachmentdata" => "attach_data.thedata",
|
||||
"attachdata" => "attach_data.thedata",
|
||||
"attachmentmimetype" => "attachments.mimetype",
|
||||
"attachmimetype" => "attachments.mimetype"
|
||||
};
|
||||
|
||||
# We might want to put this into localconfig or somewhere
|
||||
use constant PLATFORMS => ('pc', 'sun', 'macintosh', 'mac');
|
||||
use constant OPSYSTEMS => ('windows', 'win', 'linux');
|
||||
use constant PRODUCT_EXCEPTIONS => (
|
||||
'row', # [Browser]
|
||||
# ^^^
|
||||
'new', # [MailNews]
|
||||
# ^^^
|
||||
);
|
||||
use constant COMPONENT_EXCEPTIONS => (
|
||||
'hang' # [Bugzilla: Component/Keyword Changes]
|
||||
# ^^^^
|
||||
);
|
||||
|
||||
# Quicksearch-wide globals for boolean charts.
|
||||
our ($chart, $and, $or);
|
||||
|
||||
sub quicksearch {
|
||||
my ($searchstring) = (@_);
|
||||
my $cgi = Bugzilla->cgi;
|
||||
my $urlbase = correct_urlbase();
|
||||
|
||||
$chart = 0;
|
||||
$and = 0;
|
||||
$or = 0;
|
||||
|
||||
# Remove leading and trailing commas and whitespace.
|
||||
$searchstring =~ s/(^[\s,]+|[\s,]+$)//g;
|
||||
ThrowUserError('buglist_parameters_required') unless ($searchstring);
|
||||
|
||||
if ($searchstring =~ m/^[0-9,\s]*$/) {
|
||||
# Bug number(s) only.
|
||||
|
||||
# Allow separation by comma or whitespace.
|
||||
$searchstring =~ s/[,\s]+/,/g;
|
||||
|
||||
if (index($searchstring, ',') < $[) {
|
||||
# Single bug number; shortcut to show_bug.cgi.
|
||||
print $cgi->redirect(-uri => "${urlbase}show_bug.cgi?id=$searchstring");
|
||||
exit;
|
||||
}
|
||||
else {
|
||||
# List of bug numbers.
|
||||
$cgi->param('bug_id', $searchstring);
|
||||
$cgi->param('order', 'bugs.bug_id');
|
||||
$cgi->param('bugidtype', 'include');
|
||||
}
|
||||
}
|
||||
else {
|
||||
# It's not just a bug number or a list of bug numbers.
|
||||
# Maybe it's an alias?
|
||||
if ($searchstring =~ /^([^,\s]+)$/) {
|
||||
if (Bugzilla->dbh->selectrow_array(q{SELECT COUNT(*)
|
||||
FROM bugs
|
||||
WHERE alias = ?},
|
||||
undef,
|
||||
$1)) {
|
||||
print $cgi->redirect(-uri => "${urlbase}show_bug.cgi?id=$1");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
# It's no alias either, so it's a more complex query.
|
||||
my $legal_statuses = get_legal_field_values('bug_status');
|
||||
my $legal_resolutions = get_legal_field_values('resolution');
|
||||
|
||||
# Globally translate " AND ", " OR ", " NOT " to space, pipe, dash.
|
||||
$searchstring =~ s/\s+AND\s+/ /g;
|
||||
$searchstring =~ s/\s+OR\s+/|/g;
|
||||
$searchstring =~ s/\s+NOT\s+/ -/g;
|
||||
|
||||
my @words = splitString($searchstring);
|
||||
my $searchComments =
|
||||
$#words < Bugzilla->params->{'quicksearch_comment_cutoff'};
|
||||
my @openStates = BUG_STATE_OPEN;
|
||||
my @closedStates;
|
||||
my @unknownFields;
|
||||
my (%states, %resolutions);
|
||||
|
||||
foreach (@$legal_statuses) {
|
||||
push(@closedStates, $_) unless is_open_state($_);
|
||||
}
|
||||
foreach (@openStates) { $states{$_} = 1 }
|
||||
if ($words[0] eq 'ALL') {
|
||||
foreach (@$legal_statuses) { $states{$_} = 1 }
|
||||
shift @words;
|
||||
}
|
||||
elsif ($words[0] eq 'OPEN') {
|
||||
shift @words;
|
||||
}
|
||||
elsif ($words[0] =~ /^\+[A-Z]+(,[A-Z]+)*$/) {
|
||||
# e.g. +DUP,FIX
|
||||
if (matchPrefixes(\%states,
|
||||
\%resolutions,
|
||||
[split(/,/, substr($words[0], 1))],
|
||||
\@closedStates,
|
||||
$legal_resolutions)) {
|
||||
shift @words;
|
||||
# Allowing additional resolutions means we need to keep
|
||||
# the "no resolution" resolution.
|
||||
$resolutions{'---'} = 1;
|
||||
}
|
||||
else {
|
||||
# Carry on if no match found.
|
||||
}
|
||||
}
|
||||
elsif ($words[0] =~ /^[A-Z]+(,[A-Z]+)*$/) {
|
||||
# e.g. NEW,ASSI,REOP,FIX
|
||||
undef %states;
|
||||
if (matchPrefixes(\%states,
|
||||
\%resolutions,
|
||||
[split(/,/, $words[0])],
|
||||
$legal_statuses,
|
||||
$legal_resolutions)) {
|
||||
shift @words;
|
||||
}
|
||||
else {
|
||||
# Carry on if no match found
|
||||
foreach (@openStates) { $states{$_} = 1 }
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Default: search for unresolved bugs only.
|
||||
# Put custom code here if you would like to change this behaviour.
|
||||
}
|
||||
|
||||
# If we have wanted resolutions, allow closed states
|
||||
if (keys(%resolutions)) {
|
||||
foreach (@closedStates) { $states{$_} = 1 }
|
||||
}
|
||||
|
||||
$cgi->param('bug_status', keys(%states));
|
||||
$cgi->param('resolution', keys(%resolutions));
|
||||
|
||||
# Loop over all main-level QuickSearch words.
|
||||
foreach my $qsword (@words) {
|
||||
my $negate = substr($qsword, 0, 1) eq '-';
|
||||
if ($negate) {
|
||||
$qsword = substr($qsword, 1);
|
||||
}
|
||||
|
||||
my $firstChar = substr($qsword, 0, 1);
|
||||
my $baseWord = substr($qsword, 1);
|
||||
my @subWords = split(/[\|,]/, $baseWord);
|
||||
if ($firstChar eq '+') {
|
||||
foreach (@subWords) {
|
||||
addChart('short_desc', 'substring', $_, $negate);
|
||||
}
|
||||
}
|
||||
elsif ($firstChar eq '#') {
|
||||
addChart('short_desc', 'anywords', $baseWord, $negate);
|
||||
if ($searchComments) {
|
||||
addChart('longdesc', 'anywords', $baseWord, $negate);
|
||||
}
|
||||
}
|
||||
elsif ($firstChar eq ':') {
|
||||
foreach (@subWords) {
|
||||
addChart('product', 'substring', $_, $negate);
|
||||
addChart('component', 'substring', $_, $negate);
|
||||
}
|
||||
}
|
||||
elsif ($firstChar eq '@') {
|
||||
foreach (@subWords) {
|
||||
addChart('assigned_to', 'substring', $_, $negate);
|
||||
}
|
||||
}
|
||||
elsif ($firstChar eq '[') {
|
||||
addChart('short_desc', 'substring', $baseWord, $negate);
|
||||
addChart('status_whiteboard', 'substring', $baseWord, $negate);
|
||||
}
|
||||
elsif ($firstChar eq '!') {
|
||||
addChart('keywords', 'anywords', $baseWord, $negate);
|
||||
|
||||
}
|
||||
else { # No special first char
|
||||
|
||||
# Split by '|' to get all operands for a boolean OR.
|
||||
foreach my $or_operand (split(/\|/, $qsword)) {
|
||||
if ($or_operand =~ /^votes:([0-9]+)$/) {
|
||||
# votes:xx ("at least xx votes")
|
||||
addChart('votes', 'greaterthan', $1 - 1, $negate);
|
||||
}
|
||||
elsif ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) {
|
||||
# Flag and requestee shortcut
|
||||
addChart('flagtypes.name', 'substring', $1, $negate);
|
||||
$chart++; $and = $or = 0; # Next chart for boolean AND
|
||||
addChart('requestees.login_name', 'substring', $2, $negate);
|
||||
}
|
||||
elsif ($or_operand =~ /^([^:]+):([^:]+)$/) {
|
||||
# generic field1,field2,field3:value1,value2 notation
|
||||
my @fields = split(/,/, $1);
|
||||
my @values = split(/,/, $2);
|
||||
foreach my $field (@fields) {
|
||||
# Skip and record any unknown fields
|
||||
if (!defined(MAPPINGS->{$field})) {
|
||||
push(@unknownFields, $field);
|
||||
next;
|
||||
}
|
||||
$field = MAPPINGS->{$field};
|
||||
foreach (@values) {
|
||||
addChart($field, 'substring', $_, $negate);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
# Having ruled out the special cases, we may now split
|
||||
# by comma, which is another legal boolean OR indicator.
|
||||
foreach my $word (split(/,/, $or_operand)) {
|
||||
# Platform and operating system
|
||||
if (grep({lc($word) eq $_} PLATFORMS)
|
||||
|| grep({lc($word) eq $_} OPSYSTEMS)) {
|
||||
addChart('rep_platform', 'substring',
|
||||
$word, $negate);
|
||||
addChart('op_sys', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
# Priority
|
||||
elsif ($word =~ m/^[pP]([1-5](-[1-5])?)$/) {
|
||||
addChart('priority', 'regexp',
|
||||
"[$1]", $negate);
|
||||
}
|
||||
# Severity
|
||||
elsif (grep({lc($word) eq substr($_, 0, 3)}
|
||||
@{get_legal_field_values('bug_severity')})) {
|
||||
addChart('bug_severity', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
# Votes (votes>xx)
|
||||
elsif ($word =~ m/^votes>([0-9]+)$/) {
|
||||
addChart('votes', 'greaterthan',
|
||||
$1, $negate);
|
||||
}
|
||||
# Votes (votes>=xx, votes=>xx)
|
||||
elsif ($word =~ m/^votes(>=|=>)([0-9]+)$/) {
|
||||
addChart('votes', 'greaterthan',
|
||||
$2-1, $negate);
|
||||
|
||||
}
|
||||
else { # Default QuickSearch word
|
||||
|
||||
if (!grep({lc($word) eq $_}
|
||||
PRODUCT_EXCEPTIONS) &&
|
||||
length($word)>2
|
||||
) {
|
||||
addChart('product', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
if (!grep({lc($word) eq $_}
|
||||
COMPONENT_EXCEPTIONS) &&
|
||||
length($word)>2
|
||||
) {
|
||||
addChart('component', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
if (grep({lc($word) eq lc($_)}
|
||||
map($_->name, Bugzilla::Keyword->get_all))) {
|
||||
addChart('keywords', 'substring',
|
||||
$word, $negate);
|
||||
if (length($word)>2) {
|
||||
addChart('short_desc', 'substring',
|
||||
$word, $negate);
|
||||
addChart('status_whiteboard',
|
||||
'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
|
||||
addChart('short_desc', 'substring',
|
||||
$word, $negate);
|
||||
addChart('status_whiteboard', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
if ($searchComments) {
|
||||
addChart('longdesc', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
}
|
||||
# URL field (for IP addrs, host.names,
|
||||
# scheme://urls)
|
||||
if ($word =~ m/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/
|
||||
|| $word =~ /^[A-Za-z]+(\.[A-Za-z]+)+/
|
||||
|| $word =~ /:[\\\/][\\\/]/
|
||||
|| $word =~ /localhost/
|
||||
|| $word =~ /mailto[:]?/
|
||||
# || $word =~ /[A-Za-z]+[:][0-9]+/ #host:port
|
||||
) {
|
||||
addChart('bug_file_loc', 'substring',
|
||||
$word, $negate);
|
||||
}
|
||||
} # foreach my $word (split(/,/, $qsword))
|
||||
} # votes and generic field detection
|
||||
} # foreach (split(/\|/, $_))
|
||||
} # "switch" $firstChar
|
||||
$chart++;
|
||||
$and = 0;
|
||||
$or = 0;
|
||||
} # foreach (@words)
|
||||
|
||||
# Inform user about any unknown fields
|
||||
if (scalar(@unknownFields)) {
|
||||
ThrowUserError("quicksearch_unknown_field",
|
||||
{ fields => \@unknownFields });
|
||||
}
|
||||
|
||||
# Make sure we have some query terms left
|
||||
scalar($cgi->param())>0 || ThrowUserError("buglist_parameters_required");
|
||||
}
|
||||
|
||||
# List of quicksearch-specific CGI parameters to get rid of.
|
||||
my @params_to_strip = ('quicksearch', 'load', 'run');
|
||||
my $modified_query_string = $cgi->canonicalise_query(@params_to_strip);
|
||||
|
||||
if ($cgi->param('load')) {
|
||||
# Param 'load' asks us to display the query in the advanced search form.
|
||||
print $cgi->redirect(-uri => "${urlbase}query.cgi?format=advanced&"
|
||||
. $modified_query_string);
|
||||
}
|
||||
|
||||
# Otherwise, pass the modified query string to the caller.
|
||||
# We modified $cgi->params, so the caller can choose to look at that, too,
|
||||
# and disregard the return value.
|
||||
$cgi->delete(@params_to_strip);
|
||||
return $modified_query_string;
|
||||
}
|
||||
|
||||
###########################################################################
|
||||
# Helpers
|
||||
###########################################################################
|
||||
|
||||
# Split string on whitespace, retaining quoted strings as one
|
||||
sub splitString {
|
||||
my $string = shift;
|
||||
my @quoteparts;
|
||||
my @parts;
|
||||
my $i = 0;
|
||||
|
||||
# Now split on quote sign; be tolerant about unclosed quotes
|
||||
@quoteparts = split(/"/, $string);
|
||||
foreach my $part (@quoteparts) {
|
||||
# After every odd quote, quote special chars
|
||||
$part = url_quote($part) if $i++ % 2;
|
||||
}
|
||||
# Join again
|
||||
$string = join('"', @quoteparts);
|
||||
|
||||
# Now split on unescaped whitespace
|
||||
@parts = split(/\s+/, $string);
|
||||
foreach (@parts) {
|
||||
# Protect plus signs from becoming a blank.
|
||||
# If "+" appears as the first character, leave it alone
|
||||
# as it has a special meaning. Strings which start with
|
||||
# "+" must be quoted.
|
||||
s/(?<!^)\+/%2B/g;
|
||||
# Remove quotes
|
||||
s/"//g;
|
||||
}
|
||||
return @parts;
|
||||
}
|
||||
|
||||
# Expand found prefixes to states or resolutions
|
||||
sub matchPrefixes {
|
||||
my $hr_states = shift;
|
||||
my $hr_resolutions = shift;
|
||||
my $ar_prefixes = shift;
|
||||
my $ar_check_states = shift;
|
||||
my $ar_check_resolutions = shift;
|
||||
my $foundMatch = 0;
|
||||
|
||||
foreach my $prefix (@$ar_prefixes) {
|
||||
foreach (@$ar_check_states) {
|
||||
if (/^$prefix/) {
|
||||
$$hr_states{$_} = 1;
|
||||
$foundMatch = 1;
|
||||
}
|
||||
}
|
||||
foreach (@$ar_check_resolutions) {
|
||||
if (/^$prefix/) {
|
||||
$$hr_resolutions{$_} = 1;
|
||||
$foundMatch = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $foundMatch;
|
||||
}
|
||||
|
||||
# Negate comparison type
|
||||
sub negateComparisonType {
|
||||
my $comparisonType = shift;
|
||||
|
||||
if ($comparisonType eq 'substring') {
|
||||
return 'notsubstring';
|
||||
}
|
||||
elsif ($comparisonType eq 'anywords') {
|
||||
return 'nowords';
|
||||
}
|
||||
elsif ($comparisonType eq 'regexp') {
|
||||
return 'notregexp';
|
||||
}
|
||||
else {
|
||||
# Don't know how to negate that
|
||||
ThrowCodeError('unknown_comparison_type');
|
||||
}
|
||||
}
|
||||
|
||||
# Add a boolean chart
|
||||
sub addChart {
|
||||
my ($field, $comparisonType, $value, $negate) = @_;
|
||||
|
||||
$negate && ($comparisonType = negateComparisonType($comparisonType));
|
||||
makeChart("$chart-$and-$or", $field, $comparisonType, $value);
|
||||
if ($negate) {
|
||||
$and++;
|
||||
$or = 0;
|
||||
}
|
||||
else {
|
||||
$or++;
|
||||
}
|
||||
}
|
||||
|
||||
# Create the CGI parameters for a boolean chart
|
||||
sub makeChart {
|
||||
my ($expr, $field, $type, $value) = @_;
|
||||
|
||||
my $cgi = Bugzilla->cgi;
|
||||
$cgi->param("field$expr", $field);
|
||||
$cgi->param("type$expr", $type);
|
||||
$cgi->param("value$expr", url_decode($value));
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,314 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Everything Solved.
|
||||
# Portions created by Everything Solved are Copyright (C) 2006
|
||||
# Everything Solved. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Search::Saved;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::CGI;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Group;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Search qw(IsValidQueryType);
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util;
|
||||
|
||||
#############
|
||||
# Constants #
|
||||
#############
|
||||
|
||||
use constant DB_TABLE => 'namedqueries';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
userid
|
||||
name
|
||||
query
|
||||
query_type
|
||||
);
|
||||
|
||||
use constant REQUIRED_CREATE_FIELDS => qw(name query);
|
||||
|
||||
use constant VALIDATORS => {
|
||||
name => \&_check_name,
|
||||
query => \&_check_query,
|
||||
query_type => \&_check_query_type,
|
||||
link_in_footer => \&_check_link_in_footer,
|
||||
};
|
||||
|
||||
use constant UPDATE_COLUMNS => qw(name query query_type);
|
||||
|
||||
##############
|
||||
# Validators #
|
||||
##############
|
||||
|
||||
sub _check_link_in_footer { return $_[1] ? 1 : 0; }
|
||||
|
||||
sub _check_name {
|
||||
my ($invocant, $name) = @_;
|
||||
$name = trim($name);
|
||||
$name || ThrowUserError("query_name_missing");
|
||||
$name !~ /[<>&]/ || ThrowUserError("illegal_query_name");
|
||||
if (length($name) > MAX_LEN_QUERY_NAME) {
|
||||
ThrowUserError("query_name_too_long");
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
sub _check_query {
|
||||
my ($invocant, $query) = @_;
|
||||
$query || ThrowUserError("buglist_parameters_required");
|
||||
my $cgi = new Bugzilla::CGI($query);
|
||||
$cgi->clean_search_url;
|
||||
# Don't store the query name as a parameter.
|
||||
$cgi->delete('known_name');
|
||||
return $cgi->query_string;
|
||||
}
|
||||
|
||||
sub _check_query_type {
|
||||
my ($invocant, $type) = @_;
|
||||
# Right now the only query type is LIST_OF_BUGS.
|
||||
return $type ? LIST_OF_BUGS : QUERY_LIST;
|
||||
}
|
||||
|
||||
#########################
|
||||
# Database Manipulation #
|
||||
#########################
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
Bugzilla->login(LOGIN_REQUIRED);
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$class->check_required_create_fields(@_);
|
||||
$dbh->bz_start_transaction();
|
||||
my $params = $class->run_create_validators(@_);
|
||||
|
||||
# Right now you can only create a Saved Search for the current user.
|
||||
$params->{userid} = Bugzilla->user->id;
|
||||
|
||||
my $lif = delete $params->{link_in_footer};
|
||||
my $obj = $class->insert_create_data($params);
|
||||
if ($lif) {
|
||||
$dbh->do('INSERT INTO namedqueries_link_in_footer
|
||||
(user_id, namedquery_id) VALUES (?,?)',
|
||||
undef, $params->{userid}, $obj->id);
|
||||
}
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
sub preload {
|
||||
my ($searches) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
return unless scalar @$searches;
|
||||
|
||||
my @query_ids = map { $_->id } @$searches;
|
||||
my $queries_in_footer = $dbh->selectcol_arrayref(
|
||||
'SELECT namedquery_id
|
||||
FROM namedqueries_link_in_footer
|
||||
WHERE ' . $dbh->sql_in('namedquery_id', \@query_ids) . ' AND user_id = ?',
|
||||
undef, Bugzilla->user->id);
|
||||
|
||||
my %links_in_footer = map { $_ => 1 } @$queries_in_footer;
|
||||
foreach my $query (@$searches) {
|
||||
$query->{link_in_footer} = ($links_in_footer{$query->id}) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
#####################
|
||||
# Complex Accessors #
|
||||
#####################
|
||||
|
||||
sub edit_link {
|
||||
my ($self) = @_;
|
||||
return $self->{edit_link} if defined $self->{edit_link};
|
||||
my $cgi = new Bugzilla::CGI($self->url);
|
||||
if (!$cgi->param('query_type')
|
||||
|| !IsValidQueryType($cgi->param('query_type')))
|
||||
{
|
||||
$cgi->param('query_type', 'advanced');
|
||||
}
|
||||
$self->{edit_link} = $cgi->canonicalise_query;
|
||||
return $self->{edit_link};
|
||||
}
|
||||
|
||||
sub used_in_whine {
|
||||
my ($self) = @_;
|
||||
return $self->{used_in_whine} if exists $self->{used_in_whine};
|
||||
($self->{used_in_whine}) = Bugzilla->dbh->selectrow_array(
|
||||
'SELECT 1 FROM whine_events INNER JOIN whine_queries
|
||||
ON whine_events.id = whine_queries.eventid
|
||||
WHERE whine_events.owner_userid = ? AND query_name = ?', undef,
|
||||
$self->{userid}, $self->name) || 0;
|
||||
return $self->{used_in_whine};
|
||||
}
|
||||
|
||||
sub link_in_footer {
|
||||
my ($self, $user) = @_;
|
||||
# We only cache link_in_footer for the current Bugzilla->user.
|
||||
return $self->{link_in_footer} if exists $self->{link_in_footer} && !$user;
|
||||
my $user_id = $user ? $user->id : Bugzilla->user->id;
|
||||
my $link_in_footer = Bugzilla->dbh->selectrow_array(
|
||||
'SELECT 1 FROM namedqueries_link_in_footer
|
||||
WHERE namedquery_id = ? AND user_id = ?',
|
||||
undef, $self->id, $user_id) || 0;
|
||||
$self->{link_in_footer} = $link_in_footer if !$user;
|
||||
return $link_in_footer;
|
||||
}
|
||||
|
||||
sub shared_with_group {
|
||||
my ($self) = @_;
|
||||
return $self->{shared_with_group} if exists $self->{shared_with_group};
|
||||
# Bugzilla only currently supports sharing with one group, even
|
||||
# though the database backend allows for an infinite number.
|
||||
my ($group_id) = Bugzilla->dbh->selectrow_array(
|
||||
'SELECT group_id FROM namedquery_group_map WHERE namedquery_id = ?',
|
||||
undef, $self->id);
|
||||
$self->{shared_with_group} = $group_id ? new Bugzilla::Group($group_id)
|
||||
: undef;
|
||||
return $self->{shared_with_group};
|
||||
}
|
||||
|
||||
sub shared_with_users {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!exists $self->{shared_with_users}) {
|
||||
$self->{shared_with_users} =
|
||||
$dbh->selectrow_array('SELECT COUNT(*)
|
||||
FROM namedqueries_link_in_footer
|
||||
INNER JOIN namedqueries
|
||||
ON namedquery_id = id
|
||||
WHERE namedquery_id = ?
|
||||
AND user_id != userid',
|
||||
undef, $self->id);
|
||||
}
|
||||
return $self->{shared_with_users};
|
||||
}
|
||||
|
||||
####################
|
||||
# Simple Accessors #
|
||||
####################
|
||||
|
||||
sub bug_ids_only { return ($_[0]->{'query_type'} == LIST_OF_BUGS) ? 1 : 0; }
|
||||
sub url { return $_[0]->{'query'}; }
|
||||
|
||||
sub user {
|
||||
my ($self) = @_;
|
||||
return $self->{user} if defined $self->{user};
|
||||
$self->{user} = new Bugzilla::User($self->{userid});
|
||||
return $self->{user};
|
||||
}
|
||||
|
||||
############
|
||||
# Mutators #
|
||||
############
|
||||
|
||||
sub set_name { $_[0]->set('name', $_[1]); }
|
||||
sub set_url { $_[0]->set('query', $_[1]); }
|
||||
sub set_query_type { $_[0]->set('query_type', $_[1]); }
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Search::Saved - A saved search
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Search::Saved;
|
||||
|
||||
my $query = new Bugzilla::Search::Saved($query_id);
|
||||
|
||||
my $edit_link = $query->edit_link;
|
||||
my $search_url = $query->url;
|
||||
my $owner = $query->user;
|
||||
my $num_subscribers = $query->shared_with_users;
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module exists to represent a L<Bugzilla::Search> that has been
|
||||
saved to the database.
|
||||
|
||||
This is an implementation of L<Bugzilla::Object>, and so has all the
|
||||
same methods available as L<Bugzilla::Object>, in addition to what is
|
||||
documented below.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 Constructors and Database Manipulation
|
||||
|
||||
=over
|
||||
|
||||
=item C<new>
|
||||
|
||||
Does not accept a bare C<name> argument. Instead, accepts only an id.
|
||||
|
||||
See also: L<Bugzilla::Object/new>.
|
||||
|
||||
=item C<preload>
|
||||
|
||||
Sets C<link_in_footer> for all given saved searches at once, for the
|
||||
currently logged in user. This is much faster than calling this method
|
||||
for each saved search individually.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head2 Accessors
|
||||
|
||||
These return data about the object, without modifying the object.
|
||||
|
||||
=over
|
||||
|
||||
=item C<edit_link>
|
||||
|
||||
A url with which you can edit the search.
|
||||
|
||||
=item C<url>
|
||||
|
||||
The CGI parameters for the search, as a string.
|
||||
|
||||
=item C<link_in_footer>
|
||||
|
||||
Whether or not this search should be displayed in the footer for the
|
||||
I<current user> (not the owner of the search, but the person actually
|
||||
using Bugzilla right now).
|
||||
|
||||
=item C<bug_ids_only>
|
||||
|
||||
True if the search contains only a list of Bug IDs.
|
||||
|
||||
=item C<shared_with_group>
|
||||
|
||||
The L<Bugzilla::Group> that this search is shared with. C<undef> if
|
||||
this search isn't shared.
|
||||
|
||||
=item C<shared_with_users>
|
||||
|
||||
Returns how many users (besides the author of the saved search) are
|
||||
using the saved search, i.e. have it displayed in their footer.
|
||||
|
||||
=back
|
||||
@@ -1,260 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Gervase Markham <gerv@gerv.net>
|
||||
# Lance Larsh <lance.larsh@oracle.com>
|
||||
|
||||
use strict;
|
||||
|
||||
# This module implements a series - a set of data to be plotted on a chart.
|
||||
#
|
||||
# This Series is in the database if and only if self->{'series_id'} is defined.
|
||||
# Note that the series being in the database does not mean that the fields of
|
||||
# this object are the same as the DB entries, as the object may have been
|
||||
# altered.
|
||||
|
||||
package Bugzilla::Series;
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util;
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $class = ref($invocant) || $invocant;
|
||||
|
||||
# Create a ref to an empty hash and bless it
|
||||
my $self = {};
|
||||
bless($self, $class);
|
||||
|
||||
my $arg_count = scalar(@_);
|
||||
|
||||
# new() can return undef if you pass in a series_id and the user doesn't
|
||||
# have sufficient permissions. If you create a new series in this way,
|
||||
# you need to check for an undef return, and act appropriately.
|
||||
my $retval = $self;
|
||||
|
||||
# There are three ways of creating Series objects. Two (CGI and Parameters)
|
||||
# are for use when creating a new series. One (Database) is for retrieving
|
||||
# information on existing series.
|
||||
if ($arg_count == 1) {
|
||||
if (ref($_[0])) {
|
||||
# We've been given a CGI object to create a new Series from.
|
||||
# This series may already exist - external code needs to check
|
||||
# before it calls writeToDatabase().
|
||||
$self->initFromCGI($_[0]);
|
||||
}
|
||||
else {
|
||||
# We've been given a series_id, which should represent an existing
|
||||
# Series.
|
||||
$retval = $self->initFromDatabase($_[0]);
|
||||
}
|
||||
}
|
||||
elsif ($arg_count >= 6 && $arg_count <= 8) {
|
||||
# We've been given a load of parameters to create a new Series from.
|
||||
# Currently, undef is always passed as the first parameter; this allows
|
||||
# you to call writeToDatabase() unconditionally.
|
||||
$self->initFromParameters(@_);
|
||||
}
|
||||
else {
|
||||
die("Bad parameters passed in - invalid number of args: $arg_count");
|
||||
}
|
||||
|
||||
return $retval;
|
||||
}
|
||||
|
||||
sub initFromDatabase {
|
||||
my $self = shift;
|
||||
my $series_id = shift;
|
||||
|
||||
detaint_natural($series_id)
|
||||
|| ThrowCodeError("invalid_series_id", { 'series_id' => $series_id });
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my @series = $dbh->selectrow_array("SELECT series.series_id, cc1.name, " .
|
||||
"cc2.name, series.name, series.creator, series.frequency, " .
|
||||
"series.query, series.is_public " .
|
||||
"FROM series " .
|
||||
"LEFT JOIN series_categories AS cc1 " .
|
||||
" ON series.category = cc1.id " .
|
||||
"LEFT JOIN series_categories AS cc2 " .
|
||||
" ON series.subcategory = cc2.id " .
|
||||
"LEFT JOIN category_group_map AS cgm " .
|
||||
" ON series.category = cgm.category_id " .
|
||||
"LEFT JOIN user_group_map AS ugm " .
|
||||
" ON cgm.group_id = ugm.group_id " .
|
||||
" AND ugm.user_id = " . Bugzilla->user->id .
|
||||
" AND isbless = 0 " .
|
||||
"WHERE series.series_id = $series_id AND " .
|
||||
"(is_public = 1 OR creator = " . Bugzilla->user->id . " OR " .
|
||||
"(ugm.group_id IS NOT NULL)) " .
|
||||
$dbh->sql_group_by('series.series_id', 'cc1.name, cc2.name, ' .
|
||||
'series.name, series.creator, series.frequency, ' .
|
||||
'series.query, series.is_public'));
|
||||
|
||||
if (@series) {
|
||||
$self->initFromParameters(@series);
|
||||
return $self;
|
||||
}
|
||||
else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub initFromParameters {
|
||||
# Pass undef as the first parameter if you are creating a new series.
|
||||
my $self = shift;
|
||||
|
||||
($self->{'series_id'}, $self->{'category'}, $self->{'subcategory'},
|
||||
$self->{'name'}, $self->{'creator'}, $self->{'frequency'},
|
||||
$self->{'query'}, $self->{'public'}) = @_;
|
||||
|
||||
# If the first parameter is undefined, check if this series already
|
||||
# exists and update it series_id accordingly
|
||||
$self->{'series_id'} ||= $self->existsInDatabase();
|
||||
}
|
||||
|
||||
sub initFromCGI {
|
||||
my $self = shift;
|
||||
my $cgi = shift;
|
||||
|
||||
$self->{'series_id'} = $cgi->param('series_id') || undef;
|
||||
if (defined($self->{'series_id'})) {
|
||||
detaint_natural($self->{'series_id'})
|
||||
|| ThrowCodeError("invalid_series_id",
|
||||
{ 'series_id' => $self->{'series_id'} });
|
||||
}
|
||||
|
||||
$self->{'category'} = $cgi->param('category')
|
||||
|| $cgi->param('newcategory')
|
||||
|| ThrowUserError("missing_category");
|
||||
|
||||
$self->{'subcategory'} = $cgi->param('subcategory')
|
||||
|| $cgi->param('newsubcategory')
|
||||
|| ThrowUserError("missing_subcategory");
|
||||
|
||||
$self->{'name'} = $cgi->param('name')
|
||||
|| ThrowUserError("missing_name");
|
||||
|
||||
$self->{'creator'} = Bugzilla->user->id;
|
||||
|
||||
$self->{'frequency'} = $cgi->param('frequency');
|
||||
detaint_natural($self->{'frequency'})
|
||||
|| ThrowUserError("missing_frequency");
|
||||
|
||||
$self->{'query'} = $cgi->canonicalise_query("format", "ctype", "action",
|
||||
"category", "subcategory", "name",
|
||||
"frequency", "public", "query_format");
|
||||
trick_taint($self->{'query'});
|
||||
|
||||
$self->{'public'} = $cgi->param('public') ? 1 : 0;
|
||||
|
||||
# Change 'admin' here and in series.html.tmpl, or remove the check
|
||||
# completely, if you want to change who can make series public.
|
||||
$self->{'public'} = 0 unless Bugzilla->user->in_group('admin');
|
||||
}
|
||||
|
||||
sub writeToDatabase {
|
||||
my $self = shift;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
my $category_id = getCategoryID($self->{'category'});
|
||||
my $subcategory_id = getCategoryID($self->{'subcategory'});
|
||||
|
||||
my $exists;
|
||||
if ($self->{'series_id'}) {
|
||||
$exists =
|
||||
$dbh->selectrow_array("SELECT series_id FROM series
|
||||
WHERE series_id = $self->{'series_id'}");
|
||||
}
|
||||
|
||||
# Is this already in the database?
|
||||
if ($exists) {
|
||||
# Update existing series
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->do("UPDATE series SET " .
|
||||
"category = ?, subcategory = ?," .
|
||||
"name = ?, frequency = ?, is_public = ? " .
|
||||
"WHERE series_id = ?", undef,
|
||||
$category_id, $subcategory_id, $self->{'name'},
|
||||
$self->{'frequency'}, $self->{'public'},
|
||||
$self->{'series_id'});
|
||||
}
|
||||
else {
|
||||
# Insert the new series into the series table
|
||||
$dbh->do("INSERT INTO series (creator, category, subcategory, " .
|
||||
"name, frequency, query, is_public) VALUES " .
|
||||
"(?, ?, ?, ?, ?, ?, ?)", undef,
|
||||
$self->{'creator'}, $category_id, $subcategory_id, $self->{'name'},
|
||||
$self->{'frequency'}, $self->{'query'}, $self->{'public'});
|
||||
|
||||
# Retrieve series_id
|
||||
$self->{'series_id'} = $dbh->selectrow_array("SELECT MAX(series_id) " .
|
||||
"FROM series");
|
||||
$self->{'series_id'}
|
||||
|| ThrowCodeError("missing_series_id", { 'series' => $self });
|
||||
}
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
}
|
||||
|
||||
# Check whether a series with this name, category and subcategory exists in
|
||||
# the DB and, if so, returns its series_id.
|
||||
sub existsInDatabase {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $category_id = getCategoryID($self->{'category'});
|
||||
my $subcategory_id = getCategoryID($self->{'subcategory'});
|
||||
|
||||
trick_taint($self->{'name'});
|
||||
my $series_id = $dbh->selectrow_array("SELECT series_id " .
|
||||
"FROM series WHERE category = $category_id " .
|
||||
"AND subcategory = $subcategory_id AND name = " .
|
||||
$dbh->quote($self->{'name'}));
|
||||
|
||||
return($series_id);
|
||||
}
|
||||
|
||||
# Get a category or subcategory IDs, creating the category if it doesn't exist.
|
||||
sub getCategoryID {
|
||||
my ($category) = @_;
|
||||
my $category_id;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# This seems for the best idiom for "Do A. Then maybe do B and A again."
|
||||
while (1) {
|
||||
# We are quoting this to put it in the DB, so we can remove taint
|
||||
trick_taint($category);
|
||||
|
||||
$category_id = $dbh->selectrow_array("SELECT id " .
|
||||
"from series_categories " .
|
||||
"WHERE name =" . $dbh->quote($category));
|
||||
|
||||
last if defined($category_id);
|
||||
|
||||
$dbh->do("INSERT INTO series_categories (name) " .
|
||||
"VALUES (" . $dbh->quote($category) . ")");
|
||||
}
|
||||
|
||||
return $category_id;
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,342 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Frédéric Buclin.
|
||||
# Portions created by Frédéric Buclin are Copyright (C) 2007
|
||||
# Frédéric Buclin. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Status;
|
||||
|
||||
use Bugzilla::Error;
|
||||
|
||||
use base qw(Bugzilla::Field::Choice Exporter);
|
||||
@Bugzilla::Status::EXPORT = qw(
|
||||
BUG_STATE_OPEN
|
||||
SPECIAL_STATUS_WORKFLOW_ACTIONS
|
||||
|
||||
is_open_state
|
||||
closed_bug_statuses
|
||||
);
|
||||
|
||||
################################
|
||||
##### Initialization #####
|
||||
################################
|
||||
|
||||
use constant SPECIAL_STATUS_WORKFLOW_ACTIONS => qw(
|
||||
none
|
||||
duplicate
|
||||
change_resolution
|
||||
clearresolution
|
||||
);
|
||||
|
||||
use constant DB_TABLE => 'bug_status';
|
||||
|
||||
# This has all the standard Bugzilla::Field::Choice columns plus "is_open"
|
||||
sub DB_COLUMNS {
|
||||
return ($_[0]->SUPER::DB_COLUMNS, 'is_open');
|
||||
}
|
||||
|
||||
sub VALIDATORS {
|
||||
my $invocant = shift;
|
||||
my $validators = $invocant->SUPER::VALIDATORS;
|
||||
$validators->{is_open} = \&Bugzilla::Object::check_boolean;
|
||||
$validators->{value} = \&_check_value;
|
||||
return $validators;
|
||||
}
|
||||
|
||||
#########################
|
||||
# Database Manipulation #
|
||||
#########################
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
my $self = $class->SUPER::create(@_);
|
||||
add_missing_bug_status_transitions();
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $id = $self->id;
|
||||
$dbh->bz_start_transaction();
|
||||
$self->SUPER::remove_from_db();
|
||||
$dbh->do('DELETE FROM status_workflow
|
||||
WHERE old_status = ? OR new_status = ?',
|
||||
undef, $id, $id);
|
||||
$dbh->bz_commit_transaction();
|
||||
}
|
||||
|
||||
###############################
|
||||
##### Accessors ####
|
||||
###############################
|
||||
|
||||
sub is_active { return $_[0]->{'isactive'}; }
|
||||
sub is_open { return $_[0]->{'is_open'}; }
|
||||
|
||||
sub is_static {
|
||||
my $self = shift;
|
||||
if ($self->name eq 'UNCONFIRMED'
|
||||
|| $self->name eq Bugzilla->params->{'duplicate_or_move_bug_status'})
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
##############
|
||||
# Validators #
|
||||
##############
|
||||
|
||||
sub _check_value {
|
||||
my $invocant = shift;
|
||||
my $value = $invocant->SUPER::_check_value(@_);
|
||||
|
||||
if (grep { lc($value) eq lc($_) } SPECIAL_STATUS_WORKFLOW_ACTIONS) {
|
||||
ThrowUserError('fieldvalue_reserved_word',
|
||||
{ field => $invocant->field, value => $value });
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
###############################
|
||||
##### Methods ####
|
||||
###############################
|
||||
|
||||
sub BUG_STATE_OPEN {
|
||||
# XXX - We should cache this list.
|
||||
my $dbh = Bugzilla->dbh;
|
||||
return @{$dbh->selectcol_arrayref('SELECT value FROM bug_status WHERE is_open = 1')};
|
||||
}
|
||||
|
||||
# Tells you whether or not the argument is a valid "open" state.
|
||||
sub is_open_state {
|
||||
my ($state) = @_;
|
||||
return (grep($_ eq $state, BUG_STATE_OPEN) ? 1 : 0);
|
||||
}
|
||||
|
||||
sub closed_bug_statuses {
|
||||
my @bug_statuses = Bugzilla::Status->get_all;
|
||||
@bug_statuses = grep { !$_->is_open } @bug_statuses;
|
||||
return @bug_statuses;
|
||||
}
|
||||
|
||||
sub can_change_to {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!ref($self) || !defined $self->{'can_change_to'}) {
|
||||
my ($cond, @args, $self_exists);
|
||||
if (ref($self)) {
|
||||
$cond = '= ?';
|
||||
push(@args, $self->id);
|
||||
$self_exists = 1;
|
||||
}
|
||||
else {
|
||||
$cond = 'IS NULL';
|
||||
# Let's do it so that the code below works in all cases.
|
||||
$self = {};
|
||||
}
|
||||
|
||||
my $new_status_ids = $dbh->selectcol_arrayref("SELECT new_status
|
||||
FROM status_workflow
|
||||
INNER JOIN bug_status
|
||||
ON id = new_status
|
||||
WHERE isactive = 1
|
||||
AND old_status $cond
|
||||
ORDER BY sortkey",
|
||||
undef, @args);
|
||||
|
||||
# Allow the bug status to remain unchanged.
|
||||
push(@$new_status_ids, $self->id) if $self_exists;
|
||||
$self->{'can_change_to'} = Bugzilla::Status->new_from_list($new_status_ids);
|
||||
}
|
||||
|
||||
return $self->{'can_change_to'};
|
||||
}
|
||||
|
||||
sub can_change_from {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'can_change_from'}) {
|
||||
my $old_status_ids = $dbh->selectcol_arrayref('SELECT old_status
|
||||
FROM status_workflow
|
||||
INNER JOIN bug_status
|
||||
ON id = old_status
|
||||
WHERE isactive = 1
|
||||
AND new_status = ?
|
||||
AND old_status IS NOT NULL',
|
||||
undef, $self->id);
|
||||
|
||||
# Allow the bug status to remain unchanged.
|
||||
push(@$old_status_ids, $self->id);
|
||||
$self->{'can_change_from'} = Bugzilla::Status->new_from_list($old_status_ids);
|
||||
}
|
||||
|
||||
return $self->{'can_change_from'};
|
||||
}
|
||||
|
||||
sub comment_required_on_change_from {
|
||||
my ($self, $old_status) = @_;
|
||||
my ($cond, $values) = $self->_status_condition($old_status);
|
||||
|
||||
my ($require_comment) = Bugzilla->dbh->selectrow_array(
|
||||
"SELECT require_comment FROM status_workflow
|
||||
WHERE $cond", undef, @$values);
|
||||
return $require_comment;
|
||||
}
|
||||
|
||||
# Used as a helper for various functions that have to deal with old_status
|
||||
# sometimes being NULL and sometimes having a value.
|
||||
sub _status_condition {
|
||||
my ($self, $old_status) = @_;
|
||||
my @values;
|
||||
my $cond = 'old_status IS NULL';
|
||||
# For newly-filed bugs
|
||||
if ($old_status) {
|
||||
$cond = 'old_status = ?';
|
||||
push(@values, $old_status->id);
|
||||
}
|
||||
$cond .= " AND new_status = ?";
|
||||
push(@values, $self->id);
|
||||
return ($cond, \@values);
|
||||
}
|
||||
|
||||
sub add_missing_bug_status_transitions {
|
||||
my $bug_status = shift || Bugzilla->params->{'duplicate_or_move_bug_status'};
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $new_status = new Bugzilla::Status({name => $bug_status});
|
||||
# Silently discard invalid bug statuses.
|
||||
$new_status || return;
|
||||
|
||||
my $missing_statuses = $dbh->selectcol_arrayref('SELECT id
|
||||
FROM bug_status
|
||||
LEFT JOIN status_workflow
|
||||
ON old_status = id
|
||||
AND new_status = ?
|
||||
WHERE old_status IS NULL',
|
||||
undef, $new_status->id);
|
||||
|
||||
my $sth = $dbh->prepare('INSERT INTO status_workflow
|
||||
(old_status, new_status) VALUES (?, ?)');
|
||||
|
||||
foreach my $old_status_id (@$missing_statuses) {
|
||||
next if ($old_status_id == $new_status->id);
|
||||
$sth->execute($old_status_id, $new_status->id);
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Status - Bug status class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Status;
|
||||
|
||||
my $bug_status = new Bugzilla::Status({name => 'ASSIGNED'});
|
||||
my $bug_status = new Bugzilla::Status(4);
|
||||
|
||||
my @closed_bug_statuses = closed_bug_statuses();
|
||||
|
||||
Bugzilla::Status::add_missing_bug_status_transitions($bug_status);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Status.pm represents a bug status object. It is an implementation
|
||||
of L<Bugzilla::Object>, and thus provides all methods that
|
||||
L<Bugzilla::Object> provides.
|
||||
|
||||
The methods that are specific to C<Bugzilla::Status> are listed
|
||||
below.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<closed_bug_statuses>
|
||||
|
||||
Description: Returns a list of C<Bugzilla::Status> objects which can have
|
||||
a resolution associated with them ("closed" bug statuses).
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A list of Bugzilla::Status objects.
|
||||
|
||||
=item C<can_change_to>
|
||||
|
||||
Description: Returns the list of active statuses a bug can be changed to
|
||||
given the current bug status. If this method is called as a
|
||||
class method, then it returns all bug statuses available on
|
||||
bug creation.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A list of Bugzilla::Status objects.
|
||||
|
||||
=item C<can_change_from>
|
||||
|
||||
Description: Returns the list of active statuses a bug can be changed from
|
||||
given the new bug status. If the bug status is available on
|
||||
bug creation, this method doesn't return this information.
|
||||
You have to call C<can_change_to> instead.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: A list of Bugzilla::Status objects.
|
||||
|
||||
=item C<comment_required_on_change_from>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Checks if a comment is required to change to this status from another
|
||||
status, according to the current settings in the workflow.
|
||||
|
||||
Note that this doesn't implement the checks enforced by the various
|
||||
C<commenton> parameters--those are checked by internal checks in
|
||||
L<Bugzilla::Bug>.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
C<$old_status> - The status you're changing from.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
C<1> if a comment is required on this change, C<0> if not.
|
||||
|
||||
=back
|
||||
|
||||
=item C<add_missing_bug_status_transitions>
|
||||
|
||||
Description: Insert all missing transitions to a given bug status.
|
||||
|
||||
Params: $bug_status - The value (name) of a bug status.
|
||||
|
||||
Returns: nothing.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,953 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Terry Weissman <terry@mozilla.org>
|
||||
# Dan Mosedale <dmose@mozilla.org>
|
||||
# Jacob Steenhagen <jake@bugzilla.org>
|
||||
# Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Christopher Aillon <christopher@aillon.com>
|
||||
# Tobias Burnus <burnus@net-b.de>
|
||||
# Myk Melez <myk@mozilla.org>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
# Greg Hendricks <ghendricks@novell.com>
|
||||
# David D. Kilzer <ddkilzer@kilzer.net>
|
||||
|
||||
|
||||
package Bugzilla::Template;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Install::Requirements;
|
||||
use Bugzilla::Install::Util qw(install_string template_include_path include_languages);
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Status;
|
||||
use Bugzilla::Token;
|
||||
use Bugzilla::Template::Parser;
|
||||
|
||||
use Cwd qw(abs_path);
|
||||
use MIME::Base64;
|
||||
use Date::Format ();
|
||||
use File::Basename qw(dirname);
|
||||
use File::Find;
|
||||
use File::Path qw(rmtree mkpath);
|
||||
use File::Spec;
|
||||
use IO::Dir;
|
||||
|
||||
use base qw(Template);
|
||||
|
||||
# As per the Template::Base documentation, the _init() method is being called
|
||||
# by the new() constructor. We take advantage of this in order to plug our
|
||||
# UTF-8-aware Parser object in neatly after the original _init() method has
|
||||
# happened, in particular, after having set up the constants namespace.
|
||||
# See bug 413121 for details.
|
||||
sub _init {
|
||||
my $self = shift;
|
||||
my $config = $_[0];
|
||||
|
||||
$self->SUPER::_init(@_) || return undef;
|
||||
|
||||
$self->{PARSER} = $config->{PARSER}
|
||||
= new Bugzilla::Template::Parser($config);
|
||||
|
||||
# Now we need to re-create the default Service object, making it aware
|
||||
# of our Parser object.
|
||||
$self->{SERVICE} = $config->{SERVICE}
|
||||
= Template::Config->service($config);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
# Convert the constants in the Bugzilla::Constants module into a hash we can
|
||||
# pass to the template object for reflection into its "constants" namespace
|
||||
# (which is like its "variables" namespace, but for constants). To do so, we
|
||||
# traverse the arrays of exported and exportable symbols and ignoring the rest
|
||||
# (which, if Constants.pm exports only constants, as it should, will be nothing else).
|
||||
sub _load_constants {
|
||||
my %constants;
|
||||
foreach my $constant (@Bugzilla::Constants::EXPORT,
|
||||
@Bugzilla::Constants::EXPORT_OK)
|
||||
{
|
||||
if (ref Bugzilla::Constants->$constant) {
|
||||
$constants{$constant} = Bugzilla::Constants->$constant;
|
||||
}
|
||||
else {
|
||||
my @list = (Bugzilla::Constants->$constant);
|
||||
$constants{$constant} = (scalar(@list) == 1) ? $list[0] : \@list;
|
||||
}
|
||||
}
|
||||
return \%constants;
|
||||
}
|
||||
|
||||
# Returns the path to the templates based on the Accept-Language
|
||||
# settings of the user and of the available languages
|
||||
# If no Accept-Language is present it uses the defined default
|
||||
# Templates may also be found in the extensions/ tree
|
||||
sub getTemplateIncludePath {
|
||||
my $cache = Bugzilla->request_cache;
|
||||
my $lang = $cache->{'language'} || '';
|
||||
$cache->{"template_include_path_$lang"} ||= template_include_path({
|
||||
use_languages => Bugzilla->languages,
|
||||
only_language => $lang });
|
||||
return $cache->{"template_include_path_$lang"};
|
||||
}
|
||||
|
||||
sub get_format {
|
||||
my $self = shift;
|
||||
my ($template, $format, $ctype) = @_;
|
||||
|
||||
$ctype ||= 'html';
|
||||
$format ||= '';
|
||||
|
||||
# Security - allow letters and a hyphen only
|
||||
$ctype =~ s/[^a-zA-Z\-]//g;
|
||||
$format =~ s/[^a-zA-Z\-]//g;
|
||||
trick_taint($ctype);
|
||||
trick_taint($format);
|
||||
|
||||
$template .= ($format ? "-$format" : "");
|
||||
$template .= ".$ctype.tmpl";
|
||||
|
||||
# Now check that the template actually exists. We only want to check
|
||||
# if the template exists; any other errors (eg parse errors) will
|
||||
# end up being detected later.
|
||||
eval {
|
||||
$self->context->template($template);
|
||||
};
|
||||
# This parsing may seem fragile, but it's OK:
|
||||
# http://lists.template-toolkit.org/pipermail/templates/2003-March/004370.html
|
||||
# Even if it is wrong, any sort of error is going to cause a failure
|
||||
# eventually, so the only issue would be an incorrect error message
|
||||
if ($@ && $@->info =~ /: not found$/) {
|
||||
ThrowUserError('format_not_found', {'format' => $format,
|
||||
'ctype' => $ctype});
|
||||
}
|
||||
|
||||
# Else, just return the info
|
||||
return
|
||||
{
|
||||
'template' => $template,
|
||||
'extension' => $ctype,
|
||||
'ctype' => Bugzilla::Constants::contenttypes->{$ctype}
|
||||
};
|
||||
}
|
||||
|
||||
# This routine quoteUrls contains inspirations from the HTML::FromText CPAN
|
||||
# module by Gareth Rees <garethr@cre.canon.co.uk>. It has been heavily hacked,
|
||||
# all that is really recognizable from the original is bits of the regular
|
||||
# expressions.
|
||||
# This has been rewritten to be faster, mainly by substituting 'as we go'.
|
||||
# If you want to modify this routine, read the comments carefully
|
||||
|
||||
sub quoteUrls {
|
||||
my ($text, $curr_bugid, $already_wrapped) = (@_);
|
||||
return $text unless $text;
|
||||
|
||||
# We use /g for speed, but uris can have other things inside them
|
||||
# (http://foo/bug#3 for example). Filtering that out filters valid
|
||||
# bug refs out, so we have to do replacements.
|
||||
# mailto can't contain space or #, so we don't have to bother for that
|
||||
# Do this by escaping \0 to \1\0, and replacing matches with \0\0$count\0\0
|
||||
# \0 is used because it's unlikely to occur in the text, so the cost of
|
||||
# doing this should be very small
|
||||
|
||||
# escape the 2nd escape char we're using
|
||||
my $chr1 = chr(1);
|
||||
$text =~ s/\0/$chr1\0/g;
|
||||
|
||||
# If the comment is already wrapped, we should ignore newlines when
|
||||
# looking for matching regexps. Else we should take them into account.
|
||||
my $s = $already_wrapped ? qr/\s/ : qr/[[:blank:]]/;
|
||||
|
||||
# However, note that adding the title (for buglinks) can affect things
|
||||
# In particular, attachment matches go before bug titles, so that titles
|
||||
# with 'attachment 1' don't double match.
|
||||
# Dupe checks go afterwards, because that uses ^ and \Z, which won't occur
|
||||
# if it was substituted as a bug title (since that always involve leading
|
||||
# and trailing text)
|
||||
|
||||
# Because of entities, it's easier (and quicker) to do this before escaping
|
||||
|
||||
my @things;
|
||||
my $count = 0;
|
||||
my $tmp;
|
||||
|
||||
# Provide tooltips for full bug links (Bug 74355)
|
||||
my $urlbase_re = '(' . join('|',
|
||||
map { qr/$_/ } grep($_, Bugzilla->params->{'urlbase'},
|
||||
Bugzilla->params->{'sslbase'})) . ')';
|
||||
$text =~ s~\b(${urlbase_re}\Qshow_bug.cgi?id=\E([0-9]+)(\#c([0-9]+))?)\b
|
||||
~($things[$count++] = get_bug_link($3, $1, { comment_num => $5 })) &&
|
||||
("\0\0" . ($count-1) . "\0\0")
|
||||
~egox;
|
||||
|
||||
# non-mailto protocols
|
||||
my $safe_protocols = join('|', SAFE_PROTOCOLS);
|
||||
my $protocol_re = qr/($safe_protocols)/i;
|
||||
|
||||
$text =~ s~\b(${protocol_re}: # The protocol:
|
||||
[^\s<>\"]+ # Any non-whitespace
|
||||
[\w\/]) # so that we end in \w or /
|
||||
~($tmp = html_quote($1)) &&
|
||||
($things[$count++] = "<a href=\"$tmp\">$tmp</a>") &&
|
||||
("\0\0" . ($count-1) . "\0\0")
|
||||
~egox;
|
||||
|
||||
# We have to quote now, otherwise the html itself is escaped
|
||||
# THIS MEANS THAT A LITERAL ", <, >, ' MUST BE ESCAPED FOR A MATCH
|
||||
|
||||
$text = html_quote($text);
|
||||
|
||||
# Color quoted text
|
||||
$text =~ s~^(>.+)$~<span class="quote">$1</span >~mg;
|
||||
$text =~ s~</span >\n<span class="quote">~\n~g;
|
||||
|
||||
# mailto:
|
||||
# Use |<nothing> so that $1 is defined regardless
|
||||
$text =~ s~\b(mailto:|)?([\w\.\-\+\=]+\@[\w\-]+(?:\.[\w\-]+)+)\b
|
||||
~<a href=\"mailto:$2\">$1$2</a>~igx;
|
||||
|
||||
# attachment links - handle both cases separately for simplicity
|
||||
$text =~ s~((?:^Created\ an\ |\b)attachment\s*\(id=(\d+)\)(\s\[edit\])?)
|
||||
~($things[$count++] = get_attachment_link($2, $1)) &&
|
||||
("\0\0" . ($count-1) . "\0\0")
|
||||
~egmx;
|
||||
|
||||
$text =~ s~\b(attachment$s*\#?$s*(\d+))
|
||||
~($things[$count++] = get_attachment_link($2, $1)) &&
|
||||
("\0\0" . ($count-1) . "\0\0")
|
||||
~egmxi;
|
||||
|
||||
# Current bug ID this comment belongs to
|
||||
my $current_bugurl = $curr_bugid ? "show_bug.cgi?id=$curr_bugid" : "";
|
||||
|
||||
# This handles bug a, comment b type stuff. Because we're using /g
|
||||
# we have to do this in one pattern, and so this is semi-messy.
|
||||
# Also, we can't use $bug_re?$comment_re? because that will match the
|
||||
# empty string
|
||||
my $bug_word = get_text('term', { term => 'bug' });
|
||||
my $bug_re = qr/\Q$bug_word\E$s*\#?$s*(\d+)/i;
|
||||
my $comment_re = qr/comment$s*\#?$s*(\d+)/i;
|
||||
$text =~ s~\b($bug_re(?:$s*,?$s*$comment_re)?|$comment_re)
|
||||
~ # We have several choices. $1 here is the link, and $2-4 are set
|
||||
# depending on which part matched
|
||||
(defined($2) ? get_bug_link($2, $1, { comment_num => $3 }) :
|
||||
"<a href=\"$current_bugurl#c$4\">$1</a>")
|
||||
~egox;
|
||||
|
||||
# Old duplicate markers. These don't use $bug_word because they are old
|
||||
# and were never customizable.
|
||||
$text =~ s~(?<=^\*\*\*\ This\ bug\ has\ been\ marked\ as\ a\ duplicate\ of\ )
|
||||
(\d+)
|
||||
(?=\ \*\*\*\Z)
|
||||
~get_bug_link($1, $1)
|
||||
~egmx;
|
||||
|
||||
# Now remove the encoding hacks
|
||||
$text =~ s/\0\0(\d+)\0\0/$things[$1]/eg;
|
||||
$text =~ s/$chr1\0/\0/g;
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
# Creates a link to an attachment, including its title.
|
||||
sub get_attachment_link {
|
||||
my ($attachid, $link_text) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
detaint_natural($attachid)
|
||||
|| die "get_attachment_link() called with non-integer attachment number";
|
||||
|
||||
my ($bugid, $isobsolete, $desc) =
|
||||
$dbh->selectrow_array('SELECT bug_id, isobsolete, description
|
||||
FROM attachments WHERE attach_id = ?',
|
||||
undef, $attachid);
|
||||
|
||||
if ($bugid) {
|
||||
my $title = "";
|
||||
my $className = "";
|
||||
if (Bugzilla->user->can_see_bug($bugid)) {
|
||||
$title = $desc;
|
||||
}
|
||||
if ($isobsolete) {
|
||||
$className = "bz_obsolete";
|
||||
}
|
||||
# Prevent code injection in the title.
|
||||
$title = html_quote(clean_text($title));
|
||||
|
||||
$link_text =~ s/ \[details\]$//;
|
||||
my $linkval = "attachment.cgi?id=$attachid";
|
||||
# Whitespace matters here because these links are in <pre> tags.
|
||||
return qq|<span class="$className">|
|
||||
. qq|<a href="${linkval}" name="attach_${attachid}" title="$title">$link_text</a>|
|
||||
. qq| <a href="${linkval}&action=edit" title="$title">[details]</a>|
|
||||
. qq|</span>|;
|
||||
}
|
||||
else {
|
||||
return qq{$link_text};
|
||||
}
|
||||
}
|
||||
|
||||
# Creates a link to a bug, including its title.
|
||||
# It takes either two or three parameters:
|
||||
# - The bug number
|
||||
# - The link text, to place between the <a>..</a>
|
||||
# - An optional comment number, for linking to a particular
|
||||
# comment in the bug
|
||||
|
||||
sub get_bug_link {
|
||||
my ($bug_num, $link_text, $options) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined($bug_num) || ($bug_num eq "")) {
|
||||
return "<missing bug number>";
|
||||
}
|
||||
my $quote_bug_num = html_quote($bug_num);
|
||||
detaint_natural($bug_num) || return "<invalid bug number: $quote_bug_num>";
|
||||
|
||||
my ($bug_alias, $bug_state, $bug_res, $bug_desc) =
|
||||
$dbh->selectrow_array('SELECT bugs.alias, bugs.bug_status, bugs.resolution, bugs.short_desc
|
||||
FROM bugs WHERE bugs.bug_id = ?',
|
||||
undef, $bug_num);
|
||||
|
||||
if ($options->{use_alias} && $link_text =~ /^\d+$/ && $bug_alias) {
|
||||
$link_text = $bug_alias;
|
||||
}
|
||||
|
||||
if ($bug_state) {
|
||||
# Initialize these variables to be "" so that we don't get warnings
|
||||
# if we don't change them below (which is highly likely).
|
||||
my ($pre, $title, $post) = ("", "", "");
|
||||
|
||||
$title = get_text('get_status', {status => $bug_state});
|
||||
if ($bug_state eq 'UNCONFIRMED') {
|
||||
$pre = "<i>";
|
||||
$post = "</i>";
|
||||
}
|
||||
elsif (!is_open_state($bug_state)) {
|
||||
$pre = '<span class="bz_closed">';
|
||||
$title .= ' ' . get_text('get_resolution', {resolution => $bug_res});
|
||||
$post = '</span>';
|
||||
}
|
||||
if (Bugzilla->user->can_see_bug($bug_num)) {
|
||||
$title .= " - $bug_desc";
|
||||
}
|
||||
# Prevent code injection in the title.
|
||||
$title = html_quote(clean_text($title));
|
||||
|
||||
my $linkval = "show_bug.cgi?id=$bug_num";
|
||||
if ($options->{comment_num}) {
|
||||
$linkval .= "#c" . $options->{comment_num};
|
||||
}
|
||||
return qq{$pre<a href="$linkval" title="$title">$link_text</a>$post};
|
||||
}
|
||||
else {
|
||||
return qq{$link_text};
|
||||
}
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Templatization Code
|
||||
|
||||
# The Template Toolkit throws an error if a loop iterates >1000 times.
|
||||
# We want to raise that limit.
|
||||
# NOTE: If you change this number, you MUST RE-RUN checksetup.pl!!!
|
||||
# If you do not re-run checksetup.pl, the change you make will not apply
|
||||
$Template::Directive::WHILE_MAX = 1000000;
|
||||
|
||||
# Use the Toolkit Template's Stash module to add utility pseudo-methods
|
||||
# to template variables.
|
||||
use Template::Stash;
|
||||
|
||||
# Add "contains***" methods to list variables that search for one or more
|
||||
# items in a list and return boolean values representing whether or not
|
||||
# one/all/any item(s) were found.
|
||||
$Template::Stash::LIST_OPS->{ contains } =
|
||||
sub {
|
||||
my ($list, $item) = @_;
|
||||
return grep($_ eq $item, @$list);
|
||||
};
|
||||
|
||||
$Template::Stash::LIST_OPS->{ containsany } =
|
||||
sub {
|
||||
my ($list, $items) = @_;
|
||||
foreach my $item (@$items) {
|
||||
return 1 if grep($_ eq $item, @$list);
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
# Clone the array reference to leave the original one unaltered.
|
||||
$Template::Stash::LIST_OPS->{ clone } =
|
||||
sub {
|
||||
my $list = shift;
|
||||
return [@$list];
|
||||
};
|
||||
|
||||
# Allow us to still get the scalar if we use the list operation ".0" on it,
|
||||
# as we often do for defaults in query.cgi and other places.
|
||||
$Template::Stash::SCALAR_OPS->{ 0 } =
|
||||
sub {
|
||||
return $_[0];
|
||||
};
|
||||
|
||||
# Add a "substr" method to the Template Toolkit's "scalar" object
|
||||
# that returns a substring of a string.
|
||||
$Template::Stash::SCALAR_OPS->{ substr } =
|
||||
sub {
|
||||
my ($scalar, $offset, $length) = @_;
|
||||
return substr($scalar, $offset, $length);
|
||||
};
|
||||
|
||||
# Add a "truncate" method to the Template Toolkit's "scalar" object
|
||||
# that truncates a string to a certain length.
|
||||
$Template::Stash::SCALAR_OPS->{ truncate } =
|
||||
sub {
|
||||
my ($string, $length, $ellipsis) = @_;
|
||||
$ellipsis ||= "";
|
||||
|
||||
return $string if !$length || length($string) <= $length;
|
||||
|
||||
my $strlen = $length - length($ellipsis);
|
||||
my $newstr = substr($string, 0, $strlen) . $ellipsis;
|
||||
return $newstr;
|
||||
};
|
||||
|
||||
# Create the template object that processes templates and specify
|
||||
# configuration parameters that apply to all templates.
|
||||
|
||||
###############################################################################
|
||||
|
||||
# Construct the Template object
|
||||
|
||||
# Note that all of the failure cases here can't use templateable errors,
|
||||
# since we won't have a template to use...
|
||||
|
||||
sub create {
|
||||
my $class = shift;
|
||||
my %opts = @_;
|
||||
|
||||
# checksetup.pl will call us once for any template/lang directory.
|
||||
# We need a possibility to reset the cache, so that no files from
|
||||
# the previous language pollute the action.
|
||||
if ($opts{'clean_cache'}) {
|
||||
delete Bugzilla->request_cache->{template_include_path_};
|
||||
}
|
||||
|
||||
# IMPORTANT - If you make any configuration changes here, make sure to
|
||||
# make them in t/004.template.t and checksetup.pl.
|
||||
|
||||
return $class->new({
|
||||
# Colon-separated list of directories containing templates.
|
||||
INCLUDE_PATH => [\&getTemplateIncludePath],
|
||||
|
||||
# Remove white-space before template directives (PRE_CHOMP) and at the
|
||||
# beginning and end of templates and template blocks (TRIM) for better
|
||||
# looking, more compact content. Use the plus sign at the beginning
|
||||
# of directives to maintain white space (i.e. [%+ DIRECTIVE %]).
|
||||
PRE_CHOMP => 1,
|
||||
TRIM => 1,
|
||||
|
||||
COMPILE_DIR => bz_locations()->{'datadir'} . "/template",
|
||||
|
||||
# Initialize templates (f.e. by loading plugins like Hook).
|
||||
PRE_PROCESS => "global/initialize.none.tmpl",
|
||||
|
||||
# Functions for processing text within templates in various ways.
|
||||
# IMPORTANT! When adding a filter here that does not override a
|
||||
# built-in filter, please also add a stub filter to t/004template.t.
|
||||
FILTERS => {
|
||||
|
||||
# Render text in required style.
|
||||
|
||||
inactive => [
|
||||
sub {
|
||||
my($context, $isinactive) = @_;
|
||||
return sub {
|
||||
return $isinactive ? '<span class="bz_inactive">'.$_[0].'</span>' : $_[0];
|
||||
}
|
||||
}, 1
|
||||
],
|
||||
|
||||
closed => [
|
||||
sub {
|
||||
my($context, $isclosed) = @_;
|
||||
return sub {
|
||||
return $isclosed ? '<span class="bz_closed">'.$_[0].'</span>' : $_[0];
|
||||
}
|
||||
}, 1
|
||||
],
|
||||
|
||||
obsolete => [
|
||||
sub {
|
||||
my($context, $isobsolete) = @_;
|
||||
return sub {
|
||||
return $isobsolete ? '<span class="bz_obsolete">'.$_[0].'</span>' : $_[0];
|
||||
}
|
||||
}, 1
|
||||
],
|
||||
|
||||
# Returns the text with backslashes, single/double quotes,
|
||||
# and newlines/carriage returns escaped for use in JS strings.
|
||||
js => sub {
|
||||
my ($var) = @_;
|
||||
$var =~ s/([\\\'\"\/])/\\$1/g;
|
||||
$var =~ s/\n/\\n/g;
|
||||
$var =~ s/\r/\\r/g;
|
||||
$var =~ s/\@/\\x40/g; # anti-spam for email addresses
|
||||
return $var;
|
||||
},
|
||||
|
||||
# Converts data to base64
|
||||
base64 => sub {
|
||||
my ($data) = @_;
|
||||
return encode_base64($data);
|
||||
},
|
||||
|
||||
# HTML collapses newlines in element attributes to a single space,
|
||||
# so form elements which may have whitespace (ie comments) need
|
||||
# to be encoded using 
|
||||
# See bugs 4928, 22983 and 32000 for more details
|
||||
html_linebreak => sub {
|
||||
my ($var) = @_;
|
||||
$var =~ s/\r\n/\
/g;
|
||||
$var =~ s/\n\r/\
/g;
|
||||
$var =~ s/\r/\
/g;
|
||||
$var =~ s/\n/\
/g;
|
||||
return $var;
|
||||
},
|
||||
|
||||
# Prevents line break on hyphens and whitespaces.
|
||||
no_break => sub {
|
||||
my ($var) = @_;
|
||||
$var =~ s/ /\ /g;
|
||||
$var =~ s/-/\‑/g;
|
||||
return $var;
|
||||
},
|
||||
|
||||
xml => \&Bugzilla::Util::xml_quote ,
|
||||
|
||||
# This filter escapes characters in a variable or value string for
|
||||
# use in a query string. It escapes all characters NOT in the
|
||||
# regex set: [a-zA-Z0-9_\-.]. The 'uri' filter should be used for
|
||||
# a full URL that may have characters that need encoding.
|
||||
url_quote => \&Bugzilla::Util::url_quote ,
|
||||
|
||||
# This filter is similar to url_quote but used a \ instead of a %
|
||||
# as prefix. In addition it replaces a ' ' by a '_'.
|
||||
css_class_quote => \&Bugzilla::Util::css_class_quote ,
|
||||
|
||||
quoteUrls => [ sub {
|
||||
my ($context, $bug, $already_wrapped) = @_;
|
||||
return sub {
|
||||
my $text = shift;
|
||||
return quoteUrls($text, $bug, $already_wrapped);
|
||||
};
|
||||
},
|
||||
1
|
||||
],
|
||||
|
||||
bug_link => [ sub {
|
||||
my ($context, $bug, $options) = @_;
|
||||
return sub {
|
||||
my $text = shift;
|
||||
return get_bug_link($bug, $text, $options);
|
||||
};
|
||||
},
|
||||
1
|
||||
],
|
||||
|
||||
bug_list_link => sub
|
||||
{
|
||||
my $buglist = shift;
|
||||
return join(", ", map(get_bug_link($_, $_), split(/ *, */, $buglist)));
|
||||
},
|
||||
|
||||
# In CSV, quotes are doubled, and any value containing a quote or a
|
||||
# comma is enclosed in quotes.
|
||||
csv => sub
|
||||
{
|
||||
my ($var) = @_;
|
||||
$var =~ s/\"/\"\"/g;
|
||||
if ($var !~ /^-?(\d+\.)?\d*$/) {
|
||||
$var = "\"$var\"";
|
||||
}
|
||||
return $var;
|
||||
} ,
|
||||
|
||||
# Format a filesize in bytes to a human readable value
|
||||
unitconvert => sub
|
||||
{
|
||||
my ($data) = @_;
|
||||
my $retval = "";
|
||||
my %units = (
|
||||
'KB' => 1024,
|
||||
'MB' => 1024 * 1024,
|
||||
'GB' => 1024 * 1024 * 1024,
|
||||
);
|
||||
|
||||
if ($data < 1024) {
|
||||
return "$data bytes";
|
||||
}
|
||||
else {
|
||||
my $u;
|
||||
foreach $u ('GB', 'MB', 'KB') {
|
||||
if ($data >= $units{$u}) {
|
||||
return sprintf("%.2f %s", $data/$units{$u}, $u);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
# Format a time for display (more info in Bugzilla::Util)
|
||||
time => [ sub {
|
||||
my ($context, $format, $timezone) = @_;
|
||||
return sub {
|
||||
my $time = shift;
|
||||
return format_time($time, $format, $timezone);
|
||||
};
|
||||
},
|
||||
1
|
||||
],
|
||||
|
||||
# Bug 120030: Override html filter to obscure the '@' in user
|
||||
# visible strings.
|
||||
# Bug 319331: Handle BiDi disruptions.
|
||||
html => sub {
|
||||
my ($var) = Template::Filters::html_filter(@_);
|
||||
# Obscure '@'.
|
||||
$var =~ s/\@/\@/g;
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
# Remove the following characters because they're
|
||||
# influencing BiDi:
|
||||
# --------------------------------------------------------
|
||||
# |Code |Name |UTF-8 representation|
|
||||
# |------|--------------------------|--------------------|
|
||||
# |U+202a|Left-To-Right Embedding |0xe2 0x80 0xaa |
|
||||
# |U+202b|Right-To-Left Embedding |0xe2 0x80 0xab |
|
||||
# |U+202c|Pop Directional Formatting|0xe2 0x80 0xac |
|
||||
# |U+202d|Left-To-Right Override |0xe2 0x80 0xad |
|
||||
# |U+202e|Right-To-Left Override |0xe2 0x80 0xae |
|
||||
# --------------------------------------------------------
|
||||
#
|
||||
# The following are characters influencing BiDi, too, but
|
||||
# they can be spared from filtering because they don't
|
||||
# influence more than one character right or left:
|
||||
# --------------------------------------------------------
|
||||
# |Code |Name |UTF-8 representation|
|
||||
# |------|--------------------------|--------------------|
|
||||
# |U+200e|Left-To-Right Mark |0xe2 0x80 0x8e |
|
||||
# |U+200f|Right-To-Left Mark |0xe2 0x80 0x8f |
|
||||
# --------------------------------------------------------
|
||||
$var =~ s/[\x{202a}-\x{202e}]//g;
|
||||
}
|
||||
return $var;
|
||||
},
|
||||
|
||||
html_light => \&Bugzilla::Util::html_light_quote,
|
||||
|
||||
email => \&Bugzilla::Util::email_filter,
|
||||
|
||||
# iCalendar contentline filter
|
||||
ics => [ sub {
|
||||
my ($context, @args) = @_;
|
||||
return sub {
|
||||
my ($var) = shift;
|
||||
my ($par) = shift @args;
|
||||
my ($output) = "";
|
||||
|
||||
$var =~ s/[\r\n]/ /g;
|
||||
$var =~ s/([;\\\",])/\\$1/g;
|
||||
|
||||
if ($par) {
|
||||
$output = sprintf("%s:%s", $par, $var);
|
||||
} else {
|
||||
$output = $var;
|
||||
}
|
||||
|
||||
$output =~ s/(.{75,75})/$1\n /g;
|
||||
|
||||
return $output;
|
||||
};
|
||||
},
|
||||
1
|
||||
],
|
||||
|
||||
# Note that using this filter is even more dangerous than
|
||||
# using "none," and you should only use it when you're SURE
|
||||
# the output won't be displayed directly to a web browser.
|
||||
txt => sub {
|
||||
my ($var) = @_;
|
||||
# Trivial HTML tag remover
|
||||
$var =~ s/<[^>]*>//g;
|
||||
# And this basically reverses the html filter.
|
||||
$var =~ s/\@/@/g;
|
||||
$var =~ s/\</</g;
|
||||
$var =~ s/\>/>/g;
|
||||
$var =~ s/\"/\"/g;
|
||||
$var =~ s/\&/\&/g;
|
||||
# Now remove extra whitespace, and wrap it to 72 characters.
|
||||
my $collapse_filter = $Template::Filters::FILTERS->{collapse};
|
||||
$var = $collapse_filter->($var);
|
||||
$var = wrap_comment($var, 72);
|
||||
return $var;
|
||||
},
|
||||
|
||||
# Wrap a displayed comment to the appropriate length
|
||||
wrap_comment => [
|
||||
sub {
|
||||
my ($context, $cols) = @_;
|
||||
return sub { wrap_comment($_[0], $cols) }
|
||||
}, 1],
|
||||
|
||||
# We force filtering of every variable in key security-critical
|
||||
# places; we have a none filter for people to use when they
|
||||
# really, really don't want a variable to be changed.
|
||||
none => sub { return $_[0]; } ,
|
||||
},
|
||||
|
||||
PLUGIN_BASE => 'Bugzilla::Template::Plugin',
|
||||
|
||||
CONSTANTS => _load_constants(),
|
||||
|
||||
# Default variables for all templates
|
||||
VARIABLES => {
|
||||
# Function for retrieving global parameters.
|
||||
'Param' => sub { return Bugzilla->params->{$_[0]}; },
|
||||
|
||||
# Function to create date strings
|
||||
'time2str' => \&Date::Format::time2str,
|
||||
|
||||
# Generic linear search function
|
||||
'lsearch' => \&Bugzilla::Util::lsearch,
|
||||
|
||||
# Currently logged in user, if any
|
||||
# If an sudo session is in progress, this is the user we're faking
|
||||
'user' => sub { return Bugzilla->user; },
|
||||
|
||||
# If an sudo session is in progress, this is the user who
|
||||
# started the session.
|
||||
'sudoer' => sub { return Bugzilla->sudoer; },
|
||||
|
||||
# SendBugMail - sends mail about a bug, using Bugzilla::BugMail.pm
|
||||
'SendBugMail' => sub {
|
||||
my ($id, $mailrecipients) = (@_);
|
||||
require Bugzilla::BugMail;
|
||||
Bugzilla::BugMail::Send($id, $mailrecipients);
|
||||
},
|
||||
|
||||
# Allow templates to access the "corect" URLBase value
|
||||
'urlbase' => sub { return Bugzilla::Util::correct_urlbase(); },
|
||||
|
||||
# Allow templates to access docs url with users' preferred language
|
||||
'docs_urlbase' => sub {
|
||||
my ($language) = include_languages();
|
||||
my $docs_urlbase = Bugzilla->params->{'docs_urlbase'};
|
||||
$docs_urlbase =~ s/\%lang\%/$language/;
|
||||
return $docs_urlbase;
|
||||
},
|
||||
|
||||
# Allow templates to generate a token themselves.
|
||||
'issue_hash_token' => \&Bugzilla::Token::issue_hash_token,
|
||||
|
||||
# A way for all templates to get at Field data, cached.
|
||||
'bug_fields' => sub {
|
||||
my $cache = Bugzilla->request_cache;
|
||||
$cache->{template_bug_fields} ||=
|
||||
{ map { $_->name => $_ } Bugzilla->get_fields() };
|
||||
return $cache->{template_bug_fields};
|
||||
},
|
||||
|
||||
# These don't work as normal constants.
|
||||
DB_MODULE => \&Bugzilla::Constants::DB_MODULE,
|
||||
REQUIRED_MODULES =>
|
||||
\&Bugzilla::Install::Requirements::REQUIRED_MODULES,
|
||||
OPTIONAL_MODULES => sub {
|
||||
my @optional = @{OPTIONAL_MODULES()};
|
||||
@optional = sort {$a->{feature} cmp $b->{feature}}
|
||||
@optional;
|
||||
return \@optional;
|
||||
},
|
||||
},
|
||||
|
||||
}) || die("Template creation failed: " . $class->error());
|
||||
}
|
||||
|
||||
# Used as part of the two subroutines below.
|
||||
our (%_templates_to_precompile, $_current_path);
|
||||
|
||||
sub precompile_templates {
|
||||
my ($output) = @_;
|
||||
|
||||
# Remove the compiled templates.
|
||||
my $datadir = bz_locations()->{'datadir'};
|
||||
if (-e "$datadir/template") {
|
||||
print install_string('template_removing_dir') . "\n" if $output;
|
||||
|
||||
# XXX This frequently fails if the webserver made the files, because
|
||||
# then the webserver owns the directories. We could fix that by
|
||||
# doing a chmod/chown on all the directories here.
|
||||
rmtree("$datadir/template");
|
||||
|
||||
# Check that the directory was really removed
|
||||
if(-e "$datadir/template") {
|
||||
print "\n\n";
|
||||
print "The directory '$datadir/template' could not be removed.\n";
|
||||
print "Please remove it manually and rerun checksetup.pl.\n\n";
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
print install_string('template_precompile') if $output;
|
||||
|
||||
my $templatedir = bz_locations()->{'templatedir'};
|
||||
# Don't hang on templates which use the CGI library
|
||||
eval("use CGI qw(-no_debug)");
|
||||
|
||||
my $dir_reader = new IO::Dir($templatedir) || die "$templatedir: $!";
|
||||
my @language_dirs = grep { /^[a-z-]+$/i } $dir_reader->read;
|
||||
$dir_reader->close;
|
||||
|
||||
foreach my $dir (@language_dirs) {
|
||||
next if ($dir eq 'CVS');
|
||||
-d "$templatedir/$dir/default" || -d "$templatedir/$dir/custom"
|
||||
|| next;
|
||||
local $ENV{'HTTP_ACCEPT_LANGUAGE'} = $dir;
|
||||
my $template = Bugzilla::Template->create(clean_cache => 1);
|
||||
|
||||
# Precompile all the templates found in all the directories.
|
||||
%_templates_to_precompile = ();
|
||||
foreach my $subdir (qw(custom extension default), bz_locations()->{'project'}) {
|
||||
next unless $subdir; # If 'project' is empty.
|
||||
$_current_path = File::Spec->catdir($templatedir, $dir, $subdir);
|
||||
next unless -d $_current_path;
|
||||
# Traverse the template hierarchy.
|
||||
find({ wanted => \&_precompile_push, no_chdir => 1 }, $_current_path);
|
||||
}
|
||||
# The sort isn't totally necessary, but it makes debugging easier
|
||||
# by making the templates always be compiled in the same order.
|
||||
foreach my $file (sort keys %_templates_to_precompile) {
|
||||
# Compile the template but throw away the result. This has the side-
|
||||
# effect of writing the compiled version to disk.
|
||||
$template->context->template($file);
|
||||
}
|
||||
}
|
||||
|
||||
# Under mod_perl, we look for templates using the absolute path of the
|
||||
# template directory, which causes Template Toolkit to look for their
|
||||
# *compiled* versions using the full absolute path under the data/template
|
||||
# directory. (Like data/template/var/www/html/mod_perl/.) To avoid
|
||||
# re-compiling templates under mod_perl, we symlink to the
|
||||
# already-compiled templates. This doesn't work on Windows.
|
||||
if (!ON_WINDOWS) {
|
||||
my $abs_root = dirname(abs_path($templatedir));
|
||||
my $todir = "$datadir/template$abs_root";
|
||||
mkpath($todir);
|
||||
# We use abs2rel so that the symlink will look like
|
||||
# "../../../../template" which works, while just
|
||||
# "data/template/template/" doesn't work.
|
||||
my $fromdir = File::Spec->abs2rel("$datadir/template/template", $todir);
|
||||
# We eval for systems that can't symlink at all, where "symlink"
|
||||
# throws a fatal error.
|
||||
eval { symlink($fromdir, "$todir/template")
|
||||
or warn "Failed to symlink from $fromdir to $todir: $!" };
|
||||
}
|
||||
|
||||
# If anything created a Template object before now, clear it out.
|
||||
delete Bugzilla->request_cache->{template};
|
||||
# This is the single variable used to precompile templates,
|
||||
# which needs to be cleared as well.
|
||||
delete Bugzilla->request_cache->{template_include_path_};
|
||||
|
||||
print install_string('done') . "\n" if $output;
|
||||
}
|
||||
|
||||
# Helper for precompile_templates
|
||||
sub _precompile_push {
|
||||
my $name = $File::Find::name;
|
||||
return if (-d $name);
|
||||
return if ($name =~ /\/CVS\//);
|
||||
return if ($name !~ /\.tmpl$/);
|
||||
|
||||
$name =~ s/\Q$_current_path\E\///;
|
||||
$_templates_to_precompile{$name} = 1;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template - Wrapper around the Template Toolkit C<Template> object
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
my $template = Bugzilla::Template->create;
|
||||
my $format = $template->get_format("foo/bar",
|
||||
scalar($cgi->param('format')),
|
||||
scalar($cgi->param('ctype')));
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is basically a wrapper so that the correct arguments get passed into
|
||||
the C<Template> constructor.
|
||||
|
||||
It should not be used directly by scripts or modules - instead, use
|
||||
C<Bugzilla-E<gt>instance-E<gt>template> to get an already created module.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<precompile_templates($output)>
|
||||
|
||||
Description: Compiles all of Bugzilla's templates in every language.
|
||||
Used mostly by F<checksetup.pl>.
|
||||
|
||||
Params: C<$output> - C<true> if you want the function to print
|
||||
out information about what it's doing.
|
||||
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_format($file, $format, $ctype)>
|
||||
|
||||
Description: Construct a format object from URL parameters.
|
||||
|
||||
Params: $file - Name of the template to display.
|
||||
$format - When the template exists under several formats
|
||||
(e.g. table or graph), specify the one to choose.
|
||||
$ctype - Content type, see Bugzilla::Constants::contenttypes.
|
||||
|
||||
Returns: A format object.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla>, L<Template>
|
||||
@@ -1,66 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Marc Schumann.
|
||||
# Portions created by Marc Schumann are Copyright (C) 2008 Marc Schumann.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
|
||||
package Bugzilla::Template::Parser;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Template::Parser);
|
||||
|
||||
sub parse {
|
||||
my ($self, $text, @params) = @_;
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
utf8::is_utf8($text) || utf8::decode($text);
|
||||
}
|
||||
return $self->SUPER::parse($text, @params);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template::Parser - Wrapper around the Template Toolkit
|
||||
C<Template::Parser> object
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This wrapper makes the Template Toolkit aware of UTF-8 templates.
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<parse($options)>
|
||||
|
||||
Description: Parses template text using Template::Parser::parse(),
|
||||
converting the text to UTF-8 encoding before, if necessary.
|
||||
|
||||
Params: C<$text> - Text to pass to Template::Parser::parse().
|
||||
|
||||
Returns: Parsed text as returned by Template::Parser::parse().
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Template>
|
||||
@@ -1,64 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
#
|
||||
|
||||
package Bugzilla::Template::Plugin::Bugzilla;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Template::Plugin);
|
||||
|
||||
use Bugzilla;
|
||||
|
||||
sub new {
|
||||
my ($class, $context) = @_;
|
||||
|
||||
return bless {}, $class;
|
||||
}
|
||||
|
||||
sub AUTOLOAD {
|
||||
my $class = shift;
|
||||
our $AUTOLOAD;
|
||||
|
||||
$AUTOLOAD =~ s/^.*:://;
|
||||
|
||||
return if $AUTOLOAD eq 'DESTROY';
|
||||
|
||||
return Bugzilla->$AUTOLOAD(@_);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template::Plugin::Bugzilla
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Template Toolkit plugin to allow access to the persistent C<Bugzilla>
|
||||
object.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla>, L<Template::Plugin>
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Myk Melez <myk@mozilla.org>
|
||||
# Zach Lipton <zach@zachlipton.com>
|
||||
# Elliotte Martin <everythingsolved.com>
|
||||
#
|
||||
|
||||
package Bugzilla::Template::Plugin::Hook;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Install::Util qw(include_languages);
|
||||
use Bugzilla::Template;
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
use File::Spec;
|
||||
|
||||
use base qw(Template::Plugin);
|
||||
|
||||
sub load {
|
||||
my ($class, $context) = @_;
|
||||
return $class;
|
||||
}
|
||||
|
||||
sub new {
|
||||
my ($class, $context) = @_;
|
||||
return bless { _CONTEXT => $context }, $class;
|
||||
}
|
||||
|
||||
sub process {
|
||||
my ($self, $hook_name, $template) = @_;
|
||||
$template ||= $self->{_CONTEXT}->stash->{component}->{name};
|
||||
|
||||
my @hooks;
|
||||
|
||||
# sanity check:
|
||||
if (!$template =~ /[\w\.\/\-_\\]+/) {
|
||||
ThrowCodeError('template_invalid', { name => $template});
|
||||
}
|
||||
|
||||
# also get extension hook files that live in extensions/:
|
||||
# parse out the parts of the template name
|
||||
my ($vol, $subpath, $filename) = File::Spec->splitpath($template);
|
||||
$subpath = $subpath || '';
|
||||
$filename =~ m/(.*)\.(.*)\.tmpl/;
|
||||
my $templatename = $1;
|
||||
my $type = $2;
|
||||
# munge the filename to create the extension hook filename:
|
||||
my $extensiontemplate = $subpath.'/'.$templatename.'-'.$hook_name.'.'.$type.'.tmpl';
|
||||
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
|
||||
my @usedlanguages = include_languages({use_languages => Bugzilla->languages});
|
||||
foreach my $extension (@extensions) {
|
||||
next if -e "$extension/disabled";
|
||||
foreach my $language (@usedlanguages) {
|
||||
my $file = $extension.'/template/'.$language.'/'.$extensiontemplate;
|
||||
if (-e $file) {
|
||||
# tt is stubborn and won't take a template file not in its
|
||||
# include path, so we open a filehandle and give it to process()
|
||||
# so the hook gets invoked:
|
||||
open (my $fh, $file);
|
||||
push(@hooks, $fh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $paths = $self->{_CONTEXT}->{LOAD_TEMPLATES}->[0]->paths;
|
||||
|
||||
# we keep this too since you can still put hook templates in
|
||||
# template/en/custom/hook
|
||||
foreach my $path (@$paths) {
|
||||
my @files = glob("$path/hook/$template/$hook_name/*.tmpl");
|
||||
|
||||
# Have to remove the templates path (INCLUDE_PATH) from the
|
||||
# file path since the template processor auto-adds it back.
|
||||
@files = map($_ =~ /^$path\/(.*)$/ ? $1 : {}, @files);
|
||||
|
||||
# Add found files to the list of hooks, but removing duplicates,
|
||||
# which can happen when there are identical hooks or duplicate
|
||||
# directories in the INCLUDE_PATH (the latter probably being a TT bug).
|
||||
foreach my $file (@files) {
|
||||
push(@hooks, $file) unless grep($file eq $_, @hooks);
|
||||
}
|
||||
}
|
||||
|
||||
my $output;
|
||||
foreach my $hook (@hooks) {
|
||||
$output .= $self->{_CONTEXT}->process($hook);
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template::Plugin::Hook
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Template Toolkit plugin to process hooks added into templates by extensions.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item B<process>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Processes hooks added into templates by extensions.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<hook_name>
|
||||
|
||||
The unique name of the template hook.
|
||||
|
||||
=item C<template> (optional)
|
||||
|
||||
The path of the calling template.
|
||||
This is used as a work around to a bug which causes the path to the hook
|
||||
to be incorrect when the hook is called from inside a block.
|
||||
|
||||
Example: If the hook C<lastrow> is added to the template
|
||||
F<show-multiple.html.tmpl> and it is desired to force the correct template
|
||||
path, the template hook would be:
|
||||
|
||||
[% Hook.process("lastrow", "bug/show-multiple.html.tmpl") %]
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
Output from processing template extension.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Template::Plugin>
|
||||
|
||||
L<http://www.bugzilla.org/docs/tip/html/customization.html>
|
||||
|
||||
L<http://bugzilla.mozilla.org/show_bug.cgi?id=229658>
|
||||
|
||||
L<http://bugzilla.mozilla.org/show_bug.cgi?id=298341>
|
||||
@@ -1,65 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Netscape Communications
|
||||
# Corporation. Portions created by Netscape are
|
||||
# Copyright (C) 1998 Netscape Communications Corporation. All
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Bradley Baetz <bbaetz@student.usyd.edu.au>
|
||||
# Joel Peshkin <bugreport@peshkin.net>
|
||||
#
|
||||
|
||||
package Bugzilla::Template::Plugin::User;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Template::Plugin);
|
||||
|
||||
use Bugzilla::User;
|
||||
|
||||
sub new {
|
||||
my ($class, $context) = @_;
|
||||
|
||||
return bless {}, $class;
|
||||
}
|
||||
|
||||
sub AUTOLOAD {
|
||||
my $class = shift;
|
||||
our $AUTOLOAD;
|
||||
|
||||
$AUTOLOAD =~ s/^.*:://;
|
||||
|
||||
return if $AUTOLOAD eq 'DESTROY';
|
||||
|
||||
return Bugzilla::User->$AUTOLOAD(@_);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Template::Plugin::User
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Template Toolkit plugin to allow access to the C<User>
|
||||
object.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Bugzilla::User>, L<Template::Plugin>
|
||||
|
||||
@@ -1,621 +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 Digest::MD5 qw(md5_hex);
|
||||
|
||||
use base qw(Exporter);
|
||||
|
||||
@Bugzilla::Token::EXPORT = qw(issue_session_token check_token_data delete_token
|
||||
issue_hash_token check_hash_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->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
|
||||
$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->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
|
||||
$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->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
|
||||
# The user is not logged in (else he wouldn't request a new password).
|
||||
# So we have to pass this information to the template.
|
||||
$vars->{'timezone'} = $user->timezone;
|
||||
|
||||
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 issue_hash_token {
|
||||
my ($data, $time) = @_;
|
||||
$data ||= [];
|
||||
$time ||= time();
|
||||
|
||||
# The concatenated string is of the form
|
||||
# token creation time + site-wide secret + user ID + data
|
||||
my @args = ($time, Bugzilla->localconfig->{'site_wide_secret'}, Bugzilla->user->id, @$data);
|
||||
|
||||
my $token = join('*', @args);
|
||||
# Wide characters cause md5_hex() to die.
|
||||
if (Bugzilla->params->{'utf8'}) {
|
||||
utf8::encode($token) if utf8::is_utf8($token);
|
||||
}
|
||||
$token = md5_hex($token);
|
||||
|
||||
# Prepend the token creation time, unencrypted, so that the token
|
||||
# lifetime can be validated.
|
||||
return $time . '-' . $token;
|
||||
}
|
||||
|
||||
sub check_hash_token {
|
||||
my ($token, $data) = @_;
|
||||
$data ||= [];
|
||||
my ($time, $expected_token);
|
||||
|
||||
if ($token) {
|
||||
($time, undef) = split(/-/, $token);
|
||||
# Regenerate the token based on the information we have.
|
||||
$expected_token = issue_hash_token($data, $time);
|
||||
}
|
||||
|
||||
if (!$token
|
||||
|| $expected_token ne $token
|
||||
|| time() - $time > MAX_TOKEN_AGE * 86400)
|
||||
{
|
||||
my $template = Bugzilla->template;
|
||||
my $vars = {};
|
||||
$vars->{'script_name'} = basename($0);
|
||||
$vars->{'token'} = issue_hash_token($data);
|
||||
$vars->{'reason'} = (!$token) ? 'missing_token' :
|
||||
($expected_token ne $token) ? 'invalid_token' :
|
||||
'expired_token';
|
||||
print Bugzilla->cgi->header();
|
||||
$template->process('global/confirm-action.html.tmpl', $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
exit;
|
||||
}
|
||||
|
||||
# If we come here, then the token is valid and not too old.
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub CleanTokenTable {
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$dbh->do('DELETE FROM tokens
|
||||
WHERE ' . $dbh->sql_to_days('NOW()') . ' - ' .
|
||||
$dbh->sql_to_days('issuedate') . ' >= ?',
|
||||
undef, MAX_TOKEN_AGE);
|
||||
}
|
||||
|
||||
sub GenerateUniqueToken {
|
||||
# Generates a unique random token. Uses generate_random_password
|
||||
# for the tokens themselves and checks uniqueness by searching for
|
||||
# the token in the "tokens" table. Gives up if it can't come up
|
||||
# with a token after about one hundred tries.
|
||||
my ($table, $column) = @_;
|
||||
|
||||
my $token;
|
||||
my $duplicate = 1;
|
||||
my $tries = 0;
|
||||
$table ||= "tokens";
|
||||
$column ||= "token";
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth = $dbh->prepare("SELECT userid FROM $table WHERE $column = ?");
|
||||
|
||||
while ($duplicate) {
|
||||
++$tries;
|
||||
if ($tries > 100) {
|
||||
ThrowCodeError("token_generation_error");
|
||||
}
|
||||
$token = generate_random_password();
|
||||
$sth->execute($token);
|
||||
$duplicate = $sth->fetchrow_array;
|
||||
}
|
||||
return $token;
|
||||
}
|
||||
|
||||
# Cancels a previously issued token and notifies the user.
|
||||
# This should only happen when the user accidentally makes a token request
|
||||
# or when a malicious hacker makes a token request on behalf of a user.
|
||||
sub Cancel {
|
||||
my ($token, $cancelaction, $vars) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$vars ||= {};
|
||||
|
||||
# Get information about the token being canceled.
|
||||
trick_taint($token);
|
||||
my ($issuedate, $tokentype, $eventdata, $userid) =
|
||||
$dbh->selectrow_array('SELECT ' . $dbh->sql_date_format('issuedate') . ',
|
||||
tokentype, eventdata, userid
|
||||
FROM tokens
|
||||
WHERE token = ?',
|
||||
undef, $token);
|
||||
|
||||
# If we are canceling the creation of a new user account, then there
|
||||
# is no entry in the 'profiles' table.
|
||||
my $user = new Bugzilla::User($userid);
|
||||
|
||||
$vars->{'emailaddress'} = $userid ? $user->email : $eventdata;
|
||||
$vars->{'remoteaddress'} = $::ENV{'REMOTE_ADDR'};
|
||||
$vars->{'token'} = $token;
|
||||
$vars->{'tokentype'} = $tokentype;
|
||||
$vars->{'issuedate'} = $issuedate;
|
||||
# The user is probably not logged in.
|
||||
# So we have to pass this information to the template.
|
||||
$vars->{'timezone'} = $user->timezone;
|
||||
$vars->{'eventdata'} = $eventdata;
|
||||
$vars->{'cancelaction'} = $cancelaction;
|
||||
|
||||
# Notify the user via email about the cancellation.
|
||||
my $template = Bugzilla->template_inner($user->settings->{'lang'}->{'value'});
|
||||
|
||||
my $message;
|
||||
$template->process("account/cancel-token.txt.tmpl", $vars, \$message)
|
||||
|| ThrowTemplateError($template->error());
|
||||
|
||||
Bugzilla->template_inner("");
|
||||
MessageToMTA($message);
|
||||
|
||||
# Delete the token from the database.
|
||||
delete_token($token);
|
||||
}
|
||||
|
||||
sub DeletePasswordTokens {
|
||||
my ($userid, $reason) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
detaint_natural($userid);
|
||||
my $tokens = $dbh->selectcol_arrayref('SELECT token FROM tokens
|
||||
WHERE userid = ? AND tokentype = ?',
|
||||
undef, ($userid, 'password'));
|
||||
|
||||
foreach my $token (@$tokens) {
|
||||
Bugzilla::Token::Cancel($token, $reason);
|
||||
}
|
||||
}
|
||||
|
||||
# Returns an email change token if the user has one.
|
||||
sub HasEmailChangeToken {
|
||||
my $userid = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $token = $dbh->selectrow_array('SELECT token FROM tokens
|
||||
WHERE userid = ?
|
||||
AND (tokentype = ? OR tokentype = ?) ' .
|
||||
$dbh->sql_limit(1),
|
||||
undef, ($userid, 'emailnew', 'emailold'));
|
||||
return $token;
|
||||
}
|
||||
|
||||
# Returns the userid, issuedate and eventdata for the specified token
|
||||
sub GetTokenData {
|
||||
my ($token) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
return unless defined $token;
|
||||
$token = clean_text($token);
|
||||
trick_taint($token);
|
||||
|
||||
return $dbh->selectrow_array(
|
||||
"SELECT userid, " . $dbh->sql_date_format('issuedate') . ", eventdata
|
||||
FROM tokens
|
||||
WHERE token = ?", undef, $token);
|
||||
}
|
||||
|
||||
# Deletes specified token
|
||||
sub delete_token {
|
||||
my ($token) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
return unless defined $token;
|
||||
trick_taint($token);
|
||||
|
||||
$dbh->do("DELETE FROM tokens WHERE token = ?", undef, $token);
|
||||
}
|
||||
|
||||
# Given a token, makes sure it comes from the currently logged in user
|
||||
# and match the expected event. Returns 1 on success, else displays a warning.
|
||||
# Note: this routine must not be called while tables are locked as it will try
|
||||
# to lock some tables itself, see CleanTokenTable().
|
||||
sub check_token_data {
|
||||
my ($token, $expected_action, $alternate_script) = @_;
|
||||
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);
|
||||
$vars->{'alternate_script'} = $alternate_script || basename($0);
|
||||
|
||||
# Now is a good time to remove old tokens from the DB.
|
||||
CleanTokenTable();
|
||||
|
||||
# If no token was found, create a valid token for the given action.
|
||||
unless ($creator_id) {
|
||||
$token = issue_session_token($expected_action);
|
||||
$cgi->param('token', $token);
|
||||
}
|
||||
|
||||
print $cgi->header();
|
||||
|
||||
$template->process('admin/confirm-action.html.tmpl', $vars)
|
||||
|| ThrowTemplateError($template->error());
|
||||
exit;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
################################################################################
|
||||
# Internal Functions
|
||||
################################################################################
|
||||
|
||||
# Generates a unique token and inserts it into the database
|
||||
# Returns the token and the token timestamp
|
||||
sub _create_token {
|
||||
my ($userid, $tokentype, $eventdata) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
detaint_natural($userid) if defined $userid;
|
||||
trick_taint($tokentype);
|
||||
trick_taint($eventdata);
|
||||
|
||||
$dbh->bz_start_transaction();
|
||||
|
||||
my $token = GenerateUniqueToken();
|
||||
|
||||
$dbh->do("INSERT INTO tokens (userid, issuedate, token, tokentype, eventdata)
|
||||
VALUES (?, NOW(), ?, ?, ?)", undef, ($userid, $token, $tokentype, $eventdata));
|
||||
|
||||
$dbh->bz_commit_transaction();
|
||||
|
||||
if (wantarray) {
|
||||
my (undef, $token_ts, undef) = GetTokenData($token);
|
||||
$token_ts = str2time($token_ts);
|
||||
return ($token, $token_ts);
|
||||
} else {
|
||||
return $token;
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Token - Provides different routines to manage tokens.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Token;
|
||||
|
||||
Bugzilla::Token::issue_new_user_account_token($login_name);
|
||||
Bugzilla::Token::IssueEmailChangeToken($user, $old_email, $new_email);
|
||||
Bugzilla::Token::IssuePasswordToken($user);
|
||||
Bugzilla::Token::DeletePasswordTokens($user_id, $reason);
|
||||
Bugzilla::Token::Cancel($token, $cancelaction, $vars);
|
||||
|
||||
Bugzilla::Token::CleanTokenTable();
|
||||
|
||||
my $token = issue_session_token($event);
|
||||
check_token_data($token, $event)
|
||||
delete_token($token);
|
||||
|
||||
my $token = Bugzilla::Token::GenerateUniqueToken($table, $column);
|
||||
my $token = Bugzilla::Token::HasEmailChangeToken($user_id);
|
||||
my ($token, $date, $data) = Bugzilla::Token::GetTokenData($token);
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<issue_new_user_account_token($login_name)>
|
||||
|
||||
Description: Creates and sends a token per email to the email address
|
||||
requesting a new user account. It doesn't check whether
|
||||
the user account already exists. The user will have to
|
||||
use this token to confirm the creation of his user account.
|
||||
|
||||
Params: $login_name - The new login name requested by the user.
|
||||
|
||||
Returns: Nothing. It throws an error if the same user made the same
|
||||
request in the last few minutes.
|
||||
|
||||
=item C<sub IssueEmailChangeToken($user, $old_email, $new_email)>
|
||||
|
||||
Description: Sends two distinct tokens per email to the old and new email
|
||||
addresses to confirm the email address change for the given
|
||||
user. These tokens remain valid for the next MAX_TOKEN_AGE days.
|
||||
|
||||
Params: $user - User object of the user requesting a new
|
||||
email address.
|
||||
$old_email - The current (old) email address of the user.
|
||||
$new_email - The new email address of the user.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<IssuePasswordToken($user)>
|
||||
|
||||
Description: Sends a token per email to the given user. This token
|
||||
can be used to change the password (e.g. in case the user
|
||||
cannot remember his password and wishes to enter a new one).
|
||||
|
||||
Params: $user - User object of the user requesting a new password.
|
||||
|
||||
Returns: Nothing. It throws an error if the same user made the same
|
||||
request in the last few minutes.
|
||||
|
||||
=item C<CleanTokenTable()>
|
||||
|
||||
Description: Removes all tokens older than MAX_TOKEN_AGE days from the DB.
|
||||
This means that these tokens will now be considered as invalid.
|
||||
|
||||
Params: None.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<GenerateUniqueToken($table, $column)>
|
||||
|
||||
Description: Generates and returns a unique token. This token is unique
|
||||
in the $column of the $table. This token is NOT stored in the DB.
|
||||
|
||||
Params: $table (optional): The table to look at (default: tokens).
|
||||
$column (optional): The column to look at for uniqueness (default: token).
|
||||
|
||||
Returns: A token which is unique in $column.
|
||||
|
||||
=item C<Cancel($token, $cancelaction, $vars)>
|
||||
|
||||
Description: Invalidates an existing token, generally when the token is used
|
||||
for an action which is not the one expected. An email is sent
|
||||
to the user who originally requested this token to inform him
|
||||
that this token has been invalidated (e.g. because an hacker
|
||||
tried to use this token for some malicious action).
|
||||
|
||||
Params: $token: The token to invalidate.
|
||||
$cancelaction: The reason why this token is invalidated.
|
||||
$vars: Some additional information about this action.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<DeletePasswordTokens($user_id, $reason)>
|
||||
|
||||
Description: Cancels all password tokens for the given user. Emails are sent
|
||||
to the user to inform him about this action.
|
||||
|
||||
Params: $user_id: The user ID of the user account whose password tokens
|
||||
are canceled.
|
||||
$reason: The reason why these tokens are canceled.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=item C<HasEmailChangeToken($user_id)>
|
||||
|
||||
Description: Returns any existing token currently used for an email change
|
||||
for the given user.
|
||||
|
||||
Params: $user_id - A user ID.
|
||||
|
||||
Returns: A token if it exists, else undef.
|
||||
|
||||
=item C<GetTokenData($token)>
|
||||
|
||||
Description: Returns all stored data for the given token.
|
||||
|
||||
Params: $token - A valid token.
|
||||
|
||||
Returns: The user ID, the date and time when the token was created and
|
||||
the (event)data stored with that token.
|
||||
|
||||
=back
|
||||
|
||||
=head2 Security related routines
|
||||
|
||||
The following routines have been written to be used together as described below,
|
||||
although they can be used separately.
|
||||
|
||||
=over
|
||||
|
||||
=item C<issue_session_token($event)>
|
||||
|
||||
Description: Creates and returns a token used internally.
|
||||
|
||||
Params: $event - The event which needs to be stored in the DB for future
|
||||
reference/checks.
|
||||
|
||||
Returns: A unique token.
|
||||
|
||||
=item C<check_token_data($token, $event)>
|
||||
|
||||
Description: Makes sure the $token has been created by the currently logged in
|
||||
user and to be used for the given $event. If this token is used for
|
||||
an unexpected action (i.e. $event doesn't match the information stored
|
||||
with the token), a warning is displayed asking whether the user really
|
||||
wants to continue. On success, it returns 1.
|
||||
This is the routine to use for security checks, combined with
|
||||
issue_session_token() and delete_token() as follows:
|
||||
|
||||
1. First, create a token for some coming action.
|
||||
my $token = issue_session_token($action);
|
||||
2. Some time later, it's time to make sure that the expected action
|
||||
is going to be executed, and by the expected user.
|
||||
check_token_data($token, $action);
|
||||
3. The check has been done and we no longer need this token.
|
||||
delete_token($token);
|
||||
|
||||
Params: $token - The token used for security checks.
|
||||
$event - The expected event to be run.
|
||||
|
||||
Returns: 1 on success, else a warning is thrown.
|
||||
|
||||
=item C<delete_token($token)>
|
||||
|
||||
Description: Deletes the specified token. No notification is sent.
|
||||
|
||||
Params: $token - The token to delete.
|
||||
|
||||
Returns: Nothing.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,221 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
package Bugzilla::Update;
|
||||
|
||||
use strict;
|
||||
|
||||
use Bugzilla::Constants;
|
||||
|
||||
use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
|
||||
use constant LOCAL_FILE => "/bugzilla-update.xml"; # Relative to datadir.
|
||||
use constant TIME_INTERVAL => 86400; # Default is one day, in seconds.
|
||||
use constant TIMEOUT => 5; # Number of seconds before timeout.
|
||||
|
||||
# Look for new releases and notify logged in administrators about them.
|
||||
sub get_notifications {
|
||||
return if (Bugzilla->params->{'upgrade_notification'} eq 'disabled');
|
||||
|
||||
# If the XML::Twig module is missing, we won't be able to parse
|
||||
# the XML file. So there is no need to go further.
|
||||
eval("require XML::Twig");
|
||||
return if $@;
|
||||
|
||||
my $local_file = bz_locations()->{'datadir'} . LOCAL_FILE;
|
||||
# Update the local XML file if this one doesn't exist or if
|
||||
# the last modification time (stat[9]) is older than TIME_INTERVAL.
|
||||
if (!-e $local_file || (time() - (stat($local_file))[9] > TIME_INTERVAL)) {
|
||||
# Are we sure we didn't try to refresh this file already
|
||||
# but we failed because we cannot modify its timestamp?
|
||||
my $can_alter = (-e $local_file) ? utime(undef, undef, $local_file) : 1;
|
||||
if ($can_alter) {
|
||||
unlink $local_file; # Make sure the old copy is away.
|
||||
my $error = _synchronize_data();
|
||||
# If an error is returned, leave now.
|
||||
return $error if $error;
|
||||
}
|
||||
else {
|
||||
return {'error' => 'no_update', 'xml_file' => $local_file};
|
||||
}
|
||||
}
|
||||
|
||||
# If we cannot access the local XML file, ignore it.
|
||||
return {'error' => 'no_access', 'xml_file' => $local_file} unless (-r $local_file);
|
||||
|
||||
my $twig = XML::Twig->new();
|
||||
$twig->safe_parsefile($local_file);
|
||||
# If the XML file is invalid, return.
|
||||
return {'error' => 'corrupted', 'xml_file' => $local_file} if $@;
|
||||
my $root = $twig->root;
|
||||
|
||||
my @releases;
|
||||
foreach my $branch ($root->children('branch')) {
|
||||
my $release = {
|
||||
'branch_ver' => $branch->{'att'}->{'id'},
|
||||
'latest_ver' => $branch->{'att'}->{'vid'},
|
||||
'status' => $branch->{'att'}->{'status'},
|
||||
'url' => $branch->{'att'}->{'url'},
|
||||
'date' => $branch->{'att'}->{'date'}
|
||||
};
|
||||
push(@releases, $release);
|
||||
}
|
||||
|
||||
# On which branch is the current installation running?
|
||||
my @current_version =
|
||||
(BUGZILLA_VERSION =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
|
||||
|
||||
my @release;
|
||||
if (Bugzilla->params->{'upgrade_notification'} eq 'development_snapshot') {
|
||||
@release = grep {$_->{'status'} eq 'development'} @releases;
|
||||
# If there is no development snapshot available, then we are in the
|
||||
# process of releasing a release candidate. That's the release we want.
|
||||
unless (scalar(@release)) {
|
||||
@release = grep {$_->{'status'} eq 'release-candidate'} @releases;
|
||||
}
|
||||
}
|
||||
elsif (Bugzilla->params->{'upgrade_notification'} eq 'latest_stable_release') {
|
||||
@release = grep {$_->{'status'} eq 'stable'} @releases;
|
||||
}
|
||||
elsif (Bugzilla->params->{'upgrade_notification'} eq 'stable_branch_release') {
|
||||
# We want the latest stable version for the current branch.
|
||||
# If we are running a development snapshot, we won't match anything.
|
||||
my $branch_version = $current_version[0] . '.' . $current_version[1];
|
||||
|
||||
# We do a string comparison instead of a numerical one, because
|
||||
# e.g. 2.2 == 2.20, but 2.2 ne 2.20 (and 2.2 is indeed much older).
|
||||
@release = grep {$_->{'branch_ver'} eq $branch_version} @releases;
|
||||
|
||||
# If the branch is now closed, we should strongly suggest
|
||||
# to upgrade to the latest stable release available.
|
||||
if (scalar(@release) && $release[0]->{'status'} eq 'closed') {
|
||||
@release = grep {$_->{'status'} eq 'stable'} @releases;
|
||||
return {'data' => $release[0], 'deprecated' => $branch_version};
|
||||
}
|
||||
}
|
||||
else {
|
||||
# Unknown parameter.
|
||||
return {'error' => 'unknown_parameter'};
|
||||
}
|
||||
|
||||
# Return if no new release is available.
|
||||
return unless scalar(@release);
|
||||
|
||||
# Only notify the administrator if the latest version available
|
||||
# is newer than the current one.
|
||||
my @new_version =
|
||||
($release[0]->{'latest_ver'} =~ m/^(\d+)\.(\d+)(?:(rc|\.)(\d+))?\+?$/);
|
||||
|
||||
# We convert release candidates 'rc' to integers (rc ? 0 : 1) in order
|
||||
# to compare versions easily.
|
||||
$current_version[2] = ($current_version[2] && $current_version[2] eq 'rc') ? 0 : 1;
|
||||
$new_version[2] = ($new_version[2] && $new_version[2] eq 'rc') ? 0 : 1;
|
||||
|
||||
my $is_newer = _compare_versions(\@current_version, \@new_version);
|
||||
return ($is_newer == 1) ? {'data' => $release[0]} : undef;
|
||||
}
|
||||
|
||||
sub _synchronize_data {
|
||||
eval("require LWP::UserAgent");
|
||||
return {'error' => 'missing_package', 'package' => 'LWP::UserAgent'} if $@;
|
||||
|
||||
my $local_file = bz_locations()->{'datadir'} . LOCAL_FILE;
|
||||
|
||||
my $ua = LWP::UserAgent->new();
|
||||
$ua->timeout(TIMEOUT);
|
||||
$ua->protocols_allowed(['http', 'https']);
|
||||
# If the URL of the proxy is given, use it, else get this information
|
||||
# from the environment variable.
|
||||
my $proxy_url = Bugzilla->params->{'proxy_url'};
|
||||
if ($proxy_url) {
|
||||
$ua->proxy(['http', 'https'], $proxy_url);
|
||||
}
|
||||
else {
|
||||
$ua->env_proxy;
|
||||
}
|
||||
$ua->mirror(REMOTE_FILE, $local_file);
|
||||
|
||||
# $ua->mirror() forces the modification time of the local XML file
|
||||
# to match the modification time of the remote one.
|
||||
# So we have to update it manually to reflect that a newer version
|
||||
# of the file has effectively been requested. This will avoid
|
||||
# any new download for the next TIME_INTERVAL.
|
||||
if (-e $local_file) {
|
||||
# Try to alter its last modification time.
|
||||
my $can_alter = utime(undef, undef, $local_file);
|
||||
# This error should never happen.
|
||||
$can_alter || return {'error' => 'no_update', 'xml_file' => $local_file};
|
||||
}
|
||||
else {
|
||||
# We have been unable to download the file.
|
||||
return {'error' => 'cannot_download', 'xml_file' => $local_file};
|
||||
}
|
||||
|
||||
# Everything went well.
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub _compare_versions {
|
||||
my ($old_ver, $new_ver) = @_;
|
||||
while (scalar(@$old_ver) && scalar(@$new_ver)) {
|
||||
my $old = shift(@$old_ver) || 0;
|
||||
my $new = shift(@$new_ver) || 0;
|
||||
return $new <=> $old if ($new <=> $old);
|
||||
}
|
||||
return scalar(@$new_ver) <=> scalar(@$old_ver);
|
||||
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Update - Update routines for Bugzilla
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Update;
|
||||
|
||||
# Get information about new releases
|
||||
my $new_release = Bugzilla::Update::get_notifications();
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This module contains all required routines to notify you
|
||||
about new releases. It downloads an XML file from bugzilla.org
|
||||
and parses it, in order to display information based on your
|
||||
preferences. Absolutely no information about the Bugzilla version
|
||||
you are running is sent to bugzilla.org.
|
||||
|
||||
=head1 FUNCTIONS
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_notifications()>
|
||||
|
||||
Description: This function informs you about new releases, if any.
|
||||
|
||||
Params: None.
|
||||
|
||||
Returns: On success, a reference to a hash with data about
|
||||
new releases, if any.
|
||||
On failure, a reference to a hash with the reason
|
||||
of the failure and the name of the unusable XML file.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,433 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Shane H. W. Travis <travis@sedsystems.ca>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Marc Schumann <wurblzap@gmail.com>
|
||||
# Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
|
||||
package Bugzilla::User::Setting;
|
||||
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
|
||||
|
||||
# Module stuff
|
||||
@Bugzilla::User::Setting::EXPORT = qw(get_all_settings get_defaults
|
||||
add_setting);
|
||||
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::Util qw(trick_taint get_text);
|
||||
|
||||
###############################
|
||||
### Module Initialization ###
|
||||
###############################
|
||||
|
||||
sub new {
|
||||
my $invocant = shift;
|
||||
my $setting_name = shift;
|
||||
my $user_id = shift;
|
||||
|
||||
my $class = ref($invocant) || $invocant;
|
||||
my $subclass = '';
|
||||
|
||||
# Create a ref to an empty hash and bless it
|
||||
my $self = {};
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Confirm that the $setting_name is properly formed;
|
||||
# if not, throw a code error.
|
||||
#
|
||||
# NOTE: due to the way that setting names are used in templates,
|
||||
# they must conform to to the limitations set for HTML NAMEs and IDs.
|
||||
#
|
||||
if ( !($setting_name =~ /^[a-zA-Z][-.:\w]*$/) ) {
|
||||
ThrowCodeError("setting_name_invalid", { name => $setting_name });
|
||||
}
|
||||
|
||||
# If there were only two parameters passed in, then we need
|
||||
# to retrieve the information for this setting ourselves.
|
||||
if (scalar @_ == 0) {
|
||||
|
||||
my ($default, $is_enabled, $value);
|
||||
($default, $is_enabled, $value, $subclass) =
|
||||
$dbh->selectrow_array(
|
||||
q{SELECT default_value, is_enabled, setting_value, subclass
|
||||
FROM setting
|
||||
LEFT JOIN profile_setting
|
||||
ON setting.name = profile_setting.setting_name
|
||||
WHERE name = ?
|
||||
AND profile_setting.user_id = ?},
|
||||
undef,
|
||||
$setting_name, $user_id);
|
||||
|
||||
# if not defined, then grab the default value
|
||||
if (! defined $value) {
|
||||
($default, $is_enabled, $subclass) =
|
||||
$dbh->selectrow_array(
|
||||
q{SELECT default_value, is_enabled, subclass
|
||||
FROM setting
|
||||
WHERE name = ?},
|
||||
undef,
|
||||
$setting_name);
|
||||
}
|
||||
|
||||
$self->{'is_enabled'} = $is_enabled;
|
||||
$self->{'default_value'} = $default;
|
||||
|
||||
# IF the setting is enabled, AND the user has chosen a setting
|
||||
# THEN return that value
|
||||
# ELSE return the site default, and note that it is the default.
|
||||
if ( ($is_enabled) && (defined $value) ) {
|
||||
$self->{'value'} = $value;
|
||||
} else {
|
||||
$self->{'value'} = $default;
|
||||
$self->{'isdefault'} = 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
# If the values were passed in, simply assign them and return.
|
||||
$self->{'is_enabled'} = shift;
|
||||
$self->{'default_value'} = shift;
|
||||
$self->{'value'} = shift;
|
||||
$self->{'is_default'} = shift;
|
||||
$subclass = shift;
|
||||
}
|
||||
if ($subclass) {
|
||||
eval('require ' . $class . '::' . $subclass);
|
||||
$@ && ThrowCodeError('setting_subclass_invalid',
|
||||
{'subclass' => $subclass});
|
||||
$class = $class . '::' . $subclass;
|
||||
}
|
||||
bless($self, $class);
|
||||
|
||||
$self->{'_setting_name'} = $setting_name;
|
||||
$self->{'_user_id'} = $user_id;
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
###############################
|
||||
### Subroutine Definitions ###
|
||||
###############################
|
||||
|
||||
sub add_setting {
|
||||
my ($name, $values, $default_value, $subclass, $force_check) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $exists = _setting_exists($name);
|
||||
return if ($exists && !$force_check);
|
||||
|
||||
($name && $default_value)
|
||||
|| ThrowCodeError("setting_info_invalid");
|
||||
|
||||
if ($exists) {
|
||||
# If this setting exists, we delete it and regenerate it.
|
||||
$dbh->do('DELETE FROM setting_value WHERE name = ?', undef, $name);
|
||||
$dbh->do('DELETE FROM setting WHERE name = ?', undef, $name);
|
||||
# Remove obsolete user preferences for this setting.
|
||||
if (defined $values && scalar(@$values)) {
|
||||
my $list = join(', ', map {$dbh->quote($_)} @$values);
|
||||
$dbh->do("DELETE FROM profile_setting
|
||||
WHERE setting_name = ? AND setting_value NOT IN ($list)",
|
||||
undef, $name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
print get_text('install_setting_new', { name => $name }) . "\n";
|
||||
}
|
||||
$dbh->do(q{INSERT INTO setting (name, default_value, is_enabled, subclass)
|
||||
VALUES (?, ?, 1, ?)},
|
||||
undef, ($name, $default_value, $subclass));
|
||||
|
||||
my $sth = $dbh->prepare(q{INSERT INTO setting_value (name, value, sortindex)
|
||||
VALUES (?, ?, ?)});
|
||||
|
||||
my $sortindex = 5;
|
||||
foreach my $key (@$values){
|
||||
$sth->execute($name, $key, $sortindex);
|
||||
$sortindex += 5;
|
||||
}
|
||||
}
|
||||
|
||||
sub get_all_settings {
|
||||
my ($user_id) = @_;
|
||||
my $settings = get_defaults($user_id); # first get the defaults
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $sth = $dbh->prepare(
|
||||
q{SELECT name, default_value, is_enabled, setting_value, subclass
|
||||
FROM setting
|
||||
LEFT JOIN profile_setting
|
||||
ON setting.name = profile_setting.setting_name
|
||||
WHERE profile_setting.user_id = ?
|
||||
ORDER BY name});
|
||||
|
||||
$sth->execute($user_id);
|
||||
while (my ($name, $default_value, $is_enabled, $value, $subclass)
|
||||
= $sth->fetchrow_array())
|
||||
{
|
||||
|
||||
my $is_default;
|
||||
|
||||
if ( ($is_enabled) && (defined $value) ) {
|
||||
$is_default = 0;
|
||||
} else {
|
||||
$value = $default_value;
|
||||
$is_default = 1;
|
||||
}
|
||||
|
||||
$settings->{$name} = new Bugzilla::User::Setting(
|
||||
$name, $user_id, $is_enabled,
|
||||
$default_value, $value, $is_default, $subclass);
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
sub get_defaults {
|
||||
my ($user_id) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $default_settings = {};
|
||||
|
||||
$user_id ||= 0;
|
||||
|
||||
my $sth = $dbh->prepare(q{SELECT name, default_value, is_enabled, subclass
|
||||
FROM setting
|
||||
ORDER BY name});
|
||||
$sth->execute();
|
||||
while (my ($name, $default_value, $is_enabled, $subclass)
|
||||
= $sth->fetchrow_array())
|
||||
{
|
||||
|
||||
$default_settings->{$name} = new Bugzilla::User::Setting(
|
||||
$name, $user_id, $is_enabled, $default_value, $default_value, 1,
|
||||
$subclass);
|
||||
}
|
||||
|
||||
return $default_settings;
|
||||
}
|
||||
|
||||
sub set_default {
|
||||
my ($setting_name, $default_value, $is_enabled) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $sth = $dbh->prepare(q{UPDATE setting
|
||||
SET default_value = ?, is_enabled = ?
|
||||
WHERE name = ?});
|
||||
$sth->execute($default_value, $is_enabled, $setting_name);
|
||||
}
|
||||
|
||||
sub _setting_exists {
|
||||
my ($setting_name) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
return $dbh->selectrow_arrayref(
|
||||
"SELECT 1 FROM setting WHERE name = ?", undef, $setting_name) || 0;
|
||||
}
|
||||
|
||||
|
||||
sub legal_values {
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{'legal_values'} if defined $self->{'legal_values'};
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
$self->{'legal_values'} = $dbh->selectcol_arrayref(
|
||||
q{SELECT value
|
||||
FROM setting_value
|
||||
WHERE name = ?
|
||||
ORDER BY sortindex},
|
||||
undef, $self->{'_setting_name'});
|
||||
|
||||
return $self->{'legal_values'};
|
||||
}
|
||||
|
||||
sub validate_value {
|
||||
my $self = shift;
|
||||
|
||||
if (grep(/^$_[0]$/, @{$self->legal_values()})) {
|
||||
trick_taint($_[0]);
|
||||
}
|
||||
else {
|
||||
ThrowCodeError('setting_value_invalid',
|
||||
{'name' => $self->{'_setting_name'},
|
||||
'value' => $_[0]});
|
||||
}
|
||||
}
|
||||
|
||||
sub reset_to_default {
|
||||
my ($self) = @_;
|
||||
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $sth = $dbh->do(q{ DELETE
|
||||
FROM profile_setting
|
||||
WHERE setting_name = ?
|
||||
AND user_id = ?},
|
||||
undef, $self->{'_setting_name'}, $self->{'_user_id'});
|
||||
$self->{'value'} = $self->{'default_value'};
|
||||
$self->{'is_default'} = 1;
|
||||
}
|
||||
|
||||
sub set {
|
||||
my ($self, $value) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
my $query;
|
||||
|
||||
if ($self->{'is_default'}) {
|
||||
$query = q{INSERT INTO profile_setting
|
||||
(setting_value, setting_name, user_id)
|
||||
VALUES (?,?,?)};
|
||||
} else {
|
||||
$query = q{UPDATE profile_setting
|
||||
SET setting_value = ?
|
||||
WHERE setting_name = ?
|
||||
AND user_id = ?};
|
||||
}
|
||||
$dbh->do($query, undef, $value, $self->{'_setting_name'}, $self->{'_user_id'});
|
||||
|
||||
$self->{'value'} = $value;
|
||||
$self->{'is_default'} = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::User::Setting - Object for a user preference setting
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
Setting.pm creates a setting object, which is a hash containing the user
|
||||
preference information for a single preference for a single user. These
|
||||
are usually accessed through the "settings" object of a user, and not
|
||||
directly.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
use Bugzilla::User::Setting;
|
||||
my $settings;
|
||||
|
||||
$settings->{$setting_name} = new Bugzilla::User::Setting(
|
||||
$setting_name, $user_id);
|
||||
|
||||
OR
|
||||
|
||||
$settings->{$setting_name} = new Bugzilla::User::Setting(
|
||||
$setting_name, $user_id, $is_enabled,
|
||||
$default_value, $value, $is_default);
|
||||
|
||||
=head1 CLASS FUNCTIONS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<add_setting($name, \@values, $default_value, $subclass, $force_check)>
|
||||
|
||||
Description: Checks for the existence of a setting, and adds it
|
||||
to the database if it does not yet exist.
|
||||
|
||||
Params: C<$name> - string - the name of the new setting
|
||||
C<$values> - arrayref - contains the new choices
|
||||
for the new Setting.
|
||||
C<$default_value> - string - the site default
|
||||
C<$subclass> - string - name of the module returning
|
||||
the list of valid values. This means legal values are
|
||||
not stored in the DB.
|
||||
C<$force_check> - boolean - when true, the existing setting
|
||||
and all its values are deleted and replaced by new data.
|
||||
|
||||
Returns: a pointer to a hash of settings
|
||||
|
||||
|
||||
=item C<get_all_settings($user_id)>
|
||||
|
||||
Description: Provides the user's choices for each setting in the
|
||||
system; if the user has made no choice, uses the site
|
||||
default instead.
|
||||
Params: C<$user_id> - integer - the user id.
|
||||
Returns: a pointer to a hash of settings
|
||||
|
||||
=item C<get_defaults($user_id)>
|
||||
|
||||
Description: When a user is not logged in, they must use the site
|
||||
defaults for every settings; this subroutine provides them.
|
||||
Params: C<$user_id> (optional) - integer - the user id. Note that
|
||||
this optional parameter is mainly for internal use only.
|
||||
Returns: A pointer to a hash of settings. If $user_id was passed, set
|
||||
the user_id value for each setting.
|
||||
|
||||
=item C<set_default($setting_name, $default_value, $is_enabled)>
|
||||
|
||||
Description: Sets the global default for a given setting. Also sets
|
||||
whether users are allowed to choose their own value for
|
||||
this setting, or if they must use the global default.
|
||||
Params: C<$setting_name> - string - the name of the setting
|
||||
C<$default_value> - string - the new default value for this setting
|
||||
C<$is_enabled> - boolean - if false, all users must use the global default
|
||||
Returns: nothing
|
||||
|
||||
=begin private
|
||||
|
||||
=item C<_setting_exists>
|
||||
|
||||
Description: Determines if a given setting exists in the database.
|
||||
Params: C<$setting_name> - string - the setting name
|
||||
Returns: boolean - true if the setting already exists in the DB.
|
||||
|
||||
=back
|
||||
|
||||
=end private
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over 4
|
||||
|
||||
=item C<legal_values($setting_name)>
|
||||
|
||||
Description: Returns all legal values for this setting
|
||||
Params: none
|
||||
Returns: A reference to an array containing all legal values
|
||||
|
||||
=item C<validate_value>
|
||||
|
||||
Description: Determines whether a value is valid for the setting
|
||||
by checking against the list of legal values.
|
||||
Untaints the parameter if the value is indeed valid,
|
||||
and throws a setting_value_invalid code error if not.
|
||||
Params: An lvalue containing a candidate for a setting value
|
||||
Returns: nothing
|
||||
|
||||
=item C<reset_to_default>
|
||||
|
||||
Description: If a user chooses to use the global default for a given
|
||||
setting, their saved entry is removed from the database via
|
||||
this subroutine.
|
||||
Params: none
|
||||
Returns: nothing
|
||||
|
||||
=item C<set($value)>
|
||||
|
||||
Description: If a user chooses to use their own value rather than the
|
||||
global value for a given setting, OR changes their value for
|
||||
a given setting, this subroutine is called to insert or
|
||||
update the database as appropriate.
|
||||
Params: C<$value> - string - the new value for this setting for this user.
|
||||
Returns: nothing
|
||||
|
||||
=back
|
||||
@@ -1,60 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Marc Schumann.
|
||||
# Portions created by Marc Schumann are Copyright (c) 2007 Marc Schumann.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
|
||||
package Bugzilla::User::Setting::Lang;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Bugzilla::User::Setting);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
|
||||
sub legal_values {
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{'legal_values'} if defined $self->{'legal_values'};
|
||||
|
||||
return $self->{'legal_values'} = Bugzilla->languages;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::User::Setting::Lang - Object for a user preference setting for preferred language
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Lang.pm extends Bugzilla::User::Setting and implements a class specialized for
|
||||
setting the preferred language.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<legal_values()>
|
||||
|
||||
Description: Returns all legal languages
|
||||
Params: none
|
||||
Returns: A reference to an array containing the names of all legal languages
|
||||
|
||||
=back
|
||||
@@ -1,79 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
#
|
||||
|
||||
|
||||
package Bugzilla::User::Setting::Skin;
|
||||
|
||||
use strict;
|
||||
|
||||
use base qw(Bugzilla::User::Setting);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
use File::Spec::Functions;
|
||||
use File::Basename;
|
||||
|
||||
use constant BUILTIN_SKIN_NAMES => ['standard'];
|
||||
|
||||
sub legal_values {
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{'legal_values'} if defined $self->{'legal_values'};
|
||||
|
||||
my $dirbase = bz_locations()->{'skinsdir'} . '/contrib';
|
||||
# Avoid modification of the list BUILTIN_SKIN_NAMES points to by copying the
|
||||
# list over instead of simply writing $legal_values = BUILTIN_SKIN_NAMES.
|
||||
my @legal_values = @{(BUILTIN_SKIN_NAMES)};
|
||||
|
||||
foreach my $direntry (glob(catdir($dirbase, '*'))) {
|
||||
if (-d $direntry) {
|
||||
# Stylesheet set
|
||||
next if basename($direntry) =~ /^cvs$/i;
|
||||
push(@legal_values, basename($direntry));
|
||||
}
|
||||
elsif ($direntry =~ /\.css$/) {
|
||||
# Single-file stylesheet
|
||||
push(@legal_values, basename($direntry));
|
||||
}
|
||||
}
|
||||
|
||||
return $self->{'legal_values'} = \@legal_values;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::User::Setting::Skin - Object for a user preference setting for skins
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Skin.pm extends Bugzilla::User::Setting and implements a class specialized for
|
||||
skins settings.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<legal_values()>
|
||||
|
||||
Description: Returns all legal skins
|
||||
Params: none
|
||||
Returns: A reference to an array containing the names of all legal skins
|
||||
|
||||
=back
|
||||
@@ -1,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 Frédéric Buclin.
|
||||
# Portions created by Frédéric Buclin are Copyright (c) 2008 Frédéric Buclin.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Contributor(s): Frédéric Buclin <LpSolit@gmail.com>
|
||||
|
||||
package Bugzilla::User::Setting::Timezone;
|
||||
|
||||
use strict;
|
||||
|
||||
use DateTime::TimeZone;
|
||||
|
||||
use base qw(Bugzilla::User::Setting);
|
||||
|
||||
use Bugzilla::Constants;
|
||||
|
||||
sub legal_values {
|
||||
my ($self) = @_;
|
||||
|
||||
return $self->{'legal_values'} if defined $self->{'legal_values'};
|
||||
|
||||
my @timezones = DateTime::TimeZone->all_names;
|
||||
# Remove old formats, such as CST6CDT, EST, EST5EDT.
|
||||
@timezones = grep { $_ =~ m#.+/.+#} @timezones;
|
||||
# Append 'local' to the list, which will use the timezone
|
||||
# given by the server.
|
||||
push(@timezones, 'local');
|
||||
|
||||
return $self->{'legal_values'} = \@timezones;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::User::Setting::Timezone - Object for a user preference setting for desired timezone
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Timezone.pm extends Bugzilla::User::Setting and implements a class specialized for
|
||||
setting the desired timezone.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<legal_values()>
|
||||
|
||||
Description: Returns all legal timezones
|
||||
|
||||
Params: none
|
||||
|
||||
Returns: A reference to an array containing the names of all legal timezones
|
||||
|
||||
=back
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,263 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Tiago R. Mello <timello@async.com.br>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
use strict;
|
||||
|
||||
package Bugzilla::Version;
|
||||
|
||||
use base qw(Bugzilla::Object);
|
||||
|
||||
use Bugzilla::Install::Util qw(vers_cmp);
|
||||
use Bugzilla::Util;
|
||||
use Bugzilla::Error;
|
||||
|
||||
################################
|
||||
##### Initialization #####
|
||||
################################
|
||||
|
||||
use constant DEFAULT_VERSION => 'unspecified';
|
||||
|
||||
use constant DB_TABLE => 'versions';
|
||||
|
||||
use constant DB_COLUMNS => qw(
|
||||
id
|
||||
value
|
||||
product_id
|
||||
);
|
||||
|
||||
use constant NAME_FIELD => 'value';
|
||||
# This is "id" because it has to be filled in and id is probably the fastest.
|
||||
# We do a custom sort in new_from_list below.
|
||||
use constant LIST_ORDER => 'id';
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $param = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $product;
|
||||
if (ref $param) {
|
||||
$product = $param->{product};
|
||||
my $name = $param->{name};
|
||||
if (!defined $product) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'product',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
if (!defined $name) {
|
||||
ThrowCodeError('bad_arg',
|
||||
{argument => 'name',
|
||||
function => "${class}::new"});
|
||||
}
|
||||
|
||||
my $condition = 'product_id = ? AND value = ?';
|
||||
my @values = ($product->id, $name);
|
||||
$param = { condition => $condition, values => \@values };
|
||||
}
|
||||
|
||||
unshift @_, $param;
|
||||
return $class->SUPER::new(@_);
|
||||
}
|
||||
|
||||
sub new_from_list {
|
||||
my $self = shift;
|
||||
my $list = $self->SUPER::new_from_list(@_);
|
||||
return [sort { vers_cmp(lc($a->name), lc($b->name)) } @$list];
|
||||
}
|
||||
|
||||
sub bug_count {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
if (!defined $self->{'bug_count'}) {
|
||||
$self->{'bug_count'} = $dbh->selectrow_array(qq{
|
||||
SELECT COUNT(*) FROM bugs
|
||||
WHERE product_id = ? AND version = ?}, undef,
|
||||
($self->product_id, $self->name)) || 0;
|
||||
}
|
||||
return $self->{'bug_count'};
|
||||
}
|
||||
|
||||
sub remove_from_db {
|
||||
my $self = shift;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# The version cannot be removed if there are bugs
|
||||
# associated with it.
|
||||
if ($self->bug_count) {
|
||||
ThrowUserError("version_has_bugs", { nb => $self->bug_count });
|
||||
}
|
||||
|
||||
$dbh->do(q{DELETE FROM versions WHERE product_id = ? AND value = ?},
|
||||
undef, ($self->product_id, $self->name));
|
||||
}
|
||||
|
||||
sub update {
|
||||
my $self = shift;
|
||||
my ($name, $product) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
$name || ThrowUserError('version_not_specified');
|
||||
|
||||
# Remove unprintable characters
|
||||
$name = clean_text($name);
|
||||
|
||||
return 0 if ($name eq $self->name);
|
||||
my $version = new Bugzilla::Version({ product => $product, name => $name });
|
||||
|
||||
if ($version) {
|
||||
ThrowUserError('version_already_exists',
|
||||
{'name' => $version->name,
|
||||
'product' => $product->name});
|
||||
}
|
||||
|
||||
trick_taint($name);
|
||||
$dbh->do("UPDATE bugs SET version = ?
|
||||
WHERE version = ? AND product_id = ?", undef,
|
||||
($name, $self->name, $self->product_id));
|
||||
|
||||
$dbh->do("UPDATE versions SET value = ?
|
||||
WHERE product_id = ? AND value = ?", undef,
|
||||
($name, $self->product_id, $self->name));
|
||||
|
||||
$self->{'value'} = $name;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
###############################
|
||||
##### Accessors ####
|
||||
###############################
|
||||
|
||||
sub name { return $_[0]->{'value'}; }
|
||||
sub product_id { return $_[0]->{'product_id'}; }
|
||||
|
||||
###############################
|
||||
##### Subroutines ###
|
||||
###############################
|
||||
|
||||
sub create {
|
||||
my ($name, $product) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
# Cleanups and validity checks
|
||||
$name || ThrowUserError('version_blank_name');
|
||||
|
||||
# Remove unprintable characters
|
||||
$name = clean_text($name);
|
||||
|
||||
my $version = new Bugzilla::Version({ product => $product, name => $name });
|
||||
if ($version) {
|
||||
ThrowUserError('version_already_exists',
|
||||
{'name' => $version->name,
|
||||
'product' => $product->name});
|
||||
}
|
||||
|
||||
# Add the new version
|
||||
trick_taint($name);
|
||||
$dbh->do(q{INSERT INTO versions (value, product_id)
|
||||
VALUES (?, ?)}, undef, ($name, $product->id));
|
||||
|
||||
return new Bugzilla::Version($dbh->bz_last_key('versions', 'id'));
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Version - Bugzilla product version class.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
use Bugzilla::Version;
|
||||
|
||||
my $version = new Bugzilla::Version(1, 'version_value');
|
||||
|
||||
my $product_id = $version->product_id;
|
||||
my $value = $version->value;
|
||||
|
||||
$version->remove_from_db;
|
||||
|
||||
my $updated = $version->update($version_name, $product);
|
||||
|
||||
my $version = $hash_ref->{'version_value'};
|
||||
|
||||
my $version = Bugzilla::Version::create($version_name, $product);
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Version.pm represents a Product Version object.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<new($product_id, $value)>
|
||||
|
||||
Description: The constructor is used to load an existing version
|
||||
by passing a product id and a version value.
|
||||
|
||||
Params: $product_id - Integer with a product id.
|
||||
$value - String with a version value.
|
||||
|
||||
Returns: A Bugzilla::Version object.
|
||||
|
||||
=item C<bug_count()>
|
||||
|
||||
Description: Returns the total of bugs that belong to the version.
|
||||
|
||||
Params: none.
|
||||
|
||||
Returns: Integer with the number of bugs.
|
||||
|
||||
=item C<remove_from_db()>
|
||||
|
||||
Description: Removes the version from the database.
|
||||
|
||||
Params: none.
|
||||
|
||||
Retruns: none.
|
||||
|
||||
=item C<update($name, $product)>
|
||||
|
||||
Description: Update the value of the version.
|
||||
|
||||
Params: $name - String with the new version value.
|
||||
$product - Bugzilla::Product object the version belongs to.
|
||||
|
||||
Returns: An integer - 1 if the version has been updated, else 0.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SUBROUTINES
|
||||
|
||||
=over
|
||||
|
||||
=item C<create($version_name, $product)>
|
||||
|
||||
Description: Create a new version for the given product.
|
||||
|
||||
Params: $version_name - String with a version value.
|
||||
$product - A Bugzilla::Product object.
|
||||
|
||||
Returns: A Bugzilla::Version object.
|
||||
|
||||
=back
|
||||
|
||||
=cut
|
||||
@@ -1,292 +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>
|
||||
# Rosie Clarkson <rosie.clarkson@planningportal.gov.uk>
|
||||
#
|
||||
# Portions © Crown copyright 2009 - Rosie Clarkson (development@planningportal.gov.uk) for the Planning Portal
|
||||
|
||||
# This is the base class for $self in WebService method calls. For the
|
||||
# actual RPC server, see Bugzilla::WebService::Server and its subclasses.
|
||||
package Bugzilla::WebService;
|
||||
use strict;
|
||||
use Date::Parse;
|
||||
use XMLRPC::Lite;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
# For some methods, we shouldn't call Bugzilla->login before we call them
|
||||
use constant LOGIN_EXEMPT => { };
|
||||
|
||||
sub login_exempt {
|
||||
my ($class, $method) = @_;
|
||||
return $class->LOGIN_EXEMPT->{$method};
|
||||
}
|
||||
|
||||
sub type {
|
||||
my ($self, $type, $value) = @_;
|
||||
if ($type eq 'dateTime') {
|
||||
$value = $self->datetime_format($value);
|
||||
}
|
||||
return XMLRPC::Data->type($type)->value($value);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::WebService - The Web Service interface to Bugzilla
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is the standard API for external programs that want to interact
|
||||
with Bugzilla. It provides various methods in various modules.
|
||||
|
||||
Currently the only method of accessing the API is via XML-RPC. The XML-RPC
|
||||
standard is described here: L<http://www.xmlrpc.com/spec>
|
||||
|
||||
The endpoint for Bugzilla WebServices is the C<xmlrpc.cgi> script in
|
||||
your Bugzilla installation. For example, if your Bugzilla is at
|
||||
C<bugzilla.yourdomain.com>, then your XML-RPC client would access the
|
||||
API via: C<http://bugzilla.yourdomain.com/xmlrpc.cgi>
|
||||
|
||||
=head1 CALLING METHODS
|
||||
|
||||
Methods are called in the normal XML-RPC fashion. Bugzilla does not currently
|
||||
implement any extensions to the standard method of XML-RPC method calling.
|
||||
|
||||
Methods are grouped into "packages", like C<Bug> for
|
||||
L<Bugzilla::WebService::Bug>. So, for example,
|
||||
L<Bugzilla::WebService::Bug/get>, is called as C<Bug.get> in XML-RPC.
|
||||
|
||||
=head1 PARAMETERS
|
||||
|
||||
In addition to the standard parameter types like C<int>, C<string>, etc.,
|
||||
XML-RPC has two data structures, a C<< <struct> >> and an C<< <array> >>.
|
||||
|
||||
=head2 Structs
|
||||
|
||||
In Perl, we call a C<< <struct> >> a "hash" or a "hashref". You may see
|
||||
us refer to it that way in the API documentation.
|
||||
|
||||
In example code, you will see the characters C<{> and C<}> used to represent
|
||||
the beginning and end of structs.
|
||||
|
||||
For example, here's a struct in XML-RPC:
|
||||
|
||||
<struct>
|
||||
<member>
|
||||
<name>fruit</name>
|
||||
<value><string>oranges</string></value>
|
||||
</member>
|
||||
<member>
|
||||
<name>vegetable</name>
|
||||
<value><string>lettuce</string></value>
|
||||
</member>
|
||||
</struct>
|
||||
|
||||
In our example code in these API docs, that would look like:
|
||||
|
||||
{ fruit => 'oranges', vegetable => 'lettuce' }
|
||||
|
||||
=head2 Arrays
|
||||
|
||||
In example code, you will see the characters C<[> and C<]> used to
|
||||
represent the beginning and end of arrays.
|
||||
|
||||
For example, here's an array in XML-RPC:
|
||||
|
||||
<array>
|
||||
<data>
|
||||
<value><i4>1</i4></value>
|
||||
<value><i4>2</i4></value>
|
||||
<value><i4>3</i4></value>
|
||||
</data>
|
||||
</array>
|
||||
|
||||
In our example code in these API docs, that would look like:
|
||||
|
||||
[1, 2, 3]
|
||||
|
||||
=head2 How Bugzilla WebService Methods Take Parameters
|
||||
|
||||
B<All> Bugzilla WebServices functions take their parameters in
|
||||
a C<< <struct> >>. Another way of saying this would be: All functions
|
||||
take a single argument, a C<< <struct> >> that contains all parameters.
|
||||
The names of the parameters listed in the API docs for each function are
|
||||
the C<name> element for the struct C<member>s.
|
||||
|
||||
=head1 LOGGING IN
|
||||
|
||||
You can use L<Bugzilla::WebService::User/login> to log in as a Bugzilla
|
||||
user. This issues standard HTTP cookies that you must then use in future
|
||||
calls, so your XML-RPC client must be capable of receiving and transmitting
|
||||
cookies.
|
||||
|
||||
=head1 STABLE, EXPERIMENTAL, and UNSTABLE
|
||||
|
||||
Methods are marked B<STABLE> if you can expect their parameters and
|
||||
return values not to change between versions of Bugzilla. You are
|
||||
best off always using methods marked B<STABLE>. We may add parameters
|
||||
and additional items to the return values, but your old code will
|
||||
always continue to work with any new changes we make. If we ever break
|
||||
a B<STABLE> interface, we'll post a big notice in the Release Notes,
|
||||
and it will only happen during a major new release.
|
||||
|
||||
Methods (or parts of methods) are marked B<EXPERIMENTAL> if
|
||||
we I<believe> they will be stable, but there's a slight chance that
|
||||
small parts will change in the future.
|
||||
|
||||
Certain parts of a method's description may be marked as B<UNSTABLE>,
|
||||
in which case those parts are not guaranteed to stay the same between
|
||||
Bugzilla versions.
|
||||
|
||||
=head1 ERRORS
|
||||
|
||||
If a particular webservice call fails, it will throw a standard XML-RPC
|
||||
error. There will be a numeric error code, and then the description
|
||||
field will contain descriptive text of the error. Each error that Bugzilla
|
||||
can throw has a specific code that will not change between versions of
|
||||
Bugzilla.
|
||||
|
||||
The various errors that functions can throw are specified by the
|
||||
documentation of those functions.
|
||||
|
||||
If your code needs to know what error Bugzilla threw, use the numeric
|
||||
code. Don't try to parse the description, because that may change
|
||||
from version to version of Bugzilla.
|
||||
|
||||
Note that if you display the error to the user in an HTML program, make
|
||||
sure that you properly escape the error, as it will not be HTML-escaped.
|
||||
|
||||
=head2 Transient vs. Fatal Errors
|
||||
|
||||
If the error code is a number greater than 0, the error is considered
|
||||
"transient," which means that it was an error made by the user, not
|
||||
some problem with Bugzilla itself.
|
||||
|
||||
If the error code is a number less than 0, the error is "fatal," which
|
||||
means that it's some error in Bugzilla itself that probably requires
|
||||
administrative attention.
|
||||
|
||||
Negative numbers and positive numbers don't overlap. That is, if there's
|
||||
an error 302, there won't be an error -302.
|
||||
|
||||
=head2 Unknown Errors
|
||||
|
||||
Sometimes a function will throw an error that doesn't have a specific
|
||||
error code. In this case, the code will be C<-32000> if it's a "fatal"
|
||||
error, and C<32000> if it's a "transient" error.
|
||||
|
||||
=head1 COMMON PARAMETERS
|
||||
|
||||
Many Webservice methods take similar arguments. Instead of re-writing
|
||||
the documentation for each method, we document the parameters here, once,
|
||||
and then refer back to this documentation from the individual methods
|
||||
where these parameters are used.
|
||||
|
||||
=head2 Limiting What Fields Are Returned
|
||||
|
||||
Many WebService methods return an array of structs with various
|
||||
fields in the structs. (For example, L<Bugzilla::WebService::Bug/get>
|
||||
returns a list of C<bugs> that have fields like C<id>, C<summary>,
|
||||
C<creation_time>, etc.)
|
||||
|
||||
These parameters allow you to limit what fields are present in
|
||||
the structs, to possibly improve performance or save some bandwidth.
|
||||
|
||||
=over
|
||||
|
||||
=item C<include_fields> (array)
|
||||
|
||||
An array of strings, representing the (case-sensitive) names of fields.
|
||||
Only the fields specified in this hash will be returned, the rest will
|
||||
not be included.
|
||||
|
||||
If you specify an empty array, then this function will return empty
|
||||
hashes.
|
||||
|
||||
Invalid field names are ignored.
|
||||
|
||||
Example:
|
||||
|
||||
User.get( ids => [1], include_fields => ['id', 'name'] )
|
||||
|
||||
would return something like:
|
||||
|
||||
{ users => [{ id => 1, name => 'user@domain.com' }] }
|
||||
|
||||
=item C<exclude_fields> (array)
|
||||
|
||||
An array of strings, representing the (case-sensitive) names of fields.
|
||||
The fields specified will not be included in the returned hashes.
|
||||
|
||||
If you specify all the fields, then this function will return empty
|
||||
hashes.
|
||||
|
||||
Invalid field names are ignored.
|
||||
|
||||
Specifying fields here overrides C<include_fields>, so if you specify a
|
||||
field in both, it will be excluded, not included.
|
||||
|
||||
Example:
|
||||
|
||||
User.get( ids => [1], exclude_fields => ['name'] )
|
||||
|
||||
would return something like:
|
||||
|
||||
{ users => [{ id => 1, real_name => 'John Smith' }] }
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=head1 EXTENSIONS TO THE XML-RPC STANDARD
|
||||
|
||||
=head2 Undefined Values
|
||||
|
||||
Normally, XML-RPC does not allow empty values for C<int>, C<double>, or
|
||||
C<dateTime.iso8601> fields. Bugzilla does--it treats empty values as
|
||||
C<undef> (called C<NULL> or C<None> in some programming languages).
|
||||
|
||||
Bugzilla also accepts an element called C<< <nil> >>, as specified by
|
||||
the XML-RPC extension here: L<http://ontosys.com/xml-rpc/extensions.php>,
|
||||
which is always considered to be C<undef>, no matter what it contains.
|
||||
|
||||
Bugzilla does not use C<< <nil> >> values in returned data, because currently
|
||||
most clients do not support C<< <nil> >>. Instead, any fields with C<undef>
|
||||
values will be stripped from the response completely. Therefore
|
||||
B<the client must handle the fact that some expected fields may not be
|
||||
returned>.
|
||||
|
||||
=begin private
|
||||
|
||||
nil is implemented by XMLRPC::Lite, in XMLRPC::Deserializer::decode_value
|
||||
in the CPAN SVN since 14th Dec 2008
|
||||
L<http://rt.cpan.org/Public/Bug/Display.html?id=20569> and in Fedora's
|
||||
perl-SOAP-Lite package in versions 0.68-1 and above.
|
||||
|
||||
=end private
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,251 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Mads Bondo Dydensborg <mbd@dbc.dk>
|
||||
|
||||
package Bugzilla::WebService::Bugzilla;
|
||||
|
||||
use strict;
|
||||
use base qw(Bugzilla::WebService);
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Hook;
|
||||
|
||||
use DateTime;
|
||||
|
||||
# Basic info that is needed before logins
|
||||
use constant LOGIN_EXEMPT => {
|
||||
timezone => 1,
|
||||
version => 1,
|
||||
};
|
||||
|
||||
sub version {
|
||||
my $self = shift;
|
||||
return { version => $self->type('string', BUGZILLA_VERSION) };
|
||||
}
|
||||
|
||||
sub extensions {
|
||||
my $self = shift;
|
||||
my $extensions = Bugzilla::Hook::enabled_plugins();
|
||||
foreach my $name (keys %$extensions) {
|
||||
my $info = $extensions->{$name};
|
||||
foreach my $data (keys %$info) {
|
||||
$extensions->{$name}->{$data} =
|
||||
$self->type('string', $info->{$data});
|
||||
}
|
||||
}
|
||||
return { extensions => $extensions };
|
||||
}
|
||||
|
||||
sub timezone {
|
||||
my $self = shift;
|
||||
my $offset = Bugzilla->local_timezone->offset_for_datetime(DateTime->now());
|
||||
$offset = (($offset / 60) / 60) * 100;
|
||||
$offset = sprintf('%+05d', $offset);
|
||||
return { timezone => $self->type('string', $offset) };
|
||||
}
|
||||
|
||||
sub time {
|
||||
my ($self) = @_;
|
||||
my $dbh = Bugzilla->dbh;
|
||||
|
||||
my $db_time = $dbh->selectrow_array('SELECT LOCALTIMESTAMP(0)');
|
||||
my $now_utc = DateTime->now();
|
||||
|
||||
my $tz = Bugzilla->local_timezone;
|
||||
my $now_local = $now_utc->clone->set_time_zone($tz);
|
||||
my $tz_offset = $tz->offset_for_datetime($now_local);
|
||||
|
||||
return {
|
||||
db_time => $self->type('dateTime', $db_time),
|
||||
web_time => $self->type('dateTime', $now_local),
|
||||
web_time_utc => $self->type('dateTime', $now_utc),
|
||||
tz_name => $self->type('string', $tz->name),
|
||||
tz_offset => $self->type('string',
|
||||
$tz->offset_as_string($tz_offset)),
|
||||
tz_short_name => $self->type('string',
|
||||
$now_local->time_zone_short_name),
|
||||
};
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::WebService::Bugzilla - Global functions for the webservice interface.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This provides functions that tell you about Bugzilla in general.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
See L<Bugzilla::WebService> for a description of how parameters are passed,
|
||||
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
|
||||
|
||||
=over
|
||||
|
||||
=item C<version>
|
||||
|
||||
B<STABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns the current version of Bugzilla.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash with a single item, C<version>, that is the version as a
|
||||
string.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=item C<extensions>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Gets information about the extensions that are currently installed and enabled
|
||||
in this Bugzilla.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash with a single item, C<extesions>. This points to a hash. I<That> hash
|
||||
contains the names of extensions as keys, and information about the extension
|
||||
as values. One of the values that must be returned is the 'version' of the
|
||||
extension
|
||||
|
||||
=item B<History>
|
||||
|
||||
=over
|
||||
|
||||
=item Added in Bugzilla B<3.2>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<timezone>
|
||||
|
||||
B<DEPRECATED> This method may be removed in a future version of Bugzilla.
|
||||
Use L</time> instead.
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns the timezone of the server Bugzilla is running on. This is
|
||||
important because all dates/times that the webservice interface
|
||||
returns will be in this timezone.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash with a single item, C<timezone>, that is the timezone offset as a
|
||||
string in (+/-)XXXX (RFC 2822) format.
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=item C<time>
|
||||
|
||||
B<UNSTABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Gets information about what time the Bugzilla server thinks it is, and
|
||||
what timezone it's running in.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A struct with the following items:
|
||||
|
||||
=over
|
||||
|
||||
=item C<db_time>
|
||||
|
||||
C<dateTime> The current time in Bugzilla's B<local time zone>, according
|
||||
to the Bugzilla I<database server>.
|
||||
|
||||
Note that Bugzilla assumes that the database and the webserver are running
|
||||
in the same time zone. However, if the web server and the database server
|
||||
aren't synchronized for some reason, I<this> is the time that you should
|
||||
rely on for doing searches and other input to the WebService.
|
||||
|
||||
=item C<web_time>
|
||||
|
||||
C<dateTime> This is the current time in Bugzilla's B<local time zone>,
|
||||
according to Bugzilla's I<web server>.
|
||||
|
||||
This might be different by a second from C<db_time> since this comes from
|
||||
a different source. If it's any more different than a second, then there is
|
||||
likely some problem with this Bugzilla instance. In this case you should
|
||||
rely on the C<db_time>, not the C<web_time>.
|
||||
|
||||
=item C<web_time_utc>
|
||||
|
||||
The same as C<web_time>, but in the B<UTC> time zone instead of the local
|
||||
time zone.
|
||||
|
||||
=item C<tz_name>
|
||||
|
||||
C<string> The long name of the time zone that the Bugzilla web server is
|
||||
in. Will usually look something like: C<America/Los Angeles>
|
||||
|
||||
=item C<tz_short_name>
|
||||
|
||||
C<string> The "short name" of the time zone that the Bugzilla web server
|
||||
is in. This should only be used for display, and not relied on for your
|
||||
programs, because different time zones can have the same short name.
|
||||
(For example, there are two C<EST>s.)
|
||||
|
||||
This will look something like: C<PST>.
|
||||
|
||||
=item C<tz_offset>
|
||||
|
||||
C<string> The timezone offset as a string in (+/-)XXXX (RFC 2822) format.
|
||||
|
||||
=back
|
||||
|
||||
=item B<History>
|
||||
|
||||
=over
|
||||
|
||||
=item Added in Bugzilla B<3.4>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
|
||||
=back
|
||||
@@ -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.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::WebService::Constants;
|
||||
|
||||
use strict;
|
||||
use base qw(Exporter);
|
||||
|
||||
our @EXPORT = qw(
|
||||
WS_ERROR_CODE
|
||||
ERROR_UNKNOWN_FATAL
|
||||
ERROR_UNKNOWN_TRANSIENT
|
||||
ERROR_AUTH_NODATA
|
||||
|
||||
WS_DISPATCH
|
||||
);
|
||||
|
||||
# This maps the error names in global/*-error.html.tmpl to numbers.
|
||||
# Generally, transient errors should have a number above 0, and
|
||||
# fatal errors should have a number below 0.
|
||||
#
|
||||
# This hash should generally contain any error that could be thrown
|
||||
# by the WebService interface. If it's extremely unlikely that the
|
||||
# error could be thrown (like some CodeErrors), it doesn't have to
|
||||
# be listed here.
|
||||
#
|
||||
# "Transient" means "If you resubmit that request with different data,
|
||||
# it may work."
|
||||
#
|
||||
# "Fatal" means, "There's something wrong with Bugzilla, probably
|
||||
# something an administrator would have to fix."
|
||||
#
|
||||
# NOTE: Numbers must never be recycled. If you remove a number, leave a
|
||||
# comment that it was retired. Also, if an error changes its name, you'll
|
||||
# have to fix it here.
|
||||
use constant WS_ERROR_CODE => {
|
||||
# Generic errors (Bugzilla::Object and others) are 50-99.
|
||||
object_not_specified => 50,
|
||||
param_required => 50,
|
||||
params_required => 50,
|
||||
object_does_not_exist => 51,
|
||||
xmlrpc_invalid_value => 52,
|
||||
# Bug errors usually occupy the 100-200 range.
|
||||
improper_bug_id_field_value => 100,
|
||||
bug_id_does_not_exist => 101,
|
||||
bug_access_denied => 102,
|
||||
bug_access_query => 102,
|
||||
# These all mean "invalid alias"
|
||||
alias_too_long => 103,
|
||||
alias_in_use => 103,
|
||||
alias_is_numeric => 103,
|
||||
alias_has_comma_or_space => 103,
|
||||
# Misc. bug field errors
|
||||
illegal_field => 104,
|
||||
freetext_too_long => 104,
|
||||
# Component errors
|
||||
require_component => 105,
|
||||
component_name_too_long => 105,
|
||||
# Invalid Product
|
||||
no_products => 106,
|
||||
entry_access_denied => 106,
|
||||
product_access_denied => 106,
|
||||
product_disabled => 106,
|
||||
# Invalid Summary
|
||||
require_summary => 107,
|
||||
# Invalid field name
|
||||
invalid_field_name => 108,
|
||||
# Not authorized to edit the bug
|
||||
product_edit_denied => 109,
|
||||
# Comment-related errors
|
||||
comment_is_private => 110,
|
||||
comment_id_invalid => 111,
|
||||
# See Also errors
|
||||
bug_url_invalid => 112,
|
||||
bug_url_too_long => 112,
|
||||
# Insidergroup Errors
|
||||
user_not_insider => 113,
|
||||
|
||||
# Authentication errors are usually 300-400.
|
||||
invalid_username_or_password => 300,
|
||||
account_disabled => 301,
|
||||
auth_invalid_email => 302,
|
||||
extern_id_conflict => -303,
|
||||
auth_failure => 304,
|
||||
|
||||
# User errors are 500-600.
|
||||
account_exists => 500,
|
||||
illegal_email_address => 501,
|
||||
account_creation_disabled => 501,
|
||||
account_creation_restricted => 501,
|
||||
password_too_short => 502,
|
||||
password_too_long => 503,
|
||||
invalid_username => 504,
|
||||
# This is from strict_isolation, but it also basically means
|
||||
# "invalid user."
|
||||
invalid_user_group => 504,
|
||||
user_access_by_id_denied => 505,
|
||||
user_access_by_match_denied => 505,
|
||||
};
|
||||
|
||||
# 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_GENERAL => 999;
|
||||
|
||||
sub WS_DISPATCH {
|
||||
# We "require" here instead of "use" above to avoid a dependency loop.
|
||||
require Bugzilla::Hook;
|
||||
my %hook_dispatch;
|
||||
Bugzilla::Hook::process('webservice', { dispatch => \%hook_dispatch });
|
||||
|
||||
my $dispatch = {
|
||||
'Bugzilla' => 'Bugzilla::WebService::Bugzilla',
|
||||
'Bug' => 'Bugzilla::WebService::Bug',
|
||||
'User' => 'Bugzilla::WebService::User',
|
||||
'Product' => 'Bugzilla::WebService::Product',
|
||||
%hook_dispatch
|
||||
};
|
||||
return $dispatch;
|
||||
};
|
||||
|
||||
|
||||
1;
|
||||
@@ -1,198 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Mads Bondo Dydensborg <mbd@dbc.dk>
|
||||
|
||||
package Bugzilla::WebService::Product;
|
||||
|
||||
use strict;
|
||||
use base qw(Bugzilla::WebService);
|
||||
use Bugzilla::Product;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::WebService::Util qw(validate);
|
||||
|
||||
##################################################
|
||||
# Add aliases here for method name compatibility #
|
||||
##################################################
|
||||
|
||||
BEGIN { *get_products = \&get }
|
||||
|
||||
# Get the ids of the products the user can search
|
||||
sub get_selectable_products {
|
||||
return {ids => [map {$_->id} @{Bugzilla->user->get_selectable_products}]};
|
||||
}
|
||||
|
||||
# Get the ids of the products the user can enter bugs against
|
||||
sub get_enterable_products {
|
||||
return {ids => [map {$_->id} @{Bugzilla->user->get_enterable_products}]};
|
||||
}
|
||||
|
||||
# Get the union of the products the user can search and enter bugs against.
|
||||
sub get_accessible_products {
|
||||
return {ids => [map {$_->id} @{Bugzilla->user->get_accessible_products}]};
|
||||
}
|
||||
|
||||
# Get a list of actual products, based on list of ids
|
||||
sub get {
|
||||
my ($self, $params) = validate(@_, 'ids');
|
||||
|
||||
# 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 => $self->type('int', $_->id),
|
||||
name => $self->type('string', $_->name),
|
||||
description => $self->type('string', $_->description),
|
||||
}
|
||||
} @requested_accessible;
|
||||
|
||||
return { products => \@products };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Webservice::Product - The Product API
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This part of the Bugzilla API allows you to list the available Products and
|
||||
get information about them.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
See L<Bugzilla::WebService> for a description of how parameters are passed,
|
||||
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
|
||||
|
||||
=head2 List Products
|
||||
|
||||
=over
|
||||
|
||||
=item C<get_selectable_products>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns a list of the ids of the products the user can search on.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<ids>, that contains an array of product
|
||||
ids.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=item C<get_enterable_products>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns a list of the ids of the products the user can enter bugs
|
||||
against.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<ids>, that contains an array of product
|
||||
ids.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=item C<get_accessible_products>
|
||||
|
||||
B<UNSTABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns a list of the ids of the products the user can search or enter
|
||||
bugs against.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<ids>, that contains an array of product
|
||||
ids.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=item C<get>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Returns a list of information about the products passed to it.
|
||||
|
||||
Note: Can also be called as "get_products" for compatibilty with Bugzilla 3.0 API.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
A hash containing one item, C<ids>, that is an array of product ids.
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<products>, that is an array of
|
||||
hashes. Each hash describes a product, and has the following items:
|
||||
C<id>, C<name>, C<description>, and C<internals>. The C<id> item is
|
||||
the id of the product. The C<name> item is the name of the
|
||||
product. The C<description> is the description of the
|
||||
product. Finally, the C<internals> is an internal representation of
|
||||
the product.
|
||||
|
||||
Note, that if the user tries to access a product that is not in the
|
||||
list of accessible products for the user, or a product that does not
|
||||
exist, that is silently ignored, and no information about that product
|
||||
is returned.
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
@@ -1,42 +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::Server;
|
||||
use strict;
|
||||
use Bugzilla::Util qw(ssl_require_redirect);
|
||||
|
||||
sub handle_login {
|
||||
my ($self, $class, $method, $full_method) = @_;
|
||||
eval "require $class";
|
||||
return if $class->login_exempt($method);
|
||||
Bugzilla->login();
|
||||
|
||||
# Even though we check for the need to redirect in
|
||||
# Bugzilla->login() we check here again since Bugzilla->login()
|
||||
# does not know what the current XMLRPC method is. Therefore
|
||||
# ssl_require_redirect in Bugzilla->login() will have returned
|
||||
# false if system was configured to redirect for authenticated
|
||||
# sessions and the user was not yet logged in.
|
||||
# So here we pass in the method name to ssl_require_redirect so
|
||||
# it can then check for the extra case where the method equals
|
||||
# User.login, which we would then need to redirect if not
|
||||
# over a secure connection.
|
||||
Bugzilla->cgi->require_https(Bugzilla->params->{'sslbase'})
|
||||
if ssl_require_redirect($full_method);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,254 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# Contributor(s): Marc Schumann <wurblzap@gmail.com>
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
# Rosie Clarkson <rosie.clarkson@planningportal.gov.uk>
|
||||
#
|
||||
# Portions © Crown copyright 2009 - Rosie Clarkson (development@planningportal.gov.uk) for the Planning Portal
|
||||
|
||||
package Bugzilla::WebService::Server::XMLRPC;
|
||||
|
||||
use strict;
|
||||
use XMLRPC::Transport::HTTP;
|
||||
use Bugzilla::WebService::Server;
|
||||
our @ISA = qw(XMLRPC::Transport::HTTP::CGI Bugzilla::WebService::Server);
|
||||
|
||||
use Bugzilla::WebService::Constants;
|
||||
|
||||
sub initialize {
|
||||
my $self = shift;
|
||||
my %retval = $self->SUPER::initialize(@_);
|
||||
$retval{'serializer'} = Bugzilla::XMLRPC::Serializer->new;
|
||||
$retval{'deserializer'} = Bugzilla::XMLRPC::Deserializer->new;
|
||||
$retval{'dispatch_with'} = WS_DISPATCH;
|
||||
return %retval;
|
||||
}
|
||||
|
||||
sub make_response {
|
||||
my $self = shift;
|
||||
|
||||
$self->SUPER::make_response(@_);
|
||||
|
||||
# XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
|
||||
# its cookies in Bugzilla::CGI, so we need to copy them over.
|
||||
foreach (@{Bugzilla->cgi->{'Bugzilla_cookie_list'}}) {
|
||||
$self->response->headers->push_header('Set-Cookie', $_);
|
||||
}
|
||||
}
|
||||
|
||||
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, $classes, $action, $uri, $method) = @_;
|
||||
my $class = $classes->{$uri};
|
||||
my $full_method = $uri . "." . $method;
|
||||
$self->SUPER::handle_login($class, $method, $full_method);
|
||||
return;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
# This exists to validate input parameters (which XMLRPC::Lite doesn't do)
|
||||
# and also, in some cases, to more-usefully decode them.
|
||||
package Bugzilla::XMLRPC::Deserializer;
|
||||
use strict;
|
||||
# We can't use "use base" because XMLRPC::Serializer doesn't return
|
||||
# a true value.
|
||||
eval { require XMLRPC::Lite; };
|
||||
our @ISA = qw(XMLRPC::Deserializer);
|
||||
|
||||
use Bugzilla::Error;
|
||||
|
||||
# Some method arguments need to be converted in some way, when they are input.
|
||||
sub decode_value {
|
||||
my $self = shift;
|
||||
my ($type) = @{ $_[0] };
|
||||
my $value = $self->SUPER::decode_value(@_);
|
||||
|
||||
# We only validate/convert certain types here.
|
||||
return $value if $type !~ /^(?:int|i4|boolean|double|dateTime\.iso8601)$/;
|
||||
|
||||
# Though the XML-RPC standard doesn't allow an empty <int>,
|
||||
# <double>,or <dateTime.iso8601>, we do, and we just say
|
||||
# "that's undef".
|
||||
if (grep($type eq $_, qw(int double dateTime))) {
|
||||
return undef if $value eq '';
|
||||
}
|
||||
|
||||
my $validator = $self->_validation_subs->{$type};
|
||||
if (!$validator->($value)) {
|
||||
ThrowUserError('xmlrpc_invalid_value',
|
||||
{ type => $type, value => $value });
|
||||
}
|
||||
|
||||
# We convert dateTimes to a DB-friendly date format.
|
||||
if ($type eq 'dateTime.iso8601') {
|
||||
# We leave off the $ from the end of this regex to allow for possible
|
||||
# extensions to the XML-RPC date standard.
|
||||
$value =~ /^(\d{4})(\d{2})(\d{2})T(\d{2}):(\d{2}):(\d{2})/;
|
||||
$value = "$1-$2-$3 $4:$5:$6";
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
sub _validation_subs {
|
||||
my $self = shift;
|
||||
return $self->{_validation_subs} if $self->{_validation_subs};
|
||||
# The only place that XMLRPC::Lite stores any sort of validation
|
||||
# regex is in XMLRPC::Serializer. We want to re-use those regexes here.
|
||||
my $lookup = Bugzilla::XMLRPC::Serializer->new->typelookup;
|
||||
|
||||
# $lookup is a hash whose values are arrayrefs, and whose keys are the
|
||||
# names of types. The second item of each arrayref is a subroutine
|
||||
# that will do our validation for us.
|
||||
my %validators = map { $_ => $lookup->{$_}->[1] } (keys %$lookup);
|
||||
# Add a boolean validator
|
||||
$validators{'boolean'} = sub {$_[0] =~ /^[01]$/};
|
||||
# Some types have multiple names, or have a different name in
|
||||
# XMLRPC::Serializer than their standard XML-RPC name.
|
||||
$validators{'dateTime.iso8601'} = $validators{'dateTime'};
|
||||
$validators{'i4'} = $validators{'int'};
|
||||
|
||||
$self->{_validation_subs} = \%validators;
|
||||
return \%validators;
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
# This package exists to fix a UTF-8 bug in SOAP::Lite.
|
||||
# See http://rt.cpan.org/Public/Bug/Display.html?id=32952.
|
||||
package Bugzilla::XMLRPC::Serializer;
|
||||
use Scalar::Util qw(blessed);
|
||||
use strict;
|
||||
# We can't use "use base" because XMLRPC::Serializer doesn't return
|
||||
# a true value.
|
||||
eval { require XMLRPC::Lite; };
|
||||
our @ISA = qw(XMLRPC::Serializer);
|
||||
|
||||
sub new {
|
||||
my $class = shift;
|
||||
my $self = $class->SUPER::new(@_);
|
||||
# This fixes UTF-8.
|
||||
$self->{'_typelookup'}->{'base64'} =
|
||||
[10, sub { !utf8::is_utf8($_[0]) && $_[0] =~ /[^\x09\x0a\x0d\x20-\x7f]/},
|
||||
'as_base64'];
|
||||
# This makes arrays work right even though we're a subclass.
|
||||
# (See http://rt.cpan.org//Ticket/Display.html?id=34514)
|
||||
$self->{'_encodingStyle'} = '';
|
||||
return $self;
|
||||
}
|
||||
|
||||
sub as_string {
|
||||
my $self = shift;
|
||||
my ($value) = @_;
|
||||
# Something weird happens with XML::Parser when we have upper-ASCII
|
||||
# characters encoded as UTF-8, and this fixes it.
|
||||
utf8::encode($value) if utf8::is_utf8($value)
|
||||
&& $value =~ /^[\x00-\xff]+$/;
|
||||
return $self->SUPER::as_string($value);
|
||||
}
|
||||
|
||||
# Here the XMLRPC::Serializer is extended to use the XMLRPC nil extension.
|
||||
sub encode_object {
|
||||
my $self = shift;
|
||||
my @encoded = $self->SUPER::encode_object(@_);
|
||||
|
||||
return $encoded[0]->[0] eq 'nil'
|
||||
? ['value', {}, [@encoded]]
|
||||
: @encoded;
|
||||
}
|
||||
|
||||
# Removes undefined values so they do not produce invalid XMLRPC.
|
||||
sub envelope {
|
||||
my $self = shift;
|
||||
my ($type, $method, $data) = @_;
|
||||
# If the type isn't a successful response we don't want to change the values.
|
||||
if ($type eq 'response'){
|
||||
$data = _strip_undefs($data);
|
||||
}
|
||||
return $self->SUPER::envelope($type, $method, $data);
|
||||
}
|
||||
|
||||
# In an XMLRPC response we have to handle hashes of arrays, hashes, scalars,
|
||||
# Bugzilla objects (reftype = 'HASH') and XMLRPC::Data objects.
|
||||
# The whole XMLRPC::Data object must be removed if its value key is undefined
|
||||
# so it cannot be recursed like the other hash type objects.
|
||||
sub _strip_undefs {
|
||||
my ($initial) = @_;
|
||||
if (ref $initial eq "HASH" || (blessed $initial && $initial->isa("HASH"))) {
|
||||
while (my ($key, $value) = each(%$initial)) {
|
||||
if ( !defined $value
|
||||
|| (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value) )
|
||||
{
|
||||
# If the value is undefined remove it from the hash.
|
||||
delete $initial->{$key};
|
||||
}
|
||||
else {
|
||||
$initial->{$key} = _strip_undefs($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ref $initial eq "ARRAY" || (blessed $initial && $initial->isa("ARRAY"))) {
|
||||
for (my $count = 0; $count < scalar @{$initial}; $count++) {
|
||||
my $value = $initial->[$count];
|
||||
if ( !defined $value
|
||||
|| (blessed $value && $value->isa('XMLRPC::Data') && !defined $value->value) )
|
||||
{
|
||||
# If the value is undefined remove it from the array.
|
||||
splice(@$initial, $count, 1);
|
||||
$count--;
|
||||
}
|
||||
else {
|
||||
$initial->[$count] = _strip_undefs($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $initial;
|
||||
}
|
||||
|
||||
|
||||
sub BEGIN {
|
||||
no strict 'refs';
|
||||
for my $type (qw(double i4 int dateTime)) {
|
||||
my $method = 'as_' . $type;
|
||||
*$method = sub {
|
||||
my ($self, $value) = @_;
|
||||
if (!defined($value)) {
|
||||
return as_nil();
|
||||
}
|
||||
else {
|
||||
my $super_method = "SUPER::$method";
|
||||
return $self->$super_method($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub as_nil {
|
||||
return ['nil', {}];
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,568 +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>
|
||||
# Noura Elhawary <nelhawar@redhat.com>
|
||||
|
||||
package Bugzilla::WebService::User;
|
||||
|
||||
use strict;
|
||||
use base qw(Bugzilla::WebService);
|
||||
|
||||
use Bugzilla;
|
||||
use Bugzilla::Constants;
|
||||
use Bugzilla::Error;
|
||||
use Bugzilla::User;
|
||||
use Bugzilla::Util qw(trim);
|
||||
use Bugzilla::Token;
|
||||
use Bugzilla::WebService::Util qw(filter validate);
|
||||
|
||||
# Don't need auth to login
|
||||
use constant LOGIN_EXEMPT => {
|
||||
login => 1,
|
||||
offer_account_by_email => 1,
|
||||
};
|
||||
|
||||
##############
|
||||
# User Login #
|
||||
##############
|
||||
|
||||
sub login {
|
||||
my ($self, $params) = @_;
|
||||
my $remember = $params->{remember};
|
||||
|
||||
# Username and password params are required
|
||||
foreach my $param ("login", "password") {
|
||||
defined $params->{$param}
|
||||
|| ThrowCodeError('param_required', { param => $param });
|
||||
}
|
||||
|
||||
# Convert $remember from a boolean 0/1 value to a CGI-compatible one.
|
||||
if (defined($remember)) {
|
||||
$remember = $remember? 'on': '';
|
||||
}
|
||||
else {
|
||||
# Use Bugzilla's default if $remember is not supplied.
|
||||
$remember =
|
||||
Bugzilla->params->{'rememberlogin'} eq 'defaulton'? 'on': '';
|
||||
}
|
||||
|
||||
# Make sure the CGI user info class works if necessary.
|
||||
my $cgi = Bugzilla->cgi;
|
||||
$cgi->param('Bugzilla_login', $params->{login});
|
||||
$cgi->param('Bugzilla_password', $params->{password});
|
||||
$cgi->param('Bugzilla_remember', $remember);
|
||||
|
||||
Bugzilla->login;
|
||||
return { id => $self->type('int', Bugzilla->user->id) };
|
||||
}
|
||||
|
||||
sub logout {
|
||||
my $self = shift;
|
||||
Bugzilla->logout;
|
||||
return undef;
|
||||
}
|
||||
|
||||
#################
|
||||
# User Creation #
|
||||
#################
|
||||
|
||||
sub offer_account_by_email {
|
||||
my $self = shift;
|
||||
my ($params) = @_;
|
||||
my $email = trim($params->{email})
|
||||
|| ThrowCodeError('param_required', { param => 'email' });
|
||||
|
||||
my $createexp = Bugzilla->params->{'createemailregexp'};
|
||||
if (!$createexp) {
|
||||
ThrowUserError("account_creation_disabled");
|
||||
}
|
||||
elsif ($email !~ /$createexp/) {
|
||||
ThrowUserError("account_creation_restricted");
|
||||
}
|
||||
|
||||
$email = Bugzilla::User->check_login_name_for_creation($email);
|
||||
|
||||
# Create and send a token for this new account.
|
||||
Bugzilla::Token::issue_new_user_account_token($email);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub create {
|
||||
my $self = shift;
|
||||
my ($params) = @_;
|
||||
|
||||
Bugzilla->user->in_group('editusers')
|
||||
|| ThrowUserError("auth_failure", { group => "editusers",
|
||||
action => "add",
|
||||
object => "users"});
|
||||
|
||||
my $email = trim($params->{email})
|
||||
|| ThrowCodeError('param_required', { param => 'email' });
|
||||
my $realname = trim($params->{full_name});
|
||||
my $password = trim($params->{password}) || '*';
|
||||
|
||||
my $user = Bugzilla::User->create({
|
||||
login_name => $email,
|
||||
realname => $realname,
|
||||
cryptpassword => $password
|
||||
});
|
||||
|
||||
return { id => $self->type('int', $user->id) };
|
||||
}
|
||||
|
||||
|
||||
# function to return user information by passing either user ids or
|
||||
# login names or both together:
|
||||
# $call = $rpc->call( 'User.get', { ids => [1,2,3],
|
||||
# names => ['testusera@redhat.com', 'testuserb@redhat.com'] });
|
||||
sub get {
|
||||
my ($self, $params) = validate(@_, 'names', 'ids');
|
||||
|
||||
my @user_objects;
|
||||
@user_objects = map { Bugzilla::User->check($_) } @{ $params->{names} }
|
||||
if $params->{names};
|
||||
|
||||
# start filtering to remove duplicate user ids
|
||||
my %unique_users = map { $_->id => $_ } @user_objects;
|
||||
@user_objects = values %unique_users;
|
||||
|
||||
my @users;
|
||||
|
||||
# If the user is not logged in: Return an error if they passed any user ids.
|
||||
# Otherwise, return a limited amount of information based on login names.
|
||||
if (!Bugzilla->user->id){
|
||||
if ($params->{ids}){
|
||||
ThrowUserError("user_access_by_id_denied");
|
||||
}
|
||||
if ($params->{match}) {
|
||||
ThrowUserError('user_access_by_match_denied');
|
||||
}
|
||||
@users = map {filter $params, {
|
||||
id => $self->type('int', $_->id),
|
||||
real_name => $self->type('string', $_->name),
|
||||
name => $self->type('string', $_->login),
|
||||
}} @user_objects;
|
||||
|
||||
return { users => \@users };
|
||||
}
|
||||
|
||||
my $obj_by_ids;
|
||||
$obj_by_ids = Bugzilla::User->new_from_list($params->{ids}) if $params->{ids};
|
||||
|
||||
# obj_by_ids are only visible to the user if he can see
|
||||
# the otheruser, for non visible otheruser throw an error
|
||||
foreach my $obj (@$obj_by_ids) {
|
||||
if (Bugzilla->user->can_see_user($obj)){
|
||||
if (!$unique_users{$obj->id}) {
|
||||
push (@user_objects, $obj);
|
||||
$unique_users{$obj->id} = $obj;
|
||||
}
|
||||
}
|
||||
else {
|
||||
ThrowUserError('auth_failure', {reason => "not_visible",
|
||||
action => "access",
|
||||
object => "user",
|
||||
userid => $obj->id});
|
||||
}
|
||||
}
|
||||
|
||||
# User Matching
|
||||
my $limit;
|
||||
if ($params->{'maxusermatches'}) {
|
||||
$limit = $params->{'maxusermatches'} + 1;
|
||||
}
|
||||
foreach my $match_string (@{ $params->{'match'} || [] }) {
|
||||
my $matched = Bugzilla::User::match($match_string, $limit);
|
||||
foreach my $user (@$matched) {
|
||||
if (!$unique_users{$user->id}) {
|
||||
push(@user_objects, $user);
|
||||
$unique_users{$user->id} = $user;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Bugzilla->user->in_group('editusers')) {
|
||||
@users =
|
||||
map {filter $params, {
|
||||
id => $self->type('int', $_->id),
|
||||
real_name => $self->type('string', $_->name),
|
||||
name => $self->type('string', $_->login),
|
||||
email => $self->type('string', $_->email),
|
||||
can_login => $self->type('boolean', $_->is_disabled ? 0 : 1),
|
||||
email_enabled => $self->type('boolean', $_->email_enabled),
|
||||
login_denied_text => $self->type('string', $_->disabledtext),
|
||||
}} @user_objects;
|
||||
|
||||
}
|
||||
else {
|
||||
@users =
|
||||
map {filter $params, {
|
||||
id => $self->type('int', $_->id),
|
||||
real_name => $self->type('string', $_->name),
|
||||
name => $self->type('string', $_->login),
|
||||
email => $self->type('string', $_->email),
|
||||
can_login => $self->type('boolean', $_->is_disabled ? 0 : 1),
|
||||
}} @user_objects;
|
||||
}
|
||||
|
||||
return { users => \@users };
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::Webservice::User - The User Account and Login API
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This part of the Bugzilla API allows you to create User Accounts and
|
||||
log in/out using an existing account.
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
See L<Bugzilla::WebService> for a description of how parameters are passed,
|
||||
and what B<STABLE>, B<UNSTABLE>, and B<EXPERIMENTAL> mean.
|
||||
|
||||
=head2 Logging In and Out
|
||||
|
||||
=over
|
||||
|
||||
=item C<login>
|
||||
|
||||
B<STABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Logging in, with a username and password, is required for many
|
||||
Bugzilla installations, in order to search for bugs, post new bugs,
|
||||
etc. This method logs in an user.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<login> (string) - The user's login name.
|
||||
|
||||
=item C<password> (string) - The user's password.
|
||||
|
||||
=item C<remember> (bool) B<Optional> - if the cookies returned by the
|
||||
call to login should expire with the session or not. In order for
|
||||
this option to have effect the Bugzilla server must be configured to
|
||||
allow the user to set this option - the Bugzilla parameter
|
||||
I<rememberlogin> must be set to "defaulton" or
|
||||
"defaultoff". Addionally, the client application must implement
|
||||
management of cookies across sessions.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
On success, a hash containing one item, C<id>, the numeric id of the
|
||||
user that was logged in. A set of http cookies is also sent with the
|
||||
response. These cookies must be sent along with any future requests
|
||||
to the webservice, for the duration of the session.
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 300 (Invalid Username or Password)
|
||||
|
||||
The username does not exist, or the password is wrong.
|
||||
|
||||
=item 301 (Account Disabled)
|
||||
|
||||
The account has been disabled. A reason may be specified with the
|
||||
error.
|
||||
|
||||
=item 50 (Param Required)
|
||||
|
||||
A login or password parameter was not provided.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<logout>
|
||||
|
||||
B<STABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Log out the user. Does nothing if there is no user logged in.
|
||||
|
||||
=item B<Params> (none)
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=item B<Errors> (none)
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 Account Creation
|
||||
|
||||
=over
|
||||
|
||||
=item C<offer_account_by_email>
|
||||
|
||||
B<STABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Sends an email to the user, offering to create an account. The user
|
||||
will have to click on a URL in the email, and choose their password
|
||||
and real name.
|
||||
|
||||
This is the recommended way to create a Bugzilla account.
|
||||
|
||||
=item B<Param>
|
||||
|
||||
=over
|
||||
|
||||
=item C<email> (string) - the email to send the offer to.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns> (nothing)
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 500 (Illegal Email Address)
|
||||
|
||||
This Bugzilla does not allow you to create accounts with the format of
|
||||
email address you specified. Account creation may be entirely disabled.
|
||||
|
||||
=item 501 (Account Already Exists)
|
||||
|
||||
An account with that email address already exists in Bugzilla.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=item C<create>
|
||||
|
||||
B<EXPERIMENTAL>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Creates a user account directly in Bugzilla, password and all.
|
||||
Instead of this, you should use L</offer_account_by_email> when
|
||||
possible, because that makes sure that the email address specified can
|
||||
actually receive an email. This function does not check that.
|
||||
|
||||
You must be logged in and have the C<editusers> privilege in order to
|
||||
call this function.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
=over
|
||||
|
||||
=item C<email> (string) - The email address for the new user.
|
||||
|
||||
=item C<full_name> (string) B<Optional> - The user's full name. Will
|
||||
be set to empty if not specified.
|
||||
|
||||
=item C<password> (string) B<Optional> - The password for the new user
|
||||
account, in plain text. It will be stripped of leading and trailing
|
||||
whitespace. If blank or not specified, the newly created account will
|
||||
exist in Bugzilla, but will not be allowed to log in using DB
|
||||
authentication until a password is set either by the user (through
|
||||
resetting their password) or by the administrator.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<id>, the numeric id of the user that was
|
||||
created.
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
The same as L</offer_account_by_email>. If a password is specified,
|
||||
the function may also throw:
|
||||
|
||||
=over
|
||||
|
||||
=item 502 (Password Too Short)
|
||||
|
||||
The password specified is too short. (Usually, this means the
|
||||
password is under three characters.)
|
||||
|
||||
=item 503 (Password Too Long)
|
||||
|
||||
The password specified is too long. (Usually, this means the
|
||||
password is over ten characters.)
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 User Info
|
||||
|
||||
=over
|
||||
|
||||
=item C<get>
|
||||
|
||||
B<UNSTABLE>
|
||||
|
||||
=over
|
||||
|
||||
=item B<Description>
|
||||
|
||||
Gets information about user accounts in Bugzilla.
|
||||
|
||||
=item B<Params>
|
||||
|
||||
B<Note>: At least one of C<ids>, C<names>, or C<match> must be specified.
|
||||
|
||||
B<Note>: Users will not be returned more than once, so even if a user
|
||||
is matched by more than one argument, only one user will be returned.
|
||||
|
||||
In addition to the parameters below, this method also accepts the
|
||||
standard L<include_fields|Bugzilla::WebService/include_fields> and
|
||||
L<exclude_fields|Bugzilla::WebService/exclude_fields> arguments.
|
||||
|
||||
=over
|
||||
|
||||
=item C<ids> (array)
|
||||
|
||||
An array of integers, representing user ids.
|
||||
|
||||
Logged-out users cannot pass this parameter to this function. If they try,
|
||||
they will get an error. Logged-in users will get an error if they specify
|
||||
the id of a user they cannot see.
|
||||
|
||||
=item C<names> (array) - An array of login names (strings).
|
||||
|
||||
=item C<match> (array)
|
||||
|
||||
An array of strings. This works just like "user matching" in
|
||||
Bugzilla itself. Users will be returned whose real name or login name
|
||||
contains any one of the specified strings. Users that you cannot see will
|
||||
not be included in the returned list.
|
||||
|
||||
Some Bugzilla installations have user-matching turned off, in which
|
||||
case you will only be returned exact matches.
|
||||
|
||||
Most installations have a limit on how many matches are returned for
|
||||
each string, which defaults to 1000 but can be changed by the Bugzilla
|
||||
administrator.
|
||||
|
||||
Logged-out users cannot use this argument, and an error will be thrown
|
||||
if they try. (This is to make it harder for spammers to harvest email
|
||||
addresses from Bugzilla, and also to enforce the user visibility
|
||||
restrictions that are implemented on some Bugzillas.)
|
||||
|
||||
=back
|
||||
|
||||
=item B<Returns>
|
||||
|
||||
A hash containing one item, C<users>, that is an array of
|
||||
hashes. Each hash describes a user, and has the following items:
|
||||
|
||||
=over
|
||||
|
||||
=item id
|
||||
|
||||
C<int> The unique integer ID that Bugzilla uses to represent this user.
|
||||
Even if the user's login name changes, this will not change.
|
||||
|
||||
=item real_name
|
||||
|
||||
C<string> The actual name of the user. May be blank.
|
||||
|
||||
=item email
|
||||
|
||||
C<string> The email address of the user.
|
||||
|
||||
=item name
|
||||
|
||||
C<string> The login name of the user. Note that in some situations this is
|
||||
different than their email.
|
||||
|
||||
=item can_login
|
||||
|
||||
C<boolean> A boolean value to indicate if the user can login into bugzilla.
|
||||
|
||||
=item email_enabled
|
||||
|
||||
C<boolean> A boolean value to indicate if bug-related mail will be sent
|
||||
to the user or not.
|
||||
|
||||
=item login_denied_text
|
||||
|
||||
C<string> A text field that holds the reason for disabling a user from logging
|
||||
into bugzilla, if empty then the user account is enabled. Otherwise it is
|
||||
disabled/closed.
|
||||
|
||||
B<Note>: If you are not logged in to Bugzilla when you call this function, you
|
||||
will only be returned the C<id>, C<name>, and C<real_name> items. If you are
|
||||
logged in and not in editusers group, you will only be returned the C<id>, C<name>,
|
||||
C<real_name>, C<email>, and C<can_login> items.
|
||||
|
||||
=back
|
||||
|
||||
=item B<Errors>
|
||||
|
||||
=over
|
||||
|
||||
=item 51 (Bad Login Name)
|
||||
|
||||
You passed an invalid login name in the "names" array.
|
||||
|
||||
=item 304 (Authorization Required)
|
||||
|
||||
You are logged in, but you are not authorized to see one of the users you
|
||||
wanted to get information about by user id.
|
||||
|
||||
=item 505 (User Access By Id or User-Matching Denied)
|
||||
|
||||
Logged-out users cannot use the "ids" or "match" arguments to this
|
||||
function.
|
||||
|
||||
=back
|
||||
|
||||
=item B<History>
|
||||
|
||||
=over
|
||||
|
||||
=item Added in Bugzilla B<3.4>.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
@@ -1,101 +0,0 @@
|
||||
# -*- Mode: perl; indent-tabs-mode: nil -*-
|
||||
#
|
||||
# The contents of this file are subject to the Mozilla Public
|
||||
# License Version 1.1 (the "License"); you may not use this file
|
||||
# except in compliance with the License. You may obtain a copy of
|
||||
# the License at http://www.mozilla.org/MPL/
|
||||
#
|
||||
# Software distributed under the License is distributed on an "AS
|
||||
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
||||
# implied. See the License for the specific language governing
|
||||
# rights and limitations under the License.
|
||||
#
|
||||
# The Original Code is the Bugzilla Bug Tracking System.
|
||||
#
|
||||
# The Initial Developer of the Original Code is Everything Solved, Inc.
|
||||
# Portions created by the Initial Developer are Copyright (C) 2008
|
||||
# the Initial Developer. All Rights Reserved.
|
||||
#
|
||||
# Contributor(s):
|
||||
# Max Kanat-Alexander <mkanat@bugzilla.org>
|
||||
|
||||
package Bugzilla::WebService::Util;
|
||||
use strict;
|
||||
|
||||
use base qw(Exporter);
|
||||
|
||||
our @EXPORT_OK = qw(filter validate);
|
||||
|
||||
sub filter ($$) {
|
||||
my ($params, $hash) = @_;
|
||||
my %newhash = %$hash;
|
||||
my %include = map { $_ => 1 } @{ $params->{'include_fields'} || [] };
|
||||
my %exclude = map { $_ => 1 } @{ $params->{'exclude_fields'} || [] };
|
||||
|
||||
foreach my $key (keys %$hash) {
|
||||
if (defined $params->{include_fields}) {
|
||||
delete $newhash{$key} if !$include{$key};
|
||||
}
|
||||
if (defined $params->{exclude_fields}) {
|
||||
delete $newhash{$key} if $exclude{$key};
|
||||
}
|
||||
}
|
||||
|
||||
return \%newhash;
|
||||
}
|
||||
|
||||
sub validate {
|
||||
my ($self, $params, @keys) = @_;
|
||||
|
||||
# If @keys is not empty then we convert any named
|
||||
# parameters that have scalar values to arrayrefs
|
||||
# that match.
|
||||
foreach my $key (@keys) {
|
||||
if (exists $params->{$key}) {
|
||||
$params->{$key} = ref $params->{$key}
|
||||
? $params->{$key}
|
||||
: [ $params->{$key} ];
|
||||
}
|
||||
}
|
||||
|
||||
return ($self, $params);
|
||||
}
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Bugzilla::WebService::Util - Utility functions used inside of the WebService
|
||||
code. These are B<not> functions that can be called via the WebService.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is somewhat like L<Bugzilla::Util>, but these functions are only used
|
||||
internally in the WebService code.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
filter({ include_fields => ['id', 'name'],
|
||||
exclude_fields => ['name'] }, $hash);
|
||||
|
||||
validate(@_, 'ids');
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=over
|
||||
|
||||
=item C<filter_fields>
|
||||
|
||||
This helps implement the C<include_fields> and C<exclude_fields> arguments
|
||||
of WebService methods. Given a hash (the second argument to this subroutine),
|
||||
this will remove any keys that are I<not> in C<include_fields> and then remove
|
||||
any keys that I<are> in C<exclude_fields>.
|
||||
|
||||
=item C<validate>
|
||||
|
||||
This helps in the validation of parameters passed into the WebSerice
|
||||
methods. Currently it converts listed parameters into an array reference
|
||||
if the client only passed a single scalar value. It modifies the parameters
|
||||
hash in place so other parameters should be unaltered.
|
||||
|
||||
=back
|
||||
@@ -1,84 +0,0 @@
|
||||
Bugzilla Quick Start Guide
|
||||
==========================
|
||||
(or, how to get Bugzilla up and running in 10 steps)
|
||||
Christian Reis <kiko@async.com.br>
|
||||
|
||||
This express installation guide is for "normal" Bugzilla installations,
|
||||
which means a Linux or Unix system on which Apache, Perl, MySQL or PostgreSQL
|
||||
and a Sendmail compatible MTA are available. For other configurations, please
|
||||
see Section 4 of the Bugzilla Guide in the docs/ directory.
|
||||
|
||||
1. Decide from which URL and directory under your webserver root you
|
||||
will be serving the Bugzilla webpages.
|
||||
|
||||
2. Unpack the distribution into the chosen directory (there is no copying or
|
||||
installation involved).
|
||||
|
||||
3. Run ./checksetup.pl, look for unsolved requirements, and install them.
|
||||
You can run checksetup as many times as necessary to check if
|
||||
everything required has been installed.
|
||||
|
||||
These will usually include assorted Perl modules, MySQL or PostgreSQL,
|
||||
and a MTA.
|
||||
|
||||
After a successful dependency check, checksetup should complain that
|
||||
localconfig needs to be edited.
|
||||
|
||||
4. Edit the localconfig file, in particular the $webservergroup and
|
||||
$db_* variables. In particular, $db_name and $db_user will define
|
||||
your database setup in step 5.
|
||||
|
||||
5. Using the name you provided as $db_name above, create a MySQL database
|
||||
for Bugzilla. You should also create a user permission for the name
|
||||
supplied as $db_user with read/write access to that database.
|
||||
|
||||
If you are not familiar with MySQL permissions, it's a good idea to
|
||||
use the mysql_setpermission script that is installed with the MySQL
|
||||
distribution, and be sure to read Bugzilla Security - MySQL section
|
||||
in the Bugzilla Guide or PostgreSQL documentation.
|
||||
|
||||
6. Run checksetup.pl once more; if all goes well, it should set up the
|
||||
Bugzilla database for you. If not, return to step 5.
|
||||
|
||||
checksetup.pl should ask you, this time, for the administrator's
|
||||
email address and password. These will be used for the initial
|
||||
Bugzilla administrator account.
|
||||
|
||||
7. Configure Apache (or install and configure, if you don't have it up
|
||||
yet) to point to the Bugzilla directory. You should enable and
|
||||
activate mod_cgi, and add the configuration entries
|
||||
|
||||
Options +ExecCGI
|
||||
AllowOverride Limit
|
||||
DirectoryIndex index.cgi
|
||||
|
||||
to your Bugzilla <Directory> block. You may also need
|
||||
|
||||
AddHandler cgi-script .cgi
|
||||
|
||||
if you don't have that in your Apache configuration file yet.
|
||||
|
||||
8. Visit the URL you chose for Bugzilla. Your browser should display the
|
||||
default Bugzilla home page. You should then log in as the
|
||||
administrator by following the "Log in" link and supplying the
|
||||
account information you provided in step 6.
|
||||
|
||||
9. Scroll to the bottom of the page after logging in, and select
|
||||
"Parameters". Set up the relevant parameters for your local setup.
|
||||
|
||||
See section 4.2 of the Bugzilla Guide for a in-depth description of
|
||||
some of the configuration parameters available.
|
||||
|
||||
10. That's it. If anything unexpected comes up:
|
||||
|
||||
- read the error message carefully,
|
||||
- backtrack through the steps above,
|
||||
- check the official installation guide, which is section 4 in the
|
||||
Bugzilla Guide, included in the docs/ directory in various
|
||||
formats.
|
||||
|
||||
Support and installation questions should be directed to the
|
||||
mozilla-webtools@mozilla.org mailing list -- don't write to the
|
||||
developer mailing list: your post *will* be ignored if you do.
|
||||
|
||||
Further support information is at http://www.bugzilla.org/support/
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user