Patch for bug 298341: Implement code hook mechanism; patch by zach@zachlipton.com, r=timeless, a=justdave.

This commit is contained in:
jocuri%softhome.net 2006-02-28 14:39:33 +00:00
parent 37032d3287
commit ba3595875a
7 changed files with 287 additions and 100 deletions

View File

@ -69,6 +69,7 @@ if ($ENV{'PROJECT'} && $ENV{'PROJECT'} =~ /^(\w+)$/) {
}
our $attachdir = "$datadir/attachments";
our $webdotdir = "$datadir/webdot";
our $extensionsdir = "$libpath/extensions";
our @parampanels = ();
@ -87,7 +88,7 @@ our @parampanels = ();
admin => [qw(UpdateParams SetParam WriteParams)],
db => [qw($db_driver $db_host $db_port $db_name $db_user $db_pass $db_sock)],
locations => [qw($libpath $localconfig $attachdir $datadir $templatedir
$webdotdir $project)],
$webdotdir $project $extensionsdir)],
params => [qw(@parampanels)],
);
Exporter::export_ok_tags('admin', 'db', 'locations', 'params');

View File

@ -0,0 +1,83 @@
# -*- 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 Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Zach Lipton <zach@zachlipton.com>
#
package Bugzilla::Hook;
use Bugzilla::Util;
use Bugzilla::Error;
use strict;
sub process {
my $name = shift;
trick_taint($name);
# get a list of all extensions
my @extensions = glob($Bugzilla::Config::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);
if (-e $extension.'/code/'.$name.'.pl') {
do($extension.'/code/'.$name.'.pl');
ThrowCodeError("An error occured processing hook \"$name\" in ".
"Bugzilla extension \"$extension\": $@") if $@;
}
}
}
1;
__END__
=head1 NAME
Bugzilla::Hook - Extendible extension hooks for Bugzilla code
=head1 SYNOPSIS
use Bugzilla::Hook;
Bugzilla::Hook::process("hookname");
=head1 DESCRIPTION
Bugzilla allows extension modules to drop in and add routines at
arbitrary points in Bugzilla code. These points are refered to as
hooks. When a piece of standard Bugzilla code wants to allow an extension
to perform additional functions, it uses Bugzilla::Hook's process()
subroutine to invoke any extension code if installed.
=item C<process>
Invoke any code hooks with a matching name from any installed extensions.
When this subroutine is called with hook name foo, Bugzilla will attempt
to invoke any source files in C<bugzilla/extension/EXTENSION_NAME/code/foo.pl>.
See C<customization.xml> in the Bugzilla Guide for more information on
Bugzilla's extension mechanism.
=back

View File

