| <?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(); |
| <?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(); |
| order allow,deny | |||||
| deny from all |
| <?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'); |
| <?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'); |
| <?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(); |
| <?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(); |
| $(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); }); | |||||
| }); |
| $(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); }); | |||||
| }); |
| <?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(); |
| div#selectedCandidate::before { | |||||
| content: 'Selected Voter'; | |||||
| } | |||||
| .profile.ineligible { | |||||
| color: #c00; | |||||
| } |
| 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; | |||||
| } |
| 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); | |||||
| } |
| <?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; | |||||
| } | |||||
| } |
| <?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; | |||||
| } | |||||
| } |
| <?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(); |