diff options
Diffstat (limited to 'Bugzilla/Extension.pm')
-rw-r--r-- | Bugzilla/Extension.pm | 813 |
1 files changed, 813 insertions, 0 deletions
diff --git a/Bugzilla/Extension.pm b/Bugzilla/Extension.pm new file mode 100644 index 000000000..857d0d75b --- /dev/null +++ b/Bugzilla/Extension.pm @@ -0,0 +1,813 @@ +# -*- 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 extension_template_directory + extension_package_directory); + +use File::Basename; +use File::Spec; + +#################### +# 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) = @_; + 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). During checksetup.pl, extensions are loaded + # once by Bugzilla::Install::Requirements, and then later again via + # Bugzilla->extensions (because of hooks). + my $map = Bugzilla->request_cache->{extension_requirement_package_map}; + + if ($config_file) { + if ($map and defined $map->{$config_file}) { + $package = $map->{$config_file}; + } + else { + my $name = require $config_file; + if ($name =~ /^\d+$/) { + ThrowCodeError('extension_must_return_name', + { extension => $config_file, + returned => $name }); + } + $package = "${class}::$name"; + } + + __do_call($package, 'modify_inc', $config_file); + } + + if ($map and defined $map->{$extension_file}) { + $package = $map->{$extension_file}; + $package->modify_inc($extension_file) if !$config_file; + } + else { + my $name = require $extension_file; + if ($name =~ /^\d+$/) { + ThrowCodeError('extension_must_return_name', + { extension => $extension_file, returned => $name }); + } + $package = "${class}::$name"; + $package->modify_inc($extension_file) if !$config_file; + } + + $class->_validate_package($package, $extension_file); + return $package; +} + +sub _validate_package { + my ($class, $package, $extension_file) = @_; + + # For extensions from data/extensions/additional, we don't have a file + # name, so we fake it. + if (!$extension_file) { + $extension_file = $package; + $extension_file =~ s/::/\//g; + $extension_file .= '.pm'; + } + + 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 }); + } +} + +sub load_all { + my $class = shift; + my ($file_sets, $extra_packages) = extension_code_files(); + my @packages; + foreach my $file_set (@$file_sets) { + my $package = $class->load(@$file_set); + push(@packages, $package); + } + + # Extensions from data/extensions/additional + foreach my $package (@$extra_packages) { + # Don't load an "additional" extension if we already have an extension + # loaded with that name. + next if grep($_ eq $package, @packages); + # Untaint the package name + $package =~ /([\w:]+)/; + $package = $1; + eval("require $package") || die $@; + $package->_validate_package($package); + push(@packages, $package); + } + + return \@packages; +} + +# Modifies @INC so that extensions can use modules like +# "use Bugzilla::Extension::Foo::Bar", when Bar.pm is in the lib/ +# directory of the extension. +sub modify_inc { + my ($class, $file) = @_; + + # Note that this package_dir call is necessary to set things up + # for my_inc, even if we didn't take its return value. + my $package_dir = __do_call($class, 'package_dir', $file); + # Don't modify @INC for extensions that are just files in the extensions/ + # directory. We don't want Bugzilla's base lib/CGI.pm being loaded as + # Bugzilla::Extension::Foo::CGI or any other confusing thing like that. + return if $package_dir eq bz_locations->{'extensionsdir'}; + unshift(@INC, sub { __do_call($class, 'my_inc', @_) }); +} + +# This is what gets put into @INC by modify_inc. +sub my_inc { + my ($class, undef, $file) = @_; + + # This avoids infinite recursion in case anything inside of this function + # does a "require". (I know for sure that File::Spec->case_tolerant does + # a "require" on Windows, for example.) + return if $file !~ /^Bugzilla/; + + my $lib_dir = __do_call($class, 'lib_dir'); + my @class_parts = split('::', $class); + my ($vol, $dir, $file_name) = File::Spec->splitpath($file); + my @dir_parts = File::Spec->splitdir($dir); + # File::Spec::Win32 (any maybe other OSes) add an empty directory at the + # end of @dir_parts. + @dir_parts = grep { $_ ne '' } @dir_parts; + # Validate that this is a sub-package of Bugzilla::Extension::Foo ($class). + for (my $i = 0; $i < scalar(@class_parts); $i++) { + return if !@dir_parts; + if (File::Spec->case_tolerant) { + return if lc($class_parts[$i]) ne lc($dir_parts[0]); + } + else { + return if $class_parts[$i] ne $dir_parts[0]; + } + shift(@dir_parts); + } + # For Bugzilla::Extension::Foo::Bar, this would look something like + # extensions/Example/lib/Bar.pm + my $resolved_path = File::Spec->catfile($lib_dir, @dir_parts, $file_name); + open(my $fh, '<', $resolved_path); + return $fh; +} + +#################### +# Instance Methods # +#################### + +use constant enabled => 1; + +sub lib_dir { + my $invocant = shift; + my $package_dir = __do_call($invocant, 'package_dir'); + # For extensions that are just files in the extensions/ directory, + # use the base lib/ dir as our "lib_dir". Note that Bugzilla never + # uses lib_dir in this case, though, because modify_inc is prevented + # from modifying @INC when we're just a file in the extensions/ directory. + # So this particular code block exists just to make lib_dir return + # something right in case an extension needs it for some odd reason. + if ($package_dir eq bz_locations()->{'extensionsdir'}) { + return bz_locations->{'ext_libpath'}; + } + return File::Spec->catdir($package_dir, 'lib'); +} + +sub template_dir { return extension_template_directory(@_); } +sub package_dir { return extension_package_directory(@_); } + +###################### +# Helper Subroutines # +###################### + +# In order to not conflict with extensions' private subroutines, any helpers +# here should start with a double underscore. + +# This is for methods that can optionally be overridden in Config.pm. +# It falls back to the local implementation if $class cannot do +# the method. This is necessary because Config.pm is not a subclass of +# Bugzilla::Extension. +sub __do_call { + my ($class, $method, @args) = @_; + if ($class->can($method)) { + return $class->$method(@args); + } + my $function_ref; + { no strict 'refs'; $function_ref = \&{$method}; } + return $function_ref->($class, @args); +} + +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; + +Custom templates would go into F<extensions/Foo/template/en/default/>. +L<Template hooks|/Template Hooks> would go into +F<extensions/Foo/template/en/default/hook/>. + +=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. There is also a +L<wiki page|https://wiki.mozilla.org/Bugzilla:Extension_Notes> with additional HOWTOs, tips and tricks. + +=head2 Using F<extensions/create.pl> + +There is a script, L<extensions::create>, that will set up the framework +of a new extension for you. To use it, pick a name for your extension +and, in the base bugzilla directory, do: + +C<extensions/create.pl NAME> + +But replace C<NAME> with the name you picked for your extension. That +will create a new directory in the F<extensions/> directory with the name +of your extension. The directory will contain a full framework for +a new extension, with helpful comments in each file describing things +about 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, $args) = @_; + my $bar = $args->{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 +L<Bugzilla/input_params>. + +=head3 Adding New Hooks To Bugzilla + +If you need a new hook for your extension and you want that hook to be +added to Bugzilla itself, see our development process at +L<http://wiki.mozilla.org/Bugzilla:Developers>. + +In order for a new hook to be accepted into Bugzilla, it has to work, +it must have documentation in L<Bugzilla::Hook>, and it must have example +code in F<extensions/Example/Extension.pm>. + +One question that is often asked about new hooks is, "Is this the most +flexible way to implement this hook?" That is, the more power extension +authors get from a hook, the more likely it is to be accepted into Bugzilla. +Hooks that only hook a very specific part of Bugzilla will not be accepted +if their functionality can be accomplished equally well with a more generic +hook. + +=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 +B<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 Libraries + +Extensions often want to have their own Perl modules. Your extension +can load any Perl module in its F<lib/> directory. (So, if your extension is +F<extensions/Foo/>, then your Perl modules go into F<extensions/Foo/lib/>.) + +However, the C<package> name of your libraries will not work quite +like normal Perl modules do. F<extensions/Foo/lib/Bar.pm> is +loaded as C<Bugzilla::Extension::Foo::Bar>. Or, to say it another way, +C<use Bugzilla::Extension::Foo::Bar;> loads F<extensions/Foo/lib/Bar.pm>, +which should have C<package Bugzilla::Extension::Foo::Bar;> as its package +name. + +This allows any place in Bugzilla to load your modules, which is important +for some hooks. It even allows other extensions to load your modules, and +allows you to install your modules into the global Perl install +as F<Bugzilla/Extension/Foo/Bar.pm>, if you'd like, which helps allow CPAN +distribution of Bugzilla extensions. + +B<Note:> If you want to C<use> or C<require> a module that's in +F<extensions/Foo/lib/> at the top level of your F<Extension.pm>, +you must have a F<Config.pm> (see above) with at least the C<NAME> +constant defined in it. + +=head2 Templates + +Extensions store templates in a C<template> subdirectory of the extension. +(Obviously, this isn't available for extensions that aren't a directory.) + +The format of this directory is exactly like the normal layout of Bugzilla's +C<template> directory--in fact, your extension's C<template> directory +becomes part of Bugzilla's template "search path" as described in +L<Bugzilla::Install::Util/template_include_path>. + +You can actually include templates in your extension without having any +C<.pm> files in your extension at all, if you want. (That is, it's entirely +valid to have an extension that's just template files and no code files.) + +Bugzilla's templates are written in a language called Template Toolkit. +You can find out more about Template Toolkit at L<http://template-toolkit.org>. + +There are two ways to extend or modify Bugzilla's templates: you can use +template hooks (described below) or you can override existing templates +entirely (described further down). + +=head2 Template Hooks + +Templates can be extended using a system of "hooks" that add new UI elements +to a particular area of Bugzilla without modifying the code of the existing +templates. This is the recommended way for extensions to modify the user +interface of Bugzilla. + +=head3 Which Templates Can Be Hooked + +There is no list of template hooks like there is for standard code hooks. +To find what places in the user interface can be hooked, search for the +string C<Hook.process> in Bugzilla's templates (in the +F<template/en/default/> directory). That will also give you the name of +the hooks--the first argument to C<Hook.process> is the name of the hook. +(A later section in this document explains how to use that name). + +For example, if you see C<Hook.process("additional_header")>, that means +the name of the hook is C<additional_header>. + +=head3 Where Template Hooks Go + +To extend templates in your extension using template hooks, you put files into +the F<template/en/default/hook> directory of your extension. So, if you had an +extension called "Foo", your template extensions would go into +F<extensions/Foo/template/en/default/hook/>. + +(Note that the base F<template/en/default/hook> directory in Bugzilla itself +also works, although you would never use that for an extension that you +intended to distribute.) + +The files that go into this directory have a certain name, based on the +name of the template that is being hooked, and the name of the hook. +For example, let's imagine that you have an extension named "Foo", +and you want to use the C<additional_header> hook in +F<template/en/default/global/header.html.tmpl>. Your code would go into +F<extensions/Foo/template/en/default/hook/global/header-additional_header.html.tmpl>. Any code you put into that file will happen at the point that +C<Hook.process("additional_header")> is called in +F<template/en/default/global/header.html.tmpl>. + +As you can see, template extension file names follow a pattern. The +pattern looks like: + + <templates>/hook/<template path>/<template name>-<hook name>.<template type>.tmpl + +=over + +=item <templates> + +This is the full path to the template directory, like +F<extensions/Foo/template/en/default>. This works much like normal templates +do, in the sense that template extensions in C<custom> override template +extensions in C<default> for your extension, templates for different languages +can be supplied, etc. Template extensions are searched for and run in the +order described in L<Bugzilla::Install::Util/template_include_path>. + +The difference between normal templates and template hooks is that hooks +will be run for I<every> extension, whereas for normal templates, Bugzilla +just takes the first one it finds and stops searching. So while a template +extension in the C<custom> directory may override the same-named template +extension in the C<default> directory I<within your Bugzilla extension>, +it will not override the same-named template extension in the C<default> +directory of another Bugzilla extension. + +=item <template path> + +This is the part of the path (excluding the filename) that comes after +F<template/en/default/> in a template's path. So, for +F<template/en/default/global/header.html.tmpl>, this would simply be +C<global>. + +=item <template name> + +This is the file name of the template, before the C<.html.tmpl> part. +So, for F<template/en/default/global/header.html.tmpl>, this would be +C<header>. + +=item <hook name> + +This is the name of the hook--what you saw in C<Hook.process> inside +of the template you want to hook. In our example, this is +C<additional_header>. + +=item <template type> + +This is what comes after the template name but before C<.tmpl> in the +template's path. In most cases this is C<html>, but sometimes it's +C<none>, C<txt>, C<js>, or various other formats, indicating what +type of output the template has. + +=back + +=head3 Adding New Template Hooks to Bugzilla + +Adding new template hooks is just like adding code hooks (see +L</Adding New Hooks To Bugzilla>) except that you don't have to +document them, and including example code is optional. + +=head2 Overriding Existing Templates + +Sometimes you don't want to extend a template, you just want to replace +it entirely with your extension's template, or you want to add an entirely +new template to Bugzilla for your extension to use. + +To replace the F<template/en/default/global/banner.html.tmpl> template +in an extension named "Foo", create a file called +F<extensions/Foo/template/en/default/global/banner.html.tmpl>. Note that this +is very similar to the path for a template hook, except that it excludes +F<hook/>, and the template is named I<exactly> like the standard Bugzilla +template. + +You can also use this method to add entirely new templates. If you have +an extension named "Foo", and you add a file named +F<extensions/Foo/template/en/default/foo/bar.html.tmpl>, you can load +that in your code using C<< $template->process('foo/bar.html.tmpl') >>. + +=head3 A Warning About Extensions That You Want To Distribute + +You should never override an existing Bugzilla template in an +extension that you plan to distribute to others, because only one extension +can override any given template, and which extension will "win" that war +if there are multiple extensions installed is totally undefined. + +However, adding new templates in an extension that you want to distribute +is fine, though you have to be careful about how you name them, because +any templates with an identical path and name (say, both called +F<global/stuff.html.tmpl>) will conflict. The usual way to work around +this is to put all your custom templates into a template path that's +named after your extension (since the name of your extension has to be +unique anyway). So if your extension was named Foo, your custom templates +would go into F<extensions/Foo/template/en/default/foo/>. The only +time that doesn't work is with the C<page_before_template> extension, in which +case your templates should probably be in a directory like +F<extensions/Foo/template/en/default/page/foo/> so as not to conflict with +other pages that other extensions might add. + +=head2 CSS, JavaScript, and Images + +If you include CSS, JavaScript, and images in your extension that are +served directly to the user (that is, they're not read by a script and +then printed--they're just linked directly in your HTML), they should go +into the F<web/> subdirectory of your extension. + +So, for example, if you had a CSS file called F<style.css> and your +extension was called F<Foo>, your file would go into +F<extensions/Foo/web/style.css>. + +=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 DISTRIBUTING EXTENSIONS + +If you've made an extension and you want to publish it, the first +thing you'll want to do is package up your extension's code and +then put a link to it in the appropriate section of +L<http://wiki.mozilla.org/Bugzilla:Addons>. + +=head2 Distributing on CPAN + +If you want a centralized distribution point that makes it easy +for Bugzilla users to install your extension, it is possible to +distribute your Bugzilla Extension through CPAN. + +The details of making a standard CPAN module are too much to +go into here, but a lot of it is covered in L<perlmodlib> +and on L<http://www.cpan.org/> among other places. + +When you distribute your extension via CPAN, your F<Extension.pm> +should simply install itself as F<Bugzilla/Extension/Foo.pm>, +where C<Foo> is the name of your module. You do not need a separate +F<Config.pm> file, because CPAN itself will handle installing +the prerequisites of your module, so Bugzilla doesn't have to +worry about it. + +=head3 Templates in extensions distributed on CPAN + +If your extension is F</usr/lib/perl5/Bugzilla/Extension/Foo.pm>, +then Bugzilla will look for templates in the directory +F</usr/lib/perl5/Bugzilla/Extension/Foo/template/>. + +You can change this behavior by overriding the L</template_dir> +or L</package_dir> methods described lower down in this document. + +=head3 Using an extension distributed on CPAN + +There is a file named F<data/extensions/additional> in Bugzilla. +This is a plain-text file. Each line is the name of a module, +like C<Bugzilla::Extension::Foo>. In addition to the extensions +in the F<extensions/> directory, each module listed in this file +will be loaded as a Bugzilla Extension whenever Bugzilla loads or +uses extensions. + +=head1 GETTING HELP WITH WRITING EXTENSIONS + +If you are an extension author and you'd like some assistance from other +extension authors or the Bugzilla development team, you can use the +normal support channels described at L<http://www.bugzilla.org/support/>. + +=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 Class Methods + +These methods are called on your extension's class. (Like +C<< Bugzilla::Extension::Foo->some_method >>). + +=head3 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.) + +=head2 Instance Methods + +These are called on an instantiated Extension object. + +=head3 C<enabled> + +This should return C<1> if this extension's hook code should be run +by Bugzilla, and C<0> otherwise. + +=head3 C<package_dir> + +This returns the directory that your extension is located in. + +If this is an extension that was installed via CPAN, the directory will +be the path to F<Bugzilla/Extension/Foo/>, if C<Foo.pm> is the name of your +extension. + +If you want to override this method, and you have a F<Config.pm>, you must +override this method in F<Config.pm>. + +=head3 C<template_dir> + +The directory that your package's templates are in. + +This defaults to the C<template> subdirectory of the L</package_dir>. + +If you want to override this method, and you have a F<Config.pm>, you must +override this method in F<Config.pm>. + +=head3 C<lib_dir> + +The directory where your extension's libraries are. + +This defaults to the C<lib> subdirectory of the L</package_dir>. + +If you want to override this method, and you have a F<Config.pm>, you must +override this method in F<Config.pm>. + +=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. |