# ==================================================================
# 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: SQL.pm,v 1.141 2007/11/16 07:15:00 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.
# ==================================================================

# Contains the default table structure for Gossamer Links tables.
package Links::SQL;

use strict;
use vars  qw/@TABLES/;
use Links qw/:payment $DB/;

@TABLES = qw(
    Users Links Changes Category CatPrice Reviews CatLinks CatRelations
    Editors Verify Sessions EmailTemplates EmailMailings MailingIndex
    MailingList MailingListIndex ClickTrack Payments PaymentLogs
    Bookmark_Folders Bookmark_Links SearchLogs NewsletterSubscription
);

sub tables {
# ------------------------------------------------------------------
# Defines the SQL tables.
#
    my $action = shift || 'warn';
    my $output = '';

    my $ok = Links::language('dialog_ok');

# --------- Users Table ----------------
    create_table(\$output, 'Users', $action,
        cols => [
            Username        => { type => 'CHAR', size => 50, not_null => 1, form_display => Links::language('prompt_Username') },
            Password        => { type => 'CHAR', binary => 1, size => 25, not_null => 1, form_display => Links::language('prompt_Password') },
            Email           => { type => 'CHAR', size => 75, not_null => 1, regex => '^(?:.+\@.+\..+|\s*)$', form_display => Links::language('prompt_Email') },
            Name            => { type => 'CHAR', size => 75, form_display => Links::language('prompt_Name') },
            Validation      => { type => 'CHAR', size => 20, , form_display => Links::language('prompt_Validation') },
            Status          => { type => 'ENUM', values => ['Not Validated', 'Registered', 'Administrator'], not_null => 1, default => 'Registered', form_display => Links::language('prompt_Status') },
            ReceiveMail     => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'Yes', form_display => Links::language('prompt_ReceiveMail') },
            SortField       => { type => 'VARCHAR', size => 255, not_null => 1, regex => '^[\s\w]+$', default => 'Title', form_display => Links::language('prompt_SortField') },
            SortOrd         => { type => 'ENUM', values => ['ASC', 'DESC'], not_null => 1, default => 'ASC', form_display => Links::language('prompt_SortOrd') },
            PerPage         => { type => 'INT', not_null => 1, unsigned => 1, default => 15, form_display => Links::language('prompt_PerPage') },
            Grouping        => { type => 'TINYINT', not_null => 1, unsigned => 1, default => 0, form_display => Links::language('prompt_Grouping') },
        ],
        index => {
            emailndx => ['Email']
        },
        pk => 'Username',
        subclass => {
            table => { Users => 'Links::Table::Users' },
            html  => { Users => 'Links::HTML::Users' }
        }
    );

