#!/usr/bin/perl
#

package LJ::Poll;

use strict;
use HTML::TokeParser ();

require "$ENV{'LJHOME'}/cgi-bin/cleanhtml.pl";

sub clean_poll
{
    my $ref = shift;
    if ($$ref !~ /[<>]/) {
        LJ::text_out($ref);
        return;
    }

    my $poll_eat = [qw[head title style layer iframe applet object]];
    my $poll_allow = [qw[a b i u strong em img]];
    my $poll_remove = [qw[bgsound embed object caption link font]];

    LJ::CleanHTML::clean($ref, {
        'wordlength' => 40,
        'addbreaks' => 0,
        'eat' => $poll_eat,
        'mode' => 'deny',
        'allow' => $poll_allow,
        'remove' => $poll_remove,
    });
    LJ::text_out($ref);
}


sub contains_new_poll
{
    my $postref = shift;
    return ($$postref =~ /<lj-poll\b/i);
}

sub parse
{
    &LJ::nodb;
    my ($postref, $error, $iteminfo) = @_;

    $iteminfo->{'posterid'} += 0;
    $iteminfo->{'journalid'} += 0;

    my $newdata;

    my $popen = 0;
    my %popts;

    my $qopen = 0;
    my %qopts;
    
    my $iopen = 0;
    my %iopts;

    my @polls;  # completed parsed polls

    my $p = HTML::TokeParser->new($postref);

    # if we're being called from mailgated, then we're not in web context and therefore
    # do not have any BML::ml functionality.  detect this now and report errors in a 
    # plaintext, non-translated form to be bounced via email.
    my $have_bml = eval { BML::ml() } || ! $@;

    my $err = sub {
        # more than one element, either make a call to BML::ml
        # or build up a semi-useful error string from it
        if (@_ > 1) {
            if ($have_bml) {
                $$error = BML::ml(@_);
                return 0;
            }

            $$error = shift() . ": ";
            while (my ($k, $v) = each %{$_[0]}) {
                $$error .= "$k=$v,";
            }
            chop $$error;
            return 0;
        }

        # single element, either look up in %BML::ML or return verbatim
        $$error = $have_bml ? $BML::ML{$_[0]} : $_[0];
        return 0;
    };

    while (my $token = $p->get_token)    
    {
        my $type = $token->[0];
        my $append;
        
        if ($type eq "S")     # start tag
        {
            my $tag = $token->[1];
            my $opts = $token->[2];
    
            ######## Begin poll tag
            
            if ($tag eq "lj-poll") {
                return $err->('poll.error.nested', { 'tag' => 'lj-poll' })
                    if $popen;

                $popen = 1;
                %popts = ();
                $popts{'questions'} = [];

                $popts{'name'} = $opts->{'name'};
                $popts{'whovote'} = lc($opts->{'whovote'}) || "all";
                $popts{'whoview'} = lc($opts->{'whoview'}) || "all";

                if ($popts{'whovote'} ne "all" && 
                    $popts{'whovote'} ne "friends")
                {
                    return $err->('poll.error.whovote');
                }
                if ($popts{'whoview'} ne "all" && 
                    $popts{'whoview'} ne "friends" &&
                    $popts{'whoview'} ne "none")
                {
                    return $err->('poll.error.whoview');
                }
            }

            ######## Begin poll question tag
            
            elsif ($tag eq "lj-pq") 
            {
                return $err->('poll.error.nested', { 'tag' => 'lj-pq' })
                    if $qopen;

                return $err->('poll.error.missingljpoll')
                    unless $popen;

                $qopen = 1;
                %qopts = ();
                $qopts{'items'} = [];

                $qopts{'type'} = $opts->{'type'};
                if ($qopts{'type'} eq "text") {
                    my $size = 35;
                    my $max = 255;
                    if (defined $opts->{'size'}) {
                        if ($opts->{'size'} > 0 &&
                            $opts->{'size'} <= 100)
                        {
                            $size = $opts->{'size'}+0;
                        } else {
                            return $err->('poll.error.badsize');
                        }
                    }
                    if (defined $opts->{'maxlength'}) {
                        if ($opts->{'maxlength'} > 0 &&
                            $opts->{'maxlength'} <= 255)
                        {
                            $max = $opts->{'maxlength'}+0;
                        } else {
                            return $err->('poll.error.badmaxlength');
                        }
                    }

                    $qopts{'opts'} = "$size/$max";
                }
                if ($qopts{'type'} eq "scale") 
                {
                    my $from = 1;
                    my $to = 10;
                    my $by = 1;

                    if (defined $opts->{'from'}) {
                        $from = int($opts->{'from'});
                    }
                    if (defined $opts->{'to'}) {
                        $to = int($opts->{'to'});
                    }
                    if (defined $opts->{'by'}) {
                        $by = int($opts->{'by'});
                    }
                    if ($by < 1) {
                        return $err->('poll.error.scaleincrement');
                    }
                    if ($from >= $to) {
                        return $err->('poll.error.scalelessto');
                    }
                    if ((($to-$from)/$by) > 20) {
                        return $err->('poll.error.scaletoobig');
                    }
                    $qopts{'opts'} = "$from/$to/$by";
                }

                $qopts{'type'} = lc($opts->{'type'}) || "text";

                if ($qopts{'type'} ne "radio" &&
                    $qopts{'type'} ne "check" &&
                    $qopts{'type'} ne "drop" &&
                    $qopts{'type'} ne "scale" &&
                    $qopts{'type'} ne "text")
                {
                    return $err->('poll.error.unknownpqtype');
                }
                
                
            }

            ######## Begin poll item tag

            elsif ($tag eq "lj-pi")
            {
                if ($iopen) {
                    return $err->('poll.error.nested', { 'tag' => 'lj-pi' });
                }
                if (! $qopen) {
                    return $err->('poll.error.missingljpq');
                }
                if ($qopts{'type'} eq "text")
                {
                    return $err->('poll.error.noitemstext');
                }
                
                $iopen = 1;
                %iopts = ();
            }   

            #### not a special tag.  dump it right back out.

            else 
            {
                $append .= "<$tag";
                foreach (keys %$opts) {
                    $append .= " $_=\"$opts->{$_}\"";
                }
                $append .= ">";
            }
        }
        elsif ($type eq "E") 
        {
            my $tag = $token->[1];

            ##### end POLL

            if ($tag eq "lj-poll") {
                return $err->('poll.error.tagnotopen', { 'tag' => 'lj-poll' })
                    unless $popen;

                $popen = 0;

                return $err->('poll.error.noquestions')
                    unless @{$popts{'questions'}};
                
                $popts{'journalid'} = $iteminfo->{'journalid'};
                $popts{'posterid'} = $iteminfo->{'posterid'};
                
                push @polls, { %popts };

                $append .= "<lj-poll-placeholder>";
            } 

            ##### end QUESTION

            elsif ($tag eq "lj-pq") {
                return $err->('poll.error.tagnotopen', { 'tag' => 'lj-pq' })
                    unless $qopen;

                unless ($qopts{'type'} eq "scale" || 
                        $qopts{'type'} eq "text" || 
                        @{$qopts{'items'}}) 
                {
                    return $err->('poll.error.noitems');
                }

                $qopts{'qtext'} =~ s/^\s+//;
                $qopts{'qtext'} =~ s/\s+$//;
                my $len = length($qopts{'qtext'})
                    or return $err->('poll.error.notext');

                push @{$popts{'questions'}}, { %qopts };
                $qopen = 0;
                
            }

            ##### end ITEM

            elsif ($tag eq "lj-pi") {
                return $err->('poll.error.tagnotopen', { 'tag' => 'lj-pi' })
                    unless $iopen;

                $iopts{'item'} =~ s/^\s+//;
                $iopts{'item'} =~ s/\s+$//;

                my $len = length($iopts{'item'});
                return $err->('poll.error.pitoolong', { 'len' => $len, })
                    if $len > 255 || $len < 1;

                push @{$qopts{'items'}}, { %iopts };
                $iopen = 0;
            }
            
            ###### not a special tag.
            
            else
            {
                $append .= "</$tag>";
            }
        }
        elsif ($type eq "T" || $type eq "D") 
        {
            $append = $token->[1];
        } 
        elsif ($type eq "C") {
            # ignore comments
        }
        elsif ($type eq "PI") {
            $newdata .= "<?$token->[1]>";
        }
        else {
            $newdata .= "<!-- OTHER: " . $type . "-->\n";
        }

        ##### append stuff to the right place
        if (length($append))
        {
            if ($iopen) {
                $iopts{'item'} .= $append;
            }
            elsif ($qopen) {
                $qopts{'qtext'} .= $append;
            }
            elsif ($popen) {
                0;       # do nothing.
            } else {
                $newdata .= $append;
            }
        }

    } 

    if ($popen) { return $err->('poll.error.unlockedtag', { 'tag' => 'lj-poll' }); }
    if ($qopen) { return $err->('poll.error.unlockedtag', { 'tag' => 'lj-pq' }); }
    if ($iopen) { return $err->('poll.error.unlockedtag', { 'tag' => 'lj-pi' }); }

    $$postref = $newdata;
    return @polls;
}

