summaryrefslogtreecommitdiffstats
path: root/Bugzilla/Extension.pm
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/Extension.pm
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/Extension.pm')
-rw-r--r--Bugzilla/Extension.pm364
1 files changed, 364 insertions, 0 deletions
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.