# --------- Links Table ----------------
    create_table(\$output, 'Links', $action,
        cols => [
            ID            => { type => 'INT', not_null => 1, unsigned => 1, regex => '^\d+$', form_display => Links::language('prompt_ID') },
            Title         => { type => 'CHAR', size => 100, not_null => 1, weight => 3, form_display => Links::language('prompt_Title') },
            URL           => { type => 'CHAR', size => 255, not_null => 1, weight => 1, default => 'http://', regex => '^\w+:', form_display => Links::language('prompt_URL') },
            LinkOwner     => { type => 'CHAR', size => 50, not_null => 1, default => 'admin', form_display => Links::language('prompt_LinkOwner') },
            Add_Date      => { type => 'DATE', not_null => 1, form_display => Links::language('prompt_Add_Date') },
            Mod_Date      => { type => 'DATE', not_null => 1, form_display => Links::language('prompt_Mod_Date') },
            Description   => { type => 'TEXT', weight => 1, form_display => Links::language('prompt_Description') },
            Contact_Name  => { type => 'CHAR', size => 255, form_display => Links::language('prompt_Contact_Name') },
            Contact_Email => { type => 'CHAR', size => 255, form_display => Links::language('prompt_Contact_Email') },
            Hits          => { type => 'INT', not_null => 1, default => 0, regex => '^\d+$', form_display => Links::language('prompt_Hits') },
            isNew         => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No', form_display => Links::language('prompt_isNew') },
            isChanged     => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No', form_display => Links::language('prompt_isChanged') },
            isPopular     => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No', form_display => Links::language('prompt_isPopular') },
            isValidated   => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'Yes', form_display => Links::language('prompt_isValidated') },
            Rating        => { type => 'DECIMAL', precision => 4, scale => 2, not_null => 1, default => 0, regex => '^(?:10(?:\.0*)?|\d(?:\.\d*)?)$', form_display => Links::language('prompt_Rating') },
            Votes         => { type => 'SMALLINT', unsigned => 1, not_null => 1, default => 0, regex => '^\d+$', form_display => Links::language('prompt_Votes') },
            Status        => { type => 'SMALLINT', not_null => 1, default => 0, regex => '^-?\d+$', form_display => Links::language('prompt_Status') },
            Date_Checked  => { type => 'DATETIME', form_display => Links::language('prompt_Date_Checked') },
            Timestmp      => { type => 'TIMESTAMP', time_check => 1, form_display => Links::language('prompt_Timestmp') },
            ExpiryDate    => { type => 'INT', not_null => 1, default => FREE, form_display => Links::language('prompt_ExpiryDate'), form_size => 35 }, # See FREE, UNPAID & UNLIMITED constants in Links.pm
            ExpiryCounted => { type => 'TINYINT', not_null => 1, default => 0, form_display => Links::language('prompt_ExpiryCounted'), form_type => 'hidden' },
            ExpiryNotify  => { type => 'TINYINT', not_null => 1, default => 0, form_display => Links::language('prompt_ExpiryNotify'), form_type => 'hidden' },
            LinkExpired   => { type => 'INT', form_display => Links::language('prompt_LinkExpired'), form_type => 'hidden' },
        ],
        pk => 'ID',
        ai => 'ID',
        fk => {
            Users => { LinkOwner => 'Username' }
        },
        index => {
            urlndx => ['URL'],
            stndx => ['Status'],
            valexpndx => [qw/isValidated ExpiryDate/],
            newndx => ['isNew'],
            popndx => ['isPopular'],
            userndx => ['LinkOwner'],
            expiryndx => [qw/ExpiryDate ExpiryNotify/],
            expcntndx => [qw/ExpiryCounted ExpiryDate/]
        },
        subclass => {
            table => { Links => 'Links::Table::Links' },
            html  => { Links => 'Links::HTML::Links' }
        }
    );

# --------- Changes Table ----------------
    create_table(\$output, 'Changes', $action,
        cols => [
            LinkID      => { type => 'INT', not_null => 1, unsigned => 1, regex => '^\d+$' },
            Username    => { type => 'CHAR', size => 50, not_null => 1, default => 'admin' },
            ChgRequest  => { type => 'TEXT' },
            Timestmp    => { type => 'TIMESTAMP' }
        ],
        fk => {
            Links => { LinkID => 'ID' },
            Users => { Username => 'Username' }
        }
    );