# preview poll
#  -- accepts $poll hashref as found in the array returned by LJ::Poll::parse()
sub preview {
    my $poll = shift;
    return unless ref $poll eq 'HASH';
    
    my $ret = '';
    
    $ret .= "<form action='#'>\n";
    $ret .= "<b>" . BML::ml('poll.pollnum', { 'num' => 'xxxx' }) . "</b>";
    if ($poll->{'name'}) {
        LJ::Poll::clean_poll(\$poll->{'name'});
        $ret .= " <i>$poll->{'name'}</i>";
    }
    $ret .= "<br />\n";
    $ret .= BML::ml('poll.security', { 'whovote' => $BML::ML{'poll.security.'.$poll->{whovote}}, 'whoview' => $BML::ML{'poll.security.'.$poll->{whoview}}, });
    
    # iterate through all questions
    foreach my $q (@{$poll->{'questions'}}) {
        if ($q->{'qtext'}) {
            LJ::Poll::clean_poll(\$q->{'qtext'});
            $ret .= "<p>$q->{'qtext'}</p>\n";
        }
        $ret .= "<div style='margin: 10px 0 10px 40px'>";

        # text questions
        if ($q->{'type'} eq 'text') {
            my ($size, $max) = split(m!/!, $q->{'opts'});
            $ret .= LJ::html_text({ 'size' => $size, 'maxlength' => $max });

        # scale questions
        } elsif ($q->{'type'} eq 'scale') {
            my ($from, $to, $by) = split(m!/!, $q->{'opts'});
            $by ||= 1;
            my $count = int(($to-$from)/$by) + 1;
            my $do_radios = ($count <= 11);
            
            # few opts, display radios
            if ($do_radios) {
                $ret .= "<table><tr valign='top' align='center'>\n";
                for (my $at = $from; $at <= $to; $at += $by) {
                    $ret .= "<td>" . LJ::html_check({ 'type' => 'radio' }) . "<br />$at</td>\n";
                }
                $ret .= "</tr></table>\n";

            # many opts, display select
            } else {
                my @optlist = ();
                for (my $at = $from; $at <= $to; $at += $by) {
                    push @optlist, ('', $at);
                }
                $ret .= LJ::html_select({}, @optlist);
            }
            
        # questions with items
        } else {

            # drop-down list
            if ($q->{'type'} eq 'drop') {
                my @optlist = ('', '');
                foreach my $it (@{$q->{'items'}}) {
                    LJ::Poll::clean_poll(\$it->{'item'});
                    push @optlist, ('', $it->{'item'});
                }
                $ret .= LJ::html_select({}, @optlist);


            # radio or checkbox
            } else {
                foreach my $it (@{$q->{'items'}}) {
                    LJ::Poll::clean_poll(\$it->{'item'});
                    $ret .= LJ::html_check({ 'type' => $q->{'type'} }) . "$it->{'item'}<br />\n";
                }
            }
        }
        
        $ret .= "</div>\n";
        
    }
    
    $ret .= LJ::html_submit('', $BML::ML{'poll.submit'}, { 'disabled' => 1 }) . "\n";
    $ret .= "</form>";
    
    return $ret; 
}

