# ==================================================================
# Gossamer Threads Module Library - http://gossamer-threads.com/
#
#   GT::Plugins
#   Author  : Alex Krohn
#   CVS Info : 087,071,086,086,085      
#   $Id: Wizard.pm,v 1.34 2005/04/14 07:43:48 brewt Exp $
#
# Copyright (c) 2004 Gossamer Threads Inc.  All Rights Reserved.
# ==================================================================
#
# Description: A web based admin to install/uninstall/edit plugins.
#

package GT::Plugins::Wizard;
# ==================================================================
use strict;
use vars qw/@ISA $ATTRIBS $ERROR_MESSAGE $VERSION $DEBUG/;
use GT::Base;
use GT::Plugins;
use GT::Tar;
use GT::Dumper;

$ERROR_MESSAGE = 'GT::Plugins';
$DEBUG   = 0;
$VERSION = sprintf "%d.%03d", q$Revision: 1.34 $ =~ /(\d+)\.(\d+)/;
$ATTRIBS = {
    prefix         => '',
    cgi            => undef,
    initial_indent => '    ',
    tpl_root       => '.',
    tpl_prefix     => '',
    plugin_dir     => undef,
    plugin         => undef,
    tar            => undef,
    prog_ver       => undef,
    install_header => undef,
    dirs           => {},
    oo             => undef
};
@ISA = qw/GT::Base/;

sub process {
# ----------------------------------------------------------------
# Determines what to do based on cgi input, and return a hash
# content => data for printing by outside application.
#
    my $self = shift;
    ref $self->{cgi} and UNIVERSAL::can($self->{cgi}, 'param') or return $self->error('BADARGS', 'FATAL', "no cgi object passed to wizard");
    defined $self->{plugin_dir} and -d $self->{plugin_dir}     or return $self->error('BADARGS', 'FATAL', "missing/invalid plugin dir passed to wizard");

# Figure out what to do.
    my $action = $self->{cgi}->param('plugin_wiz_do') || '';
    my $vars   = {};
    my $page   = 'plugin_wizard_step1.html';
    my $plugin = $self->{cgi}->param('plugin_name');
    $self->load_plugin($plugin) if ($plugin);

    CASE: {
# Meta Information
        ($action eq 'step2') and do {
            $vars = $self->_validate_step1();
            if (defined $vars->{error}) { $page = 'plugin_wizard_step1.html'; last CASE }
            $vars = $self->_load_step2();
            $page = 'plugin_wizard_step2.html';
            last CASE;
        };
# Plugin Hooks
        ($action eq 'step3') and do {
            $vars = $self->_validate_step2() unless ($self->{cgi}->param('skip_validate'));
            if (defined $vars->{error})   { $page = 'plugin_wizard_step2.html'; last CASE }
            $vars = $self->_load_step3();
            $page = 'plugin_wizard_step3.html';
            last CASE;
        };
# Admin Menu Options.
        ($action eq 'step4') and do {
            $vars = $self->_validate_step3();
            if (defined $vars->{error})   { $page = 'plugin_wizard_step3.html'; last CASE }
            if (defined $vars->{results}) { $page = 'plugin_wizard_step3.html'; last CASE }
            $vars = $self->_load_step4();
            $page = 'plugin_wizard_step4.html';
            last CASE;
        };
# User Options.
        ($action eq 'step5') and do {
            $vars = $self->_validate_step4();
            if (defined $vars->{error})   { $page = 'plugin_wizard_step4.html'; last CASE }
            if (defined $vars->{results}) { $page = 'plugin_wizard_step4.html'; last CASE }
            $vars = $self->_load_step5();
            $page = 'plugin_wizard_step5.html';
            last CASE;
        };
# Included Files.
        ($action eq 'step6') and do {
            $vars = $self->_validate_step5();
            if (defined $vars->{error})   { $page = 'plugin_wizard_step5.html'; last CASE }
            if (defined $vars->{results}) { $page = 'plugin_wizard_step5.html'; last CASE }
            $vars = $self->_load_step6();
            $page = 'plugin_wizard_step6.html';
            last CASE;
        };
# All Done.
        ($action eq 'step7') and do {
            $vars = $self->_validate_step6();
            if (defined $vars->{error})   { $page = 'plugin_wizard_step6.html'; last CASE }
            if (defined $vars->{results}) { $page = 'plugin_wizard_step6.html'; last CASE }
            $vars = $self->_load_step7();
            $page = 'plugin_wizard_step7.html';
            last CASE;
        };
# Create the plugin and finish.
        ($action eq 'create') and do {
            $vars = $self->_validate_step7();
            if (defined $vars->{error})   { $page = 'plugin_wizard_step7.html'; last CASE }
            if (defined $vars->{results}) { $page = 'plugin_wizard_step7.html'; last CASE }
            $vars = $self->_create_install();
            if (defined $vars->{error})   { $page = 'plugin_wizard_step7.html'; last CASE }
            if (defined $vars->{results}) { $page = 'plugin_wizard_step7.html'; last CASE }
            $vars = $self->_create_code();
            if (defined $vars->{error})   { $page = 'plugin_wizard_step7.html'; last CASE }
            if (defined $vars->{results}) { $page = 'plugin_wizard_step7.html'; last CASE }
            $page = 'plugin_wizard_step8.html';
            last CASE;
        };

# Get a list of plugins that can be edited.
        $vars->{edit} = $self->_list_editable;
    }

    return $self->page($page, $vars);
}