# --------- Category Table ----------------
    my $new_category = create_table(\$output, 'Category', $action,
        cols => [
            ID                  => { type => 'INT', not_null => 1, unsigned => 1, form_display => Links::language('prompt_ID') },
            Name                => { type => 'CHAR', size => 255, not_null => 1, weight => 3, regex => '^[^/]+$', form_display => Links::language('prompt_Name') },
            FatherID            => { type => 'INT', not_null => 1, unsigned => 1, default => 0, form_size => 1, form_display => Links::language('prompt_FatherID') },
            CatRoot             => { type => 'INT', not_null => 1, unsigned => 1, default => 0, form_type => 'hidden' },
            CatDepth            => { type => 'INT', not_null => 1, unsigned => 1, default => 0, form_type => 'hidden' },
            Full_Name           => { type => 'CHAR', size => 255, form_display => Links::language('prompt_Full_Name') },
            Description         => { type => 'TEXT', weight => 1, form_display => Links::language('prompt_Description') },
            Meta_Description    => { type => 'TEXT', form_display => Links::language('prompt_Meta_Description') },
            Meta_Keywords       => { type => 'TEXT', form_display => Links::language('prompt_Meta_Keywords') },
            Header              => { type => 'TEXT', form_display => Links::language('prompt_Header') },
            Footer              => { type => 'TEXT', form_display => Links::language('prompt_Footer') },
            Category_Template   => { type => 'CHAR', size => 20, form_display => Links::language('prompt_Category_Template') },
            Number_of_Links     => { type => 'INT', not_null => 1, default => 0, form_display => Links::language('prompt_Number_of_Links') },
            Direct_Links        => { type => 'INT', not_null => 1, default => 0, form_display => Links::language('prompt_Direct_Links') },
            Has_New_Links       => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No', form_display => Links::language('prompt_Has_New_Links') },
            Has_Changed_Links   => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No', form_display => Links::language('prompt_Has_Changed_Links') },
            Newest_Link         => { type => 'DATE', form_display => Links::language('prompt_Newest_Link') },
            Timestmp            => { type => 'TIMESTAMP', time_check => 1, form_display => Links::language('prompt_Timestmp') },
            Payment_Mode        => { type => 'TINYINT', not_null => 1, default => 0, form_size => 1, form_names => [GLOBAL,NOT_ACCEPTED,OPTIONAL,REQUIRED], form_values => ['Use global settings','Not accepted','Optional','Required'], form_type => 'SELECT', form_display => Links::language('prompt_Payment_Mode') },
            Payment_Description => { type => 'TEXT', form_display => Links::language('prompt_Payment_Description') },
        ],
        subclass => {
            table => { Category => 'Links::Table::Category' },
            html  => { Category => 'Links::HTML::Category' }
        },
        pk => 'ID',
        ai => 'ID',
        index => {
            catndx => ['Name'],
            namndx => ['Full_Name'],
            fthrindex => ['FatherID'],
            rootndx => ['CatRoot'],
            c_p => ['Payment_Mode'],
        }
    );

# --------- Category Tree -------------------------
    $output .= "Creating Category tree... ";
    my $e = $DB->editor('Category');
    if ($e->add_tree(father => "FatherID", root => "CatRoot", depth => "CatDepth", force => ($new_category ? 'force' : 'check'))) {
        $output .= "okay\n";
    }
    else {
        $output .= "failed ($GT::SQL::error)\n";
    }

# --------- CatPrice Table ----------------
    create_table(\$output, 'CatPrice', $action,
        cols => [
            cp_id          => { type => 'INT', not_null => 1, unsigned => 1 },
            cp_cat_id_fk   => { type => 'INT', not_null => 1, unsigned => 1 },
            cp_term        => { type => 'CHAR', not_null => 1, size => 10 }, # e.g. 8d, 1m, 2y, 3w, unlimited, etc.
            cp_cost        => { type => 'DOUBLE', not_null => 1 },
            cp_type        => { type => 'TINYINT', not_null => 1, unsigned => 1 }, # 0 = signup, 1 = renewal, 2 = recurring
            cp_description => { type => 'TEXT' }
        ],
        pk => 'cp_id',
        ai => 'cp_id',
        fk => {
            Category => { cp_cat_id_fk => 'ID' }
        }
    );