# note: $itemid is a $ditemid (display itemid, *256 + anum)
sub register
{
    &LJ::nodb;
    my $dbh = LJ::get_db_writer();
    my $post = shift;
    my $error = shift;
    my $itemid = shift;
    my @polls = @_;
    
    foreach my $po (@polls)
    {
        my %popts = %$po;
        $popts{'itemid'} = $itemid+0;

        #### CREATE THE POLL!
        
        my $sth = $dbh->prepare("INSERT INTO poll (itemid, journalid, posterid, whovote, whoview, name) " .
                                "VALUES (?, ?, ?, ?, ?, ?)");
        $sth->execute($itemid, $popts{'journalid'}, $popts{'posterid'},
                      $popts{'whovote'}, $popts{'whoview'}, $popts{'name'});
        if ($dbh->err) {
            $$error = BML::ml('poll.dberror', { errmsg => $dbh->errstr });
            return 0;
        }
        my $pollid = $dbh->{'mysql_insertid'};

        $$post =~ s/<lj-poll-placeholder>/<lj-poll-$pollid>/;  # NOT global replace!
        
        ## start inserting poll questions
        my $qnum = 0;
        foreach my $q (@{$popts{'questions'}})
        {
            $qnum++;
            $sth = $dbh->prepare("INSERT INTO pollquestion (pollid, pollqid, sortorder, type, opts, qtext) " . 
                                 "VALUES (?, ?, ?, ?, ?, ?)");
            $sth->execute($pollid, $qnum, $qnum, $q->{'type'}, $q->{'opts'}, $q->{'qtext'});
            if ($dbh->err) {
                $$error = BML::ml('poll.dberror.questions', { errmsg => $dbh->errstr });
                return 0;
            }
            
            my $pollqid = $dbh->{'mysql_insertid'};
            
            ## start inserting poll items
            my $inum = 0;
            foreach my $it (@{$q->{'items'}}) {
                $inum++;
                $dbh->do("INSERT INTO pollitem (pollid, pollqid, pollitid, sortorder, item) " . 
                         "VALUES (?, ?, ?, ?, ?)", undef, $pollid, $qnum, $inum, $inum, $it->{'item'});
                if ($dbh->err) {
                    $$error = BML::ml('poll.dberror.items', { errmsg => $dbh->errstr });
                    return 0;
                }
            }
            ## end inserting poll items
            
        }
        ## end inserting poll questions
        
    }  ### end while over all poles

}