sub page {
# ----------------------------------------------------------------
# Returns a content => parsed_page hash ref.
#
    my ($self, $page, $vars) = @_;
    my $cgi     = $self->{cgi}->get_hash;
    for my $key (keys %$cgi) { exists $vars->{$key} or $vars->{$key} = $cgi->{$key}; }
    my $contents = GT::Template->parse(
        $self->{tpl_prefix} . $page,
        $vars,
        { root => $self->{tpl_root} }
    ) or return;
    return { content => \$contents };
}

sub load_plugin {
# ----------------------------------------------------------------
# Loads a plugin.
#
    my ($self, $plugin_name) = @_;
    $self->{plugin}->{name} = $plugin_name;
    return unless (defined $plugin_name and $plugin_name =~ /^\w{2,20}$/);

    $self->{tar} = $self->_load_tar;
    $self->_load_plugin;
    return 1;
}

sub save_plugin {
# -------------------------------------------------------------------
# Saves the plugin back to disk.
#
    my $self = shift;
    my $wizard = $self->{tar}->get_file('Wizard.pm');
    if (! $wizard) {
        $self->{tar}->add_data(name => 'Wizard.pm', body => $self->_create_wizard);
    }
    else {
        $wizard->body($self->_create_wizard);
    }
    return $self->{tar}->write;
}

sub _get_hook_params {
# ------------------------------------------------------------------------------
    my $hook  = shift;
    my $param = shift;
    my %results;
    for my $e (@$hook) {
        my $val = ref $e->{$param} ? join(", ", @{$e->{$param}}) : $e->{$param};
        $results{$val}++;
    }
    return sort keys %results;
}

sub _validate_step1 {
# -------------------------------------------------------------------
# Checks that the plugin name is valid.
#
    my $self = shift;
    my $name = $self->{cgi}->param('plugin_name');
    $name or return { error => "Please enter a valid plugin name." };
    $name =~ /^\w{2,20}$/ or return {
        error => "Plugin names must be only letters and numbers, and be between 2 and 20 characters."
    };
    $self->save_plugin    or return { error => $GT::Plugins::error };
    return { plugin_name => $name };
}

sub _load_step2 {
# -------------------------------------------------------------------
# Preloads vars for meta information.
#
    my $self = shift;
    return defined $self->{plugin}->{meta}->{prog_ver}
        ? $self->{plugin}->{meta}
        : { %{$self->{plugin}->{meta}}, prog_ver => $self->{prog_ver} };
}

sub _validate_step2 {
# -------------------------------------------------------------------
# Validates the meta information.
#
    my $self = shift;
    my $version = $self->{cgi}->param('version');
    $version or return { error => "Please make sure you enter a version, perhaps start with 0.0.1 to begin." };
    $version =~ /^[\d\.]+$/ or return { error => "Version numbers should contain only numbers and periods." };

    my $author = $self->{cgi}->param('author');
    $author or return { error => "Please make sure you enter an author." };

    my $url = $self->{cgi}->param('url');

    my $license = $self->{cgi}->param('license');
    $license or return { error => "Please make sure you enter in a license style." };

    my $prog_ver = $self->{cgi}->param('prog_ver');
    $prog_ver or return {
        error => 'Please enter a program version that your plugin will require. Set to 1 for all versions.  ' .
            'This is useful to ensure the plugin user has the required version before using the plugin.'
    };

    my $description = $self->{cgi}->param('description');

    $self->{plugin}->{meta} = {
        version     => $version,
        author      => $author,
        url         => $url,
        license     => $license,
        description => $description,
        prog_ver    => $prog_ver
    };

    $self->save_plugin or return { error => $GT::Plugins::error };

    return {};
}

sub _load_step3 {
# -------------------------------------------------------------------
# Preloads vars for hook information.
#
    my $self = shift;
    my $font = 'font face="Tahoma,Arial,Helvetica" size="2"';

# try to load the hook config file
    return { hooks => '' } unless defined $self->{plugin}->{hooks} and @{$self->{plugin}->{hooks}};

    my $output = qq~
<table border=1 width="100%" cellpadding=3 bordercolor="#C0C0C0" cellspacing=0><tr>
    <td><b><$font>Hook</font></b></td>
    <td><b><$font>Type</font></b></td>
    <td><b><$font>Code</font></b></td>
    <td><b><$font>Position</font></b></td>
  </tr>~;

    for my $hook (@{$self->{plugin}->{hooks}}) {
        my $id = join("|", @$hook);
        my ($name, $type, $code, $position) = @$hook;
        $output .= qq~
  <tr>
    <td><$font><input type="checkbox" name="delete" value="$id"> $name</font></td>
    <td><$font>$type</font></td>
    <td><$font>$code</font></td>
    <td><$font>$position</font></td>
  </tr>~;
    }
    $output .= qq~
</table>
    ~;
    return { hooks => $output };
}

sub _validate_step3 {
# -------------------------------------------------------------------
# Validate any new hooks that were added.
#
    my $self = shift;
    $self->{plugin}->{hooks} ||= [];

# Remove unwanted hooks.
    my $results = '';
    if ($self->{cgi}->param('delete_btn')) {
        my @to_delete = $self->{cgi}->param('delete');
        for my $del_id (@to_delete) {
            my $i = 0;
            for my $hook (@{$self->{plugin}->{hooks}}) {
                my $id = join("|", @$hook);
                if ($id eq $del_id) {
                    $results .= "<li>Plugin hook " . $hook->[0] . " successfully removed.";
                    splice @{$self->{plugin}->{hooks}}, $i, 1;
                }
                $i++;
            }
        }
    }
# Add new hooks
    my $add_hook = $self->{cgi}->param('name');
    if ($add_hook) {
        my $add_code = $self->{cgi}->param('code');
        my $add_type = $self->{cgi}->param('type');
        my $add_pos  = $self->{cgi}->param('pos'); # Not used; future use?
        push @{$self->{plugin}->{hooks}}, [$add_hook, $add_type, $add_code, $add_pos];
        $results .= "<li>Plugin hook $add_hook successfully added.";
    }
    my $hooks = $self->_load_step3;
    if (! $results and $self->{cgi}->param('add_btn')) {
        return { error => "Please fill out the add form completely.", hooks => $hooks->{hooks} };
    }
    if (! $results and $self->{cgi}->param('delete_btn')) {
        return { error => "Please select one or more hooks to delete.", hooks => $hooks->{hooks} };
    }
    $self->save_plugin or return { error => $GT::Plugins::error };
    if ($results) {
        return { results => $results, hooks => $hooks->{hooks} };
    }
    return {};
}

sub _load_step4 {
# -------------------------------------------------------------------
# Preloads vars for admin menu options.
#
    my $self = shift;
    return { menu => '' } unless $self->{plugin}->{menu} and @{$self->{plugin}->{menu}};
    my $font   = 'font face="Tahoma,Arial,Helvetica" size="2"';
    my $output = qq~
<table border=1 width="100%" cellpadding=3 bordercolor="#C0C0C0" cellspacing=0><tr>
    <td><b><$font>Name</font></b></td>
    <td><b><$font>URL</font></b></td>
    </tr>~;

    for my $menu (@{$self->{plugin}->{menu}}) {
        my ($name, $url) = @$menu;
        $output .= qq~
<tr>
    <td><$font><input type="checkbox" name="delete" value="$name"> $name</font></td>
    <td><$font><a href="$url" target="_blank">$url</a></font></td>
    </tr>~;
    }
    $output .= qq~
</table>
    ~;
    return { menu => $output };
}

sub _validate_step4 {
# -------------------------------------------------------------------
# Validate any new menu that were added.
#
    my $self = shift;
    $self->{plugin}->{menu} ||= [];

# Remove unwanted menu.
    my $results = '';
    if ($self->{cgi}->param('delete_btn')) {
        my @to_delete = $self->{cgi}->param('delete');
        for my $del_id (@to_delete) {
            my $i = 0;
            for my $menu (@{$self->{plugin}->{menu}}) {
                my ($name, $url) = @$menu;
                if ($name eq $del_id) {
                    splice @{$self->{plugin}->{menu}}, $i, 1;
                    $results .= "<li>Menu Option " . $name . " successfully removed.";
                }
                $i++;
            }
        }
    }

# Add new menu
    my $add_name = $self->{cgi}->param('name');
    if ($add_name) {
        my $add_url  = $self->{cgi}->param('url');
        $self->{plugin}->{menu} ||= [];
        push @{$self->{plugin}->{menu}}, [$add_name, $add_url];
        $results .= "<li>Menu Option $add_name successfully added.";
    }

    my $menu = $self->_load_step4;
    if (! $results and $self->{cgi}->param('add_btn')) {
        return { error => "Please fill out the add form completely.", menu => $menu->{menu} };
    }
    if (! $results and $self->{cgi}->param('delete_btn')) {
        return { error => "Please select one or more admin menu to delete.", menu => $menu->{menu} };
    }

    $self->save_plugin or return { error => $GT::Plugins::error };
    if ($results) {
        return { results => $results, menu => $menu->{menu} };
    }
    return {};
}

sub _load_step5 {
# -------------------------------------------------------------------
# Preloads vars for user options.
#
    my $self = shift;
    return { user => '' } unless (defined $self->{plugin}->{user} and @{$self->{plugin}->{user}});
    my $font   = 'font face="Tahoma,Arial,Helvetica" size="2"';
    my $output = qq~
    <table border=1 width="100%" cellpadding=3 bordercolor="#C0C0C0" cellspacing=0><tr>
        <td><b><$font>Name</font></b></td>
        <td><b><$font>Value</font></b></td>
        <td><b><$font>Instructions</font></b></td>
        <td><b><$font>Form Type</font></b></td>
        <td><b><$font>Form Names</font></b></td>
        <td><b><$font>Form Value</font></b></td>
      </tr>~;

    for my $opt (@{$self->{plugin}->{user}}) {
        my ($name, $val, $instructions, $form_type, $form_names, $form_values ) = @$opt;
        $form_values = @$form_values
            ? "<ul>" . join("", map { "<li>" . $self->{cgi}->html_escape($_) . "</li>" } @$form_values) . "</ul>"
            : "&nbsp;";
        $form_names  = @$form_names
            ? "<ul>" . join("", map { "<li>" . $self->{cgi}->html_escape($_) . "</li>" } @$form_names) . "</ul>"
            : "&nbsp;";
        my $ins = $self->{cgi}->html_escape($instructions);
        $val = $self->{cgi}->html_escape($val);
        $output .= qq~
      <tr>
        <td valign="top"><$font><input type="checkbox" name="delete" value="$name"> $name</font></td>
        <td valign="top"><$font>$val</font></td>
        <td valign="top"><$font>$ins&nbsp;</font></td>
        <td valign="top"><$font>$form_type</font></td>
        <td valign="top"><$font>$form_names</font></td>
        <td valign="top"><$font>$form_values</font></td>
      </tr>
~;
    }
    $output .= qq~</table>~;

    return { user => $output };
}

sub _validate_step5 {
# -------------------------------------------------------------------
# Validate any user options that were added.
#
    my $self = shift;
    $self->{plugin}->{user} ||= [];

# Remove unwanted user options.
    my $results = '';
    if ($self->{cgi}->param('delete_btn')) {
        my @to_delete = $self->{cgi}->param('delete');
        for my $del_id (@to_delete) {
            my $i = 0;
            for my $opt (@{$self->{plugin}->{user}}) {
                my ($name, $val, $ins) = @$opt;
                if ($name eq $del_id) {
                    splice @{$self->{plugin}->{user}}, $i, 1;
                    $results .= "<li>User Option " . $name . " successfully removed.";
                }
                $i++;
            }
        }
    }

# Add new user option
    my $add_name = $self->{cgi}->param('name');
    if ($add_name) {
        my $add_val     = $self->{cgi}->param('value');
        my $add_ins     = $self->{cgi}->param('instructions');
        my $form_names  = [ grep $_, split /(?:\r|\n)+/, $self->{cgi}->param('form_names') ];
        my $form_values = [ grep $_, split /(?:\r|\n)+/, $self->{cgi}->param('form_values') ];
        my $form_type   = $self->{cgi}->param('form_type');
        push @{$self->{plugin}->{user}}, [ $add_name, $add_val, $add_ins, $form_type, $form_names, $form_values ];
        $results .= "<li>User Option $add_name successfully added.";
    }
    my $user = $self->_load_step5;
    if (! $results and $self->{cgi}->param('add_btn')) {
        return { error => "Please fill out the add form completely.", user => $user->{user} };
    }
    if (! $results and $self->{cgi}->param('delete_btn')) {
        return { error => "Please select one or more user option to delete.", user => $user->{user} };
    }
    $self->save_plugin or return { error => $GT::Plugins::error };
    if ($results) {
        return { results => $results, user => $user->{user} };
    }
    return {};
}

sub _load_step6 {
# -------------------------------------------------------------------
# Preloads any user included files.
#
    my $self = shift;
    return { files => '' } unless (defined $self->{plugin}->{files} and @{$self->{plugin}->{files}});

    my $font   = 'font face="Tahoma,Arial,Helvetica" size="2"';
    my $output = qq~
<table border=1 width="100%" cellpadding=3 bordercolor="#C0C0C0" cellspacing=0><tr>
    <td><b><$font>Filename</font></b></td>
    <td><b><$font>Location</font></b></td>
    </tr>~;

    my %seen;
    for my $file (@{$self->{plugin}->{files}}) {
        my ($name, $location) = @$file;
        my $id = join("|", @$file);
        next if $name eq "$self->{plugin}->{name}.pm";
        if (exists $self->{dirs}->{$location}) {
            $location = $self->{dirs}->{$location};
        }
        $seen{$name}++;
        $output .= qq~
<tr>
    <td><$font><input type="checkbox" name="delete" value="$id"> $name</font></td>
    <td><$font>$location</font></td>
    </tr>~;
    }
    my $files = $self->{tar}->files;
    for my $file (@$files) {
        my $name = $file->name;
        my $id = $name . '|';

        next if $seen{$name} or $name eq 'Wizard.pm' or $name eq 'Install.pm' or $name eq "$self->{plugin}->{name}.pm";

        push @{$self->{plugin}->{files}}, [$name, ''];
        $output .= qq~
<tr>
    <td><$font><input type="checkbox" name="delete" value="$id"> $name</font></td>
    <td><$font>Unknown (not added in Wizard)</font></td>
    </tr>~;
    }
    $output .= qq~
</table>
    ~;
    return { files => $output };
}

sub _validate_step6 {
# -------------------------------------------------------------------
# Receives files and stores them in the tar file.
#
    my $self = shift;
    my $results = '';
    $self->{plugin}->{files} ||= [];

# Remove any existing files.
    if ($self->{cgi}->param('delete_btn')) {
        my @to_delete = $self->{cgi}->param('delete');
        for my $del_id (@to_delete) {
            my $i = 0;
            for my $file (@{$self->{plugin}->{files}}) {
                my $id = join("|", @$file);
                if ($id eq $del_id) {
                    my $name = $file->[0];
                    $self->{tar}->remove_file($name);
                    $self->{tar}->write;
                    splice @{$self->{plugin}->{files}}, $i, 1;
                    $results .= "<li>File " . $name . " successfully removed.";
                }
                $i++;
            }
        }
    }

# Add any new attachments.
    my $filename = $self->{cgi}->param('name');
    if ($filename) {
        my $filehandle = $self->{cgi}->param('file');
        my $body       = $self->{cgi}->param('add_body');
        my $location   = $self->{cgi}->param('location');
        if (ref $filehandle) {
            $body = '';
            my ($buffer, $read);
            while ($read = read($filehandle, $buffer, 4096)) {
                $body .= $buffer;
            }
        }
        $body ||= ' ';
        $body =~ s/\r//g;
        push @{$self->{plugin}->{files}}, [$filename, $location];
        my $res = $self->{tar}->add_data(name => $filename, body => $body);
        $results .= "File $filename attached successfully.";
    }
    my $file = $self->_load_step6;
    $self->save_plugin or return { error => $GT::Plugins::error };

    if (! $results and $self->{cgi}->param('add_btn')) {
        return { error => "Please fill out the add form completely.", files => $file->{files} };
    }
    if (! $results and $self->{cgi}->param('delete_btn')) {
        return { error => "Please select one or more file to delete.", files => $file->{files} };
    }
    if ($results) {
        return { results => $results, files => $file->{files} };
    }
    return {};
}

sub _load_step7 {
# -------------------------------------------------------------------
# Fetches the install/uninstall message.
#
    my $self = shift;
    return {
        install        => $self->{plugin}->{install},
        uninstall      => $self->{plugin}->{uninstall},
        install_code   => $self->{plugin}->{install_code},
        uninstall_code => $self->{plugin}->{uninstall_code}
    };
}

sub _validate_step7 {
# -------------------------------------------------------------------
# Saves the install/uninstall message.
#
    my $self = shift;
    $self->{plugin}->{install}        = $self->{cgi}->param('install');
    $self->{plugin}->{uninstall}      = $self->{cgi}->param('uninstall');
    $self->{plugin}->{install_code}   = $self->{cgi}->param('install_code');
    $self->{plugin}->{uninstall_code} = $self->{cgi}->param('uninstall_code');
    $self->save_plugin or return { error => $GT::Plugins::error };
    return {};
}

sub _create_code {
# -------------------------------------------------------------------
# Creates the code file.
#
    my $self = shift;
    my $output = '';
    my $time = localtime();
    my $version = $self->{plugin}->{meta}->{version} || 0;
    $self->{install_header} ||= '';
    my $stubs = $self->_create_stubs;

    my $plugin_pkg = $self->{prefix} . $self->{plugin}->{name};
    if (index($plugin_pkg, 'Plugins::') < 0) {
        $plugin_pkg = 'Plugins::' . $plugin_pkg;
    }

    $output = <<END_OF_PLUGIN;
# ==================================================================
# $plugin_pkg - Auto Generated Program Module
#
#   $plugin_pkg
#   Author  : $self->{plugin}->{meta}->{author}
#   Version : $version
#   Updated : $time
#
# ==================================================================
#

package $plugin_pkg;
# ==================================================================

$self->{initial_indent}use strict;
$self->{initial_indent}use GT::Base;
$self->{initial_indent}use GT::Plugins qw/STOP CONTINUE/;
$self->{initial_indent}$self->{install_header}

# Inherit from base class for debug and error methods
$self->{initial_indent}\@${plugin_pkg}::ISA = qw(GT::Base);

# Your code begins here.
$stubs

# Always end with a 1.
1;
END_OF_PLUGIN
    my $file = $self->{tar}->get_file($self->{plugin}->{name} . '.pm');
    if ($file) {
        my $overwrite = $self->{cgi}->param('overwrite');
        my $skip = $self->{cgi}->param('skip');
        if (! $overwrite and ! $skip) {
            return { error => "Overwrite the existing $self->{plugin}->{name}.pm: <br><input type=submit name=overwrite value='Yes, Overwrite it!'> <input type=submit name=skip value='No, Leave it Alone'>" };
        }
        $file->body($output) if ($overwrite);
    }
    else {
        $self->{tar}->add_data( name => $self->{plugin}->{name} . '.pm', body => $output );
    }
    $self->{tar}->write;
    return {};
}

sub _create_install {
# -------------------------------------------------------------------
# Creates the install.pm file.
#
    my $self   = shift;
    my $output = '';
    my $time   = localtime();
    my $version = $self->{plugin}->{meta}->{version} || 0;
    (my $qversion = $version) =~ s/(?=['\\])/\\/g;

    my $meta_dump = GT::Dumper->dump(var => '$META', data => $self->{plugin}->{meta});
    my $inst_mess = GT::Dumper->dump(var => 'my $inst_msg', data => $self->{plugin}->{install});
    my $uninst_mess = GT::Dumper->dump(var => 'my $uninst_msg', data => $self->{plugin}->{uninstall});
    my $install  = $self->_create_install_func;
    my $uninstall   = $self->_create_uninstall_func;

    for ($meta_dump, $inst_mess, $uninst_mess, $install, $uninstall) { s/\r//g }

    my $inst_code = $self->{plugin}->{install_code} || '';
    $inst_code =~ s/\r//g;
    $inst_code =~ s/;?$/;/; # Not ending a statement with a ; is valid Perl; handle it here.
    my $uninst_code = $self->{plugin}->{uninstall_code} || '';
    $uninst_code =~ s/\r//g;
    $uninst_code =~ s/;?$/;/; # Not ending a statement with a ; is valid Perl; handle it here.
    $self->{install_header} ||= '';

    my $plugin_pkg = $self->{prefix} . $self->{plugin}->{name};
    if (index($plugin_pkg, 'Plugins::') < 0) {
        $plugin_pkg = 'Plugins::' . $plugin_pkg;
    }

    $output    = <<END_OF_PLUGIN;
# ==================================================================
# $plugin_pkg - Auto Generated Install Module
#
#   $plugin_pkg
#   Author  : $self->{plugin}->{meta}->{author}
#   Version : $version
#   Updated : $time
#
# ==================================================================
#

package $plugin_pkg;
# ==================================================================
$self->{initial_indent}use strict;
$self->{initial_indent}use vars  qw/\$VERSION \$DEBUG \$NAME \$META/;
$self->{initial_indent}use GT::Base;
$self->{initial_indent}use GT::Plugins qw/STOP CONTINUE/;
$self->{initial_indent}$self->{install_header}

$self->{initial_indent}\$VERSION = '$qversion';
$self->{initial_indent}\$DEBUG   = 0;
$self->{initial_indent}\$NAME    = '$self->{plugin}->{name}';
# Inhert from base class for debug and error methods
$self->{initial_indent}\@${plugin_pkg}::ISA = qw(GT::Base);

$self->{initial_indent}$meta_dump

sub pre_install {
# -----------------------------------------------------------------------------
# This function displays an HTML formatted message that will display any
# instructions/information to the user before they install the plugin.
#
    $inst_mess
    return \$inst_msg;
}

sub pre_uninstall {
# -----------------------------------------------------------------------------
# This function displays an HTML formatted message that will display any
# instructions/information to the user before they remove the plugin.
#
    $uninst_mess
    return \$uninst_msg;
}

sub install {
# -----------------------------------------------------------------------------
# This function does the actual installation.  Its first argument is a plugin
# manager which you can use to register hooks, install files, add menu options,
# etc.  The second argument is a GT::Tar object which you can use to access any
# files in your plugin module.
#
# You should return an HTML formatted string that will be displayed to the
# user.
#
# If there is an error, return undef, and set the error message in
# \$Plugins::$self->{prefix}$self->{plugin}->{name}::error
#
    my (\$mgr, \$tar) = \@_;
    $install
    $inst_code
    return "The plugin has been successfully installed!";
}

sub uninstall {
# -----------------------------------------------------------------------------
# This function removes the plugin.  Its first argument is also a plugin
# manager which you can use to register hooks, install files, add menu options,
# etc. You should return an HTML formatted string that will be displayed to the
# user.
#
# If there is an error, return undef, and set the error message in
# \$${plugin_pkg}::error
#
    my \$mgr = shift;
    $uninstall
    $uninst_code
    return "The plugin has been successfully removed!";
}

1;
END_OF_PLUGIN
    my $file = $self->{tar}->get_file('Install.pm');
    if ($file) {
        $file->body($output);
    }
    else {
        $self->{tar}->add_data(name => 'Install.pm', body => $output);
    }
    $self->{tar}->write;
    return {};
}

sub _esc {
# -------------------------------------------------------------------
    $_[0] =~ s/'/\\'/g;
    $_[0] =~ s/\n/\\\n/g;
    $_[0] =~ s/\r//g;
    return;
}

sub _create_install_func {
# -------------------------------------------------------------------
# Creates the install function based on everything we know.
#
    my $self = shift;
    my $code = '';
    for my $hook (@{$self->{plugin}->{hooks}}) {
        my $val1 = $hook->[0]; $val1 =~ s/'/\\'/g;
        my $val2 = $hook->[1]; $val2 =~ s/'/\\'/g;
        my $val3 = $hook->[2]; $val3 =~ s/'/\\'/g;
        my $val4 = $hook->[3];

        $code .= qq~\n    \$mgr->install_hooks('$self->{plugin}->{name}', [['$val1', '$val2', '$val3', '$val4']]);~;
    }
    for my $menu (@{$self->{plugin}->{menu}}) {
        my $val1 = $menu->[0]; $val1 =~ s/'/\\'/g;
        my $val2 = $menu->[1]; $val2 =~ s/'/\\'/g;
        $code .= qq~\n    \$mgr->install_menu('$self->{plugin}->{name}', [['$val1', '$val2']]);~;
    }
    for my $user (@{$self->{plugin}->{user}}) {
        my $val1 = $user->[0]; $val1 =~ s/'/\\'/g;
        my $val2 = $user->[1]; _esc($val2);
        my $val3 = $user->[2]; _esc($val3);
        my $val4 = $user->[3]; _esc($val4);
        require GT::Dumper;
        my $val5 = GT::Dumper->dump(var => '', data => $user->[4], compress => 1 ); $val5 =~ s/;$//;
        my $val6 = GT::Dumper->dump(var => '', data => $user->[5], compress => 1 ); $val6 =~ s/;$//;
        my $val7 = $user->[6]; _esc($val7);
        $code .= qq~\n    \$mgr->install_options('$self->{plugin}->{name}', [['$val1', '$val2', '$val3', '$val4', $val5, $val6, '$val7']]);~;
    }
    if (@{$self->{plugin}->{files}}) {
        $code .= qq~

# Silence warnings
    \$GT::Tar::error ||= '';

# The following section will unarchive attached files into the proper location.
    my \$file;~;
    }
    for my $file (@{$self->{plugin}->{files}}) {
        my ($name, $loc) = @$file;
        next if ($name eq $self->{plugin}->{name} . '.pm');
        next if ($name eq 'Install.pm');
        my $path = '';
        if (exists $self->{dirs}->{$loc}) {
            $path = $self->{dirs}->{$loc};
        }

        my $plugin_pkg = $self->{prefix} . $self->{plugin}->{name};
        if (index($plugin_pkg, 'Plugins::') < 0) {
            $plugin_pkg = 'Plugins::' . $plugin_pkg;
        }
        $code .= qq~

# Copying $name to $path directory.
    \$file = \$tar->get_file('$name');
    \$file->name("$path/$name");
    \$file->write or return $plugin_pkg->error("Unable to extract file '$path/$name': \$GT::Tar::error", 'WARN');~;
    }
    return $code;
}

sub _create_uninstall_func {
# -------------------------------------------------------------------
# Creates the uninstall function based on everything we know.
#
    my $self = shift;
    my $code = '';
    for my $hook (@{$self->{plugin}->{hooks}}) {
        my $val1 = $hook->[0]; $val1 =~ s/'/\\'/g;
        my $val2 = $hook->[1]; $val2 =~ s/'/\\'/g;
        my $val3 = $hook->[2]; $val3 =~ s/'/\\'/g;
        my $val4 = $hook->[3];
        $code .= qq~\n    \$mgr->uninstall_hooks('$self->{plugin}->{name}', [['$val1', '$val2', '$val3', '$val4']]);~;
    }
    for my $menu (@{$self->{plugin}->{menu}}) {
        my $val1 = $menu->[0]; $val1 =~ s/'/\\'/g;
        my $val2 = $menu->[1]; $val2 =~ s/'/\\'/g;
        $code .= qq~    \$mgr->uninstall_menu('$self->{plugin}->{name}', [['$val1', '$val2']]);~;
    }
    for my $user (@{$self->{plugin}->{user}}) {
        my $val1 = $user->[0]; $val1 =~ s/'/\\'/g;
        my $val2 = $user->[1]; _esc($val2);
        my $val3 = $user->[2]; _esc($val3);
        my $val4 = $user->[3]; _esc($val4);
        require GT::Dumper;
        my $val5 = GT::Dumper->dump(var => '', data => $user->[4], compress => 1 ); $val5 =~ s/;$//;
        my $val6 = GT::Dumper->dump(var => '', data => $user->[5], compress => 1 ); $val6 =~ s/;$//;
        my $val7 = $user->[6]; _esc($val7);

        $code .= qq~\n    \$mgr->uninstall_options('$self->{plugin}->{name}', [['$val1', '$val2', '$val3', '$val4', $val5, $val6, '$val7']]);~;
    }
    return $code;
}

sub _create_stubs {
# -------------------------------------------------------------------
# Creates a subroutine stub for each hook.
#
    my $self = shift;
    my $code = '';
    if (@{$self->{plugin}->{hooks}}) {
        $code .= qq~

# PLUGIN HOOKS
# ===================================================================
~;
    }
    my %seen;
    for my $hook (@{$self->{plugin}->{hooks}}) {
        my $full_sub_name = $hook->[2];
        my ($sub_name) = $full_sub_name =~ /([^:]+)$/;
        next if $seen{$sub_name}++;
        my $hook_name = $hook->[0];
        $code .= qq~

sub $sub_name {
# -----------------------------------------------------------------------------
# This subroutine will be called whenever the hook '$hook_name' is run.  You
# should call @{[$self->{oo} || 'GT::Plugins']}->action(STOP) if you don't want the regular
# '$hook_name' code to run, otherwise the code will continue as normal.
#
    my (\@args) = \@_;

# Do something useful here

    return \@args;
}~;
    }
    if (@{$self->{plugin}->{menu}}) {
        $code .= qq~

# ADMIN MENU OPTIONS
# ===================================================================
~;
    }
    %seen = ();
    for my $menu (@{$self->{plugin}->{menu}}) {
        my $val1 = $menu->[0];
        my $val2 = $menu->[1];
        my ($func) = $val2 =~ /func=(\w+)/;
        next if $seen{$func}++;
        if ($func) {
            $code .= qq~
sub $func {
# -------------------------------------------------------------------
# This subroutine will be called whenever the user clicks on '$val1' in the
# admin menu.  Remember, you need to print your own HTTP header; to do so you
# can use:
#
#   print \$IN->header();
#

}~;
        }
    }
    return $code;
}

sub _create_wizard {
# -------------------------------------------------------------------
# Creates the Wizard.pm file which is used to load wizard information.
#
    my $self   = shift;
    my $output = '';
    my $time   = localtime();
    my $author = $self->{plugin}->{meta}->{author} || '';
    my $version = $self->{plugin}->{meta}->{version} || '';
    my $meta_dump = GT::Dumper->dump(var => '$META', data => $self->{meta});

    my $plugin_pkg = $self->{prefix} . $self->{plugin}->{name};
    if (index($plugin_pkg, 'Plugins::') < 0) {
        $plugin_pkg = 'Plugins::' . $plugin_pkg;
    }

    $output    = <<END_OF_PLUGIN;
# ==================================================================
# Auto Generated Plugin Configuration - Needed for Web Based Creator.
#
#   $plugin_pkg
#   Author  : $author
#   Version : $version
#   Updated : $time
#
# ==================================================================
#

package $plugin_pkg;
# ==================================================================
$self->{initial_indent}use strict;
$self->{initial_indent}use vars qw/\$WIZARD/;

END_OF_PLUGIN
    $output .= GT::Dumper->dump(var => '$WIZARD', data => $self->{plugin});
    $output .= "\n\n1;\n";
    return $output;
}

sub _load_tar {
# -------------------------------------------------------------------
# Loads a tar file.
#
    my $self = shift;
    my $file = $self->{plugin_dir} . "/Uninstalled/" . $self->{plugin}->{name} . ".tar";
    if (-e $file) {
        $self->{tar} = GT::Tar->open($file) or return $self->error('CANTLOAD', 'FATAL', $file, $GT::Tar::error);
    }
    else {
        $self->{tar} = new GT::Tar($file) or return $self->error('CANTLOAD', 'FATAL', $file, $GT::Tar::error);
    }
}

sub _load_plugin {
# -------------------------------------------------------------------
# Loads the meta information into self.
#
    my $self   = shift;
    my $wizard = $self->{tar}->get_file('Wizard.pm')
        or return $self->error('CANTLOAD', 'WARN', $self->{plugin}->{name}, "No Wizard.pm file found in tar!");

# Eval the install file.
    my $file = $wizard->body_as_string;
    {
        local ($@, $SIG{__DIE__}, $^W);
        eval "$file";
        if ($@) {
            return $self->error('CANTLOAD', 'WARN', $file, "Wizard.pm does not compile: $@");
        }
    }

# Load the information.

    my $plugin_pkg = $self->{prefix} . $self->{plugin}->{name};
    if (index($plugin_pkg, 'Plugins::') < 0) {
        $plugin_pkg = 'Plugins::' . $plugin_pkg;
    }

    my $var = $plugin_pkg . "::WIZARD";
    {
        no strict 'refs';
        $self->{plugin} = $$var;
    }

    return 1;
}

sub _list_editable {
# -------------------------------------------------------------------
# Returns a select list of plugins that can be edited by the wizard.
#
    my $self = shift;
    my $dir  = $self->{plugin_dir} . '/Uninstalled';
    my %plugins;
    my $count  = 0;
    my $select = "<select name=plugin_name>";
    opendir (DIR, $dir) or return $self->error('CANTOPEN', 'FATAL', $dir, "$!");
    while (defined(my $file = readdir(DIR))) {
        next unless ($file =~ /^(.+)\.tar$/);
        my $plugin_name = $1;
        $select .= "<option>$plugin_name";
        $count++;
    }
    closedir(DIR);
    $select .= "</select>";
    return $count ? $select : '';
}

1;