@ -100,6 +100,7 @@ sub sortAcceptLanguage {
# Returns the path to the templates based on the Accept-Language
# settings of the user and of the available languages
# If no Accept-Language is present it uses the defined default
# Templates may also be found in the extensions/ tree
sub getTemplateIncludePath {
# Return cached value if available
@ -113,17 +114,14 @@ sub getTemplateIncludePath {
$template_include_path = [
"$templatedir/$languages/$project",
"$templatedir/$languages/custom",
"$templatedir/$languages/extension",
"$templatedir/$languages/default"
];
} else {
$template_include_path = [
"$templatedir/$languages/custom",
"$templatedir/$languages/extension",
"$templatedir/$languages/default"
];
}
return $template_include_path;
}
my @languages = sortAcceptLanguage($languages);
my @accept_language = sortAcceptLanguage($ENV{'HTTP_ACCEPT_LANGUAGE'} || "" );
@ -144,7 +142,6 @@ sub getTemplateIncludePath {
map((
"$templatedir/$_/$project",
"$templatedir/$_/custom",
"$templatedir/$_/extension",
"$templatedir/$_/default"
), @usedlanguages
)
@ -153,12 +150,30 @@ sub getTemplateIncludePath {
$template_include_path = [
map((
"$templatedir/$_/custom",
"$templatedir/$_/extension",
"$templatedir/$_/default"
), @usedlanguages
)
];
}
# add in extension template directories:
my @extensions = glob($Bugzilla::Config::extensionsdir."/*");
foreach my $extension (@extensions) {
trick_taint($extension); # since this comes right from the filesystem
# we have bigger issues if it is insecure
push(@$template_include_path,
map((
$extension."/template/".$_),
@usedlanguages));
}
# remove duplicates since they keep popping up:
my @dirs;
foreach my $dir (@$template_include_path) {
push(@dirs, $dir) unless grep ($dir eq $_, @dirs);
}
$template_include_path = [@dirs];
return $template_include_path;
}

View File

@ -18,12 +18,19 @@
# Rights Reserved.
#
# Contributor(s): Myk Melez <myk@mozilla.org>
# Zach Lipton <zach@zachlipton.com>
#
package Bugzilla::Template::Plugin::Hook;
use strict;
use Bugzilla::Config;
use Bugzilla::Template;
use Bugzilla::Util;
use Bugzilla::Error;
use File::Spec;
use base qw(Template::Plugin);
sub load {
@ -42,7 +49,38 @@ sub process {
my $paths = $self->{_CONTEXT}->{LOAD_TEMPLATES}->[0]->paths;
my $template = $self->{_CONTEXT}->stash->{component}->{name};
my @hooks = ();
# sanity check:
if (!$template =~ /[\w\.\/\-_\\]+/) {
ThrowCodeError("Template with invalid file name found in hook call: $template");
}
# also get extension hook files that live in extensions/:
# parse out the parts of the template name
my ($vol, $subpath, $filename) = File::Spec->splitpath($template);
$subpath = $subpath || '';
$filename =~ m/(.*)\.(.*)\.tmpl/;
my $templatename = $1;
my $type = $2;
# munge the filename to create the extension hook filename:
my $extensiontemplate = $subpath.'/'.$templatename.'-'.$hook_name.'.'.$type.'.tmpl';
my @extensions = glob($Bugzilla::Config::extensionsdir."/*");
my @usedlanguages = getLanguages();
foreach my $extension (@extensions) {
foreach my $language (@usedlanguages) {
my $file = $extension.'/template/'.$language.'/'.$extensiontemplate;
if (-e $file) {
# tt is stubborn and won't take a template file not in its
# include path, so we open a filehandle and give it to process()
# so the hook gets invoked:
open (my $fh, $file);
push(@hooks, $fh);
}
}
}
# we keep this too since you can still put hook templates in
# template/en/custom/hook
foreach my $path (@$paths) {
my @files = glob("$path/hook/$template/$hook_name/*.tmpl");
@ -65,6 +103,24 @@ sub process {
return $output;
}
# get a list of languages we accept so we can find the hook
# that corresponds to our desired languages:
sub getLanguages() {
my $languages = trim(Param('languages'));
if (not ($languages =~ /,/)) { # only one language
return $languages;
}
my @languages = Bugzilla::Template::sortAcceptLanguage($languages);
my @accept_language = Bugzilla::Template::sortAcceptLanguage($ENV{'HTTP_ACCEPT_LANGUAGE'} || "" );
my @usedlanguages;
foreach my $lang (@accept_language) {
if(my @found = grep /^\Q$lang\E(-.+)?$/i, @languages) {
push (@usedlanguages, @found);
}
}
return @usedlanguages;
}
1;
__END__
@ -79,5 +135,7 @@ Template Toolkit plugin to process hooks added into templates by extensions.
=head1 SEE ALSO
L<Template::Plugin>,
L<Template::Plugin>
L<Customization.xml in the Bugzilla Guide>
L<http://bugzilla.mozilla.org/show_bug.cgi?id=229658>
L<http://bugzilla.mozilla.org/show_bug.cgi?id=298341>

View File

@ -865,6 +865,12 @@ unless (-d $attachdir) {
mkdir $attachdir, 0770;
}
# ZLL: 2005-08-20 Create extensions/ if it does not already exist:
unless (-d $extensionsdir) {
print "Creating extensions directory ($extensionsdir) ...\n";
mkdir $extensionsdir, 0770;
}
# 2000-12-14 New graphing system requires a directory to put the graphs in
# This code copied from what happens for the data dir above.

View File

@ -411,11 +411,11 @@
</section>
<section id="cust-hooks">
<title>Template Hooks</title>
<title>The Bugzilla Extension Mechanism</title>
<warning>
<para>
Template Hooks require Template Toolkit version 2.12 or
Custom extensions require Template Toolkit version 2.12 or
above, or the application of a patch. See <ulink
url="http://bugzilla.mozilla.org/show_bug.cgi?id=239112">bug
239112</ulink> for details.
@ -423,62 +423,82 @@
</warning>
<para>
Template hooks are a way for extensions to Bugzilla to insert code
into the standard Bugzilla templates without modifying the template files
themselves. The hooks mechanism defines a consistent API for extending
the standard templates in a way that cleanly separates standard code
from extension code. Hooks reduce merge conflicts and make it easier
to write extensions that work across multiple versions of Bugzilla,
making upgrading a Bugzilla installation with installed extensions easier.
</para>
<para>
A template hook is just a named place in a standard template file
where extension template files for that hook get processed. Each hook
has a corresponding directory in the Bugzilla directory tree. Hooking an
extension template to a hook is as simple as putting the extension file
into the hook's directory. When Bugzilla processes the standard template
and reaches the hook, it will process all extension templates in the
hook's directory. The hooks themselves can be added into any standard
template upon request by extension authors.
Extensions are a way for extensions to Bugzilla to insert code
into the standard Bugzilla templates and source files
without modifying these files themselves. The extension mechanism
defines a consistent API for extending the standard templates and source files
in a way that cleanly separates standard code from extension code.
Hooks reduce merge conflicts and make it easier to write extensions that work
across multiple versions of Bugzilla, making upgrading a Bugzilla installation
with installed extensions easier. Furthermore, they make it easy to install
and remove extensions as each extension is nothing more than a
simple directory structure.
</para>
<para>
To use hooks to extend a Bugzilla template, first make sure there is
a hook at the appropriate place within the template you want to extend.
Hooks appear in the standard Bugzilla templates as a single directive
in the format
There are two main types of hooks: code hooks and template hooks. Code
hooks allow extensions to invoke code at specific points in various
source files, while template hooks allow extensions to add elements to
the Bugzilla user interface.
</para>
<para>
A hook is just a named place in a standard source or template file
where extension source code or template files for that hook get processed.
Each extension has a corresponding directory in the Bugzilla directory
tree (<filename>BUGZILLA_ROOT/extensions/extension_name</filename>). Hooking
an extension source file or template to a hook is as simple as putting
the extension file into extension's template or code directory.
When Bugzilla processes the source file or template and reaches the hook,
it will process all extension files in the hook's directory.
The hooks themselves can be added into any source file or standard template
upon request by extension authors.
</para>
<para>
To use hooks to extend Bugzilla, first make sure there is
a hook at the appropriate place within the source file or template you
want to extend. The exact appearence of a hook depends on if the hook
is a code hook or a template hook.
</para>
<para>
Code hooks appear in Bugzilla source files as a single method call
in the format <literal role="code">Bugzilla::Hook->process("<varname>name</varname>");</literal>.
for instance, <filename>enter_bug.cgi</filename> may invoke the hook
"<varname>enter_bug-defaultvars</varname>". Thus, a source file at
<filename>BUGZILLA_ROOT/extensions/EXTENSION_NAME/code/enter_bug-entrydefaultvars.pl</filename>
will be automatically invoked when when the code hook is reached.
<para>
<para>
Template hooks appear in the standard Bugzilla templates as a
single directive in the format
<literal role="code">[% Hook.process("<varname>name</varname>") %]</literal>,
where <varname>name</varname> is the unique (within that template)
name of the hook.
where <varname>name</varname> is the unique name of the hook.
</para>
<para>
If you aren't sure which template you want to extend or just want
to browse the available hooks, either use your favorite multi-file search
If you aren't sure what you want to extend or just want to browse the
available hooks, either use your favorite multi-file search
tool (e.g. <command>grep</command>) to search the standard templates
for occurrences of <methodname>Hook.process</methodname> or browse
the directory tree in
<filename>BUGZILLA_ROOT/template/en/extension/hook/</filename>,
which contains a directory for each hook in the following location:
for occurrences of <methodname>Hook.process</methodname> or the source
files for occurences of <methodname>Bugzilla::Hook::process</methodname>.
</para>
<para>
<filename>BUGZILLA_ROOT/template/en/extension/hook/PATH_TO_STANDARD_TEMPLATE/STANDARD_TEMPLATE_NAME/HOOK_NAME/</filename>
</para>
<para>
If there is no hook at the appropriate place within the Bugzilla template
you want to extend,
If there is no hook at the appropriate place within the Bugzilla
source file or template you want to extend,
<ulink url="http://bugzilla.mozilla.org/enter_bug.cgi?product=Bugzilla&amp;component=User%20Interface">file
a bug requesting one</ulink>, specifying:
</para>
<simplelist>
<member>the template for which you are requesting a hook;</member>
<member>the source or template file for which you are
requesting a hook;</member>
<member>
where in the template you would like the hook to be placed
(line number/position for latest version of template in CVS
where in the file you would like the hook to be placed
(line number/position for latest version of the file in CVS
or description of location);
</member>
<member>the purpose of the hook;</member>
@ -487,9 +507,8 @@
<para>
The Bugzilla reviewers will promptly review each hook request,
name the hook, add it to the template, check the new version
of the template into CVS, and create the corresponding directory in
<filename>BUGZILLA_ROOT/template/en/extension/hook/</filename>.
name the hook, add it to the template or source file, and check
the new version of the template into CVS.
</para>
<para>
@ -505,13 +524,13 @@
<para>
After making sure the hook you need exists (or getting it added if not),
add your extension template to the directory within the Bugzilla
directory tree corresponding to the hook.
add your extension to the directory within the Bugzilla
extensions tree corresponding to the hook.
</para>
<para>
That's it! Now, when the standard template containing the hook
is processed, your extension template will be processed at the point
That's it! Now, when the source file or template containing the hook
is processed, your extension file will be processed at the point
where the hook appears.
</para>
@ -542,14 +561,9 @@
...]]></programlisting>
<para>
The corresponding directory for this hook is
<filename>BUGZILLA_ROOT/template/en/extension/hook/global/useful-links.html.tmpl/edit/</filename>.
</para>
<para>
You put a template named
<filename>projman-edit-projects.html.tmpl</filename>
into that directory with the following content:
The corresponding extension file for this hook is
<filename>BUGZILLA_ROOT/extensions/projman/template/en/hook/global/useful-links-edit.html.tmpl</filename>.
You then create that template file and add the following constant:
</para>
<programlisting><![CDATA[...[% ', <a href="edit-projects.cgi">projects</a>' IF user.groups.projman_admins %]]]></programlisting>
@ -558,69 +572,76 @@
Voila! The link now appears after the other administration links in the
navigation bar for users in the <literal>projman_admins</literal> group.
</para>
<para>
Now, let us say your extension adds a custom "project_manager" field
to enter_bug.cgi. You want to modify the CGI script to set the default
project manager to be productname@company.com. Looking at
<filename>enter_bug.cgi</filename>, you see the enter_bug-entrydefaultvars
hook near the bottom of the file before the default form values are set.
The corresponding extension source file for this hook is located at
<filename>BUGZILLA_ROOT/extensions/projman/code/enter_bug-entrydefaultvars.pl</filename>.
You then create that file and add the following:
</para>
<programlisting>$default{'project_manager'} = $product.'@company.com';</programlisting>
<para>
This code will be invoked whenever enter_bug.cgi is executed.
Assuming that the rest of the customization was completed (e.g. the
custom field was added to the enter_bug template and the required hooks
were used in process_bug.cgi), the new field will now have this
default value.
</para>
<para>
Notes:
</para>
<itemizedlist>
<listitem>
<para>
You may want to prefix your extension template names
with the name of your extension, e.g.
<filename>projman-foo.html.tmpl</filename>,
so they do not conflict with the names of templates installed by
other extensions.
</para>
</listitem>
<listitem>
<para>
If your extension includes entirely new templates in addition to
extensions of standard templates, it should install those new
templates into an extension-specific subdirectory of the
<filename>BUGZILLA_ROOT/template/en/extension/</filename>
directory. The <filename>extension/</filename> directory, like the
extensions of standard templates, it should store those new
templates in its
<filename>BUGZILLA_ROOT/extensions/template/en/</filename>
directory. Extension template directories, like the
<filename>default/</filename> and <filename>custom/</filename>
directories, is part of the template search path, so putting templates
directories, are part of the template search path, so putting templates
there enables them to be found by the template processor.
</para>
<para>
The template processor looks for templates first in the
<filename>custom/</filename> directory (i.e. templates added by the
specific installation), then in the <filename>extension/</filename>
directory (i.e. templates added by extensions), and finally in the
specific installation), then in the <filename>extensions/</filename>
directory (i.e. templates added by extensions), and finally in the
<filename>default/</filename> directory (i.e. the standard Bugzilla
templates). Thus extension templates can override standard templates,
but installation-specific templates override both.
</para>
<para>
Note that overriding standard templates with extension templates
gives you great power but also makes upgrading an installation harder.
As with custom templates, we recommend using this functionality
sparingly and only when absolutely necessary.
templates). Thus, installation-specific templates override both
default and extension templates.
</para>
</listitem>
<listitem>
<para>
Installation customizers can also take advantage of hooks when adding
code to a Bugzilla template. To do so, create directories in
If you are looking to customize Bugzilla, you can also take advantage
of template hooks. To do so, create a directory in
<filename>BUGZILLA_ROOT/template/en/custom/hook/</filename>
equivalent to the directories in
<filename>BUGZILLA_ROOT/template/en/extension/hook/</filename>
for the hooks you want to use, then place your customization templates
into those directories.
that corresponds to the hook you wish to use, then place your
customization templates into those directories. For example,
if you wanted to use the hook "end" in
<filename>global/useful-links.html.tmpl</filename>, you would
create the directory <filename>BUGZILLA_ROOT/template/en/custom/hook/
global/useful-links.html.tmpl/end/</filename> and add your customization
template to this directory.
</para>
<para>
Obviously this method of customizing Bugzilla only lets you add code
to the standard templates; you cannot change the existing code.
Nevertheless, for those customizations that only add code, this method
can reduce conflicts when merging changes, making upgrading
your customized Bugzilla installation easier.
to the standard source files and templates; you cannot change the
existing code. Nevertheless, for those customizations that only add
code, this method can reduce conflicts when merging changes,
making upgrading your customized Bugzilla installation easier.
</para>
</listitem>
</itemizedlist>

View File

@ -41,6 +41,7 @@ use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Bug;
use Bugzilla::User;
use Bugzilla::Hook;
use Bugzilla::Product;
require "globals.pl";
@ -589,6 +590,8 @@ foreach my $row (@$grouplist) {
$vars->{'group'} = \@groups;
Bugzilla::Hook::process("enter_bug-entrydefaultvars");
$vars->{'default'} = \%default;
my $format = $template->get_format("bug/create/create",