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>
+&nbsp;
\ No newline at end of file
-- 
GitLab