| 1 | # UVmenu: menu for interaction with the votetaker |
| 2 | # Used by uvvote.pl, uvcfv.pl, uvcount.pl |
| 3 | |
| 4 | package UVmenu; |
| 5 | |
| 6 | use strict; |
| 7 | use UVconfig; |
| 8 | use UVmessage; |
| 9 | use UVrules; |
| 10 | use vars qw($VERSION); |
| 11 | |
| 12 | use Text::Wrap qw(wrap $columns); |
| 13 | |
| 14 | # Module version |
| 15 | $VERSION = "0.4"; |
| 16 | |
| 17 | ############################################################################## |
| 18 | # Menu for interaction with the votetaker # |
| 19 | # Parameters: votes list and header (references to arrays) # |
| 20 | # Body, Mailadress, Name, Ballot ID, # |
| 21 | # Voting (references to strings) # |
| 22 | # List of newly set fields (reference to array) # |
| 23 | # List of errors to correct (Array-Ref) # |
| 24 | # Return Values: 'w': proceed # |
| 25 | # 'i': ignore (don't save vote) # |
| 26 | ############################################################################## |
| 27 | |
| 28 | sub menu { |
| 29 | my ($votes, $header, $body, $addr, $name, $ballot_id, $voting, $set, $errors) = @_; |
| 30 | my $input = ""; |
| 31 | my $voter_addr = $$addr || ''; |
| 32 | my $voter_name = $$name || ''; |
| 33 | my @newvotes = @$votes; |
| 34 | my $mailonly = 0; |
| 35 | my %errors; |
| 36 | $$ballot_id ||= ''; |
| 37 | |
| 38 | foreach my $error (@$errors) { |
| 39 | |
| 40 | # unrecognized vote: extract group number und display warning |
| 41 | if ($error =~ /^UnrecognizedVote #(\d+)#(.+)$/) { |
| 42 | $errors{UnrecognizedVote} ||= UVmessage::get("MENU_UNRECOGNIZEDVOTE"); |
| 43 | $errors{UnrecognizedVote} .= "\n " . UVmessage::get("MENU_UNRECOGNIZED_LIST") |
| 44 | . " #$1: $2"; |
| 45 | |
| 46 | # violated rule: extract rule number and display warning |
| 47 | } elsif ($error =~ /^ViolatedRule #(\d+)$/) { |
| 48 | $errors{ViolatedRule} ||= UVmessage::get("MENU_VIOLATEDRULE", (RULE => "#$1")); |
| 49 | |
| 50 | } else { |
| 51 | # special handling if called from uvballot.pl |
| 52 | $mailonly = 1 if ($error =~ s/Ballot$//); |
| 53 | |
| 54 | # get error message for this error from messages.cfg |
| 55 | $errors{$error} = UVmessage::get("MENU_" . uc($error)); |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | # This loop is only left by 'return' |
| 60 | while (1) { |
| 61 | |
| 62 | system($config{clearcmd}); |
| 63 | print UVmessage::get("MENU_PROBLEMS") . "\n"; |
| 64 | |
| 65 | foreach my $error (keys %errors) { |
| 66 | print "* $errors{$error}\n"; |
| 67 | } |
| 68 | |
| 69 | my $menucaption = UVmessage::get("MENU_CAPTION"); |
| 70 | print "\n\n$menucaption\n"; |
| 71 | print "=" x length($menucaption), "\n\n"; |
| 72 | |
| 73 | # don't print this option if called from uvcfv.pl |
| 74 | unless ($mailonly) { |
| 75 | print "(0) ", UVmessage::get("MENU_DIFF_BALLOT"), "\n"; |
| 76 | } |
| 77 | |
| 78 | print "(1) ", UVmessage::get("MENU_SHOW_MAIL"), "\n\n", |
| 79 | UVmessage::get("MENU_CHANGE_PROPERTIES"), "\n", |
| 80 | "(2) ", UVmessage::get("MENU_ADDRESS"), " [$voter_addr]\n"; |
| 81 | |
| 82 | # don't print these options if called from uvcfv.pl |
| 83 | unless ($mailonly) { |
| 84 | print "(3) ", UVmessage::get("MENU_NAME"), " [$voter_name]\n"; |
| 85 | print "(4) ", UVmessage::get("MENU_VOTES"), " [", @$votes, "]\n"; |
| 86 | print "(5) ", UVmessage::get("MENU_BALLOT_ID"), " [$$ballot_id]\n" |
| 87 | if ($config{personal}); |
| 88 | print "(6) ", UVmessage::get("MENU_BDSG"), "\n" if ($config{bdsg}); |
| 89 | print "(7) ", UVmessage::get("MENU_VOTING"), " [", $$voting, "]\n"; |
| 90 | } |
| 91 | |
| 92 | print "\n", |
| 93 | "(i) ", UVmessage::get("MENU_IGNORE"), "\n", |
| 94 | "(w) ", UVmessage::get("MENU_PROCEED"), "\n\n", |
| 95 | UVmessage::get("MENU_PROMPT"); |
| 96 | |
| 97 | do { $input = <STDIN>; } until ($input); |
| 98 | chomp $input; |
| 99 | print "\n"; |
| 100 | |
| 101 | # only accept 1, 2, i and w if called from uvcfv.pl |
| 102 | next if ($mailonly && $input !~ /^[12iw]$/i); |
| 103 | |
| 104 | if ($input eq '0') { |
| 105 | # ignore SIGPIPE (Bug in more and less) |
| 106 | $SIG{PIPE} = 'IGNORE'; |
| 107 | open (DIFF, "|$config{diff} - $config{sampleballotfile} | $config{pager}"); |
| 108 | print DIFF $$body, "\n"; |
| 109 | close (DIFF); |
| 110 | |
| 111 | } elsif ($input eq '1') { |
| 112 | system($config{clearcmd}); |
| 113 | # ignore SIGPIPE (Bug in more and less) |
| 114 | $SIG{PIPE} = 'IGNORE'; |
| 115 | open (MORE, "|$config{pager}"); |
| 116 | print MORE join("\n", @$header), "\n\n", $$body, "\n"; |
| 117 | close (MORE); |
| 118 | |
| 119 | print "\n", UVmessage::get("MENU_GETKEY"); |
| 120 | $input = <STDIN>; |
| 121 | |
| 122 | } elsif ($input eq '2') { |
| 123 | my $sel; |
| 124 | do { |
| 125 | print "[a] ", UVmessage::get("MENU_ADDRESS_OK"), "\n", |
| 126 | "[b] ", UVmessage::get("MENU_ADDRESS_CHANGE"), "\n", |
| 127 | "[c] ", UVmessage::get("MENU_ADDRESS_INVALID"), "\n\n", |
| 128 | UVmessage::get("MENU_PROMPT"); |
| 129 | $sel = <STDIN>; |
| 130 | } until ($sel =~ /^[abc]$/i); |
| 131 | if ($sel =~ /^a$/i) { |
| 132 | delete $errors{SuspiciousAccount}; |
| 133 | delete $errors{InvalidAddress}; |
| 134 | next; |
| 135 | } elsif ($sel =~ /^c$/i) { |
| 136 | delete $errors{SuspiciousAccount}; |
| 137 | $errors{InvalidAddress} = UVmessage::get("MENU_INVALIDADDRESS") . " " . |
| 138 | UVmessage::get("MENU_INVALIDADDRESS2"); |
| 139 | next; |
| 140 | } |
| 141 | |
| 142 | do { |
| 143 | print "\n", UVmessage::get("MENU_ADDRESS_PROMPT"), " "; |
| 144 | $voter_addr = <STDIN>; |
| 145 | chomp ($voter_addr); |
| 146 | } until ($voter_addr); |
| 147 | $$addr = $voter_addr; |
| 148 | push (@$set, 'Adresse'); |
| 149 | delete $errors{SuspiciousAccount}; |
| 150 | delete $errors{InvalidAddress}; |
| 151 | check_ballotid(\%errors, \$voter_addr, $ballot_id, \%ids); |
| 152 | |
| 153 | } elsif ($input eq '3') { |
| 154 | my $sel; |
| 155 | do { |
| 156 | print "[a] ", UVmessage::get("MENU_NAME_OK"), "\n", |
| 157 | "[b] ", UVmessage::get("MENU_NAME_CHANGE"), "\n", |
| 158 | "[c] ", UVmessage::get("MENU_NAME_INVALID"), "\n\n", |
| 159 | UVmessage::get("MENU_PROMPT"); |
| 160 | $sel = <STDIN>; |
| 161 | } until ($sel =~ /^[abc]$/i); |
| 162 | if ($sel =~ /^a$/i) { |
| 163 | delete $errors{InvalidName}; |
| 164 | next; |
| 165 | } elsif ($sel =~ /^c$/i) { |
| 166 | $errors{InvalidName} = UVmessage::get("MENU_INVALIDNAME"); |
| 167 | next; |
| 168 | } |
| 169 | print UVmessage::get("MENU_NAME"), ": "; |
| 170 | $voter_name = <STDIN>; |
| 171 | chomp ($voter_name); |
| 172 | $$name = $voter_name; |
| 173 | push (@$set, 'Name'); |
| 174 | delete $errors{NoName}; |
| 175 | delete $errors{InvalidName}; |
| 176 | |
| 177 | $errors{InvalidName} = UVmessage::get("MENU_INVALIDNAME") |
| 178 | unless ($voter_name =~ /$config{name_re}/); |
| 179 | |
| 180 | } elsif ($input eq '4') { |
| 181 | # set votes |
| 182 | |
| 183 | my $sel; |
| 184 | do { |
| 185 | print "[a] ", UVmessage::get("MENU_VOTES_OK"), "\n", |
| 186 | "[b] ", UVmessage::get("MENU_VOTES_RESET"), "\n", |
| 187 | "[c] ", UVmessage::get("MENU_VOTES_INVALID"), "\n", |
| 188 | "[d] ", UVmessage::get("MENU_VOTES_CANCELLED"), "\n\n", |
| 189 | UVmessage::get("MENU_PROMPT"); |
| 190 | $sel = <STDIN>; |
| 191 | } until ($sel =~ /^[abcd]$/i); |
| 192 | if ($sel =~ /^[ad]$/i) { |
| 193 | delete $errors{NoVote}; |
| 194 | delete $errors{UnrecognizedVote}; |
| 195 | delete $errors{ViolatedRule}; |
| 196 | delete $errors{DuplicateVote}; |
| 197 | if ($sel =~ /^d$/i) { |
| 198 | # cancelled vote: replace all votes with an A |
| 199 | @$votes = split(//, 'A' x scalar @groups); |
| 200 | push @$set, 'Stimmen'; |
| 201 | # some errors are irrelevant when cancelling a vote: |
| 202 | delete $errors{InvalidName}; |
| 203 | delete $errors{NoName}; |
| 204 | delete $errors{InvalidBDSG}; |
| 205 | delete $errors{InvalidAddress}; |
| 206 | delete $errors{SuspiciousAccount}; |
| 207 | } |
| 208 | next; |
| 209 | } elsif ($sel =~ /^c$/i) { |
| 210 | $errors{NoVote} = UVmessage::get("MENU_INVALIDVOTE"); |
| 211 | next; |
| 212 | } |
| 213 | |
| 214 | # Set columns for Text::Wrap |
| 215 | $columns = $config{rightmargin}; |
| 216 | print "\n", wrap('', '', UVmessage::get("MENU_VOTES_REENTER_ASK")), "\n\n"; |
| 217 | print UVmessage::get("MENU_VOTES_REENTER_LEGEND"), "\n"; |
| 218 | |
| 219 | for (my $n=0; $n<@groups; $n++) { |
| 220 | my $voteinput = ""; |
| 221 | $votes->[$n] ||= 'E'; |
| 222 | |
| 223 | # repeat while invalid character entered |
| 224 | while (!($voteinput =~ /^[JNE]$/)) { |
| 225 | my $invalid = $#groups ? 0 : 1; |
| 226 | print UVmessage::get("MENU_VOTES_REENTER", (GROUP => $groups[$n])); |
| 227 | $voteinput = <STDIN>; |
| 228 | chomp $voteinput; |
| 229 | $voteinput ||= $votes->[$n]; |
| 230 | $voteinput =~ tr/jne/JNE/; |
| 231 | } |
| 232 | |
| 233 | # valid input, save new votes |
| 234 | $newvotes[$n] = $voteinput; |
| 235 | } |
| 236 | |
| 237 | print "\n\n"; |
| 238 | my $oldvotes = UVmessage::get("MENU_VOTES_REENTER_OLD"); |
| 239 | my $newvotes = UVmessage::get("MENU_VOTES_REENTER_NEW"); |
| 240 | my $oldlen = length($oldvotes); |
| 241 | my $newlen = length($newvotes); |
| 242 | my $maxlen = 1 + (($newlen>$oldlen) ? $newlen : $oldlen); |
| 243 | print $oldvotes, ' ' x ($maxlen - length($oldvotes)), @$votes, "\n", |
| 244 | $newvotes, ' ' x ($maxlen - length($newvotes)), @newvotes, "\n\n"; |
| 245 | |
| 246 | do { |
| 247 | print "[a] ", UVmessage::get("MENU_VOTES_REENTER_ACK"), " ", |
| 248 | "[b] ", UVmessage::get("MENU_VOTES_REENTER_NACK"), "\n\n", |
| 249 | UVmessage::get("MENU_PROMPT"); |
| 250 | $sel = <STDIN>; |
| 251 | } until ($sel =~ /^[ab]$/i); |
| 252 | |
| 253 | next if ($sel =~ /^b$/i); |
| 254 | @$votes = @newvotes; |
| 255 | push @$set, 'Stimmen'; |
| 256 | delete $errors{UnrecognizedVote}; |
| 257 | delete $errors{DuplicateVote}; |
| 258 | delete $errors{NoVote}; |
| 259 | delete $errors{ViolatedRule}; |
| 260 | |
| 261 | if (my $rule = UVrules::rule_check($votes)) { |
| 262 | $errors{ViolatedRule} = UVmessage::get("MENU_VIOLATEDRULE", (RULE => "#$rule")); |
| 263 | } |
| 264 | |
| 265 | } elsif ($input eq '5' && $config{personal}) { |
| 266 | print "\n", UVmessage::get("MENU_BALLOT_ID"), ": "; |
| 267 | $$ballot_id = <STDIN>; |
| 268 | chomp ($$ballot_id); |
| 269 | push (@$set, 'Kennung'); |
| 270 | check_ballotid(\%errors, \$voter_addr, $ballot_id, \%ids); |
| 271 | |
| 272 | } elsif ($input eq '6' && $config{bdsg}) { |
| 273 | my $sel; |
| 274 | do { |
| 275 | print "[a] ", UVmessage::get("MENU_BDSG_ACCEPTED"), "\n", |
| 276 | "[b] ", UVmessage::get("MENU_BDSG_DECLINED"), "\n\n", |
| 277 | UVmessage::get("MENU_PROMPT"); |
| 278 | $sel = <STDIN>; |
| 279 | } until ($sel =~ /^[ab]$/i); |
| 280 | |
| 281 | if ($sel =~ /^a$/i) { |
| 282 | delete $errors{InvalidBDSG}; |
| 283 | } else { |
| 284 | $errors{InvalidBDSG} = UVmessage::get("MENU_INVALIDBDSG"); |
| 285 | } |
| 286 | |
| 287 | } elsif ($input eq '7') { |
| 288 | my $sel; |
| 289 | do { |
| 290 | print "[a] ", UVmessage::get("MENU_VOTING_CORRECT"), "\n", |
| 291 | "[b] ", UVmessage::get("MENU_VOTING_WRONG"), "\n\n", |
| 292 | UVmessage::get("MENU_PROMPT"); |
| 293 | $sel = <STDIN>; |
| 294 | } until ($sel =~ /^[ab]$/i); |
| 295 | |
| 296 | if ($sel =~ /^a$/i) { |
| 297 | delete $errors{NoVoting}; |
| 298 | delete $errors{WrongVoting}; |
| 299 | } else { |
| 300 | $errors{WrongVoting} = UVmessage::get("MENU_WRONGVOTING"); |
| 301 | } |
| 302 | |
| 303 | } elsif ($input eq '7') { |
| 304 | my $sel; |
| 305 | do { |
| 306 | print "[a] ", UVmessage::get("MENU_VOTING_CORRECT"), "\n", |
| 307 | "[b] ", UVmessage::get("MENU_VOTING_WRONG"), "\n\n", |
| 308 | UVmessage::get("MENU_PROMPT"); |
| 309 | $sel = <STDIN>; |
| 310 | } until ($sel =~ /^[ab]$/i); |
| 311 | |
| 312 | if ($sel =~ /^a$/i) { |
| 313 | delete $errors{NoVoting}; |
| 314 | delete $errors{WrongVoting}; |
| 315 | } else { |
| 316 | $errors{WrongVoting} = UVmessage::get("MENU_WRONGVOTING"); |
| 317 | } |
| 318 | |
| 319 | } elsif ($input =~ /^i$/i) { |
| 320 | my $ignore = UVmessage::get("MENU_IGNORE_STRING"); |
| 321 | # Set columns for Text::Wrap |
| 322 | $columns = $config{rightmargin}; |
| 323 | print wrap('', '', UVmessage::get("MENU_IGNORE_WARNING", |
| 324 | (MENU_IGNORE_STRING => $ignore) |
| 325 | )); |
| 326 | if (<STDIN> eq "$ignore\n") { |
| 327 | print "\n"; |
| 328 | return "i"; |
| 329 | } |
| 330 | |
| 331 | } elsif ($input =~ /^w$/i) { |
| 332 | |
| 333 | if (keys %errors) { |
| 334 | if ((keys %errors)==1 && $errors{UnrecognizedVote}) { |
| 335 | # unrecognized vote lines aren't errors if votetaker |
| 336 | # did not change them |
| 337 | @$errors = (); |
| 338 | } else { |
| 339 | # Set columns for Text::Wrap |
| 340 | $columns = $config{rightmargin}; |
| 341 | @$errors = keys %errors; |
| 342 | my $warning = ' ' . UVmessage::get("MENU_ERROR_WARNING") . ' '; |
| 343 | my $length = length($warning); |
| 344 | print "\n", '*' x (($config{rightmargin}-$length)/2), $warning, |
| 345 | '*' x (($config{rightmargin}-$length)/2), "\n\n", |
| 346 | wrap('', '', UVmessage::get("MENU_ERROR_TEXT")), "\n\n", |
| 347 | '*' x $config{rightmargin}, "\n\n", |
| 348 | UVmessage::get("MENU_ERROR_GETKEY"); |
| 349 | my $input = <STDIN>; |
| 350 | next if ($input !~ /^y$/i); |
| 351 | print "\n"; |
| 352 | } |
| 353 | } else { |
| 354 | @$errors = (); |
| 355 | } |
| 356 | |
| 357 | system($config{clearcmd}); |
| 358 | print "\n", UVmessage::get("MENU_PROCESSING"), "\n"; |
| 359 | return "w"; |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | sub check_ballotid { |
| 364 | my ($errors, $voter_addr, $ballot_id, $ids) = @_; |
| 365 | |
| 366 | return 0 unless ($config{personal}); |
| 367 | |
| 368 | delete $errors->{NoBallotID}; |
| 369 | delete $errors->{WrongBallotID}; |
| 370 | delete $errors->{AddressNotRegistered}; |
| 371 | |
| 372 | if ($$ballot_id) { |
| 373 | if ($ids->{$$voter_addr}) { |
| 374 | if ($ids->{$$voter_addr} ne $$ballot_id) { |
| 375 | # ballot id incorrect |
| 376 | $errors->{WrongBallotID} = UVmessage::get("MENU_WRONGBALLOTID"); |
| 377 | } |
| 378 | } else { |
| 379 | $errors->{AddressNotRegistered} = UVmessage::get("MENU_ADDRESSNOTREGISTERED"); |
| 380 | } |
| 381 | } else { |
| 382 | $errors->{NoBallotID} = UVmessage::get("MENU_NOBALLOTID"); |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | } |
| 387 | |
| 388 | |
| 389 | ############################################################################## |
| 390 | # Menu for sorting out duplicate votings manually # |
| 391 | # Parameters: References to hashes with the paragraphs from the result file # |
| 392 | # and the default value # |
| 393 | # Return value: selected menu item (1, 2 or 0) # |
| 394 | ############################################################################## |
| 395 | |
| 396 | sub dup_choice { |
| 397 | my ($vote1, $vote2, $default) = @_; |
| 398 | |
| 399 | print STDERR "\n", UVmessage::get("MENU_DUP_VOTE"), "\n\n"; |
| 400 | print STDERR UVmessage::get("MENU_DUP_FIRST"), "\n"; |
| 401 | print STDERR "A: $vote1->{A}\n"; |
| 402 | print STDERR "N: $vote1->{N}\n"; |
| 403 | print STDERR "D: $vote1->{D}\n"; |
| 404 | print STDERR "K: $vote1->{K}\n"; |
| 405 | print STDERR "S: $vote1->{S}\n\n"; |
| 406 | print STDERR UVmessage::get("MENU_DUP_SECOND"), "\n"; |
| 407 | print STDERR "A: $vote2->{A}\n"; |
| 408 | print STDERR "N: $vote2->{N}\n"; |
| 409 | print STDERR "D: $vote2->{D}\n"; |
| 410 | print STDERR "K: $vote2->{K}\n"; |
| 411 | print STDERR "S: $vote2->{S}\n\n"; |
| 412 | print STDERR "1: ", UVmessage::get("MENU_DUP_DELFIRST"), "\n", |
| 413 | "2: ", UVmessage::get("MENU_DUP_DELSECOND"), "\n", |
| 414 | "0: ", UVmessage::get("MENU_DUP_DELNONE"), "\n\n"; |
| 415 | |
| 416 | my $input; |
| 417 | |
| 418 | do { |
| 419 | print STDERR UVmessage::get("MENU_PROMPT"), "[$default] "; |
| 420 | $input = <STDIN>; |
| 421 | chomp $input; |
| 422 | } until ($input eq '' || ($input >= 0 && $input<3)); |
| 423 | |
| 424 | return $input || $default; |
| 425 | } |
| 426 | |
| 427 | 1; |