An Online Voting System
Description: This deliverable contains three files:
login.php, joinAndVote.php, and voteProcess.php.
"login.php" file loads a login
form for user name and password. If either user name or
password is entered incorrectly, user is redirected back to
this login form.
If user logs in successfully, he/she will get a voting form
with group size field and join button enabled
and all other controls disabled. This voting form is loaded
by "joinAndVote.php" file. A
session id will be used to represent a unique user and shown in
the id field in the form. The voting form posts requests to itself.
When a user enters a group size and clicks on the join button,
a join request is initiated
and sent through an xmlhttp object to "voteProcess.php" script
in the server. First voter will
set the oveall group size.
After enough users have joined the group
successfully, vote field and vote button are enabled,
and all other controls are disabled. When user enters a vote
value of 0 or 1, and clicks on the vote button, a vote request
is sent to the same script "voteProvess.php" in the server.
If a response with proper majority and
tally information is not ready, a "wait" message comes back
instead, the client will continue polling the server for a
proper response. User will not be able to vote in the next
round until previous round is finished. And when an agreement
is reached, "agreement reached!!" message will be shown and
all controls are disabled. All the requests and responses
are handled by xmlhttp object.
1.In this example, four people try to log into the system.
2.All four voters join the same group. And first
voter gets the right to set the overall group size.
3.Three voters wait for the response before the fourth one casts a vote.
4.An agreement has been reached. All voters cannot vote further.
5.Here are the vote table entries after all the rounds.
login.php
<?php
/*
login.php file presents user a login form.
A user need to enter a vaid user name and password to
be able to vote.
Author: Chao Liang
Date: 5/12/2006
*/
session_start(); // Start a new session
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title> User login form</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<script language="JavaScript" type="text/JavaScript">
document.loginForm.reset(); // Clear the form data
</script>
</head>
<body>
<h1> User Log in </h1>
<?php
// Handle a situation that user name or password is wrong.
if (isset($_REQUEST['username']))
{
echo "User name and password are wrong." . "<br/>";
echo "Please re-enter." . "<br/>";
}
?>
<form method='get' action='joinAndVote.php'>
<table>
<tr>
<td>
User Name:
</td>
<td>
<input type='text' name='username' id='username'/>
</td>
</tr>
<tr>
<td>
Password:
</td>
<td>
<input type='password' name='passwd' id='passwd'/>
</td>
</tr>
<tr>
<td>
<input type = 'submit' name='login' value = 'login' />
</td>
</tr>
</table>
</form>
</body>
</html>
joinAndVote.php
<?php
/*
Verify.php file verifies user name and password. It will
redirect user back to the login form if either user name
or password is wrong. If user logs in successfully, it will
move onto a voting page.
Author: Chao Liang
Date: 5/12/2006
*/
// If user does not provide user name or password, it will
// be redirect back to the login page.
// First need to test out the join, wait and vote.
session_start();
if (!isset($_REQUEST['username']) || !isset($_REQUEST['passwd']))
{
die("Please enter user name and password");
header("Location: http://localhost/voter/login.php");
}
$connection = mysql_connect("localhost","cs297", "cs297");
mysql_select_db('users', $connection);
$sql = "select * from user where username='" . $_REQUEST['username'] .
"' and password = '" . $_REQUEST['passwd'] . "'";
$resultSet = mysql_query($sql, $connection);
// Check if the user name and password pair is in the database.
// Redirect back to login page if they are not.
if (mysql_num_rows($resultSet) != 1)
{
session_unset();
session_destroy();
header("Location: http://localhost/voter/login.php");
}
else
{
$fname = mysql_result($resultSet, 0, 'fname');
echo "welcome! " . $fname . "<br/>";
echo "Please click on Join button to join " . "<br/>"
. "then click on Vote button to vote";
}
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title> Join and Vote</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
<script language="JavaScript" type="text/JavaScript">
var joinInterval; // A global timer object for join.
var voteInterval; // Another global timer object for vote.
// Control for joining group or polling for current size
var firstJoin;
// A boolean for checking if user clicks on join or vote button.
var join;
var majority; // Majority value comes from server.
var tally; // Tally value comes from server.
var group; // Group id of user.
var vote; // Vote from the previous round.
var id; // User id represented by session id.
var round; // Previous round number
var agree; // Agreement value start with -1.
var size; // Size of the group.
var sentinel // Threshold.
var first // Check if it is the first voter.
var curSize // Current size in the group.
/*
An event handler that will be called when user
clicks on the vote button. It will connect to server
every half second to request majority and tally
information of previous round.
*/
function voteToServer()
{
var voteComp = document.getElementById("vote");
vote = voteComp.value;
voteInterval = window.setInterval("requestInfo()",2000);
requestInfo();
// Always return false to mean do not move to other page.
return false;
}
/*
An event handler that will be called when user
clicks on the join button. It will connect to server
to join a group. After user clicks on the join button,
vote field and vote button will be enabled, and join
button will be disabled.
*/
function connectToServer()
{
var joinButton = document.getElementById("join");
joinButton.disabled = true;
join = true; // So join request will be constructed
firstJoin = true; // So curSize request will be constructed
joinInterval = window.setInterval("requestInfo()",500);
// Always return false, so it does not move to other page.
return false;
}
/*
A function will be called to display all information from
the server into the correct fields.
*/
function displayInfo()
{
var idComp = document.getElementById("id");
var majComp = document.getElementById("majority");
var talComp = document.getElementById("tally");
var roundComp = document.getElementById("round");
var agreeComp = document.getElementById("agreement");
var groupComp = document.getElementById("group");
var voteComp = document.getElementById("vote");
var sizeComp = document.getElementById("groupSize");
var sentinelComp = document.getElementById("threshold");
var firstComp = document.getElementById("first");
var curComp = document.getElementById("curSize");
idComp.value = id;
majComp.value = majority;
talComp.value = tally;
roundComp.value = round;
agreeComp.value = agree;
groupComp.value = group;
voteComp.value = vote;
sizeComp.value = size;
sentinelComp.value = sentinel;
firstComp.value = first;
curComp.value = curSize;
}
/*
A function parses response. A response will contain just
"wait" if others in the same group have not yet voted in
the same round. If it is valid information, it will contains
id, round, agreement, majority,tally, group, and vote separated
with a ":". The timer will stop once valid information returned.
A Message will be shown if an agreement is reached, and all the
controls will be disabled.
*/
function parseResponse(response)
{
var voteButton = document.getElementById('voteButton');
var divComp = document.getElementById('respInfo');
var voteElement = document.getElementById('vote');
var firstIndex = response.indexOf("result:");
if (firstIndex != -1)
{
var res = response.substring(firstIndex+7);
if (res.indexOf("wait") != -1)
{
voteButton.disabled = true;
}
else if (res.indexOf("full") != -1)
{
voteButton.disabled = true;
divComp = "A group is full!!"
voteElement.disabled = true;
}
else
{
var resArray = res.split(":");
id = resArray[0];
agree = resArray[1];
majority = resArray[2];
tally = resArray[3];
group = resArray[4];
round = resArray[5];
vote = resArray[6];
size = resArray[7];
sentinel = resArray[8];
first = parseInt(resArray[9]);
curSize = resArray[10];
// -1 indicates that agreement is not reached
if (resArray[1] != -1)
{
divComp.innerHTML = "Agreement reach!!";
voteButton.disabled = true;
voteElement.disabled = true;
setIntervalOut();
}
// required number of people have joined
else if (join == true && curSize == size)
{
var voteField = document.getElementById("vote");
var voteButton = document.getElementById("voteButton");
var joinButton = document.getElementById("join");
var grSizeField = document.getElementById("groupSize");
voteField.disabled = false;
voteButton.disabled = false;
joinButton.disabled = true;
grSizeField.disabled = true;
join = false;
setIntervalOut();
}
else if (join == true && first) // reset group size
{
var joinButton = document.getElementById("join");
var sizeField = document.getElementById("groupSize");
joinButton.disabled = false;
sizeField.disable = false;
}
else if (join == true && !first)
{
var joinButton = document.getElementById("join");
var sizeComp = document.getElementById("groupSize");
sizeComp.disabled = true;
joinButton.disabled = true;
}
else // Just get the information of current round
{
setIntervalOut();
voteButton.disabled = false;
voteElement.disabled = false;
}
}
}
else
return 0;
}
/*
This function creates an xmlHttpRequest object
according to the browser used.
return: created xmlHttpRequest object
*/
function createXMLHttpRequest()
{
var transfer;
if (window.ActiveXObject)
{
transfer = new ActiveXObject('Msxml2.XMLHTTP');
}
else if (window.XMLHttpRequest)
{
transfer = new XMLHttpRequest();
}
return transfer;
}
/*
This function will clear the timer
*/
function setIntervalOut()
{
if (joinInterval != null)
clearInterval(joinInterval);
if (voteInterval != null)
clearInterval(voteInterval);
}
/*
This function sends request to the server, and register
the callback function to process the response when result
comes back from the server. A random value parameter will
be sent to the server to avoid use the cache url data.
*/
function requestInfo()
{
var xmlHttpObject;
var idValue;
var idComp;
var roundValue;
var roundComp;
var voteButton;
xmlHttpObject = createXMLHttpRequest();
idComp = document.getElementById('id');
idValue = idComp.value;
sizeComp = document.getElementById('groupSize');
sizeValue = sizeComp.value;
roundComp = document.getElementById('round');
// Advance to the next round
roundValue = parseInt(roundComp.value) + 1;
if (join == true) // click on the join button
{
if (firstJoin == true) // Just join
{
xmlHttpObject.open("get", "voteProcess.php?join="
+ 1 + "&size=" + sizeValue
+ "&dummy=" + Math.random(), true);
firstJoin = false;
}
else // Request for current size
xmlHttpObject.open("get", "voteProcess.php?firstJoin=" + 0
+ "&dummy=" + Math.random(), true);
}
else // click on the vote button
xmlHttpObject.open("get", "voteProcess.php?vote=" + vote
+ "&round=" + roundValue + "&size="
+ sizeValue +
"&dummy=" + Math.random(), true);
// callback function
xmlHttpObject.onreadystatechange = function ()
{
// The response is stable for process when readyState
// is 4
if (xmlHttpObject.readyState == 4)
{
// check if upload is successful or not
if (xmlHttpObject.status == 200)
{
var response = xmlHttpObject.responseText;
var infoDiv = document.getElementById('respInfo');
infoDiv.innerHTML = response;
parseResponse(response);
displayInfo();
return true;
}
else
{
// Status will be shown when an error occurs
displayInfo("An error occurred: " +
xmlHttpObject.statusText);
return false;
}
}
};
xmlHttpObject.send(null); // A request is sent
}
</script>
</head>
<body>
<form method="post" action="verify.php">
<table summary="table used for formatting this form">
<tr>
<td>
<label for="id">
Id:
</label>
</td>
<td>
<input type="text" name="id" size="40"
maxlength="60" id="id" value = "<?=session_id()?>" disabled/>
</td>
</tr>
<tr>
<td>
<label for="agreement">
Agreement:
</label>
</td>
<td>
<input type="text" name="agreement" size="20"
maxlength="40" id="agreement" disabled/>
</td>
</tr>
<tr>
<td>
<label for="majority">
Majority:
</label>
</td>
<td>
<input type="text" name="majority" size="20"
maxlength="40" id="majority" disabled/>
</td>
</tr>
<tr>
<td>
<label for="tally">
Tally:
</label>
</td>
<td>
<input type="text" name="tally" size="20"
maxlength="40" id="tally" disabled/>
</td>
</tr>
<tr>
<td>
<label for="group">
Group:
</label>
</td>
<td>
<input type="text" name="group" size="20"
maxlength="40" id="group" disabled />
</td>
</tr>
<tr>
<td>
<label for="round">
Round:
</label>
</td>
<td>
<input type="text" name="round" size="20"
maxlength="40" id="round" disabled />
</td>
</tr>
<tr>
<td>
<label for="vote">
Vote:
</label>
</td>
<td>
<input type="text" name="vote" size="20"
maxlength="40" id="vote" disabled/>
</td>
</tr>
<tr>
<td>
<label for="groupSize">
Group Size:
</label>
</td>
<td>
<input type="text" name="groupSize" size="20"
maxlength="40" id="groupSize" />
</td>
</tr>
<tr>
<td>
<label for="threshold">
Threshold:
</label>
</td>
<td>
<input type="text" name="threshold" size="20"
maxlength="40" id="threshold" disabled/>
</td>
</tr>
<tr>
<td>
<label for="first">
First Voter:
</label>
</td>
<td>
<input type="text" name="first" size="20"
maxlength="40" id="first" disabled/>
</td>
</tr>
<tr>
<td>
<label for="curSize">
Current size:
</label>
</td>
<td>
<input type="text" name="curSize" size="20"
maxlength="40" id="curSize" disabled/>
</td>
</tr>
<tr>
<td>
<input type="submit" name="join" value="Join" id="join"
onclick="return connectToServer();"/>
</td>
<td>
<input type="submit" name="voteButton" value="Vote" id="voteButton"
onclick="return voteToServer()" disabled />
</td>
</tr>
</table>
</form>
<div id="respInfo"></div>
</body>
</html>
voteProcess.php
<?php
/*
Vote.php file will process the vote from user. All the
the voting information stored in "vote" table of "users"
database. When a vote comes from a user, all available
information will be drawn from the database to calcualte
majority and tally for user. A "wait" message will be
sent if not all users in the same group voted at current
round.
*/
// Starts different sessions for different user.
session_start();
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Process a vote</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"/>
</head>
<body>
<?php
/*
A class manages votes from users and updates the
database if necessary. It also constructs response
each round.
*/
class ElectionManager
{
public static $groupSize = 4; // Maximum group size
private $connection; // Database connection
/*
A function opens a database connection and
selects a database.
*/
public function openDatabase()
{
$connection = mysql_connect("localhost","cs297", "cs297");
@mysql_select_db("users");
}
/*
A function to get a random value of 0 or 1.
Return: ramdomized 0 or 1
*/
public static function flip ()
{
srand();
$result = rand(0,1);
return $result;
}
/*
A function constructs proper response. It will contain
all the needed fields separated by a ":".
Return: A constructed response.
*/
public function constructResponse($id, $agree, $maj, $tally,$group,
$round, $vote, $size, $sentinel, $first, $curSize)
{
$result = $id . ":";
$result = $result . $agree . ":";
$result = $result . $maj . ":";
$result = $result . $tally . ":";
$result = $result . $group . ":";
$result = $result . $round . ":";
$result = $result . $vote . ":";
$result = $result . $size . ":";
$result = $result . $sentinel . ":";
$result = $result . $first . ":";
$result = $result . $curSize . ":";
return $result;
}
/*
A function is used to check if the id given represents
the first voter in the group.
Parameter: $id - voter id
Return: array of (1 or 0, size), where size
is the size of the group. 1 for first
voter and 0 for other voter
*/
public function checkFirstVoter($id)
{
// First time comes in and "vote" table is empty
$firstVoter = false;
$size;
$sql = "select * from vote";
$sqlResult = mysql_query($sql) or die(mysql_error());
$resultRows = mysql_num_rows($sqlResult);
// Get the size from database if it is not first voter
while ($rowArr = mysql_fetch_array($sqlResult))
{
$size = $rowArr[7];
break;
}
// First voter can change the group size
$voterSql = "select * from vote where id='" . $id . "'";
$voterSqlResult = mysql_query($voterSql) or die(mysql_error());
while ($arr = mysql_fetch_array($voterSqlResult))
{
$firstVoter = $arr[9];
break;
}
// first voter just join or first voter want to reset size
if ($resultRows == 0 || $firstVoter)
{
// Make sure voter enter group size when joining.
if (isset($_REQUEST['size']) && $_REQUEST['size'] == null)
die("Enter group size before click on join button!");
$size = $_REQUEST['size'];
return array(1, $size);
}
else
return array(0, $size);
}
/*
A main function processes 'join' and 'vote' request.
It will insert an entry into the database once a user
joins. When a user votes, it inputs the vote into database
and calculate majority and tally information and puts the
updated information back to the database.
Return: a "wait" message or a proper majority and tally
information.
*/
// The layout of the table vote is as following:
// id, agreement, majority, tally, group, round, vote,
// groupSize, threshold, firstVoter, curSize.
public function main()
{
// Open a database connection first
$this->openDatabase();
$id = session_id();
$groupNum = 0;
// The join round's round value is -1
if (isset($_REQUEST['join']))
{
$curSize = 0;
$result;
$voterSizePair = $this->checkFirstVoter($id);
$firstVoter = $voterSizePair[0];
$size = $voterSizePair[1];
$selectSql =
"select * from vote where groupNum='" . $groupNum . "'";
$selectResult = mysql_query($selectSql) or die(mysql_error());
$rows = mysql_num_rows($selectResult);
// Get current size from database
while ($arr = mysql_fetch_array($selectResult))
{
$curSize = $arr[10];
break;
}
// Current size will increase by one after insertion
$curSize = $curSize + 1;
if ($rows == $size)
{
return "full"; // A group is full
}
$sql = "insert into vote values('$id', -1, -1, 0, $groupNum,
-1, -1, $size, -1, $firstVoter, $curSize)";
$insertResult = mysql_query($sql) or die(mysql_error());
// Update current size of all other voters to increase by one.
$updateSql = "update vote set curSize = '" . $curSize .
"', groupSize='" . $size . "'";
$updateResult = mysql_query($updateSql) or die(mysql_error());
$this->closeDatabase(); // close database connection
$result = $this->constructResponse
($id, -1, -1, 0, $groupNum, -1,
-1, $size, -1, $firstVoter, $curSize);
return $result;
}
// Handle request after join, but before vote
// Vote is happening only when current size
// is equal to group size.
else if (isset($_REQUEST['firstJoin']))
{
$sql = "select * from vote where groupNum='" . $groupNum . "'" .
" and id='" . $id . "'";
$sqlResult = mysql_query($sql) or die(mysql_error());
$result = "";
while ($arr = mysql_fetch_array($sqlResult))
{
$eachRow = $this->constructResponse
($arr['id'], $arr['agreement'],
$arr['majority'], $arr['tally'],
$arr['groupNum'], $arr['round'],
$arr['vote'], $arr['groupSize'],
$arr['threshold'], $arr['firstVoter'],
$arr['curSize']);
$result = $eachRow;
}
$this->closeDatabase(); // close database
return $result;
}
else // user clicks on vote
{
$size = $_REQUEST['size'];
// id is not in the group
if ( $groupNum === false)
{
// groupNum is -1 to indicate id has not join
$result = $this->constructResponse
($id, -1, -1, 0, -1, -1, -1, -1, -1, 0, 0);
return $result;
}
if (isset($_REQUEST['vote']) &&
$_REQUEST['vote'] == null)
die("Enter a vote value before click on vote button!");
$round = $_REQUEST['round'];
$vote = $_REQUEST['vote'];
$dupSql = "select * from vote where id='" . $id .
"' and round='" . $round . "'";
$dupResult = mysql_query($dupSql)
or die(mysql_error());
$existRows = mysql_num_rows($dupResult);
// Avoid duplicated insertion
if ($existRows != 1)
{
$curSize;
$first;
$previousSql = "select * from vote where id='" . $id .
"' and round='" . ($round - 1) . "'";
$previousResult = mysql_query($previousSql)
or die(mysql_error());
// Get curSize, firstVoter information from database
while ($arr = mysql_fetch_array($previousResult))
{
$curSize = $arr['curSize'];
$first = $arr['firstVoter'];
break;
}
$sql = "insert into vote values('$id', -1, " .
"-1, 0, 0, $round,".
"$vote,$size, -1,$first,$curSize)";
mysql_query($sql) or die(mysql_error());
}
$result = "";
// Make sure majority and tally won't be delivered util
// all users in the same group voted.
// A "wait" message as an indicator that shows not all users
// have been voted.
if (!$this->getReady($round, 0, $size))
return "wait";
$result = $this->retrieveInfo($round, 0, $id);
$this->closeDatabase(); // close database connection
return $result;
}
}
/*
A function checks if all the users in the same group have
been voted.
Return: True for all users vote in the same round,
otherwise false.
*/
public function getReady($round, $groupId, $size)
{
$sql = "select * from vote where round ='" . $round .
"' and groupNum ='" . $groupId . "'";
$result = mysql_query($sql) or die(mysql_error());
$rows = mysql_num_rows($result);
if ($rows < $size)
return false;
else
return true;
}
/*
A function calcualtes majority, tally and agreement information
based on combinations of $round, $groupNum, and $id. It updates
the corresponding fields in database. And it constructed proper
response message.
Return: A proper message contains $id,
$agreement, $majority,
$tally, $group, $round, and $vote
separated by ":".
*/
public function retrieveInfo($round, $groupId, $id)
{
$majority;
$tally;
$threshold;
$agreement = -1;
$numHeads = 0;
$numTails = 0;
$arr;
$size = -1;
$selectSql = "select * from vote where round >= '"
. 0 . "' and groupNum = '" . $groupId . "'";
$selectResult = mysql_query($selectSql)
or die(mysql_error());
while ($arr = mysql_fetch_array($selectResult))
{
if ($arr['vote'] == 1)
$numHeads++;
else
$numTails++;
if ($size == -1)
$size = $arr['groupSize'];
}
$majority = ($numHeads > $numTails)? 1 : 0;
$tally = ($majority == 1)? $numHeads : $numTails;
if($numHeads >= ((7.0 / 8.0) * $size))
{
$agreement = 1;
}
else if($numTails >= ((7.0 / 8.0) * $size))
{
$agreement = 0;
}
$retFlip = ElectionManager::flip();
if ($retFlip)
$threshold = (int)((5.0 / 8.0) * $size + 1);
else
$threshold = (int)((3.0 / 4.0) * $size + 1);
// An sql string updates the database
$updateSql = "update vote set majority = '" . $majority
. "', tally ='" . $tally . "'" . ", agreement='"
. $agreement . "', threshold='" . $threshold
. "' where round = '" . $round
. "' and groupNum = '" . $groupId . "' and id='"
. $id . "'";
$updateResult = mysql_query($updateSql) or die(mysql_error());
// An sql string select a row according to groupNum and id
$selectSql = "select * from vote where round = '" . $round
. "' and groupNum = '" . $groupId . "'"
. " and id='" . $id . "'";
$afterUpdate = mysql_query($selectSql) or die(mysql_error());
$result = "";
while ($arr = mysql_fetch_array($afterUpdate))
{
$eachRow = $this->constructResponse
($arr['id'], $arr['agreement'],
$arr['majority'], $arr['tally'],
$arr['groupNum'], $arr['round'],
$arr['vote'], $arr['groupSize'],
$arr['threshold'], $arr['firstVoter'],
$arr['curSize']);
$result = $eachRow;
}
return $result;
}
/*
A function closes database connection.
*/
public function closeDatabase()
{
mysql_close();
}
}
// Construct an ElectionManager object
$manager = new ElectionManager();
$result = $manager->main();
echo "result:" . $result;
?>
</body>
</html>
|