# --------- Reviews Table ----------------
    create_table(\$output, 'Reviews', $action,
        cols => [
            ReviewID             => { type => 'INT', not_null => 1, unsigned => 1, form_display => Links::language('prompt_ReviewID') },
            Review_LinkID        => { type => 'INT', not_null => 1, unsigned => 1, regex => '^\d+$', form_display => Links::language('prompt_Review_LinkID') },
            Review_Owner         => { type => 'CHAR', size => 50, not_null => 1, form_display => Links::language('prompt_Review_Owner') },
            Review_Rating        => { type => 'SMALLINT', unsigned => 1, not_null => 1, default => 0, regex => '^\d+$', form_display => Links::language('prompt_Review_Rating') },
            Review_Date          => { type => 'DATETIME', not_null => 1, form_display => Links::language('prompt_Review_Date') },
            Review_ModifyDate    => { type => 'DATETIME', not_null => 1, form_display => Links::language('prompt_Review_ModifyDate') },
            Review_Subject       => { type => 'CHAR', size => 100, not_null => 1, form_display => Links::language('prompt_Review_Subject') },
            Review_Contents      => { type => 'TEXT', not_null => 1, form_display => Links::language('prompt_Review_Contents') },
            Review_ByLine        => { type => 'CHAR', size => 50, form_display => Links::language('prompt_Review_ByLine') },
            Review_WasHelpful    => { type => 'INT', unsigned => 1, regex => '^\d+$', form_display => Links::language('prompt_Review_WasHelpful') },
            Review_WasNotHelpful => { type => 'INT', unsigned => 1, regex => '^\d+$', form_display => Links::language('prompt_Review_WasNotHelpful') },
            Review_Validated     => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No', form_display => Links::language('prompt_Review_Validated') },
            Review_GuestName     => { type => 'CHAR', size => 75, form_display => Links::language('prompt_Review_GuestName') },
            Review_GuestEmail    => { type => 'CHAR', size => 75, regex => '^(?:(?:.+\@.+\..+)|\s*)$', form_display => Links::language('prompt_Review_GuestEmail') },
        ],
        pk => 'ReviewID',
        ai => 'ReviewID',
        subclass => {
            table => { Reviews => 'Links::Table::Reviews' }
        },
        index => {
            rownerndx => ['Review_Owner'],
            rdatendx => ['Review_Date'],
            rlinkndx => ['Review_LinkID']
        },
        fk => {
            Links => { Review_LinkID => 'ID' },
            Users => { Review_Owner => 'Username' }
        }
    );

# --------- CatLinks Table ----------------
    create_table(\$output, 'CatLinks', $action,
        cols => [
            LinkID     => { type => 'INT', not_null => 1, unsigned => 1 },
            CategoryID => { type => 'INT', not_null => 1, unsigned => 1 }
        ],
        subclass => {
            table => { CatLinks => 'Links::Table::CatLinks' }
        },
        index => {
            lndx => ['LinkID']
        },
        unique => {
            cl_cl_q => [qw/CategoryID LinkID/]
        },
        fk => {
            Links => { LinkID => 'ID' },
            Category => { CategoryID => 'ID' }
        }
    );

# --------- CatRelations Table ----------------
    create_table(\$output, 'CatRelations', $action,
        cols => [
            CategoryID   => { type => 'INT', not_null => 1, unsigned => 1 },
            RelatedID    => { type => 'INT', not_null => 1, unsigned => 1 },
            RelationName => { type => 'VARCHAR', size => 255 }
        ],
        index => {
            catid => ['CategoryID']
        },
        fk => {
            Category => { CategoryID => 'ID', RelatedID => 'ID' }
        }
    );

# --------- User Editors Table ----------------
    create_table(\$output, 'Editors', $action,
        cols => [
            Username     => { type => 'CHAR', size => 50, not_null => 1 },
            CategoryID   => { type => 'INT', unsigned => 1, not_null => 1 },
            CanAddCat    => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
            CanModCat    => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
            CanDelCat    => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
            CanMoveCat   => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
            CanAddLink   => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
            CanDelLink   => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
            CanModLink   => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
            CanCopyLink  => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
            CanMoveLink  => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
            CanValLink   => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
            CanModReview => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
            CanAddRel    => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
            CanAddEdit   => { type => 'ENUM', values => ['No', 'Yes'], not_null => 1, default => 'No' },
        ],
        unique => {
            edituserndx => ['Username', 'CategoryID']
        },
        fk => {
            Users => { Username => 'Username' },
            Category => { CategoryID => 'ID' }
        }
    );

