#!/usr/bin/perl -w

# author: mvc
# $Id: biblio.pl,v 1.17 2007/08/24 22:31:35 root Exp root $
# aug-sep 2005: first version
# apr 2006: multiple values for URLs, database_name; update of individual fields
# may 2006: pubmed-style 'shopping cart'
# fall 2006: slash->drupal conversion
# aug 2007: generate list of database names automatically

# Q: why are these all defined as function references?
# A: because mod_perl requires it:
#    http://perl.apache.org/docs/general/perl_reference/perl_reference.html

use strict;
use CGI qw(escape unescape);
CGI->compile(':standard');
use CGI::Carp qw(fatalsToBrowser warningsToBrowser);
use DBI;
use XML::Simple;
use Data::Dumper;

############################

# global constants
my $user;
my $debug = 0;

my $q = new CGI;
my $tmpdir = '/tmp/';
my $op = $q->param('op') || '';
my $file = $q->param('file') || '';
my $f1 = $q->param('f1') || '';
my $v1 = $q->param('v1') || '';
my $f2 = $q->param('f2') || '';
my $v2 = $q->param('v2') || '';
my $k1 = $q->param('k1') || '';
my $db = $q->param('db') || 'A';
my $rd = $q->param('rd') || 'A';
my $gostart = $q->param('gostart') || '0';
my $go = $q->param('go') || '';
my $start = $q->param('start') || '0';
my $id = $q->param('id') || '0';
my @args = $q->param;
my %save1 = $q->cookie('save1');
my %save2 = $q->cookie('save2');
my $per_page_default = 30;

# these features are currently undocumented
my $per_page = $q->param('display') || $per_page_default;
my $show_abstracts = $q->param('show_abstracts') || 0;

