From ba3595875a57fe4f632925550b046c10b3e08a6a Mon Sep 17 00:00:00 2001 From: "jocuri%softhome.net" Date: Tue, 28 Feb 2006 14:39:33 +0000 Subject: [PATCH] Patch for bug 298341: Implement code hook mechanism; patch by zach@zachlipton.com, r=timeless, a=justdave. --- webtools/bugzilla/Bugzilla/Config.pm | 3 +- webtools/bugzilla/Bugzilla/Hook.pm | 83 +++++++ webtools/bugzilla/Bugzilla/Template.pm | 25 ++- .../bugzilla/Bugzilla/Template/Plugin/Hook.pm | 60 ++++- webtools/bugzilla/checksetup.pl | 6 + webtools/bugzilla/docs/xml/customization.xml | 207 ++++++++++-------- webtools/bugzilla/enter_bug.cgi | 3 + 7 files changed, 287 insertions(+), 100 deletions(-) create mode 100644 webtools/bugzilla/Bugzilla/Hook.pm diff --git a/webtools/bugzilla/Bugzilla/Config.pm b/webtools/bugzilla/Bugzilla/Config.pm index 935fc2c9eaf2..33480f5f1e66 100644 --- a/webtools/bugzilla/Bugzilla/Config.pm +++ b/webtools/bugzilla/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/webtools/bugzilla/Bugzilla/Hook.pm b/webtools/bugzilla/Bugzilla/Hook.pm new file mode 100644 index 000000000000..91188e87be6d --- /dev/null +++ b/webtools/bugzilla/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 +# + +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 + +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. +See C in the Bugzilla Guide for more information on +Bugzilla's extension mechanism. + +=back diff --git a/webtools/bugzilla/Bugzilla/Template.pm b/webtools/bugzilla/Bugzilla/Template.pm index 6327a31a5427..57d113ef7d34 100644 --- a/webtools/bugzilla/Bugzilla/Template.pm +++ b/webtools/bugzilla/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/webtools/bugzilla/Bugzilla/Template/Plugin/Hook.pm b/webtools/bugzilla/Bugzilla/Template/Plugin/Hook.pm index b189c5d2649e..bcfda1e5b533 100644 --- a/webtools/bugzilla/Bugzilla/Template/Plugin/Hook.pm +++ b/webtools/bugzilla/Bugzilla/Template/Plugin/Hook.pm @@ -18,12 +18,19 @@ # Rights Reserved. # # Contributor(s): Myk Melez +# Zach Lipton # 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, +L +L L +L diff --git a/webtools/bugzilla/checksetup.pl b/webtools/bugzilla/checksetup.pl index f5cf909d5441..4c3997ddb9f2 100755 --- a/webtools/bugzilla/checksetup.pl +++ b/webtools/bugzilla/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/webtools/bugzilla/docs/xml/customization.xml b/webtools/bugzilla/docs/xml/customization.xml index 0198670ba1a5..ef091cb065fc 100644 --- a/webtools/bugzilla/docs/xml/customization.xml +++ b/webtools/bugzilla/docs/xml/customization.xml @@ -411,11 +411,11 @@
- Template Hooks + The Bugzilla Extension Mechanism - 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 bug 239112 for details. @@ -423,62 +423,82 @@ - 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. - - - - 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. - 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. + + + + 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 (BUGZILLA_ROOT/extensions/extension_name). 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. + + + + 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. + + + + Code hooks appear in Bugzilla source files as a single method call + in the format Bugzilla::Hook->process("name");. + for instance, enter_bug.cgi may invoke the hook + "enter_bug-defaultvars". Thus, a source file at + BUGZILLA_ROOT/extensions/EXTENSION_NAME/code/enter_bug-entrydefaultvars.pl + will be automatically invoked when when the code hook is reached. + + + + Template hooks appear in the standard Bugzilla templates as a + single directive in the format [% Hook.process("name") %], - where name is the unique (within that template) - name of the hook. + where name is the unique name of the hook. - 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. grep) to search the standard templates - for occurrences of Hook.process or browse - the directory tree in - BUGZILLA_ROOT/template/en/extension/hook/, - which contains a directory for each hook in the following location: + for occurrences of Hook.process or the source + files for occurences of Bugzilla::Hook::process. - BUGZILLA_ROOT/template/en/extension/hook/PATH_TO_STANDARD_TEMPLATE/STANDARD_TEMPLATE_NAME/HOOK_NAME/ - - - - 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, file a bug requesting one, specifying: - the template for which you are requesting a hook; + the source or template file for which you are + requesting a hook; - 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); the purpose of the hook; @@ -487,9 +507,8 @@ 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 - BUGZILLA_ROOT/template/en/extension/hook/. + name the hook, add it to the template or source file, and check + the new version of the template into CVS. @@ -505,13 +524,13 @@ 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. - 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. @@ -542,14 +561,9 @@ ...]]> - The corresponding directory for this hook is - BUGZILLA_ROOT/template/en/extension/hook/global/useful-links.html.tmpl/edit/. - - - - You put a template named - projman-edit-projects.html.tmpl - into that directory with the following content: + The corresponding extension file for this hook is + BUGZILLA_ROOT/extensions/projman/template/en/hook/global/useful-links-edit.html.tmpl. + You then create that template file and add the following constant: projects' IF user.groups.projman_admins %]]]> @@ -558,69 +572,76 @@ Voila! The link now appears after the other administration links in the navigation bar for users in the projman_admins group. - + + + 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 + enter_bug.cgi, 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 + BUGZILLA_ROOT/extensions/projman/code/enter_bug-entrydefaultvars.pl. + You then create that file and add the following: + + + $default{'project_manager'} = $product.'@company.com'; + + + 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. + + Notes: - - - You may want to prefix your extension template names - with the name of your extension, e.g. - projman-foo.html.tmpl, - so they do not conflict with the names of templates installed by - other extensions. - - - 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 - BUGZILLA_ROOT/template/en/extension/ - directory. The extension/ directory, like the + extensions of standard templates, it should store those new + templates in its + BUGZILLA_ROOT/extensions/template/en/ + directory. Extension template directories, like the default/ and custom/ - 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. The template processor looks for templates first in the custom/ directory (i.e. templates added by the - specific installation), then in the extension/ - directory (i.e. templates added by extensions), and finally in the + specific installation), then in the extensions/ + directory (i.e. templates added by extensions), and finally in the default/ directory (i.e. the standard Bugzilla - templates). Thus extension templates can override standard templates, - but installation-specific templates override both. - - - - 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. - 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 BUGZILLA_ROOT/template/en/custom/hook/ - equivalent to the directories in - BUGZILLA_ROOT/template/en/extension/hook/ - 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 + global/useful-links.html.tmpl, you would + create the directory BUGZILLA_ROOT/template/en/custom/hook/ + global/useful-links.html.tmpl/end/ and add your customization + template to this directory. 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. diff --git a/webtools/bugzilla/enter_bug.cgi b/webtools/bugzilla/enter_bug.cgi index 5d9cd0626b5a..c5680fff6fca 100755 --- a/webtools/bugzilla/enter_bug.cgi +++ b/webtools/bugzilla/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",