# --------- Verify History Table ----------------
    create_table(\$output, 'Verify', $action,
        cols => [
            LinkID       => { type => 'INT', unsigned => 1, not_null => 1 },
            Status       => { type => 'SMALLINT', not_null => 1, default => 0 },
            Date_Checked => { type => 'DATE' }
        ],
        index => {
            veriflndx =>  ['LinkID']
        },
        fk => {
            Links => { LinkID => 'ID' }
        }
    );

# --------- Session Table ----------------
    create_table(\$output, 'Sessions', $action,
        cols => [
            session_id      => { type => 'CHAR', size => 32, not_null => 1, binary => 1 },
            session_user_id => { type => 'CHAR', size => 50, not_null => 1 },
            session_date    => { type => 'INT', not_null => 1 },
            session_expires => { type => 'TINYINT', default => 1 },
            session_data    => { type => 'TEXT' }
        ],
        pk => 'session_id',
        fk => {
            Users => { session_user_id => 'Username' }
        }
    );

# --------- Email Template Table ----------------
    create_table(\$output, 'EmailTemplates', $action,
        cols => [
            Name          => { type => 'CHAR', size => 50, not_null => 1, regex => '\S' },
            MsgFrom       => { type => 'TEXT', not_null => 1, regex => '\A(?:\S+\@[a-zA-Z0-9][a-zA-Z0-9-]*(?:\.[a-zA-Z0-9][a-zA-Z0-9-]*)+)\Z' },
            MsgFromName   => { type => 'TEXT', not_null => 1 },
            Subject       => { type => 'TEXT', not_null => 1 },
            Message       => { type => 'MEDIUMTEXT', not_null => 1 },
            MessageFormat => { type => 'ENUM', values => [qw[text html]], not_null => 1, default => 'text' },
            LinkTemplate  => { type => 'MEDIUMTEXT' }
        ],
        pk => 'Name'
    );

# --------- Email Mailings Table -------------
    create_table(\$output, 'EmailMailings', $action,
        cols => [
            ID      => { type => 'INT', unsigned => 1, not_null => 1 },
            Mailing => { type => 'INT', unsigned => 1, not_null => 1 },
            Email   => { type => 'TEXT', size => 75, not_null => 1 },
            Sent    => { type => 'TINYINT', default => 0, not_null => 1 },
            LinkID  => { type => 'INT', unsigned => 1 } # If this is a sending to link owners, this will hold the Link ID
        ],
        pk => 'ID',
        ai => 'ID'
    );

# --------- Email Mailing Index Table --------
    create_table(\$output, 'MailingIndex', $action,
        cols => [
            Mailing       => { type => 'INT', unsigned => 1, not_null => 1 },
            extra         => { type => 'TINYTEXT', not_null => 1 },
            done          => { type => 'INT' },
            mailfrom      => { type => 'TEXT', not_null => 1 },
            name          => { type => 'TEXT', not_null => 1 },
            subject       => { type => 'TEXT', not_null => 1 },
            message       => { type => 'MEDIUMTEXT', not_null => 1 },
            messageformat => { type => 'ENUM', values => [qw[text html]], not_null => 1, default => 'text' },
        ],
        pk => 'Mailing',
        ai => 'Mailing'
    );

# --------- MailingList Table ----------------
    create_table(\$output, 'MailingList', $action,
        cols => [
            ID    => { type => 'INT', not_null => 1 },
            Email => { type => 'CHAR', size => 255, not_null => 1 }
        ],
        index => {
            maillistndx => ['Email']
        }
    );

# --------- MailingListIndex Table ----------------
    create_table(\$output, 'MailingListIndex', $action,
        cols => [
            ID           => { type => 'INT', unsigned => 1, not_null => 1 },
            Name         => { type => 'CHAR', size => 255, not_null => 1 },
            DateModified => { type => 'INT', not_null => 1 },
            DateCreated  => { type => 'INT', not_null => 1 }
        ],
        pk => 'ID',
        ai => 'ID'
    );

