# ==================================================================
# Gossamer Links - enhanced directory management system
#
#   Website  : http://gossamer-threads.com/
#   Support  : http://gossamer-threads.com/scripts/support/
#   CVS Info : 087,071,086,086,085      
#   Revision : $Id: S1S2.pm,v 1.32 2005/04/16 02:11:50 brewt Exp $
#
# Copyright (c) 2001 Gossamer Threads Inc.  All Rights Reserved.
# Redistribution in part or in whole strictly prohibited. Please
# see LICENSE file for full details.
# ==================================================================

package Links::Import::S1S2;

use 5.004_04;
use strict;
use vars qw/$Warning_Code $Critical_Code $Mild_Code $Print_Out/;

use DBI;
use GT::SQL;

sub critical {
    $Critical_Code->(@_);
}

sub warning {
    $Warning_Code->(@_);
}

sub mild_warning {
    ref $Mild_Code eq 'CODE' and $Mild_Code->(@_);
}

sub import_print {
    if (ref $Print_Out eq 'CODE') {
        $Print_Out->(@_);
    }
    else {
        print @_;
    }
}

# Takes 3-4 arguments: hash reference, 2 or 3 code refs
# The hash reference is the options hash for an import.
# The first code reference will be called when a warning occurs.
# The second code reference will be called when a critical error occurs.
# If provided, the third code reference will be called when a mild warning occurs
sub import {
    my $opt = shift;
    return if ref $opt ne 'HASH';
    {
        my $warning = shift;
        return if ref $warning ne 'CODE';
        $Warning_Code = $warning;

        my $critical = shift;
        return if ref $critical ne 'CODE';
        $Critical_Code = $critical;

        my $mild = shift;
        $Mild_Code = $mild if ref $mild eq 'CODE';

        my $output = shift;
        $Print_Out = $output if ref $output eq 'CODE';
    }

    for (qw/Category Links CategoryRelations CategoryAlternates Validate Users Subscribe/) {
        local ($!,$@);
        my $did = do "$$opt{source}/$_.def";
        critical "Error parsing file $$opt{source}/$_: $@" if !$did and $@;
        critical "Error reading file $$opt{source}/$_: $!" if !$did and $!;
    }

# Check that all necessary databases have been loaded from the def files
    my $DEBUG_counter = 0;
    for ($Links::DBSQL::Category::db_driver,
         $Links::DBSQL::Category::db_user,
         $Links::DBSQL::Category::db_pass,
         $Links::DBSQL::Category::db_host,
         $Links::DBSQL::Category::db_table,
         $Links::DBSQL::Category::db_name) {
        defined $_ or critical "The source def files did not load correctly (Category)";
    }
    for ($Links::DBSQL::Links::db_driver,
         $Links::DBSQL::Links::db_user,
         $Links::DBSQL::Links::db_pass,
         $Links::DBSQL::Links::db_host,
         $Links::DBSQL::Links::db_table,
         $Links::DBSQL::Links::db_name) {
        defined $_ or critical "The source def files did not load correctly (Links)";
    }
    for ($Links::DBSQL::CategoryRelations::db_driver,
         $Links::DBSQL::CategoryRelations::db_user,
         $Links::DBSQL::CategoryRelations::db_pass,
         $Links::DBSQL::CategoryRelations::db_host,
         $Links::DBSQL::CategoryRelations::db_table,
         $Links::DBSQL::CategoryRelations::db_name) {
        defined $_ or critical "The source def files did not load correctly (CategoryRelations)";
    }
    for ($Links::DBSQL::CategoryAlternates::db_driver,
         $Links::DBSQL::CategoryAlternates::db_user,
         $Links::DBSQL::CategoryAlternates::db_pass,
         $Links::DBSQL::CategoryAlternates::db_host,
         $Links::DBSQL::CategoryAlternates::db_table,
         $Links::DBSQL::CategoryAlternates::db_name) {
        defined $_ or critical "The source def files did not load correctly (CategoryAlternates)";
    }
    for ($Links::DBSQL::Validate::db_driver,
         $Links::DBSQL::Validate::db_user,
         $Links::DBSQL::Validate::db_pass,
         $Links::DBSQL::Validate::db_host,
         $Links::DBSQL::Validate::db_table,
         $Links::DBSQL::Validate::db_name) {
        defined $_ or critical "The source def files did not load correctly (Validate)";
    }
    for ($Links::DBSQL::Users::db_driver,
         $Links::DBSQL::Users::db_user,
         $Links::DBSQL::Users::db_pass,
         $Links::DBSQL::Users::db_host,
         $Links::DBSQL::Users::db_table,
         $Links::DBSQL::Users::db_name) {
        defined $_ or critical "The source def files did not load correctly (Users)";
    }
    for ($Links::DBSQL::Subscribe::db_driver,
         $Links::DBSQL::Subscribe::db_user,
         $Links::DBSQL::Subscribe::db_pass,
         $Links::DBSQL::Subscribe::db_host,
         $Links::DBSQL::Subscribe::db_table,
         $Links::DBSQL::Subscribe::db_name) {
        defined $_ or critical "The source def files did not load correctly (Subscribe)";
    }

    my %i_dbh;
    my $i_dbi_opts = { AutoCommit => 1, RaiseError => 0, PrintError => 0 };

    {
        my ($no_warning) = ($Links::DBSQL::Category::db_port,
                            $Links::DBSQL::Links::db_port,
                            $Links::DBSQL::Validate::db_port,
                            $Links::DBSQL::Users::db_port,
                            $Links::DBSQL::Subscribe::db_port,
                            $Links::DBSQL::CategoryRelations::db_port,
                            $Links::DBSQL::CategoryAlternates::db_port);
    }

    for (   ['Category', $Links::DBSQL::Category::db_name, $Links::DBSQL::Category::db_driver, $Links::DBSQL::Category::db_host, $Links::DBSQL::Category::db_port, $Links::DBSQL::Category::db_user, $Links::DBSQL::Category::db_pass ],
            ['Links',    $Links::DBSQL::Links::db_name,    $Links::DBSQL::Links::db_driver,    $Links::DBSQL::Links::db_host,    $Links::DBSQL::Links::db_port,    $Links::DBSQL::Links::db_user,    $Links::DBSQL::Links::db_pass    ],
            ['Validate', $Links::DBSQL::Validate::db_name, $Links::DBSQL::Validate::db_driver, $Links::DBSQL::Validate::db_host, $Links::DBSQL::Validate::db_port, $Links::DBSQL::Validate::db_user, $Links::DBSQL::Validate::db_pass ],
            ['Users',    $Links::DBSQL::Users::db_name,    $Links::DBSQL::Users::db_driver,    $Links::DBSQL::Users::db_host,    $Links::DBSQL::Users::db_port,    $Links::DBSQL::Users::db_user,    $Links::DBSQL::Users::db_pass    ],
            ['Subscribe',$Links::DBSQL::Subscribe::db_name,$Links::DBSQL::Subscribe::db_driver,$Links::DBSQL::Subscribe::db_host,$Links::DBSQL::Subscribe::db_port,$Links::DBSQL::Subscribe::db_user,$Links::DBSQL::Subscribe::db_pass],
            ['CategoryRelations',$Links::DBSQL::CategoryRelations::db_name,$Links::DBSQL::CategoryRelations::db_driver,$Links::DBSQL::CategoryRelations::db_host,$Links::DBSQL::CategoryRelations::db_port,$Links::DBSQL::CategoryRelations::db_user,$Links::DBSQL::CategoryRelations::db_pass],
            ['CategoryAlternates',$Links::DBSQL::CategoryAlternates::db_name,$Links::DBSQL::CategoryAlternates::db_driver,$Links::DBSQL::CategoryAlternates::db_host,$Links::DBSQL::CategoryAlternates::db_port,$Links::DBSQL::CategoryAlternates::db_user,$Links::DBSQL::CategoryAlternates::db_pass]) {
        my $driver = $$_[2] || "mysql";
        critical "The source def files did not load correctly (no \$db_name set for $$_[0] table)" unless $$_[1];
        next if exists $i_dbh{$$_[1]};
        my $dsn = "DBI:$driver:$$_[1]";
        if ($driver eq "mysql") {
            if ($$_[3]) {
                $dsn .= ":$$_[3]";
                if ($$_[4]) {
                    $dsn .= ":$$_[4]";
                }
            }
        }
        $i_dbh{$$_[1]} = DBI->connect($dsn,@$_[5,6],$i_dbi_opts) or critical("Couldn't connect to source $$_[0] db: ".$DBI::errstr);
    }

    my $DB = new GT::SQL(def_path => $$opt{destination}, subclass => 0);
    my $e_prefix = $DB->prefix;
    my $e_dbh;
    {
        my $table = $DB->table("Links");
        $table->connect();
        $e_dbh = $table->{driver}->connect();
    }

    my %e_standard_cols = (
        Category => { map { ($_ => 1) } qw/ID Name FatherID Full_Name Description Meta_Description Meta_Keywords Header Footer Category_Template Number_of_Links Has_New_Links Has_Changed_Links Newest_Link Timestmp Payment_Mode/},
        Users    => { map { ($_ => 1) } qw/Username Password Email Name Validation Status ReceiveMail/},
        Links    => { map { ($_ => 1) } qw/ID Title URL LinkOwner Add_Date Mod_Date Description Contact_Name Contact_Email Hits isNew isChanged isPopular isValidated Rating Votes Status Date_Checked Timestmp ExpiryDate ExpiryCounted ExpiryNotify/},
    );

    my %e_non_standard_cols;
    for my $table (keys %e_standard_cols) {
        my %cols = $DB->table($table)->cols;
        for (grep !$e_standard_cols{$table}{$_}, keys %cols) {
            $e_non_standard_cols{$table}{$_} = 1;
        }
    }

    my %i_standard_cols = (
        Category => { map { ($_ => 1) } qw/ID Name Description Meta_Description Meta_Keywords Header Footer Number_of_Links Has_New_Links Has_Changed_Links Newest_Link/},
        Users    => { map { ($_ => 1) } qw/Username Password Email Validation Status/},
        Links    => { map { ($_ => 1) } qw/ID Title URL Add_Date Mod_Date CategoryID Description Contact_Name Contact_Email Hits isNew isChanged isPopular Rating Votes ReceiveMail Status Date_Checked/},
        Validate => { map { ($_ => 1) } qw/ID Title URL Add_Date Mod_Date CategoryID Description Contact_Name Contact_Email Hits isNew isChanged isPopular Rating Votes ReceiveMail Status Date_Checked LinkID Mode/},
    );

    my %i_non_standard_cols;
    $i_non_standard_cols{Category} = { map { !$i_standard_cols{Category}{$_} ? ($_ => 1) : () } keys %Links::DBSQL::Category::db_def } if keys %Links::DBSQL::Category::db_def;
    $i_non_standard_cols{Users} =    { map { !$i_standard_cols{Users}{$_}    ? ($_ => 1) : () } keys %Links::DBSQL::Users::db_def }    if keys %Links::DBSQL::Users::db_def;
    $i_non_standard_cols{Links} =    { map { !$i_standard_cols{Links}{$_}    ? ($_ => 1) : () } keys %Links::DBSQL::Links::db_def }    if keys %Links::DBSQL::Links::db_def;
    $i_non_standard_cols{Validate} = { map { !$i_standard_cols{Validate}{$_} ? ($_ => 1) : () } keys %Links::DBSQL::Validate::db_def } if keys %Links::DBSQL::Validate::db_def;

    my $Links_counter;
    my $Category_counter;

    my $odbc = 0;
    if ($DB->table('Links')->{connect}->{driver} eq 'ODBC') {
        $odbc = 1;
        $i_dbh{$Links::DBSQL::Links::db_name}->{LongReadLen} = 1000000;
    }

    if ($$opt{clear_tables}) {
    # Delete everything from all tables, EXCEPT for the `admin' user from the Users table
        $e_dbh->do("DELETE FROM ${e_prefix}Users WHERE Username <> 'admin'") or critical "Unable to delete all existing users: ".$e_dbh->errstr;
        for (qw/Links Category CatLinks CatRelations Category_Score_List
            Category_Word_List ClickTrack Editors EmailMailings EmailTemplates
            Links_Score_List Links_Word_List MailingIndex MailingList
            MailingListIndex Sessions Verify/) {
            $e_dbh->do("DELETE FROM $e_prefix$_");
        }
        unless ($$opt{straight_import}) {
            $Links_counter = $Category_counter = 0;
        }
    }
    else {
        my $sth = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Links") or critical "Unable to prepare query `SELECT MAX(ID) FROM ${e_prefix}Links': ".$e_dbh->errstr;
        $sth->execute or critical "Unable to execute query `SELECT MAX(ID) FROM ${e_prefix}Links': ".$sth->errstr;
        $Links_counter = $sth->fetchrow_array;
        $sth->finish();

        $sth = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Category") or critical "Unable to prepare query `SELECT MAX(ID) FROM ${e_prefix}Category': ".$e_dbh->errstr;
        $sth->execute or critical "Unable to execute query `SELECT MAX(ID) FROM ${e_prefix}Category': ".$sth->errstr;
        $Category_counter = $sth->fetchrow_array;
        $sth->finish();
    }

# Users
    {
        my $get_cols = "Username, Password, Email, Validation, Status";
        my $ins_cols = "(Name, Username, Password, Email, Validation, Status";
        my $ins_vals = "(?, ?, ?, ?, ?, ?";
        for (keys %{$e_non_standard_cols{"${e_prefix}Users"}}) {
            if ($i_non_standard_cols{Users}{$_}) {
                $ins_cols .= ", $_";
                $ins_vals .= ", ?";
                $get_cols .= ", $_";
            }
            else {
                mild_warning("Custom destination column `${e_prefix}Users.$_' has no equivelant import column. It will contain the default values for the column");
            }
        }
        for (grep +(not $e_standard_cols{"${e_prefix}Users"}{$_} and not $e_non_standard_cols{"${e_prefix}Users"}{$_}), keys %{$i_non_standard_cols{Users}}) {
            next if $e_non_standard_cols{"${e_prefix}Users"}{$_};
            if ($opt->{create_columns}) {
                mild_warning("Custom import column `Users.$_' had no destination equivelant. A column will be created");
                my $editor = $DB->editor("Users");
                my @def = @{$Links::DBSQL::Users::db_def{$_}};
                $editor->add_col(
                    $_,
                    {
                        type                  => ((uc $def[1] eq 'CHAR' and $def[3] > 255) ? 'TEXT' : $def[1]),
                        ($def[2] ? (form_size => ((index($def[2],"x") > -1) ? [split(/x/,$def[2],2)] : $def[2])) : ()),
                        size                  => $def[3],
                        ($def[4] ? (not_null  => 1)       : ()),
                        ($def[5] ? (default   => $def[5]) : ()),
                        ($def[6] ? (regex     => $def[6]) : ()),
                        ($def[7] ? (weight    => $def[7]) : ())
                    }
                );
                $ins_cols .= ", $_";
                $ins_vals .= ", ?";
                $get_cols .= ", $_";

                $e_non_standard_cols{"${e_prefix}Users"}{$_} = 1;
            }
            else {
                warning("Custom import column `Users.$_' has no destination equivelant. It will be ignored");
            }
        }
        $ins_cols .= ")";
        $ins_vals .= ")";
        my $sth = $i_dbh{$Links::DBSQL::Users::db_name}->prepare("SELECT $get_cols FROM $Links::DBSQL::Users::db_table") or critical("Unable to prepare query `SELECT $get_cols FROM $Links::DBSQL::Users::db_table': ".$i_dbh{$Links::DBSQL::Users::db_name}->errstr);
        $sth->execute() or critical("Unable to execute query `SELECT $get_cols FROM $Links::DBSQL::Users::db_table': ".$sth->errstr);
        my $ins_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}Users $ins_cols VALUES $ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Users $ins_cols VALUES $ins_vals': ".$e_dbh->errstr);
        my $user_count_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?") or critical("Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?': ".$e_dbh->errstr);
        my $username_sth = $e_dbh->prepare("SELECT Username FROM ${e_prefix}Users WHERE Email = ?") or critical("Unable to prepare query `SELECT Username FROM ${e_prefix}Users WHERE Email = ?': ".$e_dbh->errstr);

        # What other than the Name and ReceiveMail can be updated here?
        my $user_mod_sth = $e_dbh->prepare("UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?': ".$e_dbh->errstr;

        while (my $row = $sth->fetchrow_arrayref) {
            $user_count_sth->execute($$row[2]) or warning("Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?': ".$user_count_sth->errstr);
            if ($user_count_sth->fetchrow_array) { # This e-mail address already exists, so skip it
                next;
            }
            $ins_sth->execute(@$row[0,0],($$row[1] or random_pass()),@$row[2..$#$row]) or warning("Unable to execute query `INSERT INTO ${e_prefix}Users $ins_cols VALUES $ins_vals' ($$row[0]): ".$ins_sth->errstr),next;
        }
    }
# Subscribe users - these users receive the newsletter.
    {
        my $get_subscribers = $i_dbh{$Links::DBSQL::Subscribe::db_name}->prepare("SELECT Name, Email FROM $Links::DBSQL::Subscribe::db_table") or warning("Unable to prepare query `SELECT Name, Email FROM $Links::DBSQL::Subscribe::db_table': ".$i_dbh{$Links::DBSQL::Subscribe::db_name}->errstr);
        $get_subscribers->execute();
        my $count_users = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?");
        my $add_user = $e_dbh->prepare("INSERT INTO ${e_prefix}Users (Name, Username, Password, Email, ReceiveMail, Status) VALUES (?, ?, ?, ?, 'Yes', 'Registered')");
        my $give_newsletter = $e_dbh->prepare("UPDATE ReceiveMail = 'Yes' WHERE Email = ?");

        my $sub_imported = 0;
        import_print "\nImporting Subscribed users (users who receive the newsletter) ...\n";
        while (my $row = $get_subscribers->fetchrow_arrayref) {
# If we are under ODBC we need to reset the sth handle to avoid a "Invalid Cursor State" error.
            $odbc and ($count_users->finish);
            $count_users->execute($$row[1]) or warning("Unable to count users with email $$row[1]: ".$count_users->errstr), next;
            if ($count_users->fetchrow_array) {
                $give_newsletter->execute($$row[1]) or warning("Unable to set Newsletter = 'Yes' for user with e-mail $$row[1]: ".$give_newsletter->errstr),--$sub_imported;
            }
            else { # User doesn't already exist
                $add_user->execute($$row[0], $$row[1], random_pass(), $$row[1]) or warning("Unable to insert user $$row[1]: ".$add_user->errstr),--$sub_imported;
            }
            import_print "$sub_imported\n" unless ++$sub_imported % 500;
        }
        import_print "$sub_imported Subscribed users imported.\n";
    }

# Everything else (in most cases including even more users)
    {
        # Category select statements
        my $cat_get_cols = "ID, Name, Description, Meta_Description, Meta_Keywords, " .
                "Header, Footer, Number_of_Links, Has_New_Links, Has_Changed_Links, Newest_Link";
        my $cat_ins_cols = "(ID, Name, FatherID, Full_Name, Description, Meta_Description, Meta_Keywords, " .
                "Header, Footer, Number_of_Links, Has_New_Links, Has_Changed_Links, Newest_Link";
        my $cat_ins_vals = "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?";

        # Links select statements
        my $links_get_cols = "ID, Contact_Name, Contact_Email, ReceiveMail, CategoryID, " .
                       "Title, URL, Add_Date, Mod_Date, Description, Hits, isNew, " .
                       "isChanged, isPopular, Rating, Votes, Status, Date_Checked";
        my $links_ins_cols = "(ID, LinkOwner, isValidated, Contact_Name, Contact_Email, " .
                       "Title, URL, Add_Date, Mod_Date, Description, Hits, isNew, " .
                       "isChanged, isPopular, Rating, Votes, Status, Date_Checked";
        my $links_ins_vals = "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?";

        # Validate select statements
        my $validate_get_cols = "ID, Contact_Name, Contact_Email, ReceiveMail, CategoryID, " .
                       "Title, URL, Add_Date, Mod_Date, Description, Hits, isNew, " .
                       "isChanged, isPopular, Rating, Votes, Status, Date_Checked";
        my $validate_ins_cols = "(ID, LinkOwner, isValidated, Contact_Name, Contact_Email, " .
                       "Title, URL, Add_Date, Mod_Date, Description, Hits, isNew, " .
                       "isChanged, isPopular, Rating, Votes, Status, Date_Checked";
        my $validate_ins_vals = "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?";

        # Build up extra fields that exist in both old and new Category tables
        for (keys %{$e_non_standard_cols{"${e_prefix}Category"}}) {
            if ($i_non_standard_cols{Category}{$_}) {
                $cat_ins_cols .= ", $_";
                $cat_ins_vals .= ", ?";
                $cat_get_cols .= ", $_";
            }
            else {
                mild_warning("Custom destination column `${e_prefix}Category.$_' has no equivelant import column. It will contain the default values for the column");
            }
        }

        for (grep !$e_non_standard_cols{"${e_prefix}Category"}{$_}, keys %{$i_non_standard_cols{Category}}) {
            next if $e_non_standard_cols{"${e_prefix}Category"}{$_};
            if ($opt->{create_columns}) {
                mild_warning("Custom import column `Category.$_' had no destination equivelant. A destination column will be created");
                my $editor = $DB->editor("Category");
                my @def = @{$Links::DBSQL::Category::db_def{$_}};
                $editor->add_col(
                    $_,
                    {
                        type                  => ((uc $def[1] eq 'CHAR' and $def[3] > 255) ? 'TEXT' : $def[1]),
                        ($def[2] ? (form_size => ((index($def[2],"x") > -1) ? [split(/x/,$def[2],2)] : $def[2])) : ()),
                        size                  => $def[3],
                        ($def[4] ? (not_null  => 1)       : ()),
                        ($def[5] ? (default   => $def[5]) : ()),
                        ($def[6] ? (regex     => $def[6]) : ()),
                        ($def[7] ? (weight    => $def[7]) : ())
                    }
                );
                $cat_ins_cols .= ", $_";
                $cat_ins_vals .= ", ?";
                $cat_get_cols .= ", $_";

                $e_non_standard_cols{"${e_prefix}Category"}{$_} = 1;
            }
            else {
                warning("Custom import column `Category.$_' has no destination equivelant. It will be ignored");
            }
        }
        $cat_ins_cols .= ")";
        $cat_ins_vals .= ")";


        for (keys %{$e_non_standard_cols{"${e_prefix}Links"}}) {
            if ($i_non_standard_cols{Links}{$_}) {
                $links_ins_cols .= ", $_";
                $links_ins_vals .= ", ?";
                $links_get_cols .= ", $_";
            }
            else {
                mild_warning("Custom destination column `${e_prefix}Links.$_' has no equivelant import column. It will contain the default values for the column");
            }
        }

        for (grep !$e_non_standard_cols{"${e_prefix}Links"}{$_}, keys %{$i_non_standard_cols{Links}}) {
            next if $e_non_standard_cols{"${e_prefix}Links"}{$_};
            if ($opt->{create_columns}) {
                mild_warning("Custom import column `Links.$_' had no destination equivelant. A destination column will be created");
                my $editor = $DB->editor("Links");
                my @def = @{$Links::DBSQL::Links::db_def{$_}};
                $editor->add_col(
                    $_,
                    {
                        type                  => ((uc $def[1] eq 'CHAR' and $def[3] > 255) ? 'TEXT' : $def[1]),
                        ($def[2] ? (form_size => ((index($def[2],"x") > -1) ? [split(/x/,$def[2],2)] : $def[2])) : ()),
                        size                  => $def[3],
                        ($def[4] ? (not_null  => 1)       : ()),
                        ($def[5] ? (default   => $def[5]) : ()),
                        ($def[6] ? (regex     => $def[6]) : ()),
                        ($def[7] ? (weight    => $def[7]) : ())
                    }
                );
                $links_ins_cols .= ", $_";
                $links_ins_vals .= ", ?";
                $links_get_cols .= ", $_";

                $e_non_standard_cols{"${e_prefix}Links"}{$_} = 1;
            }
            else {
                warning("Custom import column `Links.$_' has no destination equivelant. It will be ignored");
            }
        }
        $links_ins_cols .= ")";
        $links_ins_vals .= ")";


        for (keys %{$e_non_standard_cols{"${e_prefix}Links"}}) {
            if ($i_non_standard_cols{Validate}{$_}) {
                $validate_ins_cols .= ", $_";
                $validate_ins_vals .= ", ?";
                $validate_get_cols .= ", $_";
            }
            else {
                mild_warning("Custom destination column `${e_prefix}Links.$_' has no equivelant Validate import column. It will contain the default values for the column");
            }
        }

        for (grep !$e_non_standard_cols{"${e_prefix}Links"}{$_}, keys %{$i_non_standard_cols{Validate}}) {
            next if $e_non_standard_cols{"${e_prefix}Links"}{$_};
            if ($opt->{create_columns}) {
                mild_warning("Custom import column `Validate.$_' had no destination Links equivelant. A destination column will be created");
                my $editor = $DB->editor("Links");
                my @def = @{$Links::DBSQL::Validate::db_def{$_}};
                $editor->add_col(
                    $_,
                    {
                        type                  => ((uc $def[1] eq 'CHAR' and $def[3] > 255) ? 'TEXT' : $def[1]),
                        ($def[2] ? (form_size => ((index($def[2],"x") > -1) ? [split(/x/,$def[2],2)] : $def[2])) : ()),
                        size                  => $def[3],
                        ($def[4] ? (not_null  => 1)       : ()),
                        ($def[5] ? (default   => $def[5]) : ()),
                        ($def[6] ? (regex     => $def[6]) : ()),
                        ($def[7] ? (weight    => $def[7]) : ())
                    }
                );
                $validate_ins_cols .= ", $_";
                $validate_ins_vals .= ", ?";
                $validate_get_cols .= ", $_";

                $e_non_standard_cols{"${e_prefix}Links"}{$_} = 1;

            }
            else {
                warning("Custom import column `Validate.$_' has no destination equivelant. It will be ignored");
            }
        }
        $validate_ins_cols .= ")";
        $validate_ins_vals .= ")";

        my $cat_sth = $i_dbh{$Links::DBSQL::Category::db_name}->prepare("SELECT $cat_get_cols FROM $Links::DBSQL::Category::db_table ORDER BY Name") or critical("Unable to prepare query `SELECT $cat_get_cols FROM $Links::DBSQL::Category::db_table ORDER BY Name': ".$i_dbh{$Links::DBSQL::Category::db_name}->errstr);
        $cat_sth->execute() or critical("Unable to execute query `SELECT $cat_get_cols FROM $Links::DBSQL::Category::db_table ORDER BY Name': ".$cat_sth->errstr);

        my $get_cat_relations = $i_dbh{$Links::DBSQL::CategoryRelations::db_name}->prepare("SELECT CategoryID, RelatedID from $Links::DBSQL::CategoryRelations::db_table") or critical "Unable to prepare query `SELECT CategoryID, RelatedID from $Links::DBSQL::CategoryRelations::db_table': ".$i_dbh{$Links::DBSQL::CategoryRelations::db_name}->errstr;
        my $add_cat_relation = $e_dbh->prepare("INSERT INTO ${e_prefix}CatRelations (CategoryID, RelatedID) VALUES (?, ?)") or critical "Unable to prepare query `INSERT INTO ${e_prefix}CatRelations (CategoryID, RelatedID) VALUES (?, ?)': ".$e_dbh->errstr;
        my @cat_map; # $cat_map[old_id] = new_id; Don't need this with --straight-import enabled

        my $count_cats_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?") or critical("Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$e_dbh->errstr);

        my $get_cat_alts = $i_dbh{$Links::DBSQL::CategoryAlternates::db_name}->prepare("SELECT CategoryID FROM $Links::DBSQL::CategoryAlternates::db_table WHERE LinkID = ?") or critical "Unable to prepare query `SELECT * FROM $Links::DBSQL::CategoryAlternates::db_table WHERE LinkID = ?': ".$i_dbh{$Links::DBSQL::CategoryAlternates::db_name}->errstr;

        my $cat_ins_sth = $odbc ?
                                ($e_dbh->prepare("SET IDENTITY_INSERT ${e_prefix}Category ON; INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals': ".$e_dbh->errstr)) :
                                ($e_dbh->prepare("INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals': ".$e_dbh->errstr));

        my $cat_ins_simple_sth = $odbc ?
                                ($e_dbh->prepare("SET IDENTITY_INSERT ${e_prefix}Category ON; INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)': ".$e_dbh->errstr)) :
                                ($e_dbh->prepare("INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Category (ID, Name, Full_Name, FatherID) VALUES (?, ?, ?, ?)': ".$e_dbh->errstr));

        my $user_ins_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}Users (Username, Email, Password, Name, ReceiveMail, Status) VALUES (?, ?, ?, ?, ?, 'Registered')") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Users (Username, Email, Name, ReceiveMail, Status) VALUES (?, ?, ?, ?, ?, 'Registered')': ".$e_dbh->errstr);
        my $cat_links_sth = $e_dbh->prepare("INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)") or critical("Unable to prepare query `INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)': ".$e_dbh->errstr);
        my $insert_link_sth = $odbc ?
                                ($e_dbh->prepare("SET IDENTITY_INSERT ${e_prefix}Links ON; INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals': ".$e_dbh->errstr)) :
                                ($e_dbh->prepare("INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals': ".$e_dbh->errstr));

        my $insert_vlink_sth = $odbc ?
                                ($e_dbh->prepare("SET IDENTITY_INSERT ${e_prefix}Links ON; INSERT INTO ${e_prefix}Links $validate_ins_cols VALUES $validate_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Links $validate_ins_cols VALUES $validate_ins_vals': ".$e_dbh->errstr)) :
                                ($e_dbh->prepare("INSERT INTO ${e_prefix}Links $validate_ins_cols VALUES $validate_ins_vals") or critical("Unable to prepare query `INSERT INTO ${e_prefix}Links $validate_ins_cols VALUES $validate_ins_vals': ".$e_dbh->errstr));

        my $father_sth = $e_dbh->prepare("SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?") or critical("Unable to prepare query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$e_dbh->errstr);

        my $get_links_sth = $i_dbh{$Links::DBSQL::Links::db_name}->prepare("SELECT $links_get_cols FROM $Links::DBSQL::Links::db_table WHERE CategoryID = ?") or critical("Unable to prepare query `SELECT $links_get_cols FROM $Links::DBSQL::Links::db_table WHERE CategoryID = ?': ".$i_dbh{$Links::DBSQL::Links::db_name}->errstr);
        my $get_vlinks_sth = $i_dbh{$Links::DBSQL::Validate::db_name}->prepare("SELECT $validate_get_cols FROM $Links::DBSQL::Validate::db_table WHERE CategoryID = ?") or critical("Unable to prepare query `SELECT $validate_get_cols FROM $Links::DBSQL::Validate::db_table WHERE CategoryID = ?': ".$i_dbh{$Links::DBSQL::Validate::db_name}->errstr);

        my $user_count_sth = $e_dbh->prepare("SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?") or critical("Unable to prepare query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?': ".$e_dbh->errstr);
        my $username_sth = $e_dbh->prepare("SELECT Username FROM ${e_prefix}Users WHERE Email = ?") or critical("Unable to prepare query `SELECT Username FROM ${e_prefix}Users WHERE Email = ?': ".$e_dbh->errstr);

        # What other than the Name and ReceiveMail can be updated here?
        my $user_mod_sth = $e_dbh->prepare("UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?': ".$e_dbh->errstr;
        my $num_links_sth = $e_dbh->prepare("UPDATE ${e_prefix}Category SET Number_of_Links = ? WHERE ID = ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Category SET Number_of_Links = ? WHERE ID = ?': ".$e_dbh->errstr;

        import_print "\nImporting Categories and Links ...\n";
        my $links_imported = 0;
        my $cats_imported = 0;
        my @missing_cats; # contains the Full_Name's of missing categories.
        my %missing_cats; # contains Full_name => true for missing categories.

# Have to go through hoops here as ODBC can only run one sth at a time.
        my $sub;
        if ($odbc) {
            my $results = $cat_sth->fetchall_arrayref;
            $cat_sth->finish;
            import_print "\n\tImporting ", scalar @$results, " categories ..\n";
            $sub = sub { return shift @$results; }
        }
        else {
            $sub = sub { $cat_sth->fetchrow_arrayref; }
        }
        while(my $row = $sub->()) {
            $row = [@$row];
            my $old_id = shift @$row;
            my $new_id = $$opt{straight_import} ? $old_id : ++$Category_counter;
            my ($name) = (my $full_name = shift @$row) =~ m[([^/]*)\Z];
            unless (defined $name and length $name) {
                $Category_counter-- unless $$opt{straight_import};
                warning "Cannot insert Category $full_name because it is an invalid name";
                next;
            }
            my ($father_full_name) = $full_name =~ m[\A(.*)/];
            my $father_id;
            if (not defined $father_full_name) {
                $father_id = 0;
            }
            else {
                $odbc and $father_sth->finish;
                $father_sth->execute($father_full_name) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$father_sth->errstr;
                if (my $ar = $father_sth->fetchrow_arrayref()) {
                    $father_id = $ar->[0] || 0;
                }
                else {
                    if ($$opt{create_missing_categories}) {
                        if ($missing_cats{$father_full_name}++) {
                            mild_warning "$father_full_name is needed for category $full_name and is already in the list of categories to be created";
                        }
                        else {
                            my $ins_pos = @missing_cats;
                            splice @missing_cats, $ins_pos, 0, $father_full_name;
                            mild_warning "$father_full_name is needed for category $full_name and does not exist. It will be created";
                            my $fn = $father_full_name;
                            while ($fn =~ s[/[^/]*\Z][]) {
                                $count_cats_sth->execute($fn) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$count_cats_sth->errstr;
                                if ($count_cats_sth->fetchrow_array) { # It exists
                                    last;
                                }
                                else {
                                    splice @missing_cats, $ins_pos, 0, $fn;
                                    mild_warning "$fn is needed for category $full_name and does not exist. It will be created";
                                }
                            }
                        }
                    }
                    else {
                        warning "No father row found for $full_name! This may be a serious error as $full_name should probably have a father category";
                    }
                    $father_id = 0;
                }
            }
            if ($$opt{data_integrity}) {
                $odbc and $count_cats_sth->finish;
                $count_cats_sth->execute($full_name) or critical "Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Category WHERE Full_Name = ?': ".$count_cats_sth->errstr;
                unless ($count_cats_sth->fetchrow_array) {
                    unless ($cat_ins_sth->execute($new_id,$name,$father_id,$full_name,@$row)) {
                        $Category_counter-- unless $$opt{straight_import};
                        warning "Unable to insert category `$full_name' (SQL query: `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals'): ".$cat_ins_sth->errstr;
                        next;
                    }
                    elsif (not $$opt{straight_import}) {
                        $cat_map[$old_id] = $new_id;
                    }
                }
                else {
                    --$Category_counter unless $$opt{straight_import};
                    mild_warning("Duplicate category found ($full_name) and skipped");
                    next;
                }
            }
            else {
                unless ($cat_ins_sth->execute($new_id,$name,$father_id,$full_name,@$row)) {
                    --$Category_counter unless $$opt{straight_import};
                    warning("Unable to insert category `$full_name' (SQL query: `INSERT INTO ${e_prefix}Category $cat_ins_cols VALUES $cat_ins_vals'): ".$cat_ins_sth->errstr);
                    next;
                }
                elsif (not $$opt{straight_import}) {
                    $cat_map[$old_id] = $new_id;
                }
            }

            import_print "$cats_imported Categories imported\n" unless ++$cats_imported % 500;

            my $num_of_links = 0;
            my $link_sub;
            $get_links_sth->execute($old_id) or critical "Unable to execute query: ".$get_links_sth->errstr;
            if ($odbc) {
                my $links_results = $get_links_sth->fetchall_arrayref;
                $get_links_sth->finish;
                $link_sub = sub { return shift @$links_results; }
            }
            else {
                $link_sub = sub { $get_links_sth->fetchrow_arrayref; }
            }
            while(my $row = $link_sub->()) {
                $row = [@$row];
                my ($id, $contact_name, $contact_email, $receive_mail, $cat_id) = splice @$row,0,5;

                unshift @$row, $contact_name, $contact_email;

                $get_cat_alts->execute($id) or critical "Unable to execute query `SELECT CategoryID FROM $Links::DBSQL::CategoryAlternates::db_table WHERE LinkID = ?': ".$get_cat_alts->errstr;
                my @alt_ids;
                while (my $row = $get_cat_alts->fetchrow_arrayref) {
                    push @alt_ids, ($$opt{straight_import} ? $$row[0] : $cat_map[$$row[0]]);
                }

                $id = ++$Links_counter unless $$opt{straight_import};
                my $username;
                $odbc and $user_count_sth->finish;
                $user_count_sth->execute($contact_email) or warning("Unable to execute query `SELECT COUNT(*) FROM ${e_prefix}Users WHERE Email = ?': ".$user_count_sth->errstr);
                if ($user_count_sth->fetchrow_array) { # This e-mail address already exists
                    $user_mod_sth->execute($contact_name, ($receive_mail eq 'Yes' ? 'Yes' : 'No'), $contact_email) or warning("Unable to execute query `UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?': ".$user_mod_sth->errstr);
                    $odbc and $username_sth->finish;
                    $username_sth->execute($contact_email) or warning("Unable to execute query: ".$username_sth->errstr);
                    $username = $username_sth->fetchrow_arrayref()->[0];
                }
                elsif ($contact_email) {
                    $user_ins_sth->execute(($contact_email) x 2, '', (defined $contact_name ? $contact_name : ""), ($receive_mail eq 'Yes' ? 'Yes' : 'No')) or warning("Unable to execute query `INSERT INTO ${e_prefix}Users (Username, Password, Email, Name, ReceiveMail, Status) VALUES (?, ?, ?, ?, 'Registered')': ".$user_ins_sth->errstr);
                    $username = $contact_email;
                }
                else {
                    mild_warning("Not enough information to add a user for link `".($$row[0] or '<unknown>')." (URL: ".($$row[1] or "<none>")."). Setting link owner to `admin'");
                    $username = 'admin';
                }
                if ($insert_link_sth->execute($id,$username,'Yes',@$row)) {
                    for ($new_id,@alt_ids) {
                        if (! defined $_) { next; }
                        $cat_links_sth->execute($id,$_) or warning "Unable to execute query `INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)': ".$cat_links_sth->errstr;
                    }
                    $num_of_links++;
                    import_print "$links_imported Links imported\n" unless ++$links_imported % 500;
                }
                else {
                    $Links_counter-- unless $$opt{straight_import};
                    warning("Unable to insert validated link `$$row[0]' (SQL query: `INSERT INTO ${e_prefix}Links $links_ins_cols VALUES $links_ins_vals'): ".$insert_link_sth->errstr);
                }
            }

            {
                # Even with a straight import, Validate ID's cannot stay the same because they would conflict with link ID's.
                my $sth = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Links") or critical "Unable to prepare query `SELECT MAX(ID) FROM ${e_prefix}Links': ".$e_dbh->errstr;
                $sth->execute or critical "Unable to execute query `SELECT MAX(ID) FROM ${e_prefix}Links': ".$sth->errstr;
                $Links_counter = $sth->fetchrow_array;
                $sth->finish;
                if ($$opt{straight_import}) {
                    # For a straight import, we need to make sure that the link ID's used
                    # for non-validated links start after the highest old Link ID.
                    $sth = $i_dbh{$Links::DBSQL::Links::db_name}->prepare("SELECT MAX(ID) FROM $Links::DBSQL::Users::db_table") or critical "Unable to prepare query `SELECT MAX(ID) FROM $Links::DBSQL::Users::db_table: ".$i_dbh{$Links::DBSQL::Links::db_name}->errstr;
                    $sth->execute or critical "Unable to execute query `SELECT MAX(ID) FROM $Links::DBSQL::Users::db_table: ".$sth->errstr;
                    my $old_max = $sth->fetchrow_array;
                    $sth->finish;
                    $Links_counter = $old_max if $old_max > $Links_counter;
                }
            }
            $get_vlinks_sth->execute($old_id) or critical "Unable to execute query: ".$get_vlinks_sth->errstr;
            if ($odbc) {
                my $links_results = $get_vlinks_sth->fetchall_arrayref;
                $get_vlinks_sth->finish;
                $link_sub = sub { return shift @$links_results }
            }
            else {
                $link_sub = sub { $get_vlinks_sth->fetchrow_arrayref }
            }
            while(my $row = $link_sub->()) {
                $row = [@$row]; # Get rid of a peculiar read-only aliasing in DBI
                my ($id, $contact_name, $contact_email, $receive_mail, $cat_id) = splice @$row,0,5;

                unshift @$row, $contact_name, $contact_email;

                $get_cat_alts->execute($id) or critical "Unable to execute query `SELECT CategoryID FROM $Links::DBSQL::CategoryAlternates::db_table WHERE LinkID = ?': ".$get_cat_alts->errstr;
                my @alt_ids;
                while (my $row = $get_cat_alts->fetchrow_arrayref) {
                    push @alt_ids, ($$opt{straight_import} ? $$row[0] : $cat_map[$$row[0]]);
                }

                $id = ++$Links_counter;
                my $username;
                $user_count_sth->execute($contact_email) or warning("Unable to execute query: ".$user_count_sth->errstr);
                if ($user_count_sth->fetchrow_array) { # Exists
                    $user_mod_sth->execute($contact_name, ($receive_mail eq 'Yes' ? 'Yes' : 'No'), $contact_email) or warning("Unable to execute query `UPDATE ${e_prefix}Users SET Name = ?, ReceiveMail = ? WHERE Email = ?': ".$user_mod_sth->errstr);
                    $username_sth->execute($contact_email) or warning("Unable to execute query: ".$username_sth->errstr);
                    $username = $username_sth->fetchrow_arrayref()->[0];
                }
                elsif ($contact_email) { # Doesn't exist, but we can make the e-mail address into a username
                    $user_ins_sth->execute(($contact_email) x 2, '', (defined $contact_name ? $contact_name : ""), ($receive_mail eq 'Yes' ? 'Yes' : 'No')) or warning("Unable to execute query `INSERT INTO ${e_prefix}Users (Username, Email, Name, ReceiveMail) VALUES (?, ?, ?, ?, ?)': ".$user_ins_sth->errstr);
                    $username = $contact_email;
                }
                else { # Can't make a user; use the `admin' user.
                    mild_warning("Not enough information to add a user for link `".($$row[0] or '<unknown>')." (URL: ".($$row[1] or "<none>")."). Setting link owner to `admin'");
                    $username = 'admin';
                }
                if ($insert_vlink_sth->execute($id,$username,'No',@$row)) {
                    for ($id,@alt_ids) {
                        $cat_links_sth->execute($_,$new_id) or warning "Unable to execute query `INSERT INTO ${e_prefix}CatLinks (LinkID, CategoryID) VALUES (?, ?)': ".$cat_links_sth->errstr;
                    }
                    $num_of_links++;
                    import_print "$links_imported Links imported\n" unless ++$links_imported % 500;
                }
                else {
                    $Links_counter--;
                    warning("Unable to insert non-validated link `$$row[0]' (SQL query: `INSERT INTO ${e_prefix}Links $validate_ins_cols VALUES $validate_ins_vals'): ".$insert_vlink_sth->errstr);
                }
            }
            $num_links_sth->execute($num_of_links,$new_id) or warning "Unable to execute query `UPDATE ${e_prefix}Category SET Number_of_Links = ? WHERE ID = ?': ".$num_links_sth->errstr;
        }
        my $missing_cats;
        if ($$opt{create_missing_categories} and @missing_cats) {
            my $counter = $e_dbh->prepare("SELECT MAX(ID) FROM ${e_prefix}Category");
            $counter->execute();
            my $count = $counter->fetchrow_array();
            my $update_sub_cats = $e_dbh->prepare("UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? AND Full_Name NOT LIKE ?") or critical "Unable to prepare query `UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? AND Full_Name NOT LIKE ?': ".$e_dbh->errstr;
            for (@missing_cats) {
                my ($name) = m[([^/]+)\Z];
                my ($father_full) = m[\A(.*)/];
                my $father_id;
                if ($father_full) {
                    $father_sth->execute($father_full) or critical "Unable to execute query `SELECT ID FROM ${e_prefix}Category WHERE Full_Name = ?': ".$father_sth->errstr;
                    $father_id = $father_sth->fetchrow_array;
                }
                else { # Must be a root category
                    $father_id = 0;
                }
                $cat_ins_simple_sth->execute(++$count,$name,$_,$father_id) or critical "Unable to create missing category $_: ".$cat_ins_simple_sth->errstr;
                $update_sub_cats->execute($count,"$_/%","$_/%/%") or critical "Unable to execute query `UPDATE ${e_prefix}Category SET FatherID = ? WHERE Full_Name LIKE ? and Full_Name NOT LIKE ?': ".$update_sub_cats->errstr;
                $missing_cats++;
            }
        }

        import_print "$cats_imported Categories imported";
        import_print ", $missing_cats missing categories created" if $missing_cats;
        import_print ".\n";
        import_print "$links_imported Links imported.\n";

# Category Relations:
        if ($$opt{straight_import}) {
            $get_cat_relations->execute or critical "Unable to execute query `SELECT CategoryID, RelatedID from $Links::DBSQL::CategoryRelations::db_table': ".$get_cat_relations->errstr;
            while (my $row = $get_cat_relations->fetchrow_arrayref) {
                $add_cat_relation->execute(@$row) or warning "Unable to add category relation for categories with ID's $$row[0] and $$row[1]. Reason: ".$add_cat_relation->errstr;
            }
        }
        else {
            $get_cat_relations->execute or critical "Unable to execute query `SELECT CategoryID, RelatedID from $Links::DBSQL::CategoryRelations::db_table': ".$get_cat_relations->errstr;
            while (my $row = $get_cat_relations->fetchrow_arrayref) {
                $add_cat_relation->execute(@cat_map[@$row]) or warning "Unable to add category relation for categories with ID's: (new: $cat_map[$$row[0]], old: $$row[0]) and (new: $cat_map[$$row[1]], old: $$row[1]). Reason: ".$add_cat_relation->errstr;
            }
        }
    }

    for (keys %i_dbh) {
        $i_dbh{$_}->disconnect;
    }
    $e_dbh->disconnect;
    import_print "\nNOTE: You must run Rebuild Cat. tree, Repair Tables, and Rebuild Search after performing an import!\n";

    1;
}

# Returns a random password of random length (20-25 characters).
sub random_pass () {
    my @chars = ('a'..'z','A'..'Z',0..9,qw a _ [ ] { } ` ' " ! @ ^ * ( ) - _ = + : ; . / \ a,'#',',');
    my $pass = join '', map { $chars[rand @chars] } 0..(20+rand(5));
}

1;
