]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Patch for bug 298341: Implement code hook mechanism; patch by zach@zachlipton.com...
authorjocuri%softhome.net <>
Tue, 28 Feb 2006 22:39:00 +0000 (22:39 +0000)
committerjocuri%softhome.net <>
Tue, 28 Feb 2006 22:39:00 +0000 (22:39 +0000)
Bugzilla/Config.pm
Bugzilla/Hook.pm [new file with mode: 0644]
Bugzilla/Template.pm
Bugzilla/Template/Plugin/Hook.pm
checksetup.pl
docs/xml/customization.xml
enter_bug.cgi

index 935fc2c9eaf21b8efc7b8b35d2b68a1cdff23bf7..33480f5f1e66888cfc50594df60a1b90329c7e8f 100644 (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');
diff --git a/Bugzilla/Hook.pm b/Bugzilla/Hook.pm
new file mode 100644 (file)
index 0000000..91188e8
--- /dev/null
@@ -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
index 6327a31a54275e980aa1a0a4f724067fd79a83d3..57d113ef7d34b5741c533361fb95ebc8050c07ef 100644 (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;
 }
 
index b189c5d2649e798797602b077db68fdd5e362ecf..bcfda1e5b533ec00601476561b5f7d9ff46c4762 100644 (file)
 # 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>
index f5cf909d54414624b4aeb2f2fabf8482d6e9401c..4c3997ddb9f29b6aae76cbbefe7fb3bee6ace907 100755 (executable)
@@ -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.
index 0198670ba1a514e056fc0d3afc5ebea48278c51e..ef091cb065fc2c3d7b539019ef0204bad5ffaf19 100644 (file)
   </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.
     </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&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>
 
     <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>
 
     <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>
 
 ...]]></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>
       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>    
index 5d9cd0626b5a03d477453240266d3f296a04d2fa..c5680fff6fca8e75b22f498d35881771322444aa 100755 (executable)
@@ -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",