| @@ -0,0 +1,52 @@ | |||
| <?php | |||
| include('../inc/inc.php'); | |||
| if (!$user->loggedin()) { | |||
| header('Location: /login.php'); | |||
| die(); | |||
| } | |||
| if ($user->getRole() !== "admin") { | |||
| header('Location: /index.php'); | |||
| die(); | |||
| } | |||
| $header = new Header("2021 Michigan Flyers Election : Poll Worker"); | |||
| $header->addStyle("/styles/style.css"); | |||
| $header->addStyle("/styles/admin.css"); | |||
| $header->addScript("/js/jquery-1.11.3.min.js"); | |||
| $header->addScript("/js/admin-search.js"); | |||
| $header->setAttribute('title', 'Michigan Flyers'); | |||
| $header->setAttribute('tagline', '2021 Election Administration'); | |||
| $header->output(); | |||
| $voters = $db->fetchAssoc('select ANY_VALUE(skymanager_id) as `skymanager_id`, ANY_VALUE(members.voting_id) as `voting_id`, ANY_VALUE(name) as `name`, ANY_VALUE(username) as `username`, group_concat(proxy.voting_id) as `proxies`, ANY_VALUE(upstream_proxy.delegate_id) as `delegate`, md5(coalesce(ANY_VALUE(email), "")) as `gravatar_hash` from members left join proxy on (members.voting_id=proxy.delegate_id) left join proxy as upstream_proxy on (upstream_proxy.voting_id=members.voting_id) where members.voting_id is not null group by members.voting_id UNION select skymanager_id, voting_id, name, username, NULL as `proxies`, NULL as `delegate`, md5(coalesce(email, "")) as `gravatar_hash` from members where members.voting_id is null'); | |||
| ?> | |||
| <script type="text/javascript"> | |||
| var voters = <?= json_encode($voters); ?>; | |||
| </script> | |||
| <form> | |||
| <div class="form-row"> | |||
| <div class="selector"> | |||
| <label class="radio"> | |||
| <input type="radio" name="button" value="ci" checked /> | |||
| <a class="radio-button-label" href="#">Check-In</a> | |||
| </label> | |||
| <label class="radio"> | |||
| <input type="radio" name="button" value="pe" /> | |||
| <a class="radio-button-label" href="/admin/paper.php">Paper Entry</a> | |||
| </label> | |||
| </div> | |||
| </div> | |||
| <div class="form-row"> | |||
| <input type="text" placeholder="Voter Search" id="voter-searchbox" name="voter-searchbox" value="" /> | |||
| <div id="voter-results"></div> | |||
| <input type="hidden" name="voter" id="voter-input" value="0" /> | |||
| <div id="selectedVoter" class="selected candidate voter"> | |||
| <span class="placeholder">No Selected Voter</span> | |||
| </div> | |||
| </div> | |||
| </form> | |||
| <?php | |||
| $footer = new Footer(); | |||
| $footer->output(); | |||
| @@ -0,0 +1,147 @@ | |||
| <?php | |||
| include('../inc/inc.php'); | |||
| if (!$user->loggedin()) { | |||
| header('Location: /login.php'); | |||
| die(); | |||
| } | |||
| if ($user->getRole() !== "admin") { | |||
| header('Location: /index.php'); | |||
| die(); | |||
| } | |||
| if (!empty($_POST['ballot']) && !empty($_POST['candidate'])) { | |||
| $candidate_selected = (int) $_POST['candidate']; | |||
| $voter_selected = (int) $_POST['voter']; | |||
| $ballot = $_POST['ballot']; | |||
| if ($candidate_selected != $_POST['candidate']) $error = "An eccor occurred while processing your ballot. Please retry."; | |||
| if ($voter_selected != $_POST['voter']) $error = "An eccor occurred while processing your ballot. Please retry."; | |||
| if ($ballot !== "PRESIDENT" && $ballot !== "DIRECTOR") $error = "An eccor occurred while processing your ballot. Please retry."; | |||
| if (empty($error)) { | |||
| $result = $db->query("INSERT INTO votes (candidate_id, position, member_id, vote_type, submitter_id) SELECT $candidate_selected, \"$ballot\", $voter_selected, 'IN PERSON', $voter_selected UNION SELECT $candidate_selected, \"$ballot\", voting_id, 'PROXY IN PERSON', delegate_id from proxy where delegate_id=$voter_selected"); | |||
| $candidate = $db->fetchRow('select skymanager_id, name, username, md5(coalesce(email, "")) as `gravatar_hash` from members where skymanager_id=' . $candidate_selected); | |||
| if ($result) { | |||
| $proxy_votes = $db->fetchAssoc("SELECT member_id, submitter_id from votes where submitter_id={$user->voterId()} and position=\"$ballot\""); | |||
| $num_affected_rows = count($proxy_votes); | |||
| if ($num_affected_rows > 1) { | |||
| $proxy_str = ""; | |||
| foreach ($proxy_votes as $proxy_vote) { | |||
| if ($proxy_vote['member_id'] === $proxy_vote['submitter_id']) | |||
| continue; | |||
| $proxy_str .= "#{$proxy_vote['member_id']} "; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| $header = new Header("2021 Michigan Flyers Election : Poll Worker"); | |||
| $header->addStyle("/styles/style.css"); | |||
| $header->addStyle("/styles/admin.css"); | |||
| $header->addStyle("/styles/vote.css"); | |||
| $header->addScript("/js/jquery-1.11.3.min.js"); | |||
| $header->addScript("/js/search.js"); | |||
| $header->addScript("/js/admin-search.js"); | |||
| $header->setAttribute('title', 'Michigan Flyers'); | |||
| $header->setAttribute('tagline', '2021 Election Administration'); | |||
| $header->output(); | |||
| $candidates = $db->fetchAssoc('select skymanager_id, name, username, md5(coalesce(email, "")) as `gravatar_hash` from members where voting_id is not null'); | |||
| $voters = $db->fetchAssoc('select ANY_VALUE(skymanager_id) as `skymanager_id`, ANY_VALUE(members.voting_id) as `voting_id`, ANY_VALUE(name) as `name`, ANY_VALUE(username) as `username`, group_concat(proxy.voting_id) as `proxies`, ANY_VALUE(upstream_proxy.delegate_id) as `delegate`, md5(coalesce(ANY_VALUE(email), "")) as `gravatar_hash` from members left join proxy on (members.voting_id=proxy.delegate_id) left join proxy as upstream_proxy on (upstream_proxy.voting_id=members.voting_id) where members.voting_id is not null group by members.voting_id UNION select skymanager_id, voting_id, name, username, NULL as `proxies`, NULL as `delegate`, md5(coalesce(email, "")) as `gravatar_hash` from members where members.voting_id is null'); | |||
| ?> | |||
| <script type="text/javascript"> | |||
| var voters = <?= json_encode($voters); ?>; | |||
| var candidates = <?= json_encode($candidates); ?>; | |||
| </script> | |||
| <form action="paper.php" method="POST"> | |||
| <div class="form-row"> | |||
| <div class="selector"> | |||
| <label class="radio"> | |||
| <input type="radio" name="button" value="ci" /> | |||
| <a class="radio-button-label" href="/admin/checkin.php">Check-In</a> | |||
| </label> | |||
| <label class="radio"> | |||
| <input type="radio" name="button" value="pe" checked /> | |||
| <a class="radio-button-label" href="#">Paper Entry</a> | |||
| </label> | |||
| </div> | |||
| </div> | |||
| <div class="form-row"> | |||
| <div class="selector"> | |||
| <label class="radio"> | |||
| <input type="radio" id="vote-president" name="ballot" value="PRESIDENT" checked /> | |||
| <span class="radio-button-label">President</span> | |||
| </label> | |||
| <label class="radio"> | |||
| <input type="radio" id="vote-director" name="ballot" value="DIRECTOR" /> | |||
| <span class="radio-button-label">Director-At-Large</span> | |||
| </label> | |||
| </div> | |||
| </div> | |||
| <div class="form-row"> | |||
| <input type="text" placeholder="Voter Search" id="voter-searchbox" name="voter-searchbox" value="" /> | |||
| <div id="voter-results"></div> | |||
| <input type="hidden" name="voter" id="voter-input" value="0" /> | |||
| <div id="selectedVoter" class="selected candidate voter"> | |||
| <span class="placeholder">No Selected Voter</span> | |||
| </div> | |||
| </div> | |||
| <div class="form-row"> | |||
| <input type="text" placeholder="Candidate Search" id="searchbox" name="searchbox" value="" /> | |||
| <div id="results"></div> | |||
| <input type="hidden" name="candidate" id="candidate-input" value="0" /> | |||
| <div id="selectedCandidate" class="selected candidate"> | |||
| <span class="placeholder">No Candidate Selected</span> | |||
| </div> | |||
| </div> | |||
| <div class="form-row"> | |||
| <input class="submit" type="submit" name="submit" value="Submit Ballot" /> | |||
| </div> | |||
| </form> | |||
| <?php if (!empty($_POST['ballot'])): ?> | |||
| <div id="vote-result"> | |||
| <div id="status" class="<?= $result ? "success" : "failure"; ?>"></div> | |||
| <div id="message" class="<?= $result ? "success" : "failure"; ?>"> | |||
| <?= !empty($error) ? $error : ($result ? "This Ballot has been successfully Submitted" : | |||
| "This ballot has already been submitted.") ?> | |||
| </div> | |||
| </div> | |||
| <?php endif; ?> | |||
| <?php if ($result): ?> | |||
| <div id="ballot"> | |||
| <div class="ballot-section"> | |||
| <h4 class="section-heading">Position</h4> | |||
| <h2 class="ballot-position"><?= ucwords(strtolower($ballot)); ?></h2> | |||
| </div> | |||
| <div class="ballot-section"> | |||
| <h4 class="section-heading">Candidate</h4> | |||
| <div id="vote-profile" class="candidate"> | |||
| <div class="profile-icon"> | |||
| <img src="https://www.gravatar.com/avatar/<?= $candidate['gravatar_hash']; ?>.png?d=mp&s=64" /> | |||
| </div> | |||
| <div class="profile"> | |||
| <h2 class="profile-name"><?= $candidate['name']; ?></h2> | |||
| <h4 class="profile-id"><?= $candidate['skymanager_id']; ?></h4> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="ballot-section"> | |||
| <h4 class="section-heading">Voter ID</h4> | |||
| <h4 id="ballot-voter-id">#<?= $user->voterId(); ?></h4> | |||
| </div> | |||
| <?php if ($proxy_str): ?> | |||
| <div class="ballot-section"> | |||
| <h4 class="section-heading">Proxy Votes</h4> | |||
| <h4 id="ballot-voter-id"><?= $proxy_str; ?></h4> | |||
| </div> | |||
| <?php endif; ?> | |||
| </div> | |||
| <?php endif; ?> | |||
| <?php | |||
| $footer = new Footer(); | |||
| $footer->output(); | |||
| @@ -0,0 +1,2 @@ | |||
| order allow,deny | |||
| deny from all | |||
| @@ -0,0 +1,81 @@ | |||
| <?php | |||
| class DBHandler{ | |||
| private $mysql; | |||
| function __construct($hostname, $username, $password, $database){ | |||
| global $dbs; | |||
| $this->mysql = mysqli_connect($hostname, $username, $password); | |||
| if(!$this->mysql) die("MySql error: " . mysql_error()); | |||
| mysqli_select_db($this->mysql, $database); | |||
| } | |||
| public function sanitize($text){ | |||
| return mysqli_real_escape_string($this->mysql, $text); | |||
| } | |||
| public function query($query){ | |||
| return mysqli_query($this->mysql, $query); | |||
| } | |||
| public function fetchRow($query){ | |||
| $result = $this->query($query); | |||
| if($result === false || $result === true) die(mysqli_error($this->mysql));//return $result; | |||
| return mysqli_fetch_assoc($result); | |||
| } | |||
| public function fetchAssoc($query){ | |||
| $result = $this->query($query); | |||
| if($result === false || $result === true) die(mysqli_error($this->mysql));//return $result; | |||
| $data = array(); | |||
| while($row = mysqli_fetch_assoc($result)){ | |||
| $data[] = $row; | |||
| } | |||
| return $data; | |||
| } | |||
| public function getError() { | |||
| return mysqli_error($this->mysql); | |||
| } | |||
| public function lastInsertId(){ | |||
| return mysqli_insert_id($this->mysql); | |||
| } | |||
| public function getAffectedRows() { | |||
| return mysqli_affected_rows($this->mysql); | |||
| } | |||
| public function verifyInteger($input, $minimum = 1){ | |||
| /* | |||
| * Pretty hacky solution. | |||
| * | |||
| * First checks if the integer cast of the input is equal to itself. | |||
| * This filters out decimals, alternate bases, and exponents. | |||
| * Then checks if the input is numeric, which filters out other strings that slip by the first check. | |||
| * This guarantees that it's in a numeric format, which combined with the first filter, should guarantee that it is an integer | |||
| */ | |||
| return (((int) $input == $input) && is_numeric($input) && (($minimum === false) || (int) $input >= $minimum)); | |||
| } | |||
| // Always returns a key of length 40. TODO: Add arbitrary length | |||
| public function randomKey(){ | |||
| //Cryptographically Secure Key | |||
| if(function_exists('openssl_random_pseudo_bytes')) return base64_encode(openssl_random_pseudo_bytes(30)); | |||
| //Fallback (Not cryptographically secure) | |||
| $str = ""; | |||
| for($i=0; $i<30; $i++){ | |||
| $str .= chr(mt_rand(0,255)); | |||
| } | |||
| return base64_encode($str); | |||
| } | |||
| } | |||
| $db = new DBHandler('localhost', '2021mfelection', 'SHa9SGdAlNmafLvJSWjZ', '2021mfelection'); | |||
| @@ -0,0 +1,15 @@ | |||
| <?php | |||
| // Defines | |||
| define('BASE', dirname(__DIR__)); | |||
| define('BASEURL', $_SERVER['SERVER_NAME']); | |||
| // Start the session | |||
| session_start(); | |||
| // Database and Authentication | |||
| require_once(BASE . '/inc/db.php'); | |||
| require_once(BASE . '/inc/user.php'); | |||
| // Templates | |||
| require_once(BASE . '/templates/header.php'); | |||
| require_once(BASE . '/templates/footer.php'); | |||
| @@ -0,0 +1,112 @@ | |||
| <?php | |||
| require_once('db.php'); | |||
| class User{ | |||
| private $username = ""; | |||
| private $email = ""; | |||
| private $name = ""; | |||
| private $uid = -1; | |||
| private $voterId = -1; | |||
| private $loggedin = false; | |||
| private $role = 0; | |||
| function __construct(){ | |||
| if(isset($_SESSION['token']) && strlen($_SESSION['token']) > 41) { | |||
| $this->parseToken($_SESSION['token']); | |||
| } | |||
| } | |||
| public function login($username, $password){ | |||
| $data = http_build_query([ | |||
| 'username' => $username, | |||
| 'password' => $password, | |||
| 'grant_type' => 'password' | |||
| ]); | |||
| $opt = [ | |||
| 'http' => [ | |||
| 'method' => 'POST', | |||
| 'header' => "Content-type: application/x-www-form-urlencoded\r\n" | |||
| . "Content-Length: " . strlen($data) . "\r\n", | |||
| 'content' => $data | |||
| ] | |||
| ]; | |||
| $ctx = stream_context_create($opt); | |||
| $token = file_get_contents('https://beta.schedule.michiganflyers.org/api/oauth/token', false, $ctx); | |||
| if (!empty($token)) { | |||
| $_SESSION['token'] = json_decode($token)->access_token; | |||
| return $this->parseToken($_SESSION['token']); | |||
| } | |||
| return false; | |||
| } | |||
| private function parseToken($token) { | |||
| global $db; | |||
| $data = explode('.', $token); | |||
| if (count($data) != 3) | |||
| return false; | |||
| $obj = json_decode(base64_decode($data[1])); | |||
| $this->username = $obj->preferred_username; | |||
| $this->name = $obj->name; | |||
| $this->uid = $obj->sub; | |||
| $this->email = $obj->email; | |||
| $this->loggedin = true; | |||
| // Get voter ID | |||
| $result = $db->fetchRow('select members.voting_id from members left join proxy on (members.voting_id=proxy.voting_id) where proxy.delegate_id is null and skymanager_id=' . ((int) $this->uid)); | |||
| if ($result) | |||
| $this->voterId = $result['voting_id']; | |||
| else | |||
| $this->voterId = null; | |||
| return true; | |||
| } | |||
| public function username(){ | |||
| return $this->username; | |||
| } | |||
| public function name(){ | |||
| return $this->name; | |||
| } | |||
| public function voterId(){ | |||
| return $this->voterId; | |||
| } | |||
| public function email(){ | |||
| return $this->email; | |||
| } | |||
| public function gravatarUrl($size = 128){ | |||
| return 'https://www.gravatar.com/avatar/' . md5($this->email) . ".png?r=pg&s=$size"; | |||
| } | |||
| public function loggedin(){ | |||
| return $this->loggedin; | |||
| } | |||
| public function getRole(){ | |||
| return $this->username === 'tyzoid' ? 'admin' : 'voter'; | |||
| //return $this->role; | |||
| } | |||
| public function logout(){ | |||
| $_SESSION['token'] = ""; | |||
| $this->username = ""; | |||
| $this->uid = -1; | |||
| $this->loggedin = false; | |||
| } | |||
| public function getUserId(){ | |||
| return $this->uid; | |||
| } | |||
| } | |||
| $user = new User(); | |||
| @@ -0,0 +1,77 @@ | |||
| <?php | |||
| include('inc/inc.php'); | |||
| if (!$user->loggedin()) { | |||
| header('Location: /login.php'); | |||
| die(); | |||
| } | |||
| if (!$user->voterId()) { | |||
| header('Location: /login.php?denied'); | |||
| die(); | |||
| } | |||
| $header = new Header("2021 Michigan Flyers Election"); | |||
| $header->addStyle("/styles/style.css"); | |||
| $header->addScript("/js/jquery-1.11.3.min.js"); | |||
| $header->addScript("/js/search.js"); | |||
| $header->setAttribute('title', 'Michigan Flyers'); | |||
| $header->setAttribute('tagline', '2021 Online Ballot'); | |||
| $header->output(); | |||
| $candidates = $db->fetchAssoc('select skymanager_id, name, username, md5(coalesce(email, "")) as `gravatar_hash` from members where voting_id is not null'); | |||
| $votes = $db->fetchAssoc("select position from votes where member_id={$user->voterId()}"); | |||
| foreach ($votes as &$vote) { | |||
| $vote = $vote['position']; | |||
| } | |||
| unset($vote); | |||
| $president_voted = in_array("PRESIDENT", $votes); | |||
| $director_voted = in_array("DIRECTOR", $votes); | |||
| $president_disabled = $president_voted; | |||
| $director_disabled = $director_voted || !$president_voted; | |||
| $president_disabled_reason = $president_voted ? "You have already voted for President." : ""; | |||
| $director_disabled_reason = $director_disabled ? ($director_voted ? "You have already voted for Director." : "You must vote for President first.") : ""; | |||
| ?> | |||
| <script type="text/javascript"> | |||
| var candidates = <?= json_encode($candidates); ?>; | |||
| </script> | |||
| <form action="vote.php" method="POST"> | |||
| <div class="form-row"> | |||
| <div class="selector"> | |||
| <label class="radio"> | |||
| <input type="radio" id="vote-president" name="ballot" | |||
| value="PRESIDENT" <?= $president_disabled ? "disabled" : "checked"; ?> /> | |||
| <span class="radio-button-label">President</span> | |||
| <?php if ($president_disabled_reason): ?> | |||
| <div class="hover-tooltip"><?= $president_disabled_reason; ?></div> | |||
| <?php endif; ?> | |||
| </label> | |||
| <label class="radio"> | |||
| <input type="radio" id="vote-director" name="ballot" | |||
| value="DIRECTOR" <?= $director_disabled ? "disabled" : ($president_disabled ? "checked" : ""); ?> /> | |||
| <span class="radio-button-label">Director-At-Large</span> | |||
| <?php if ($director_disabled_reason): ?> | |||
| <div class="hover-tooltip"><?= $director_disabled_reason; ?></div> | |||
| <?php endif; ?> | |||
| </label> | |||
| </div> | |||
| </div> | |||
| <div class="form-row"> | |||
| <input type="text" placeholder="Candidate Search" id="searchbox" name="searchbox" value="" /> | |||
| <div id="results"></div> | |||
| <input type="hidden" name="candidate" id="candidate-input" value="0" /> | |||
| <div id="selectedCandidate" class="selected candidate"> | |||
| <span class="placeholder">No Candidate Selected</span> | |||
| </div> | |||
| </div> | |||
| <div class="form-row"> | |||
| <input class="submit" type="submit" name="submit" value="Submit Ballot" /> | |||
| </div> | |||
| </form> | |||
| <?php | |||
| $footer = new Footer(); | |||
| $footer->output(); | |||
| @@ -0,0 +1,97 @@ | |||
| $(function(){ | |||
| function isMatch(candidate, text) { | |||
| if (candidate.name.toLowerCase().includes(text)) | |||
| return true; | |||
| if (candidate.username.toLowerCase().includes(text)) | |||
| return true; | |||
| if (("" + candidate.skymanager_id).includes(text)) | |||
| return true; | |||
| if (candidate.voting_id && ("" + candidate.voting_id).includes(text)) | |||
| return true; | |||
| return false; | |||
| } | |||
| function createChild(candidate) { | |||
| var li = document.createElement('li'); | |||
| li.className = 'candidate'; | |||
| var profileimgsect = document.createElement('div'); | |||
| profileimgsect.className = 'profile-icon'; | |||
| var profileimg = document.createElement('img'); | |||
| profileimg.src = 'https://www.gravatar.com/avatar/' + candidate.gravatar_hash + '.png?d=mp&s=64'; | |||
| profileimgsect.appendChild(profileimg); | |||
| var profiletext = document.createElement('div'); | |||
| profiletext.className = 'profile'; | |||
| var profilename = document.createElement('h2'); | |||
| profilename.className = 'profile-name'; | |||
| profilename.textContent = candidate.name; | |||
| var profileid = document.createElement('h4'); | |||
| profileid.className = 'profile-id'; | |||
| profileid.textContent = candidate.voting_id; | |||
| if (!candidate.voting_id || candidate.delegate) { | |||
| profiletext.className += ' ineligible'; | |||
| profileid.textContent += " (Ineligible)"; | |||
| } | |||
| profiletext.appendChild(profilename); | |||
| profiletext.appendChild(profileid); | |||
| if (candidate.proxies) { | |||
| var proxies = document.createElement('div'); | |||
| proxies.className = 'proxies'; | |||
| proxies.textContent = 'Proxy Votes: ' + candidate.proxies.replace(',', ', '); | |||
| profiletext.appendChild(proxies); | |||
| } | |||
| li.appendChild(profileimgsect); | |||
| li.appendChild(profiletext); | |||
| li.addEventListener('click', function() { | |||
| var csc = document.getElementById("selectedVoter"); | |||
| while (csc.firstChild) { | |||
| csc.removeChild(csc.firstChild); | |||
| } | |||
| csc.appendChild(profileimgsect); | |||
| csc.appendChild(profiletext); | |||
| document.getElementById('voter-input').value = candidate.voting_id; | |||
| document.getElementById('voter-searchbox').value = ""; | |||
| search(''); | |||
| }); | |||
| return li; | |||
| } | |||
| function search(text) { | |||
| var list = document.createElement('ul'); | |||
| var matches = 0; | |||
| for (var i = 0; text.length > 0 && i < voters.length; i++) { | |||
| if (isMatch(voters[i], text.toLowerCase())) { | |||
| list.appendChild(createChild(voters[i])); | |||
| if (++matches > 4) | |||
| break; | |||
| } | |||
| } | |||
| var container = document.getElementById('voter-results'); | |||
| while (container.firstChild) { | |||
| container.removeChild(container.firstChild); | |||
| } | |||
| if (matches) | |||
| container.appendChild(list); | |||
| } | |||
| $('#voter-searchbox').bind('textInput input', function() { search(this.value); }); | |||
| }); | |||
| @@ -0,0 +1,83 @@ | |||
| $(function(){ | |||
| function isMatch(candidate, text) { | |||
| if (candidate.name.toLowerCase().includes(text)) | |||
| return true; | |||
| if (candidate.username.toLowerCase().includes(text)) | |||
| return true; | |||
| if (("" + candidate.skymanager_id).includes(text)) | |||
| return true; | |||
| return false; | |||
| } | |||
| function createChild(candidate) { | |||
| var li = document.createElement('li'); | |||
| li.className = 'candidate'; | |||
| var profileimgsect = document.createElement('div'); | |||
| profileimgsect.className = 'profile-icon'; | |||
| var profileimg = document.createElement('img'); | |||
| profileimg.src = 'https://www.gravatar.com/avatar/' + candidate.gravatar_hash + '.png?d=mp&s=64'; | |||
| profileimgsect.appendChild(profileimg); | |||
| var profiletext = document.createElement('div'); | |||
| profiletext.className = 'profile'; | |||
| var profilename = document.createElement('h2'); | |||
| profilename.className = 'profile-name'; | |||
| profilename.textContent = candidate.name; | |||
| var profileid = document.createElement('h4'); | |||
| profileid.className = 'profile-id'; | |||
| profileid.textContent = candidate.skymanager_id; | |||
| profiletext.appendChild(profilename); | |||
| profiletext.appendChild(profileid); | |||
| li.appendChild(profileimgsect); | |||
| li.appendChild(profiletext); | |||
| li.addEventListener('click', function() { | |||
| var csc = document.getElementById("selectedCandidate"); | |||
| while (csc.firstChild) { | |||
| csc.removeChild(csc.firstChild); | |||
| } | |||
| csc.appendChild(profileimgsect); | |||
| csc.appendChild(profiletext); | |||
| document.getElementById('candidate-input').value = candidate.skymanager_id; | |||
| document.getElementById('searchbox').value = ""; | |||
| search(''); | |||
| }); | |||
| return li; | |||
| } | |||
| function search(text) { | |||
| var list = document.createElement('ul'); | |||
| var matches = 0; | |||
| for (var i = 0; text.length > 0 && i < candidates.length; i++) { | |||
| if (isMatch(candidates[i], text.toLowerCase())) { | |||
| list.appendChild(createChild(candidates[i])); | |||
| if (++matches > 4) | |||
| break; | |||
| } | |||
| } | |||
| var container = document.getElementById('results'); | |||
| while (container.firstChild) { | |||
| container.removeChild(container.firstChild); | |||
| } | |||
| if (matches) | |||
| container.appendChild(list); | |||
| } | |||
| //$('#searchbox').bind('change keypress keydown keyup', function() { search(this.value); }); | |||
| $('#searchbox').bind('textInput input', function() { search(this.value); }); | |||
| }); | |||
| @@ -0,0 +1,50 @@ | |||
| <?php | |||
| require_once("inc/inc.php"); | |||
| $alsovalid = array('-', '_'); | |||
| if (isset($_GET['denied'])) { | |||
| $error = "Your account is not eligible to submit a ballot."; | |||
| } else if (isset($_GET['logout'])) { | |||
| $user->logout(); | |||
| } else if (isset($_POST['login'])){ | |||
| if(isset($_POST['username']) && isset($_POST['password']) && !empty($_POST['username']) && !empty($_POST['password'])){ | |||
| $username = $_POST['username']; | |||
| $password = $_POST['password']; | |||
| if($user->login($username, $password)){ | |||
| header("Location: /"); | |||
| die(); | |||
| } else { | |||
| $error = "Incorrect username or password."; | |||
| } | |||
| } else { | |||
| $error = "Must fill in both username and password."; | |||
| } | |||
| } | |||
| $header = new Header("Login Required"); | |||
| $header->addStyle("/styles/style.css"); | |||
| $header->setAttribute('title', 'Michigan Flyers'); | |||
| $header->setAttribute('tagline', '2021 Online Ballot'); | |||
| $header->output(); | |||
| ?> | |||
| <h3 id="login-help">Sign in with your Skymanager Account</h3> | |||
| <?php | |||
| if(isset($error)) echo "<span class=\"errormessage\">$error</span>"; | |||
| ?> | |||
| <form action="login.php" method="POST"> | |||
| <div class="form-row"> | |||
| <label for="username">Username</label> | |||
| <input class="login" type="text" name="username" /> | |||
| </div> | |||
| <div class="form-row"> | |||
| <label for="password">Password</label> | |||
| <input class="login" type="password" name="password" /> | |||
| </div> | |||
| <div class="form-row"> | |||
| <input class="loginbutton" type="submit" name="login" value="Log In" /> | |||
| </div> | |||
| </form> | |||
| <?php | |||
| $footer = new Footer(); | |||
| $footer->output(); | |||
| @@ -0,0 +1,7 @@ | |||
| div#selectedCandidate::before { | |||
| content: 'Selected Voter'; | |||
| } | |||
| .profile.ineligible { | |||
| color: #c00; | |||
| } | |||
| @@ -0,0 +1,267 @@ | |||
| body, html{ | |||
| width:100%; | |||
| height:100%; | |||
| padding:0px; | |||
| margin:0px; | |||
| font-family: 'Fira Sans', sans-serif; | |||
| font-size:20px; | |||
| color: #000; | |||
| } | |||
| h1, h2, h3, h4, h5, h6{ | |||
| font-weight: 800; | |||
| margin:0px; | |||
| padding:0px; | |||
| } | |||
| h3, h4, h5, h6 { | |||
| font-weight: 600; | |||
| } | |||
| span.errormessage { | |||
| color: #c00; | |||
| padding: 10px 0px; | |||
| font-size: 0.8rem; | |||
| } | |||
| #login-help { | |||
| margin: 0px; | |||
| padding: 10px 0px; | |||
| } | |||
| div#container { | |||
| position:relative; | |||
| min-height:100%; | |||
| } | |||
| div.loginbar { | |||
| height:36px; | |||
| background-color:#11111a; | |||
| padding:0px 10px; | |||
| line-height:36px; | |||
| font-size: 0.75rem; | |||
| border-bottom:1px solid #000; | |||
| color:#fff; | |||
| } | |||
| div.loginbar a { | |||
| display:block; | |||
| padding:6px 10px; | |||
| height:24px; | |||
| line-height:24px; | |||
| float:right; | |||
| color:#aaa; | |||
| text-decoration:none; | |||
| } | |||
| div.loginbar a:hover { | |||
| background-color:#444; | |||
| color:#fff; | |||
| text-decoration:underline; | |||
| } | |||
| div.header { | |||
| background-color:#333; | |||
| padding:20px; | |||
| color:#fff; | |||
| border-top:1px solid #444; | |||
| border-bottom:4px solid #6ac; | |||
| } | |||
| div.content { | |||
| } | |||
| div.page { | |||
| padding:0px 20px 20px; | |||
| } | |||
| div.message { | |||
| padding-bottom:5px; | |||
| } | |||
| div.footercontainer { | |||
| position:absolute; | |||
| bottom:0px; | |||
| width:100%; | |||
| height:100px; | |||
| } | |||
| div.footercontainer div.footer { | |||
| border-top:4px solid #6ac; | |||
| background-color:#333; | |||
| padding:20px; | |||
| color:#fff; | |||
| height:56px; | |||
| } | |||
| div#results { | |||
| position: absolute; | |||
| z-index: 1; | |||
| left: 0; | |||
| right: 0; | |||
| background-color: #fff; | |||
| } | |||
| div.selected.candidate::before { | |||
| position: absolute; | |||
| left: 10px; | |||
| top: -11px; | |||
| height: 20px; | |||
| padding: 0px 5px; | |||
| line-height: 20px; | |||
| background-color: #fff; | |||
| content: 'Selected Candidate'; | |||
| font-size: 0.6rem; | |||
| color: rgba(0,0,0,0.6); | |||
| display: block; | |||
| text-align: center; | |||
| } | |||
| div.selected.candidate.voter::before { | |||
| content: 'Selected Voter'; | |||
| } | |||
| form .form-row:nth-child(2n+1) div.selected.candidate::before { | |||
| background-color: #eee; | |||
| } | |||
| div.selected.candidate { | |||
| border: 1px solid rgba(0, 0, 0, .2); | |||
| position: relative; | |||
| margin-top: 20px; | |||
| padding: 10px 10px 0px 10px; | |||
| border-radius: 10px; | |||
| } | |||
| div.selected.candidate span.placeholder { | |||
| font-size: 0.6rem; | |||
| color: rgba(0, 0, 0, .8); | |||
| } | |||
| .candidate { | |||
| border: 0px solid rgba(0,0,0,.2); | |||
| border-top-width: 1px; | |||
| border-bottom-width: 1px; | |||
| display: flex; | |||
| } | |||
| #results ul { | |||
| margin: 0; | |||
| padding: 0; | |||
| } | |||
| li.candidate { | |||
| list-style-type: none; | |||
| padding: 20px; | |||
| cursor: pointer; | |||
| } | |||
| .candidate > div.profile-icon { | |||
| padding: 2px; | |||
| margin-right: 20px; | |||
| } | |||
| .candidate > div.profile-icon img { | |||
| border-radius: 100%; | |||
| border: 2px solid #fff; | |||
| box-shadow: 0px 0px 0px 2px #000; | |||
| } | |||
| .candidate > div.profile { | |||
| flex-grow: 1; | |||
| } | |||
| .candidate .profile-id::before { | |||
| content: 'ID: '; | |||
| } | |||
| form, form input { | |||
| font-size: 1rem; | |||
| } | |||
| form label { | |||
| display: block; | |||
| } | |||
| form div.selector { | |||
| display: flex; | |||
| } | |||
| form div.selector label { | |||
| margin: 0px 10px; | |||
| flex-grow: 1; | |||
| } | |||
| form label.radio input { | |||
| display: none; | |||
| } | |||
| form label.radio .radio-button-label { | |||
| text-decoration: none; | |||
| color: #000; | |||
| text-align: center; | |||
| display: block; | |||
| padding: 10px; | |||
| border: 2px solid rgba(0,0,0,.2); | |||
| border-radius: 10px; | |||
| } | |||
| form label.radio input:checked+.radio-button-label { | |||
| background-color: #000; | |||
| color: #fff; | |||
| border: 2px solid #fff; | |||
| box-shadow: 0px 0px 0px 2px #000; | |||
| } | |||
| form label.radio { | |||
| position: relative; | |||
| } | |||
| form label.radio div.hover-tooltip { | |||
| position: absolute; | |||
| pointer-events: none; | |||
| opacity: 0; | |||
| z-index: 2; | |||
| transition: opacity 300ms ease-in-out; | |||
| margin-top: 5px; | |||
| padding: 10px; | |||
| border-radius: 10px; | |||
| background-color: #fff; | |||
| border: 2px solid rgba(0, 0, 0, .2); | |||
| } | |||
| form label.radio div.hover-tooltip::before { | |||
| position:absolute; | |||
| content: ' '; | |||
| background-color: #fff; | |||
| top: -10px; | |||
| left: 20px; | |||
| width: 16px; | |||
| height: 16px; | |||
| transform: rotate(45deg); | |||
| border: 2px solid rgba(0, 0, 0, .2); | |||
| border-bottom-color: transparent; | |||
| border-right-color: transparent; | |||
| } | |||
| form label.radio:hover div.hover-tooltip { | |||
| position: absolute; | |||
| opacity: 1; | |||
| } | |||
| form .form-row { | |||
| padding: 10px 20px; | |||
| margin: 0px -20px; | |||
| } | |||
| form .form-row:nth-child(2n+1) { | |||
| background-color: #eee; | |||
| } | |||
| form input { | |||
| box-sizing: border-box; | |||
| width: 100%; | |||
| display:block; | |||
| padding: 10px; | |||
| } | |||
| @@ -0,0 +1,120 @@ | |||
| div#vote-result { | |||
| padding: 20px 0px; | |||
| display: flex; | |||
| align-items: center; | |||
| justify-content: center; | |||
| } | |||
| div#vote-result div#status{ | |||
| flex-shrink: 0; | |||
| flex-grow: 0; | |||
| flex-basis: 60px; | |||
| width: 60px; | |||
| height: 60px; | |||
| border-radius: 100%; | |||
| line-height: 60px; | |||
| text-align: center; | |||
| border: 2px solid transparent; | |||
| font-size: 1.4rem; | |||
| } | |||
| div#vote-result div#status.success::before { content: '\2714'; } | |||
| div#vote-result div#status.failure::before { content: '\2718'; } | |||
| div#vote-result div#status.success { | |||
| border-color: #0a0; | |||
| color: #0a0; | |||
| } | |||
| div#vote-result div#status.failure { | |||
| background-color: #a00; | |||
| color: #fff; | |||
| } | |||
| div#vote-result div#message { | |||
| margin-left: 20px; | |||
| } | |||
| div#ballot { | |||
| border: 2px solid rgba(0, 0, 0, .2); | |||
| padding: 0px 10px; | |||
| border-radius: 10px; | |||
| overflow: hidden; | |||
| } | |||
| div#ballot div.ballot-section { | |||
| border-bottom: 2px solid rgba(0, 0, 0, .2); | |||
| margin: 0px -10px; | |||
| padding: 0px 10px; | |||
| } | |||
| div#ballot div.ballot-section:last-child { | |||
| border-bottom-width: 0px; | |||
| } | |||
| div#ballot h4#ballot-voter-id { | |||
| padding: 5px 0px; | |||
| } | |||
| div.ballot-section .candidate { | |||
| border: 0; | |||
| } | |||
| div.ballot-section h4.section-heading { | |||
| display: inline-block; | |||
| height: 30px; | |||
| line-height: 30px; | |||
| width: auto; | |||
| background-color: #000; | |||
| color: #fff; | |||
| padding: 0px 30px 0px 10px; | |||
| margin: 0px -10px; | |||
| position:relative; | |||
| } | |||
| div.ballot-section h4.section-heading::after { | |||
| position: absolute; | |||
| right: -29px; | |||
| content: ' '; | |||
| height: 0px; | |||
| width: 0px; | |||
| border: 15px solid transparent; | |||
| border-left: 15px solid #000; | |||
| border-top: 15px solid #000; | |||
| } | |||
| div.ballot-section h2.ballot-position { | |||
| margin: 0px -10px; | |||
| padding: 5px 10px; | |||
| } | |||
| div#ballot div#vote-profile { | |||
| padding: 12px 10px 10px; | |||
| margin: 0px -10px; | |||
| max-width: 600px; | |||
| align-items: center; | |||
| justify-content: center; | |||
| margin-bottom: 10px; | |||
| } | |||
| a#vote-again { | |||
| text-decoration: none; | |||
| font-size: 1rem; | |||
| text-align: center; | |||
| margin: 0px auto 10px; | |||
| display: inline-block; | |||
| position: relative; | |||
| left: 50%; | |||
| transform: translateX(-50%); | |||
| padding: 10px 20px; | |||
| border: 2px solid #6ac; | |||
| background-color: rgba(102, 170, 204, .2); | |||
| border-radius: 10px; | |||
| color: #000; | |||
| transition: background-color 125ms ease-in-out; | |||
| } | |||
| a#vote-again:hover { | |||
| background-color: rgba(102, 170, 204, .6); | |||
| } | |||
| @@ -0,0 +1,63 @@ | |||
| <?php | |||
| /** | |||
| * Footer Template | |||
| * | |||
| * Uses HTML5, but all template HTML is (should be) xHTML 1.1 compatable | |||
| */ | |||
| class Footer{ | |||
| private $scripts = array(); | |||
| /** | |||
| * Constructor | |||
| * | |||
| * @param string $title - the title of the page | |||
| * @param mixed $scripts - the url (or array of urls) of the script(s) to include | |||
| */ | |||
| public function __construct($title = null, $stylesheets = null){ | |||
| // Page Title | |||
| if (! empty($title)) $this->title = $title; | |||
| if (! empty($scripts) && is_array($scripts)) $this->scripts = array_merge($scripts, $this->scripts); | |||
| else if (! empty($scripts)) $this->addScript($scripts); | |||
| } | |||
| public function setTitle($title = "Ranks"){ | |||
| $this->title = $title; | |||
| } | |||
| public function addScript($script){ | |||
| if (empty($script) || strncmp($script, '/', 1) !== 0) return false; | |||
| $scripts[] = $script; | |||
| return true; | |||
| } | |||
| public function output($return = false){ | |||
| // End Content/page div | |||
| $html = "\t\t\t\t</div>\n"; | |||
| $html = "\t\t\t</div>\n"; | |||
| // Footer content | |||
| /* | |||
| $html .= "\t\t\t<div class=\"footercontainer\">\n"; | |||
| $html .= "\t\t\t\t<div class=\"footer\">\n"; | |||
| $html .= "\t\t\t\t\t<h2>Copyright © 2015. All Rights Reserved</h2>\n"; | |||
| $html .= "\t\t\t\t</div>\n"; | |||
| $html .= "\t\t\t</div>\n"; | |||
| */ | |||
| // End Container | |||
| $html .= "\t\t</div>\n"; | |||
| // Add any scripts to be included at the bottom of the page. | |||
| foreach ($this->scripts as $script){ | |||
| echo "<script type=\"text/javascript\" src=\"$script\"></script>"; | |||
| } | |||
| $html .= "\t</body>\n"; | |||
| $html .= "</html>\n"; | |||
| if($return === true) return $html; | |||
| echo $html; | |||
| } | |||
| } | |||
| @@ -0,0 +1,122 @@ | |||
| <?php | |||
| /** | |||
| * Header Template | |||
| * | |||
| * Uses HTML5, but all template HTML is (should be) xHTML 1.1 compatable | |||
| */ | |||
| class Header{ | |||
| private $title = "Template"; | |||
| private $scripts = array(); | |||
| private $stylesheets = array( | |||
| "https://fonts.googleapis.com/css2?family=Fira+Sans:wght@400;600;800&display=swap" | |||
| ); | |||
| /* | |||
| * The Attributes array. Holds data for the page. | |||
| */ | |||
| private $attributes = array( | |||
| "title" => "Template", | |||
| "tagline" => "A Tyzoid Production", | |||
| "showbar" => true, | |||
| "loginbar" => "" | |||
| ); | |||
| /** | |||
| * Constructor | |||
| * | |||
| * @param string $title - the title of the page | |||
| * @param mixed $scripts - the url (or array of urls) of the script(s) to include | |||
| * @param mixed $stylesheets - the url (or array of urls) of the stylesheet(s) to include | |||
| */ | |||
| public function __construct($title = null, $scripts = null, $stylesheets = null){ | |||
| // Page Title | |||
| if (! empty($title)) $this->title = $title; | |||
| if (! empty($scripts) && is_array($scripts)) $this->scripts = array_merge($scripts, $this->scripts); | |||
| else if (! empty($scripts)) $this->addScript($scripts); | |||
| if (! empty($stylesheets) && is_array($stylesheets)) $this->stylesheets = array_merge($stylesheets, $this->stylesheets); | |||
| else if (! empty($stylesheets)) $this->addStyle($stylesheets); | |||
| } | |||
| public function setTitle($title){ | |||
| $this->title = $title; | |||
| } | |||
| public function addScript($script){ | |||
| if (empty($script) || strncmp($script, '/', 1) !== 0) return false; | |||
| $this->scripts[] = $script; | |||
| return true; | |||
| } | |||
| public function addStyle($stylesheet){ | |||
| if (empty($stylesheet) || strncmp($stylesheet, '/', 1) !== 0) return false; | |||
| $this->stylesheets[] = $stylesheet; | |||
| return true; | |||
| } | |||
| public function setAttribute($attribute, $value){ | |||
| $this->attributes[$attribute] = $value; | |||
| } | |||
| public function output($return = false){ | |||
| global $user; | |||
| // Doctype | |||
| $html = "<!doctype html>\n"; | |||
| $html .= "<html>\n"; | |||
| $html .= "\t<head>\n"; | |||
| // Page Attributes/Head information | |||
| $html .= "\t\t<title>" . $this->title . "</title>"; | |||
| // Set mobile-friendly | |||
| $html .= '<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />'; | |||
| // Stylesheet import | |||
| foreach ($this->stylesheets as $style){ | |||
| $html .= "\t\t<link rel=\"stylesheet\" type=\"text/css\" href=\"$style\" />\n"; | |||
| } | |||
| // Script import | |||
| foreach ($this->scripts as $script){ | |||
| $html .= "\t\t<script type=\"text/javascript\" src=\"$script\"></script>\n"; | |||
| } | |||
| // Start Body of the page | |||
| $html .= "\t</head>\n"; | |||
| $html .= "\t<body>\n"; | |||
| // Page layout | |||
| $html .= "\t\t<div id=\"container\">\n"; | |||
| // Login/Statusbar | |||
| $html .= "\t\t\t<div class=\"loginbar\">\n"; | |||
| if ($user->loggedin()) { | |||
| $html .= "\t\t\t\t" . $user->name() . "\n"; | |||
| $html .= "\t\t\t\t" . " <a href=\"/login.php?logout\">Log Out</a>\n"; | |||
| if ($user->getRole() === "admin") | |||
| $html .= "\t\t\t\t" . " <a href=\"/admin/checkin.php\">Poll Worker</a>\n"; | |||
| $html .= "\t\t\t\t<a href=\"/index.php\">Home</a>\n"; | |||
| } else if (basename($_SERVER['PHP_SELF']) != 'login.php') { | |||
| $html .= "\t\t\t\t<a href=\"/login.php\">Log In</a>\n"; | |||
| } else { | |||
| $html .= "\t\t\t\t<a href=\"/index.php\">Home</a>\n"; | |||
| } | |||
| $html .= "\t\t\t</div>\n"; | |||
| $html .= "\t\t\t<div class=\"header\">\n"; | |||
| $html .= "\t\t\t\t<h1>{$this->attributes['title']}</h1>\n"; | |||
| $html .= "\t\t\t\t<h2>{$this->attributes['tagline']}</h2>\n"; | |||
| $html .= "\t\t\t</div>\n"; | |||
| $html .= "\t\t\t<div class=\"content\">\n"; | |||
| $html .= "\t\t\t\t<div class=\"page\">\n"; | |||
| //End divs should be closed in the footer template | |||
| if($return === true) return $html; | |||
| echo $html; | |||
| } | |||
| } | |||
| @@ -0,0 +1,118 @@ | |||
| <?php | |||
| include('inc/inc.php'); | |||
| if (!$user->loggedin()) { | |||
| header('Location: /login.php'); | |||
| die(); | |||
| } | |||
| if (!$user->voterId()) { | |||
| header('Location: /login.php?denied'); | |||
| die(); | |||
| } | |||
| $candidate_selected = (int) $_POST['candidate']; | |||
| $ballot = $_POST['ballot']; | |||
| if ($candidate_selected != $_POST['candidate']) $error = "An eccor occurred while processing your ballot. Please retry."; | |||
| if ($ballot !== "PRESIDENT" && $ballot !== "DIRECTOR") $error = "An eccor occurred while processing your ballot. Please retry."; | |||
| if (!$error) { | |||
| //$result = $db->query("INSERT INTO votes (candidate_id, position, member_id) values ($candidate_selected, \"$ballot\", {$user->voterId()})"); | |||
| $result = $db->query("INSERT INTO votes (candidate_id, position, member_id, vote_type, submitter_id) SELECT $candidate_selected, \"$ballot\", {$user->voterId()}, 'ONLINE', {$user->voterId()} UNION SELECT $candidate_selected, \"$ballot\", voting_id, 'PROXY ONLINE', delegate_id from proxy where delegate_id={$user->voterId()}"); | |||
| $candidate = $db->fetchRow('select skymanager_id, name, username, md5(coalesce(email, "")) as `gravatar_hash` from members where skymanager_id=' . $candidate_selected); | |||
| if ($result) { | |||
| $to = 'mf2021elec@gmail.com'; | |||
| $from = 'noreply@tyzoid.com'; | |||
| $subject = "[TEST] Ballot Submitted ({$user->voterId()} -> {$candidate['skymanager_id']})"; | |||
| $headers = | |||
| "From: {$from}\r\n" . | |||
| "Message-ID: 2021election-voter-{$user->voterId()}-{$ballot}-" . mt_rand() . "@tyzoid.com\r\n"; | |||
| $body = "Position: " . ucwords(strtolower($ballot)) . "\r\n" . | |||
| "Candidate: {$candidate['name']} (ID #{$candidate['skymanager_id']})\r\n" . | |||
| "Voter #{$user->voterId()}\r\n"; | |||
| $proxy_votes = $db->fetchAssoc("SELECT member_id, submitter_id from votes where submitter_id={$user->voterId()} and position=\"$ballot\""); | |||
| $num_affected_rows = count($proxy_votes); | |||
| if ($num_affected_rows > 1) { | |||
| $proxy_str = ""; | |||
| foreach ($proxy_votes as $proxy_vote) { | |||
| if ($proxy_vote['member_id'] === $proxy_vote['submitter_id']) | |||
| continue; | |||
| $proxy_str .= "#{$proxy_vote['member_id']} "; | |||
| } | |||
| $body .= "Proxies: $proxy_str\r\n"; | |||
| } | |||
| if (!mail($to, $subject, $body, $headers, "-f$from")) | |||
| $error = "Ballot audit record failed to create"; | |||
| } | |||
| } | |||
| $votes = $db->fetchAssoc("select position from votes where member_id={$user->voterId()}"); | |||
| foreach ($votes as &$vote) { | |||
| $vote = $vote['position']; | |||
| } | |||
| unset($vote); | |||
| $voteFor = null; | |||
| if (count($votes) == 1) { | |||
| $voteFor = $votes[0] == "PRESIDENT" ? "Director" : "President"; | |||
| } | |||
| $header = new Header("2021 Michigan Flyers Election"); | |||
| $header->addStyle("/styles/style.css"); | |||
| $header->addStyle("/styles/vote.css"); | |||
| $header->addScript("/js/jquery-1.11.3.min.js"); | |||
| $header->addScript("/js/search.js"); | |||
| $header->setAttribute('title', 'Michigan Flyers'); | |||
| $header->setAttribute('tagline', '2021 Online Ballot'); | |||
| $header->output(); | |||
| ?> | |||
| <div id="vote-result"> | |||
| <div id="status" class="<?= $result ? "success" : "failure"; ?>"></div> | |||
| <div id="message" class="<?= $result ? "success" : "failure"; ?>"> | |||
| <?= $error ? $error : ($result ? "Your Ballot has been successfully Submitted" : | |||
| "Your ballot has already been submitted.") ?> | |||
| </div> | |||
| </div> | |||
| <?php if ($voteFor): ?> | |||
| <a href="/" id="vote-again">Vote for <?= $voteFor; ?></a> | |||
| <?php endif; ?> | |||
| <?php if ($result): ?> | |||
| <div id="ballot"> | |||
| <div class="ballot-section"> | |||
| <h4 class="section-heading">Position</h4> | |||
| <h2 class="ballot-position"><?= ucwords(strtolower($ballot)); ?></h2> | |||
| </div> | |||
| <div class="ballot-section"> | |||
| <h4 class="section-heading">Candidate</h4> | |||
| <div id="vote-profile" class="candidate"> | |||
| <div class="profile-icon"> | |||
| <img src="https://www.gravatar.com/avatar/<?= $candidate['gravatar_hash']; ?>.png?d=mp&s=64" /> | |||
| </div> | |||
| <div class="profile"> | |||
| <h2 class="profile-name"><?= $candidate['name']; ?></h2> | |||
| <h4 class="profile-id"><?= $candidate['skymanager_id']; ?></h4> | |||
| </div> | |||
| </div> | |||
| </div> | |||
| <div class="ballot-section"> | |||
| <h4 class="section-heading">Voter ID</h4> | |||
| <h4 id="ballot-voter-id">#<?= $user->voterId(); ?></h4> | |||
| </div> | |||
| <?php if ($proxy_str): ?> | |||
| <div class="ballot-section"> | |||
| <h4 class="section-heading">Proxy Votes</h4> | |||
| <h4 id="ballot-voter-id"><?= $proxy_str; ?></h4> | |||
| </div> | |||
| <?php endif; ?> | |||
| </div> | |||
| <?php endif; ?> | |||
| <?php | |||
| $footer = new Footer(); | |||
| $footer->output(); | |||