# --------- ClickTrack Table ----------------
    create_table(\$output, 'ClickTrack', $action,
        cols => [
            LinkID    => { type => 'INT', not_null => 1 },
            IP        => { type => 'CHAR', size => 16, not_null => 1 },
            ClickType => { type => 'ENUM', values => ['Rate', 'Hits','Review'], not_null => 1 },
            ReviewID  => { type => 'INT', not_null => 1, default => 0},
            Created   => { type => 'TIMESTAMP' }
        ],
        subclass => {
            table => { ClickTrack => 'Links::Table::ClickTrack' }
        },
        unique => {
            ct_licr => ['LinkID', 'IP', 'ClickType','ReviewID']
        },
        index => {
            cndx => ['Created']
        }
    );

# --------- Payments Table ----------------
    create_table(\$output, 'Payments', $action,
        cols => [
            payments_id     => { type => 'CHAR', not_null => 1, size => 16 },
            payments_linkid => { type => 'INT', unsigned => 1, not_null => 1 },
            payments_status => { type => 'INT', not_null => 1, default => 0, unsigned => 1 }, # 0 = pending, 1 = completed, 2 = declined, 3 = error
            payments_method => { type => 'CHAR', not_null => 1, size => 25 },
            payments_type   => { type => 'TINYINT', not_null => 1, unsigned => 1 }, # 0 = initial payment, 1 = renewal payment, 2 = recurring payment
            payments_amount => { type => 'DOUBLE', not_null => 1 },
            payments_term   => { type => 'CHAR', not_null => 1, size => 10 }, # e.g. 8d, 1m, 2y, 3w, unlimited, etc.
            payments_start  => { type => 'INT', not_null => 1, unsigned => 1 },
            payments_last   => { type => 'INT', not_null => 1, unsigned => 1 },
        ],
        pk => 'payments_id',
        fk => {
            Links => { payments_linkid => 'ID' }
        },
        index => {
            p_sl => ['payments_status', 'payments_last'],
            p_ll => ['payments_linkid', 'payments_last'],
            p_al => ['payments_amount', 'payments_last'],
        }
    );

# --------- Payment Logs Table ----------------
    create_table(\$output, 'PaymentLogs', $action,
        cols => [
            paylogs_id          => { type => 'INT', not_null => 1, unsigned => 1 },
            paylogs_payments_id => { type => 'CHAR', not_null => 1, size => 16 },
            paylogs_type        => { type => 'INT', not_null => 1, default => 0, unsigned => 1 }, # 0 = info, 1 = accepted, 2 = declined, 3 = error
            paylogs_time        => { type => 'INT', not_null => 1, unsigned => 1 },
            paylogs_viewed      => { type => 'TINYINT', not_null => 1, default => 0, unsigned => 1 },
            paylogs_text        => { type => 'TEXT' },
        ],
        pk => 'paylogs_id',
        ai => 'paylogs_id',
        fk => {
            Payments => { paylogs_payments_id => 'payments_id' }
        },
        index => {
            pl_yt => ['paylogs_type', 'paylogs_time'],
            pl_t => ['paylogs_time']
        }
    );

# --------- Bookmark Folders Table ----------------
    create_table(\$output, 'Bookmark_Folders', $action,
        cols => [
            my_folder_id               => { type => 'INT', not_null => 1, unsigned => 1 },
            my_folder_name             => { type => 'VARCHAR', not_null => 1, size => 255 },
            my_folder_description      => { type => 'VARCHAR', size => 255 },
            my_folder_user_username_fk => { type => 'VARCHAR', size => 50 },
            my_folder_default          => { type => 'TINYINT', not_null => 1, default => 0, unsigned => 1 },
            my_folder_public           => { type => 'TINYINT', not_null => 1, default => 0, unsigned => 1 }
        ],
        pk => 'my_folder_id',
        ai => 'my_folder_id',
        fk => {
            Users => { my_folder_user_username_fk => 'Username' }
        }
    );

