From 6e673f2e6a7dd7eaeaca1e457e6168bc23bde014 Mon Sep 17 00:00:00 2001 From: Denis Walker <dez@sucs.org> Date: Sun, 30 Nov 2008 17:59:26 +0000 Subject: [PATCH] Adds election component Makes password check function in session class public --- components/election.php | 141 +++++++++++++++++++++++++++++++++ lib/session.php | 2 +- templates/election-results.tpl | 26 ++++++ templates/election-vote.tpl | 33 ++++++++ 4 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 components/election.php create mode 100644 templates/election-results.tpl create mode 100644 templates/election-vote.tpl diff --git a/components/election.php b/components/election.php new file mode 100644 index 0000000..3661ed5 --- /dev/null +++ b/components/election.php @@ -0,0 +1,141 @@ +<?php +# Settings for this particular election: +define('ELECTION_YEAR', '2009'); +define('ELECTION_START', '2008-12-01 00:00'); +define('ELECTION_END', '2008-12-08 00:00'); +define('ELECTION_NO_MANIFESTO', 'http://sucs.org/vote/None.html'); + +$smarty->assign("election_year", ELECTION_YEAR); +$smarty->assign("title", "Executive Election ".ELECTION_YEAR); + +function display_ballot() { + global $DB, $smarty, $output; + + require_once("../lib/members.php"); + $members = new Members; + $role = $DB->GetAll("SELECT post FROM election_candidates GROUP BY post"); + + foreach ($role as $post) { + $candidate[$post['post']] = $DB->GetAll("SELECT username, manifesto_link FROM election_candidates WHERE post=? ORDER BY username ASC", $post['post']); + + $i=0; + foreach ($candidate[$post['post']] as $person) { + $details = $members->memberView($person['username']); + $candidate[$post['post']][$i]['realname'] = $details[0]['cn']; + if (empty($person['manifesto_link'])) $candidate[$post['post']][$i]['manifesto_link'] = ELECTION_NO_MANIFESTO; + $i++; + } + } + + $smarty->assign("candidate", $candidate); + $output = $smarty->fetch('election-vote.tpl'); +} + + +if (!$session->loggedin) { + # You can't vote if you're not logged in! + trigger_error("You are not logged in.", E_USER_WARNING); +} else { + # The election hasn't started yet + if (strtotime('now') < strtotime(ELECTION_START)) trigger_error("The polling station is not yet open. Voting begins on ".date('l jS F, Y \a\t H:i', strtotime(ELECTION_START))."."); + + # The election is over - display the results + else if (strtotime('now') > strtotime(ELECTION_END)) { + require_once("../lib/members.php"); + $members = new Members; + $role = $DB->GetAll("SELECT post FROM election_candidates GROUP BY post"); + + foreach ($role as $post) { + $candidate[$post['post']] = $DB->GetAll("SELECT username, manifesto_link FROM election_candidates WHERE post=? ORDER BY votes DESC", $post['post']); + + $i=0; + foreach ($candidate[$post['post']] as $person) { + $details = $members->memberView($person['username']); + $candidate[$post['post']][$i]['realname'] = $details[0]['cn']; + if (empty($person['manifesto_link'])) $candidate[$post['post']][$i]['manifesto_link'] = ELECTION_NO_MANIFESTO; + $candidate[$post['post']][$i]['votes'] = $DB->GetOne("SELECT count(username) FROM election_votes WHERE ".$post['post']."=?", array($person['username'])); + $i++; + } + } + + $smarty->assign("candidate", $candidate); + $output = $smarty->fetch('election-results.tpl'); + } else { + # It's election time + # Check the user hasn't already voted + $vote_details = $DB->GetRow("SELECT time, ipaddress FROM election_votes WHERE username=?", $session->username); + if (count($vote_details)>0) { + trigger_error("You already voted on ".date('l jS F, Y \a\t H:i', strtotime($vote_details['time']))." from IP address ".$vote_details['ipaddress'].".", E_USER_WARNING); + $output = "<p>Please email the Returning Officer at <a href=\"mailto:vote@sucs.org\">vote@sucs.org</a> with any queries.</p>\n<p>Once polling has closed on ".date('l jS F, Y \a\t H:i', strtotime(ELECTION_END)).", the election results will be available on this page.</p>"; + } else { + if ($_POST['submit']=="Cast Votes") { + # We have a ballot paper to process + $output = "<h2>Thank you for your vote</h2>\n<p>You will shortly receive an email confirming your vote.</p>\n<p>Once polling has closed on ".date('l jS F, Y \a\t H:i', strtotime(ELECTION_END)).", the election results will be available on this page.</p>"; + + # Establish which positions are contested + $role = $DB->GetCol("SELECT post FROM election_candidates GROUP BY post HAVING count(username) > 1"); + + $fail = FALSE; + $invalid_candidate = FALSE; + $abstain_count = 0; + foreach ($role as $post) { + if ($_POST[$post] == "abstain") $abstain_count++; + # check that we have a valid option selected + if (!($DB->GetOne("SELECT username FROM election_candidates WHERE post=? AND username=?", array($post, $_POST[$post])) || $_POST[$post]=="abstain")) $fail = TRUE; + } + + + # Check their password first + if ($session->check_pass($session->username, $_POST['vote_passwd'])) { + if ($fail) { + # Either no option or an invalid candidate was supplied + trigger_error("An invalid option was selected in at least one ballot. Please try again.", E_USER_WARNING); + display_ballot(); + } else { + # User has abstained in all votes + if ($abstain_count == count($role)) { + trigger_error("You have not selected any candidates in any ballots.", E_USER_WARNING); + display_ballot(); + } else { + # The input is valid - record the vote + $vote = array( + 'username' => $session->username, + 'time' => 'now', + 'ipaddress' => $_SERVER['REMOTE_ADDR']); + + # Email the Returning Officer + $ro_message = "User: ".$vote['username']."\nIP: ".$vote['ipaddress']."\n\n"; + + # Email the voter with confirmation + $voter_message = "Thank you for voting in the SUCS election this year. Here are the votes you cast:\n\n"; + + foreach ($role as $post) { + # Don't add votes up as we go + # $DB->Execute("UPDATE election_candidates SET votes=votes+1 WHERE post=? AND username=?", array($post, $_POST[$post])); + $vote[$post] = $_POST[$post]; + $ro_message .= ucfirst($post).": ".$_POST[$post]."\n"; + $voter_message .= ucfirst($post).": ".$_POST[$post]."\n"; + } + + $voter_message .= "\nResults of the election will be announced on ".date('l jS F, Y \a\t H:i', strtotime(ELECTION_END))." at http://sucs.org/Vote\n"; + + $DB->AutoExecute("election_votes", $vote, 'INSERT'); + + mail("SUCS Returning Officer <vote@sucs.org>", "[SUCS Election] Vote received from ".$vote['username'], $ro_message, "From: SUCS Election ".ELECTION_YEAR." <vote@sucs.org>"); + mail($session->fullname." <".$session->username."@sucs.org>", "[SUCS Election] Thanks For Voting", $voter_message, "From: SUCS Election ".ELECTION_YEAR." <vote@sucs.org>"); + } + } + } else { + display_ballot(); + } + } else { + # Display the ballot paper + display_ballot(); + } + } + } +} + +$smarty->assign("body", $output); + +?> diff --git a/lib/session.php b/lib/session.php index 08f2a14..9ba5c4f 100644 --- a/lib/session.php +++ b/lib/session.php @@ -279,7 +279,7 @@ private $datahash=''; // hash of data field } /* check using ldap directly */ - private function check_pass($user, $pass) + public function check_pass($user, $pass) { // Open connection if (!($ldap = @ldap_connect("ldap://localhost"))) { diff --git a/templates/election-results.tpl b/templates/election-results.tpl new file mode 100644 index 0000000..c84e20b --- /dev/null +++ b/templates/election-results.tpl @@ -0,0 +1,26 @@ +<h2>Election Results</h2> + +{foreach name=outer key=position item=cand from=$candidate} +<h2>{$position|capitalize}</h2> +{if count($cand) > 1} +<table> +<tr> + <th>Candidate</th> + <th>Votes</th> +</tr> + +{foreach item=person from=$cand} +<tr> + <td><a href="{$person.manifesto_link}">{$person.realname}</a> ({$person.username})</td> + <td style="text-align: right;">{$person.votes}</td> +</tr> +{/foreach} + +</table> + +{else} +There is no vote for {$position|capitalize} as only one candidate was nominated:<br /> +<a href="{$cand.0.manifesto_link}">{$cand.0.realname}</a> ({$cand.0.username})<br /> +{/if} + +{/foreach} diff --git a/templates/election-vote.tpl b/templates/election-vote.tpl new file mode 100644 index 0000000..93f8735 --- /dev/null +++ b/templates/election-vote.tpl @@ -0,0 +1,33 @@ +<p>Please use the form below to cast your votes for the {$election_year} SUCS executive committee.<br /> +(Click on a candidate's name to view their manifesto)</p> + +<form method="post" action="{$smarty.server.REQUEST_URI}"> +{foreach name=outer key=position item=cand from=$candidate} +<h2>{$position|capitalize}</h2> + {if count($cand) > 1} + {foreach item=person from=$cand} +<input type="radio" name="{$position}" value="{$person.username}" id="{$position}{$person.username}" /><label for="{$position}{$person.username}"><a href="{$person.manifesto_link}">{$person.realname}</a> ({$person.username})</label><br /> + {/foreach} +<input type="radio" name="{$position}" value="abstain" id="{$position}abstain" /><label for="{$position}abstain"><em>Abstain</em></label><br /> + {else} +There is no vote for {$position|capitalize} as only one candidate was nominated:<br /> +<a href="{$cand.0.manifesto_link}">{$cand.0.realname}</a> ({$cand.0.username}) + {/if} + +{/foreach} + +<div class="row"> + <label for="vote_passwd">SUCS Password:</label> + <span class="textinput"> + <input name="vote_passwd" id="vote_passwd" type="password" /> + <div class="note">Your SUCS password is required to ensure that the vote is from you.</div> + </span> +</div> +<div class="row"> + <span class="textinput"> + <input type="submit" value="Cast Votes" name="submit" /> + </span> +</div> + +</form> + \ No newline at end of file -- GitLab