87c2867507e6ed00cc87c60963236e8a2c695d7b
2 package IkiWiki
::Plugin
::poll
;
10 hook
(type
=> "getsetup", id
=> "poll", call
=> \
&getsetup
);
11 hook
(type
=> "preprocess", id
=> "poll", call
=> \
&preprocess
);
12 hook
(type
=> "scan", id
=> "poll", call
=> \
&scan
);
13 hook
(type
=> "sessioncgi", id
=> "poll", call
=> \
&sessioncgi
);
28 (?
>(?
:[^\
[\
]]|\
[[^\
[]|\
][^\
]])+)
33 (?
>(?
:[^\
[\
]]|\
[[^\
[]|\
][^\
]])+)
43 my $content = $params{content
};
44 my $prefix = $config{prefix_directives
} ?
"!poll" : "poll";
45 my $type = IkiWiki
::pagetype
($pagesources{$params{page
}});
46 if (defined $type and $type eq "mdwn") {
48 while ($content =~ m{(\\?)\[\[\Q$prefix\E(\s+id="([^"]*)")?\s+($params_re)\s*\]\]}gs) {
49 my ($escape, $poll, $directive) = ($1, $3, $4);
51 $poll = '' unless defined $poll;
52 error
("poll id=`$poll' must match (|[a-z][a-z0-9_-]*) on page=`$params{page}'")
53 unless $poll =~ m/^(|[a-z][a-z0-9_-]*)$/;
55 my $userdir = defined $config{userdir
} ?
$config{userdir
}.'/' : '';
56 while ($directive =~ m/(^|\s+)(\d+)(="([^"]*)")?\s+"?([^"]*)"?/gs) {
57 my ($unknown_votes, $known_votes, $choice) = ($2, $4, $5);
58 my @known_votes = defined $known_votes ?
grep {length $_ > 0} (split(/\s+/, $known_votes)) : ();
60 { unknown_votes
=> $unknown_votes
61 , known_votes
=> \
@known_votes
63 foreach my $user (@known_votes) {
64 my $userpage_best = bestlink
($params{page
}, $user);
65 $userpage_best = (length $userpage_best > 0 ?
$userpage_best : bestlink
($userdir, $user));
66 $userpage_best = (length $userpage_best > 0 ?
$userpage_best : ($user =~ m{/} ?
'/' : $userdir).$user);
68 debug
("userpage_best=$userpage_best");
69 add_link
($params{page
}, $userpage_best);
72 error
("poll id=`$poll' already exists on page=`$params{page}'")
73 if exists $polls{$poll};
74 $polls{$poll} = \
%poll;
76 $IkiWiki::pagestate
{$params{page
}}{poll
} = \
%polls;
87 my $open=IkiWiki
::yesno
($params{open});
88 my $showtotal=IkiWiki
::yesno
($params{total
});
89 my $showpercent=IkiWiki
::yesno
($params{percent
});
90 my $expandable=IkiWiki
::yesno
($params{expandable
});
91 my $num=++$pagenum{$params{page
}}{$params{destpage
}};
97 my $unknown_votes = shift;
98 my $known_votes = shift;
100 unless $unknown_votes =~ /^\d+$/;
101 my @users = $known_votes ?
grep {length $_ > 0} (split(/\s+/, $known_votes)) : ();
104 my $tot = ($unknown_votes + @users);
106 { unknown_votes
=> $unknown_votes
110 push @choices, $choice;
114 my $uri_page = URI
::Escape
::uri_escape_utf8
($params{page
}, '^A-Za-z0-9\-\._~/');
115 my $userdir = defined $config{userdir
} ?
$config{userdir
}.'/' : '';
117 foreach my $choice (@choices) {
118 if ($open && exists $config{cgiurl
}) {
119 # use POST to avoid robots
120 $ret.="<form method=\"POST\" action=\"".IkiWiki
::cgiurl
()."\">\n";
122 $ret.="<dt class='choice'>";
123 my $percent = $total > 0 ?
int($choices{$choice}{total
} / $total * 100) : 0;
124 my $votes = $choices{$choice}{total
};
127 $votes .= " ($percent%)"
129 if (@
{$choices{$choice}{users
}} > 0) {
130 $votes .= " : ".join(', ', map {
132 #my $userpage = linkpage(($config{userdir}?$config{userdir}.'/':'').$_);
133 #htmllink($params{page}, $params{destpage}, $userpage, linktext => pagetitle($_))
134 my $userpage_best = bestlink
($userdir, $user);
135 $userpage_best = (length $userpage_best > 0 ?
$userpage_best : bestlink
($userdir, $user));
136 $userpage_best = (length $userpage_best > 0 ?
$userpage_best : ($user =~ m{/} ?
'/' : $userdir).$user);
141 , noimageinline
=> 1 )
142 } @
{$choices{$choice}{users
}});
143 $votes .= " + ".$choices{$choice}{unknown_votes
}." "
144 . ($choices{$choice}{unknown_votes
} > 1 ? gettext
("unknowns") : gettext
("unknown"))
145 if $choices{$choice}{unknown_votes
};
148 $votes .= " : ".($choices{$choice}{unknown_votes
}." ".gettext
("unknowns"))
149 if $choices{$choice}{unknown_votes
};
151 if ($open && exists $config{cgiurl
}) {
152 my $choice_escaped = URI
::Escape
::uri_escape_utf8
($choice, '^A-Za-z0-9\ \-\._~/');
153 $ret.="<input type=\"hidden\" name=\"do\" value=\"poll\" />\n";
154 $ret.="<input type=\"hidden\" name=\"num\" value=\"$num\" />\n";
155 $ret.="<input type=\"hidden\" name=\"page\" value=\"$uri_page\" />\n";
156 $ret.="<input type=\"hidden\" name=\"choice\" value=\"$choice_escaped\" />\n";
157 $ret.="<input type=\"submit\" value=\"".gettext
("vote")."\" />\n";
159 $ret.="<span class='description'>$choice</span>";
161 $ret.="<dd class='votes'>";
163 $ret.="<hr class='poll' align=left width=\"$percent%\"/>\n";
164 if ($open && exists $config{cgiurl
}) {
170 if ($expandable && $open && exists $config{cgiurl
}) {
172 $ret.="<form method=\"POST\" action=\"".IkiWiki
::cgiurl
()."\">\n";
173 $ret.="<input type=\"hidden\" name=\"do\" value=\"poll\" />\n";
174 $ret.="<input type=\"hidden\" name=\"num\" value=\"$num\" />\n";
175 $ret.="<input type=\"hidden\" name=\"page\" value=\"$uri_page\" />\n";
176 $ret.=gettext
("Write in").": <input name=\"choice\" size=50 />\n";
177 $ret.="<input type=\"submit\" value=\"".gettext
("vote")."\" />\n";
184 return "<dl class='poll'>$ret</dl>";
186 sub sessioncgi
($$) {
189 if (defined $cgi->param('do') && $cgi->param('do') eq "poll") {
190 my $choice = Encode
::decode_utf8
(URI
::Escape
::uri_unescape
(IkiWiki
::possibly_foolish_untaint
($cgi->param('choice'))));
192 if (! defined $choice || not length $choice) {
193 error
("no choice specified");
195 my $num=$cgi->param('num');
196 if (! defined $num) {
197 error
("no num specified");
199 my $page=Encode
::decode_utf8
(URI
::Escape
::uri_unescape
(IkiWiki
::possibly_foolish_untaint
($cgi->param('page'))));
200 if (! defined $page || ! exists $pagesources{$page}) {
202 error
("bad page name");
204 &IkiWiki
::check_canedit
($page, $cgi, $session);
206 # Did they vote before? If so, let them change their vote,
207 # and check for dups.
208 my $choice_param="poll_choice_${page}_$num";
209 my $oldchoice=$session->param($choice_param);
210 #if (defined $oldchoice && $oldchoice eq $choice) {
211 # # Same vote; no-op.
212 # IkiWiki::redirect($cgi, urlto($page));
215 my $prefix=$config{prefix_directives
} ?
"!poll" : "poll";
216 my $content=readfile
(srcfile
($pagesources{$page}));
217 # Now parse the content, find the right poll,
218 # and find the choice within it, and increment its number.
219 # If they voted before, decrement that one.
227 my ($action, $unknown_votes, $known_votes) = @_;
228 my $user = $session->param("name");
230 foreach (split(/\s+/, $known_votes)) {
234 if ($action eq 'add') {
236 if (exists $users{$user} or (defined $oldchoice and $oldchoice eq $choice)) {
237 delete $users{$user};
238 $known_votes = join(' ', sort {lc $a <=> lc $b} (keys %users));
241 $known_votes = join(' ', sort {lc $a <=> lc $b} ($user, keys %users));
248 elsif ($action eq 'del') {
250 if (exists $users{$user}) {
251 delete $users{$user};
252 $known_votes = join(' ', sort {lc $a <=> lc $b} (keys %users));
256 $unknown_votes = ($unknown_votes-1 >=0 ?
$unknown_votes-1 : 0);
259 return $unknown_votes.($known_votes?
"=\"$known_votes\"":"")
261 if ($params=~s/(^|\s+)(\d+)(="([^"]*)")?(\s+)"?\Q$choice\E"?(\s+|$)/$1.$vote->('add', $2, $4)."$5\"$choice\"".$6/es) {
263 elsif ($params=~/expandable=(\w+)/
264 & &IkiWiki
::yesno
($1)) {
265 $choice=~s/["\]\n\r]//g;
266 $params.=" 1 \"$choice\""
269 if (defined $oldchoice and not ($oldchoice eq $choice)
270 and not ($params=~/multiple=(\w+)/ & &IkiWiki
::yesno
($1))) {
271 $params=~s/(^|\s+)(\d+)(="([^"]*)")?(\s+)"?\Q$oldchoice\E"?(\s+|$)/$1.$vote->('del', $2, $4)."$5\"$oldchoice\"".$6/es;
281 (?
:(?
<id_space
>\s
+)id
="(?<id>[^"]*)")?
283 (?<params>$params_re)
290 .($+{id} eq ''?'':$+{id_space}.'id="'.$+{id}.'"')
292 .$edit->($+{escape}, $+{params})
297 # Store their vote, update the page, and redirect to it.
298 writefile($pagesources{$page}, $config{srcdir}, $content);
299 if (defined $oldchoice and $choice eq $oldchoice) {
300 $session->param($choice_param, undef);
301 # TOTRY: $session->clear($choice_param);
304 $session->param($choice_param, $choice);
306 IkiWiki::cgi_savesession($session);
308 IkiWiki::disable_commit_hook();
310 ( file => $pagesources{$page}
311 , message => "poll vote
: id
=$id: $choice"
312 , session => $session
313 , token => IkiWiki::rcs_prepedit($pagesources{$page})
315 IkiWiki::enable_commit_hook();
316 IkiWiki::rcs_update();
318 require IkiWiki::Render;
320 IkiWiki::saveindex();
321 # Need to set cookie in same http response that does the redir.
322 eval q{use CGI::Cookie};
324 my $cookie = CGI
::Cookie
->new
325 ( -name
=> $session->name
326 , -value
=> $session->id );
329 , -url
=> urlto
($page) );
333 package IkiWiki
::PageSpec
;
334 sub match_poll
($$;@
) {
335 my ($page, $match, %params) = @_;
336 my $polls = $IkiWiki::pagestate
{$page}{poll
};
337 if (defined $polls and %$polls) {
338 my ($match_id, $match_user, $match_choice) = $match =~ m/^id=(.*?) user=(.*?) choice=(.*?)$/;
339 my $match_id_re = IkiWiki
::glob2re
($match_id?
$match_id:'*');
340 my @polls = grep {$_ =~ $match_id_re} (keys %$polls);
341 return IkiWiki
::FailReason
->new("no poll match id=`$match_id'", $page => $IkiWiki::DEPEND_CONTENT
)
343 foreach my $poll (@polls) {
344 my %poll = %{$polls->{$poll}};
345 my $match_user_re = IkiWiki
::glob2re
($match_user?
$match_user:'*');
346 my $match_choice_re = IkiWiki
::glob2re
($match_choice?
$match_choice:'*');
347 while (my ($choice, $data) = each %poll) {
348 next unless $choice =~ $match_choice_re;
349 if ($match_user eq '') {
350 if ($data->{unknown_votes
} > 0) {
351 return IkiWiki
::SuccessReason
->new("unkown user has voted for choice=`$choice'", $page => $IkiWiki::DEPEND_CONTENT
);
354 return IkiWiki
::FailReason
->new("no unkown user has voted for choice=`$choice'", $page => $IkiWiki::DEPEND_CONTENT
);
358 foreach my $user (@
{$data->{known_votes
}}) {
359 next unless $user =~ $match_user_re;
360 return IkiWiki
::SuccessReason
->new("user=`$user' has voted for choice=`$choice'", $page => $IkiWiki::DEPEND_CONTENT
);
365 return IkiWiki
::FailReason
->new("no user=`$match_user' has voted for choice=`$match_choice'", $page => $IkiWiki::DEPEND_CONTENT
);
368 return IkiWiki
::FailReason
->new("no poll", $page => $IkiWiki::DEPEND_CONTENT
);