diff options
-rw-r--r-- | Bugzilla/Extension.pm | 4 | ||||
-rw-r--r-- | contrib/extension-convert.pl | 304 | ||||
-rw-r--r-- | extensions/BmpConvert/Extension.pm | 12 | ||||
-rw-r--r-- | extensions/Example/Extension.pm | 138 |
4 files changed, 381 insertions, 77 deletions
diff --git a/Bugzilla/Extension.pm b/Bugzilla/Extension.pm index 08d5c86c3..48efd76f6 100644 --- a/Bugzilla/Extension.pm +++ b/Bugzilla/Extension.pm @@ -281,8 +281,8 @@ For example, here's an implementation of a hook named C<foo_start> that gets an argument named C<bar>: sub foo_start { - my ($self, $params) = @_; - my $bar = $params->{bar}; + my ($self, $args) = @_; + my $bar = $args->{bar}; print "I got $bar!\n"; } diff --git a/contrib/extension-convert.pl b/contrib/extension-convert.pl new file mode 100644 index 000000000..1f29db3da --- /dev/null +++ b/contrib/extension-convert.pl @@ -0,0 +1,304 @@ +#!/usr/bin/perl -w +# +# 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 Everything Solved, Inc. +# Portions created by the Initial Developer are Copyright (C) 2009 the +# Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Max Kanat-Alexander <mkanat@bugzilla.org> + +use strict; +use warnings; +use lib qw(. lib); + +use Bugzilla; +use Bugzilla::Constants; +use Bugzilla::Util qw(trim); + +use File::Basename; +use File::Copy qw(move); +use File::Find; +use File::Path qw(mkpath rmtree); + +my $from = $ARGV[0] + or die <<END; +You must specify the name of the extension you are converting from, +as the first argument. +END +my $extension_name = ucfirst($from); + +my $extdir = bz_locations()->{'extensionsdir'}; + +my $from_dir = "$extdir/$from"; +if (!-d $from_dir) { + die "$from_dir does not exist.\n"; +} + +my $to_dir = "$extdir/$extension_name"; +if (-d $to_dir) { + die "$to_dir already exists, not converting.\n"; +} + +if (ON_WINDOWS) { + # There's no easy way to recursively copy a directory on Windows. + print "WARNING: This will modify the contents of $from_dir.\n", + "Press Ctrl-C to stop or any other key to continue...\n"; + getc; + move($from_dir, $to_dir) + || die "rename of $from_dir to $to_dir failed: $!"; +} +else { + print "Copying $from_dir to $to_dir...\n"; + system("cp", "-r", $from_dir, $to_dir); +} + +# Make sure we don't accidentally modify the $from_dir anywhere else +# in this script. +undef $from_dir; + +if (!-d $to_dir) { + die "$to_dir was not created.\n"; +} + +my $version = get_version($to_dir); +move_template_hooks($to_dir); +rename_module_packages($to_dir, $extension_name); +my $install_requirements = get_install_requirements($to_dir); +my ($modules, $subs) = code_files_to_subroutines($to_dir); + +my $config_pm = <<END; +package Bugzilla::Extension::$extension_name; +use strict; +use constant NAME => '$extension_name'; +$install_requirements +__PACKAGE__->NAME; +END + +my $extension_pm = <<END; +package Bugzilla::Extension::$extension_name; +use strict; +use base qw(Bugzilla::Extension); + +$modules + +our \$VERSION = '$version'; + +$subs + +__PACKAGE__->NAME; +END + +open(my $config_fh, '>', "$to_dir/Config.pm") || die "$to_dir/Config.pm: $!"; +print $config_fh $config_pm; +close($config_fh); +open(my $extension_fh, '>', "$to_dir/Extension.pm") + || die "$to_dir/Extension.pm: $!"; +print $extension_fh $extension_pm; +close($extension_fh); + +rmtree("$to_dir/code"); +unlink("$to_dir/info.pl"); + +############### +# Subroutines # +############### + +sub rename_module_packages { + my ($dir, $name) = @_; + my $lib_dir = "$dir/lib"; + + # We don't want things like Bugzilla::Extension::Testopia::Testopia. + if (-d "$lib_dir/$name") { + print "Moving contents of $lib_dir/$name into $lib_dir...\n"; + foreach my $file (glob("$lib_dir/$name/*")) { + my $dirname = dirname($file); + my $basename = basename($file); + rename($file, "$dirname/../$basename") || warn "$file: $!\n"; + } + } + + my @modules; + find({ wanted => sub { $_ =~ /\.pm$/i and push(@modules, $_) }, + no_chdir => 1 }, $lib_dir); + my %module_rename; + foreach my $file (@modules) { + open(my $fh, '<', $file) || die "$file: $!"; + my $content = do { local $/ = undef; <$fh> }; + close($fh); + if ($content =~ /^package (\S+);/m) { + my $package = $1; + my $new_name = $file; + $new_name =~ s/^$lib_dir\///; + $new_name =~ s/\.pm$//; + $new_name = join('::', File::Spec->splitdir($new_name)); + $new_name = "Bugzilla::Extension::${name}::$new_name"; + print "Renaming $package to $new_name...\n"; + $content =~ s/^package \Q$package\E;/package \Q$new_name\E;/; + open(my $write_fh, '>', $file) || die "$file: $!"; + print $write_fh $content; + close($write_fh); + $module_rename{$package} = $new_name; + } + } + + print "Renaming module names inside of library and code files...\n"; + my @code_files = glob("$dir/code/*.pl"); + rename_modules_internally(\%module_rename, [@modules, @code_files]); +} + +sub rename_modules_internally { + my ($rename, $files) = @_; + + # We can't use \b because :: matches \b. + my $break = qr/^|[^\w:]|$/; + foreach my $file (@$files) { + open(my $fh, '<', $file) || die "$file: $!"; + my $content = do { local $/ = undef; <$fh> }; + close($fh); + foreach my $old_name (keys %$rename) { + my $new_name = $rename->{$old_name}; + $content =~ s/($break)\Q$old_name\E($break)/$1$new_name$2/gms; + } + open(my $write_fh, '>', $file) || die "$file: $!"; + print $write_fh $content; + close($write_fh); + } +} + +sub get_version { + my ($dir) = @_; + print "Getting version info from info.pl...\n"; + my $info; + { + local @INC = ("$dir/lib", @INC); + $info = do "$dir/info.pl"; die $@ if $@; + } + return $info->{version}; +} + +sub get_install_requirements { + my ($dir) = @_; + my $file = "$dir/code/install-requirements.pl"; + return '' if !-f $file; + + print "Moving install-requirements.pl code into Config.pm...\n"; + my ($modules, $code) = process_code_file($file); + $modules = join('', @$modules); + $code = join('', @$code); + if ($modules) { + return "$modules\n\n$code"; + } + return $code; +} + +sub process_code_file { + my ($file) = @_; + open(my $fh, '<', $file) || die "$file: $!"; + my $stuff_started; + my (@modules, @code); + foreach my $line (<$fh>) { + $stuff_started = 1 if $line !~ /^#/; + next if !$stuff_started; + next if $line =~ /^use (warnings|strict|lib|Bugzilla)[^\w:]/; + if ($line =~ /^(?:use|require)\b/) { + push(@modules, $line); + } + else { + push(@code, $line); + } + } + close $fh; + return (\@modules, \@code); +} + +sub code_files_to_subroutines { + my ($dir) = @_; + + my @dir_files = glob("$dir/code/*.pl"); + my (@all_modules, @subroutines); + foreach my $file (@dir_files) { + next if $file =~ /install-requirements/; + print "Moving $file code into Extension.pm...\n"; + my ($modules, $code) = process_code_file($file); + my @code_lines = map { " $_" } @$code; + my $code_string = join('', @code_lines); + $code_string =~ s/Bugzilla->hook_args/\$args/g; + $code_string =~ s/my\s+\$args\s+=\s+\$args;//gs; + chomp($code_string); + push(@all_modules, @$modules); + my $name = basename($file); + $name =~ s/-/_/; + $name =~ s/\.pl$//; + + my $subroutine = <<END; +sub $name { + my (\$self, \$args) = \@_; +$code_string +} +END + push(@subroutines, $subroutine); + } + + my %seen_modules = map { trim($_) => 1 } @all_modules; + my $module_string = join("\n", sort keys %seen_modules); + my $subroutine_string = join("\n", @subroutines); + return ($module_string, $subroutine_string); +} + +sub move_template_hooks { + my ($dir) = @_; + foreach my $lang (glob("$dir/template/*")) { + next if !_file_matters($lang); + mkpath("$lang/hook") || die "$lang/hook: $!"; + # Hooks can be in all sorts of weird places, including + # template/default/hook. + foreach my $hooks_container ($lang, "$lang/default/hook") { + foreach my $file (glob("$hooks_container/*")) { + next if !_file_matters($file, 1); + my $dirname = basename($file); + print "Moving $file to $lang/hook/$dirname...\n"; + rename($file, "$lang/hook/$dirname") || die "move failed: $!"; + } + } + } +} + +sub _file_matters { + my ($path, $tmpl) = @_; + my @ignore = qw(default custom CVS hook); + my $file = basename($path); + return 0 if grep(lc($_) eq lc($file), @ignore); + # Hidden files + return 0 if $file =~ /^\./; + if ($tmpl) { + return 1 if $file =~ /\.tmpl$/; + } + return 0 if !-d $path; + return 1; +} + +__END__ + +=head1 NAME + +extension-convert.pl - Convert extensions from the pre-3.6 format to the +3.6 format. + +=head1 SYNOPSIS + + contrib/extension-convert.pl name + + Converts an extension in the F<extensions/> directory into the new + extension layout for Bugzilla 3.6. diff --git a/extensions/BmpConvert/Extension.pm b/extensions/BmpConvert/Extension.pm index 29113bd08..ee08e04f0 100644 --- a/extensions/BmpConvert/Extension.pm +++ b/extensions/BmpConvert/Extension.pm @@ -29,10 +29,10 @@ use Image::Magick; our $VERSION = '1.0'; sub attachment_process_data { - my ($self, $params) = @_; - return unless $params->{attributes}->{mimetype} eq 'image/bmp'; + my ($self, $args) = @_; + return unless $args->{attributes}->{mimetype} eq 'image/bmp'; - my $data = ${$params->{data}}; + my $data = ${$args->{data}}; my $img = Image::Magick->new(magick => 'bmp'); # $data is a filehandle. @@ -49,9 +49,9 @@ sub attachment_process_data { } undef $img; - ${$params->{data}} = $data; - $params->{attributes}->{mimetype} = 'image/png'; - $params->{attributes}->{filename} =~ s/^(.+)\.bmp$/$1.png/i; + ${$args->{data}} = $data; + $args->{attributes}->{mimetype} = 'image/png'; + $args->{attributes}->{filename} =~ s/^(.+)\.bmp$/$1.png/i; } __PACKAGE__->NAME; diff --git a/extensions/Example/Extension.pm b/extensions/Example/Extension.pm index 290867e0d..425fdfb18 100644 --- a/extensions/Example/Extension.pm +++ b/extensions/Example/Extension.pm @@ -35,9 +35,9 @@ use Data::Dumper; our $VERSION = '1.0'; sub attachment_process_data { - my ($self, $params) = @_; - my $type = $params->{attributes}->{mimetype}; - my $filename = $params->{attributes}->{filename}; + my ($self, $args) = @_; + my $type = $args->{attributes}->{mimetype}; + my $filename = $args->{attributes}->{filename}; # Make sure images have the correct extension. # Uncomment the two lines below to make this check effective. @@ -45,44 +45,44 @@ sub attachment_process_data { my $format = $1; if ($filename =~ /^(.+)(:?\.[^\.]+)$/) { my $name = $1; - #$params->{attributes}->{filename} = "${name}.$format"; + #$args->{attributes}->{filename} = "${name}.$format"; } else { # The file has no extension. We append it. - #$params->{attributes}->{filename} .= ".$format"; + #$args->{attributes}->{filename} .= ".$format"; } } } sub auth_login_methods { - my ($self, $params) = @_; - my $modules = $params->{modules}; + my ($self, $args) = @_; + my $modules = $args->{modules}; if (exists $modules->{Example}) { $modules->{Example} = 'Bugzilla/Extension/Example/Auth/Login.pm'; } } sub auth_verify_methods { - my ($self, $params) = @_; - my $modules = $params->{modules}; + my ($self, $args) = @_; + my $modules = $args->{modules}; if (exists $modules->{Example}) { $modules->{Example} = 'Bugzilla/Extension/Example/Auth/Verify.pm'; } } sub bug_columns { - my ($self, $params) = @_; - my $columns = $params->{'columns'}; + my ($self, $args) = @_; + my $columns = $args->{'columns'}; push (@$columns, "delta_ts AS example") } sub bug_end_of_create { - my ($self, $params) = @_; + my ($self, $args) = @_; # This code doesn't actually *do* anything, it's just here to show you # how to use this hook. - my $bug = $params->{'bug'}; - my $timestamp = $params->{'timestamp'}; + my $bug = $args->{'bug'}; + my $timestamp = $args->{'timestamp'}; my $bug_id = $bug->id; # Uncomment this line to see a line in your webserver's error log whenever @@ -91,11 +91,11 @@ sub bug_end_of_create { } sub bug_end_of_create_validators { - my ($self, $params) = @_; + my ($self, $args) = @_; # This code doesn't actually *do* anything, it's just here to show you # how to use this hook. - my $bug_params = $params->{'params'}; + my $bug_params = $args->{'params'}; # Uncomment this line below to see a line in your webserver's error log # containing all validated bug field values every time you file a bug. @@ -107,11 +107,11 @@ sub bug_end_of_create_validators { } sub bug_end_of_update { - my ($self, $params) = @_; + my ($self, $args) = @_; # This code doesn't actually *do* anything, it's just here to show you # how to use this hook. - my ($bug, $timestamp, $changes) = @$params{qw(bug timestamp changes)}; + my ($bug, $timestamp, $changes) = @$args{qw(bug timestamp changes)}; foreach my $field (keys %$changes) { my $used_to_be = $changes->{$field}->[0]; @@ -140,19 +140,19 @@ sub bug_end_of_update { } sub bug_fields { - my ($self, $params) = @_; + my ($self, $args) = @_; - my $fields = $params->{'fields'}; + my $fields = $args->{'fields'}; push (@$fields, "example") } sub bug_format_comment { - my ($self, $params) = @_; + my ($self, $args) = @_; # This replaces every occurrence of the word "foo" with the word # "bar" - my $regexes = $params->{'regexes'}; + my $regexes = $args->{'regexes'}; push(@$regexes, { match => qr/\bfoo\b/, replace => 'bar' }); # And this links every occurrence of the word "bar" to example.com, @@ -168,48 +168,48 @@ sub bug_format_comment { # Used by bug_format_comment--see its code for an explanation. sub _replace_bar { - my $params = shift; + my $args = shift; # $match is the first parentheses match in the $bar_match regex # in bug-format_comment.pl. We get up to 10 regex matches as # arguments to this function. - my $match = $params->{matches}->[0]; + my $match = $args->{matches}->[0]; # Remember, you have to HTML-escape any data that you are returning! $match = html_quote($match); return qq{<a href="http://example.com/">$match</a>}; }; sub buglist_columns { - my ($self, $params) = @_; + my ($self, $args) = @_; - my $columns = $params->{'columns'}; + my $columns = $args->{'columns'}; $columns->{'example'} = { 'name' => 'bugs.delta_ts' , 'title' => 'Example' }; } sub colchange_columns { - my ($self, $params) = @_; + my ($self, $args) = @_; - my $columns = $params->{'columns'}; + my $columns = $args->{'columns'}; push (@$columns, "example") } sub config { - my ($self, $params) = @_; + my ($self, $args) = @_; - my $config = $params->{config}; + my $config = $args->{config}; $config->{Example} = "Bugzilla::Extension::Example::Config"; } sub config_add_panels { - my ($self, $params) = @_; + my ($self, $args) = @_; - my $modules = $params->{panel_modules}; + my $modules = $args->{panel_modules}; $modules->{Example} = "Bugzilla::Extension::Example::Config"; } sub config_modify_panels { - my ($self, $params) = @_; + my ($self, $args) = @_; - my $panels = $params->{panels}; + my $panels = $args->{panels}; # Add the "Example" auth methods. my $auth_params = $panels->{'auth'}->{params}; @@ -221,11 +221,11 @@ sub config_modify_panels { } sub flag_end_of_update { - my ($self, $params) = @_; + my ($self, $args) = @_; # This code doesn't actually *do* anything, it's just here to show you # how to use this hook. - my $flag_params = $params; + my $flag_params = $args; my ($object, $timestamp, $old_flags, $new_flags) = @$flag_params{qw(object timestamp old_flags new_flags)}; my ($removed, $added) = diff_arrays($old_flags, $new_flags); @@ -244,14 +244,14 @@ sub flag_end_of_update { } sub install_before_final_checks { - my ($self, $params) = @_; - print "Install-before_final_checks hook\n" unless $params->{silent}; + my ($self, $args) = @_; + print "Install-before_final_checks hook\n" unless $args->{silent}; } sub mailer_before_send { - my ($self, $params) = @_; + my ($self, $args) = @_; - my $email = $params->{email}; + my $email = $args->{email}; # If you add a header to an email, it's best to start it with # 'X-Bugzilla-<Extension>' so that you don't conflict with # other extensions. @@ -259,10 +259,10 @@ sub mailer_before_send { } sub object_before_create { - my ($self, $params) = @_; + my ($self, $args) = @_; - my $class = $params->{'class'}; - my $object_params = $params->{'params'}; + my $class = $args->{'class'}; + my $object_params = $args->{'params'}; # Note that this is a made-up class, for this example. if ($class->isa('Bugzilla::ExampleObject')) { @@ -273,9 +273,9 @@ sub object_before_create { } sub object_before_set { - my ($self, $params) = @_; + my ($self, $args) = @_; - my ($object, $field, $value) = @$params{qw(object field value)}; + my ($object, $field, $value) = @$args{qw(object field value)}; # Note that this is a made-up class, for this example. if ($object->isa('Bugzilla::ExampleObject')) { @@ -285,10 +285,10 @@ sub object_before_set { } sub object_end_of_create_validators { - my ($self, $params) = @_; + my ($self, $args) = @_; - my $class = $params->{'class'}; - my $object_params = $params->{'params'}; + my $class = $args->{'class'}; + my $object_params = $args->{'params'}; # Note that this is a made-up class, for this example. if ($class->isa('Bugzilla::ExampleObject')) { @@ -299,10 +299,10 @@ sub object_end_of_create_validators { } sub object_end_of_set_all { - my ($self, $params) = @_; + my ($self, $args) = @_; - my $object = $params->{'class'}; - my $object_params = $params->{'params'}; + my $object = $args->{'class'}; + my $object_params = $args->{'params'}; # Note that this is a made-up class, for this example. if ($object->isa('Bugzilla::ExampleObject')) { @@ -314,10 +314,10 @@ sub object_end_of_set_all { } sub object_end_of_update { - my ($self, $params) = @_; + my ($self, $args) = @_; my ($object, $old_object, $changes) = - @$params{qw(object old_object changes)}; + @$args{qw(object old_object changes)}; # Note that this is a made-up class, for this example. if ($object->isa('Bugzilla::ExampleObject')) { @@ -329,9 +329,9 @@ sub object_end_of_update { } sub page_before_template { - my ($self, $params) = @_; + my ($self, $args) = @_; - my ($vars, $page) = @$params{qw(vars page_id)}; + my ($vars, $page) = @$args{qw(vars page_id)}; # You can see this hook in action by loading page.cgi?id=example.html if ($page eq 'example.html') { @@ -340,19 +340,19 @@ sub page_before_template { } sub product_confirm_delete { - my ($self, $params) = @_; + my ($self, $args) = @_; - my $vars = $params->{vars}; + my $vars = $args->{vars}; $vars->{'example'} = 1; } sub sanitycheck_check { - my ($self, $params) = @_; + my ($self, $args) = @_; my $dbh = Bugzilla->dbh; my $sth; - my $status = $params->{'status'}; + my $status = $args->{'status'}; # Check that all users are Australian $status->('example_check_au_user'); @@ -374,12 +374,12 @@ sub sanitycheck_check { } sub sanitycheck_repair { - my ($self, $params) = @_; + my ($self, $args) = @_; my $cgi = Bugzilla->cgi; my $dbh = Bugzilla->dbh; - my $status = $params->{'status'}; + my $status = $args->{'status'}; if ($cgi->param('example_repair_au_user')) { $status->('example_repair_au_user_start'); @@ -393,9 +393,9 @@ sub sanitycheck_repair { } sub template_before_create { - my ($self, $params) = @_; + my ($self, $args) = @_; - my $config = $params->{'config'}; + my $config = $args->{'config'}; # This will be accessible as "example_global_variable" in every # template in Bugzilla. See Bugzilla/Template.pm's create() function # for more things that you can set. @@ -403,9 +403,9 @@ sub template_before_create { } sub template_before_process { - my ($self, $params) = @_; + my ($self, $args) = @_; - my ($vars, $file, $template) = @$params{qw(vars file template)}; + my ($vars, $file, $template) = @$args{qw(vars file template)}; $vars->{'example'} = 1; @@ -415,16 +415,16 @@ sub template_before_process { } sub webservice { - my ($self, $params) = @_; + my ($self, $args) = @_; - my $dispatch = $params->{dispatch}; + my $dispatch = $args->{dispatch}; $dispatch->{Example} = "Bugzilla::Extension::Example::WebService"; } sub webservice_error_codes { - my ($self, $params) = @_; + my ($self, $args) = @_; - my $error_map = $params->{error_map}; + my $error_map = $args->{error_map}; $error_map->{'example_my_error'} = 10001; } |