# --------- Bookmark Links Table ----------------
    create_table(\$output, 'Bookmark_Links', $action,
        cols => [
            my_id               => { type => 'INT', not_null => 1, unsigned => 1 },
            my_link_id_fk       => { type => 'INT', not_null => 1, unsigned => 1 },
            my_user_username_fk => { type => 'VARCHAR', size  => 50 },
            my_folder_id_fk     => { type => 'INT', not_null => 1, unsigned => 1 },
            my_comment          => { type => 'VARCHAR', size  => '255' }
        ],
        pk => 'my_id',
        ai => 'my_id',
        fk => {
            Users            => { my_user_username_fk => 'Username' },
            Bookmark_Folders => { my_folder_id_fk => 'my_folder_id' },
            Links            => { my_link_id_fk => 'ID' },
        }
    );

# --------- SearchLogs Table ----------------
    create_table(\$output, 'SearchLogs', $action,
        cols => [
            slog_query => { type => 'VARCHAR', not_null => 1, size => 255 },
            slog_count => { type => 'INT', not_null => 1, default => 0 },
            slog_hits  => { type => 'INT', not_null => 1, default => 0 },
            slog_time  => { type => 'FLOAT' },
            slog_last  => { type => 'INT', not_null => 1, default => 0 },
        ],
        pk => 'slog_query'
    );

# --------- Newsletter Subscription Table ----------------
    create_table(\$output, 'NewsletterSubscription', $action,
        cols => [
            UserID     => { type => 'CHAR', size => 50 },
            CategoryID => { type => 'INT', not_null => 1 },
        ],
        unique => {
            ns_uc => ['UserID', 'CategoryID']
        },
        fk => {
            Users    => { UserID => 'Username' },
            Category => { CategoryID => 'ID' }
        }
    );

    return $output;
}

sub create_table {
    my ($output, $table, $action, @def) = @_;

    $$output .= Links::language('dialog_create', $table);
    my $c = $DB->creator($table);
    $c->clear_schema() if $action eq 'force';

    @def % 2 and die "Odd number of table defs passed to create_table()";
    while (@def) {
        my ($meth, $arg) = splice @def, 0, 2;
        $c->$meth($arg);
    }

    if ($c->create($action)) {
        $$output .= Links::language('dialog_ok');
        return 1;
    }
    else {
        $$output .= Links::language($GT::SQL::errcode eq 'TBLEXISTS' ? ('error_failed_exists') : ('error_failed_other', $GT::SQL::error));
        $GT::SQL::errcode if 0; # silence "used only once" warning
        $c->set_defaults;
        $c->save_schema;
        return 0;
    }
}

sub load_from_sql {
# ---------------------------------------------------------------
# Creates def files based on existing tables.
#
    my ($output, $return);
    foreach my $table (@TABLES) {
        $output .= "$table .. ";
        my $c = $DB->creator($table);
        $return = $c->load_table($table);
        if ($return) {
            if ($table eq 'Links' or $table eq 'Users' or $table eq 'Category') {
                $c->subclass(
                    table => { $table => "Links::Table::$table" },
                    html  => { $table => "Links::HTML::$table" }
                );
            }
            elsif ($table eq 'CatLinks' or $table eq 'ClickTrack') {
                $c->subclass(
                    table => { $table => "Links::Table::$table" }
                );
            }
            $output .= "ok!\n";
            $c->save_schema();
        }
        else {
            $output .= "failed: $GT::SQL::error\n";
        }
    }
    return $output;
}

sub load {
# ---------------------------------------------------------------
# Return a hash of current connection settings.
#
    my %h = ();
    $h{prefix}   = $DB->prefix();
    $h{database} = $DB->{connect}->{database};
    $h{login}    = $DB->{connect}->{login};
    $h{password} = $DB->{connect}->{password};
    $h{host}     = $DB->{connect}->{host};
    $h{host}    .= ":" . $DB->{connect}->{port} if $DB->{connect}->{port};
    $h{driver}   = $DB->{connect}->{driver};
    return \%h;
}

1;