# security
$gostart = abs int $gostart;
$start = abs int $start;
$per_page = abs int $per_page;
$id = abs int $id;
$go =~ s/[\W]+//g;
$db =~ s/[\W]+//g;
$rd =~ s/[\W]+//g;
$f1 =~ s/[\W]+//g;
$v1 =~ s/[^-\w \/?]+//g;
$f2 =~ s/[\W]+//g;
$v2 =~ s/[^-\w \/?]+//g;
$k1 =~ s/[^-\w \/?\[\]()*',.&]+//g; # keywords have all these characters (sigh)

my $fr_check_user = sub {
    # drupal authentication
    my $drupal_uid = CGI::cookie('drupal_uid');
    my $drupal_admin = CGI::cookie('drupal_admin');
    if ($drupal_uid && $drupal_uid > 0) {
        return 1;
    } else {
        return 0;
    }
};

my $fr_check_admin = sub {
    # drupal authentication
    my $drupal_uid = CGI::cookie('drupal_uid');
    my $drupal_admin = CGI::cookie('drupal_admin');
    if ($drupal_admin && $drupal_admin eq 'true') {
        return 1;
    } else {
        return 0;
    }
};

# generate this automatically, so RGP can upload new
# files, corresponding to new database names
# (NONE is only an option when applicable records exist)
my %database_names = ();

my %research_designs = (
        "COM" , "Commentary",
        "DES" , "Descriptive",
        "MA"  , "Meta-analysis",
        "PSY" , "Psychometric",
        "QE"  , "Quasi experimental",
        "RCT" , "RCT",
        "REV" , "Review",
        "NONE", "Records with none specified",
        "A",  "All Research Designs",
    );

my %research_design_descriptions = (
        "COM" , "Commentaries provide opinions or reflections on other
                research or key issues.",
        "DES" , "Descriptive designs are those which use case studies,
                program descriptions and/or descriptive statistics.",
        "MA"  , "Meta-analyses provide overviews of randomized control
                trials.",
        "PSY" , "Psychometric refers to designs focused on developing
                measures.",
        "QE"  , "Quasi-experimental designs are those which use samples of
                convenience to test hypotheses.",
        "RCT" , "Randomised controlled trials (RCT) are designs which
                compare randomly selected groups.",
        "REV" , "Reviews provide overviews of research of all design
                types.",
        "NONE", "No research design available in summary.",
    );

my $dbh;
my @db_fields = qw( rec_number ref_type_name ref_type_num database_path
        database_name title journal translated_title alt_title short_title
        orig_pub language authors secondary_authors pub_location
        auth_address work_type notes research_design pages volume number
        year pub_dates accession_num publisher isbn urls subset1 subset2
        subset3 abstract );
my $sth_insert;
my $sth_update;
my %sth_update_fields;
my %sth_fetch_fields;
my $sth_keyword_check;
my $sth_keyword_insert;
my $sth_biblio_keyword_check;
my $sth_biblio_keyword_insert;
my $fr_db_init = sub {
    my $db_host = 'localhost';
    my $db_user = 'rgp_biblio';
    my $db_pass = 'rgp_biblio';
    my $db_name = 'rgp_biblio';
    my $db_uri = "dbi:mysql:dbname=${db_name};host=${db_host}";
    $dbh = DBI->connect($db_uri, $db_user, $db_pass, { RaiseError => 1,
            AutoCommit => 0 })
        || die "Error connecting to the database: $DBI::errstr\n";
    unlink '/tmp/mvc2-dbi.log' if $debug > 1;
    $dbh->trace(1, '/tmp/mvc2-dbi.log') if $debug > 1;
    # set up database queries
    $sth_insert = $dbh->prepare(q{
            INSERT INTO biblio (
                    rec_number,
                    ref_type_name,
                    ref_type_num,
                    database_path,
                    database_name,
                    title,
                    journal,
                    translated_title,
                    alt_title,
                    short_title,
                    orig_pub,
                    language,
                    authors,
                    secondary_authors,
                    pub_location,
                    auth_address,
                    work_type,
                    notes,
                    research_design,
                    pages,
                    volume,
                    number,
                    year,
                    pub_dates,
                    accession_num,
                    publisher,
                    isbn,
                    urls,
                    subset1,
                    subset2,
                    subset3,
                    abstract
                  ) VALUES (
                    ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
                    ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
                  ) })
        || die $dbh->errstr;
    $sth_update = $dbh->prepare(q{
            UPDATE biblio SET 
                    rec_number = ?,
                    ref_type_name = ?,
                    ref_type_num = ?,
                    database_path = ?,
                    database_name = ?,
                    title = ?,
                    journal = ?,
                    translated_title = ?,
                    alt_title = ?,
                    short_title = ?,
                    orig_pub = ?,
                    language = ?,
                    authors = ?,
                    secondary_authors = ?,
                    pub_location = ?,
                    auth_address = ?,
                    work_type = ?,
                    notes = ?,
                    research_design = ?,
                    pages = ?,
                    volume = ?,
                    number = ?,
                    year = ?,
                    pub_dates = ?,
                    accession_num = ?,
                    publisher = ?,
                    isbn = ?,
                    urls = ?,
                    subset1 = ?,
                    subset2 = ?,
                    subset3 = ?,
                    abstract =?
                WHERE id=?
            })
        || die $dbh->errstr;
    foreach my $db_field (@db_fields) {
        $sth_update_fields{$db_field} = 
            $dbh->prepare(qq{UPDATE biblio SET $db_field = ? WHERE id = ?})
                || die $dbh->errstr;
    }
    foreach my $db_field (@db_fields) {
        $sth_fetch_fields{$db_field} = 
            $dbh->prepare(qq{SELECT $db_field FROM biblio WHERE id = ?})
                || die $dbh->errstr;
    }
    $sth_keyword_check = $dbh->prepare(q{
            SELECT kid FROM keywords WHERE keyword=?})
        || die $dbh->errstr;
    $sth_keyword_insert = $dbh->prepare(q{
            INSERT INTO keywords(keyword) VALUES (?)})
        || die $dbh->errstr;
    $sth_biblio_keyword_check = $dbh->prepare(q{
            SELECT id,kid FROM biblio_keywords WHERE id=? AND kid=?})
        || die $dbh->errstr;
    $sth_biblio_keyword_insert = $dbh->prepare(q{
            INSERT INTO biblio_keywords(id,kid) VALUES (?,?)})
        || die $dbh->errstr;
};

my $fr_normalize_research_design = sub {
    my $research_design = shift;
    $research_design = 'Descriptive'
        if $research_design eq 'Correlational';
    $research_design = 'Psychometric'
        if $research_design eq 'Correlational - Factor analysis';
    $research_design = 'Descriptive'
        if $research_design eq 'Correlational?';
    $research_design = 'Quasi experimental'
        if $research_design eq 'Correlational? Experimental - Quasi?';
    $research_design = 'Quasi experimental'
        if $research_design eq 'Correlational? Experimental - Sample as own control?';
    $research_design = 'Quasi experimental'
        if $research_design eq 'Correlational? Experimental?';
    $research_design = 'Descriptive'
        if $research_design eq 'Descriptive';
    $research_design = 'Descriptive'
        if $research_design eq 'Descriptive - Case studies';
    $research_design = 'Commentary'
        if $research_design eq 'Descriptive - Commentary/description';
    $research_design = 'Commentary'
        if $research_design eq 'Descriptive - Commentary/description, Correlational?';
    $research_design = 'Commentary'
        if $research_design eq 'Descriptive - Commentary/description, Meta-analysis?';
    $research_design = 'Commentary'
        if $research_design eq 'Descriptive - Commentary/description?';
    $research_design = 'Descriptive'
        if $research_design eq 'Descriptive - Qualitative';
    $research_design = 'Descriptive'
        if $research_design eq 'Descriptive - Qualitiative';
    $research_design = 'Descriptive'
        if $research_design eq 'Descriptive?';
    $research_design = 'Descriptive'
        if $research_design eq 'Descriptive? Correlational?';
    $research_design = 'Review'
        if $research_design eq 'Descriptive? Correlational? Meta-analysis?';
    $research_design = 'Quasi experimental'
        if $research_design eq 'Descriptive? Experimental - Sample as own control/Quasi?';
    $research_design = 'Descriptive'
        if $research_design eq 'Descriptive? Experimental?';
    $research_design = 'Descriptive'
        if $research_design eq 'Descriptive? Meta-analysis?';
    $research_design = 'Quasi Experimental'
        if $research_design eq 'Experimental';
    $research_design = 'Quasi experimental'
        if $research_design eq 'Experimental - Quasi';
    $research_design = 'Quasi experimental'
        if $research_design eq 'Experimental - Quasi?';
    $research_design = 'RCT'
        if $research_design eq 'Experimental - RCT';
    $research_design = 'RCT'
        if $research_design eq 'Experimental - RCT (baseline data)';
    $research_design = 'Quasi experimental'
        if $research_design eq 'Experimental - Sample as own control';
    $research_design = 'Quasi experimental'
        if $research_design eq 'Experimental - Sample as own control?';
    $research_design = 'Quasi Experimental'
        if $research_design eq 'Experimental?';
    $research_design = 'Meta-analysis'
        if $research_design eq 'Meta-analysis';
    $research_design = 'Review'
        if $research_design eq 'Meta-analysis - Mixed?';
    $research_design = 'Review'
        if $research_design eq 'Meta-analysis - Qualitative';
    $research_design = 'Meta-analysis'
        if $research_design eq 'Meta-analysis - RCTs';
    $research_design = 'Meta-analysis'
        if $research_design eq 'Metanalysis';
    return $research_design;
};

my $fr_fixup = sub {
    my $input = shift;
    return '' unless defined $input;
    $input =~ s/^\s*(.*?)\s*$/$1/;
    #return $dbh->quote($input); # done implicitly with variable substitution
    return $input;
};

my $fr_fixup_byref = sub {
    my $input = shift;
    return '' unless defined $input;
    $$input =~ s/^\s*(.*?)\s*$/$1/;
    $$input = substr($$input, 0, 255) if length $$input > 255;
    # the VARCHAR type strips trailing spaces, so do this here too for
    # consistency when comparing strings
    $$input =~ s/ +$//;
    return $$input;
};

my $fr_fixup_byref_full = sub {
    my $input = shift;
    return '' unless defined $input;
    $$input =~ s/^\s*(.*?)\s*$/$1/;
    return $$input;
};

my $fr_joindata = sub {
    my $input = shift;
    $input =~ s/[\r\n]+/; /g;
    return $input;
};

my $fr_print_record = sub {
    # this function is called 'print record' but it also prepares the
    # imported content to be inserted into the database.
    #
    # note that this deliberately uses defined instead of exists, so that
    # fr_add_record won't have to check for undefined hash values.  this is
    # a tad messy but simpler to code than properly testing for existance
    # twice.  as far as the database is concerned null values are
    # equivalent to empty strings, so this is okay. -- mvc
    my $record = shift;

#    print "<pre>", Dumper($record), "</pre>";
    if (defined $record->{'rec-number'}->{content}) {
        printf "Record Number: %d<br>\n",
        &$fr_fixup_byref(\$record->{'rec-number'}->{content});
    }
    if (defined $record->{'ref-type'}->{name}) {
        printf "Reference Type Name: %s<br>\n",
        &$fr_fixup_byref(\$record->{'ref-type'}->{name});
    }
    if (defined $record->{'ref-type'}->{content}) {
        printf "Reference Type Number: %s<br>\n",
        &$fr_fixup_byref(\$record->{'ref-type'}->{content});
    }
    if (defined $record->{database}->{path}) {
        printf "Database Path: %s<br>\n",
        &$fr_fixup_byref(\$record->{database}->{path});
    }
    if (defined $record->{'remote-database-name'}->{style}->{content}) {
        printf "Database Name: %s<br>\n",
        &$fr_fixup_byref(\$record->{'remote-database-name'}->{style}->{content});
    }
    if (defined $record->{titles}->{title}->{style}->{content}) {
        printf "Title: %s<br>\n",
        &$fr_fixup_byref(\$record->{titles}->{title}->{style}->{content});
    }
    if (defined $record->{titles}->{'secondary-title'}->{style}->{content}) {
        printf "Secondary Title: %s<br>\n",
        &$fr_fixup_byref(\$record->{titles}->{'secondary-title'}->{style}->{content});
    }
    if (defined $record->{titles}->{'translated-title'}->{style}->{content}) {
        printf "Translated Title: %s<br>\n",
        &$fr_fixup_byref(\$record->{titles}->{'translated-title'}->{style}->{content});
    }
    if (defined $record->{titles}->{'short-title'}->{style}->{content}) {
        printf "Short Title: %s<br>\n",
        &$fr_fixup_byref(\$record->{titles}->{'short-title'}->{style}->{content});
    }
    if (defined $record->{titles}->{'alt-title'}->{style}->{content}) {
        printf "Alternative Title: %s<br>\n",
        &$fr_fixup_byref(\$record->{titles}->{'alt-title'}->{style}->{content});
    }
    if (defined $record->{'orig-pub'}->{style}->{content}) {
        printf "Original Publication: %s<br>\n",
        &$fr_fixup_byref(\$record->{'orig-pub'}->{style}->{content});
    }
    if (defined $record->{language}->{style}->{content}) {
        printf "Language: %s<br>\n",
        &$fr_fixup_byref(\$record->{language}->{style}->{content});
    }
    if (defined($record->{keywords}->{keyword})) {
        if (ref($record->{keywords}->{keyword}) eq 'ARRAY') {
            my @keywords;
            foreach my $keyword (@{$record->{keywords}->{keyword}}) {
                if (defined $keyword->{style}->{content}) {
                    #printf "Keyword: %s<br>\n", $keyword->{style}->{content};
                    push @keywords, $keyword->{style}->{content};
                    &$fr_fixup_byref(\$keywords[$#keywords]);
                }
            }
            my $keywords = join('; ', @keywords);
            $record->{mykeywords} = $keywords;
            printf "Keyword%s: %s<br>\n",
                $#keywords > 0 ? 's' : '',
                $keywords;
        } else {
            my $keyword = $record->{keywords}->{keyword};
            if (defined $keyword->{style}->{content}) {
                printf "Keyword: %s<br>\n", $keyword->{style}->{content};
                $record->{mykeywords} = $keyword->{style}->{content};
                $record->{mykeywords} =~ s/^\s*(.*?)\s*$/$1/;
                &$fr_fixup_byref(\$record->{mykeywords});
            }
        }
    }
    if (defined($record->{contributors}->{authors}->{author})) {
        if (ref($record->{contributors}->{authors}->{author}) eq 'ARRAY') {
            my @authors;
            foreach my $author (@{$record->{contributors}->{authors}->{author}}) {
                if (defined $author->{style}->{content}) {
                    #printf "Author: %s<br>\n", $author->{style}->{content};
                    push @authors, $author->{style}->{content};
                    &$fr_fixup_byref(\$authors[$#authors]);
                }
            }
            if ($#authors > 0) {
                my $temp = pop @authors;
                $authors[$#authors] .= ' and '.$temp;
            }
            my $authors = join(', ', @authors);
            $record->{contributors}->{myauthors} = join(', ', @authors);
            printf "Author%s: %s<br>\n",
                $authors[$#authors] =~ m/and/i ? 's' : '',
                $record->{contributors}->{myauthors};
        } else {
            if (defined 
                    $record->{contributors}->{authors}->{author}->{style}->{content}) {
                my $authors = 
                    $record->{contributors}->{authors}->{author}->{style}->{content};
                &$fr_fixup_byref(\$authors);
                #$authors =~ s/ (&|and) /, /;
                #$authors =~ s/([- .\w]+(,[- .\w]+)),/$1;/g;
                $authors =~ s/ et al\.$//;
                printf "Author%s: %s<br>\n",
                    $authors =~ m/\b(&|and|et al)\b/i ?
                        's' : $authors =~ m/,.*,/ ? 's' : '',
                    $authors;
                $record->{contributors}->{myauthors} = $authors;
            }
        }
    }
    if (defined($record->{contributors}->{'secondary-authors'}->{author})) {
        if (ref($record->{contributors}->{'secondary-authors'}->{author}) eq 'ARRAY') {
            my @authors;
            foreach my $author 
                (@{$record->{contributors}->{'secondary-authors'}->{author}}) {
                if (defined $author->{style}->{content}) {
                    push @authors, $author->{style}->{content};
                    &$fr_fixup_byref(\$authors[$#authors]);
                }
            }
            if ($#authors > 0) {
                my $temp = pop @authors;
                $authors[$#authors] .= ' and '.$temp;
            }
            my $authors = join(', ', @authors);
            $record->{contributors}->{myauthors2} = join(', ', @authors);
            printf "Author%s: %s<br>\n",
                $authors[$#authors] =~ m/and/i ? 's' : '',
                $record->{contributors}->{myauthors2};
        } else {
            if (defined $record->{contributors}->{'secondary-authors'}->{author}->{style}->{content}) {
                my $authors = $record->{contributors}->{'secondary-authors'}->{author}->{style}->{content};
                &$fr_fixup_byref(\$authors);
                #$authors =~ s/ (&|and) /, /;
                #$authors =~ s/([- .\w]+(,[- .\w]+)),/$1;/g;
                $authors =~ s/ et al\.$//;
                printf "Author%s: %s<br>\n",
                    $authors =~ m/\b(&|and|et al)\b/i ?
                        's' : $authors =~ m/,.*,/ ? 's' : '',
                    $authors;
                $record->{contributors}->{myauthors2} = $authors;
            }
        }
    }
    if (defined $record->{'pub-location'}->{style}->{content}) {
        printf "Publication Location: %s<br>\n",
            &$fr_fixup_byref(\$record->{'pub-location:'}->{style}->{content});
    }
    if (defined $record->{'auth-address'}->{style}->{content}) {
        printf "Author Address: %s<br>\n",
            &$fr_fixup_byref(\$record->{'auth-address'}->{style}->{content});
    }
    if (defined $record->{'work-type'}->{style}->{content}) {
        printf "Work Type: %s<br>\n",
               &$fr_fixup_byref(\$record->{'work-type'}->{style}->{content});
    }
    if (defined $record->{notes}->{style}->{content}) {
        printf "Notes: %s<br>\n", &$fr_joindata(
                &$fr_fixup_byref(\$record->{notes}->{style}->{content}));
    }
    if (defined $record->{'research-notes'}->{style}->{content}) {
        $record->{'research-notes'}->{style}->{content} =
            &$fr_normalize_research_design(
                    &$fr_fixup_byref(\$record->{'research-notes'}->{style}->{content}));
        printf "Research Design: %s<br>\n",
            $record->{'research-notes'}->{style}->{content};
    }
    if (defined $record->{pages}->{style}->{content}) {
        printf "Pages: %s<br>\n", 
               &$fr_fixup_byref(\$record->{pages}->{style}->{content});
    }
    if (defined $record->{volume}->{style}->{content}) {
        printf "Volume: %s<br>\n",
               &$fr_fixup_byref(\$record->{volume}->{style}->{content});
    }
    if (defined $record->{number}->{style}->{content}) {
        printf "Number: %s<br>\n",
               &$fr_fixup_byref(\$record->{number}->{style}->{content});
    }
    if (defined $record->{dates}->{year}->{style}->{content}) {
        printf "Year: %s<br>\n",
            &$fr_fixup_byref(\$record->{dates}->{year}->{style}->{content});
    }
    if (defined $record->{dates}->{'pub-dates'}->{date}->{style}->{content}) {
        printf "Publication Date: %s<br>\n",
            &$fr_fixup_byref(
                    \$record->{dates}->{'pub-dates'}->{date}->{style}->{content});
    }
    if (defined $record->{'accession-num'}->{style}->{content}) {
        printf "Accession Number: %s<br>\n",
            &$fr_fixup_byref(\$record->{'accession-num'}->{style}->{content});
    }
    if (defined $record->{publisher}->{style}->{content}) {
        printf "Publisher: %s<br>\n",
            &$fr_fixup_byref(\$record->{publisher}->{style}->{content});
    }
    if (defined $record->{isbn}->{style}->{content}) {
        printf "ISBN: %s<br>\n",
            &$fr_fixup_byref(\$record->{isbn}->{style}->{content});
    }
    # 255 characters isn't enough for multiple URLs, so this is a TEXT column
    if (defined($record->{urls}->{'related-urls'}->{url})) {
        if (ref($record->{urls}->{'related-urls'}->{url}) eq 'ARRAY') {
            my @urls;
            foreach my $url (@{$record->{urls}->{'related-urls'}->{url}}) {
                if (defined $url->{style}->{content}) {
                    next unless $url->{style}->{content} =~ m/http:\/\//;
                    $url->{style}->{content} =~ s/.*(http:\/\/[^\s]+).*/$1/;
                    printf qq(URL: <a href="%s" target="_blank">%s</a><br>\n),
                           $url->{style}->{content},
                           length $url->{style}->{content} > 80 ?
                               substr($url->{style}->{content},0,80)."&hellip;" :
                               $url->{style}->{content};
                    push @urls, $url->{style}->{content};
                    #$urls[$#urls] =~ s/^\s*(.*?)\s*$/$1/;
                    #$urls[$#urls] =~ s/ /%20/g;
                    #$urls[$#urls] =~ s/\s*//g;
                }
            }
            my $urls = join("\n", @urls);
            $record->{myurls} = $urls;
        } elsif (defined $record->{urls}->{'related-urls'}->{url}->{style}) {
            # this case is probably due to a data entry error
            if (ref($record->{urls}->{'related-urls'}->{url}->{style}) eq 'ARRAY') {
                my @urls;
                foreach my $url (@{$record->{urls}->{'related-urls'}->{url}->{style}}) {
                    if (defined $url->{content}) {
                        next unless $url->{content} =~ m/http:\/\//;
                        $url->{style}->{content} =~ s/.*(http:\/\/[^\s]+).*/$1/;
                        #$url->{style}->{content} = escape($url->{style}->{content});
                        printf qq(URL: <a href="%s" target="_blank">%s</a><br>\n),
                               $url->{content},
                               length $url->{content} > 80 ?
                                   substr($url->{content},0,80)."&hellip;" :
                                   $url->{content};
                        push @urls, $url->{content};
                        #$urls[$#urls] =~ s/^\s*(.*?)\s*$/$1/;
                        #$urls[$#urls] =~ s/ /%20/g;
                        #$urls[$#urls] =~ s/\s*//g;
                    }
                }
                my $urls = join("\n", @urls);
                $record->{myurls} = $urls;
                print "<b>(Possible data entry error for this URL; but it's
                    been imported correctly anyways.)</b><br />\n";
            } elsif (defined 
                    $record->{urls}->{'related-urls'}->{url}->{style}->{content}) {
                my $url = $record->{urls}->{'related-urls'}->{url}->{style}->{content};
                if ($url =~ m/http:\/\//) {
                    $url =~ s/.*(http:\/\/[^\s]+).*/$1/;
                    #$url = escape($url);
                    #$url =~ s/^\s*(.*?)\s*$/$1/;
                    #$url =~ s/ /%20/g;
                    #$url =~ s/\s*//g;
                    $record->{myurls} = $url;
                    printf qq(URL: <a href="%s" target="_blank">%s</a><br>\n),
                           $url, $url;
                }
            }
        }
    }
    if (defined $record->{custom1}->{style}->{content}) {
        printf "Subset #1: %s<br>\n",
            &$fr_fixup_byref(\$record->{custom1}->{style}->{content});
    }
    if (defined $record->{custom2}->{style}->{content}) {
        printf "Subset #2: %s<br>\n",
            &$fr_fixup_byref(\$record->{custom2}->{style}->{content});
    }
    if (defined $record->{custom3}->{style}->{content}) {
        printf "Subset #3: %s<br>\n",
            &$fr_fixup_byref(\$record->{custom3}->{style}->{content});
    }
    if (defined $record->{abstract}->{style}->{content}) {
        printf "Abstract: %s<br>\n",
            &$fr_fixup_byref_full(\$record->{abstract}->{style}->{content});
    }
};

my $fr_add_record = sub {
    my $record = shift;
    my $sth_insert = shift;
    my $rv = $sth_insert->execute(
            $record->{'rec-number'}->{content} || '',
            $record->{'ref-type'}->{name} || '',
            $record->{'ref-type'}->{content} || '',
            $record->{database}->{path} || '',
            $record->{'remote-database-name'}->{style}->{content} || '',
            $record->{titles}->{title}->{style}->{content} || '',
            $record->{titles}->{'secondary-title'}->{style}->{content} || '',
            $record->{titles}->{'translated-title'}->{style}->{content} || '',
            $record->{titles}->{'alt-title'}->{style}->{content} || '',
            $record->{titles}->{'short-title'}->{style}->{content} || '',
            $record->{'orig-pub'}->{style}->{content} || '',
            $record->{language}->{style}->{content} || '',
            $record->{contributors}->{myauthors} || '',
            $record->{contributors}->{myauthors2} || '',
            $record->{'pub-location'}->{style}->{content} || '',
            $record->{'auth-address'}->{style}->{content} || '',
            $record->{'work-type'}->{style}->{content} || '',
            $record->{notes}->{style}->{content} || '',
            $record->{'research-notes'}->{style}->{content} || '',
            $record->{pages}->{style}->{content} || '',
            $record->{volume}->{style}->{content} || '',
            $record->{number}->{style}->{content} || '',
            $record->{dates}->{year}->{style}->{content} || '',
            $record->{dates}->{'pub-dates'}->{date}->{style}->{content} || '',
            $record->{'accession-num'}->{style}->{content} || '',
            $record->{publisher}->{style}->{content} || '',
            $record->{isbn}->{style}->{content} || '',
            $record->{myurls} || '',
            $record->{'custom1'}->{style}->{content} || '',
            $record->{'custom2'}->{style}->{content} || '',
            $record->{'custom3'}->{style}->{content} || '',
            $record->{abstract}->{style}->{content} || ''
        ) || die $dbh->errstr;
    return $dbh->{mysql_insertid};
};

my $fr_update_field = sub {
    my $datum = shift;
    my $field = shift;
    my $id = shift;
    my $sth_update_field = $sth_update_fields{$field};
    return $sth_update_field->execute($datum, &$fr_fixup($id)) 
        || die $dbh->errstr;
};

my $fr_fetch_field = sub {
    my $field = shift;
    my $id = shift;
    my $sth_fetch_field = $sth_fetch_fields{$field};
    $sth_fetch_field->execute(&$fr_fixup($id)) || die $dbh->errstr;
    my @result = $sth_fetch_field->fetchrow_array;
    return $result[0] if scalar @result;
    # for now a precondition of this function is that the record exist
    die $dbh->errstr;
};

my $fr_check_field = sub {
    my $datum = shift;
    my $field = shift;
    my $id = shift;
    my $updated = shift;
    return unless $datum && $datum ne '';
    my $old_datum = &$fr_fetch_field($field, $id);
    if (!defined $old_datum) {
        &$fr_update_field($datum, $field, $id);
        push @$updated, $field." (new)";
    } elsif ($old_datum ne $datum) {
        &$fr_update_field($datum, $field, $id);
        push @$updated, $field;
    }
};

# for these fields, allow multiple entries in the db
my $fr_check_field2 = sub {
    my $datum = shift;
    my $field = shift;
    my $id = shift;
    my $updated = shift;
    return unless $datum && $datum ne '';
    my $old_datum = &$fr_fetch_field($field, $id);
    if (!defined $old_datum) {
        &$fr_update_field($datum, $field, $id);
        push @$updated, $field." (new)";
    } elsif ($old_datum ne $datum) {
        my @old_data = split /[\r\n]/, $old_datum;
        if (!scalar grep /^$datum$/, @old_data) {
            push @old_data, $datum;
            &$fr_update_field(join("\n", @old_data), $field, $id);
            push @$updated, $field." (additional value)";
        }
    }
};

# for these fields, allow multiple entries in the db & in the input XML file
my $fr_check_field3 = sub {
    my $datum = shift;
    return unless $datum && $datum ne '';
    my @data = split(/[\r\n]/, $datum);
    my $field = shift;
    my $id = shift;
    my $updated = shift;
    my $old_datum = &$fr_fetch_field($field, $id);
    if (!defined $old_datum) {
        &$fr_update_field($datum, $field, $id);
        push @$updated, $field." (new)";
    } else {
        # for these fields, allow multiple entries
        my @old_data = split /[\r\n]/, $old_datum;
        my $new_data_count = 0;
        foreach my $datum (@data) {
            next unless $datum ne '';
            if (!scalar grep(/^\Q$datum\E$/, @old_data)) {
                push @old_data, $datum;
                &$fr_update_field(join("\n", @old_data), $field, $id);
                $new_data_count++;
            }
        }
        if ($new_data_count > 1) {
            push @$updated, $field." ($new_data_count additional values)";
        } elsif ($new_data_count > 0) {
            push @$updated, $field." (one additional value)";
        }
    }
};

my $fr_update_record = sub {
    my $record = shift;
    my $id = shift;
    my @updated;
    &$fr_check_field($record->{'rec-number'}->{content}, 'rec_number', $id, \@updated);
    &$fr_check_field($record->{'ref-type'}->{name}, 'ref_type_name', $id, \@updated);
    &$fr_check_field($record->{'ref-type'}->{content}, 'ref_type_num', $id, \@updated);
    &$fr_check_field($record->{database}->{path}, 'database_path', $id, \@updated);
    &$fr_check_field2($record->{'remote-database-name'}->{style}->{content}, 'database_name', $id, \@updated);
    &$fr_check_field($record->{titles}->{title}->{style}->{content}, 'title', $id, \@updated);
    &$fr_check_field($record->{titles}->{'secondary-title'}->{style}->{content}, 'journal', $id, \@updated);
    &$fr_check_field($record->{titles}->{'translated-title'}->{style}->{content}, 'translated_title', $id, \@updated);
    &$fr_check_field($record->{titles}->{'alt-title'}->{style}->{content}, 'alt_title', $id, \@updated);
    &$fr_check_field($record->{titles}->{'short-title'}->{style}->{content}, 'short_title', $id, \@updated);
    &$fr_check_field($record->{'orig-pub'}->{style}->{content}, 'orig_pub', $id, \@updated);
    &$fr_check_field($record->{language}->{style}->{content}, 'language', $id, \@updated);
    &$fr_check_field($record->{contributors}->{myauthors}, 'authors', $id, \@updated);
    &$fr_check_field($record->{contributors}->{myauthors2}, 'secondary_authors', $id, \@updated);
    &$fr_check_field($record->{'pub-location'}->{style}->{content}, 'pub_location', $id, \@updated);
    &$fr_check_field($record->{'auth-address'}->{style}->{content}, 'auth_address', $id, \@updated);
    &$fr_check_field($record->{'work-type'}->{style}->{content}, 'work_type', $id, \@updated);
    &$fr_check_field($record->{notes}->{style}->{content}, 'notes', $id, \@updated);
    &$fr_check_field($record->{'research-notes'}->{style}->{content}, 'research_design', $id, \@updated);
    &$fr_check_field($record->{pages}->{style}->{content}, 'pages', $id, \@updated);
    &$fr_check_field($record->{volume}->{style}->{content}, 'volume', $id, \@updated);
    &$fr_check_field($record->{number}->{style}->{content}, 'number', $id, \@updated);
    &$fr_check_field($record->{dates}->{year}->{style}->{content}, 'year', $id, \@updated);
    &$fr_check_field($record->{dates}->{'pub-dates'}->{date}->{style}->{content}, 'pub_dates', $id, \@updated);
    &$fr_check_field($record->{'accession-num'}->{style}->{content}, 'accession_num', $id, \@updated);
    &$fr_check_field($record->{publisher}->{style}->{content}, 'publisher', $id, \@updated);
    &$fr_check_field($record->{isbn}->{style}->{content}, 'isbn', $id, \@updated);
    &$fr_check_field3($record->{myurls}, 'urls', $id, \@updated);
    &$fr_check_field($record->{'custom1'}->{style}->{content}, 'subset1', $id, \@updated);
    &$fr_check_field($record->{'custom2'}->{style}->{content}, 'subset2', $id, \@updated);
    &$fr_check_field($record->{'custom3'}->{style}->{content}, 'subset3', $id, \@updated);
    &$fr_check_field($record->{abstract}->{style}->{content}, 'abstract', $id, \@updated);
    return [ @updated ];
};

my $fr_highlight = sub {
    my $text = shift || '';
    my $terms = shift;
    my $entire_string = shift || 0; # optional
    return $text unless $terms;
    if (ref $terms eq "ARRAY") {
        foreach my $term (@$terms) {
            next unless $term ne '';
            $entire_string ?
                $text =~ s/^\Q$term\E$/<span class=highlight>$&<\/span>/gi :
                $text =~ s/\Q$term\E/<span class=highlight>$&<\/span>/gi;
        }
    } else {
        $entire_string ?
            $text =~ s/^\Q$terms\E$/<span class=highlight>$&<\/span>/gi :
            $text =~ s/\Q$terms\E/<span class=highlight>$&<\/span>/gi;
    }
    return $text;
};

my $fr_highlight_field = sub {
    # slightly tricky -- takes a list of search terms, which fields are
    # being searched, the name of the current field, a flag stating whether
    # the special field 'all of the above' includes the current field, an
    # optional flag stating whether the highlighting must match the entire
    # field text, plus the text of the field itself.  a search term is only
    # highlighted if it was searched for on this particular field, so we
    # need to correlate the entries in the array of search terms and the
    # array of search fields, instead of highlighting every search word
    # regardless of where it appears.  if we didn't check, users would be
    # confused about how the search logic works, so this needs to be the
    # same.
    my $this_field = shift;
    my $required_fields = shift;
    my $include_any = shift;
    my $text = shift || '';
    my $terms = shift;
    my $entire_string = shift || 0; # optional
    return $text unless defined $this_field && defined $required_fields;
    if (ref $required_fields eq "ARRAY" && ref $terms eq "ARRAY") {
        return $text unless $#$required_fields == $#$terms;
        for (my $i=0;$i<=$#$required_fields;$i++) {
            my $required_field = $$required_fields[$i];
            my $term = $$terms[$i];
            next unless $required_field ne '' && $term ne '';
            if ($this_field eq $required_field || 
                    ($include_any && $required_field eq 'any')) {
                $text = &$fr_highlight($text, $term, $entire_string);
            }
        }
        return $text;
    } else {
        if ($this_field eq $required_fields || 
                ($include_any && $required_fields eq 'any')){
            return &$fr_highlight($text, $terms, $entire_string);
        } else {
            return $text;
        }
    }
};

my $fr_map_pubtype = sub {
    my $type = shift;
    return unless $type;
    return "Experimental, RCT" if $type eq "I";
    return "Experimental, non-randomized" if $type eq "II-1";
    return "Experimental, case control" if $type eq "II-2";
    return "Experimental, quasi" if $type eq "II-3";
    return "Descriptive" if $type eq "III";
    return "Other";
};

my $fr_display_in_list_old = sub {
    my $list = shift || return;
    my $context = shift || '';
    # sort by author name
    foreach my $id
        (sort {$list->{$a}->{authors} cmp $list->{$b}->{authors}} keys %$list) {
        my $record = $list->{$id};
        $record->{title} .= "." unless $record->{title} =~ /[.?!]$/;
        $record->{authors} .= "." unless $record->{authors} =~ /[.?!]$/;
        printf qq(<p class=record_head>Record %d (%s)
                %s<label class=save>[<input type=checkbox name="s2_%d" value="y">save]</label>%s
            </p>
            <p class=record>%s (%s) %s <em>%s</em>, <em>%s</em>(%s), %s.</p>\n),
            $id,
            &$fr_map_pubtype($record->{pubtype}),
            $context ? '<!-- ' : '', $id, $context ? ' -->' : '',
            &$fr_highlight_field('authors', $f1, 1, $record->{authors}, $v1),
            &$fr_highlight_field('year', $f1, 0, $record->{year}, $v1, 1),
            &$fr_highlight_field('title', $f1, 1, $record->{title}, $v1),
            &$fr_highlight_field('journal', $f1, 1, $record->{journal}, $v1),
            $record->{volume}, $record->{num}, $record->{page};
        printf "<p class=abstract>%s</p>\n",
            &$fr_highlight_field('abstract', $f1, 1, $record->{abstract}, $v1)
                    if $record->{abstract};
    }
};

my $fr_display_in_list_new = sub {
    my $list = shift || return;
    my $context = shift || '';
    # sort by author name here too
    foreach my $id 
        (sort {$list->{$a}->{authors} cmp $list->{$b}->{authors}} keys %$list) {
        my $record = $list->{$id};
        $record->{title} .= "." unless $record->{title} =~ /[.?!]$/;
        $record->{authors} .= "." unless $record->{authors} =~ /[.?!]$/;

        my $urls = '';
        if ($record->{urls}) {
            foreach my $url (split (/[\r\n]+/, $record->{urls})) {
                my $url_disp = length $url > 80 ?
                    substr($url,0,80)."&hellip;" : $url;
                $url_disp = $url if $context > 1;
                $urls .= qq(<br /><span class=other><b>Abstract URL:</b> ).
                        qq(<a href="$url" target="_blank">$url_disp</a></span>);
            }
        }

        printf qq(<p class=record>
            %s<span class="record_head">Record #%d, ID #%d</span>%s
            %s<span class="displaylink">[<a
                href="%s?op=display&amp;id=%d">more details</a>]</span>%s
            %s<label class="save">[<input type=checkbox name="s1_%d" value="y">save]</label>%s
            <br />
            %s (%s%s) %s <em>%s</em>, <em>%s</em>%s, %s. %s %s%s%s%s%s%s</p>\n),
            $context > 1 ? '<!-- ': '', $record->{rec_number}, $id, $context > 1 ? ' -->': '',
            $context > 1 ? '<!-- ': '', $q->url(), $id, $context > 1 ? ' -->': '',
            $context ? '<!-- ' : '', $id, $context ? ' -->' : '',
            &$fr_highlight_field('authors', [ $f1, $f2 ], 1,
                    $record->{authors}, [ $v1, $v2 ] ),
            $record->{pub_dates} ? $record->{pub_dates}.', ' : '',
            &$fr_highlight_field('year', [ $f1, $f2 ], 0,
                    $record->{year}, [ $v1, $v2 ] ),
            &$fr_highlight_field('title', [ $f1, $f2 ], 1,
                    $record->{title}, [ $v1, $v2 ] ),
            &$fr_highlight_field('journal', [ $f1, $f2 ], 1,
                    $record->{journal}, [ $v1, $v2 ] ),
            &$fr_highlight_field('volume', [ $f1, $f2 ], 0,
                    $record->{volume}, [ $v1, $v2 ] ),
            $record->{number} ?
                '('. &$fr_highlight_field('number', [ $f1, $f2 ], 0,
                            $record->{number}, [ $v1, $v2 ]) .')' :
                '',
            &$fr_highlight_field('pages', [ $f1, $f2 ], 0,
                    $record->{pages}, [ $v1, $v2 ] ),
            $record->{auth_address} ?
                '<!-- <br /><span class=other><b>Address:</b> '.
                    $record->{auth_address}.'</span> -->'
                : '',
            $record->{database_name} ?
                sprintf '<br /><span class=other><b>Database Name:</b>
                    %s</span>', &$fr_joindata($record->{database_name})
                : '',
            $record->{research_design} ?
                sprintf '<br /><span class=other><b>Research Design:</b>
                    %s</span>',
                    &$fr_highlight_field('research_design', [ $f1, $f2 ],
                            1, $record->{research_design}, [ $v1, $v2 ]),
                : '',
            $record->{subset1} ? '<br /><span class=other>
                <b>Subset #1:</b> '.$record->{subset1}.'</span>' : '',
            $record->{subset2} ? '<br /><span class=other>
                <b>Subset #2:</b> '.$record->{subset2}.'</span>' : '',
            $record->{subset3} ? '<br /><span class=other>
                <b>Subset #3:</b> '.$record->{subset3}.'</span>' : '',
            $urls;

        printf "<p class=abstract><b>Abstract:</b><br>%s</p>\n",
                &$fr_highlight_field('abstract', [ $f1, $f2 ], 1,
                    $record->{abstract}, [ $v1, $v2 ] ),
                        if $record->{abstract} && $show_abstracts;

        ## print keywords
        #my @keywords;
        #$sth_keyword_biblio->execute($id);
        #while (my $row = $sth_keyword_biblio->fetch) {
        #    $sth_keyword->execute($row->[0]);
        #    while (my $row2 = $sth_keyword->fetch) {
        #        push @keywords, &$fr_highlight($row2->[0], $k1, 1);
        #    }
        #}
        #my $keywords = join '; ', sort @keywords;
        #print "<p class=keywords><b>Keywords:</b><br />$keywords</p>"
        #    if $keywords;
    }
};

my $fr_list_new = sub {
    print $q->h2("List of Records");

    print qq(<div class=clipboard>);
    printf qq(<a href="%s?op=clipboard">Clipboard</a>: %d saved entr%s\n),
           $q->url(),
           scalar keys %save1, scalar keys %save1 == 1 ? 'y' : 'ies';
    print qq(<span class=displaylink>[<a href="javascript:popUp('help.html')">Help</a>]</span>\n);
    print qq(</div>);
    #print "<h2>debug: cookie values</h2>\n" if $debug;
    #print Data::Dumper->Dump([\%save1], [qw(*save1)]), "<br>\n" if $debug;

    # internally these indices are zero-based but we present them otherwise
    $start = $gostart if $start == 0 && $gostart > 0;
    $start-- if $start > 0;

    # the navigation bar at the top both displays the number of records and
    # allows users to search over various fields
    #
    # i admit this is a bit of a mess. -- mvc
    my $nav = sprintf '<form action="%s" method=get>', $q->url();
    $nav .= '<input type=hidden name=op value=list>';

    my $nav0 = qq(<nobr>Database(s): <select name=db>\n);
    foreach my $db (sort keys %database_names) {
        $nav0 .= sprintf qq(<option value="%s">%s</option>\n),
                    $db, $database_names{$db};
    }
    $nav0 .= qq(</select></nobr> |\n);
    $nav0 =~ s/value="$db"/value="$db" selected/;

    my $nav0a = qq(<nobr>Research design(s): <select name=rd>\n);
    foreach my $rd (sort {$research_designs{$a} cmp $research_designs{$b}}
            keys %research_designs) {
        next if $rd eq 'NONE';
        $nav0a .= sprintf qq(<option value="%s">%s</option>\n),
                    $rd, $research_designs{$rd};
    }
    $nav0a .= sprintf qq(<option value="%s">%s</option>\n),
                'NONE', $research_designs{'NONE'};
    $nav0a .= qq(</select></nobr> | \n);
    $nav0a =~ s/value="$rd"/value="$rd" selected/;

    $nav0 .= $nav0a;

    my $nav3 = qq( | <nobr>Keyword: <input name=k1 value="$k1"></nobr> );

    my $params = '';

    if ($per_page != $per_page_default) {
        $params = "&amp;per_page=$per_page";
        print "<input type=hidden name=per_page value=$per_page>";
    }

    my $where = 'WHERE ';
    if ($v1) {
        # default field unless valid
        $f1 = 'title' unless $f1 eq 'abstract' or $f1 eq 'year'
            or $f1 eq 'authors' or $f1 eq 'notes'
            or $f1 eq 'journal' or $f1 eq 'any'
            or $f1 eq 'research_design' or $f1 eq 'volume'
            or $f1 eq 'subset1' or $f1 eq 'subset2' or $f1 eq 'subset3'
            or $f1 eq 'number' or $f1 eq 'pages';
        # keep these search keys when forming links
        $params .= "&amp;f1=$f1&amp;v1=$v1";
        # housekeeping
        $f1 = 'any' if $f1 eq 'any2';
        if ($f1 eq 'any') {
            $where .= "title LIKE '%${v1}%'
                OR abstract LIKE '%${v1}%'
                OR authors LIKE '%${v1}%'
                OR notes LIKE '%${v1}%'
                OR research_design LIKE '%${v1}%'
                OR journal LIKE '%${v1}%'";
        } elsif ( $f1 eq 'year' || $f1 eq 'volume' || $f1 eq 'number' ||
                $f1 eq 'subset1' || $f1 eq 'subset2' || $f1 eq 'subset3' ) {
            $where .= "$f1 LIKE '${v1}'";
        } else {
            $where .= "$f1 LIKE '%${v1}%'";
        }
    }
    if ($v2) {
        $where .= $where eq "WHERE " ? "" : " AND ";
        # default field unless valid
        $f2 = 'title' unless $f2 eq 'abstract' or $f2 eq 'year'
            or $f2 eq 'authors' or $f2 eq 'notes'
            or $f2 eq 'journal' or $f2 eq 'any'
            or $f2 eq 'research_design' or $f2 eq 'volume'
            or $f2 eq 'subset1' or $f2 eq 'subset2' or $f2 eq 'subset3'
            or $f2 eq 'number' or $f2 eq 'pages';
        # keep these search keys when forming links
        $params .= "&amp;f2=$f2&amp;v2=$v2";
        # housekeeping
        $f2 = 'any' if $f2 eq 'any2';
        if ($f2 eq 'any') {
            $where .= "title LIKE '%${v2}%'
                OR abstract LIKE '%${v2}%'
                OR authors LIKE '%${v2}%'
                OR notes LIKE '%${v2}%'
                OR research_design LIKE '%${v2}%'
                OR journal LIKE '%${v2}%'";
        } elsif ( $f2 eq 'year' || $f2 eq 'volume' || $f2 eq 'number' ||
                $f2 eq 'subset1' || $f2 eq 'subset2' || $f2 eq 'subset3' ) {
            $where .= "$f2 LIKE '${v2}'";
        } else {
            $where .= "$f2 LIKE '%${v2}%'";
        }
    }

    if ($db ne "A" && exists $database_names{$db}) {
        $where .= $where eq "WHERE " ? "" : " AND ";
        if ($db eq 'NONE') {
            $where .= "database_name = '' ";
        } else {
            $where .= "database_name LIKE '%$database_names{$db}%' ";
        }
        $params .= "&amp;db=$db";
    }

    if ($rd ne "A" && exists $research_designs{$rd}) {
        $where .= $where eq "WHERE " ? "" : " AND ";
        if ($rd eq 'NONE') {
            $where .= "research_design = '' ";
        } else {
            $where .= "research_design LIKE '$research_designs{$rd}' ";
        }
        $params .= "&amp;rd=$rd";
    }

    $where .= $where eq "WHERE " ? "1" : "";

    # keywords are a special case
    my $list;
    my $num_recs;
    if ($k1) {
        $params .= "&amp;k1=$k1";
        my $q_k1 = $dbh->quote($k1);
        print "\n<!-- SELECT * from biblio $where AND keyword LIKE $q_k1 AND ".
            "keywords.kid=biblio_keywords.kid AND biblio.id = biblio_keywords.id ".
            "ORDER BY authors LIMIT $start, $per_page -->\n";

        # this is ugly. it should be re-written with a proper INNER JOIN.
        # if this database gets larger someone will have to get around to
        # that to keep performance within reasonable limits. -- mvc

        # there's no other way to figure out how many records our query
        # matches.
        $list = $dbh->selectall_hashref("SELECT biblio.id
                FROM biblio, biblio_keywords, keywords
                $where AND keyword LIKE $q_k1
                AND keywords.kid=biblio_keywords.kid
                AND biblio.id = biblio_keywords.id", "id")
            || die "Error connecting to the database: $DBI::errstr\n";
        $num_recs = scalar keys %{$list};

        $list = $dbh->selectall_hashref("SELECT
                biblio.id, keywords.kid, keyword,
                rec_number, ref_type_name, ref_type_num, database_path,
                database_name, title, journal, translated_title, alt_title,
                orig_pub, authors, secondary_authors, pub_location,
                auth_address, language, work_type, notes, research_design,
                pages, volume, number, year, pub_dates, accession_num,
                publisher, isbn, urls, subset1, subset2, subset3, abstract
                FROM biblio, biblio_keywords, keywords
                $where AND keyword LIKE $q_k1
                AND keywords.kid=biblio_keywords.kid
                AND biblio.id = biblio_keywords.id
                ORDER BY authors LIMIT $start, $per_page", "id")
            || die "Error connecting to the database: $DBI::errstr\n";
    } else {
        print "\n<!-- SELECT * from biblio $where ORDER BY authors LIMIT ".
            "$start, $per_page -->\n";

        # there's no other way to figure out how many records our query
        # matches.
        $list = $dbh->selectall_hashref("SELECT id FROM biblio $where", "id")
            || die "Error connecting to the database: $DBI::errstr\n";
        $num_recs = scalar keys %{$list};

        $list = $dbh->selectall_hashref("SELECT
                id, rec_number, ref_type_name, ref_type_num, database_path,
                database_name, title, journal, translated_title, alt_title,
                orig_pub, authors, secondary_authors, pub_location,
                auth_address, language, work_type, notes, research_design,
                pages, volume, number, year, pub_dates, accession_num,
                publisher, isbn, urls, subset1, subset2, subset3, abstract
                FROM biblio $where ORDER BY authors LIMIT $start, $per_page", "id")
            || die "Error connecting to the database: $DBI::errstr\n";
    }

    my $curr_recs = scalar keys %{$list};
    my $end = $start + $per_page > $num_recs ? $num_recs : $start + $per_page;
    $end = $start + $curr_recs;
    $end = $start + 1 if $curr_recs == 0;

    my $nav1 = sprintf "<nobr>Displaying %d of %d records from %d to %d</nobr>\n",
        $curr_recs, $num_recs, $start+1, $end;

#    $nav1 .= sprintf ' | <a href="%s%s">First %d</a>',
#        $q->url(), "?op=list${params}",
#        $per_page
#            if $start > 0; # && $start > $per_page;
    $nav1 .= sprintf ' | <button name=start value="1">First %d</button>',
        $per_page
            if $start > 0;

#    $nav1 .= sprintf ' | <a href="%s%s%d">Previous %d</a>',
#        $q->url(), "?op=list${params}&amp;start=",
#        $start - $per_page >= 0 ? $start - $per_page + 1 : 1,
#        $start < $per_page ? $per_page - $start : $per_page,
#            if $start > 0 && $curr_recs > 0 && $num_recs > $end && $start > $per_page;
    $nav1 .= sprintf ' | <button name=start value="%d">Previous %d</button>',
        $start - $per_page >= 0 ? $start - $per_page + 1 : 1,
        $start < $per_page ? $per_page - $start : $per_page,
            if $start > 0 && $curr_recs > 0 && $num_recs >= $end
                && $start > $per_page;

#    $nav1 .= sprintf ' | <a href="%s%s%d">Next %d</a>',
#        $q->url(), "?op=list${params}&amp;start=", $end + 1,
#        $per_page > $num_recs - $end ? $num_recs - $end : $per_page
#            if $num_recs - $end > 0;
    $nav1 .= sprintf ' | <button name=start value="%d">Next %d</button>',
        $end + 1,
        $per_page > $num_recs - $end ? $num_recs - $end : $per_page
            if $num_recs - $end > 0;

    $nav1 .= sprintf ' | <nobr>Start display at:</nobr> '.
        '<input size=4 name=gostart value=%d> ', $start + 1
            if $num_recs > $curr_recs;

    $nav1 .= " | ";

    my $nav2 = qq( <nobr>Search: <select name=f1>
            <option value="title">Title</option>
            <option value="authors">Author(s)</option>
            <option value="abstract">Abstract</option>
            <option value="journal">Journal name</option>
            <option value="research_design">Research design</option>
            <option value="any">Any of the above</option>
            <option value="any2">----------------</option>
            <option value="volume">Volume</option>
            <option value="number">Issue</option>
            <option value="pages">Pages</option>
            <option value="year">Year</option>
            <option value="subset1">Subset #1</option>
            <option value="subset2">Subset #2</option>
            <option value="subset3">Subset #3</option>
        </select>);
    $nav2 =~ s/value="$f1"/value="$f1" selected/;
    $nav2 .= qq( = <input name=v1 value="$v1"></nobr> );

    my $nav2a = qq( | <nobr><select name=f2>
            <option value="authors">Author(s)</option>
            <option value="title">Title</option>
            <option value="abstract">Abstract</option>
            <option value="journal">Journal name</option>
            <option value="research_design">Research design</option>
            <option value="any">Any of the above</option>
            <option value="any2">----------------</option>
            <option value="volume">Volume</option>
            <option value="number">Issue</option>
            <option value="pages">Pages</option>
            <option value="year">Year</option>
            <option value="subset1">Subset #1</option>
            <option value="subset2">Subset #2</option>
            <option value="subset3">Subset #3</option>
        </select>);
    $nav2a =~ s/value="$f2"/value="$f2" selected/;
    $nav2a .= qq( = <input name=v2 value="$v2"></nobr> );

    $nav2 = $nav2 . $nav2a . $nav3.
        '<input class=bibliobutton type=submit name=go value=Go>';

    my $allnav = "<hr /><div class=nav> ". $nav. $nav1. "<br>". $nav0. $nav2.
        " </div>";
        #" </form></div>";
    print $allnav, "<hr />";

    my $sth_keyword_biblio = $dbh->prepare(q{
            SELECT kid FROM biblio_keywords WHERE id=?})
        || die $dbh->errstr;

    my $sth_keyword = $dbh->prepare(q{
            SELECT keyword FROM keywords WHERE kid=? ORDER BY keyword})
        || die $dbh->errstr;

    if ($curr_recs) {
        &$fr_display_in_list_new($list);
    } else {
        print "<p class=warning>No records selected!</p>\n";
    }
    print "</form>\n";
};

my $fr_clipboard_new = sub {
    print $q->h2("Clipboard");
    print qq(<span class=displaylink>[<a href="javascript:history.back()">back to previous page</a>] </span>);
    #print "<h2>debug: cookie values</h2>\n" if $debug;
    #print Data::Dumper->Dump([\%save1], [qw(*save1)]), "<br>\n" if $debug;
    if (!scalar keys %save1) {
        print $q->h3("No saved records");
        print $q->p("Please select some records first.");
    } else {
        printf qq(<span class="displaylink">[
                <a href="%s?op=clipboard_print">printer-friendly version of
                this page</a> ]</span>\n), $q->url();
        printf qq(<span class="displaylink">[<a href="%s?op=clear">clear clipboard</a>]</span>\n), $q->url();
        my $where = "WHERE ";
        foreach my $saved_id (keys %save1) {
            $saved_id =~ s/s1_//;
            $where .= " OR id=$saved_id";
        }
        $where =~ s/WHERE  OR/WHERE /;
        print "\n<!-- SELECT * FROM biblio $where LIMIT 10 ORDER BY authors -->\n\n";
        my $list = $dbh->selectall_hashref("SELECT * FROM biblio $where 
                ORDER BY authors", "id")
            || die "Error connecting to the database: $DBI::errstr\n";
        &$fr_display_in_list_new($list, 1);
    }
};

my $fr_clipboard_old = sub {
    print $q->h2("Clipboard (Old Records)");
    print qq(<span class=displaylink>[<a href="javascript:history.back()">back to previous page</a>]</span>);
    #print "<h2>debug: cookie values</h2>\n" if $debug;
    #print Data::Dumper->Dump([\%save2], [qw(*save2)]), "<br>\n" if $debug;
    if (!scalar keys %save2) {
        print $q->h3("No saved records");
        print $q->p("Please select some records first.");
    } else {
        printf qq(<span class="displaylink">[
                <a href="%s?op=clipboard_old_print">printer-friendly version of
                this page</a> ]</span>\n), $q->url();
        printf qq(<span class="displaylink">[
                <a href="%s?op=clear_old">clear clipboard</a> ]</span>\n), $q->url();
        my $where = "WHERE ";
        foreach my $saved_id (keys %save2) {
            $saved_id =~ s/s2_//;
            $where .= " OR id=$saved_id";
        }
        $where =~ s/WHERE  OR/WHERE /;
        print "\n<!-- SELECT * FROM biblio $where LIMIT 10 ORDER BY authors -->\n\n";
        my $list = $dbh->selectall_hashref("SELECT * FROM biblio_old $where 
                ORDER BY authors", "id")
            || die "Error connecting to the database: $DBI::errstr\n";
        &$fr_display_in_list_old($list, 1);
    }
};

my $fr_clipboard_new_print = sub {
    print $q->h2("Clipboard");
    print qq(<span class=displaylink>[<a href="javascript:history.back()">back to previous page</a>]</span>);
    if (!scalar keys %save1) {
        print $q->h3("No records saved");
        print $q->p("Please select some records first.");
        return;
    }
    my $where = "WHERE ";
    foreach my $saved_id (keys %save1) {
        $saved_id =~ s/s1_//;
        $where .= " OR id=$saved_id";
    }
    $where =~ s/WHERE  OR/WHERE /;
    print "\n<!-- SELECT * FROM biblio $where LIMIT 10 ORDER BY authors -->\n\n";
    my $list = $dbh->selectall_hashref("SELECT * FROM biblio $where 
            ORDER BY authors", "id")
        || die "Error connecting to the database: $DBI::errstr\n";
    &$fr_display_in_list_new($list, 2);
};

my $fr_clipboard_old_print = sub {
    print $q->h2("Clipboard (Old Records)");
    print qq(<span class=displaylink>[<a href="javascript:history.back()">back to previous page</a>]</span>);
    #print "<h2>debug: cookie values</h2>\n" if $debug;
    #print Data::Dumper->Dump([\%save2], [qw(*save2)]), "<br>\n" if $debug;
    if (!scalar keys %save2) {
        print $q->h3("No saved records");
        print $q->p("Please select some records first.");
        return;
    }
    my $where = "WHERE ";
    foreach my $saved_id (keys %save2) {
        $saved_id =~ s/s2_//;
        $where .= " OR id=$saved_id";
    }
    $where =~ s/WHERE  OR/WHERE /;
    print "\n<!-- SELECT * FROM biblio $where LIMIT 10 ORDER BY authors -->\n\n";
    my $list = $dbh->selectall_hashref("SELECT * FROM biblio_old $where 
            ORDER BY authors", "id")
        || die "Error connecting to the database: $DBI::errstr\n";
    &$fr_display_in_list_old($list, 2);
};

my $fr_list_old = sub {
    print $q->h2("Old Records");

    print qq(<div class="clipboard">);
    printf qq(<a href="%s?op=clipboard_old">Clipboard</a>: %d saved entr%s\n),
           $q->url(),
           scalar keys %save2, scalar keys %save2 == 1 ? 'y' : 'ies';
    print qq(<span class=displaylink>[<a href="javascript:popUp('help.html')">Help</a>]</span>\n);
    print qq(</div>);

    # internally these indices are zero-based but we present them otherwise
    $start = $gostart if $start == 0 && $gostart > 0;
    $start-- if $start > 0;

    printf '<form action="%s" method=get>', $q->url();
    print '<input type=hidden name=op value=list_old>';

    my $params = '';

    if ($per_page != $per_page_default) {
        $params = "&amp;per_page=$per_page";
        print "<input type=hidden name=per_page value=$per_page>";
    }

    my $where = '';
    if ($v1) {
        $f1 = 'any' if $f1 eq 'any2';
        $f1 = 'title' unless $f1 eq 'abstract' or $f1 eq 'year'
            or $f1 eq 'authors' or $f1 eq 'journal' or $f1 eq 'any';
        # keep these search keys
        $params .= "&amp;f1=$f1&amp;v1=$v1";

        if ($f1 eq 'any') {
            $where = "WHERE title LIKE '%${v1}%'
                OR abstract LIKE '%${v1}%'
                OR authors LIKE '%${v1}%'
                OR journal LIKE '%${v1}%'";
        } elsif ($f1 eq 'year') {
            $where = "WHERE $f1 = '${v1}'";
        } else {
            $where = "WHERE $f1 LIKE '%${v1}%'";
        }
        print "<!-- $where -->";
    }

    my $list = $dbh->selectall_hashref("SELECT id FROM biblio_old $where", "id")
        || die "Error connecting to the database: $DBI::errstr\n";
    my $num_recs = scalar keys %{$list};

    $list = $dbh->selectall_hashref("SELECT * FROM biblio_old $where ORDER
            BY authors LIMIT $start, $per_page", "id")
        || die "Error connecting to the database: $DBI::errstr\n";

    my $end = $start + $per_page > $num_recs ? $num_recs : $start + $per_page;
    my $curr_recs = scalar keys %{$list};
    $end = $start + $curr_recs;
    if ($curr_recs == 0) {
        $end = $start + 1;
    }

    my $nav1 = sprintf "<p><nobr>Displaying %d of %d records from %d to %d</nobr> \n",
        $curr_recs, $num_recs, $start+1, $end;

    #$nav1 .= sprintf ' | <a href="%s%s">First %d</a>',
    #    $q->url(), "?op=list_old${params}",
    #    $per_page
    #        if $start > 0;
    $nav1 .= sprintf ' | <button name=start value="1">First %d</button>',
        $per_page
            if $start > 0;

    #$nav1 .= sprintf ' | <a href="%s%s%d">Previous %d</a>',
    #    $q->url(), "?op=list_old${params}&amp;start=",
    #    $start - $per_page >= 0 ? $start - $per_page + 1 : 1,
    #    $start < $per_page ? $per_page - $start : $per_page,
    #        if $start > 0 && $curr_recs > 0 && $num_recs > $end
    #            && $start > $per_page;
    $nav1 .= sprintf ' | <button name=start value="%d">Previous %d</button>',
        $start - $per_page >= 0 ? $start - $per_page + 1 : 1,
        $start < $per_page ? $per_page - $start : $per_page,
            if $start > 0 && $curr_recs > 0 && $num_recs >= $end
                && $start > $per_page;

    #$nav1 .= sprintf ' | <a href="%s%s%d">Next %d</a>',
    #    $q->url(), "?op=list_old${params}&amp;start=", $end + 1,
    #    $per_page > $num_recs - $end ? $num_recs - $end : $per_page
    #        if $num_recs - $end > 0;
    $nav1 .= sprintf ' | <button name=start value="%d">Next %d</button>',
        $end + 1,
        $per_page > $num_recs - $end ? $num_recs - $end : $per_page
            if $num_recs - $end > 0;

    $nav1 .= sprintf qq( | <nobr>Start display at:
            <input size=4 name=gostart value=%d></nobr> ),
        $start + 1
            if $num_recs > $curr_recs;

    $nav1 .= " | ";
#    $nav1 .= sprintf "<span class=warning>Search terms: %s matches
#        &ldquo;%s&rdquo;</span>\n", $f1, $v1 if $v1;
#    $nav1 .= "</form>";

    my $nav2 = qq(<nobr><select name=f1>
            <option value="title">Title</option>
            <option value="authors">Author(s)</option>
            <option value="journal">Journal name</option>
            <option value="abstract">Abstract</option>
            <option value="any">Any of the above</option>
            <option value="any2">----------------</option>
            <option value="year">Year</option>
        </select>);
    $nav2 =~ s/value="$f1"/value="$f1" selected/;
    $nav2 .= qq( = <input name=v1 value="$v1"></nobr>
        <input class=bibliobutton type=submit name=go value=Go>);
    $nav2 .= " ";

    print "<hr />", $nav1, $nav2, "<hr />";

    if ($curr_recs) {
        &$fr_display_in_list_old($list);
    } else {
        print "<p class=warning>No records selected!</p>\n";
    }
    print "</form>\n";
};

my $fr_display = sub {
    # this shows all the information we have about the record, if the user
    # wants it
    print "<h2>Record #$id</h2>\n";
    my $record = $dbh->selectrow_hashref("SELECT
            id, rec_number, ref_type_name, ref_type_num, database_path,
            database_name, title, journal, translated_title, alt_title,
            orig_pub, authors, secondary_authors, pub_location,
            auth_address, language, work_type, notes, research_design,
            pages, volume, number, year, pub_dates, accession_num,
            publisher, isbn, urls, subset1, subset2, subset3, abstract,
            modified
            FROM biblio WHERE id=$id LIMIT 1");

    if (!$record) {
        print "No such record #$id.  Please try again.<br>\n";
        return;
    }

    printf qq(<span class="displaylink">[
            <a href="%s?op=display&amp;id=%d&amp;s1_%d=y">save to clipboard</a>
            ]</span><br>\n),
           $q->url(), $id, $id;
    $record->{title} .= "." unless $record->{title} =~ /[.?!]$/;
    $record->{authors} .= "." unless $record->{authors} =~ /[.?!]$/;

    printf "<p class=record>
        <span class=record_head>Record #%d, ID #%d
        (publication type: %s) </span><br />
        %s (%s%s) %s <em>%s</em>, <em>%s</em>%s, %s.</p>\n",
        $record->{rec_number}, $id, $record->{ref_type_name},
        $record->{authors},
        $record->{pub_dates} ? $record->{pub_dates}.', ' : '',
        $record->{year},
        $record->{title},
        $record->{journal},
        $record->{volume},
        $record->{number} ?  '('.$record->{number}.')' : '',
        $record->{pages};

    printf '<span class=other><b>Secondary Author(s):</b> %s</span><br />',
        $record->{secondary_authors} if $record->{secondary_authors};
    printf '<span class=other><b>Translated Title:</b> %s</span><br />',
        $record->{translated_title} if $record->{translated_title};
    printf '<span class=other><b>Alternate Title:</b> %s</span><br />',
        $record->{alt_title} if $record->{alt_title};
    printf '<span class=other><b>Originally Published As:</b> %s</span><br />',
        $record->{orig_pub} if $record->{orig_pub};
    printf '<span class=other><b>Language:</b> %s</span><br />',
        $record->{language} if $record->{language};
    printf '<span class=other><b>ISBN:</b> %s</span><br />',
        $record->{isbn} if $record->{isbn};
    printf '<span class=other><b>Work Type:</b> %s</span><br />',
        $record->{work_type} if $record->{work_type};
    printf '<span class=other><b>Database Name:</b> %s</span><br />',
        &$fr_joindata($record->{database_name}) if $record->{database_name};
    printf '<span class=other><b>Research Design:</b> %s</span><br />',
        $record->{research_design} if $record->{research_design};
    print '<span class=other><b>Address:</b> '.
                $record->{auth_address}.'</span><br />' if $record->{auth_address};
    if ($record->{urls}) {
        foreach my $url (split (/[\r\n]+/, $record->{urls})) {
            my $url_disp = length $url > 80 ?  substr($url,0,80)."&hellip;" : $url;
            print qq(<span class=other><b>Abstract URL:</b> ).
                  qq(<a href="$url" target="_blank">$url_disp</a></span><br />);
        }
    }
    print '<p>';
    print '<span class=other><b>Subset #1:</b> '.
        $record->{subset1}.'</span><br />' if $record->{subset1};
    print '<span class=other><b>Subset #2:</b> '.
        $record->{subset2}.'</span><br />' if $record->{subset2};
    print '<span class=other><b>Subset #3:</b> '.
        $record->{subset3}.'</span><br />' if $record->{subset3};
    printf "<p class=abstract><b>Notes:</b><br>%s</p>\n",
            &$fr_joindata($record->{notes})
                    if $record->{notes};

    printf "<p class=abstract><b>Abstract:</b><br>%s</p>\n",
            $record->{abstract}
                    if $record->{abstract} && $show_abstracts;

    # print keywords
    my @keywords;
    my $sth_keyword_biblio = $dbh->prepare(q{
            SELECT kid FROM biblio_keywords WHERE id=?})
        || die $dbh->errstr;
    my $sth_keyword = $dbh->prepare(q{
            SELECT keyword FROM keywords WHERE kid=? ORDER BY keyword})
        || die $dbh->errstr;
    $sth_keyword_biblio->execute($id);
    while (my $row = $sth_keyword_biblio->fetch) {
        $sth_keyword->execute($row->[0]);
        while (my $row2 = $sth_keyword->fetch) {
            push @keywords, &$fr_highlight($row2->[0], $k1, 1);
        }
    }
    my $keywords = join '; ', sort @keywords;
    print "<p class=keywords><b>Keywords:</b><br />$keywords</p>" if $keywords;
    print "<p class=other><b>Record last updated:</b> $record->{modified}</p>";
};

my $fr_search_new = sub {
    # the same search interface is available from the main record listing,
    # but it's here in case that's too cryptic for some users
    printf qq(<h2>Search New Records</h2>
    <form action="%s" method=get>
    <input type=hidden name=op value=list>
    <p>
    <table>
        <tr>
            <td>
                Where
                <select name=f1>
                    <option value="title">Title</option>
                    <option value="authors">Author(s)</option>
                    <option value="abstract">Abstract</option>
                    <option value="journal">Journal name</option>
                    <option value="research_design">Research design</option>
                    <option value="any">Any of the above</option>
                    <option value="any2">----------------</option>
                    <option value="volume">Volume</option>
                    <option value="number">Issue</option>
                    <option value="pages">Pages</option>
                    <option value="year">Year</option>
                    <option value="subset1">Subset #1</option>
                    <option value="subset2">Subset #2</option>
                    <option value="subset3">Subset #3</option>
                </select>
                matches:
            </td>
            <td>
                <input name=v1 size=40><br>
            </td>
        <tr>
            <td>
                and
                <select name=f2>
                    <option value="authors">Author(s)</option>
                    <option value="title">Title</option>
                    <option value="abstract">Abstract</option>
                    <option value="journal">Journal name</option>
                    <option value="research_design">Research design</option>
                    <option value="any">Any of the above</option>
                    <option value="any2">----------------</option>
                    <option value="volume">Volume</option>
                    <option value="number">Issue</option>
                    <option value="pages">Pages</option>
                    <option value="year">Year</option>
                    <option value="subset1">Subset #1</option>
                    <option value="subset2">Subset #2</option>
                    <option value="subset3">Subset #3</option>
                </select>
                matches:
            </td>
            <td>
                <input name=v2 size=40>
            </td>
        <tr>
            <td>
                with the following keywords:
            </td>
            <td>
                <input name=k1 size=40>),
        $q->url();

    print qq(<tr><td>from the following database: <td><select name=db>\n);
    foreach my $db (sort keys %database_names) {
        printf qq(<option value="%s">%s</option>\n),
                    $db, $database_names{$db};
    }
    print qq(</select>\n);

    print qq(<tr><td>with the following research designs: <td><select name=rd>\n);
    foreach my $rd (sort {$research_designs{$a} cmp $research_designs{$b}} keys %research_designs) {
        next if $rd eq 'NONE';
        printf qq(<option value="%s">%s</option>\n),
                    $rd, $research_designs{$rd};
    }
    printf qq(<option value="%s">%s</option>\n),
                'NONE', $research_designs{'NONE'};
    print qq(</select>\n);

    print qq(<tr><td colspan=2 align=right>
            <input class=bibliobutton type=submit value=Search> </table> </form>\n);
};

my $fr_search_old = sub {
    # the same search interface is available from the main record listing,
    # but it's here in case that's too cryptic for some users
    printf '<h2>Search Old Records</h2>
        <form action="%s" method=get>
        <input type=hidden name=op value=list_old>
        <p>
        Search for records in which:
        <select name=f1>
            <option value=title>Title</option>
            <option value=abstract>Abstract</option>
            <option value=year>Year</option>
            <option value=authors>Author(s)</option>
            <option value=journal>Journal name</option>
            <option value=any>Any of the above</option>
        </select>
        matches
        <input name=v1>
        <input class=bibliobutton type=submit value=Search>
        </form>
        ',
        $q->url();
};

my $fr_upload = sub {
    # first, let's be paranoid about security and other possible problems
    if (!defined($file) || $file eq '') {
        print $q->h3("Error: no file name given");
        return;
    }
    if ($file !~ m/^[-\w .]+$/) {
        print $q->h3("Error: invalid characters in given file name");
        print $q->p("Please rename the file and try again.");
        return;
    }
    if (! -e $tmpdir.$file) {
        print $q->h3("Error: no such file '$file'");
        return;
    }
    if (! -r $tmpdir.$file) {
        print $q->h3("Error: file '$file' not readable");
        return;
    }

    # alright, now we can import the XML file
    my $xs1 = XML::Simple->new();
    my $doc = $xs1->XMLin($tmpdir.$file, ForceContent => 1);
    my $num_records_added = 0;
    my $num_records_updated = 0;
    my $num_keywords_added = 0;

    # return the keyword id, or 0 if not already added
    my $fr_check_keyword = sub {
        my $keyword = shift;
        my $sth_keyword_check = shift;
        $sth_keyword_check->execute($keyword);
        if ($sth_keyword_check->rows > 0) {
            my $result = $sth_keyword_check->fetchrow_hashref;
            return $result->{kid};
        } else {
            return 0;
        }
    };

    my $fr_process_record = sub {
        my $record = shift;
        # check if this record's already in the database, which we do by
        # checking the endnotes database filename (without the path), and
        # the endnotes record number within that file.  hopefully this is
        # a unique identifier.
        #if (!(defined $record->{'rec-number'}->{content} &&
        #    defined $record->{database}->{path})) {
        #    print "<b>Warning: no record number or database source
        #        given for this entry!</b><br>\n";
        #    print "<b>Skipping because we can't test if it's already
        #        been upload...</b>\n";
        #} else {
            #my $path = $record->{database}->{path};
            #$path =~ s/.*[\/\\]//;
            #$path = $dbh->quote($path);
            #$path =~ s/^'/'%/;
            #my $query = sprintf "SELECT id, rec_number, modified,
            #    database_path FROM biblio where rec_number=%d AND
            #        database_path LIKE %s",
            #    $record->{'rec-number'}->{content}, $path;
            #my $ref = $dbh->selectrow_hashref($query);
            my $id;
            #if (defined $ref) {
            #    $path =~ s/%//;
            #    printf "<b>Already in database!</b> (id=%d)
            #            (database=%s) (date added=%s)<br>\n",
            #            $ref->{id}, $path, $ref->{modified};
            #    print "<b>To add this record anyways, please upload it
            #        using a different record number or Endnotes file
            #        name.</b><br>\n";
            #    $id = $ref->{id};
            #} else {
                my $query2 = sprintf "SELECT id, title, journal, volume, number, year,
                   pages, rec_number, modified, database_path
                        FROM biblio WHERE 
                        title LIKE %s AND
                        journal LIKE %s AND
                        volume LIKE %s AND
                        number LIKE %s AND
                        year=%d",
                    $dbh->quote($record->{titles}->{title}->{style}->{content} || ''),
                    $dbh->quote($record->{titles}->{'secondary-title'}->{style}->{content} || ''),
                    $dbh->quote($record->{volume}->{style}->{content} || ''),
                    $dbh->quote($record->{number}->{style}->{content} || ''),
                    $record->{dates}->{year}->{style}->{content} || 0;
                my $ref2 = $dbh->selectrow_hashref($query2);
                print "<!-- DEBUG \n$query2\n -->\n";
                if (defined $ref2) {
                    printf "<b>Already in database!</b> (id=%d)
                            (database=%s) (date added=%s)<br>\n",
                            $ref2->{id}, $ref2->{database_path}, $ref2->{modified};
                    print "<b>A record already exists with the same title,
                          journal name, volume, and number.  It will be
                          updated with these new data and keywords.</b><br>\n";
                    $id = $ref2->{id};
                    my $updated = &$fr_update_record($record, $id);
                    if (scalar @$updated) {
                        print "(Fields updated were: ", join('; ',@$updated), ")<br>\n";
                        $num_records_updated++;
                    } else {
                        print "(No fields updated.)<br>\n";
                    }
                } else {
                    # new record
                    $id = &$fr_add_record($record, $sth_insert);
                    printf "<b>Added to database</b> (id=%d)<br>\n", $id;
                    $num_records_added++;
                }
            #}

            # handle this separately so that people can add new keywords
            # more easily.  they can upload the same file with only the
            # keywords changed, and it will work.
            if (exists $record->{mykeywords}) {
                my @keywords = split(/; /, $record->{mykeywords});
                foreach my $keyword (@keywords) {
                    # make sure keywords are in database; get kid
                    my $kid = &$fr_check_keyword($keyword, $sth_keyword_check);
                    if ($kid == 0) {
                        $sth_keyword_insert->execute($keyword);
                        $kid = $dbh->{mysql_insertid};
                        $num_keywords_added++;
                    }
                    # add association between this keyword and this record,
                    # if not already there
                    $sth_biblio_keyword_check->execute($id,$kid);
                    if (!$sth_biblio_keyword_check->rows) {
                        $sth_biblio_keyword_insert->execute($id,$kid);
                    }
                }
            }
        #}
    };

    print $q->h3("Parsing file...");
    if (ref $doc->{records}->{record} eq "ARRAY") {
        for (my $i=0;$i<=$#{$doc->{records}->{record}};$i++) {
            print "<hr>\n";
            my $record = $doc->{records}->{record}[$i];
            &$fr_print_record($record);     # print and pre-process record
            &$fr_process_record($record);   # add it, plus its keywords
        }
    } else {
        # just one record in XML file
        print "<hr>\n";
        my $record = $doc->{records}->{record};
        &$fr_print_record($record);
        &$fr_process_record($record);
    }
    $dbh->commit or die $dbh->errstr;

    # success -- delete file
    #unlink $tmpdir.$file;

    my $url = $q->url();
    print qq(<hr /><h2>Imported
            $num_records_added new records;
            $num_keywords_added new keywords;
            $num_records_updated updates to existing records</h2>
            <h3>Go to <a name=end href="$url?op=list">list of current
            records</a> or <a href="$url?op=upload_form">upload
            another file</a></h3>);
};

my $fr_upload_form = sub {
    #
    # i couldn't get this file upload to work in perl.  i spent all
    # afternoon on it and i have no idea what was wrong.  while i'm sure it
    # could have been resolved it was faster to write this one part in PHP.
    # i blame mod_perl. -- mvc
    #
    print << 'EOF';
<h2>XML file upload</h2>

<p>Upload an XML file exported from Endnotes here.

<form enctype="multipart/form-data" action="/biblio/upload.php" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="4194304" />
<input name="userfile" type="file" size=70 />
<input type="submit" value="Upload File" />
</form>

<br>
<br>

<div class="box">

<p>Please note: the software currently supports only the &ldquo;Journal
Article&rdquo; entry type, and only the following data fields:

<table width="100%">
<tr>
<td valign="top">

<ul>
<li>abstract
<li>accession-num
<li>alt-title
<li>auth-address
<li>author
<li>authors
<li>contributors
<li>custom1
<li>custom2
<li>custom3
<li>database
</ul>
</td>

<td valign="top">
<ul>
<li>date
<li>dates
<li>isbn
<li>keyword
<li>keywords
<li>language
<li>notes
<li>number
<li>orig-pub
<li>pages
<li>pub-dates
</ul>
</td>

<td valign="top">
<ul>
<li>publisher
<li>pub-location
<li>rec-number
<li>ref-type
<li>related-urls
<li>remote-database-name
<li>research-notes
<li>secondary-authors
<li>secondary-title
<li>short-title
<li>source-app
</ul>
</td>

<td valign="top">
<ul>
<li>style
<li>title
<li>titles
<li>translated-title
<li>url
<li>urls
<li>volume
<li>work-type
<li>year
</ul>
</td>
</tr>
</table>
</div>

EOF

#<form method=post action="$ENV{'SCRIPT_NAME'}" enctype="multipart/form-data">
#<table>
#<tr> <td>Input file: <td><input type=file name="file" accept="*/*" size=70>
#<td align=center colspan=2><input type=submit name=op value="upload">
#<input type=hidden name=op value=upload>
#</table>
#</form>

};

my $fr_show_homepage = sub {
    open(INCLUDE,"</home/rgp/rgp.toronto.on.ca/biblio/index.html");
    while (my $block = <INCLUDE>) {
        print $block;
    }
    close(INCLUDE);
};

my $fr_show_links = sub {
    my $url = $q->url();
    print qq(
        <p>
        <strong>
        <a href="$url?op=list">List current records</a> |
        <a href="$url?op=list_old">List old records</a> <br>
        <a href="$url?op=search">Search current records</a> |
        <a href="$url?op=search_old">Search old records</a> <br>
        </strong>
        </p>
    );
    print qq(
            <div class="box">
            <strong>Administrators Only:
            <a href="$url?op=upload_form">Upload file</a></strong>
            </div>
            )
        if &$fr_check_admin();
};

my $fr_show_signup = sub {
    my $url = $q->url();
    print qq(
            <p class="box">
            <strong>This service is only available to registered users.
            Please sign in or apply for an account
            <a href="/user">here</a>.</strong>
            </p>
        );
};

my $fr_cleanup = sub {
    $dbh->trace(0) if $debug > 1;
    $dbh->disconnect || die "Error connecting to the database: $DBI::errstr\n";
};

my $fr_myfooter = sub {
    #my $url = $q->url();
    #print "<hr>\n" unless $op =~ /print/;
    #print qq([ <a href="$url">RGP Reference Manager Start Page</a> ]);
    #print $q->end_html;
    if ($op !~ /print/) {
        open(TAIL,"</home/rgp/rgp.toronto.on.ca/biblio/drupalfoot.html");
        while (my $block = <TAIL>) {
            print $block;
        }
        close(TAIL);
    }
};

my $fr_clear_new = sub {
    print $q->h2("Clipboard");
    #print "<h2>debug: cookie values</h2>\n" if $debug;
    #print Data::Dumper->Dump([\%save1], [qw(*save1)]), "<br>\n" if $debug;
    print $q->p(qq(The clipboard has been cleared.));
};

my $fr_clear_old = sub {
    print $q->h2("Clipboard (Old Records)");
    #print "<h2>debug: cookie values</h2>\n" if $debug;
    #print Data::Dumper->Dump([\%save2], [qw(*save2)]), "<br>\n" if $debug;
    print $q->p(qq(The clipboard has been cleared.));
};

my $fr_myheader = sub {
    my $op = shift || '';

    # set cookies
    foreach my $save (grep /^s1_\d+$/, @args) {
        $save1{$save} = 'y';
    }
    foreach my $save (grep /^s2_\d+$/, @args) {
        $save2{$save} = 'y';
    }
    %save1 = () if $op eq 'clear';
    %save2 = () if $op eq 'clear_old';
    my $cookie_save1 = $q->cookie(-name=>'save1', -value=>\%save1);
    my $cookie_save2 = $q->cookie(-name=>'save2', -value=>\%save2);

    # so that the count of clipboard entries is up to date
    print $q->header(-cookie=>[$cookie_save1,$cookie_save2], -expires=>'-1d');

    #print $q->start_html(-title=>'RGP Reference Manager',
    #    -style=>{'src'=>'/biblio/biblio.css'},
    #    -lang=>'en-CA');

    #<?xml version="1.0" encoding="utf-8"?>
    #<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.0//EN"
    #    "http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd">
    #<html xmlns="http://www.w3.org/1999/xhtml" lang="en-CA">

    print << 'EOF' ;
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<html>
<head>
<script language="JavaScript">
<!-- Begin
function popUp(URL) {
    day = new Date();
    id = day.getTime();
    eval("page" + id + " = window.open(URL, '" + id + "', 'toolbar=0,scrollbars=1,location=0,statusbar=0,menubar=0,resizable=0,width=300,height=400,left = 200,top = 200');");
}
// End -->
</script>
<title>RGP Reference Manager</title>
<link rel="stylesheet" type="text/css" href="/biblio/biblio.css" />
EOF

    if ($op !~ /print/) {
        open(HEAD,"</home/rgp/rgp.toronto.on.ca/biblio/drupalhead.html");
        while (my $block = <HEAD>) {
            print $block;
        }
        close(HEAD);
    }
};

# generate list of all db names
my $fr_db_list = sub {
    my %db_list;
    my $dbs = $dbh->selectall_arrayref("SELECT DISTINCT(database_name) FROM biblio")
            || die "Error connecting to the database: $DBI::errstr\n";
    # this field contains strings separated by newlines
    foreach my $db (@$dbs) {
        foreach my $db2 (@$db) {
            my @dbs2 = split /[\n\r]/, $db2;
            foreach my $db3 (@dbs2) {
                # normalize case
                $db3 = lc $db3;
                $db3 =~ s/\b[a-z]/\U$&/g;
                $db3 =~ s/\b(And|Of|For|The|A)\b/\l$&/g;
                $db_list{$db3} = 1;
            }
        }
    }
    my @db_list = sort keys %db_list;
    %db_list = ();
    for (my $i=0; $i <= $#db_list; $i++) {
        my $key = sprintf("DB%02d", $i);
        $db_list{$key} = $db_list[$i];
    }
    $db_list{'A'} = 'All Databases';
    # check for records with no db specified
    my $list = $dbh->selectall_hashref("SELECT database_name FROM biblio 
            WHERE database_name = ''", "database_name")
        || die "Error connecting to the database: $DBI::errstr\n";
    my $num_recs = scalar keys %{$list};
    $db_list{'NONE'} = 'Records with none specified' if $num_recs;
    %database_names = %db_list;
};

# main()
&$fr_myheader($op);
if (&$fr_check_user()) {
    &$fr_db_init();
    &$fr_db_list();
    if ($op eq 'upload' && &$fr_check_admin()) {
        &$fr_upload();
    } elsif ($op eq 'upload_form' && &$fr_check_admin()) {
        &$fr_upload_form();
    } elsif ($op eq 'list') {
        &$fr_list_new();
    } elsif ($op eq 'search') {
        &$fr_search_new();
    } elsif ($op eq 'list_old') {
        &$fr_list_old();
    } elsif ($op eq 'search_old') {
        &$fr_search_old();
    } elsif ($op eq 'clipboard') {
        &$fr_clipboard_new();
    } elsif ($op eq 'clipboard_old') {
        &$fr_clipboard_old();
    } elsif ($op eq 'clipboard_print') {
        &$fr_clipboard_new_print();
    } elsif ($op eq 'clipboard_old_print') {
        &$fr_clipboard_old_print();
    } elsif ($op eq 'clear') {
        &$fr_clear_new();
    } elsif ($op eq 'clear_old') {
        &$fr_clear_old();
    } elsif ($op eq 'display') {
        &$fr_display();
    } else {
        &$fr_show_links();
        &$fr_show_homepage();
    }
    &$fr_cleanup();
} else {
    &$fr_show_signup();
    &$fr_show_homepage();
}
&$fr_myfooter($op);
