summaryrefslogtreecommitdiffstats
path: root/Bugzilla
diff options
context:
space:
mode:
authormkanat%bugzilla.org <>2009-11-24 07:09:41 +0100
committermkanat%bugzilla.org <>2009-11-24 07:09:41 +0100
commit5fc80f94271780b6ff6d1dbba554df35e803ac51 (patch)
treeebc3f2bc12bb32ab280cacb1cd88b35001fb2c0e /Bugzilla
parent78413d851910175fcc8aef2249be377cab7dd7e8 (diff)
downloadbugzilla-5fc80f94271780b6ff6d1dbba554df35e803ac51.tar.gz
bugzilla-5fc80f94271780b6ff6d1dbba554df35e803ac51.tar.xz
Bug 430014: Re-write the code hooks system so that it uses modules instead of individual .pl files
Patch by Max Kanat-Alexander <mkanat@bugzilla.org> (module owner) a=mkanat
Diffstat (limited to 'Bugzilla')
-rw-r--r--Bugzilla/Attachment.pm2
-rw-r--r--Bugzilla/Auth/Login/Stack.pm2
-rw-r--r--Bugzilla/Auth/Verify/Stack.pm2
-rw-r--r--Bugzilla/Bug.pm10
-rw-r--r--Bugzilla/Config.pm4
-rw-r--r--Bugzilla/DB/Schema.pm2
-rw-r--r--Bugzilla/Error.pm2
-rw-r--r--Bugzilla/Extension.pm364
-rw-r--r--Bugzilla/Flag.pm2
-rw-r--r--Bugzilla/Hook.pm204
-rw-r--r--Bugzilla/Install/DB.pm2
-rw-r--r--Bugzilla/Install/Requirements.pm48
-rw-r--r--Bugzilla/Install/Util.pm122
-rw-r--r--Bugzilla/Mailer.pm2
-rw-r--r--Bugzilla/Object.pm10
-rw-r--r--Bugzilla/Search.pm2
-rw-r--r--Bugzilla/Template.pm10
17 files changed, 563 insertions, 227 deletions
diff --git a/Bugzilla/Attachment.pm b/Bugzilla/Attachment.pm
index 42372393c..f3210425f 100644
--- a/Bugzilla/Attachment.pm
+++ b/Bugzilla/Attachment.pm
@@ -561,7 +561,7 @@ sub _check_data {
$data = <$fh>;
}
}
- Bugzilla::Hook::process('attachment-process_data', { data => \$data,
+ Bugzilla::Hook::process('attachment_process_data', { data => \$data,
attributes => $params });
# Do not validate the size if we have a filehandle. It will be checked later.
diff --git a/Bugzilla/Auth/Login/Stack.pm b/Bugzilla/Auth/Login/Stack.pm
index a5752f22b..bef9171c9 100644
--- a/Bugzilla/Auth/Login/Stack.pm
+++ b/Bugzilla/Auth/Login/Stack.pm
@@ -35,7 +35,7 @@ sub new {
my $list = shift;
my %methods = map { $_ => "Bugzilla/Auth/Login/$_.pm" } split(',', $list);
lock_keys(%methods);
- Bugzilla::Hook::process('auth-login_methods', { modules => \%methods });
+ Bugzilla::Hook::process('auth_login_methods', { modules => \%methods });
$self->{_stack} = [];
foreach my $login_method (split(',', $list)) {
diff --git a/Bugzilla/Auth/Verify/Stack.pm b/Bugzilla/Auth/Verify/Stack.pm
index c23b532fd..2df3fcd25 100644
--- a/Bugzilla/Auth/Verify/Stack.pm
+++ b/Bugzilla/Auth/Verify/Stack.pm
@@ -30,7 +30,7 @@ sub new {
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 });
+ Bugzilla::Hook::process('auth_verify_methods', { modules => \%methods });
$self->{_stack} = [];
foreach my $verify_method (split(',', $list)) {
diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm
index c27f23823..d8134e3cb 100644
--- a/Bugzilla/Bug.pm
+++ b/Bugzilla/Bug.pm
@@ -107,7 +107,7 @@ sub DB_COLUMNS {
$dbh->sql_date_format('deadline', '%Y-%m-%d') . ' AS deadline',
@custom_names);
- Bugzilla::Hook::process("bug-columns", { columns => \@columns });
+ Bugzilla::Hook::process("bug_columns", { columns => \@columns });
return @columns;
}
@@ -543,7 +543,7 @@ sub create {
$dbh->do('INSERT INTO longdescs (' . join(',', @columns) . ")
VALUES ($qmarks)", undef, @values);
- Bugzilla::Hook::process('bug-end_of_create', { bug => $bug,
+ Bugzilla::Hook::process('bug_end_of_create', { bug => $bug,
timestamp => $timestamp,
});
@@ -613,7 +613,7 @@ sub run_create_validators {
delete $params->{lastdiffed};
delete $params->{bug_id};
- Bugzilla::Hook::process('bug-end_of_create_validators',
+ Bugzilla::Hook::process('bug_end_of_create_validators',
{ params => $params });
return $params;
@@ -870,7 +870,7 @@ sub update {
$changes->{'dup_id'} = [$old_dup || undef, $cur_dup || undef];
}
- Bugzilla::Hook::process('bug-end_of_update', { bug => $self,
+ Bugzilla::Hook::process('bug_end_of_update', { bug => $self,
timestamp => $delta_ts,
changes => $changes,
});
@@ -1779,7 +1779,7 @@ sub fields {
# Custom Fields
map { $_->name } Bugzilla->active_custom_fields
);
- Bugzilla::Hook::process("bug-fields", {'fields' => \@fields} );
+ Bugzilla::Hook::process('bug_fields', {'fields' => \@fields} );
return @fields;
}
diff --git a/Bugzilla/Config.pm b/Bugzilla/Config.pm
index cab18b5a1..ceb1861ed 100644
--- a/Bugzilla/Config.pm
+++ b/Bugzilla/Config.pm
@@ -68,7 +68,7 @@ sub _load_params {
}
# This hook is also called in editparams.cgi. This call here is required
# to make SetParam work.
- Bugzilla::Hook::process('config-modify_panels',
+ Bugzilla::Hook::process('config_modify_panels',
{ panels => \%hook_panels });
}
# END INIT CODE
@@ -84,7 +84,7 @@ sub param_panels {
$param_panels->{$module} = "Bugzilla::Config::$module" unless $module eq 'Common';
}
# Now check for any hooked params
- Bugzilla::Hook::process('config-add_panels',
+ Bugzilla::Hook::process('config_add_panels',
{ panel_modules => $param_panels });
return $param_panels;
}
diff --git a/Bugzilla/DB/Schema.pm b/Bugzilla/DB/Schema.pm
index c5003f798..f34f05e2f 100644
--- a/Bugzilla/DB/Schema.pm
+++ b/Bugzilla/DB/Schema.pm
@@ -1635,7 +1635,7 @@ sub _initialize {
if exists $abstract_schema->{$table};
}
unlock_keys(%$abstract_schema);
- Bugzilla::Hook::process('db_schema-abstract_schema',
+ Bugzilla::Hook::process('db_schema_abstract_schema',
{ schema => $abstract_schema });
unlock_hash(%$abstract_schema);
}
diff --git a/Bugzilla/Error.pm b/Bugzilla/Error.pm
index 661c72f74..dbd9688a9 100644
--- a/Bugzilla/Error.pm
+++ b/Bugzilla/Error.pm
@@ -107,7 +107,7 @@ sub _throw_error {
# 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',
+ Bugzilla::Hook::process('webservice_error_codes',
{ error_map => \%error_map });
my $code = $error_map{$error};
if (!$code) {
diff --git a/Bugzilla/Extension.pm b/Bugzilla/Extension.pm
new file mode 100644
index 000000000..1046e09ae
--- /dev/null
+++ b/Bugzilla/Extension.pm
@@ -0,0 +1,364 @@
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public
+# License Version 1.1 (the "License"); you may not use this file
+# except in compliance with the License. You may obtain a copy of
+# the License at http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS
+# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+# 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 Developers are Copyright (C) 2009 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Max Kanat-Alexander <mkanat@bugzilla.org>
+
+package Bugzilla::Extension;
+use strict;
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Install::Util qw(extension_code_files);
+
+use File::Basename qw(basename);
+
+####################
+# Subclass Methods #
+####################
+
+sub new {
+ my ($class, $params) = @_;
+ $params ||= {};
+ bless $params, $class;
+ return $params;
+}
+
+#######################################
+# Class (Bugzilla::Extension) Methods #
+#######################################
+
+sub load {
+ my ($class, $extension_file, $config_file) = @_;
+ require $config_file if $config_file;
+
+ my $package;
+ # This is needed during checksetup.pl, because Extension packages can
+ # only be loaded once (they return "1" the second time they're loaded,
+ # instead of their name). If an extension has only an Extension.pm,
+ # and no Config.pm, the Extension.pm gets loaded by
+ # Bugzilla::Install::Requirements before this load() method is ever
+ # called.
+ my $map = Bugzilla->request_cache->{extension_requirement_package_map};
+ if ($map and defined $map->{$extension_file}) {
+ $package = $map->{$extension_file};
+ }
+ else {
+ my $name = require $extension_file;
+ if ($name =~ /^\d+$/) {
+ ThrowCodeError('extension_must_return_name',
+ { extension => $extension_file, returned => $name });
+ }
+ $package = "${class}::$name";
+ }
+
+ if (!eval { $package->NAME }) {
+ ThrowCodeError('extension_no_name',
+ { filename => $extension_file, package => $package });
+ }
+
+ if (!$package->isa($class)) {
+ ThrowCodeError('extension_must_be_subclass',
+ { filename => $extension_file,
+ package => $package,
+ class => $class });
+ }
+
+ return $package;
+}
+
+sub load_all {
+ my $class = shift;
+ my $file_sets = extension_code_files();
+ my @packages;
+ foreach my $file_set (@$file_sets) {
+ my $package = $class->load(@$file_set);
+ push(@packages, $package);
+ }
+
+ return \@packages;
+}
+
+####################
+# Instance Methods #
+####################
+
+use constant enabled => 1;
+
+1;
+
+__END__
+
+=head1 NAME
+
+Bugzilla::Extension - Base class for Bugzilla Extensions.
+
+=head1 SYNOPSIS
+
+The following would be in F<extensions/Foo/Extension.pm> or
+F<extensions/Foo.pm>:
+
+ package Bugzilla::Extension::Foo
+ use strict;
+ use base qw(Bugzilla::Extension);
+
+ our $VERSION = '0.02';
+ use constant NAME => 'Foo';
+
+ sub some_hook_name { ... }
+
+ __PACKAGE__->NAME;
+
+=head1 DESCRIPTION
+
+This is the base class for all Bugzilla extensions.
+
+=head1 WRITING EXTENSIONS
+
+The L</SYNOPSIS> above gives a pretty good overview of what's basically
+required to write an extension. This section gives more information
+on exactly how extensions work and how you write them.
+
+=head2 Example Extension
+
+There is a sample extension in F<extensions/Example/> that demonstrates
+most of the things described in this document, so if you find the
+documentation confusing, try just reading the code instead.
+
+=head2 Where Extension Code Goes
+
+Extension code lives under the F<extensions/> directory in Bugzilla.
+
+There are two ways to write extensions:
+
+=over
+
+=item 1
+
+If your extension will have only code and no templates or other files,
+you can create a simple C<.pm> file in the F<extensions/> directory.
+
+For example, if you wanted to create an extension called "Foo" using this
+method, you would put your code into a file called F<extensions/Foo.pm>.
+
+=item 2
+
+If you plan for your extension to have templates and other files, you
+can create a whole directory for your extension, and the main extension
+code would go into a file called F<Extension.pm> in that directory.
+
+For example, if you wanted to create an extension called "Foo" using this
+method, you would put your code into a file called
+F<extensions/Foo/Extension.pm>.
+
+=back
+
+=head2 The Extension C<NAME>.
+
+The "name" of an extension shows up in several places:
+
+=over
+
+=item 1
+
+The name of the package:
+
+C<package Bugzilla::Extension::Foo;>
+
+=item 2
+
+In a C<NAME> constant that B<must> be defined for every extension:
+
+C<< use constant NAME => 'Foo'; >>
+
+=item 3
+
+At the very end of the file:
+
+C<< __PACKAGE__->NAME; >>
+
+You'll notice that though most Perl packages end with C<1;>, Bugzilla
+Extensions must B<always> end with C<< __PACKAGE__->NAME; >>.
+
+=back
+
+The name must be identical in all of those locations.
+
+=head2 Hooks
+
+In L<Bugzilla::Hook>, there is a L<list of hooks|Bugzilla::Hook/HOOKS>.
+These are the various areas of Bugzilla that an extension can "hook" into,
+which allow your extension to perform code during that point in Bugzilla's
+execution.
+
+If your extension wants to implement a hook, all you have to do is
+write a subroutine in your hook package that has the same name as
+the hook. The subroutine will be called as a method on your extension,
+and it will get the arguments specified in the hook's documentation as
+named parameters in a hashref.
+
+For example, here's an implementation of a hook named C<foo_start>
+that gets an argument named C<bar>:
+
+ sub foo_start {
+ my ($self, $params) = @_;
+ my $bar = $params->{bar};
+ print "I got $bar!\n";
+ }
+
+And that would go into your extension's code file--the file that was
+described in the L</Where Extension Code Goes> section above.
+
+During your subroutine, you may want to know what values were passed
+as CGI arguments to the current script, or what arguments were passed to
+the current WebService method. You can get that data via
+<Bugzilla/input_params>.
+
+=head2 If Your Extension Requires Certain Perl Modules
+
+If there are certain Perl modules that your extension requires in order
+to run, there is a way you can tell Bugzilla this, and then L<checksetup>
+will make sure that those modules are installed, when you run L<checksetup>.
+
+To do this, you need to specify a constant called C<REQUIRED_MODULES>
+in your extension. This constant has the same format as
+L<Bugzilla::Install::Requirements/REQUIRED_MODULES>.
+
+If there are optional modules that add additional functionality to your
+application, you can specify them in a constant called OPTIONAL_MODULES,
+which has the same format as
+L<Bugzilla::Install::Requirements/OPTIONAL_MODULES>.
+
+=head3 If Your Extension Needs Certain Modules In Order To Compile
+
+If your extension needs a particular Perl module in order to
+I<compile>, then you have a "chicken and egg" problem--in order to
+read C<REQUIRED_MODULES>, we have to compile your extension. In order
+to compile your extension, we need to already have the modules in
+C<REQUIRED_MODULES>!
+
+To get around this problem, Bugzilla allows you to have an additional
+file, besides F<Extension.pm>, called F<Config.pm>, that contains
+just C<REQUIRED_MODULES>. If you have a F<Config.pm>, it must also
+contain the C<NAME> constant, instead of your main F<Extension.pm>
+containing the C<NAME> constant.
+
+The contents of the file would look something like this for an extension
+named C<Foo>:
+
+ package Bugzilla::Extension::Foo;
+ use strict;
+ use constant NAME => 'Foo';
+ use constant REQUIRED_MODULES => [
+ {
+ package => 'Some-Package',
+ module => 'Some::Module',
+ version => 0,
+ }
+ ];
+ __PACKAGE__->NAME;
+
+Note that it is I<not> a subclass of C<Bugzilla::Extension>, because
+at the time that module requirements are being checked in L<checksetup>,
+C<Bugzilla::Extension> cannot be loaded. Also, just like F<Extension.pm>,
+it ends with C<< __PACKAGE__->NAME; >>. Note also that it has the exact
+same C<package> name as F<Extension.pm>.
+
+This file may not use any Perl modules other than L<Bugzilla::Constants>,
+L<Bugzilla::Install::Util>, L<Bugzilla::Install::Requirements>, and
+modules that ship with Perl itself.
+
+If you want to define both C<REQUIRED_MODULES> and C<OPTIONAL_MODULES>,
+they must both be in F<Config.pm> or both in F<Extension.pm>.
+
+Every time your extension is loaded by Bugzilla, F<Config.pm> will be
+read and then F<Extension.pm> will be read, so your methods in F<Extension.pm>
+will have access to everything in F<Config.pm>. Don't define anything
+with an identical name in both files, or Perl may throw a warning that
+you are redefining things.
+
+This method of setting C<REQUIRED_MODULES> is of course not available if
+your extension is a single file named C<Foo.pm>.
+
+If any of this is confusing, just look at the code of the Example extension.
+It uses this method to specify requirements.
+
+=head2 Disabling Your Extension
+
+If you want your extension to be totally ignored by Bugzilla (it will
+not be compiled or seen to exist at all), then create a file called
+C<disabled> in your extension's directory. (If your extension is just
+a file, like F<extensions/Foo.pm>, you cannot use this method to disable
+your extension, and will just have to remove it from the directory if you
+want to totally disable it.) Note that if you are running under mod_perl,
+you may have to restart your web server for this to take effect.
+
+If you want your extension to be compiled and have L<checksetup> check
+for its module pre-requisites, but you don't want the module to be used
+by Bugzilla, then you should make your extension's L</enabled> method
+return C<0> or some false value.
+
+=head1 ADDITIONAL CONSTANTS
+
+In addition to C<NAME>, there are some other constants you might
+want to define:
+
+=head2 C<$VERSION>
+
+This should be a string that describes what version of your extension
+this is. Something like C<1.0>, C<1.3.4> or a similar string.
+
+There are no particular restrictions on the format of version numbers,
+but you should probably keep them to just numbers and periods, in the
+interest of other software that parses version numbers.
+
+By default, this will be C<undef> if you don't define it.
+
+=head1 SUBCLASS METHODS
+
+In addition to hooks, there are a few methods that your extension can
+define to modify its behavior, if you want:
+
+=head2 C<enabled>
+
+This should return C<1> if this extension's hook code should be run
+by Bugzilla, and C<0> otherwise.
+
+=head2 C<new>
+
+Once every request, this method is called on your extension in order
+to create an "instance" of it. (Extensions are treated like objects--they
+are instantiated once per request in Bugzilla, and then methods are
+called on the object.)
+
+=head1 BUGZILLA::EXTENSION CLASS METHODS
+
+These are used internally by Bugzilla to load and set up extensions.
+If you are an extension author, you don't need to care about these.
+
+=head2 C<load>
+
+Takes two arguments, the path to F<Extension.pm> and the path to F<Config.pm>,
+for an extension. Loads the extension's code packages into memory using
+C<require>, does some sanity-checking on the extension, and returns the
+package name of the loaded extension.
+
+=head2 C<load_all>
+
+Calls L</load> for every enabled extension installed into Bugzilla,
+and returns an arrayref of all the package names that were loaded.
diff --git a/Bugzilla/Flag.pm b/Bugzilla/Flag.pm
index 8315a3ef6..2fa4c8ded 100644
--- a/Bugzilla/Flag.pm
+++ b/Bugzilla/Flag.pm
@@ -533,7 +533,7 @@ sub update_flags {
my @new_summaries = $class->snapshot($self->flags);
my @changes = $class->update_activity(\@old_summaries, \@new_summaries);
- Bugzilla::Hook::process('flag-end_of_update', { object => $self,
+ Bugzilla::Hook::process('flag_end_of_update', { object => $self,
timestamp => $timestamp,
old_flags => \@old_summaries,
new_flags => \@new_summaries,
diff --git a/Bugzilla/Hook.pm b/Bugzilla/Hook.pm
index dc1cd6be1..24a7d73c2 100644
--- a/Bugzilla/Hook.pm
+++ b/Bugzilla/Hook.pm
@@ -18,88 +18,25 @@
# Rights Reserved.
#
# Contributor(s): Zach Lipton <zach@zachlipton.com>
-#
+# Max Kanat-Alexander <mkanat@bugzilla.org>
package Bugzilla::Hook;
use strict;
-
use Bugzilla::Constants;
-use Bugzilla::Util;
-use Bugzilla::Error;
-
-use Scalar::Util qw(blessed);
-
-BEGIN {
- if ($ENV{MOD_PERL}) {
- require ModPerl::Const;
- import ModPerl::Const -compile => 'EXIT';
- }
- else {
- # Create a fake constant. We have to do this in a string eval,
- # otherwise this will always be defined.
- eval('sub ModPerl::EXIT;');
- }
-}
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');
- if ($@) {
- if ($ENV{MOD_PERL} and blessed $@ and $@ == ModPerl::EXIT) {
- exit;
- }
- else {
- ThrowCodeError('extension_invalid',
- { errstr => $@, name => $name,
- extension => $extension });
- }
- }
- # Flush stored data.
- Bugzilla->hook_args({});
+ foreach my $extension (@{ Bugzilla->extensions }) {
+ local @INC = @INC;
+ my $ext_dir = bz_locations()->{'extensionsdir'};
+ my $ext_name = $extension->NAME;
+ unshift(@INC, "$ext_dir/$ext_name/lib");
+ if ($extension->can($name)) {
+ $extension->$name($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__
@@ -122,39 +59,16 @@ 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.
+The implementation of extensions is described in L<Bugzilla::Extension>.
=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.
+When a hook named C<HOOK_NAME> is run, Bugzilla looks through all
+enabled L<extensions|Bugzilla::Extension> for extensions that implement
+a subroutined named C<HOOK_NAME>.
-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.
-
-You may also want to use L<Bugzilla/input_params> to get parameters
-that were passed to the current CGI script or WebService method.
-
-=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.
+See L<Bugzilla::Extension> for more details about how an extension
+can run code during a hook.
=head1 SUBROUTINES
@@ -194,7 +108,7 @@ 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 attachment-process_data
+=head2 attachment_process_data
This happens at the very beginning process of the attachment creation.
You can edit the attachment content itself as well as all attributes
@@ -212,7 +126,7 @@ L<Bugzilla::Attachment/create>. The data it contains hasn't been checked yet.
=back
-=head2 auth-login_methods
+=head2 auth_login_methods
This allows you to add new login types to Bugzilla.
(See L<Bugzilla::Auth::Login>.)
@@ -243,13 +157,13 @@ login methods that weren't passed to L<Bugzilla::Auth/login>.)
=back
-=head2 auth-verify_methods
+=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
+=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
@@ -264,7 +178,7 @@ your column name(s) onto the array.
=back
-=head2 bug-end_of_create
+=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.
@@ -280,7 +194,7 @@ values.
=back
-=head2 bug-end_of_create_validators
+=head2 bug_end_of_create_validators
This happens during L<Bugzilla::Bug/create>, after all parameters have
been validated, but before anything has been inserted into the database.
@@ -295,7 +209,7 @@ A hashref. The validated parameters passed to C<create>.
=back
-=head2 bug-end_of_update
+=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.
@@ -314,7 +228,7 @@ C<$changes-E<gt>{field} = [old, new]>
=back
-=head2 bug-fields
+=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
@@ -331,7 +245,7 @@ your column name(s) onto the array.
=back
-=head2 bug-format_comment
+=head2 bug_format_comment
Allows you to do custom parsing on comments before they are displayed. You do
this by returning two regular expressions: one that matches the section you
@@ -396,7 +310,7 @@ the summary line).
=back
-=head2 buglist-columns
+=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
@@ -424,7 +338,7 @@ The definition is structured as:
=back
-=head2 colchange-columns
+=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
@@ -440,7 +354,7 @@ See L</buglist-columns>.
=back
-=head2 config-add_panels
+=head2 config_add_panels
If you want to add new panels to the Parameters administrative interface,
this is where you do it.
@@ -461,7 +375,7 @@ extension.)
=back
-=head2 config-modify_panels
+=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
@@ -485,7 +399,7 @@ L</config-add_panels> if you want to add new panels.
=back
-=head2 enter_bug-entrydefaultvars
+=head2 enter_bug_entrydefaultvars
This happens right before the template is loaded on enter_bug.cgi.
@@ -497,7 +411,7 @@ Params:
=back
-=head2 flag-end_of_update
+=head2 flag_end_of_update
This happens at the end of L<Bugzilla::Flag/update_flags>, after all other changes
are made to the database and after emails are sent. It gives you a before/after
@@ -523,7 +437,7 @@ changed flags, and search for a specific condition like C<added eq 'review-'>.
=back
-=head2 install-before_final_checks
+=head2 install_before_final_checks
Allows execution of custom code before the final checks are done in
checksetup.pl.
@@ -538,37 +452,13 @@ 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
+=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
+=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
@@ -588,7 +478,7 @@ database when run.
=back
-=head2 mailer-before_send
+=head2 mailer_before_send
Called right before L<Bugzilla::Mailer> sends a message to the MTA.
@@ -600,7 +490,7 @@ Params:
=back
-=head2 object-before_create
+=head2 object_before_create
This happens at the beginning of L<Bugzilla::Object/create>.
@@ -620,7 +510,7 @@ A hashref. The set of named parameters passed to C<create>.
=back
-=head2 object-before_set
+=head2 object_before_set
Called during L<Bugzilla::Object/set>, before any actual work is done.
You can use this to perform actions before a value is changed for
@@ -646,7 +536,7 @@ The value being set on the object.
=back
-=head2 object-end_of_create_validators
+=head2 object_end_of_create_validators
Called at the end of L<Bugzilla::Object/run_create_validators>. You can
use this to run additional validation when creating an object.
@@ -671,7 +561,7 @@ validated by the C<VALIDATORS> specified for the object.
=back
-=head2 object-end_of_set_all
+=head2 object_end_of_set_all
This happens at the end of L<Bugzilla::Object/set_all>. This is a
good place to call custom set_ functions on objects, or to make changes
@@ -693,7 +583,7 @@ A hashref. The set of named parameters passed to C<set_all>.
=back
-=head2 object-end_of_update
+=head2 object_end_of_update
Called during L<Bugzilla::Object/update>, after changes are made
to the database, but while still inside a transaction.
@@ -719,7 +609,7 @@ L<Bugzilla::Object/update> returns.
=back
-=head2 page-before_template
+=head2 page_before_template
This is a simple way to add your own pages to Bugzilla. This hooks C<page.cgi>,
which loads templates from F<template/en/default/pages>. For example,
@@ -746,7 +636,7 @@ your template.
=back
-=head2 product-confirm_delete
+=head2 product_confirm_delete
Called before displaying the confirmation message when deleting a product.
@@ -758,7 +648,7 @@ Params:
=back
-=head2 sanitycheck-check
+=head2 sanitycheck_check
This hook allows for extra sanity checks to be added, for use by
F<sanitycheck.cgi>.
@@ -772,7 +662,7 @@ to the user. (F<sanitycheck.cgi>'s C<Status>)
=back
-=head2 sanitycheck-repair
+=head2 sanitycheck_repair
This hook allows for extra sanity check repairs to be made, for use by
F<sanitycheck.cgi>.
@@ -786,7 +676,7 @@ to the user. (F<sanitycheck.cgi>'s C<Status>)
=back
-=head2 template-before_create
+=head2 template_before_create
This hook allows you to modify the configuration of L<Bugzilla::Template>
objects before they are created. For example, you could add a new
@@ -805,7 +695,7 @@ look at the code for C<create> in L<Bugzilla::Template>.)
=back
-=head2 template-before_process
+=head2 template_before_process
This hook allows you to define additional variables that will be available to
the template being processed. You probably want to restrict your hook
@@ -869,7 +759,7 @@ plugins).
=back
-=head2 webservice-error_codes
+=head2 webservice_error_codes
If your webservice extension throws custom errors, you can set numeric
codes for those errors here.
@@ -887,3 +777,7 @@ 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
+
+=head1 SEE ALSO
+
+L<Bugzilla::Extension>
diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm
index 51d258227..d7eb14c24 100644
--- a/Bugzilla/Install/DB.pm
+++ b/Bugzilla/Install/DB.pm
@@ -590,7 +590,7 @@ sub update_table_definitions {
# New --TABLE-- changes should go *** A B O V E *** this point #
################################################################
- Bugzilla::Hook::process('install-update_db');
+ Bugzilla::Hook::process('install_update_db');
# We do this here because otherwise the foreign key from
# products.classification_id to classifications.id will fail
diff --git a/Bugzilla/Install/Requirements.pm b/Bugzilla/Install/Requirements.pm
index 1fa53de9b..190dbe968 100644
--- a/Bugzilla/Install/Requirements.pm
+++ b/Bugzilla/Install/Requirements.pm
@@ -26,7 +26,8 @@ package Bugzilla::Install::Requirements;
use strict;
use Bugzilla::Constants;
-use Bugzilla::Install::Util qw(vers_cmp install_string);
+use Bugzilla::Install::Util qw(vers_cmp install_string
+ extension_requirement_packages);
use List::Util qw(max);
use Safe;
use Term::ANSIColor;
@@ -138,9 +139,9 @@ sub REQUIRED_MODULES {
},
);
- my $all_modules = _get_extension_requirements(
- 'REQUIRED_MODULES', \@modules);
- return $all_modules;
+ my $extra_modules = _get_extension_requirements('REQUIRED_MODULES');
+ push(@modules, @$extra_modules);
+ return \@modules;
};
sub OPTIONAL_MODULES {
@@ -291,9 +292,9 @@ sub OPTIONAL_MODULES {
},
);
- my $all_modules = _get_extension_requirements(
- 'OPTIONAL_MODULES', \@modules);
- return $all_modules;
+ my $extra_modules = _get_extension_requirements('OPTIONAL_MODULES');
+ push(@modules, @$extra_modules);
+ return \@modules;
};
# This maps features to the files that require that feature in order
@@ -312,31 +313,20 @@ use constant FEATURE_FILES => (
updates => ['Bugzilla/Update.pm'],
);
-# This implements the install-requirements hook described in Bugzilla::Hook.
+# This implements the REQUIRED_MODULES and OPTIONAL_MODULES stuff
+# described in in Bugzilla::Extension.
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);
+ my ($function) = @_;
+
+ my $packages = extension_requirement_packages();
+ my @modules;
+ foreach my $package (@$packages) {
+ if ($package->can($function)) {
+ my $extra_modules = $package->$function;
+ push(@modules, @$extra_modules);
}
}
-
- unshift(@all_modules, @$base_modules);
- return \@all_modules;
+ return \@modules;
};
sub check_requirements {
diff --git a/Bugzilla/Install/Util.pm b/Bugzilla/Install/Util.pm
index d3fb4e5f8..254cc237b 100644
--- a/Bugzilla/Install/Util.pm
+++ b/Bugzilla/Install/Util.pm
@@ -37,6 +37,8 @@ use base qw(Exporter);
our @EXPORT_OK = qw(
bin_loc
get_version_and_os
+ extension_code_files
+ extension_requirement_packages
indicate_progress
install_string
include_languages
@@ -79,6 +81,96 @@ sub get_version_and_os {
os_ver => $os_details[3] };
}
+sub _extension_paths {
+ my $dir = bz_locations()->{'extensionsdir'};
+ my @extension_items = glob("$dir/*");
+ my @paths;
+ foreach my $item (@extension_items) {
+ my $basename = basename($item);
+ # Skip CVS directories and any hidden files/dirs.
+ next if ($basename eq 'CVS' or $basename =~ /^\./);
+ if (-d $item) {
+ if (!-e "$item/disabled") {
+ push(@paths, $item);
+ }
+ }
+ elsif ($item =~ /\.pm$/i) {
+ push(@paths, $item);
+ }
+ }
+ return @paths;
+}
+
+sub extension_code_files {
+ my ($requirements_only) = @_;
+ my @files;
+ foreach my $path (_extension_paths()) {
+ my @load_files;
+ if (-d $path) {
+ my $extension_file = "$path/Extension.pm";
+ my $config_file = "$path/Config.pm";
+ if (-e $extension_file) {
+ push(@load_files, $extension_file);
+ }
+ if (-e $config_file) {
+ push(@load_files, $config_file);
+ }
+
+ # Don't load Extension.pm if we just want Config.pm and
+ # we found both.
+ if ($requirements_only and scalar(@load_files) == 2) {
+ shift(@load_files);
+ }
+ }
+ else {
+ push(@load_files, $path);
+ }
+ next if !scalar(@load_files);
+ # We know that these paths are safe, because they came from
+ # extensionsdir and we checked them specifically for their format.
+ # Also, the only thing we ever do with them is pass them to "require".
+ trick_taint($_) foreach @load_files;
+ push(@files, \@load_files);
+ }
+ return \@files;
+}
+
+# Used by _get_extension_requirements in Bugzilla::Install::Requirements.
+sub extension_requirement_packages {
+ # If we're in a .cgi script or some time that's not the requirements phase,
+ # just use Bugzilla->extensions. This avoids running the below code during
+ # a normal Bugzilla page, which is important because the below code
+ # doesn't actually function right if it runs after
+ # Bugzilla::Extension->load_all (because stuff has already been loaded).
+ # (This matters because almost every page calls Bugzilla->feature, which
+ # calls OPTIONAL_MODULES, which calls this method.)
+ if (eval { Bugzilla->extensions }) {
+ return Bugzilla->extensions;
+ }
+ my $packages = _cache()->{extension_requirement_packages};
+ return $packages if $packages;
+ $packages = [];
+ my %package_map;
+
+ my $extension_files = extension_code_files('requirements only');
+ foreach my $file_set (@$extension_files) {
+ my $file = shift @$file_set;
+ my $name = require $file;
+ if ($name =~ /^\d+$/) {
+ die install_string('extension_must_return_name',
+ { file => $file, returned => $name });
+ }
+ my $package = "Bugzilla::Extension::$name";
+ $package_map{$file} = $package;
+ push(@$packages, $package);
+ }
+ _cache()->{extension_requirement_packages} = $packages;
+ # Used by Bugzilla::Extension->load if it's called after this method
+ # (which only happens during checksetup.pl, currently).
+ _cache()->{extension_requirement_package_map} = \%package_map;
+ return $packages;
+}
+
sub indicate_progress {
my ($params) = @_;
my $current = $params->{current};
@@ -93,8 +185,8 @@ sub indicate_progress {
sub install_string {
my ($string_id, $vars) = @_;
- _cache()->{template_include_path} ||= template_include_path();
- my $path = _cache()->{template_include_path};
+ _cache()->{install_string_path} ||= template_include_path();
+ my $path = _cache()->{install_string_path};
my $string_template;
# Find the first template that defines this string.
@@ -134,10 +226,10 @@ sub include_languages {
# function in Bugzilla->request_cache. This is done to improve the
# performance of the template processing.
my $to_be_cached = 0;
- if (exists $ENV{'SERVER_SOFTWARE'} and not @_) {
- my $cache = Bugzilla->request_cache;
+ if (not @_) {
+ my $cache = _cache();
if (exists $cache->{include_languages}) {
- return @{$cache->{include_languages}}
+ return @{ $cache->{include_languages} };
}
$to_be_cached = 1;
}
@@ -202,8 +294,7 @@ sub include_languages {
# Cache the result if we are in CGI mode and called without parameter
# (see the comment at the top of this function).
if ($to_be_cached) {
- my $cache = Bugzilla->request_cache;
- $cache->{include_languages} = \@usedlanguages;
+ _cache()->{include_languages} = \@usedlanguages;
}
return @usedlanguages;
@@ -241,12 +332,8 @@ sub template_base_directories {
# 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 @template_dirs;
- my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
- foreach my $extension (@extensions) {
- next if (-e "$extension/disabled" or !-d "$extension/template");
- push(@template_dirs, "$extension/template");
- }
+ my @extensions = grep { -d "$_/template" } _extension_paths();
+ my @template_dirs = map { "$_/template" } @extensions;
push(@template_dirs, bz_locations()->{'templatedir'});
return \@template_dirs;
}
@@ -384,12 +471,13 @@ sub init_console {
}
# This is like request_cache, but it's used only by installation code
-# for setup.cgi and things like that.
+# for checksetup.pl and things like that.
our $_cache = {};
sub _cache {
- if ($ENV{MOD_PERL}) {
- require Apache2::RequestUtil;
- return Apache2::RequestUtil->request->pnotes();
+ # If the normal request_cache is available (which happens any time
+ # after the requirements phase) then we should use that.
+ if (eval { Bugzilla->request_cache; }) {
+ return Bugzilla->request_cache;
}
return $_cache;
}
diff --git a/Bugzilla/Mailer.pm b/Bugzilla/Mailer.pm
index 83ae5a600..71dc8f1f5 100644
--- a/Bugzilla/Mailer.pm
+++ b/Bugzilla/Mailer.pm
@@ -171,7 +171,7 @@ sub MessageToMTA {
Debug => Bugzilla->params->{'smtp_debug'};
}
- Bugzilla::Hook::process('mailer-before_send', { email => $email });
+ Bugzilla::Hook::process('mailer_before_send', { email => $email });
if ($method eq "Test") {
my $filename = bz_locations()->{'datadir'} . '/mailer.testfile';
diff --git a/Bugzilla/Object.pm b/Bugzilla/Object.pm
index a1857db1c..92353b6a0 100644
--- a/Bugzilla/Object.pm
+++ b/Bugzilla/Object.pm
@@ -279,7 +279,7 @@ sub set {
superclass => __PACKAGE__,
function => 'Bugzilla::Object->set' });
- Bugzilla::Hook::process('object-before_set',
+ Bugzilla::Hook::process('object_before_set',
{ object => $self, field => $field,
value => $value });
@@ -303,7 +303,7 @@ sub set_all {
my $method = "set_$key";
$self->$method($params->{$key});
}
- Bugzilla::Hook::process('object-end_of_set_all', { object => $self,
+ Bugzilla::Hook::process('object_end_of_set_all', { object => $self,
params => $params });
}
@@ -348,7 +348,7 @@ sub update {
$dbh->do("UPDATE $table SET $columns WHERE $id_field = ?", undef,
@values, $self->id) if @values;
- Bugzilla::Hook::process('object-end_of_update',
+ Bugzilla::Hook::process('object_end_of_update',
{ object => $self, old_object => $old_self,
changes => \%changes });
@@ -412,7 +412,7 @@ sub check_required_create_fields {
# This hook happens here so that even subclasses that don't call
# SUPER::create are still affected by the hook.
- Bugzilla::Hook::process('object-before_create', { class => $class,
+ Bugzilla::Hook::process('object_before_create', { class => $class,
params => $params });
foreach my $field ($class->REQUIRED_CREATE_FIELDS) {
@@ -445,7 +445,7 @@ sub run_create_validators {
$field_values{$field} = $value;
}
- Bugzilla::Hook::process('object-end_of_create_validators',
+ Bugzilla::Hook::process('object_end_of_create_validators',
{ class => $class, params => \%field_values });
return \%field_values;
diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm
index 4aaf7e14c..7b8ac10e2 100644
--- a/Bugzilla/Search.pm
+++ b/Bugzilla/Search.pm
@@ -170,7 +170,7 @@ sub COLUMNS {
# The short_short_desc column is identical to short_desc
$columns{'short_short_desc'} = $columns{'short_desc'};
- Bugzilla::Hook::process("buglist-columns", { columns => \%columns });
+ Bugzilla::Hook::process('buglist_columns', { columns => \%columns });
$cache->{search_columns} = \%columns;
return $cache->{search_columns};
diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm
index c7b8cee52..ce7c2ab1c 100644
--- a/Bugzilla/Template.pm
+++ b/Bugzilla/Template.pm
@@ -86,9 +86,9 @@ sub process {
my $self = shift;
my ($file, $vars) = @_;
- Bugzilla::Hook::process("template-before_process",
- { vars => $vars, file => $file,
- template => $self });
+ #Bugzilla::Hook::process('template_before_process',
+ # { vars => $vars, file => $file,
+ # template => $self });
return $self->SUPER::process(@_);
}
@@ -188,7 +188,7 @@ sub quoteUrls {
my $tmp;
my @hook_regexes;
- Bugzilla::Hook::process('bug-format_comment',
+ Bugzilla::Hook::process('bug_format_comment',
{ text => \$text, bug => $bug, regexes => \@hook_regexes,
comment => $comment });
@@ -793,7 +793,7 @@ sub create {
},
};
- Bugzilla::Hook::process('template-before_create', { config => $config });
+ Bugzilla::Hook::process('template_before_create', { config => $config });
my $template = $class->new($config)
|| die("Template creation failed: " . $class->error());
return $template;