From d5fce9d5efc06ee69f7abce3fe64b7c144c9b6c3 Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Tue, 11 Feb 2014 04:30:54 +0100 Subject: [PATCH] add user names in polls and poll() pagespec to match against --- poll.pm | 323 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 229 insertions(+), 94 deletions(-) diff --git a/poll.pm b/poll.pm index 3bd4af2..612b49d 100644 --- a/poll.pm +++ b/poll.pm @@ -7,94 +7,151 @@ use IkiWiki 3.00; use Encode; sub import { - hook(type => "getsetup", id => "poll", call => \&getsetup); + hook(type => "getsetup", id => "poll", call => \&getsetup); hook(type => "preprocess", id => "poll", call => \&preprocess); + hook(type => "scan", id => "poll", call => \&scan); hook(type => "sessioncgi", id => "poll", call => \&sessioncgi); -} - -sub getsetup () { - return - plugin => { - safe => 1, - rebuild => undef, - section => "widget", - }, -} + } my %pagenum; +sub getsetup () { + return + plugin => + { safe => 1 + , rebuild => undef + , section => "widget" + }; + } +sub scan (@) { + my %params = @_; + my $page = $params{page}; + my $content = $params{content}; + my $prefix = $config{prefix_directives} ? "!poll" : "poll"; + my $type = IkiWiki::pagetype($pagesources{$page}); + if (defined $type and $type eq "mdwn") { + my %polls = (); + while ($content =~ m{(\\?)\[\[\Q$prefix\E(\s+id="([^"]*)")?\s+(.+?)\s*\]\]}gs) { + my ($escape, $poll, $directive) = ($1, $3, $4); + next if $escape; + $poll = '' unless defined $poll; + error("poll id=`$poll' must match (|[a-z][a-z0-9_-]*) on page=`$page'") + unless $poll =~ m/^(|[a-z][a-z0-9_-]*)$/; + my %poll = (); + while ($directive =~ m/(^|\s+)(\d+)(="([^"]*)")?\s+"?([^"]*)"?/gs) { + my ($unknown_votes, $known_votes, $choice) = ($2, $4, $5); + my @known_votes = defined $known_votes ? split(/\s+/, $known_votes) : (); + $poll{$choice} = + { unknown_votes => $unknown_votes + , known_votes => \@known_votes + }; + foreach my $user (@known_votes) { + my $userpage = linkpage(($config{userdir}?$config{userdir}.'/':'').$user); + add_link($page, $userpage); + } + } + error("poll id=`$poll' already exists on page=`$page'") + if exists $polls{$poll}; + $polls{$poll} = \%poll; + } + $IkiWiki::pagestate{$page}{poll} = \%polls; + } + } sub preprocess (@) { - my %params=(open => "yes", total => "yes", percent => "yes", - expandable => "no", @_); - + my %params= + ( open => "yes" + , total => "yes" + , percent => "yes" + , expandable => "no" + , @_ ); + my $open=IkiWiki::yesno($params{open}); my $showtotal=IkiWiki::yesno($params{total}); my $showpercent=IkiWiki::yesno($params{percent}); my $expandable=IkiWiki::yesno($params{expandable}); my $num=++$pagenum{$params{page}}{$params{destpage}}; - + my %choices; my @choices; my $total=0; while (@_) { - my $key=shift; - my $value=shift; - - next unless $key =~ /^\d+/; - - my $num=$key; - $key=shift; - $value=shift; - - $choices{$key}=$num; - push @choices, $key; - $total+=$num; - } - + my $unknown_votes = shift; + my $known_votes = shift; + next + unless $unknown_votes =~ /^\d+$/; + my @users = $known_votes ? split(/\s+/, $known_votes) : (); + my $choice = shift; + shift; + my $tot = ($unknown_votes + @users); + $choices{$choice} = + { unknown_votes => $unknown_votes + , users => \@users + , total => $tot + }; + push @choices, $choice; + $total += $tot; + } + use URI::Escape; + my $uri_page = URI::Escape::uri_escape_utf8($params{page}, '^A-Za-z0-9\-\._~/'); my $ret=""; foreach my $choice (@choices) { if ($open && exists $config{cgiurl}) { # use POST to avoid robots $ret.="
\n"; - } - my $percent=$total > 0 ? int($choices{$choice} / $total * 100) : 0; - $ret.="

\n"; - if ($showpercent) { - $ret.="$choice ($percent%)\n"; - } + } + $ret.="

"; + my $percent = $total > 0 ? int($choices{$choice}{total} / $total * 100) : 0; + my $votes = $choices{$choice}{total}; + $votes .= '/'.$total + if $showtotal; + $votes .= " ($percent%)" + if $showpercent; + if (@{$choices{$choice}{users}} > 0) { + $votes .= " : ".join(', ', map { + my $userpage = linkpage(($config{userdir}?$config{userdir}.'/':'').$_); + htmllink($params{page}, $params{destpage}, '/'.$userpage, linktext => pagetitle($_)) + } @{$choices{$choice}{users}}); + $votes .= " + ".$choices{$choice}{unknown_votes}." " + . ($choices{$choice}{unknown_votes} > 1 ? gettext("unknowns") : gettext("unknown")) + if $choices{$choice}{unknown_votes}; + } else { - $ret.="$choice ($choices{$choice})\n"; - } + $votes .= " : ".($choices{$choice}{unknown_votes}." ".gettext("unknowns")) + if $choices{$choice}{unknown_votes}; + } if ($open && exists $config{cgiurl}) { $ret.="\n"; $ret.="\n"; - $ret.="\n"; + $ret.="\n"; $ret.="\n"; $ret.="\n"; - } - $ret.="

\n
\n"; + } + $ret.="$choice"; + $ret.="
"; + $ret.="
"; + $ret.=$votes; + $ret.="
\n"; if ($open && exists $config{cgiurl}) { $ret.="
\n"; - } - } + } + $ret.="\n"; + } if ($expandable && $open && exists $config{cgiurl}) { - $ret.="

\n"; + $ret.="

"; $ret.="
\n"; $ret.="\n"; $ret.="\n"; - $ret.="\n"; + $ret.="\n"; $ret.=gettext("Write in").": \n"; $ret.="\n"; + $ret.="
\n"; + $ret.="
"; + $ret.="
\n"; $ret.="\n"; $ret.="

\n"; - } - - if ($showtotal) { - $ret.="".gettext("Total votes:")." $total\n"; - } - return "
$ret
"; -} - + } + return "
$ret
"; + } sub sessioncgi ($$) { my $cgi=shift; my $session=shift; @@ -102,28 +159,27 @@ sub sessioncgi ($$) { my $choice=decode_utf8($cgi->param('choice')); if (! defined $choice || not length $choice) { error("no choice specified"); - } + } my $num=$cgi->param('num'); if (! defined $num) { error("no num specified"); - } - my $page=IkiWiki::possibly_foolish_untaint($cgi->param('page')); + } + my $page=Encode::decode_utf8(URI::Escape::uri_unescape(IkiWiki::possibly_foolish_untaint($cgi->param('page')))); if (! defined $page || ! exists $pagesources{$page}) { + use Data::Dumper; error("bad page name"); - } - + } + # Did they vote before? If so, let them change their vote, # and check for dups. my $choice_param="poll_choice_${page}_$num"; my $oldchoice=$session->param($choice_param); - if (defined $oldchoice && $oldchoice eq $choice) { - # Same vote; no-op. - IkiWiki::redirect($cgi, urlto($page)); - exit; - } - + #if (defined $oldchoice && $oldchoice eq $choice) { + # # Same vote; no-op. + # IkiWiki::redirect($cgi, urlto($page)); + # exit; + # } my $prefix=$config{prefix_directives} ? "!poll" : "poll"; - my $content=readfile(srcfile($pagesources{$page})); # Now parse the content, find the right poll, # and find the choice within it, and increment its number. @@ -131,53 +187,132 @@ sub sessioncgi ($$) { my $edit=sub { my $escape=shift; my $params=shift; - return "\\[[$prefix $params]]" if $escape; + return $params + if $escape; if (--$num == 0) { - if ($params=~s/(^|\s+)(\d+)\s+"?\Q$choice\E"?(\s+|$)/$1.($2+1)." \"$choice\"".$3/se) { - } + my $vote = sub { + my ($action, $unknown_votes, $known_votes) = @_; + my $user = $session->param("name"); + my %users; + foreach (split(/\s+/, $known_votes)) { + $users{$_} = 1; + } + if ($action eq 'add') { + if (defined $user) { + if (exists $users{$user} or (defined $oldchoice and $oldchoice eq $choice)) { + delete $users{$user}; + $known_votes = join(' ', sort {lc $a <=> lc $b} (keys %users)); + } + else { + $known_votes = join(' ', sort {lc $a <=> lc $b} ($user, keys %users)); + } + } + else { + $unknown_votes += 1; + } + } + elsif ($action eq 'del') { + if (defined $user) { + if (exists $users{$user}) { + delete $users{$user}; + $known_votes = join(' ', sort {lc $a <=> lc $b} (keys %users)); + } + } + else { + $unknown_votes = ($unknown_votes-1 >=0 ? $unknown_votes-1 : 0); + } + } + return $unknown_votes.($known_votes?"=\"$known_votes\"":"") + }; + if ($params=~s/(^|\s+)(\d+)(="([^"]*)")?(\s+)"?\Q$choice\E"?(\s+|$)/$1.$vote->('add', $2, $4)."$5\"$choice\"".$6/es) { + } elsif ($params=~/expandable=(\w+)/ & &IkiWiki::yesno($1)) { $choice=~s/["\]\n\r]//g; $params.=" 1 \"$choice\"" if length $choice; - } - if (defined $oldchoice) { - $params=~s/(^|\s+)(\d+)\s+"?\Q$oldchoice\E"?(\s+|$)/$1.($2-1 >=0 ? $2-1 : 0)." \"$oldchoice\"".$3/se; - } - } - return "[[$prefix $params]]"; - }; - $content =~ s{(\\?)\[\[\Q$prefix\E\s+([^]]+)\s*\]\]}{$edit->($1, $2)}seg; - + } + if (defined $oldchoice and not ($oldchoice eq $choice)) { + $params=~s/(^|\s+)(\d+)(="([^"]*)")?(\s+)"?\Q$oldchoice\E"?(\s+|$)/$1.$vote->('del', $2, $4)."$5\"$oldchoice\"".$6/es; + } + } + return "$params"; + }; + my $id=''; + $content =~ s{(\\?)\[\[\Q$prefix\E(\s+id="([^"]*)")?(\s+)(.+?)(\s*)\]\]}{$id=$3;$1.'[['.$prefix.$2.$4.$edit->($1, $5).$6.']]'}gse; + # Store their vote, update the page, and redirect to it. writefile($pagesources{$page}, $config{srcdir}, $content); - $session->param($choice_param, $choice); + if (defined $oldchoice and $choice eq $oldchoice) { + $session->param($choice_param, undef); + # TOTRY: $session->clear($choice_param); + } + else { + $session->param($choice_param, $choice); + } IkiWiki::cgi_savesession($session); - $oldchoice=$session->param($choice_param); if ($config{rcs}) { IkiWiki::disable_commit_hook(); - IkiWiki::rcs_commit( - file => $pagesources{$page}, - message => "poll vote ($choice)", - token => IkiWiki::rcs_prepedit($pagesources{$page}), - session => $session, - ); + IkiWiki::rcs_commit + ( file => $pagesources{$page} + , message => "poll vote: id=$id: $choice" + , session => $session + , token => IkiWiki::rcs_prepedit($pagesources{$page}) + ); IkiWiki::enable_commit_hook(); IkiWiki::rcs_update(); - } + } require IkiWiki::Render; IkiWiki::refresh(); IkiWiki::saveindex(); - - # Need to set cookie in same http response that does the - # redir. + # Need to set cookie in same http response that does the redir. eval q{use CGI::Cookie}; error($@) if $@; - my $cookie = CGI::Cookie->new(-name=> $session->name, -value=> $session->id); - print $cgi->redirect(-cookie => $cookie, - -url => urlto($page)); + my $cookie = CGI::Cookie->new + ( -name => $session->name + , -value => $session->id ); + print $cgi->redirect + ( -cookie => $cookie + , -url => urlto($page) ); exit; - } -} + } + } +package IkiWiki::PageSpec; + sub match_poll ($$;@) { + my ($page, $match, %params) = @_; + my $polls = $IkiWiki::pagestate{$page}{poll}; + if (defined $polls and %$polls) { + my ($match_poll, $match_user, $match_choice) = $match =~ m/^id=(.*?) user=(.*?) choice=(.*?)$/; + if (exists $polls->{$match_poll}) { + my %poll = %{$polls->{$match_poll}}; + my $match_user_re = IkiWiki::glob2re($match_user?$match_user:'*'); + my $match_choice_re = IkiWiki::glob2re($match_choice?$match_choice:'*'); + while (my ($choice, $data) = each %poll) { + next unless $choice =~ $match_choice_re; + if ($match_user eq '') { + if ($data->{unknown_votes} > 0) { + return IkiWiki::SuccessReason->new("unkown user has voted for choice=`$choice'", $page => $IkiWiki::DEPEND_CONTENT); + } + else { + return IkiWiki::FailReason->new("no unkown user has voted for choice=`$choice'", $page => $IkiWiki::DEPEND_CONTENT); + } + } + else { + foreach my $user (@{$data->{known_votes}}) { + next unless $user =~ $match_user_re; + return IkiWiki::SuccessReason->new("user=`$user' has voted for choice=`$choice'", $page => $IkiWiki::DEPEND_CONTENT); + } + } + } + return IkiWiki::FailReason->new("no user=`$match_user' has voted for choice=`$match_choice'", $page => $IkiWiki::DEPEND_CONTENT); + } + else { + return IkiWiki::FailReason->new("no poll id=`$match_poll'", $page => $IkiWiki::DEPEND_CONTENT); + } + } + else { + return IkiWiki::FailReason->new("no poll", $page => $IkiWiki::DEPEND_CONTENT); + } + } -1 +1; -- 2.20.1