sub show_polls
{
    &LJ::nodb;
    my $itemid = shift;
    my $remote = shift;
    my $postref = shift;

    $$postref =~ s/<lj-poll-(\d+)>/&show_poll($itemid, $remote, $1)/eg;
}

sub show_poll
{
    &LJ::nodb;
    my $dbr = LJ::get_db_reader();
    my $itemid = shift;
    my $remote = shift;
    my $pollid = shift;
    my $opts = shift;  # hashref.  {"mode" => results/enter/ans}
    my $sth;

    my $mode = $opts->{'mode'};
    $pollid += 0;
    
    my $po = $dbr->selectrow_hashref("SELECT * FROM poll WHERE pollid=?", undef, $pollid);
    return "<b>[" . BML::ml('poll.error.pollnotfound', { 'num' => $pollid }) . "]</b>" unless $po;
    return "<b>[$BML::ML{'poll.error.noentry'}]</b>"
        if $itemid && $po->{'itemid'} != $itemid;

    my ($can_vote, $can_view) = find_security($po, $remote);

    # update the mode if we need to
    $mode = 'results' unless $remote;
    if (!$mode && $remote) {
        my $time = $dbr->selectrow_array('SELECT datesubmit FROM pollsubmission '.
                                         'WHERE pollid=? AND userid=?', undef, $pollid, $remote->{userid});
        $mode = $time ? 'results' : $can_vote ? 'enter' : 'results';
    }

    ### load all the questions
    my @qs;
    $sth = $dbr->prepare('SELECT * FROM pollquestion WHERE pollid=?');
    $sth->execute($pollid);
    push @qs, $_ while $_ = $sth->fetchrow_hashref;
    @qs = sort { $a->{sortorder} <=> $b->{sortorder} } @qs;

    ### load all the items
    my %its;
    $sth = $dbr->prepare("SELECT pollqid, pollitid, item FROM pollitem WHERE pollid=? ORDER BY sortorder");
    $sth->execute($pollid);
    while (my ($qid, $itid, $item) = $sth->fetchrow_array) {
        push @{$its{$qid}}, [ $itid, $item ];
    }

    # see if we have a hook for alternate poll contents
    my $ret = LJ::run_hook('alternate_show_poll_html', $po, $mode, \@qs);
    return $ret if $ret;

    ### view answers to a particular question in a poll
    if ($mode eq "ans") 
    {
        return "<b>[$BML::ML{'poll.error.cantview'}]</b>"
            unless $can_view;

        # get the question from @qs, which we loaded earlier
        my $q;
        foreach (@qs) {
            $q = $_ if $_->{pollqid} == $opts->{qid};
        }
        return "<b>[$BML::ML{'poll.error.questionnotfound'}]</b>"
            unless $q;

        # get the item information from %its, also loaded earlier
        my %it;
        $it{$_->[0]} = $_->[1] foreach (@{$its{$opts->{qid}}});

        LJ::Poll::clean_poll(\$q->{'qtext'});
        $ret .= $q->{'qtext'};
        $ret .= "<p>";

        my $LIMIT = 2000;
        $sth = $dbr->prepare("SELECT u.user, pr.value, ps.datesubmit ".
                             "FROM useridmap u, pollresult pr, pollsubmission ps " . 
                             "WHERE u.userid=pr.userid AND pr.pollid=? AND pollqid=? " . 
                             "AND ps.pollid=pr.pollid AND ps.userid=pr.userid LIMIT $LIMIT");
        $sth->execute($pollid, $opts->{'qid'});

        my @res;
        push @res, $_ while $_ = $sth->fetchrow_hashref;
        @res = sort { $a->{datesubmit} cmp $b->{datesubmit} } @res;
        
        foreach my $res (@res) {
            my ($user, $value) = ($res->{user}, $res->{value});
            
            ## some question types need translation; type 'text' doesn't.
            if ($q->{'type'} eq "radio" || $q->{'type'} eq "drop") {
                $value = $it{$value};
            }
            elsif ($q->{'type'} eq "check") {
                $value = join(", ", map { $it{$_} } split(/,/, $value));
            }

            LJ::Poll::clean_poll(\$value);
            $ret .= "<p>" . LJ::ljuser($user) . " -- $value</p>\n";
        }

        # temporary
        if (@res == $LIMIT) {
            $ret .= "<p>[$BML::ML{'poll.error.truncated'}]</p>";
        }
        
        return $ret;
    }

    # Users cannot vote unless they are logged in
    return "<?needlogin?>"
        if $mode eq 'enter' && !$remote;

    my $do_form = $mode eq 'enter' && $can_vote;
    my %preval;

    if ($do_form) {
        $sth = $dbr->prepare("SELECT pollqid, value FROM pollresult WHERE pollid=? AND userid=?");
        $sth->execute($pollid, $remote->{'userid'});
        while (my ($qid, $value) = $sth->fetchrow_array) {
            $preval{$qid} = $value;
        }

        $ret .= "<form action='$LJ::SITEROOT/poll/?id=$pollid' method='post'>";
        $ret .= LJ::form_auth();
        $ret .= LJ::html_hidden('pollid', $pollid);
    }

    $ret .= "<b><a href='$LJ::SITEROOT/poll/?id=$pollid'>" . BML::ml('poll.pollnum', { 'num' => $pollid }) . "</a></b> ";
    if ($po->{'name'}) {
        LJ::Poll::clean_poll(\$po->{'name'});
        $ret .= "<i>$po->{'name'}</i>";
    }
    $ret .= "<br />\n";
    $ret .= BML::ml('poll.security', { 'whovote' => $BML::ML{'poll.security.'.$po->{whovote}}, 
                                       'whoview' => $BML::ML{'poll.security.'.$po->{whoview}} }); 
    my $text = LJ::run_hook('extra_poll_description', $po, \@qs);
    $ret .= "<br />$text" if $text;

    ## go through all questions, adding to buffer to return
    foreach my $q (@qs)
    {
        my $qid = $q->{'pollqid'};
        LJ::Poll::clean_poll(\$q->{'qtext'});
        $ret .= "<p>$q->{'qtext'}</p><div style='margin: 10px 0 10px 40px'>";
        
        ### get statistics, for scale questions
        my ($valcount, $valmean, $valstddev, $valmedian);
        if ($q->{'type'} eq "scale") 
        {
            ## manually add all the possible values, since they aren't in the database
            ## (which was the whole point of making a "scale" type):
            my ($from, $to, $by) = split(m!/!, $q->{'opts'});
            $by = 1 unless ($by > 0 and int($by) == $by);
            for (my $at=$from; $at<=$to; $at+=$by) {
                push @{$its{$qid}}, [ $at, $at ];  # note: fake itemid, doesn't matter, but needed to be unique
            }

            $sth = $dbr->prepare("SELECT COUNT(*), AVG(value), STDDEV(value) FROM pollresult WHERE pollid=? AND pollqid=?");
            $sth->execute($pollid, $qid);
            ($valcount, $valmean, $valstddev) = $sth->fetchrow_array;
            
            # find median:
            $valmedian = 0;
            if ($valcount == 1) { 
                $valmedian = $valmean;
            } elsif ($valcount > 1) {
                my ($mid, $fetch);
                # fetch two mids and average if even count, else grab absolute middle
                $fetch = ($valcount % 2) ? 1 : 2;
                $mid = int(($valcount+1)/2);
                my $skip = $mid-1;

                $sth = $dbr->prepare("SELECT value FROM pollresult WHERE pollid=? AND pollqid=? " .
                                     "ORDER BY value+0 LIMIT $skip,$fetch");
                $sth->execute($pollid, $qid);
                while (my ($v) = $sth->fetchrow_array) {
                    $valmedian += $v;
                }
                $valmedian /= $fetch;
            }
        }

        my $usersvoted = 0;
        my %itvotes;
        my $maxitvotes = 1;

        if ($mode eq "results")
        {
            ### to see individual's answers
            $ret .= "<a href='$LJ::SITEROOT/poll/?id=$pollid&amp;qid=$qid&amp;mode=ans'>$BML::ML{'poll.viewanswers'}</a><br />";

            ### but, if this is a non-text item, and we're showing results, need to load the answers:
            if ($q->{'type'} ne "text") {
                $sth = $dbr->prepare("SELECT value FROM pollresult WHERE pollid=? AND pollqid=?");
                $sth->execute($pollid, $qid);
                while (my ($val) = $sth->fetchrow_array) {
                    $usersvoted++;
                    if ($q->{'type'} eq "check") {
                        foreach (split(/,/,$val)) {
                            $itvotes{$_}++;
                        }
                    } else {
                        $itvotes{$val}++;
                    }
                }
                
                foreach (values %itvotes) {
                    $maxitvotes = $_ if ($_ > $maxitvotes);
                }
            }
        }

        #### text questions are the easy case

        if ($q->{'type'} eq "text" && $do_form) {
            my ($size, $max) = split(m!/!, $q->{'opts'});

            $ret .= LJ::html_text({ 'size' => $size, 'maxlength' => $max, 
                                    'name' => "pollq-$qid", 'value' => $preval{$qid} });
        }

        #### drop-down list
        elsif ($q->{'type'} eq 'drop' && $do_form) {
            my @optlist = ('', '');
            foreach my $it (@{$its{$qid}}) {
                my ($itid, $item) = @$it;
                LJ::Poll::clean_poll(\$item);
                push @optlist, ($itid, $item);
            }
            $ret .= LJ::html_select({ 'name' => "pollq-$qid", 
                                      'selected' => $preval{$qid} }, @optlist);
        }

        #### scales (from 1-10) questions

        elsif ($q->{'type'} eq "scale" && $do_form) {
            my ($from, $to, $by) = split(m!/!, $q->{'opts'});
            $by ||= 1;
            my $count = int(($to-$from)/$by) + 1;
            my $do_radios = ($count <= 11);

            # few opts, display radios
            if ($do_radios) {

                $ret .= "<table><tr valign='top' align='center'>";

                for (my $at=$from; $at<=$to; $at+=$by) {
                    $ret .= "<td style='text-align: center;'>";
                    $ret .= LJ::html_check({ 'type' => 'radio', 'name' => "pollq-$qid",
                                             'value' => $at, 'id' => "pollq-$pollid-$qid-$at",
                                             'selected' => (defined $preval{$qid} && $at == $preval{$qid}) });
                    $ret .= "<br /><label for='pollq-$pollid-$qid-$at'>$at</label></td>";
                }

                $ret .= "</tr></table>\n";

            # many opts, display select
            # but only if displaying form
            } else {

                my @optlist = ('', '');
                for (my $at=$from; $at<=$to; $at+=$by) {
                    push @optlist, ($at, $at);
                }
                $ret .= LJ::html_select({ 'name' => "pollq-$qid", 'selected' => $preval{$qid} }, @optlist);
            }

        }

        #### now, questions with items

        else
        {
            my $do_table = 0;

            if ($q->{'type'} eq "scale") { # implies ! do_form
                my $stddev = sprintf("%.2f", $valstddev);
                my $mean = sprintf("%.2f", $valmean);
                $ret .= BML::ml('poll.scaleanswers', { 'mean' => $mean, 'median' => $valmedian, 'stddev' => $stddev });
                $ret .= "<br />\n";
                $do_table = 1;
                $ret .= "<table>";
            }

            foreach my $it (@{$its{$qid}})
            {
                my ($itid, $item) = @$it;
                LJ::Poll::clean_poll(\$item);

                # displaying a radio or checkbox
                if ($do_form) {
                    $ret .= LJ::html_check({ 'type' => $q->{'type'}, 'name' => "pollq-$qid",
                                             'value' => $itid, 'id' => "pollq-$pollid-$qid-$itid",
                                             'selected' => ($preval{$qid} =~ /\b$itid\b/) });
                    $ret .= " <label for='pollq-$pollid-$qid-$itid'>$item</label><br />";
                    next;
                }

                # displaying results
                my $count = $itvotes{$itid}+0;
                my $percent = sprintf("%.1f", (100 * $count / ($usersvoted||1)));
                my $width = 20+int(($count/$maxitvotes)*380);

                if ($do_table) {
                    $ret .= "<tr valign='middle'><td align='right'>$item</td>";
                    $ret .= "<td><img src='$LJ::IMGPREFIX/poll/leftbar.gif' align='absmiddle' height='14' width='7' />";
                    $ret .= "<img src='$LJ::IMGPREFIX/poll/mainbar.gif' align='absmiddle' height='14' width='$width' alt='$count ($percent%)' />";
                    $ret .= "<img src='$LJ::IMGPREFIX/poll/rightbar.gif' align='absmiddle' height='14' width='7' /> ";
                    $ret .= "<b>$count</b> ($percent%)</td></tr>";
                } else {
                    $ret .= "<p>$item<br />";
                    $ret .= "<span style='white-space: nowrap'><img src='$LJ::IMGPREFIX/poll/leftbar.gif' align='absmiddle' height='14' width='7' />";
                    $ret .= "<img src='$LJ::IMGPREFIX/poll/mainbar.gif' align='absmiddle' height='14' width='$width' alt='$count ($percent%)' />";
                    $ret .= "<img src='$LJ::IMGPREFIX/poll/rightbar.gif' align='absmiddle' height='14' width='7' /> ";
                    $ret .= "<b>$count</b> ($percent%)</span></p>";
                }
            }

            if ($do_table) {
                $ret .= "</table>";
            }

        }

        $ret .= "</div>";
    }
    
    if ($do_form) {
        $ret .= LJ::html_submit('poll-submit', $BML::ML{'poll.submit'}) . "</form>\n";;
    }
    
    return $ret;
}

sub find_security
{
    &LJ::nodb;

    my $po = shift;
    my $remote = shift;
    my $sth;

    ## if remote is poll owner, can do anything.
    if ($remote && $remote->{'userid'} == $po->{'posterid'}) {
        return (1, 1);
    }

    ## need to be both a person and with a visible journal to vote
    if ($remote &&
        ($remote->{'journaltype'} ne "P" || $remote->{'statusvis'} ne "V")) {
        return (0, 0);
    }

    my $is_friend = 0;
    if (($po->{'whoview'} eq "friends" || 
         $po->{'whovote'} eq "friends") && $remote)
    {
        $is_friend = LJ::is_friend($po->{'journalid'}, $remote->{'userid'});
    }

    my %sec;
    if ($po->{'whoview'} eq "all" ||
        ($po->{'whoview'} eq "friends" && $is_friend) ||
        ($po->{'whoview'} eq "none" && $remote && $remote->{'userid'} == $po->{'posterid'}))
    {
        $sec{'view'} = 1;
    }

    if ($po->{'whovote'} eq "all" ||
        ($po->{'whovote'} eq "friends" && $is_friend))
    {
        $sec{'vote'} = 1;
    }

    if ($sec{'vote'} && (LJ::is_banned($remote, $po->{'journalid'}) ||
                         LJ::is_banned($remote, $po->{'posterid'}))) {
        $sec{'vote'} = 0;
    }
    
    return ($sec{'vote'}, $sec{'view'});
}

sub submit
{
    &LJ::nodb;
    
    my $remote = shift;
    my $form = shift;
    my $error = shift;
    my $sth;

    my $dbh = LJ::get_db_writer();

    unless ($remote) {
        $$error = $BML::ML{'error.noremote'}; # instead of <?needremote?>, because errors are displayed in LJ::bad_input()
        return 0;
    }

    my $pollid = $form->{'pollid'}+0;
    my $po = $dbh->selectrow_hashref("SELECT itemid, whovote, journalid, posterid, whoview, whovote, name ".
                                     "FROM poll WHERE pollid=?", undef, $pollid);
    unless ($po) {
        $$error = $BML::ML{'poll.error.nopollid'};
        return 0;	
    }
    
    my ($can_vote, undef) = find_security($po, $remote);

    unless ($can_vote) {
        $$error = $BML::ML{'poll.error.cantvote'};
        return 0;
    }

    ### load all the questions
    my @qs;
    $sth = $dbh->prepare("SELECT pollqid, type, opts, qtext FROM pollquestion WHERE pollid=?");
    $sth->execute($pollid);
    push @qs, $_ while $_ = $sth->fetchrow_hashref;
    
    foreach my $q (@qs) {
        my $qid = $q->{'pollqid'}+0;
        my $val = $form->{"pollq-$qid"};
        if ($q->{'type'} eq "check") {
            ## multi-selected items are comma separated from htdocs/poll/index.bml
            $val = join(",", sort { $a <=> $b } split(/,/, $val));
        }
        if ($q->{'type'} eq "scale") {
            my ($from, $to, $by) = split(m!/!, $q->{'opts'});
            if ($val < $from || $val > $to) { 
                # bogus! cheating?
                $val = "";
            }
        }
        if ($val ne "") {
            $dbh->do("REPLACE INTO pollresult (pollid, pollqid, userid, value) VALUES (?, ?, ?, ?)",
                     undef, $pollid, $qid, $remote->{'userid'}, $val);
        } else {
            $dbh->do("DELETE FROM pollresult WHERE pollid=? AND pollqid=? AND userid=?",
                     undef, $pollid, $qid, $remote->{'userid'});
        }
    }
    
    ## finally, register the vote happened
    $dbh->do("REPLACE INTO pollsubmission (pollid, userid, datesubmit) VALUES (?, ?, NOW())",
             undef, $pollid, $remote->{'userid'});

    return 1;
}

1;
