diff options
author | jocuri%softhome.net <> | 2006-02-28 23:39:00 +0100 |
---|---|---|
committer | jocuri%softhome.net <> | 2006-02-28 23:39:00 +0100 |
commit | 90e1688ef09332be00b31278f8d5ee7703ac81b2 (patch) | |
tree | 5bc8d02bc88eda0c195229b3bd2cb6c96c0037d5 | |
parent | da1db1402be5d249990d1beb5f41390b92f7e0be (diff) | |
download | bugzilla-90e1688ef09332be00b31278f8d5ee7703ac81b2.tar.gz bugzilla-90e1688ef09332be00b31278f8d5ee7703ac81b2.tar.xz |
Patch for bug 298341: Implement code hook mechanism; patch by zach@zachlipton.com, r=timeless, a=justdave.
-rw-r--r-- | Bugzilla/Config.pm | 3 | ||||
-rw-r--r-- | Bugzilla/Hook.pm | 83 | ||||
-rw-r--r-- | Bugzilla/Template.pm | 25 | ||||
-rw-r--r-- | Bugzilla/Template/Plugin/Hook.pm | 60 | ||||
-rwxr-xr-x | checksetup.pl | 6 | ||||
-rw-r--r-- | docs/xml/customization.xml | 201 | ||||
-rwxr-xr-x | enter_bug.cgi | 3 |
7 files changed, 284 insertions, 97 deletions
diff --git a/Bugzilla/Config.pm b/Bugzilla/Config.pm index 935fc2c9e..33480f5f1 100644 --- a/Bugzilla/Config.pm +++ b/Bugzilla/Config.pm @@ -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'); diff --git a/Bugzilla/Hook.pm b/Bugzilla/Hook.pm new file mode 100644 index 000000000..91188e87b --- /dev/null +++ b/Bugzilla/Hook.pm @@ -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 diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm index 6327a31a5..57d113ef7 100644 --- a/Bugzilla/Template.pm +++ b/Bugzilla/Template.pm @@ -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; } diff --git a/Bugzilla/Template/Plugin/Hook.pm b/Bugzilla/Template/Plugin/Hook.pm index b189c5d26..bcfda1e5b 100644 --- a/Bugzilla/Template/Plugin/Hook.pm +++ b/Bugzilla/Template/Plugin/Hook.pm @@ -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> diff --git a/checksetup.pl b/checksetup.pl index f5cf909d5..4c3997ddb 100755 --- a/checksetup.pl +++ b/checksetup.pl @@ -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. diff --git a/docs/xml/customization.xml b/docs/xml/customization.xml index 0198670ba..ef091cb06 100644 --- a/docs/xml/customization.xml +++ b/docs/xml/customization.xml @@ -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. + 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> + 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 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. + 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 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 - <literal role="code">[% Hook.process("<varname>name</varname>") %]</literal>, - where <varname>name</varname> is the unique (within that template) - name of the hook. + 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> - 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 - 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: + 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 name of the hook. </para> <para> - <filename>BUGZILLA_ROOT/template/en/extension/hook/PATH_TO_STANDARD_TEMPLATE/STANDARD_TEMPLATE_NAME/HOOK_NAME/</filename> + 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 the source + files for occurences of <methodname>Bugzilla::Hook::process</methodname>. </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&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,7 +572,28 @@ 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> @@ -566,61 +601,47 @@ <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> diff --git a/enter_bug.cgi b/enter_bug.cgi index 5d9cd0626..c5680fff6 100755 --- a/enter_bug.cgi +++ b/enter_bug.cgi @@ -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", |