From 710df2e42bb5558a611577ee6f374b4450b37f84 Mon Sep 17 00:00:00 2001 From: morkt Date: Sun, 18 Oct 2015 12:31:16 +0400 Subject: discussion board polls. --- data/js/polls.js | 26 ++++++++++++++ lib/VNDB/DB/Polls.pm | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) create mode 100644 data/js/polls.js create mode 100644 lib/VNDB/DB/Polls.pm diff --git a/data/js/polls.js b/data/js/polls.js new file mode 100644 index 00000000..b34f0f7b --- /dev/null +++ b/data/js/polls.js @@ -0,0 +1,26 @@ +function addPoll() { + var a = byId('poll_add'); + setClass(a, 'hidden', false); + var parentNode = function(n, tag) { + while(n && n.nodeName.toLowerCase() != tag) + n = n.parentNode; + return n; + }; + var show = function(v) { + setClass(parentNode(byId('poll_q'), 'tr'), 'hidden', !v); + setClass(parentNode(byId('poll_opt'), 'tr'), 'hidden', !v); + setClass(parentNode(byId('poll_max'), 'tr'), 'hidden', !v); + setClass(parentNode(byId('poll_preview'),'tr'), 'hidden', !v); + setClass(parentNode(byId('poll_recast'), 'tr'), 'hidden', !v); + setClass(parentNode(a, 'tr'), 'hidden', v); + }; + a.onclick = function() { + show(true); + return true; + }; + show(false); +} + +// Discussion board polls +if(byId('poll_add')) + addPoll(); diff --git a/lib/VNDB/DB/Polls.pm b/lib/VNDB/DB/Polls.pm new file mode 100644 index 00000000..0b8ff81d --- /dev/null +++ b/lib/VNDB/DB/Polls.pm @@ -0,0 +1,95 @@ + +package VNDB::DB::Polls; + +use strict; +use warnings; +use Exporter 'import'; + +our @EXPORT = qw|dbPollGet dbPollVote dbPollAdd dbPollEdit|; + + +# Options: id, tid, uid, what +# What: votes +sub dbPollGet { + my($self, %o) = @_; + $o{what} ||= ''; + $o{uid} ||= $self->authInfo->{id}; + + my %where = ( + $o{id} ? ('p.id = ?' => $o{id}) : + $o{tid} ? ('p.tid = ?' => $o{tid}) : (), + ); + + my @select = ( + qw|p.id p.question p.max_options p.preview p.recast|, + $o{what} =~ /votes/ ? + ('(SELECT COUNT(DISTINCT uid) FROM polls_votes pv WHERE pv.pid = p.id) AS votes') : (), + ); + my $p = $self->dbRow(q| + SELECT !s + FROM polls p + !W|, + join(', ', @select), \%where + ); + return $p unless %$p; + + my $options_query = $o{what} =~ /votes/ ? + q|SELECT id, option, COUNT(pv.optid) AS votes + FROM polls_options po + LEFT JOIN polls_votes pv ON po.id = pv.optid + WHERE po.pid = ? GROUP BY id ORDER BY id| : + q|SELECT id, option + FROM polls_options po + WHERE po.pid = ? ORDER BY id|; + $p->{options} = $self->dbAll($options_query, $p->{id}); + + $p->{user} = $o{uid} ? [ + map $_->{optid}, @{$self->dbAll(q| + SELECT optid FROM polls_votes + WHERE pid = ? AND uid = ?|, $p->{id}, $o{uid})} + ] : []; + + return $p; +} + + +sub dbPollVote { + my($self, $id, %o) = @_; + + $self->dbExec('DELETE FROM polls_votes WHERE pid = ? AND uid = ?', $id, $o{uid}); + $self->dbExec('INSERT INTO polls_votes (pid, uid, optid) VALUES (?, ?, ?)', + $id, $o{uid}, $_) for @{$o{options}}; +} + + +sub dbPollAdd { + my($self, %o) = @_; + + my $id = $self->dbRow(q| + INSERT INTO polls (tid, question, max_options, preview, recast) + VALUES (?, ?, ?, ?, ?) RETURNING id|, + $o{tid}, $o{question}, $o{max_options}, $o{preview}, $o{recast} + )->{id}; + + $self->dbExec('INSERT INTO polls_options (pid, option) VALUES (?, ?)', $id, $_) + for @{$o{options}}; + + return $id; +} + + +sub dbPollEdit { + my($self, $id, %o) = @_; + + my %set = map exists $o{$_} ? ("$_ = ?" => $o{$_}) : (), + qw|question max_options preview recast|; + + $self->dbExec('UPDATE polls !H WHERE id = ?', \%set, $id); + $self->dbExec('DELETE FROM polls_options WHERE pid = ?', $id); + $self->dbExec('INSERT INTO polls_options (pid, option) VALUES (?, ?)', $id, $_) + for @{$o{options}}; +} + + +1; + -- cgit v1.2.3 From ddbf3ae0ae530954b0e105e99819c85cfe4de17c Mon Sep 17 00:00:00 2001 From: morkt Date: Sun, 18 Oct 2015 12:32:05 +0400 Subject: discussion board polls. --- data/global.pl | 1 + data/js/main.js | 1 + data/lang.txt | 54 ++++++++++++++ data/style.css | 18 +++++ lib/VNDB/DB/Discussions.pm | 2 + lib/VNDB/Handler/Discussions.pm | 160 +++++++++++++++++++++++++++++++++++++++- lib/VNDB/Util/FormHTML.pm | 2 + util/sql/schema.sql | 24 ++++++ 8 files changed, 261 insertions(+), 1 deletion(-) diff --git a/data/global.pl b/data/global.pl index de810926..a11c6869 100644 --- a/data/global.pl +++ b/data/global.pl @@ -115,6 +115,7 @@ our %S; posts => [ 25, 'VNDB Recent Posts', '/t' ], }, staff_roles => [qw|scenario chardesign art music songs director staff|], + poll_options => 20, # max number of options in discussion board polls ); diff --git a/data/js/main.js b/data/js/main.js index dc1d3b27..79a280c1 100644 --- a/data/js/main.js +++ b/data/js/main.js @@ -48,6 +48,7 @@ VARS = /*VARS*/; //include charops.js //include filter.js //include misc.js +//include polls.js // VN editing (/v+/edit) //include vnrel.js diff --git a/data/lang.txt b/data/lang.txt index ccfee14c..4827b981 100644 --- a/data/lang.txt +++ b/data/lang.txt @@ -4568,6 +4568,24 @@ es : Avanzado... tr : Gelişmiş... uk : Розширений режим it : Avanzate... + +:_poll_novote_login +en : You must be logged in to be able to vote. + +:_poll_choose +en : You may choose up to [_1] options + +:_poll_vote +en : Vote + +:_poll_no_votes +en : Nobody voted yet. + +:_poll_results +en : View results + +:_poll_total_votes +en : [_1] [quant,_1,vote,votes] total # Post edit/reply/new thread form @@ -4727,6 +4745,30 @@ es : Mirar [url,/d9.3,d9.3] para ver los formatos permitidos tr : Biçimlendirme kodları için [url,/d9.3,d9.3]'e bakınız. uk : Правила розмітки читай у [url,/d9.3,d9.3]. it : Vedi [url,/d9.3,d9.3] per i codici di formattazione + +:_postedit_form_poll_add +en : Add poll + +:_postedit_form_poll_q +en : Poll question + +:_postedit_form_poll_warning +en : All votes will be reset if any changes to the poll fields are made! + +:_postedit_form_poll_opt +en : Poll options + +:_postedit_form_poll_optmax +en : one per line,[br][_1] max + +:_postedit_form_poll_max +en : Number of options voter is allowed to choose + +:_postedit_form_poll_view +en : Allow users to view poll results before voting + +:_postedit_form_poll_recast +en : Allow users to change their vote # Browsing threads by board (/t/{board_id}) @@ -4980,6 +5022,9 @@ es : Último mensaje tr : Son gönderi uk : Останнє повідомлення it : Ultimo messaggio + +:_threadlist_poll +en : poll @@ -16225,6 +16270,9 @@ es : Personaje principal inválido. Asgúrate que la ID es correcta, que el pers tr : Geçersiz ana karakter. ID'nin doğruluğundan, ana karakterin bir başka karakterin örneği olmadığından, ve bu girdinin başka bir yerde ana karakter olarak kullanılmadığından emin olun. uk : Неправильний головний герой. Переконайтеся у правильності ID, головний персонаж — це не втілення іншого персонажа, і що цей запис не використовується в якості головного персонажа десь ще. it : Personaggio principale non valido. Assicurati che ID sia corretto, che il personaggio non sia un'instanza di un altro personaggio, e che questa pagina non è utilizzata come personaggio principale da un'altra parte. + +:_formerr_e_poll +en : Inappropriate number of options in a poll. :_formerr_title en : Error @@ -16261,6 +16309,12 @@ es : ¡[_1] es un campo requerido! tr : [_1] alanı gereklidir! uk : [_1] — обов’язкове поле! it : [_1] è un campo obbligatorio! + +:_formerr_min +en : [_1]: minimum number is [_2] + +:_formerr_max +en : [_1]: maximum number is [_2] :_formerr_minlength en : [_1]: should have at least [_2] characters diff --git a/data/style.css b/data/style.css index 736fb910..5cc054ef 100644 --- a/data/style.css +++ b/data/style.css @@ -314,6 +314,7 @@ div.mainbox.discussions b.boards a { color: $grayedout$; } div.discussions td.tc2 { width: 50px; } div.discussions td.tc3 { width: 90px; } div.discussions td.tc4 { width: 170px; } +div.discussions .pollflag { color: $grayedout$; padding-right: 6px; } div.postsearch td.tc1_1 { width: 60px; padding-left: 0; padding-right: 0; text-align: right } div.postsearch td.tc1_2 { width: 25px; padding-left: 0 } div.postsearch td.tc2 { width: 65px; } @@ -401,6 +402,23 @@ div#vntags { margin: 15px 30px 0 30px; border-top: 1px solid +/***** Polls ****/ + +.votebooth thead td { font-weight: normal; background: transparent; padding-bottom: 5px; } +.votebooth tfoot td { padding-top: 5px } +.votebooth td { vertical-align: middle; padding: 0 8px; } +.votebooth { margin: 0 30px } +.votebooth td.tc1 { padding-right: 20px } +.votebooth td.tc2 { min-width: 220px } +.votebooth td.tc2 div { margin: 2px; } +.votebooth td.tc2 div.graph { float: left; height: 14px; background-color: $border$; padding: 0; } +.votebooth td.tc3 { text-align: right; padding-right: 16px; } +.votebooth .submit { width: 100px } +.votebooth .option { margin-left: 8px } +.votebooth .option.own { font-weight: bold } + + + /***** VN edit *****/ #jt_box_vn_rel table { margin-bottom: 10px; } diff --git a/lib/VNDB/DB/Discussions.pm b/lib/VNDB/DB/Discussions.pm index 2be975db..2af85c4e 100644 --- a/lib/VNDB/DB/Discussions.pm +++ b/lib/VNDB/DB/Discussions.pm @@ -41,6 +41,7 @@ sub dbThreadGet { qw|t.id t.title t.count t.locked t.hidden|, $o{what} =~ /firstpost/ ? ('tpf.uid AS fuid', q|EXTRACT('epoch' from tpf.date) AS fdate|, 'uf.username AS fusername') : (), $o{what} =~ /lastpost/ ? ('tpl.uid AS luid', q|EXTRACT('epoch' from tpl.date) AS ldate|, 'ul.username AS lusername') : (), + 'p.id AS poll', ); my @join = ( @@ -54,6 +55,7 @@ sub dbThreadGet { ) : (), $o{type} && $o{iid} ? 'JOIN threads_boards tb ON tb.tid = t.id' : (), + 'LEFT JOIN polls p ON p.tid = t.id', ); my $order = sprintf { diff --git a/lib/VNDB/Handler/Discussions.pm b/lib/VNDB/Handler/Discussions.pm index 6029deae..5b8c7418 100644 --- a/lib/VNDB/Handler/Discussions.pm +++ b/lib/VNDB/Handler/Discussions.pm @@ -6,10 +6,12 @@ use warnings; use TUWF ':html', 'xml_escape', 'uri_escape'; use POSIX 'ceil'; use VNDB::Func; +use List::Util qw(first max); TUWF::register( qr{t([1-9]\d*)(?:/([1-9]\d*))?} => \&thread, + qr{t([1-9]\d*)(/[1-9]\d*)?/vote} => \&vote, qr{t([1-9]\d*)\.([1-9]\d*)} => \&redirect, qr{t/(all|db|an|ge|[vpu])([1-9]\d*)?} => \&board, qr{t([1-9]\d*)/reply} => \&edit, @@ -37,6 +39,8 @@ sub thread { my $p = $self->dbPostGet(tid => $tid, results => 25, page => $page, what => 'user'); return $self->resNotFound if !$p->[0]; + my $poll = $self->dbPollGet(id => $t->{poll}, what => 'votes') if $t->{poll}; + $self->htmlHeader(title => $t->{title}, noindex => 1); div class => 'mainbox'; h1 $t->{title}; @@ -56,6 +60,8 @@ sub thread { end; end 'div'; + _poll($self, "/t$tid".($page > 1 ? "/$page" : ''), $poll) if $poll; + $self->htmlBrowseNavigate("/t$tid/", $page, [ $t->{count}, 25 ], 't', 1); div class => 'mainbox thread'; table class => 'stripe'; @@ -154,6 +160,8 @@ sub edit { my $p = $num && $self->dbPostGet(tid => $tid, num => $num, what => 'user')->[0]; return $self->resNotFound if $num && !$p->{num}; + $t->{poll} = $self->dbPollGet(id => $t->{poll}) if $tid && $t->{poll}; + # are we allowed to perform this action? return $self->htmlDenied if !$self->authCan('board') || ($tid && ($t->{locked} || $t->{hidden}) && !$self->authCan('boardmod')) @@ -167,6 +175,11 @@ sub edit { !$tid || $num == 1 ? ( { post => 'title', maxlength => 50 }, { post => 'boards', maxlength => 50 }, + { post => 'poll_q', required => 0, maxlength => 100 }, + { post => 'poll_opt', required => 0, maxlength => 100*$self->{poll_options} }, + { post => 'poll_max', required => 0, default => 1, min => 1, max => $self->{poll_options} }, + { post => 'poll_preview', required => 0 }, + { post => 'poll_recast', required => 0 }, ) : (), $self->authCan('boardmod') ? ( { post => 'locked', required => 0 }, @@ -205,6 +218,22 @@ sub edit { } } + # validate poll options + my %poll; + if(!$frm->{_err} && $frm->{poll_opt}) { + # split by lines, trimming whitespace + my @options = split /\s*\n\s*/, $frm->{poll_opt}; + %poll = ( + question => $frm->{poll_q}, + preview => $frm->{poll_preview}?1:0, + recast => $frm->{poll_recast}?1:0, + options => \@options, + max_options => $frm->{poll_max}, + ); + push @{$frm->{_err}}, [ 'poll_max', 'max', scalar @options ] if @options > 1 && @options < $poll{max_options}; + push @{$frm->{_err}}, 'poll' if @options > $self->{poll_options} || @options < 2; + } + if(!$frm->{_err}) { my($ntid, $nnum) = ($tid, $num); @@ -218,6 +247,16 @@ sub edit { ); $self->dbThreadEdit($tid, %thread) if $tid; $ntid = $self->dbThreadAdd(%thread) if !$tid; + if(%poll) { + $poll{tid} = $ntid; + if($tid && $t->{poll}) { + my $same = (!first { !($t->{poll}{$_} ~~ $poll{$_}) } qw|question preview recast max_options|) + && [ map $_->{option}, @{$t->{poll}{options}} ] ~~ $poll{options}; + $self->dbPollEdit($t->{poll}{id}, %poll) unless $same; + } else { + $self->dbPollAdd(%poll); + } + } } # create/edit post @@ -242,10 +281,18 @@ sub edit { $frm->{title} ||= $t->{title}; $frm->{locked} = $t->{locked} if !exists $frm->{locked}; $frm->{hidden} = $t->{hidden} if !exists $frm->{hidden}; + if($t->{poll}) { + $frm->{poll_q} ||= $t->{poll}{question}; + $frm->{poll_max} ||= $t->{poll}{max_options}; + $frm->{poll_preview} = $t->{poll}{preview} if !exists $frm->{poll_preview}; + $frm->{poll_recast} = $t->{poll}{recast} if !exists $frm->{poll_recast}; + $frm->{poll_opt} ||= join "\n", map $_->{option}, @{$t->{poll}{options}}; + } } } delete $frm->{_err} unless ref $frm->{_err}; $frm->{boards} ||= $board; + $frm->{poll_max} ||= 1; # generate html my $url = !$tid ? "/t/$board/new" : !$num ? "/t$tid/reply" : "/t$tid.$num/edit"; @@ -273,11 +320,53 @@ sub edit { ) : (), [ text => name => mt('_postedit_form_msg').'
'.mt('_inenglish').'', short => 'msg', rows => 25, cols => 75 ], [ static => content => mt('_postedit_form_msg_format') ], + (!$tid || $num == 1) ? ( + [ input => short => 'poll_q', name => mt('_postedit_form_poll_q'), width => 250 ], + $num && $frm->{poll_opt} ? ( + [ static => content => ''.mt('_postedit_form_poll_warning').'' ] + ) : (), + [ text => short => 'poll_opt', name => mt('_postedit_form_poll_opt').'
'.mt('_postedit_form_poll_optmax', $self->{poll_options}).'', rows => 8, cols => 35 ], + [ input => short => 'poll_max', width => 16, post => ' '.mt('_postedit_form_poll_max') ], + [ check => short => 'poll_preview', name => mt('_postedit_form_poll_view') ], + [ check => short => 'poll_recast', name => mt('_postedit_form_poll_recast') ], + !$frm->{poll_opt} ? ( + [ static => content => '
' ] + ) : (), + ) : (), ]); $self->htmlFooter; } +sub vote { + my($self, $tid, $page) = @_; + return $self->htmlDenied if !$self->authCan('board'); + + my $f = $self->formValidate( + { post => 'option', multi => 1, template => 'int' } + ); + return $self->resNotFound if $f->{_err}; + + my $url = '/t'.$tid.($page//''); + my $poll = $self->dbPollGet(tid => $tid); + return $self->resNotFound if !%$poll || @{$f->{option}} > $poll->{max_options}; + + # user has already voted and poll doesn't allow to change a vote. + return $self->resRedirect($url, 'post') if @{$poll->{user}} && !$poll->{recast}; + + my %options = map +($_->{id} => 1), @{$poll->{options}}; + # validate user choice. + my %choices; + for(@{$f->{option}}) { + return $self->resNotFound if !exists $options{$_}; + $choices{$_} = 1; + } + + $self->dbPollVote($poll->{id}, uid => $self->authInfo->{id}, options => [ keys %choices ]) if %choices; + $self->resRedirect($url, 'post'); +} + + sub board { my($self, $type, $iid) = @_; $iid ||= ''; @@ -510,7 +599,10 @@ sub _threadlist { my($self, $n, $o) = @_; Tr; td class => 'tc1'; - a $o->{locked} ? ( class => 'locked' ) : (), href => "/t$o->{id}", shorten $o->{title}, 50; + a $o->{locked} ? ( class => 'locked' ) : (), href => "/t$o->{id}"; + span class => 'pollflag', '['.mt('_threadlist_poll').']' if $o->{poll}; + txt shorten $o->{title}, 50; + end; b class => 'boards'; my $i = 1; my @boards = sort { $a->{type}.$a->{iid} cmp $b->{type}.$b->{iid} } grep $_->{type}.($_->{iid}||'') ne $board, @{$o->{boards}}; @@ -541,5 +633,71 @@ sub _threadlist { } +sub _poll { + my($self, $url, $poll) = @_; + my %own_votes = map +($_ => 1), @{$poll->{user}} if @{$poll->{user}}; + my $preview = !%own_votes && $self->reqGet('pollview') && $poll->{preview}; + + div class => 'mainbox poll'; + form action => $url.'/vote', method => 'post'; + h1 class => 'question', $poll->{question} if $poll->{question}; + table class => 'votebooth'; + if(!$self->authCan('board')) { + tfoot; Tr; td class => 'tc1', colspan => 3; + b class => 'standout', mt('_poll_novote_login'); + end; end; end; + } else { + my $allow_vote = !%own_votes || $poll->{recast}; + if($allow_vote && $poll->{max_options} > 1) { + thead; Tr; td colspan => 3; + i mt('_poll_choose', $poll->{max_options}); + end; end; end; + } + tfoot; Tr; + td class => 'tc1'; + input type => 'submit', class => 'submit', value => mt('_poll_vote') if $allow_vote; + end; + td class => 'tc2', colspan => 2; + if($poll->{preview} || %own_votes) { + if(!$poll->{votes}) { + i mt('_poll_no_votes'); + } elsif(!$preview && !%own_votes) { + a href => $url.'?pollview=1', id => 'pollpreview', mt('_poll_results'); + } else { + txt mt('_poll_total_votes', $poll->{votes}); + } + } + end; + end; end; + } + tbody; + my $max = max map $_->{votes}, @{$poll->{options}}; + my $show_graph = $max && (%own_votes || $preview); + my $graph_width = 200; + for my $opt (@{$poll->{options}}) { + my $own = exists $own_votes{$opt->{id}} ? ' own' : ''; + Tr $own ? (class => 'odd') : (); + td class => 'tc1'; + input type => $poll->{max_options} > 1 ? 'checkbox' : 'radio', name => 'option', class => 'option', value => $opt->{id}, $own ? (checked => '') : () if !%own_votes || $poll->{recast}; + span class => 'option'.$own, $opt->{option}; + end; + if($show_graph) { + td class => 'tc2'; + div class => 'graph', style => sprintf('width: %dpx', $opt->{votes}/$max*$graph_width), ' '; + div class => 'number', $opt->{votes}; + end; + td class => 'tc3', sprintf('%.3g%%', $poll->{votes} ? $opt->{votes}/$poll->{votes}*100 : 0); + } else { + td class => 'tc2', colspan => 2, ''; + } + end; + } + end; + end 'table'; + end 'form'; + end 'div'; +} + + 1; diff --git a/lib/VNDB/Util/FormHTML.pm b/lib/VNDB/Util/FormHTML.pm index 9612f904..22eeb4c5 100644 --- a/lib/VNDB/Util/FormHTML.pm +++ b/lib/VNDB/Util/FormHTML.pm @@ -34,6 +34,8 @@ sub htmlFormError { if($type eq 'required') { li; lit mt $field eq 'editsum' ?'_formerr_tpl_editsum' : '_formerr_required', $field; end; } + li mt '_formerr_min', $field, $rule if $type eq 'min'; + li mt '_formerr_max', $field, $rule if $type eq 'max'; li mt '_formerr_minlength', $field, $rule if $type eq 'minlength'; li mt '_formerr_maxlength', $field, $rule if $type eq 'maxlength'; li mt '_formerr_enum', $field, join ', ', @$rule if $type eq 'enum'; diff --git a/util/sql/schema.sql b/util/sql/schema.sql index 59240ea9..8dd960be 100644 --- a/util/sql/schema.sql +++ b/util/sql/schema.sql @@ -528,3 +528,27 @@ CREATE TABLE wlists ( added timestamptz NOT NULL DEFAULT NOW(), PRIMARY KEY(uid, vid) ); + +CREATE TABLE polls ( + id SERIAL PRIMARY KEY, + tid integer UNIQUE NOT NULL DEFAULT 0, -- references threads + question varchar(100) NOT NULL DEFAULT '', + max_options smallint NOT NULL DEFAULT 1, + preview boolean NOT NULL DEFAULT FALSE, + recast boolean NOT NULL DEFAULT FALSE +); + +CREATE TABLE polls_options ( + id SERIAL PRIMARY KEY, + pid integer NOT NULL REFERENCES polls (id) ON DELETE CASCADE, + option varchar(100) NOT NULL +); + +CREATE TABLE polls_votes ( + pid integer NOT NULL REFERENCES polls (id) ON DELETE CASCADE, + uid integer NOT NULL REFERENCES users (id) ON DELETE CASCADE, + optid integer NOT NULL REFERENCES polls_options (id) ON DELETE CASCADE, + PRIMARY KEY (pid, uid, optid) +); + +ALTER TABLE polls ADD FOREIGN KEY (tid) REFERENCES threads (id) ON DELETE CASCADE; -- cgit v1.2.3 From 842fe0971e8b8c9909893fac6e62f64fb5e021b8 Mon Sep 17 00:00:00 2001 From: morkt Date: Tue, 10 Nov 2015 08:08:10 +0400 Subject: various fixes. --- lib/VNDB/Handler/Discussions.pm | 91 +++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 45 deletions(-) diff --git a/lib/VNDB/Handler/Discussions.pm b/lib/VNDB/Handler/Discussions.pm index 5b8c7418..4d9aba72 100644 --- a/lib/VNDB/Handler/Discussions.pm +++ b/lib/VNDB/Handler/Discussions.pm @@ -177,7 +177,7 @@ sub edit { { post => 'boards', maxlength => 50 }, { post => 'poll_q', required => 0, maxlength => 100 }, { post => 'poll_opt', required => 0, maxlength => 100*$self->{poll_options} }, - { post => 'poll_max', required => 0, default => 1, min => 1, max => $self->{poll_options} }, + { post => 'poll_max', required => 0, default => 1, template => 'uint', min => 1, max => $self->{poll_options} }, { post => 'poll_preview', required => 0 }, { post => 'poll_recast', required => 0 }, ) : (), @@ -341,28 +341,29 @@ sub edit { sub vote { my($self, $tid, $page) = @_; return $self->htmlDenied if !$self->authCan('board'); + return if !$self->authCheckCode; - my $f = $self->formValidate( - { post => 'option', multi => 1, template => 'int' } - ); - return $self->resNotFound if $f->{_err}; - - my $url = '/t'.$tid.($page//''); + my $url = '/t'.$tid.($page ? "/$page" : ''); my $poll = $self->dbPollGet(tid => $tid); - return $self->resNotFound if !%$poll || @{$f->{option}} > $poll->{max_options}; + return $self->resNotFound if !%$poll; # user has already voted and poll doesn't allow to change a vote. return $self->resRedirect($url, 'post') if @{$poll->{user}} && !$poll->{recast}; - my %options = map +($_->{id} => 1), @{$poll->{options}}; - # validate user choice. - my %choices; - for(@{$f->{option}}) { - return $self->resNotFound if !exists $options{$_}; - $choices{$_} = 1; + my $f = $self->formValidate({ + post => 'option', multi => 1, enum => [ map $_->{id}, @{$poll->{options}} ], + }); + if(!$f->{_err} && (!@{$f->{option}} || @{$f->{option}} > $poll->{max_options})) { + push @{$f->{_err}}, 'poll'; + } + if($f->{_err}) { + $self->htmlHeader(title => mt '_poll_error'); + $self->htmlFormError($f, 1); + $self->htmlFooter; + return; } - $self->dbPollVote($poll->{id}, uid => $self->authInfo->{id}, options => [ keys %choices ]) if %choices; + $self->dbPollVote($poll->{id}, uid => $self->authInfo->{id}, options => $f->{option}); $self->resRedirect($url, 'post'); } @@ -641,35 +642,33 @@ sub _poll { div class => 'mainbox poll'; form action => $url.'/vote', method => 'post'; h1 class => 'question', $poll->{question} if $poll->{question}; + input type => 'hidden', name => 'formcode', value => $self->authGetCode($url.'/vote'); table class => 'votebooth'; - if(!$self->authCan('board')) { - tfoot; Tr; td class => 'tc1', colspan => 3; - b class => 'standout', mt('_poll_novote_login'); - end; end; end; - } else { - my $allow_vote = !%own_votes || $poll->{recast}; - if($allow_vote && $poll->{max_options} > 1) { - thead; Tr; td colspan => 3; - i mt('_poll_choose', $poll->{max_options}); - end; end; end; - } - tfoot; Tr; - td class => 'tc1'; - input type => 'submit', class => 'submit', value => mt('_poll_vote') if $allow_vote; - end; - td class => 'tc2', colspan => 2; - if($poll->{preview} || %own_votes) { - if(!$poll->{votes}) { - i mt('_poll_no_votes'); - } elsif(!$preview && !%own_votes) { - a href => $url.'?pollview=1', id => 'pollpreview', mt('_poll_results'); - } else { - txt mt('_poll_total_votes', $poll->{votes}); - } - } - end; - end; end; - } + my $allow_vote = $self->authCan('board') && (!%own_votes || $poll->{recast}); + if($allow_vote && $poll->{max_options} > 1) { + thead; Tr; td colspan => 3; + i mt('_poll_choose', $poll->{max_options}); + end; end; end; + } + tfoot; Tr; + td class => 'tc1'; + input type => 'submit', class => 'submit', value => mt('_poll_vote') if $allow_vote; + if(!$self->authCan('board')) { + b class => 'standout', mt('_poll_novote_login'); + } + end; + td class => 'tc2', colspan => 2; + if($poll->{preview} || %own_votes) { + if(!$poll->{votes}) { + i mt('_poll_no_votes'); + } elsif(!$preview && !%own_votes) { + a href => $url.'?pollview=1', id => 'pollpreview', mt('_poll_results'); + } else { + txt mt('_poll_total_votes', $poll->{votes}); + } + } + end; + end; end; tbody; my $max = max map $_->{votes}, @{$poll->{options}}; my $show_graph = $max && (%own_votes || $preview); @@ -678,8 +677,10 @@ sub _poll { my $own = exists $own_votes{$opt->{id}} ? ' own' : ''; Tr $own ? (class => 'odd') : (); td class => 'tc1'; - input type => $poll->{max_options} > 1 ? 'checkbox' : 'radio', name => 'option', class => 'option', value => $opt->{id}, $own ? (checked => '') : () if !%own_votes || $poll->{recast}; - span class => 'option'.$own, $opt->{option}; + label; + input type => $poll->{max_options} > 1 ? 'checkbox' : 'radio', name => 'option', class => 'option', value => $opt->{id}, $own ? (checked => '') : () if $allow_vote; + span class => 'option'.$own, $opt->{option}; + end; end; if($show_graph) { td class => 'tc2'; -- cgit v1.2.3 From 721f22030f2f87c291bde088bf96648e9cdedc0d Mon Sep 17 00:00:00 2001 From: morkt Date: Tue, 10 Nov 2015 08:11:15 +0400 Subject: added poll error message. --- data/lang.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data/lang.txt b/data/lang.txt index 5cb34a3e..5ebafdeb 100644 --- a/data/lang.txt +++ b/data/lang.txt @@ -4575,6 +4575,9 @@ en : View results :_poll_total_votes en : [_1] [quant,_1,vote,votes] total +:_poll_error +en : Poll error + # Post edit/reply/new thread form -- cgit v1.2.3