Skip to content

Instantly share code, notes, and snippets.

@dylanwh
Created May 18, 2020 13:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dylanwh/adf0575ce9acf10f5cb81677fb6acdf7 to your computer and use it in GitHub Desktop.
Save dylanwh/adf0575ce9acf10f5cb81677fb6acdf7 to your computer and use it in GitHub Desktop.
--- bugzilla-5.2/Bugzilla/Extension.pm 2020-03-28 15:20:01.000000000 -0400
+++ harmony/Bugzilla/Extension.pm 2020-05-18 09:35:52.000000000 -0400
@@ -13,22 +13,51 @@
use Bugzilla::Constants;
use Bugzilla::Error;
-use Bugzilla::Install::Util qw(
- extension_code_files extension_template_directory
- extension_package_directory extension_web_directory);
+use Bugzilla::Install::Util qw( extension_code_files );
use File::Basename;
use File::Spec;
+use List::Util qw(any);
+use Scalar::Util qw(refaddr);
-####################
-# Subclass Methods #
-####################
+BEGIN { push @INC, \&INC_HOOK }
-sub new {
- my ($class, $params) = @_;
- $params ||= {};
- bless $params, $class;
- return $params;
+sub INC_HOOK {
+ my (undef, $fake_file) = @_;
+ state $bz_locations = bz_locations();
+ my ($vol, $dir, $file) = File::Spec->splitpath($fake_file);
+ my @dirs = grep { length $_ } File::Spec->splitdir($dir);
+
+ if (@dirs > 2 && $dirs[0] eq 'Bugzilla' && $dirs[1] eq 'Extension') {
+ my $extension = $dirs[2];
+ splice @dirs, 0, 3, File::Spec->splitdir($bz_locations->{extensionsdir}),
+ $extension, "lib";
+ my $real_file
+ = Cwd::realpath(File::Spec->catpath($vol, File::Spec->catdir(@dirs), $file));
+
+ my $first = 1;
+ $INC{$fake_file} = $real_file;
+ my $found = open my $fh, '<', $real_file;
+ unless ($found) {
+ require Carp;
+ Carp::croak
+ "Can't locate $fake_file while looking for $real_file in \@INC (\@INC contains: @INC)";
+ }
+ return sub {
+ no warnings;
+ if (!$first) {
+ return 0 if eof $fh;
+ $_ = readline $fh or return 0;
+ return 1;
+ }
+ else {
+ $_ = qq{# line 1 "$real_file"\n};
+ $first = 0;
+ return 1;
+ }
+ };
+ }
+ return;
}
#######################################
@@ -58,13 +87,10 @@
}
$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;
@@ -73,7 +99,6 @@
{extension => $extension_file, returned => $name});
}
$package = "${class}::$name";
- $package->modify_inc($extension_file) if !$config_file;
}
$class->_validate_package($package, $extension_file);
@@ -104,83 +129,18 @@
sub load_all {
my $class = shift;
- my ($file_sets, $extra_packages) = extension_code_files();
- my @packages;
+ state $EXTENSIONS = [];
+ return $EXTENSIONS if @$EXTENSIONS;
+
+ my ($file_sets) = extension_code_files();
foreach my $file_set (@$file_sets) {
my $package = $class->load(@$file_set);
- push(@packages, $package);
+ push(@$EXTENSIONS, $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);
- }
+ Bugzilla::Hook::finalize($EXTENSIONS);
- # 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;
+ return $EXTENSIONS;
}
####################
@@ -189,45 +149,21 @@
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 package_dir {
+ my ($class) = @_;
+ state $bz_locations = bz_locations();
+ my (undef, undef, $name) = split(/::/, $class);
+ return File::Spec->catdir($bz_locations->{extensionsdir}, $name);
}
-sub template_dir { return extension_template_directory(@_); }
-sub package_dir { return extension_package_directory(@_); }
-sub web_dir { return extension_web_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);
+sub template_dir {
+ my ($class) = @_;
+ return File::Spec->catdir($class->package_dir, "template");
+}
+
+sub web_dir {
+ my ($class) = @_;
+ return File::Spec->catdir($class->package_dir, "web");
}
1;
@@ -240,7 +176,7 @@
=head1 SYNOPSIS
-The following would be in F<extensions/Foo/Extension.pm> or
+The following would be in F<extensions/Foo/Extension.pm> or
F<extensions/Foo.pm>:
package Bugzilla::Extension::Foo
@@ -255,7 +191,7 @@
__PACKAGE__->NAME;
Custom templates would go into F<extensions/Foo/template/en/default/>.
-L<Template hooks|/Template Hooks> would go into
+L<Template hooks|/Template Hooks> would go into
F<extensions/Foo/template/en/default/hook/>.
=head1 DESCRIPTION
@@ -266,14 +202,14 @@
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
+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:
+and, in the base Bugzilla directory, do:
C<extensions/create.pl NAME>
@@ -300,7 +236,7 @@
=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.
+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>.
@@ -375,14 +311,14 @@
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
+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>.
+added to Bugzilla itself, see our development process at
+L<https://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
@@ -407,7 +343,7 @@
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
+which has the same format as
L<Bugzilla::Install::Requirements/OPTIONAL_MODULES>.
=head3 If Your Extension Needs Certain Modules In Order To Compile
@@ -442,11 +378,11 @@
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
+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
+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>,
@@ -458,7 +394,7 @@
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
+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.
@@ -467,7 +403,7 @@
=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
+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
@@ -478,12 +414,12 @@
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
+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
+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.
@@ -519,13 +455,13 @@
=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
+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
+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
@@ -542,10 +478,10 @@
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
+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<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
@@ -557,7 +493,7 @@
=item <templates>
-This is the full path to the template directory, like
+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
@@ -569,13 +505,13 @@
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>
+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
+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>.
@@ -588,7 +524,7 @@
=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
+of the template you want to hook. In our example, this is
C<additional_header>.
=item <template type>
@@ -602,7 +538,7 @@
=head3 Adding New Template Hooks to Bugzilla
-Adding new template hooks is just like adding code hooks (see
+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.
@@ -613,11 +549,11 @@
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
+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.
+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
@@ -649,27 +585,12 @@
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.
+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
+extension was called F<Foo>, your file would go into
F<extensions/Foo/web/style.css>.
-=head2 Documenting Extensions
-
-Documentation goes in F<extensions/Foo/docs/en/rst/>, if it's in English, or
-change "en" to something else if it's not. The user documentation index file
-must be called index-user.rst; the admin documentation must be called
-index-admin.rst. These will end up in the User Guide and the Administration
-Guide respectively. Both documentation types are optional. You can use various
-Sphinx constructs such as :toctree: or :include: to include further reST files
-if you need more than one page of docs.
-
-When documenting extensions to the Bugzilla API, if your extension provides
-them, the index file would be F<extensions/Foo/docs/en/rst/api/v1/index.rst>.
-When and if your API has more than one version, increment the version number.
-These docs will get included in the WebService API Reference.
-
=head2 Disabling Your Extension
If you want your extension to be totally ignored by Bugzilla (it will
@@ -689,21 +610,21 @@
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>.
+then put a link to it in the appropriate section of
+L<https://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
+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.
+and on L<https://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>,
+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
@@ -731,7 +652,7 @@
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/>.
+normal support channels described at L<https://www.bugzilla.org/support/>.
=head1 ADDITIONAL CONSTANTS
@@ -777,9 +698,9 @@
=head3 C<package_dir>
-This returns the directory that your extension is located in.
+This returns the directory that your extension is located in.
-If this is an extension that was installed via CPAN, the directory will
+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.
@@ -797,7 +718,7 @@
=head3 C<web_dir>
-The directory that your package's web related files are in, such as css and javascript.
+The directory that your package's web related files are in, such as CSS and JavaScript.
This defaults to the C<web> subdirectory of the L</package_dir>.
@@ -829,13 +750,3 @@
Calls L</load> for every enabled extension installed into Bugzilla,
and returns an arrayref of all the package names that were loaded.
-
-=head1 B<Methods in need of POD>
-
-=over
-
-=item modify_inc
-
-=item my_inc
-
-=back
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment