# ==================================================================
# Gossamer Threads Module Library - http://gossamer-threads.com/
#
#   MySQLMan
#   Author: Scott Beck
#   CVS Info : 087,071,086,086,085      
#   $Id: MySQLMan.pm,v 1.29 2009/04/25 03:14:33 brewt Exp $
#
# Copyright (c) 2002 Gossamer Threads Inc.  All Rights Reserved.
# ==================================================================
#
# Description:
#       Module wrapper to mysqlman functionality
#

package MySQLMan;

# ==================================================================

use strict;
use vars qw($VERSION %CFG $DBH @ISA $IN @COOKIES );

use FindBin qw/$Bin/;

$VERSION = '1.09';

@ISA = qw(GT::Base);

# Required Librariers
# -----------------------------------------------------------------------------
# Make sure we are using perl 5.003 and load other required files.
#
check_libraries();

sub new {
# -----------------------------------------------------------------------------
    my $class = shift;
    my $bin = $Bin;

    # findbin sets the path to / sometimes if it can't
    # find the cwd.
    $bin ||= '.'; 
    $bin = '.' if $bin eq '/';


    $class->fatal("Arguments to new() must be a hash")
      if @_ & 1;
    my %opts = @_;

    $CFG{do} = delete $opts{do};
    $CFG{do} = 'do' unless defined $CFG{do};

    $CFG{template_dont_save} = delete $opts{template_dont_save};
    $CFG{template_dont_save} = 1 unless defined $CFG{template_dont_save};

# FILES
# -----------------------------------------------------------------------------
# the URL to mysql.cgi.
# You can set it as "mysql.cgi" to make it relative.
    $CFG{script_url} = delete $opts{script_url};
    $CFG{script_url} = "mysql.cgi" unless defined $CFG{script_url};

# The URL to home/top.  It is used for the link TOP on each page
# in the tool bar.
    $CFG{home_url} = delete $opts{home_url};
    $CFG{home_url} = "/" unless defined $CFG{home_url};

# The PATH to the template directory.
    $CFG{template_dir} = delete $opts{template_dir};
    $CFG{template_dir} = $bin . "/templates"
      unless defined $CFG{template_dir};

# CONNECTION
# -----------------------------------------------------------------------------
# Mysqlman allows user to skip the login page and connect
# to a database directly.  (On = 1, Off = 0)
    $CFG{direct_connect} = delete $opts{direct_connect};
    $CFG{direct_connect} = 0 unless defined $CFG{direct_connect};

# Database used.
    $CFG{direct_db} = delete $opts{direct_db};
    $CFG{direct_db} = 'mysql' unless defined $CFG{direct_db};

# Host to connect to.
    $CFG{direct_host} = delete $opts{direct_host};
    $CFG{direct_host} = 'localhost' unless defined $CFG{direct_host};

# Port.  Standard port will be used if not specified.
    $CFG{direct_port} = delete $opts{direct_port};
    $CFG{direct_port} = '' unless defined $CFG{direct_port};

# User name and password.
    $CFG{direct_user} = delete $opts{direct_user};
    $CFG{direct_user} = 'root' unless defined $CFG{direct_user};
    $CFG{direct_pass} = delete $opts{direct_pass};
    $CFG{direct_pass} = '' unless defined $CFG{direct_pass};

# DISPLAY OPTION
# -----------------------------------------------------------------------------
# Set the number of rows to be displayed in one page.
    $CFG{page_length} = delete $opts{page_length};
    $CFG{page_length} = 20 unless defined $CFG{page_length};

# All tables displayed will be sorted by the column indicated
# here by default.  (0 indicates the first column.)
    $CFG{default_sort} = delete $opts{default_sort};
    $CFG{default_sort} = 0 unless defined $CFG{default_sort};

# Show 'NULL' when the cell is null?
    $CFG{show_null} = delete $opts{show_null};
    $CFG{show_null} = 1 unless defined $CFG{show_null};

# Show columns with type TIMESTAMP in insert/edit form?
    $CFG{show_timestamp_field} = delete $opts{show_timestamp_field};
    $CFG{show_timestamp_field} = 0 unless defined $CFG{show_timestamp_field};

# Prompt confirmation before deleting a single record?
    $CFG{confirm_delete_record} = delete $opts{confirm_delete_record};
    $CFG{confirm_delete_record} = 1 unless defined $CFG{confirm_delete_record};

# INSERT RECORD OPTIONS
# -----------------------------------------------------------------------------
# Would you like to be able to insert null values?  If you turn
# this option off then all fields not filled will be treated as ''
# (blank).    (Yes = 1, No = 0)
    $CFG{insert_null} = delete $opts{insert_null};
    $CFG{insert_null} = 1 unless defined $CFG{insert_null};

# Where would you like to be brought to when you have done one insert?
    $CFG{insert_origin} = delete $opts{insert_origin};
    $CFG{insert_origin} = 'insert'
      unless defined $CFG{insert_origin}; #('tables' - to table list
                                         #      OR
                                         # 'insert' - to insert new record page)

# MYSQLDUMP
# -----------------------------------------------------------------------------
# For very large databases, it might be a good idea to divide
# the contents of the tables into pages to be written into files
# to reduce the load on memory.
# (on = 1, off = 0)
    $CFG{dump_in_pages} = delete $opts{dump_in_pages};
    $CFG{dump_in_pages} = 0 unless defined $CFG{dump_in_pages};

# The number of records to be written into the file at a time.
    $CFG{dump_page_length} = delete $opts{dump_page_length};
    $CFG{dump_page_length} = 1000 unless defined $CFG{dump_page_length};

# MISC
# -----------------------------------------------------------------------------
# Cookies used in the script
    $CFG{db_host_cookie_name} = delete $opts{db_host_cookie_name};
    $CFG{db_host_cookie_name} = 'MySQLMan_host'
      unless defined $CFG{db_host_cookie_name};
    $CFG{db_user_cookie_name} = delete $opts{db_user_cookie_name};
    $CFG{db_user_cookie_name} = 'MySQLMan_username'
      unless defined $CFG{db_user_cookie_name};
    $CFG{db_pass_cookie_name} = delete $opts{db_pass_cookie_name};
    $CFG{db_pass_cookie_name} = 'MySQLMan_password'
      unless defined $CFG{db_pass_cookie_name};
    $CFG{url_cookie_name} = delete $opts{url_cookie_name};
    $CFG{url_cookie_name} = 'MySQLMan_url' unless defined $CFG{url_cookie_name};


# Set debug to 1 for debug.
    $CFG{debug} = delete $opts{debug};
    $CFG{debug} = 0 unless defined $CFG{debug};

    $class->fatal( "Unknown arguments to new(): " . join ', ', sort keys %opts )
      if keys %opts;

    my $c;
    return bless \$c, $class;
}

sub process {
# -----------------------------------------------------------------------------
    my $class = shift;

# Initialize for mod_perl
    $DBH      = undef;    
    @COOKIES  = ();

    $IN       = GT::CGI->new;

    my $level;
    if ( defined( $IN->param('db_user') ) || defined( $IN->param('db_host') ) )
    {
        do_login();
    }
    else {
        $level = $IN->param( $CFG{do} ) || '';

        if ( $level eq "logout" ) {
            do_logout();
            if ( $CFG{'debug'} ) { cgierr("debug"); }
            return 1;
        }

        if ( ( $level ne "login" ) && !$IN->param('form_login') ) {
            assign_cookies() or return; # undef means user was redirected
        }

        if ( $CFG{'direct_connect'} && !$IN->param ) { $level = 'tables'; }

        if ( !$level or $level eq 'show_dbs' ) { show_dbs(); }    # Diplay the database list.
        elsif ( $level eq "database" ) {
            modify_db();
        }                                    # Create or drop a database.
        elsif ( $level eq "login" ) {
            html_login();
        }    # Prompt the log-in page when needed.
        elsif ( $level eq "tables" ) {
            show_tables();
        }    # Display the list of tables.
        elsif ( $level eq "browse" ) {
            table_browse();
        }    # Do a general browse.
        elsif ( $level eq "select" ) {
            table_select();
        }    # Compose query criteria for browse.
        elsif ( $level eq "insert" ) {
            html_insert();
        }    # Input value for insert.
        elsif ( $level eq "insert_record" ) {
            insert_record();
        }    # Insert the value input into table.
        elsif ( $level eq "property" ) {
            table_property();
        }    # Display column spec's of the current table.
        elsif ( $level eq "modify" ) {
            table_modify();
        }    # Modify the table contents.
        elsif ( $level eq "create" ) {
            html_table_def( 'create' );
        }    # Construct the specifications of the new table.
        elsif ( $level eq "create_table" ) {
            create_table();
        }    # Create a new table according to the specification.
        elsif ( $level eq "alter_table" ) {
            alter_table();
        }    # Change the structure of a table.
        elsif ( $level eq "add_col" ) {
            html_table_def( 'add_col' );
        }    # Add new column(s) to a table.
        elsif ( $level eq "sql_monitor" ) {
            sql_monitor();
        }    # Process query entered in SQL Monitor.
        elsif ( $level eq "sql_monitor_file" ) {
            sql_monitor_file();
        }    # Process queries saved in a file.
        elsif ( $level eq "import" ) {
            import_record();
        }    # Do import from file.
        elsif ( $level eq "export" ) {
            export_record();
        }    # Do export to file.
        elsif ( $level eq "mysqldump" ) {
            mysqldump();
        }    # Create a table dump into file specified.
        elsif ( $level eq "top_level_op" ) {
            top_level_op();
        }    # Create db/create table/SQL Monitor/import/export/
             # add fields/rename table.
        elsif ( $level eq "show_query" ) {
            html_show_query();
        }    # display saved query in SQL monitor.
        elsif ( $level eq "save_search" ) { html_save_search(); }
        elsif ( $level eq "help" )        {
            html_help();
        }    # display help pages
        else {
            cgierr("fatal error: $@");
        }    # Display error message if error occurs.

        if ($DBH) { $DBH->disconnect; }
    }
    if ( $CFG{'debug'} ) { cgierr("debug"); }
    %CFG = ();
    return 1;
}

sub show_dbs {
# -----------------------------------------------------------------------------
# Diplays all the databases in MySQL. The function will
# take the output of SHOW DATABASES query and list all
# the databases in MySQL.  Each name is a link to the table
# list of the database and a "Drop" link is also created here
# with each database for easy management.

    my ( $feedback ) = @_;

    if ( !$DBH ) { connect_db() or return; }
    my $query = "SHOW DATABASES";
    my $sth   = exec_query($query) or return;

    my $database_list = [];

    while ( my ($db) = $sth->fetchrow_array ) {
        push @$database_list, { name => $db };
    }
    $sth->finish;

    html_database( undef, $database_list, $feedback ) or return;
}

#=================================================#
#              DATABASE MANAGEMENT                #
#=================================================#

sub modify_db {
# -----------------------------------------------------------------------------
# Then function will determine whether to create a new
# database or drop a existing one.
#
    my $action = $IN->param('action') || '';

    if    ( $action eq 'drop_db' )   { drop_db(); }
    elsif ( $action eq 'create_db' ) { create_db(); }
    else { cgierr("database modify action cannot be identified."); }
}

sub drop_db {
# -----------------------------------------------------------------------------
# Here a "DROP DATABASE db_name" query is executed.  If
# the confirmed flag is not on then the user will be brought
# to a confirmation page.  If the action is confirmed, the
# database specified will be dropped and the user will be brought
# back to the database list page.
# It is disabled in demo mode.


    my $db = $IN->param('db') || '';

    my ( $query, $sth );

    $query = "DROP DATABASE $db";

    if ( $IN->param('comfirmed') ) {


        if ( !$DBH ) { connect_db() or return; }
        $sth = exec_query($query) or return;
        $sth->finish;
        my $message = "Database $db Dropped.";
        show_dbs( $message );
    }
    else { html_confirm_action( $query ); }
}

sub create_db {
# -----------------------------------------------------------------------------
# Before a new database is created, the name specified will
# be tested to see if it is a valid one.  If it is, then the
# table will be created and the user will be brought back to the
# database list.
# It is disabled in demo mode.


    my $db = $IN->param('db') || '';

    my ( $query, $sth );
    valid_name_check($db) or return;


    if ( !$DBH ) { connect_db() or return; }
    $query = "CREATE DATABASE $db";
    $sth = exec_query($query) or return;
    $sth->finish;

    my $message = "New Database $db Created";
    show_dbs( $message );
}

#=================================================#
#               TABLE MANAGEMENT                  #
#=================================================#

# ================= #
#   Table Display   #
# ================= #

sub show_tables {
# -----------------------------------------------------------------------------
# Shows all the tables in the database chosen.  Browse/Select
# /Properties/Insert/Drop/Empty links are also created with
# each table name.

    my ( $feedback ) = @_;
    my ( $query, $sth, $table );
    my $data_source = $IN->param("data_source") || '';

    if ( !$DBH ) { connect_db() or return; }
    $query = "show tables";
    $sth   = exec_query($query) or return;

    my @tables;
    while ( ($table) = $sth->fetchrow_array() ) {
        push @tables, {
            name => $table,
            count => record_count($table)
        };
    }
    $sth->finish;
    html_table( \@tables, $feedback );
}

# ======================= #
#   Table Browse/Select   #
# ======================= #

sub table_browse {
# -----------------------------------------------------------------------------
# Browse, Select/Search
#
#   The function does a "SELECT * FROM table_name" query
#   to do a browse and will display the results according to
#   the select criteria specified by user in "select".  If a
#   primary key exists in the table, then "edit" and "drop" links
#   are also created with each record.
#
# SQL-Monitor
#
#   A query entered in SQL monitor that requires
#   displaying it's result uses this sub-routine as well.
#   These queries include explain, select, describe, and desc.

    my ( $query ) = @_;
    my (
        $where_clause,  $start_row, $empty_set,     @pri_key,
        $prep,          $sth,       @cols,          $rows,
        @ary,           $index,     $col_name,      $table_records,
        $record,        $cells,     $page_jump,     $page_link,
        $rows_in_page,  @fields,    @example,       @record_modify,
        $record_modify, $edit_link, @table_list,    $table_num,
        $pri_key_count, $pri_key,   $query_printed, $query_count,
        $total_rec_num
    );

    my $data_source = $IN->param("data_source")   || '';
    my $table       = $IN->param("table")         || '';
    my $page        = $IN->param("page")          || "1";
    my $action      = $IN->param("browse_action") || $IN->param("action") || '';
    my $fields      = $IN->param("fields")        || '';
    my $where       = $IN->param("where")         || '';
    my $example     = $IN->param("example")       || '';


    if ( ( $page =~ m/\D/ ) ) {
        sqlerr("Page number cannot be $page.  Please enter a valid page number.");
        return;
    }

# The first row of the page arrived.
    $start_row = ( ( $page - 1 ) * $CFG{'page_length'} );

    if ( !$DBH ) { connect_db() or return; }

# get all column names for the table and store them in @cols.
    if ( $action eq 'browse' || $action eq 'select' ) {
        @cols = get_cols( $table );
    }

    if ( $action eq 'browse' ) {
        $fields = '*';
        $index = $IN->param("sort_index") || $cols[ $CFG{'sort_default'} ];
    }
    elsif ( $action eq 'select' ) {

# SELECT clause.
        if ( !$fields ) {

            @fields = ();
            for ( my $i = 0 ; $i <= $#cols ; $i++ ) {
                if ( $IN->param("*select_field*_$cols[$i]") ne '' ) {
                    push ( @fields, $IN->param("*select_field*_$cols[$i]") );
                }
            }
            $fields = join ",", @fields;
        }

# WHERE clause.
        if ($where) { $where_clause = "WHERE $where"; }

# Query by example.
        if ( !$example ) {
            @example = ();
            for ( my $i = 0 ; $i <= $#cols ; $i++ ) {
                if ( $IN->param("*example*_$cols[$i]") ne '' ) {
                    my $temp =
                      $cols[$i] . ' like '
                      . $DBH->quote( $IN->param("*example*_$cols[$i]") );
                    push ( @example, $temp );
                }
            }
            $example = join " and ", @example;
        }

        if ($example) {
            if ($where_clause) { $where_clause .= " and $example"; }
            else { $where_clause = "WHERE $example"; }
        }

# get sort index
        $query = qq~SELECT $fields
                    FROM $table 
                    $where_clause
                    LIMIT 1~;

        $prep = exec_query($query) or return;
        $index = $IN->param("sort_index")
          || $prep->{NAME}->[ $CFG{'sort_default'} ];
        $prep->finish;
    }

    @pri_key = ();
    $pri_key = '';

    if ( $action eq 'browse' || $action eq 'select' ) {

# Prepare the contents in the table selected.

        @pri_key = get_pri_key($table);
        $pri_key = join ",", @pri_key;

        if ( $pri_key ne '' ) { $pri_key = ',' . $pri_key; }

# the actual query sent to statement handler.  The
# primary key is selected for delete and edit.
        $query = qq~SELECT $fields $pri_key 
                    FROM $table 
                    $where_clause   
                    ORDER BY $index 
                    LIMIT $start_row, $CFG{'page_length'}~;

# The query that gets printed in SQL-Message.
        $query_printed = qq~SELECT $fields
                            FROM $table 
                            $where_clause   
                            ORDER BY $index 
                            LIMIT $start_row, $CFG{'page_length'}~;

# counts the total number of resulting records from browse/select
        $query_count = qq~SELECT COUNT(*) 
                         FROM $table 
                         $where_clause~;
        $sth = exec_query($query_count) or return;
        ($total_rec_num) = $sth->fetchrow;
        $sth->finish;
    }
    else {    # from SQL Monitor.

        if ( !$query ) { $query = $IN->param("query") || ''; }
        @table_list = get_table_list($query);
        if ( $#table_list == 0 ) { ($table) = @table_list; }
    }

# Get all records or records that satisfy the search criteria.
# Or, $query is simply the query enter in SQL monitor.
    $sth  = exec_query($query) or return;
    $rows = $sth->rows;

    if ( $action eq 'monitor' ) {
        $total_rec_num = $rows;
        $rows_in_page  = $rows - $start_row;
        if ( $rows_in_page > $CFG{'page_length'} ) {
            $rows_in_page = $CFG{'page_length'};
        }
    }
    else { $rows_in_page = $rows; }

    $page_jump =
      link_page_jump( $fields, $example, $index, $total_rec_num );

# Links to next/previous/top page if there is any.
    $page_link = link_page( $rows_in_page, $table, $fields, $example, $index, $total_rec_num );

    my $example_esc = $IN->escape($example);
    my $where_esc   = $IN->escape($where);
    my $query_esc   = $IN->escape($query);

    if (@pri_key) { $pri_key_count = $#pri_key + 1; }
    else { $pri_key_count = 0; }

# Display column names
    $col_name = [];
    if ( $action eq 'browse' || $action eq 'select' ) {
        for ( my $i = 0 ; $i < $sth->{NUM_OF_FIELDS} - $pri_key_count ; $i++ ) {
            push @$col_name, {
                name => $sth->{NAME}->[$i],
                link => qq~\n$CFG{'script_url'}?$CFG{do}=browse&data_source=$data_source&table=$table&page=$page&sort_index=$sth->{NAME}->[$i]&action=$action&fields=$fields&where=$where_esc&example=$example_esc&query=$query_esc~
            };
        }
    }
    else {
        for ( my $i = 0 ; $i < $sth->{NUM_OF_FIELDS} - $pri_key_count ; $i++ ) {
            push @$col_name, { name => $sth->{NAME}->[$i] };
        }
    }

    $example_esc = $IN->html_escape($example);
    $where_esc   = $IN->html_escape($where);
    $query_esc   = $IN->html_escape($query);

# Display the contents in the table selected.
    $table_records = [];
    my $counter = 0;

# Display the resulting set of records.  The result is divided into pages and the page
# by the length specified in mysql.cfg.
#
# From "Browse" or "Select/Search"
#     The LIMIT clause of the query limits the records to display, so we don't display all.
# From "SQL Monitor"
#     Records between $start_row and $start_row+$CFG{'page_length'} will be displayed.
#
    while ( @ary = $sth->fetchrow_array() ) {
        if (
            $action ne 'monitor'
            || (   $counter >= $start_row
                && $counter < ( $CFG{'page_length'} + $start_row ) )
          )
        {
            $record        = [];
            @record_modify = ();

            for ( my $i = 0 ; $i < $sth->{NUM_OF_FIELDS} ; $i++ ) {
                if ( $i < $sth->{NUM_OF_FIELDS} - $pri_key_count ) {

                    $ary[$i] = $IN->html_escape( $ary[$i] );
                    $ary[$i] =~ s/(\r|\n)+/<BR>/g;
                    $ary[$i] =~ s/\t+/&nbsp;&nbsp;&nbsp;/g;
                    $ary[$i] =~ s/\s+/&nbsp;/g;
                    $ary[$i] =~ s/&lt;BR&gt;/&lt;BR&gt;\n/g;

                    push @$record, {
                        name => \$ary[$i],
                        null => !defined($ary[$i])
                    };
                }

# if there are any primary keys, then push "pri = value"
# into @record_modify.
                else {
                    my $cell = $ary[$i];
                    $cell = $DBH->quote($cell);
                    my $col_name = $sth->{NAME}->[$i];
                    push ( @record_modify, $col_name . ' = ' . $cell );
                }
            }
            if (@pri_key && ($action ne 'monitor')) {
                $record_modify = join ' and ', @record_modify;
                my $link =
                    $CFG{script_url} .
                    "?$CFG{do}=modify" .
                    ";data_source=" . $IN->escape($data_source) .
                    ";table=" . $IN->escape($table) .
                    ";page=" . $IN->escape($page) .
                    ";sort_index=" . $IN->escape($index) .
                    ";fields=" . $IN->escape($fields) .
                    ";where=" . $IN->escape($where) .
                    ";example=" . $IN->escape($example) .
                    ";record_modify=" . $IN->escape($record_modify) .
                    ";browse_action=" . $IN->escape($action);
                my $edit_link = $link . ";action=edit_record";
                my $delete_link = $link . ";action=delete_record";
                push @$table_records, {
                    record    => $record,
                    edit_link => \$edit_link,
                    delete_link => \$delete_link
                };
            }
            else {
                push @$table_records, { record => $record };
            }
        }
        $counter++;
    }
    if ( !@$table_records ) { $empty_set = 1; }
    else { $empty_set = 0; }

    $sth->finish;

    my $save_search_link = "$CFG{script_url}?$CFG{do}=save_search;data_source=" . $IN->escape($data_source) . ";table=" . $IN->escape($table) . ";page=" . $IN->escape($page) . ";sort_index=" . $IN->escape($index) . ";fields=" . $IN->escape($fields) . ";where=" . $IN->escape($where) . ";example=" . $IN->escape($example) . ";action=edit_record;record_modify=" . $IN->escape($record_modify) . ";browse_action=" . $IN->escape($action) . ";query=" . $IN->escape($query);

    html_display(
        'table_browse.html',
        {
            total_rows    => $total_rec_num,
            table         => $table,
            page          => scalar $IN->param('page') || '1',
            empty_set     => $empty_set,
            page_jump     => \$page_jump,
            show_null     => $CFG{show_null},
            page_link     => \$page_link,
            col_name      => $col_name,
            col_num       => scalar(@$col_name),
            table_records => $table_records,
            query         => $query,
            query_printed => $query_printed || $query,
            pri_key          => $pri_key,
            help_topic       => "browse",
            save_search_link => \$save_search_link,
            fields           => $fields,
            where_esc        => \$where_esc,
            example_esc      => \$example_esc,
            query_esc        => \$query_esc,
            confirm_delete_record => $CFG{confirm_delete_record},
        }
    );
}

sub table_select {
# -----------------------------------------------------------------------------
# This function creates the search form for a SELECT... query (search).
# Field names are in check boxes and "query by example" input
# fields are created with the check boxes.


    my ( $query, $select_table, $example_table, @type_ary, @cols );

    my $data_source = $IN->param("data_source") || '';
    my $table       = $IN->param("table")       || '';
    my $page        = $IN->param("page")        || 1;

    $select_table  = [];
    $example_table = [];


    if ( !$DBH ) { connect_db() or return; }

# Get the names of the all columns.
    @cols = get_cols( $table );

# fields selection (for SELECT)
    @type_ary = get_col_type($table);

    for (my $i = 0; $i < @cols; $i++) {
        push @$select_table, { name => $cols[$i] };
        push @$example_table, {
            name => $cols[$i],
            type => $type_ary[$i]
        };

    }

    html_display(
        'table_select.html',
        {
            select_table      => $select_table,
            select_table_cnt  => scalar(@$select_table),
            example_table     => $example_table,
            example_table_cnt => scalar(@$example_table),
            help_topic        => "select",
        }
    );
}

sub table_property {
# -----------------------------------------------------------------------------
# The function outputs the result of "describe table_name"
# query.  It reads the output row by row and create
# Change/Drop/Primary/Index/Unique links with each field.

    my ( $feedback ) = @_;
    my ( $page, $query, $sth, @ary, $table_property );

    my $data_source = $IN->param("data_source") || '';
    my $table       = $IN->param("table")       || '';


    if ( !$DBH ) { connect_db() or return; }
    $query = "describe $table";
    $sth   = exec_query($query) or return;
    my $columns = [ map {; { name => $_ } } @{$sth->{NAME}} ];

# Display the contents in the table selected.
    $table_property = [];
    while ( @ary = $sth->fetchrow_array() ) {
        push @$table_property, {
            columns      => [ map {; { name => $_ } } @ary ],
            change_link  => qq~$CFG{'script_url'}?$CFG{do}=alter_table&data_source=$data_source&table=$table&col=$ary[0]&action=alter_col~,
            drop_link    => qq~$CFG{'script_url'}?$CFG{do}=alter_table&data_source=$data_source&table=$table&col=$ary[0]&action=drop_col~,
            primary_link => qq~$CFG{'script_url'}?$CFG{do}=alter_table&data_source=$data_source&table=$table&col=$ary[0]&action=set_primary~,
            index_link   => qq~$CFG{'script_url'}?$CFG{do}=alter_table&data_source=$data_source&table=$table&col=$ary[0]&action=set_index~,
            unique_link  => qq~$CFG{'script_url'}?$CFG{do}=alter_table&data_source=$data_source&table=$table&col=$ary[0]&action=set_unique~
        };
    }
    $sth->finish;

    my $key_table = get_key_table();
    html_display(
        'property.html',
        {
            table_property => $table_property,
            table_property_cnt => scalar(@$table_property),
            table_columns  => $columns,
            table_columns_cnt => scalar(@$columns),
            key_table      => $key_table,
            key_table_cnt  => scalar(@$key_table),
            feedback       => $feedback,
            help_topic     => "properties",
        }
    );
}

# ===================== #
#   Insert New Record   #
# ===================== #

sub insert_record {
# -----------------------------------------------------------------------------
# This function insert a new record into the table specified.


    my $table = $IN->param('table') || '';
    my $feedback;
    my ( $query, $sth, @insert_info, $new_record, @insert_fields, $fields,
        @insert_values, $values );


    if ( !$DBH ) { connect_db() or return; }

# Get the info of the new record to be inserted.
    @insert_info = compose_new_condition( 1 );

    my $counter = 0;
    foreach my $element (@insert_info) {

        my $is_value = $counter % 2;

        if ($is_value) {    # Get the values
            push ( @insert_values, $element );
        }
        else {              # Get the name of the fields
            push ( @insert_fields, $element );
        }
        $counter++;
    }

# Make the input from the form into a string to fit in the query.
    $fields = join ",", @insert_fields;
    $values = join ",", @insert_values;

    $query = "INSERT INTO $table ($fields) VALUES ($values)";
    $sth = exec_query($query) or return;
    $sth->finish;

    $feedback = 'Record Inserted.';
    if ( $CFG{'insert_origin'} eq 'table' ) { show_tables( $feedback ); }
    else { html_insert( $feedback ) }
}

# ====================== #
#    Create New Table    #
# ====================== #

sub create_table {
# -----------------------------------------------------------------------------
# This function takes in the input from the create table
# form and put them together to produce a create table
# query.


    my $table = $IN->param('table') || '';
    my (
        @field_list,  $col_spec, @primary_list, @index_list,
        @unique_list, $fields,   $primary,      $index,
        $unique,      $sth,      $query
    );


    if ( !$DBH ) { connect_db() or return; }

# get the specification of each column.
    for ( my $i = 0 ; $i < $IN->param('num_of_fields') ; $i++ ) {

# Make the text input fields into a string to fit in the query.
        $col_spec = concate_col_spec( $i );

        push ( @field_list, "$col_spec" );

# Check index fields.
        if ( $IN->param("primary_$i") ) {
            push ( @primary_list, $IN->param("field_$i") );
        }
        if ( $IN->param("index_$i") ) {
            push ( @index_list, $IN->param("field_$i") );
        }
        if ( $IN->param("unique_$i") ) {
            push ( @unique_list, $IN->param("field_$i") );
        }
    }

    $fields  = join ",", @field_list;
    $primary = join ",", @primary_list;
    $index   = join ",", @index_list;
    $unique  = join ",", @unique_list;

    $query = "CREATE TABLE $table($fields";

    if ($primary) { $query .= ", PRIMARY KEY ($primary)" }
    if ($index)   { $query .= ", INDEX ($index)" }
    if ($unique)  { $query .= ", UNIQUE ($unique)" }

    $query .= ')';

    $sth = exec_query($query) or return;
    $sth->finish;

    show_tables( "Table $table Created." );
}

# ====================== #
#   Table Modification   #
# ====================== #

sub table_modify {
# -----------------------------------------------------------------------------
# Determine modify action.
#
    my $action = $IN->param('action') || '';

    if    ( $action eq 'drop_table' )    { drop_table(); }
    elsif ( $action eq 'empty_table' )   { empty_table(); }
    elsif ( $action eq 'delete_record' ) { delete_record(); }
    elsif ( $action eq 'edit_record' )   { edit_record_html(); }
    elsif ( $action eq 'update' )        { update_record() }
    else { cgierr("modify action cannot be identified"); }
}

sub drop_table {
# -----------------------------------------------------------------------------
# The function drops the table specified if the confirmed
# flag is on.


    my $table = $IN->param('table') || '';
    my ( $query, $sth );


    $query = "DROP TABLE $table";

    if ( $IN->param('comfirmed') ) {
        if ( !$DBH ) { connect_db() or return; }
        $sth = exec_query($query) or return;
        show_tables( "Table $table dropped!" );
    }
    else { html_confirm_action( $query ); }
}

sub empty_table {
# -----------------------------------------------------------------------------
# The function deletes all the records in the table specified
# if the confirmed flag is on.


    my ( $table, $query, $sth );

    $table = $IN->param('table') || '';


    $query = "DELETE FROM $table";

    if ( $IN->param('comfirmed') ) {
        if ( !$DBH ) { connect_db() or return; }
        $sth = exec_query($query) or return;
        show_tables( "Table $table emptied!" );
    }
    else { html_confirm_action( $query ); }
}

sub delete_record {
# -----------------------------------------------------------------------------
# Delete a single record in the table selected.
# $record_modify consists the primary key(s) value of
# the record being deleted.
#
    my $table         = $IN->param('table') || '';
    my $record_modify = $IN->param('record_modify') || '';
    my ( $sth, $query );


    if ( !$DBH ) { connect_db() or return; }
    $query = "DELETE FROM $table WHERE $record_modify LIMIT 1";
    $sth = exec_query($query) or return;
    $sth->finish;

    table_browse();
}

sub edit_record_html {
# -----------------------------------------------------------------------------
# Pre-processing stage before the edit record form is
# displayed.  This function prepares necessary information
# for the edit form.
# $record_modify consists the primary key(s)
# value of the record being edited.
# i.e. key = value.
#
    my $table         = $IN->param('table') || '';
    my $record_modify = $IN->param('record_modify') || '';
    my ( $sth, $query, @record, $update, $update_fields, $form_hidden );


    if ( !$DBH ) { connect_db() or return; }

# Get the record being modified.
    $query  = "SELECT * FROM $table WHERE $record_modify";
    $sth    = exec_query($query) or return;
    @record = $sth->fetchrow_array;
    my $row = $sth->rows;
    $sth->finish;

# create the the edit record form table.
    ($update_fields, $form_hidden) = form_fields( 1, @record );
    html_update( $update_fields, $form_hidden );
}

sub update_record {
# -----------------------------------------------------------------------------
# Take in the input from the edit table form and update
# the record specified.
#
    my $table         = $IN->param('table') || '';
    my $record_modify = $IN->param('record_modify') || '';
    my ( $sth, $query, $update, @fields );


    if ( !$DBH ) { connect_db() or return; }

# Get the updated values in each field.  Each element in the
# field is in the form "field = value".
    @fields = compose_new_condition();

    $update = join ",", @fields;
    $query  = "UPDATE $table SET $update WHERE $record_modify";
    $sth    = exec_query($query) or return;
    $sth->finish;

    table_browse();
}

# ====================== #
#    Table Alteration    #
# ====================== #

sub alter_table {
# -----------------------------------------------------------------------------
# Identify alter table action.
#
    my $action = $IN->param('action') || '';

    if    ( $action eq 'alter_col' )    { alter_col_html(); }
    elsif ( $action eq 'do_alter_col' ) { alter_col(); }
    elsif ( $action eq 'drop_col' )     { drop_col(); }
    elsif ( $action eq 'set_primary' )  { set_primary(); }
    elsif ( $action eq 'set_index' )    { set_index(); }
    elsif ( $action eq 'set_unique' )   { set_unique(); }
    elsif ( $action eq 'drop_key' )     { drop_key(); }
    elsif ( $action eq 'add_col' )      { add_col(); }
    elsif ( $action eq 'rename_table' ) { rename_table(); }
    else { cgierr("Alter Table action cannot be identified"); }
}

sub alter_col_html {
# -----------------------------------------------------------------------------
# The function first reads in the spec's of the column
# chosen in the current table.  Then the type/length_set
# /attribute is identified individually.
#
    my $table = $IN->param('table') || '';
    my $col   = $IN->param('col') || '';

    my ( $field, $type,  $null,       $key,        $default, $extra );
    my ( $sth,   $query, $length_set, $attributes, $dump,    $type_name );


# Get column specification.
    if ( !$DBH ) { connect_db() or return; }
    $col   = $DBH->quote($col);
    $query = "SHOW COLUMNS FROM $table LIKE $col";
    $sth   = exec_query($query) or return;

# parse column definition.
    my @ary = $sth->fetchrow_array;
    (
        $field, $type_name, $length_set, $attributes, $null, $key, $default,
        $extra
      )
      = parse_col_spec(@ary);
    $sth->finish;

    html_alter_col( $field, $type_name, $length_set, $attributes, $null, $default, $extra );
}

sub alter_col {
# -----------------------------------------------------------------------------
# Updates the column specification.  The input from the
# alter column is taken in and made into a string to be
# fit as part of the query string.  Then the user is
# brought back to the property page.
#
    my $table = $IN->param('table') || '';
    my $col   = $IN->param('col') || '';
    my ( $col_spec, $sth, $query );


    if ( !$DBH ) { connect_db() or return; }

# Get the updated column specs in string format.
    $col_spec = concate_col_spec( 0 );

    $query = "ALTER TABLE $table CHANGE $col $col_spec";
    $sth = exec_query($query) or return;
    $sth->finish;

    table_property( "Specification of Column $col of Table $table Has Been Changed." );
}

sub drop_col {
# -----------------------------------------------------------------------------
# The function drops the column/field specified
# if the confirmed flag is on.
#
    my $table = $IN->param('table') || '';
    my $col   = $IN->param('col') || '';
    my ( $col_spec, $sth, $query );


    $query = "ALTER TABLE $table DROP $col";

    if ( $IN->param('comfirmed') ) {
        if ( !$DBH ) { connect_db() or return; }
        $sth = exec_query($query) or return;
        $sth->finish;
        table_property( "Column $col of Table $table Has Been Dropped." );
    }
    else { html_confirm_action( $query ); }
}

sub set_primary {
# -----------------------------------------------------------------------------
# The function will first set the column not nullable
# and then set the column as primary key.  Note that an
# error will occur if all there already exists a primary key.
#
    my $table = $IN->param('table') || '';
    my $col   = $IN->param('col') || '';
    my ( $sth, $query );


    if ( !$DBH ) { connect_db() or return; }
    $query = "ALTER TABLE $table ADD PRIMARY KEY ($col)";

# Set the column not nullable
    set_col_not_null();

    $sth = exec_query($query) or return;
    $sth->finish;

    table_property( "Column $col set as primary key." );
}

sub set_index {
# -----------------------------------------------------------------------------
# The function will first set the column not nullable
# and then set the column as index.  Note that an
# error will occur if all there already exists a primary key.
#
    my $table = $IN->param('table') || '';
    my $col   = $IN->param('col') || '';
    my ( $sth, $query );


    $query = "ALTER TABLE $table ADD INDEX ($col)";

    if ( !$DBH ) { connect_db() or return; }

# Set the column not nullable.
    set_col_not_null();

    $sth = exec_query($query) or return;
    $sth->finish;

    table_property( "Column $col set as index." );
}

sub set_unique {
# -----------------------------------------------------------------------------
# The function will first set the column not nullable
# and then set the column as unique.  Note that an
# error will occur if all there already exists a primary key.
#
    my $table = $IN->param('table') || '';
    my $col   = $IN->param('col') || '';
    my ( $sth, $query );


    $query = "ALTER TABLE $table ADD UNIQUE ($col)";

    if ( !$DBH ) { connect_db() or return; }

# Set the column not nullable.
    set_col_not_null();

    $sth = exec_query($query) or return;
    $sth->finish;

    table_property( "Column $col set as unique." );
}

sub drop_key {
# -----------------------------------------------------------------------------
# Drops the key specified.
#
    my $table    = $IN->param('table') || '';
    my $key_name = $IN->param('key_name') || '';
    my ( $sth, $query );


    if ( $key_name eq 'PRIMARY' ) {
        $query = "ALTER TABLE $table DROP PRIMARY KEY";
    }
    else { $query = "ALTER TABLE $table DROP INDEX $key_name"; }

    if ( !$DBH ) { connect_db() or return; }
    $sth = exec_query($query) or return;
    $sth->finish;

    if ( $key_name eq 'PRIMARY' ) {
        table_property( "Primary Key of Table $table Has Been Dropped." );
    }
    else {
        table_property( "Index $key_name of Table $table Has Been Dropped." );
    }
}

sub add_col {
# -----------------------------------------------------------------------------
# Adds new columns to the table specified.
#
    my $table    = $IN->param('table') || '';
    my $position = $IN->param('position') || '';
    my (
        @field_list,  $col_spec, @primary_list, @index_list,
        @unique_list, $fields,   $primary,      $index,
        $unique,      $sth,      $query
    );


    if ( !$DBH ) { connect_db() or return; }

    for ( my $i = 0 ; $i < $IN->param('num_of_fields') ; $i++ ) {

        $col_spec = 'ADD ' . concate_col_spec( $i ) . " $position";
        push ( @field_list, "$col_spec" );

        if ( $IN->param("primary_$i") ) {
            push ( @primary_list, $IN->param("field_$i") );
        }
        if ( $IN->param("index_$i") ) {
            push ( @index_list, $IN->param("field_$i") );
        }
        if ( $IN->param("unique_$i") ) {
            push ( @unique_list, $IN->param("field_$i") );
        }

        $position = "After " . $IN->param("field_$i");
    }

# elements in @field_list are in the form "ADD field_name field_spec".
    $fields  = join ",", @field_list;
    $primary = join ",", @primary_list;
    $index   = join ",", @index_list;
    $unique  = join ",", @unique_list;

    $query = "ALTER TABLE $table $fields";

    if ($primary) { $query .= ", ADD PRIMARY KEY ($primary)" }
    if ($index)   { $query .= ", ADD INDEX ($index)" }
    if ($unique)  { $query .= ", ADD UNIQUE ($unique)" }

    $sth = exec_query($query) or return;
    $sth->finish;

    table_property( "Column(s) added to Table $table" );
}

sub rename_table {
# -----------------------------------------------------------------------------
# Renames the table chosen to a new name specified.
# The name entered will be checked to see if it is a
# valid one.  If it is, then the table will be renamed
# and the user will be brought back to the table property
# page.
#
    my $new_name  = $IN->param('table') || '';
    my $old_table = $IN->param('old_table') || '';
    my ( $query, $sth, @name );

    valid_name_check( $IN->param('table') ) or return;


    if ( !$DBH ) { connect_db() or return; }
    $query = "ALTER TABLE $old_table RENAME AS $new_name";
    $sth = exec_query($query) or return;
    $sth->finish;

    table_property( "Table $old_table Renamed to $new_name." );
}

#=================================================#
#             Top Level Operations                #
#=================================================#

sub top_level_op {
# -----------------------------------------------------------------------------
# Determine which top level operation page to display.


    my $action = $IN->param('action') || '';
    if    ( $action eq 'create_db' )    { html_create_db() }
    elsif ( $action eq 'sql_monitor' )  { html_sql_monitor() }
    elsif ( $action eq 'create_table' ) { html_create_table() }
    elsif ( $action eq 'import' ) { html_import( table_field_prep() ) }
    elsif ( $action eq 'export' ) { html_export( table_field_prep() ) }
    elsif ( $action eq 'add_fields' )   { html_add_fields() }
    elsif ( $action eq 'rename_table' ) { html_rename_table() }
    elsif ( $action eq 'mysqldump' )    { html_mysqldump() }
    else { cgierr("Action cannot be identified in top level operation.") }
}

#=================================================#
#                 SQL Monitor                     #
#=================================================#

sub sql_monitor {
# -----------------------------------------------------------------------------
# The monitor will be enabled when a database is selected
# from the database list page.  It will take in the input
# from the text box and send it to mysql.  The query will
# first be determined if it is of a "browse" one.  If it is,
# the result will be displayed using &table_browse.
# It is disabled in demo mode.
#
    my $queries = $IN->param('query') || '';

    if ( $IN->param('from_saved_queries') ) {
        $queries = get_pre_query() || return sqlerr("No query specified.");
    }

    my ( @query, @lines, $query, $command, $sth, $rows, $message );
    my $query_no = 0;


# What type of queries prompt a browse mode.
    my %browse_cmd = (
        explain  => 1,
        select   => 1,
        show     => 1,
        describe => 1,
        desc     => 1,
    );

    if ( !$IN->param('save_query') ) {

        @lines = split /[\n\r]/, $queries;

        for ( my $i = 0 ; $i <= $#lines ; $i++ ) {

# strips out the beginning and ending spaces.
            $lines[$i] =~ s/^\s+//;
            $lines[$i] =~ s/\s+$//;

            if ( !( $lines[$i] =~ /^#/ ) ) {

                if ( ( $lines[$i] =~ /;\s*$/ ) or ( $i == $#lines ) ) {

                    $lines[$i] =~ s/\s*;\s*$//;
                    $query .= "$lines[$i]";
                    $query =~ s/^\s+//;
                    $query =~ s/\s+$//;

                    if ( $query ne '' ) {
                        @query   = split / /, $query;
                        $command = lc( $query[0] );

# display the result if the query of a browse one.
# else execute the query and return the number of
# rows affect.

                        if ( $browse_cmd{$command} ) {
                            return table_browse( $query );
                        }
                        else {
                            if ( !$DBH ) { connect_db() or return; }
                            $rows = $DBH->do($query)
                              or
                              return sqlerr(\( GT::CGI->html_escape( $DBI::errstr ) . ". <P>Query: " . GT::CGI->html_escape( $query ) ) );
                            $rows += 0;
                            $message = "$rows row(s) affected";
                        }
                        $query_no++;
                        $query = '';
                    }
                }
                else {
                    $query .= " $lines[$i]";
                }
            }
        }
        if ( $query_no > 1 ) { $message = 'Queries executed successfully.' }
        return html_sql_monitor( $message );
    }
}

sub sql_monitor_file {
# -----------------------------------------------------------------------------
# Does the same thing as sub sql_monitor only that the
# queries are read from a file.  The functions first checkes
# if a server file path is entered.  If not, then it will look
# for the file uploaded to the temp directory from local drive.
#
    my $file = $IN->param('server_file');
    my ($data);

    my $query = '';

    if ( !$file ) { $file = create_temp_file() or return; }


    if ( !$DBH ) { connect_db() or return; }

    open QF, "<$file" or cgierr("Cannot open query file '$file': $!");
    while ( defined( my $line = <QF> ) ) {

        my $isComment   = 0;
        my $isBlankLine = 0;

        if ( $line =~ /^\s*#/ )  { $isComment   = 1 }
        if ( $line =~ /^\s*\n/ ) { $isBlankLine = 1 }
        if ( !$isComment and !$isBlankLine ) { $query .= " $line"; }

        if ( $line =~ /;\s*(\n|$)/ ) {

            if ( !$DBH ) { connect_db() or return; }

# strips out the beginning and ending spaces.
            $query =~ s/^\s+//;
            $query =~ s/\s+$//;
            $query =~ s/(\r|\n)+/ /g;

            if ( $query ne ';' ) {

# run query.
                $DBH->do($query)
                  or return sqlerr(\(GT::CGI->html_escape($DBI::errstr) . ". <P>Query: " . GT::CGI->html_escape($query)));
            }

# clean up query and prepare to take in the next one.
            $query = '';
        }
    }
    close QF;
    if ( $IN->param('upload_local_file') && !$IN->param('server_file') ) {
        unlink $file;
    }

# In case the last query does not end with a semi-colon.
    $query =~ s/^\s+//;
    $query =~ s/\s+$//;
    $query =~ s/(\r|\n)+/ /g;

    if ( !( $query =~ /;$/ ) and ( $query ne '' ) ) {

# run query.
        $DBH->do($query) or return sqlerr(\(GT::CGI->html_escape($DBI::errstr) . ". <P>Query: " . GT::CGI->html_escape($query)));
    }

    html_sql_monitor( "Queries executed successfully." );
}

#=================================================#
#               Import / Export                   #
#=================================================#

sub import_record {
# -----------------------------------------------------------------------------
# Import records to the table specified from a delimited
# text file.
# It is disabled in demo mode.
#
    my $delimiter =
      defined( $IN->param('delimiter') ) ? $IN->param('delimiter') : '';
    my $rec_del = defined( $IN->param('rec_del') ) ? $IN->param('rec_del') : '';
    my $table           = $IN->param('table')           || '';
    my $file            = $IN->param('server_file')     || '';
    my $import_all_cols = $IN->param('import_all_cols') || '';
    my $local           = $IN->param('local')           || '';
    my $replace_op      = $IN->param('replace_op')      || '';
    my $replace_act     = $IN->param('replace_act')     || '';
    my $escape_char     =
      defined( $IN->param('escape_char') ) ? $IN->param('escape_char') : '';
    my $ignore_line = $IN->param('ignore_line') || 0;

    my @select_fields = $IN->param('ImportRight');

    my ( $query, $sth, $file_q, $delimiter_q, $rec_del_q, $escape_char_q,
        $field_op );


    if ( !$DBH ) { connect_db() or return; }

    if ( !$file ) {
        $file = create_temp_file() or return;
    }

# quote the inputs
    $file_q        = $DBH->quote($file);
    $delimiter_q   = "'" . $delimiter . "'";
    $rec_del_q     = "'" . $rec_del . "'";
    $escape_char_q = $DBH->quote($escape_char);

    if ( !$replace_op ) { $replace_act = ''; }

    $query = qq~LOAD DATA $local INFILE $file_q $replace_act
                INTO TABLE $table
                FIELDS
                    TERMINATED BY $delimiter_q
                    ESCAPED BY $escape_char_q
                LINES TERMINATED BY $rec_del_q
                IGNORE $ignore_line LINES~;

    if ( !$import_all_cols ) {    # import selected fields only.
        my $selected_cols = "(" . join ( ",", @select_fields ) . ")";
        $query .= " $selected_cols";
    }

    $sth = exec_query($query) or return;
    $sth->finish;

    if ( $IN->param('upload_local_file') && !$IN->param('server_file') ) {
        unlink $file;
    }

    show_tables( "File Imported Successfully." );
}

sub export_record {
# -----------------------------------------------------------------------------
# Exort records from the table specified and produce a
# delimited text file.
# It is disabled in demo mode.
#
    my $export_all_cols = $IN->param('export_all_cols') || '';
    my $delimiter       = defined( $IN->param('delimiter') ) ? $IN->param('delimiter') : '';
    my $rec_del         = defined( $IN->param('rec_del') ) ? $IN->param('rec_del') : '';
    my $table           = $IN->param('table') || '';
    my $file            = $IN->param('file')  || '';
    my $escape_char     = defined( $IN->param('escape_char') ) ? $IN->param('escape_char') : '';
    my $to_screen       = $IN->param('export_to_screen') || '';
    if ($to_screen) { $file = get_temp_file_name(); }

    my ( $query, $sth, $file_q, $delimiter_q, $rec_del_q, $escape_char_q, $cols );

    my @select_fields = $IN->param('ImportRight');


    if ( !$to_screen && !$file ) {
        sqlerr("Please provide a file name for export.");
        return;
    }

    if ( !$DBH ) { connect_db() or return; }

# quote the parameters.
    $file_q        = $DBH->quote($file);
    $delimiter_q   = $DBH->quote($delimiter);
    $rec_del_q     = "'" . $rec_del . "'";
    $escape_char_q = $DBH->quote($escape_char);

    if ($export_all_cols) {    # select all fields
        $cols = '*';
    }
    else {                     # export selected fields only.
        $cols = join ( ",", @select_fields );
    }

    if ( $IN->param('from_save_result') ) {

        my $fields = $IN->param('fields');
        my $action = $IN->param('browse_action');

        if ( $action eq 'browse' ) {
            $query = qq~SELECT *
                        INTO OUTFILE $file_q 
                        FIELDS
                            TERMINATED BY $delimiter_q
                            ESCAPED BY $escape_char_q
                        LINES TERMINATED BY $rec_del_q
                        FROM $table~;
        }
        else {

            my $where_clause = '';
            my $where        = $IN->param('where');
            my $example      = $IN->param('example');

            if ($where) { $where_clause = "WHERE $where"; }

# Query by example.
            if ($example) {
                if ($where_clause) { $where_clause .= " AND $example"; }
                else { $where_clause = "WHERE $example"; }
            }

            $query = qq~SELECT $fields
                        INTO OUTFILE $file_q 
                        FIELDS
                            TERMINATED BY $delimiter_q
                            ESCAPED BY $escape_char_q
                        LINES TERMINATED BY $rec_del_q
                        FROM $table 
                        $where_clause~;
        }
    }
    else {
        $query = qq~SELECT $cols
                    INTO OUTFILE $file_q 
                    FIELDS
                        TERMINATED BY $delimiter_q
                        ESCAPED BY $escape_char_q
                    LINES TERMINATED BY $rec_del_q
                    FROM $table~;
    }
    $sth = exec_query($query) or return;
    $sth->finish;

    if ($to_screen) {
        print $IN->header( 'text/plain' );
        print "# Exported data from table $table\n";
        print "# A temporary file ($file) was create in the temp directory.\n";
        print "# Please remove the file manually as necessary.\n\n";
        print "# =========== Export Starts ===========\n";
        open( TEMP, $file ) or cgierr("error open file");
        while ( my $line = <TEMP> ) {
            print $line;
        }
        close TEMP;
        print "\n# =========== Export Ends ===========";

        unlink($file);
        return 1;
    }
    elsif ( $IN->param('from_save_result') ) {
        table_browse();
    }
    else {
        return show_tables( "File Exported Successfully." );
    }
}

sub table_field_prep {
# -----------------------------------------------------------------------------
# Create options for import and export HTML pages.  The
# options are the fields of the table selected.
#
    my $table   = $IN->param('table') || '';
    my $options = '';

    if ( !$DBH ) { connect_db() or return; }
    my $query = "SELECT * FROM $table LIMIT 1";
    my $sth = exec_query($query) or return;
    for ( my $i = 0 ; $i < $sth->{NUM_OF_FIELDS} ; $i++ ) {
        $options .=
          qq~<OPTION value="$sth->{NAME}->[$i]">$sth->{NAME}->[$i]</OPTION>\n~;
    }
    $sth->finish;
    return ( $options, $sth->{NUM_OF_FIELDS} );
}

#=================================================#
#                  Mysqldump                      #
#=================================================#

sub mysqldump {
# -----------------------------------------------------------------------------
# Create dump file.  The result is either printed to the
# screen or saved to a file.
#
    my $file = $IN->param('dump_file') || '';

    my $dump_whole_db  = $IN->param('dump_whole_db')  || '';
    my $op_create_st   = $IN->param('op_create_st')   || '';
    my $op_dump_db     = $IN->param('op_dump_db')     || '';
    my $add_drop_table = $IN->param('add_drop_table') || '';

    my $complete_insert = $IN->param('complete_insert') || '';
    my $delayed_insert  = $IN->param('delayed_insert')  || '';
    my $extended_insert = $IN->param('extended_insert') || '';

    my $db_host = $IN->cookie( $CFG{'db_host_cookie_name'} ) || 'localhost';
    if ( $CFG{'direct_connect'} ) {
        $db_host = $CFG{'direct_host'} || $db_host;
    }

    my ( @table_list, $sth, $query, $table, $table_selected, %ins_options,
        @ary );

    %ins_options = ();
    if ($complete_insert) { $ins_options{'complete_insert'} = 1; }
    if ($delayed_insert)  { $ins_options{'delayed_insert'}  = 1; }
    if ($extended_insert) { $ins_options{'extended_insert'} = 1; }


    if ( !$DBH ) { connect_db() or return; }

    $query = "SHOW TABLES";
    $sth   = exec_query($query) or return;

    if ($dump_whole_db) {    # store all tables in @table_list
        while ( ($table) = $sth->fetchrow_array() ) {
            push ( @table_list, $table );
        }
    }
    else {                   # store selected tables in @table_list.
        while ( ($table) = $sth->fetchrow_array() ) {
            if ( $IN->param("*dump_table*_$table") ) {
                push ( @table_list, $IN->param("*dump_table*_$table") );
            }
        }
    }
    $sth->finish;

    if ( $IN->param("dump_to_screen") ) {

        print $IN->header( 'text/plain' );

        print qq|# MySQL dump
# Generated by MySQLMan $VERSION (http://gossamer-threads.com/scripts/)
# Host: $db_host    Database: | . get_db( $IN->param('data_source') ) . qq| 
#--------------------------------------------------------

|;
        foreach $table_selected (@table_list) {

# Write CREATE TABLE statement.
            if ($op_create_st) {
                print "#\n# Table structure for table '$table_selected'\n#\n";
                if ($add_drop_table) {
                    print "DROP TABLE IF EXISTS $table_selected;\n";
                }
                print construct_create_table_statement($table_selected);
            }

# Write INSERT statements.
            if ($op_dump_db) {
                print "#\n# Dumping data for table '$table_selected'\n#\n\n";
                if ( $CFG{'dump_in_pages'} ) {
                    $query = "SELECT COUNT(*) FROM $table_selected";
                    $sth = exec_query($query) or return;
                    my ($rows) = $sth->fetchrow_array;
                    my $pages = $rows / $CFG{'dump_page_length'};
                    $pages = int($pages) + 1;
                    for ( my $j = 1 ; $j <= $pages ; $j++ ) {
                        print data_dump( $table_selected, $j, %ins_options );
                    }
                    $sth->finish;
                }
                else {
                    print data_dump( $table_selected, '', %ins_options );
                }
                print "\n";
            }
        }
        print "# ----------- Dump ends -----------";

    }
    else {
        if ( !$file ) {
            sqlerr('Please provide a file name for mysqldump.');
            return;
        }

# Check if dump file specified already existed.
        if ( not dump_file_check($file) ) { return }

# create and dump to file.
        open( DUMP_FILE, ">>$file" )
          or cgierr("Unable to create dump file '$file': $!");

        print DUMP_FILE qq|# MySQL dump
# Generated by MySQLMan $VERSION (http://gossamer-threads.com/scripts/)
# Host: $db_host    Database: | . get_db( $IN->param('data_source') ) . qq| 
#--------------------------------------------------------

|;
        foreach $table_selected (@table_list) {

# Write CREATE TABLE statement.
            if ($op_create_st) {
                print DUMP_FILE
                  "#\n# Table structure for table '$table_selected'\n#\n";
                if ($add_drop_table) {
                    print DUMP_FILE "DROP TABLE IF EXISTS $table_selected;\n";
                }
                print DUMP_FILE construct_create_table_statement(
                    $table_selected);
            }

# Write INSERT statements.
            if ($op_dump_db) {
                print DUMP_FILE
                  "#\n# Dumping data for table '$table_selected'\n#\n\n";
                if ( $CFG{'dump_in_pages'} ) {
                    $query = "SELECT COUNT(*) FROM $table_selected";
                    $sth = exec_query($query) or return;
                    my ($rows) = $sth->fetchrow_array;
                    my $pages = $rows / $CFG{'dump_page_length'};
                    $pages = int($pages) + 1;
                    for ( my $j = 1 ; $j <= $pages ; $j++ ) {
                        print DUMP_FILE data_dump( $table_selected, $j,
                            %ins_options );
                    }
                    $sth->finish;
                }
                else {
                    print DUMP_FILE data_dump( $table_selected, '',
                        %ins_options );
                }
                print DUMP_FILE "\n";
            }
        }
        print DUMP_FILE "# ----------- Dump file ends -----------";
        close(DUMP_FILE);

        show_tables( "Dump file created" );
    }
}

sub construct_create_table_statement {
# -----------------------------------------------------------------------------
# Given a table name, construct a a CREATE TABLE statement
# that creates the exact table.

    my ($table) = @_;
    my (
        $sth,                 $query,    $create_query,
        $col_spec,            $type,     $key_name,
        $non_unique,          $not_null, @key_col,
        @build_key_statement, $key_spec
    );
    my ( $field, $type_name, $length_set, $attributes, $null, $key, $default,
        $extra );

    my @col_spec = ();

    $query = qq~DESCRIBE $table~;
    $sth   = exec_query($query) or return;

# Construct column specifications.
    while ( my @ary = $sth->fetchrow_array ) {
        (
            $field, $type_name, $length_set, $attributes, $null, $key, $default,
            $extra
          )
          = parse_col_spec(@ary)
          or return;
        if ($null) { $not_null = '' }
        else { $not_null = 'NOT NULL' }
        if ($length_set) { $length_set = "($length_set)" }
        if ( ( defined $default ) or ($not_null) ) {
            if ( not defined $default ) { $default = ''; }
            $default = "DEFAULT " . $DBH->quote($default);
        }
        $col_spec =
"  $field $type_name$length_set $attributes $default $not_null $extra";
        push ( @col_spec, $col_spec );
    }
    $sth->finish;

# find out if there are keys in the table.
    $query = qq~SHOW KEYS FROM $table~;
    $sth   = exec_query($query) or return;

    $key_name            = '';
    @build_key_statement = ();
    while ( my @ary = $sth->fetchrow_array ) {
        my $cur_non_unique = $ary[1];
        my $cur_key_name   = $ary[2];
        my $col            = $ary[4];
        if ( $key_name eq $cur_key_name ) { push ( @key_col, $col ); }
        else {
            if ($key_name) {
                if ( $key_name eq 'PRIMARY' ) {
                    $key_spec =
                      '  PRIMARY KEY (' . join ( ",", @key_col ) . ')';
                }
                elsif ($non_unique) {
                    $key_spec =
                      "  KEY $key_name (" . join ( ",", @key_col ) . ')';
                }
                else {
                    $key_spec =
                      "  UNIQUE $key_name (" . join ( ",", @key_col ) . ')';
                }
                push ( @build_key_statement, $key_spec );
            }
            @key_col = ();
            push ( @key_col, $col );
            $key_name   = $cur_key_name;
            $non_unique = $cur_non_unique;
        }
    }

    $sth->finish;

    if ($key_name) {
        if ( $key_name eq 'PRIMARY' ) {
            $key_spec = '  PRIMARY KEY (' . join ( ",", @key_col ) . ')';
        }
        elsif ($non_unique) {
            $key_spec = "  KEY $key_name (" . join ( ",", @key_col ) . ')';
        }
        else {
            $key_spec = "  UNIQUE $key_name (" . join ( ",", @key_col ) . ')';
        }
        push ( @build_key_statement, $key_spec );
    }

    if (@build_key_statement) {
        $create_query =
          "CREATE TABLE $table (\n"
          . join ( ",\n", @col_spec ) . ",\n"
          . join ( ",\n", @build_key_statement )
          . "\n);\n\n";
    }
    else {
        $create_query =
          "CREATE TABLE $table (\n" . join ( ",\n", @col_spec ) . "\n);\n\n";
    }

    return $create_query;
}

sub data_dump {
# -----------------------------------------------------------------------------
# Construct INSERT TABLE statements for a given table.

    my ( $table, $page, %options ) = @_;
    my ( $sth, $query, $count, @ary, $field, @records, @fields,
        $insert_statement, $not_first_rec );

    my $complete_insert = $options{'complete_insert'} || '';
    my $delayed_insert  = $options{'delayed_insert'}  || '';
    my $extended_insert = $options{'extended_insert'} || '';

    $query = qq~SELECT COUNT(*) FROM $table~;
    $sth = exec_query($query) or return;
    ($count) = $sth->fetchrow_array;
    $sth->finish;

    if ( !$page ) { $query = "SELECT * FROM $table"; }
    else {
        my $start_row = $CFG{'dump_page_length'} * ( $page - 1 );
        $query =
          "SELECT * FROM $table LIMIT $start_row, $CFG{dump_page_length}";
    }
    $sth = exec_query($query) or return;

# if complete-insert option is selected, construct the fields in the table.
    my $complete_op_fields = '';
    my @field_list         = ();
    if ($complete_insert) {
        for ( my $i = 0 ; $i < $sth->{NUM_OF_FIELDS} ; $i++ ) {
            push ( @field_list, $sth->{NAME}->[$i] );
        }
        $complete_op_fields = '(' . join ( ",", @field_list ) . ')';
    }

# if delayed-insert option is selected, added DELAYED keyword in INSERT statements.
    my $delayed_keyword = '';
    if ($delayed_insert) { $delayed_keyword = 'DELAYED' }

    $not_first_rec = 0;
    while ( @ary = $sth->fetchrow_array ) {
        @fields = ();
        foreach $field (@ary) {
            if ( defined($field) ) { push ( @fields, $DBH->quote($field) ); }
            else { push ( @fields, 'NULL' ) }
        }

        if ($extended_insert) {
            if ($not_first_rec) {
                $insert_statement .= ",(" . join ( ",", @fields ) . ")";
            }
            else {
                $insert_statement =
"INSERT $delayed_keyword INTO $table $complete_op_fields VALUES("
                  . join ( ",", @fields ) . ")";
                $not_first_rec = 1;
            }
        }
        else {
            $insert_statement =
              "INSERT $delayed_keyword INTO $table $complete_op_fields VALUES("
              . join ( ",", @fields ) . ");\n";
        }

        if ( !$extended_insert ) { push ( @records, $insert_statement ); }
    }
    if ($extended_insert) {
        if ($insert_statement) { $insert_statement .= ";\n"; }
        push ( @records, $insert_statement );
    }

    $sth->finish;
    return @records;
}

sub dump_file_check {
# -----------------------------------------------------------------------------
# Check to see if the dump file specified already existed.
# returns 1 if there isn't such file; undef if there is.

    my $file = shift;
    my ($dir) = $file =~ m,(.*)[/\\][^/\\]+,;
    if ( -e $file ) {
        sqlerr("File '$file' already exists.  Please use another file name.");
        return;
    }
    elsif ( $dir and !-w $dir ) {
        sqlerr(
"Can not create file in '$dir' directory. Please use another file name."
        );
        return;
    }
    elsif ( !$dir and !-w '.' ) {
        sqlerr(
"Can not create file in '.' directory. Please use another file name."
        );
        return;
    }
    else {
        return 1;
    }
}

sub get_checkbox_tables_in_database {
# -----------------------------------------------------------------------------
# get the list of tables of the database that the user
# is currently in and create a checkbox for each name for
# SQL dump
#
    my ( $sth, $query, $table, $table_checkboxes );

    if ( !$DBH ) { connect_db() or return; }

    $query = "SHOW TABLES";
    $sth   = exec_query($query) or return;

    $table_checkboxes = [];
    while ( ($table) = $sth->fetchrow_array ) {
        push @$table_checkboxes, { name => $table };
    }
    $sth->finish;
    return $table_checkboxes;
}

#=================================================#
#                  UTILITIES                      #
#=================================================#

sub build_saved_query_table {
# -----------------------------------------------------------------------------
# build a table for queries saved.
#
    my $query_table = [];

    if ( $IN->cookie('query_1') ) {
        for ( my $j = 10 ; $j >= 1 ; $j-- ) {

            my $partial_query;
            my @temp = split / /, $IN->cookie("query_$j");

            if ( $#temp > 10 ) {
                $partial_query =
"$temp[0] $temp[1] $temp[2] ... $temp[$#temp-2] $temp[$#temp-2] $temp[$#temp]";
            }
            else {
                $partial_query = $IN->cookie("query_$j");
            }

            if ( $IN->cookie("query_$j") ) {
                push @$query_table, {
                    num           => $j,
                    partial_query => $IN->html_escape($partial_query),
                    show_link     => qq~$CFG{'script_url'}?$CFG{do}=show_query&query_displayed=$j~
                };
            }
        }
    }

    return $query_table;
}

sub create_temp_file {
# -----------------------------------------------------------------------------
# creates a temp file and return the path to the file.
#
    my $temp_file = get_temp_file_name();
    my $fh        = $IN->param('upload_local_file') || '';

    if ( !$fh ) { sqlerr('Query file not specified.'); return; }

    if ( !open( OUTFILE, ">$temp_file" ) ) {
        cgierr(
            "There was an error opening temp file '$temp_file' for writing.\n");
    }

    open( OUTFILE, ">>$temp_file" );
    my ($buffer);
    while ( read( $fh, $buffer, 1024 ) ) {
        print OUTFILE $buffer;
    }

    close($fh);
    close(OUTFILE);
    chmod( 0666, "$temp_file" );

    return $temp_file;
}

sub get_temp_file_name {
# -----------------------------------------------------------------------------
# This function creates a random temp file name, looks
# up the temp directory and returns the path to the
# temp file.

    my $temp_file_path = '';
    my $directory      = '.';
    my @temp_dirs      =
      ( "/usr/tmp", "/var/tmp", "C:/temp", "/tmp", "/temp", "/WWW_ROOT" );
    foreach (@temp_dirs) {
        if ( -d $_ && -w _ ) { $directory = $_; last; }
    }
    my $rand_name = "GTMM" . time . $$ . int( rand(1000) );
    while ( -e $directory . '/' . $rand_name ) {
        $rand_name = "GTMM" . time . $$ . int( rand(1000) );
    }
    $temp_file_path = "$directory/$rand_name";
    return $temp_file_path;
}

sub parse_col_spec {
# -----------------------------------------------------------------------------
# Given a column specification from DESCRIBE TABLE, the function
# finds out
#    field name
#    typename
#    length/set
#    attributes
#    if nullable
#    if set as key
#    default
#    extra property.

    my @ary = @_;

    my $field   = $ary[0];
    my $type    = $ary[1];
    my $null    = $ary[2];
    my $key     = $ary[3];
    my $default = $ary[4];
    my $extra   = $ary[5];

    my (
        $chop_att,      $attributes,     $dump,         $value_q,
        $value_unquote, $type_name,      $length_set,   @type_field,
        @length_set,    @new_length_set, $new_length_set
    );

# Get column type.
    ( $type_name, $dump ) = split /([(])/, $type, 2;

# Get length/set
    $length_set = '';

    my @t = split / /, $type;
    if (   ( $t[0] ne $t[$#t] )
        && ( ( $type_name ne 'set' ) && ( $type_name ne 'enum' ) ) )
    {
        for ( my $i = 0 ; $i < $#t ; $i++ ) { $chop_att .= $t[$i] }
    }
    else { $chop_att = $type }
    my @tmp = split /([()])/, $chop_att;
    my $flag = 0;
    for ( my $i = 0 ; $i < $#tmp ; $i++ ) {
        if ($flag) { $length_set .= $tmp[$i]; }
        if ( $tmp[$i] eq '(' ) { $flag = 1; }
    }

# Get Attribute.
    @type_field = split / /, $type;
    if (   ( $type_field[0] ne $type_field[$#type_field] )
        && ( ( $type_name ne 'set' ) && ( $type_name ne 'enum' ) ) )
    {
        $attributes = $type_field[$#type_field];
    }
    else { $attributes = ''; }

    if ( $type_name eq 'set' || $type_name eq 'enum' ) {

# Get the elements in length/set.
        @length_set = parse_length_set($length_set);

        foreach (@length_set) {
            if ( $_ ne "''" ) {
                ($value_unquote) = $_ =~ m{^\'(([^\']|\'\')+)\'};
                $value_unquote =~ s/''/\\'/g;
                $value_unquote =~ s/,/\\,/g;
                $value_q = "'" . $value_unquote . "'";
            }
            push ( @new_length_set, $value_q );
        }
        $new_length_set = join ",", @new_length_set;
    }
    else { $new_length_set = $length_set }

    return ( $field, $type_name, $new_length_set, $attributes, $null, $key,
        $default, $extra );
}

sub set_col_not_null {
# -----------------------------------------------------------------------------
# Takes in a table name and the column name.  Then
# function will make the column specified not nullable.
# It will first read in the current spec of the column
# and then reconstruct the spec to set the column not null.
#
    my $table = $IN->param('table') || '';
    my $col   = $IN->param('col') || '';

    my ( $sth, $query, @attr, $new_spec, $col_q );

    $col_q = $DBH->quote($col);

    if ( !$DBH ) { connect_db() or return; }
    $query = "SHOW COLUMNS FROM $table LIKE $col_q";
    $sth   = exec_query($query) or return;
    @attr  = $sth->fetchrow_array();

# reconstruct spec and set column not null.
    for ( my $i = 0 ; $i < $sth->{NUM_OF_FIELDS} ; $i++ ) {
        if ( $i == 2 ) { $new_spec .= ' NOT NULL '; }
        elsif ( ( $i == 4 ) && ( defined( $attr[$i] ) ) ) {
            $new_spec .= " DEFAULT " . $DBH->quote( $attr[$i] );
        }
        elsif ( ( $i == 3 ) || ( $i == 6 ) ) { }
        else { $new_spec .= " $attr[$i] "; }
    }
    $sth->finish;

    $query = "ALTER TABLE $table CHANGE $col $new_spec";
    $sth = exec_query($query) or return;
    $sth->finish;
}

sub get_pri_key {
# -----------------------------------------------------------------------------
# Gets the primary key of the table specified.

    my $table = shift;
    my ( $sth, $query, @ary, @pri_key );

    @pri_key = ();

    $query = "DESCRIBE $table";
    $sth = exec_query($query) or return;
    while ( @ary = $sth->fetchrow_array ) {
        if ( $ary[3] eq 'PRI' ) { push ( @pri_key, $ary[0] ); }
    }
    $sth->finish;

    return @pri_key;
}

sub get_table_list {
# -----------------------------------------------------------------------------
# Gets the list of tables which the query input is querying
# from.  Since the function is only used in "sub table_browse";
#

    my $query = shift;
    my (
        @query,          $token,     $flag, @table_list,
        $cur_token,      $pre_token, $stop, $cmd,
        $explain_select, $got_list
    );

#strip beginning and endding space.
    $query =~ s/^\s+//;
    $query =~ s/\s+$//;
    $query =~ s/(\r|\n)+/ /g;

    @query = split /([ ,])/, $query;
    $cmd   = lc( $query[0] );

# find table list from:
# 1. describe / desc
# 2. explain table
    if ( ( $cmd eq 'describe' ) || ( $cmd eq 'desc' ) || ( $cmd eq 'explain' ) )
    {

        @table_list = ();
        $flag       = 0;

        for ( my $i = 1 ; $i <= $#query ; $i++ ) {
            if ( defined( $query[$i] ) ) {
                if (   ( $query[$i] ne '' )
                    && ( $query[$i] ne ' ' )
                    && ( $query[$i] ne ',' ) )
                {

# get the first name after the command.
                    if ( $flag == 0 ) {
                        push ( @table_list, $query[$i] );
                        my $tmp = lc( $query[$i] );
                        if ( $tmp eq 'select' ) { $explain_select = 1 }
                        $flag = 1;
                    }
                }
            }
        }
        $got_list = 1;
    }

# find table list from queries like:
# 1. select queries
# 2. show queries
# 3. explain select ....
    if ( !$got_list || $explain_select ) {

        @table_list = ();
        $flag       = 0;
        $stop       = 0;
        $pre_token  = '';

        foreach (@query) {
            if ( ( $_ ne '' ) && ( $_ ne ' ' ) ) {
                $token = lc($_);
                if ( $flag == 1 ) {

# determine the type of the current token.
                    if ( $token ne ',' ) { $cur_token = 'word' }
                    else { $cur_token = 'comma' }

# stop then the "from" clause ends
                    if (   ( $cur_token eq 'word' )
                        && ( $cur_token eq $pre_token ) )
                    {
                        $stop = 1;
                    }

                    if ( !$stop && $token ne ',' ) { push ( @table_list, $_ ) }
                    $pre_token = $cur_token;
                }
                if ( $token eq 'from' ) { $flag = 1; }
            }
        }
    }
    return @table_list;
}

sub form_fields {
# -----------------------------------------------------------------------------
# Create a form input table for insert and edit.  $update
# is a flag indicating whether or not it is from edit.  @value
# consists the list of original values (in order) in the
# record being updated.  Note that in order to set a field
# to be null, the input value has to be null.  In other words,
# if there is value in input field and null checkbox is checked,
# the null option will be overwritten and the value in the input
# field will be taken.
#
# Option: you can set it in the config file such that any colume
# with type TIMESTAMP will not be shown in the form.

    my ( $update, @value ) = @_;

    my $table = $IN->param('table') || '';
    my (
        $query,         $sth,    $form_table,   @ary,
        @type,          @domain, @domain_new,   %domain_h,
        $value_unquote, $flag,   $double_comma, $length_set
    );

    my @timestamp_hidden = ();

    if ( !$DBH ) { connect_db() or return; }

    $query = "DESCRIBE $table";
    $sth   = exec_query($query) or return;

    $form_table = [];

    my $k = 0
      ; # Value counter.  Used to identify which element in @value is considered.

    while ( @ary = $sth->fetchrow_array ) {
        my $tabindex_count = $k + 1;

# $ary[1] is in the form "type_name(length_set) attribute"
#
        @type = split /([(])/, $ary[1];
        $length_set = '';
        for ( my $j = 2 ; $j <= $#type ; $j++ ) { $length_set .= $type[$j]; }
        chop $length_set;

        my $ins = {
            field => $ary[0],
            type  => $type[0],
        };
        if ( $CFG{'insert_null'} ) {
            if ( $ary[2] eq 'YES' ) {
                $ins->{nullable} = 1;
                if ( !defined( $value[$k] ) && $update ) {
                    $ins->{null} = 1;
                }
                else {
                    $ins->{null} = 0;
                }
            }
            else {
                $ins->{nullable} = 0;
            }
        }

# Handle type 'Enum'
        if ( $type[0] eq 'enum' ) {
            $ins->{function_select} = function_select($ary[0]);
            my $select = '';
            if ( defined( $value[$k] ) && $update ) {
                $select .= qq~<option>$value[$k]</option>~;
            }

# Create an empty choice.  This choice available even if it is not specified in
# enum.  We need it in case the value needs to be null.
            $select .= qq~\n<option></option>~;

# All other choices in specified in the enumeration.
            @type = parse_length_set($length_set);

            for ( my $i = 0 ; $i <= $#type ; $i++ ) {
                if (   defined( $type[$i] )
                    && $type[$i] ne "''"
                    && $type[$i] ne '' )
                {
                    ($value_unquote) = $type[$i] =~ m{^\'(([^\']|\'\')+)\'};

# Since MySQL stores single quotes in 2 single quotes in column specs,
# we translate them back to 1.
                    $value_unquote =~ s/''/'/g;
                    $value_unquote =~ s/\\\\/\\/g; #/
                    $select .= qq~\n<option>$value_unquote</option>~;
                }
            }

            $ins->{enum_select_options} = \$select;
            push @$form_table, $ins;
        }

# Handle type 'Set'
        elsif ( $type[0] eq 'set' ) {

            my $j = 0;

            $ins->function_select( $ary[0]);

# For update, check if '' is in the set.
            @domain = split /(,)/, $value[$k];
            foreach (@domain) {
                if ( $_ ne ',' ) {
                    if ( $_ ne '' ) { push ( @domain_new, $_ ) }
                    else { push ( @domain_new, "''" ) }
                }
            }
            if ( $domain[$#domain] eq ',' ) { push ( @domain_new, "''" ); }
            %domain_h = map { $_ => 1 } @domain_new;

            @type = parse_length_set($length_set);
            my @set_options;

            for ( my $i = 0 ; $i <= $#type ; $i++ ) {

                if ( defined( $type[$i] ) && $type[$i] ne '' ) {

                    if ( $type[$i] ne "''" ) {
                        ($value_unquote) = $type[$i] =~ m{^\'(([^\']|\'\')+)\'};
                    }
                    else { $value_unquote = $type[$i]; }

                    if ( $value_unquote ne "''" ) {
                        $value_unquote =~ s/''/'/g;
                    }
                    $value_unquote =~ s/\\\\/\\/g; #//

# Create checkboxes for each element in the set.  Checkboxes are checked if
# value is selected in the original set.
                    my $opt = {
                        value   => $IN->html_escape($value_unquote),
                        num     => $j,
                        checked => 0
                    };
                    if ( $domain_h{$value_unquote} ) {
                        $opt->{checked} = 1;
                    }
                    push @set_options, $opt;
                    $j++;
                }
            }
            $ins->{set_options} = \@set_options;
            push @$form_table, $ins;
        }

# Handle all other types
        else {
            my $type_lookup  = lc( $ary[1] );
            my $hidden_field = '';

# hide columns with type TIMESTAMP if necessary.
            if ( ( $type_lookup =~ m/timestamp/ )
                && !$CFG{'show_timestamp_field'} )
            {
                if ( !$update ) {
                    $hidden_field = qq~<INPUT TYPE="hidden" NAME="*insert*_$ary[0]_null" VALUE="NULL">~;
                    push ( @timestamp_hidden, $hidden_field );
                }
                else {
                    $hidden_field =
                      qq~<INPUT TYPE="hidden" NAME="*insert*_$ary[0]" VALUE="~
                      . $IN->html_escape( $value[$k] ) . qq~">~;
                    push ( @timestamp_hidden, $hidden_field );
                }
            }
            else {
                $ins->{value} = \$IN->html_escape( $value[$k] );
                $ins->{function_select} = function_select( $ary[0] );
                push @$form_table, $ins;
            }
        }
        $k++;
    }
    $sth->finish;

    my $form_hidden = '';
    if (@timestamp_hidden) {
        $form_hidden = \join ( "", @timestamp_hidden );
    }

    return($form_table, $form_hidden);
}

sub parse_length_set {
# -----------------------------------------------------------------------------
# A simple parser that gets each element in the length set field.
# Basically, it counts the number for sigle quotes.  Each element
# must have even number of quotes.  If a comma encountered, then
# the check the number for quotes seen so far.  If the number is odd,
# then concate the comma to temp string, otherwise, push the current
# tmep string to array.

    my ($length_set) = @_;

    my @type = split /([,'])/,
      $length_set;    # list of quoted elements in length_set
    my @new_type   = ();
    my $remainder  = 0;
    my $cur_choice = '';
    my $q_count    = 0;
    my $begin      = 0;
    my $k          = 0;

    foreach my $element (@type) {
        if ( $k == $#type ) {
            $cur_choice .= $element;
            push ( @new_type, $cur_choice );
        }
        elsif ( $element ne '' ) {
            if ( $element eq ',' ) {
                $remainder = $q_count % 2;
                if ($remainder) {
                    if ($begin) {
                        push ( @new_type, $cur_choice );
                        $begin      = 0;
                        $q_count    = 0;
                        $cur_choice = '';
                    }
                    else {
                        $begin = 1;
                        $cur_choice .= $element;
                        $q_count = 0;
                    }
                }
                else {
                    if ($begin) {
                        $cur_choice .= $element;
                    }
                    else {
                        push ( @new_type, $cur_choice );
                        $q_count    = 0;
                        $cur_choice = '';
                    }
                }
            }
            elsif ( $element eq "'" ) {
                $q_count++;
                $cur_choice .= $element;
            }
            else {
                $cur_choice .= $element;
            }
        }
        $k++;
    }
    return @new_type;
}

sub compose_new_condition {
# -----------------------------------------------------------------------------
# Reconstructs the input from "sub form_fields" to an array
# of "field = value" pairs.
#
# The functions is modified after v1.03
# The way that the script inserts a record is changed to
#
# INSERT INTO tablename (column1,column2,...columnn)
# VALUES (value1,value2, .. valuen)
#
# as the SET col=val isn't supported by all versions of mysql.
# Therefore, if is_insert flag in on, the function returns
# a array in the following format:
#
# (field_name_1, value_for_field_name_1, field_name_2, value_for_field_name_2, ...)
#
# Please note that the value in $value is quoted.
#

    my ( $is_insert ) = @_;
    my $table = $IN->param('table') || '';
    my (
        $query,      $prep,          $sth,  @ary,
        $value,      @insert_fields, @type, @set,
        $new_record, $value_unquote
    );

    $query = "DESCRIBE $table";
    $prep  = exec_query($query) or return;

    while ( @ary = $prep->fetchrow_array ) {

        @type = split /[(),]/, $ary[1];

# Handle columns of type 'SET'
        if ( $type[0] eq 'set' ) {
            my $j = 0;    # checkbox counter
            my $k = 0;    # counter for how many checkboxes are checked.

# check each checkbox box to see if they are check.
            for ( my $i = 1 ; $i < ( $#type + 1 ) ; $i++ ) {
                ($value_unquote) = $type[$i] =~ m{^\'(([^\']|\'\')+)\'};
                if ( defined( $type[$i] ) ) {
                    if ( defined( $IN->param("*insert*_$ary[0]_set_$j") ) ) {
                        if ( $IN->param("*insert*_$ary[0]_set_$j") ne "''" ) {
                            push ( @set,
                                $IN->param("*insert*_$ary[0]_set_$j") );
                        }
                        else { push ( @set, '' ); }
                        $k++;
                    }
                    $j++;
                }
            }

            $value = $DBH->quote( join ( ",", @set ) );

# If none of the checkboxes is checked, check for null option.
            if ( !$k ) {
                if ( $IN->param("*insert*_$ary[0]_null") ) { $value = 'NULL'; }
                else { $value = '""'; }
            }

        }

# Handle all other types.
        else {

# if nothing in input field.
            if ( !$IN->param("*insert*_$ary[0]")
                && ( $IN->param("*insert*_$ary[0]") ne '0' ) )
            {

# check for null
                if ( $IN->param("*insert*_$ary[0]_null") ) { $value = 'NULL'; }

# check for function.
                else {
                    if ( $IN->param("*insert*_$ary[0]_function") ) {
                        $value = $IN->param("*insert*_$ary[0]_function") . '()';
                    }
                    else { $value = '""'; }
                }
            }

# check if any function is needed to apply on the input.
            elsif ( $IN->param("*insert*_$ary[0]_function") ) {
                $value =
                  $IN->param("*insert*_$ary[0]_function") . '('
                  . $DBH->quote( $IN->param("*insert*_$ary[0]") ) . ')';
            }

# otherwise make the field equal to the value entered.
            else { $value = $DBH->quote( $IN->param("*insert*_$ary[0]") ); }
        }
        if ($is_insert) {
            push ( @insert_fields, $ary[0] );
            push ( @insert_fields, $value );
        }
        else {
            push ( @insert_fields, "$ary[0] = $value" );
        }
    }
    $prep->finish;

    return (@insert_fields);
}

sub function_select {
# -----------------------------------------------------------------------------
# Creates enumeration of functions available in select input.

    my ( $field ) = @_;

    return \html_display(
        'functions.txt',
        {
            field    => $field,
            do_param => $CFG{do}
        },
        { print => 0 }
    );
}

sub get_key_table {
# -----------------------------------------------------------------------------
# Creates a key/index table.  A "drop" link is created
# together with each each read in.
#
    my $data_source = $IN->param('data_source') || '';
    my $table       = $IN->param('table') || '';
    my (
        $sth,      $query,       @ary,  $unique,  $non_unique,
        $key_name, $column_name, $keys
    );

    $query = "SHOW INDEX from $table";
    $sth = exec_query($query) or return;
    $keys = [];
    while ( @ary = $sth->fetchrow_array ) {
        my $non_unique  = $ary[1];
        my $key_name    = $ary[2];
        my $column_name = $ary[4];
        if ($non_unique) { $unique = 'NO'; }
        else { $unique = 'YES'; }
        push @$keys, {
            name        => $key_name,
            unique      => $unique,
            column_name => $column_name,
            drop_link   => qq~$CFG{'script_url'}?$CFG{do}=alter_table&data_source=$data_source&table=$table&action=drop_key&key_name=$key_name~
        };
    }
    $sth->finish;

    return $keys;
}

sub get_db {
# -----------------------------------------------------------------------------
# Gets the database name from the data source which is
# in the format "DBI:mysql:database_name:host"

    my $db = shift;

    my @dsn = split /([:])/, $db;
    $db = $dsn[4];
    if ( $CFG{'direct_connect'} ) { $db = $CFG{'direct_db'}; }

    return $db;
}

sub get_cols {
# -----------------------------------------------------------------------------
# Takes in a table name and return the columns in an ]
# array.

    my ( $table ) = @_;
    my ( $query, $sth, @cols );
    if ( !$DBH ) { connect_db() or return; }
    $query = "SELECT * FROM $table LIMIT 1";
    $sth = exec_query($query) or return;
    for ( my $i = 0 ; $i < $sth->{NUM_OF_FIELDS} ; $i++ ) {
        push ( @cols, $sth->{NAME}->[$i] );
    }
    $sth->finish;
    return @cols;
}

sub valid_name_check {
# -----------------------------------------------------------------------------
# Checks to see if the input database/table name is a
# valid one.  The function checks the following:
# 1. if a name is entered at all;
# 2. if there are spaces in the name;
# 3. if the name is consisted of valid characters; and
# 4. if the name is consisted of only numbers.

    my $name = shift;

    $name =~ s/^\s+//;
    $name =~ s/\s+$//;

    my @name = split / /, $name;

    if    ( !$name )     { sqlerr("Please provide a valid name."); }
    elsif ( $#name > 0 ) { sqlerr("Spaces are not allowed in name."); }
    elsif ( $name =~ m/[^\w_\$]/ ) {
        sqlerr(
            "Invalid name.  A name may consist of characters, numbers, and also '_' and '\$'."
        );
    }
    elsif ( !( $name =~ m/\D/ ) ) {
        sqlerr("Invalid name.  A name may not consist only of numbers.");
    }
    else { return 1; }
}

sub alias_name_check {
# -----------------------------------------------------------------------------
# Checks to see if the input table name is of the format
# 1. database.table.column  OR
# 2. table.column

    my ( $table ) = @_;
    my @alias_table = split /[.]/, $table;

    if ( $#alias_table > 0 ) {
        html_demo_prompt( 'Action not allowed in demo mode' );
    }
    else { return 1; }
}

sub concate_col_spec {
# -----------------------------------------------------------------------------
# Reconstruct the input variables into a string in the form
# "field_name(type(length_set) attribute DEFAULT default_value extra)"

    my ( $i ) = @_;
    my $col_spec;

    $col_spec = '';
    $col_spec .= $IN->param("field_$i") . ' ';
    $col_spec .= $IN->param("type_$i");
    if ( $IN->param("length_set_$i") ) {
        $col_spec .= '(' . $IN->param("length_set_$i") . ')';
    }
    $col_spec .= ' ' . $IN->param("attributes_$i") . ' ';
    $col_spec .= $IN->param("null_$i") . ' ';
    my $default = $IN->param("default_$i");
    if ( length $default ) {
        $col_spec .= 'DEFAULT ' . $DBH->quote($default) . ' ';
    }
    $col_spec .= $IN->param("extra_$i");

    return $col_spec;
}

sub link_page {
# -----------------------------------------------------------------------------
# Provides hyperlinks to next, previous, or top pages when needed

    my ( $rows, $table, $fields, $example, $index, $total_rec_num ) = @_;
    my (
        $data_source, $cur_page, $page,  $output,      $sort_index,
        $action,      $where,    $query, $cur_rec_num, $more_page
    );

    $data_source = $IN->param("data_source")   || '';
    $cur_page    = $IN->param("page")          || 1;
    $sort_index  = $index;
    $action      = $IN->param("browse_action") || $IN->param("action") || '';
    $where = $IN->escape( $IN->param("where") ) || '';
    $query = $IN->escape( $IN->param("query") ) || '';

    $example = $IN->escape($example);

    $cur_rec_num = ( $cur_page - 1 ) * $CFG{'page_length'} + $rows;
    if ( $cur_rec_num < $total_rec_num ) { $more_page = 1; }
    else { $more_page = 0; }

    $output = '';

# the very first page.
    if (    ( $cur_page == 1 )
        and ( $rows == $CFG{'page_length'} )
        and ($more_page) )
    {
        $page = $cur_page + 1;
        $output .= qq~&lt; <A href="$CFG{'script_url'}?$CFG{do}=browse&data_source=$data_source&table=$table&page=$page&sort_index=$sort_index&action=$action&fields=$fields&where=$where&example=$example&query=$query">Next page</A> &gt;~;
    }

# the very last page.
    elsif ( ( !$more_page ) and ( $cur_page != 1 ) ) {
        $page = $cur_page - 1;
        $output .= qq~&lt; <A href="$CFG{'script_url'}?$CFG{do}=browse&data_source=$data_source&table=$table&page=$page&sort_index=$sort_index&action=$action&fields=$fields&where=$where&example=$example&query=$query">Previous page</A> &gt; ~;
    }

# any page between the first and the last page.
    elsif ( ( $cur_page != 1 )
        and ( $rows == $CFG{'page_length'} )
        and ($more_page) )
    {
        $page = $cur_page + 1;
        $output .= qq~&lt; <A href="$CFG{'script_url'}?$CFG{do}=browse&data_source=$data_source&table=$table&page=$page&sort_index=$sort_index&action=$action&fields=$fields&where=$where&example=$example&query=$query">Next page</A> &gt; ~;
        $page = $cur_page - 1;
        $output .= qq~&lt; <A href="$CFG{'script_url'}?$CFG{do}=browse&data_source=$data_source&table=$table&page=$page&sort_index=$sort_index&action=$action&fields=$fields&where=$where&example=$example&query=$query">Previous page</A> &gt; ~;
    }

# else there is only one page to display.  As a result, no links are available.

# link to jump back to the first page.
    if ( ( $cur_page != 1 ) ) {
        $output .= qq~&lt; <A href="$CFG{'script_url'}?$CFG{do}=browse&data_source=$data_source&table=$table&page=1&sort_index=$sort_index&action=$action&fields=$fields&where=$where&example=$example&query=$query">Top page</A> &gt;~;
    }
    return $output;
}

sub link_page_jump {
# -----------------------------------------------------------------------------
# Produces a text field to let the user enter a number
# and the user will be brought to the page specified.

    my ( $sth, $table, $where, $action, $query, $data_source, $pages, $output );

    my ( $fields, $example, $index, $rows ) = @_;

    $data_source = $IN->param("data_source")   || '';
    $table       = $IN->param("table")         || '';
    $where       = $IN->param("where")         || '';
    $action      = $IN->param("browse_action") || $IN->param("action") || '';

    $pages = $rows / $CFG{'page_length'};
    if ( ( $rows % $CFG{'page_length'} ) != 0 ) { $pages = int($pages) + 1 }
    if ( $rows > $CFG{'page_length'} ) {
        return html_page_jump( $pages, $fields, $example, $index );
    }
    else { return ''; }
}

sub record_count {
# -----------------------------------------------------------------------------
# Counts to total number of records/rows in the table
# specified.

    my ( $sth, $rows, $query );

    my $tablename = shift;

    $query = "SELECT COUNT(*) FROM $tablename";
    $sth   = exec_query($query) or return;
    $rows  = $sth->fetchrow;
    $sth->finish;
    return $rows;
}

sub get_col_type {
# -----------------------------------------------------------------------------
# Gets the type each field/column of the table specified.

    my $table = shift;
    my ( $sth, $query, @ary, @type_ary );

    $query = "DESCRIBE $table";
    $sth = exec_query($query) or return;
    while ( @ary = $sth->fetchrow_array() ) {
        push ( @type_ary, $ary[1] );
    }
    $sth->finish;
    return @type_ary;
}

sub get_pre_query {
# -----------------------------------------------------------------------------

    my $i  = $IN->param('query_select') || '';

    if ( not $i ) {
        return 0;
    }
    else {
        return $IN->cookie("query_$i");
    }
}

sub do_login {
# -----------------------------------------------------------------------------
# Assign login info to cookies.
#
    assign_auth_cookie();

# redirects the user to the database list if init_login flag is on.
    if ( $IN->param('init_login') ) {
        my $redirect_url =
          $CFG{'script_url'}
          . "?$CFG{do}="
          . $IN->param( $CFG{do} )
          . '&data_source='
          . $IN->param('data_source')
          . '&init_login='
          . $IN->param('init_login');
        print $IN->redirect(
            -url    => $redirect_url,
            -cookie => \@COOKIES
        );
        return 1;
    }
    else {
        print $IN->header( -cookie => \@COOKIES );
    }

    return html_back();
}

sub assign_auth_cookie {
# -----------------------------------------------------------------------------
# assign values to cookies used in the scirpt.
#
    my $db_host = $IN->param('db_host');
    my $db_user = $IN->param('db_user');
    my $db_pass = $IN->param('db_pass');
    my $expire  = $IN->param('db_expire') ? "+5y" : "";

    push @COOKIES, $IN->cookie(
        -name     => $CFG{'db_host_cookie_name'},
        -value    => $db_host,
        -expires  => $expire,
        -httponly => 1,
    );

    push @COOKIES, $IN->cookie(
        -name     => $CFG{'db_user_cookie_name'},
        -value    => $db_user,
        -expires  => $expire,
        -httponly => 1,
    );

    push @COOKIES, $IN->cookie(
        -name     => $CFG{'db_pass_cookie_name'},
        -value    => $db_pass,
        -expires  => $expire,
        -httponly => 1,
    );
}

sub assign_cookies {
# -----------------------------------------------------------------------------
# assign the current url to cookie.  This function tells
# the script where to go to after each login.  Also, if a
# query is entered in the SQL monitor, the query will be saved
# as a cookie if requested.
#
    my $cur_url;
    if ( defined $ENV{REQUEST_METHOD}
        and ( uc $ENV{REQUEST_METHOD} ne 'POST' ) )
    {
        $cur_url = $IN->url;
    }
    else {
        $cur_url = $IN->url( query_string => 0 );
    }
    my $url_modified = 0;
    my $data_source  = $IN->param('data_source') || '';
    my $table        = $IN->param('table') || '';
    my $action       = $IN->param('action') || '';
    my $new_do       = '';
    my $new_action   = '';
    my $query        = $IN->param('query') || '';
    my $query_esc    = $IN->escape($query);

    if ( $IN->param( $CFG{do} ) eq 'database' ) {
        if ( $IN->param('comfirmed') || ( $action eq 'create_db' ) ) {
            $data_source  = '';
            $new_do       = '';
            $url_modified = 1;
        }
    }
    elsif ( $IN->param( $CFG{do} ) eq 'insert_record' ) {
        $new_do       = "$CFG{insert_origin}";
        $url_modified = 1;
    }
    elsif ( $IN->param( $CFG{do} ) eq 'modify' ) {
        if ( $IN->param('comfirmed') ) {
            $new_do       = 'tables';
            $url_modified = 1;
        }
    }
    elsif ( $IN->param( $CFG{do} ) eq 'create_table' ) {
        $new_do       = 'tables';
        $url_modified = 1;
    }
    elsif ( $IN->param( $CFG{do} ) eq 'alter_table' ) {
        if ( ( $action ne 'alter_col' ) && ( $action ne 'do_alter_col' ) ) {
            $new_do       = 'property';
            $url_modified = 1;
        }
    }
    elsif ($IN->param( $CFG{do} ) eq 'sql_monitor'
        || $IN->param( $CFG{do} ) eq 'sql_monitor_file' )
    {
        $new_do       = 'top_level_op';
        $new_action   = 'sql_monitor';
        $url_modified = 1;
    }
    elsif ( $IN->param( $CFG{do} ) eq 'import' ) {
        $new_do       = 'tables';
        $url_modified = 1;
    }
    elsif ( $IN->param( $CFG{do} ) eq 'export' ) {
        $new_do       = 'tables';
        $url_modified = 1;
    }
    elsif ( $IN->param( $CFG{do} ) eq 'mysqldump' ) {
        $new_do       = 'tables';
        $url_modified = 1;
    }
    else {    # do nothing
    }

    if ($url_modified) {
        $cur_url =
          $CFG{'script_url'}
          . "?$CFG{do}=$new_do&data_source=$data_source&action=$new_action&table=$table";
    }
    push @COOKIES, $IN->cookie(
        -name  => $CFG{'url_cookie_name'},
        -value => $cur_url,
        -path  => "/"
    );

# store URL to cookie.  Save query to cookie if needed.
    if ( $IN->param('from_monitor') ) {
        if ( $IN->param('save_query') ) {
            if ( $IN->cookie('query_10') ) {    # delete the oldest query

                for (1..9) {
                    push @COOKIES, $IN->cookie(
                        -name    => 'query_' . $_,
                        -value   => $IN->cookie('query_' . ($_+1) ),
                        -path    => "/",
                        -expires => '+1y'
                    );
                }

                my $redirect_url =
                  $CFG{'script_url'}
                  . "?$CFG{do}="
                  . $IN->param( $CFG{do} )
                  . '&data_source='
                  . $IN->param('data_source')
                  . "&action=monitor&from_monitor=1&query=$query_esc";
                print $IN->redirect(
                    -url    => $redirect_url,
                    -cookie => \@COOKIES
                );

                return;
            }
            else {    # add the query to the last slot.
                my $new_query;
                for my $i ( 1 .. 10 ) {
                    if ( not $IN->cookie("query_$i") ) {
                        push @COOKIES, $IN->cookie(
                            -name    => "query_$i",
                            -value   => $IN->param('query'),
                            -path    => "/",
                            -expires => '+1y'
                        );
                        last;
                    }
                }
                my $redirect_url =
                  $CFG{'script_url'}
                  . "?$CFG{do}="
                  . $IN->param( $CFG{do} )
                  . '&data_source='
                  . $IN->param('data_source')
                  . "&action=monitor&from_monitor=1&query=$query_esc";
                print $IN->redirect(
                    -url    => $redirect_url,
                    -cookie => \@COOKIES
                );

                return;
            }
        }
        elsif ( $IN->param('delete_query') ) {

            my @not_deleted_queries;
            for my $i ( 1 .. 10 ) {
                if ( !$IN->param("delete_query_$i") ) {
                    if ( !$IN->cookie("query_$i") ) {
                        push ( @not_deleted_queries, 0 );
                    }
                    else {
                        push ( @not_deleted_queries, $IN->cookie("query_$i") );
                    }
                }
            }

            for (1..10) {
                push @COOKIES, $IN->cookie(
                    -name    => 'query_' . $_,
                    -value   => $not_deleted_queries[$_ - 1] || '',
                    -path    => "/",
                    -expires => '+1y'
                );
            }

            my $redirect_url =
              $CFG{'script_url'}
              . "?$CFG{do}="
              . $IN->param( $CFG{do} )
              . '&data_source='
              . $IN->param('data_source');

            print $IN->redirect(
                -url    => $redirect_url,
                -cookie => \@COOKIES
            );

            return;
        }
    }

    return 1;
}

sub do_logout {
# -----------------------------------------------------------------------------
# Logs the users out by making all the cookies used in
# the script expire.
#
    cookie_cleanup();
    print $IN->header( -cookie => \@COOKIES );
    return html_logout( $IN );
}

sub cookie_cleanup {
# -----------------------------------------------------------------------------
# Makes all cookies expire.
#
    push @COOKIES, $IN->cookie(
        -name    => $CFG{'db_host_cookie_name'},
        -value   => '',
        -expires => '-1y',
        -path    => '/'
    );
    push @COOKIES, $IN->cookie(
        -name    => $CFG{'db_user_cookie_name'},
        -value   => '',
        -expires => '-1y',
        -path    => '/'
    );
    push @COOKIES, $IN->cookie(
        -name    => $CFG{'db_pass_cookie_name'},
        -value   => '',
        -expires => '-1y',
        -path    => '/'
    );
    push @COOKIES, $IN->cookie(
        -name    => $CFG{'url_cookie_name'},
        -value   => '',
        -expires => '-1y',
        -path    => '/'
    );
}

sub connect_db {
# -----------------------------------------------------------------------------
# Tries to connect the database with user name and password
# provided first.  If access denied then tries connecting
# again with user name and password undefined.  If both
# fail then an login page will be prompted.


    my ( $db, $db_host, $db_port, $username, $password, $message );
    my $data_source = $IN->param("data_source") || 'DBI:mysql:';
    my $init_login  = $IN->param('init_login');
    my $cur_db      = get_db( $IN->param('data_source') ) || '',

# gets user data from cookies.
    $username = $IN->cookie( $CFG{'db_user_cookie_name'} ) || '';
    $password = $IN->cookie( $CFG{'db_pass_cookie_name'} ) || '';
    $db_host  = $IN->cookie( $CFG{'db_host_cookie_name'} ) || 'localhost';

    my $db_connection = $IN->param("db_for_connection") || '';

    if ($db_connection) {
        $data_source = "DBI:mysql:" . $IN->param("db_for_connection");
    }

# while under demo mode, check if the user is using the host specified and make sure
# the script only connect to the database specified.


    if ( $db_host && ( $data_source eq 'DBI:mysql:' ) ) {
        $data_source = "DBI:mysql:host=$db_host";
    }
    else { $data_source = $data_source . ":$db_host" }

# set connect info to the info specified in the config file if direct_connect is on.
    if ( $CFG{'direct_connect'} ) {
        $db       = $CFG{'direct_db'};
        $username = $CFG{'direct_user'};
        $password = $CFG{'direct_pass'};
        $db_host  = $CFG{'direct_host'};
        $db_port  = $CFG{'direct_port'};

        $data_source = "DBI:mysql:$db";
        if ($db_host) { $data_source .= ":$db_host"; }
        if ($db_port) { $data_source .= ":$db_port"; }
    }

# while under demo mode, check if the user is using the database specified.

# connects to mysql.
    $DBH = DBI->connect( 
        "$data_source", 
        "$username", 
        "$password",
        { 
            RaiseError => 0, 
            PrintError => 0, 
            AutoCommit => 1 
        } 
    );

    if ( not $DBH ) {
        my $orig_error = $DBI::errstr;

# If the connection fails then the user name and/or the password may be wrong or that they are correct but the
# the connection fail for some other reason

        if ( $DBI::errstr =~ m/(.ccess denied)/ )
        {    # If the user name and/or the password is incorrect
            $DBH =
              DBI->connect( "$data_source", undef, undef,
                { RaiseError => 0, PrintError => 0, AutoCommit => 1 } )
              or return sqlerr($orig_error);
        }
        elsif ( $DBI::errstr =~ m/(.nknown database)/ )
        {    # database specified not existed
            return sqlerr("$DBI::errstr.");
        }
        elsif (( $DBI::errstr =~ m/an't connect to/ )
            || ( $DBI::errstr =~ m/Unknown MySQL Server Host/ ) )
        {
            return sqlerr("$DBI::errstr.");
        }
        else { cgierr("connection failed: $DBI::errstr"); }
    }
    return 1;
}

sub exec_query {
# -----------------------------------------------------------------------------
# Send the input qeury MySQL thru database handler.

    my $query = shift;
    my ($sth);

    
    $sth = $DBH->prepare($query) 
        or return sqlerr( \( GT::CGI->html_escape( $DBI::errstr ) . " <P>Query: " . GT::CGI->html_escape( $query ) ) );
    $sth->execute() 
        or return sqlerr( \( GT::CGI->html_escape( $DBI::errstr ) . " <P>Query: " . GT::CGI->html_escape( $query ) ) );

    return $sth;
}

sub sqlerr {
# -----------------------------------------------------------------------------
# Error prompt.
#
    my $error = shift;
    my ( $message, $init_login );

    $init_login = 0;
    my $db_connection = $IN->param("db_for_connection") || '';

# If access denied, then a login page will be displayed.
    if ( $DBI::errstr =~ m/(.ccess denied)/ ) {
        $error   = GT::CGI->html_escape( $error );
        $message = \"<p>Permission to perform action denied!</p><blockquote>MySQL Said: $error</blockquote><p>Please enter your user name and password";
        my $args = $IN->get_hash;
        if ( !keys %$args or ( $IN->param( $CFG{do} ) eq 'login' ) ) {
            $message = 'Welcome! Please enter your log-in info.';

            $init_login = 1;
        }
        elsif ( $IN->param('init_login') ) {

            $message = 'Login failed.  Please enter another hostname/username/password.';
            $init_login = 1;
        }
        &html_login( $message, $init_login );
        if ( $CFG{'debug'} ) { cgierr("debug"); }
        return;
    }

# If connect error, login page is prompted to let the user to enter another
# host name.
    elsif (
        ( $DBI::errstr =~ m/an't connect to/ )
        or ( $DBI::errstr =~ m/Unknown MySQL Server Host/ ) 
    )
    {
        $message = qq~Connection to MySQL failed.
                      The hostname may be different or the server may be down.
                      Please enter a new hostname and try again.~;
        my $args = $IN->get_hash;
        if ( !keys %$args or ( $IN->param( $CFG{do} ) eq 'login' ) ) {
            $message = 'Welcome! Please enter your log-in info.';

            $init_login = 1;
        }
        elsif ( $IN->param('init_login') ) {

            $message = 'Login failed.  Please enter another hostname/username/password.';
            $init_login = 1;
        }
        html_login( $message, $init_login );
        if ( $CFG{'debug'} ) { cgierr("debug"); }
        return;
    }

# database specified not existed
    elsif ( $DBI::errstr =~ m/(.nknown database)/ ) {
        if ( !$db_connection ) {
            $message = "Connection to MySQL failed.  A database name is needed for connection.";
            html_login_dbname( $message, $init_login );
            if ( $CFG{'debug'} ) { cgierr("debug"); }
            return;
        }
        else {
            $message = "Unknown database '$db_connection'.  Please enter another database name.";
            html_login_dbname( $message, $init_login );
            if ( $CFG{'debug'} ) { cgierr("debug"); }
            return;
        }
    }

# display the error message.
    else {

        if ( $CFG{'debug'} ) {
            cgierr($error);
        }
        else {
            html_sqlerr( $error );
            if ( $CFG{'debug'} ) { cgierr("debug"); }
            return;
        }
    }
}

sub html_display {
# -----------------------------------------------------------------------------
    my ( $page, $tags, $opts ) = @_;

    $tags ||= {};
    $opts ||= {};

# Populate the tags with frequently used values
    my %extra_tags = (
        do               => scalar $IN->param( $CFG{do} ),
        do_param         => $CFG{do},

        home_url         => $CFG{'home_url'},
        script_url       => $CFG{'script_url'},
        version          => $VERSION,

        data_source      => scalar $IN->param('data_source'),
        db               => get_db( $IN->param('data_source') ) || '',
        table            => scalar $IN->param('table'),

        from_monitor     => scalar $IN->param('from_monitor'),
        action           => scalar $IN->param('browse_action') || $IN->param('action'),
    );
    for ( keys %extra_tags ) {
        next if defined $tags->{$_};
        $tags->{$_} = $extra_tags{$_};
    }

# Ensure required options have been defaulted
    $opts->{print}  = 1 unless defined $opts->{print};
    $opts->{escape} = 1 unless defined $opts->{escape};
    $opts->{root}   ||= $CFG{template_dir};
    $opts->{dont_save} = $CFG{template_dont_save} unless defined $opts->{dont_save};

# If the parse call will print, need to print the CGI header
    if ( $opts->{print} ) {
        print $IN->header(
            @COOKIES ? ( -cookies => \@COOKIES ) : ()
        );
    }
    return GT::Template->parse( $page, $tags, $opts );
}

sub cgierr {
# -----------------------------------------------------------------------------
# Displays any errors and prints out FORM and ENVIRONMENT
# information. Useful for debugging.
#
    eval { local $SIG{__DIE__}; $IN = GT::CGI->new; };
    if ( defined $GT::CGI::PRINTED_HEAD ) {
        print "Content-type: text/plain\n\n" unless ($GT::CGI::PRINTED_HEAD);
    }
    else {
        print "Content-type: text/plain\n\n";
    }

    my ( $key, $env );
    my ( $error, $nolog ) = @_;
    if ( $CFG{'debug'} ) {
        print "\n\nDEBUG\n==========================================\n";
    }
    else {
        print
          "\n\nCGI ERROR\n==========================================\n";
    }
    $error and print "Error Message       : $error\n";
    $0     and print "Script Location     : $0\n";
    $]     and print "Perl Version        : $]\n";

    print "\nConfiguration\n-------------------------------------------\n";
    foreach $key ( sort keys %CFG ) {
        my $space = " " x ( 20 - length($key) );
        print "$key$space: $CFG{$key}\n";
    }
    if ( $IN ) {
        print "\nCookies\n-------------------------------------------\n";
        print "$CFG{'db_user_cookie_name'} : "
          . $IN->cookie( $CFG{'db_user_cookie_name'} );
        print "\n$CFG{'db_pass_cookie_name'} : "
          . $IN->cookie( $CFG{'db_pass_cookie_name'} );
        print "\n$CFG{'db_host_cookie_name'} : "
          . $IN->cookie( $CFG{'db_host_cookie_name'} );
        print "\n$CFG{'url_cookie_name'} :   "
          . $IN->cookie( $CFG{'url_cookie_name'} );
        print "\nquery_1 :   " . $IN->cookie('query_1');
        print "\nquery_2 :   " . $IN->cookie('query_2');
        print "\nquery_3 :   " . $IN->cookie('query_3');
        print "\nquery_4 :   " . $IN->cookie('query_4');
        print "\nquery_5 :   " . $IN->cookie('query_5');
        print "\nquery_6 :   " . $IN->cookie('query_6');
        print "\nquery_7 :   " . $IN->cookie('query_7');
        print "\nquery_8 :   " . $IN->cookie('query_8');
        print "\nquery_9 :   " . $IN->cookie('query_9');
        print "\nquery_10 :   " . $IN->cookie('query_10');

        print
          "\n\nForm Variables\n-------------------------------------------\n";
        foreach $key ( sort $IN->param ) {
            my $space = " " x ( 20 - length($key) );
            print "$key$space: " . $IN->param($key) . "\n";
        }
    }
    print
      "\nEnvironment Variables\n-------------------------------------------\n";
    foreach $env ( sort keys %ENV ) {
        my $space = " " x ( 20 - length($env) );
        print "$env$space: $ENV{$env}\n";
    }
    print "\nStack Trace \n-------------------------------------------\n";
    my $i = 0;
    while ( my ( $file, $line, $sub ) = ( caller( $i++ ) )[ 1, 2, 3 ] ) {
        print qq!($sub) called from ($file) line ($line)<BR>\n!;
    }
    exit;
}

sub check_libraries {
# -----------------------------------------------------------------------------
# Required Librariers
# Make sure we are using perl 5.003 and load other required files.

    eval {
        local $SIG{__DIE__};
        require DBI;
    };
    if ($@) {
        my $message =
"<font size=+2>Error loading required libraries</font>\n\nReason: $@\n\n1.\tIt's likely that you do not have the module installed.\n\tYou can find this at: <a href=http://www.perl.com/CPAN/modules/by-module/DBI/>http://www.perl.com/CPAN/modules/by-module/DBI/</a>\n\n2.\tIf the file exists:\n\t";
        $message .=
qq~Please check if the path to the folder that contains the file is in \@INC.
        
\t\@INC is a special variable in Perl that contains the list of places to
\tlook for Perl scripts to be evaluated by the do, require, or use
\tconstructs.  The error occurs because DBI.pm cannot be found in \@INC.  
\tPlease check if you have Perl installed properly and check if you have the
\tcorrect path to perl in the cgi.  If you do, then please contact the
\tserver admin.
~;
        library_error($message);
    }

    eval {
        local $SIG{__DIE__};
        require DBD::mysql;
    };
    if ($@) {
        my $message =
"<font size=+2>Error loading required libraries</font>\n\nReason: $@\n\n1.\tIt's likely that you do not have the module installed.\n\tYou can find this at: <a href=http://www.perl.com/CPAN/modules/by-module/DBD/>http://www.perl.com/CPAN/modules/by-module/DBD/</a>\n\n2.\tIf the file exists:\n\t";
        $message .=
qq~Please check if the path to the folder that contains the file is in \@INC.
        
\t\@INC is a special variable in Perl that contains the list of places to
\tlook for Perl scripts to be evaluated by the do, require, or use
\tconstructs.  The error occurs because mysql.pm cannot be found in \@INC.  
\tPlease check if you have Perl installed properly and check if you have the
\tcorrect path to perl in the cgi.  If you do, then please contact the
\tserver admin.
~;
        library_error($message);
    }
    eval {
        local $SIG{__DIE__};
        require strict;
        require 5.004;    # We need at least Perl 5.004
    };
    if ($@) {
        cgierr(
"MySQLMan requires at least perl version 5.004 or better. You are using: $[. ($@)"
        );
    }

    eval {
        local $SIG{__DIE__};
        require GT::CGI;
        require GT::Base;
        require GT::Template;
    };
    if ($@) {
        library_error(
"<font size=+2>Error loading required libraries</font>\n\nReason: $@\n\n1.\tPlease check if the file is in MySQLMan directory, permission is set correctly, and that it compiles.\n\n2.\tIf the file exists in the folder, then check if the path to MySQLMan folder is in \@INC.\n\tIf \@INC does not contain the path then add\n\t\t<B>use lib '/path/to/MySQLMan';</B>\n\tto the beginning of mysql.cgi.\n\n\tPlease refer the comments in mysql.cgi for detail.\n\n3.\tPlease check if the file was uploaded in <B>ASCII</B> mode (instead of BINARY mode)."
        );
    }
}

sub library_error {
# -----------------------------------------------------------------------------
# HTML error page.  Displayed when there error occurred while
# loading required libraries.

    my ($message) = @_;
    if ( defined $GT::CGI::PRINTED_HEAD ) {
        print "Content-type: text/html\n\n" unless ($GT::CGI::PRINTED_HEAD);
    }
    else {
        print "Content-type: text/html\n\n";
    }
    print qq~<HTML>
<HEAD>
<TITLE>
MySQLMan - Error loading required libraries.
</TITLE>
</HEAD>

<BODY BGCOLOR="#CCCCCC">

<table border=1 bgcolor="#FFFFFF" cellpadding=5 cellspacing=3 width="100%" align=center valign=top>
    <tr>
        <td bgcolor="navy">
          <FONT FACE="MS Sans Serif, arial,helvetica" size=1 COLOR="#FFFFFF">   
          <b>MySQLMan: Error loading required libraries.</b>
          </font>
        </td>
    </tr>
    <tr>
        <td>
            <UL>
            <pre>
            <font FACE="MS Sans Serif, arial,helvetica">
$message
            </font>
            </pre>
            </UL>
        </td>
    </tr>
</table>

</BODY>
</HTML>

~;

    exit;
}

# Was in html.pl
# Common Variables
# -----------------------------------------------------------------------------
# home_url   : the URL to home page.  (Specified in mysql.cfg)
# script_url : the URL to mysql.cgi.  (Specified in mysql.cfg)
# db         : the name of current database.
# table      : the name of current table.
# version    : the current version of the script
# help_topic : help_topic for sub html_help
#

sub html_database {
# -----------------------------------------------------------------------------
# List of databases in MySQL.
#     db_table : the table that contain the databases in MySQL
#                together with "drop" links to drop databases.
#     $feedback: feedback message of any action performed before
#                arriving the page.

    my ( $data_source, $db_table, $feedback ) = @_;
    html_display(
        'database.html',
        {
            db_table     => $db_table,
            db_table_cnt => scalar(@$db_table),
            table        => '',
            feedback     => $feedback,
            help_topic   => 'db_list',
        }
    );
}

sub html_table {
# -----------------------------------------------------------------------------
# List of tables in the database selected.
#     table_tables: the table that contains the table names in
#                   the database together with action links
#                   (browse/select/property/insert/empty/drop)
#     $feedback   : feedback message of any action performed before
#                   arriving the page.

    my ( $table_tables, $feedback ) = @_;
    html_display(
        'table.html',
        {
            table_tables     => $table_tables,
            table_tables_cnt => scalar(@$table_tables),
            table            => '',
            feedback         => $feedback,
            help_topic       => "table_list",
        }
    );
}

sub html_table_browse {
# -----------------------------------------------------------------------------
# This will display the result of any query that requires records to
# be displayed.
#     page         : the current page.
#     page_jump    : the input box that allows the user to go to any page.
#     page_link    : the links to prev/next/top page.
#     col_name     : the column names in the table.
#     table_records: the table that contains the result of the query.
#     query_printed: the query executed.
#     total_rows   : total number of rows resulted from the query.
#
    my (
       $table,         $page_jump,     $page_link,
        $col_name, $table_records, $query,         $empty_set,
        $pri_key,  $query_printed, $total_rec_num, $save_search_link
      ) = @_;

    html_display(
        'table_browse.html',
        {
            total_rows    => $total_rec_num,
            table         => $table,
            page          => scalar $IN->param('page') || '1',
            empty_set     => $empty_set,
            page_jump     => $page_jump,
            page_link     => $page_link,
            col_name      => $col_name,
            table_records => $table_records,
            query         => $query,
            query_printed => $query_printed || $query,
            pri_key          => $pri_key,
            help_topic       => "browse",
            save_search_link => $save_search_link
        }
    );
}

sub html_insert {
# -----------------------------------------------------------------------------
# Insert new record.
#     $form_fields: the input table that lets the user to enter
#                   values in to create a new record.
#     $feedback   : feedback message of any action performed before
#                   arriving the page.

    my ( $feedback ) = @_;

    my $table = $IN->param('table');
    if ( $CFG{'demo_mode'} ) { alias_name_check( $table ) or return; }
    my ($form_fields, $form_hidden) = form_fields( 0, () ) or return;

    html_display(
        'insert.html',
        {
            insert_fields => $form_fields,
            insert_fields_cnt => scalar(@$form_fields),
            form_hidden   => $form_hidden,
            feedback      => $feedback,
            help_topic    => "insert",
            insert_null   => $CFG{insert_null},
        }
    );
}

sub html_update {
# -----------------------------------------------------------------------------
# Edit record page.
#     $insert_fields: the input table with original values of the record
#                     in the input fields.
    my ( $insert_fields, $form_hidden ) = @_;
    html_display(
        'edit.html',
        {
            insert_fields => $insert_fields,
            insert_fields_cnt => scalar(@$insert_fields),
            form_hidden   => $form_hidden,
            record_modify => scalar $IN->param('record_modify'),
            page => scalar $IN->param('page') || 1,
            action     => scalar $IN->param('action'),
            sort_index => scalar $IN->param('sort_index'),
            fields     => scalar $IN->param('fields'),
            where      => scalar $IN->param('where'),
            example    => scalar $IN->param('example'),
            browse_action => scalar $IN->param('browse_action'),
            help_topic    => 'edit',
        }
    );
}

sub html_table_def {
# -----------------------------------------------------------------------------
# lets the user to construct the specificaitons of fields/columns.
# Used when creating a new table or adding new field(s)/column(s).
# If a new table is to be created, the table name is first checked to
# see if it is a valid one.  Then the number of fields/columns is checked
# also for its validity.
#     $columns: the input table that lets user to construct field/column
#               spec's.
#
    my ( $action ) = @_;
    my ($columns);

    if ( $action eq 'create' ) { $action = 'create_table'; }
    else { $action = 'alter_table'; }

    valid_name_check( $IN->param('table') ) or return;

    if ( $IN->param('num_of_fields') < 1 ) {
        sqlerr("Number of fields cannot be less than 0.");
    }
    elsif ( $IN->param('num_of_fields') > 500 ) {
        sqlerr("Number of fields too large.");
    }
    else {
        for ( my $i = 0 ; $i < $IN->param('num_of_fields') ; $i++ ) {
            $columns .= html_display(
                'create_field.txt',
                {
                    field_num => $i,
                    do_param  => $CFG{do}
                },
                {
                    print => 0 # don't print this 
                }
            );
        }

        html_display(
            'create_table.html',
            {
                do            => $action,
                columns       => \$columns,
                num_of_fields => scalar $IN->param('num_of_fields'),
                position      => scalar $IN->param('position'),
                help_topic    => 'col_def',
            }
        );
    }
}

sub html_alter_col {
# -----------------------------------------------------------------------------
# Change field/column specification.
#     col    : the field/column name that's being changed.
#     field  : same as col.
#     type   : the original field/column type.
#     null   : either 'YES' or ''.
#     default: the default value.
#     extra  : extra properties. (auto_increment)

    my ( $field, $type, $length_set, $attributes, $null, $default, $extra )
      = @_;
    my ( $type_select, $null_select, $extra_select, $attribute_select );

# show the original field/column specs.
    $type_select = "<option>$type</option>";

    if ($attributes) { $attribute_select = "<option>$attributes</option>" }

    if ($null) { $null_select = '<option>null</option>' }
    else { $null_select = '<option>not null</option>' }

    if ($extra) { $extra_select = "<option>$extra</option>" }

    html_display(
        'alter_col.html',
        {
            col              => scalar $IN->param('col'),
            field            => $field,
            type             => $type,
            length_set       => $length_set,
            attributes       => $attributes,
            null             => $null,
            default          => $default,
            extra            => $extra,
            type_select      => \$type_select,
            null_select      => \$null_select,
            extra_select     => \$extra_select,
            attribute_select => \$attribute_select,
            help_topic       => 'col_def_change',
        }
    );
}

sub html_page_jump {
# -----------------------------------------------------------------------------
# displays a text input box that allow user to go to
# any page of the result table.
#     pages: total number of pages of the result.
#
    my ( $pages, $fields, $example, $index ) = @_;
    html_display(
        'page_jump.txt',
        {
            pages       => $pages,
            fields      => $fields,
            where       => scalar $IN->param('where'),
            action      => scalar $IN->param('action'),
            sort_index  => $index,
            example     => $example,
            query       => scalar $IN->param('query'),
        },
        { print => 0 },
    );
}

sub html_confirm_action {
# -----------------------------------------------------------------------------
# Confirmation page.
#     $query: the actual query that is going to be executed.
#
    my ( $query ) = @_;

    html_display(
        'confirm.html',
        {
            table        => scalar $IN->param('table'),
            action       => scalar $IN->param('action'),
            query        => $query,
            query_param  => scalar $query,
            col          => scalar $IN->param('col'),
            key_name     => scalar $IN->param('key_name'),
            redirect_url => scalar $IN->cookie( $CFG{'url_cookie_name'} )
              || $CFG{'script_url'},
            help_topic => 'confirm',
        }
    );
}

sub html_sqlerr {
# -----------------------------------------------------------------------------
# Error message.
#     error: the actual error message.
    my ( $error ) = @_;
    my $table = $IN->param('table');
    if ( ( $IN->param( $CFG{do} ) eq 'create' )
        || $IN->param('action') eq 'rename_table' )
    {
        $table = '';
    }

    html_display(
        'sqlerr.html',
        {
            table       => $table,
            error       => $error,
            help_topic  => 'sqlerr',
        }
    );
}

sub html_login {
# -----------------------------------------------------------------------------
# Login page.  The URL of the previous page is read in from cookie
# or the script URL is used if the cookie is not set.
#     url    : the URL to the previous page.
#     user   : Username.  Read from cookie.
#     pass   : Password.  Read from cookie.
#     host   : Host name.  Used for database connection and is read from cookie.
#     message: message displayed in the login page.
#
    my ( $message, $init_login ) = @_;
    my $url = $IN->cookie( $CFG{'url_cookie_name'} ) || $CFG{'script_url'};

# For Netscape.
    $url =~ s/\;/&/g;

    if ( $url =~ m/\?/ ) { $url .= '&from_login=1' }
    else { $url .= '?from_login=1' }

    html_display(
        'login.html',
        {
            url         => $url,
            user        => scalar $IN->cookie( $CFG{'db_user_cookie_name'} ),
            pass        => scalar $IN->cookie( $CFG{'db_pass_cookie_name'} ),
            host        => scalar $IN->cookie( $CFG{'db_host_cookie_name'} ),
            message     => $message,
            init_login  => $init_login,
            help_topic  => 'login',
        }
    );
}

sub html_login_dbname {
# -----------------------------------------------------------------------------
# Login page.  The URL of the previous page is read in from cookie
# or the script URL is used if the cookie is not set.
#     url    : the URL to the previous page.
#     user   : Username.  Read from cookie.
#     pass   : Password.  Read from cookie.
#     host   : Host name.  Used for database connection and is read from cookie.
#     message: message displayed in the login page.
#
    my ( $message, $init_login ) = @_;
    my $url = $IN->cookie( $CFG{'url_cookie_name'} ) || $CFG{'script_url'};

    html_display(
        'login_dbname.html',
        {
            url         => $url,
            message     => $message,
            init_login  => $init_login,
            help_topic  => 'login_dbname',
        }
    );
}

sub html_logout {
# -----------------------------------------------------------------------------
# Logout message.
#
    html_display(
        'logout.html',
        {
            data_source => '',
            table       => '',
            help_topic  => 'logout',
            version     => $VERSION,
        }
    );
}

sub html_back {
# -----------------------------------------------------------------------------
# The confirmation page that is displayed after login.
#     url: the URL to the page before the login command is performed.


    html_display(
        'login_back.html',
        {
            url         => scalar $IN->param('url') || "$CFG{'script_url'}",
            help_topic  => 'login_back',
        }
    );
}

#=================================================#
#             Top Level Operations                #
#=================================================#

sub html_create_db {
# -----------------------------------------------------------------------------
# Create new database.
#
    html_display(
        'op_create_db.html',
        { help_topic  => 'create_db' }
    );
}

sub html_sql_monitor {
# -----------------------------------------------------------------------------
# SQL monitor.
#     $message: the feedback message from SQL monitor
#               in the form "num_rows rows affacted"
    my ( $message ) = @_;

# build a table for queries saved.
    my $query_table = build_saved_query_table();

    html_display(
        'op_sql_monitor.html',
        {
            feedback        => $message,
            help_topic      => 'sql_monitor',
            version         => $VERSION,
            query_table     => $query_table,
            query_table_cnt => scalar(@$query_table),
        }
    );
}

sub html_create_table {
# -----------------------------------------------------------------------------
# Create new table page.


    html_display(
        'op_create_table.html',
        {
            table       => '',
            help_topic  => 'create_table',
        }
    );
}

sub html_import {
# -----------------------------------------------------------------------------
# Data import page.
#
    my ( $options, $size ) = @_;
    my $empty_op = '';
    my $empty;

    for ( my $i ; $i < $size ; $i++ ) {
        $empty .= '<OPTION value=""></OPTION>' . "\n";
    }

    $size = $size + 1;
    if ( $size > 10 ) { $size = 10; }

    html_display(
        'op_import.html',
        {
            field_options => \$options,
            empty_options => \$empty,
            size          => $size,
            help_topic    => 'import',
        }
    );
}

sub html_export {
# -----------------------------------------------------------------------------
# Data export page.
#
    my ( $options, $size ) = @_;
    my $empty_op = '';
    my $empty;

    for ( my $i ; $i < $size ; $i++ ) {
        $empty .= '<OPTION value=""></OPTION>' . "\n";
    }

    $size = $size + 1;
    if ( $size > 10 ) { $size = 10; }
    html_display(
        'op_export.html',
        {
            field_options => \$options,
            empty_options => \$empty,
            size          => $size,
            help_topic    => 'export',
        }
    );
}

sub html_save_search {
# -----------------------------------------------------------------------------
    html_display(
        'save_search.html',
        {
            insert_fields => '',
            record_modify => scalar $IN->param('record_modify'),
            page          => scalar $IN->param('page') || 1,
            action        => scalar $IN->param('action'),
            sort_index    => scalar $IN->param('sort_index'),
            fields        => scalar $IN->param('fields'),
            where         => scalar $IN->param('where'),
            example       => scalar $IN->param('example'),
            browse_action => scalar $IN->param('browse_action'),
            help_topic    => 'save_search_result',
            version       => $VERSION,
            query         => scalar $IN->param('query')
        }
    );
}

sub html_mysqldump {
# -----------------------------------------------------------------------------
# Mysqldump page
#

    my $table_checkboxes = get_checkbox_tables_in_database() or return;

    html_display(
        'op_mysqldump.html',
        {
            tb_checkboxes => $table_checkboxes,
            tb_checkboxes_cnt => scalar(@$table_checkboxes),
            help_topic    => 'sql_dump',
        }
    );

}

sub html_add_fields {
# -----------------------------------------------------------------------------
# Add field/column to table page.
#

    my $options_col_order = qq|\n<option value="">At End of Table</option>\n<option value="FIRST">At Beginning of Table</option>|;
    foreach my $col ( get_cols( $IN->param('table') ) ) {
        $options_col_order .=
          qq|\n<option value="AFTER $col">After $col</option>|;
    }

    html_display(
        'op_add_fields.html',
        {
            opt_col_order => \$options_col_order,
            help_topic    => 'add_col',
        }
    );
}

sub html_rename_table {
# -----------------------------------------------------------------------------
# Rename table page.
#
    html_display(
        'op_rename_table.html',
        { help_topic  => 'rename' }
    );
}

#=================================================#
#            Show SQL Monitor Query               #
#=================================================#

sub html_show_query {
# -----------------------------------------------------------------------------
    my $i     = $IN->param('query_displayed');
    my $query = $IN->cookie("query_$i");
    $query =~ s/\n/<BR>\n/g;

    html_display(
        'show_query.html',
        {
            query    => $query,
            i        => $i
        }
    );
}

#=================================================#
#                   Help Pages                    #
#=================================================#

sub html_help {
# -----------------------------------------------------------------------------
    my $topic = $IN->param("help_topic");
    if ( $topic =~ /^\w+\.jpg$/ ) {
        if (open( FH, "$CFG{template_dir}/help_$topic" )) {
            binmode FH;
            binmode STDOUT;
            while ( read( FH, my $buf, 4096 ) ) {
                print $buf;
            }
            close FH;
        }
        # else die?
    }
    else {
        if ( $topic !~ /^\w+$/ ) {
            sqlerr("Invalid help topic: $topic\n");
        }
        html_display(
            'help_' . $topic . '.html',
        );
    }
}

#=================================================#
#                  Demo Prompt                    #
#=================================================#

sub html_demo_prompt {
# -----------------------------------------------------------------------------
# Demo message.  It is displayed when demo mode is on and the action performed
# is disabled.
#

    my ( $demo_message ) = @_;
    html_display(
        'demo_prompt.html',
        {
            demo_message => $demo_message,
            help_topic   => 'demo',
        }
    );
    return;
}

1;

__END__


