From 5fc80f94271780b6ff6d1dbba554df35e803ac51 Mon Sep 17 00:00:00 2001 From: "mkanat%bugzilla.org" <> Date: Tue, 24 Nov 2009 06:09:41 +0000 Subject: Bug 430014: Re-write the code hooks system so that it uses modules instead of individual .pl files Patch by Max Kanat-Alexander (module owner) a=mkanat --- Bugzilla/Extension.pm | 364 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 Bugzilla/Extension.pm (limited to 'Bugzilla/Extension.pm') 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 + +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 or +F: + + 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 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 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 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 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. + +=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 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. + +=back + +=head2 The Extension C. + +The "name" of an extension shows up in several places: + +=over + +=item 1 + +The name of the package: + +C + +=item 2 + +In a C constant that B 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 end with C<< __PACKAGE__->NAME; >>. + +=back + +The name must be identical in all of those locations. + +=head2 Hooks + +In L, there is a L. +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 +that gets an argument named C: + + 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 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 +. + +=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 +will make sure that those modules are installed, when you run L. + +To do this, you need to specify a constant called C +in your extension. This constant has the same format as +L. + +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. + +=head3 If Your Extension Needs Certain Modules In Order To Compile + +If your extension needs a particular Perl module in order to +I, then you have a "chicken and egg" problem--in order to +read C, we have to compile your extension. In order +to compile your extension, we need to already have the modules in +C! + +To get around this problem, Bugzilla allows you to have an additional +file, besides F, called F, that contains +just C. If you have a F, it must also +contain the C constant, instead of your main F +containing the C constant. + +The contents of the file would look something like this for an extension +named C: + + 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 a subclass of C, because +at the time that module requirements are being checked in L, +C cannot be loaded. Also, just like F, +it ends with C<< __PACKAGE__->NAME; >>. Note also that it has the exact +same C name as F. + +This file may not use any Perl modules other than L, +L, L, and +modules that ship with Perl itself. + +If you want to define both C and C, +they must both be in F or both in F. + +Every time your extension is loaded by Bugzilla, F will be +read and then F will be read, so your methods in F +will have access to everything in F. 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 is of course not available if +your extension is a single file named C. + +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 in your extension's directory. (If your extension is just +a file, like F, 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 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 method +return C<0> or some false value. + +=head1 ADDITIONAL CONSTANTS + +In addition to C, 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 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 + +This should return C<1> if this extension's hook code should be run +by Bugzilla, and C<0> otherwise. + +=head2 C + +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 + +Takes two arguments, the path to F and the path to F, +for an extension. Loads the extension's code packages into memory using +C, does some sanity-checking on the extension, and returns the +package name of the loaded extension. + +=head2 C + +Calls L for every enabled extension installed into Bugzilla, +and returns an arrayref of all the package names that were loaded. -- cgit v1.2.3-24-g4f1b