Last active
January 8, 2024 01:18
-
-
Save davidmintz/2f1ec5b472f7e3e52b038f0ca9b75be9 to your computer and use it in GitHub Desktop.
quick and dirty survey in PHP and Javascript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* collect and record a "vote" for a simple survey with one question, three choices. | |
* | |
* I thought it would be so simple that it would be easy to do in one physical file without | |
* getting too monolithic. Not so. But... oh well. | |
*/ | |
/** | |
* @return PDO | |
*/ | |
function get_db_connection() : PDO | |
{ | |
$path = __DIR__.'/../../data/database.sqlite'; | |
$db = new PDO("sqlite:$path"); | |
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); | |
return $db; | |
} | |
function fetch_member(PDO $db, string $token) :? stdClass | |
{ | |
$stmt = $db->prepare('SELECT * FROM members WHERE hash = :token',); | |
$stmt->execute(['token'=>$token]); | |
try { return $stmt->fetchObject(); } catch (\Exception $e) { | |
} | |
return null; | |
} | |
function validate_token(PDO $db) : string | |
{ | |
$token = $_GET['t'] ?: null; | |
$error = false; | |
if (! $token) { | |
return '<p id="token-element" class="alert alert-danger p-3"><strong>Error:</strong> missing URL token. Please check your URL and try again.</p>'; | |
} | |
$db = get_db_connection(); | |
try { | |
// validate the token | |
$member = fetch_member($db, $token); | |
//exit("shit? " . __LINE__); | |
if (! $member) { | |
$error = '<p id="token-element" class="alert alert-danger p-3"><strong>Sorry!</strong> Your URL token is not valid. Please check your URL and try again.</p>'; | |
} | |
//exit("shit?"); | |
// make sure they have not yet voted | |
elseif ($member->vote) { | |
$error = sprintf('<p id="token-element" data-token="" class="alert alert-danger p-3"><strong>Sorry!</strong> A vote has already been cast by (or on behalf of) member <strong>%s</strong></p>', | |
// $token, | |
$member->name); | |
} | |
if ($error) { | |
return $error; | |
} | |
// all good | |
return sprintf('<p id="token-element" data-token="%s" class="alert alert-info p-3">Welcome %s. Please make your choice and hit submit.</p>',$token, $member->name); | |
} catch (\Exception $e) { | |
return '<p id="token-element" class="alert alert-danger p-3"><strong>Damn!</strong> database access error. Sorry!</p>'; | |
} | |
} | |
if (date('Ymd.Hi') > '20240108.1200') { | |
exit('Sorry. The voting period ended at 12:00 pm Monday, 08 Jan 2024'); | |
} | |
if (!empty($_SERVER)) { | |
if ($_SERVER['REQUEST_METHOD'] == 'GET') { | |
$output = validate_token($db = get_db_connection()); | |
} elseif($_SERVER['REQUEST_METHOD'] == 'POST') { | |
// record vote | |
// input is (should be) via xhr | |
// can't just read $_POST. I didn't know this either, till now | |
$request_body = file_get_contents('php://input'); | |
$post = json_decode($request_body); | |
// validate | |
header('Content-type: application/json',true); | |
if (empty($post->vote) or ! in_array($post->vote, [1,2,3])) { | |
exit(json_encode(['message'=>"You must submit a valid vote",'status' => 'FAILED'])); | |
} | |
if (empty($post->token)) { | |
exit(json_encode(['message'=>"Your request is missing an identification token",'status' => 'FAILED'])); | |
} | |
// find the member again, make sure they exist and have not voted | |
$db = get_db_connection(); | |
$member = fetch_member($db, $post->token); | |
$error = null; | |
if (! $member) { | |
$error = 'Invalid URL identification token. Please double-check your URL and try again.'; | |
} elseif ($member->vote) { | |
// because they could legitimately load the page in two different tabs or windows, etc | |
$error = "A vote has already been cast on by (or on behalf of) $member->name"; | |
} | |
if ($error) { | |
exit(json_encode(['message' => $error, 'status'=>'FAILED'])); | |
} | |
// all good | |
try { | |
$stmt = $db->prepare('UPDATE members SET vote = :vote, vote_datetime = datetime() WHERE hash = :token'); | |
$result = $stmt->execute(['vote' => $post->vote,'token'=>$post->token]); | |
} catch (\Exception $e) { | |
exit(json_encode(['message' => 'Shit. Database error. Sorry!', 'status'=>'FAILED'])); | |
} | |
exit(json_encode(['message' => 'Success! Your vote has been recorded. Thank you.', 'status'=>'OK'])); | |
} else { | |
$output = 'invalid request method'; | |
exit($output); | |
} | |
} else exit("wtf?!"); | |
?> | |
<!doctype html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<meta name="description" content="residents of Martha's Vineyard, Massachussets, USA, who demand a ceasefire in Gaza"> | |
<meta property="og:type" content="website" /> | |
<meta property="og:title" content="Ceasefire MV | MV For Palestine" /> | |
<meta property="og:image" content="https://ceasefiremv.org/images/logo.png" /> | |
<meta property="og:description" content="Ceasefire MV, also know as MV For Palestine, is a group of people residing on Martha's Vineyard, Massachussets, USA, who demand an immediate and permanent ceasefire and a halt to the Israeli military assault on Gaza."> | |
<title>Ceasefire MV | MV For Palestine: Stop The War On Gaza</title> | |
<style> | |
body { max-width: 1200px; margin: auto; } | |
</style> | |
<script src="https://unpkg.com/axios/dist/axios.min.js"></script> | |
<link href="/css/bootstrap.min.css" rel="stylesheet"> | |
</head> | |
<main class="container"> | |
<div class="row"> | |
<div class="col-md-7 offset-md-3"> | |
<div class="d-flex justify-content-between align-items-center"> | |
<div class="w-100 text-center "> | |
<h1 class="text-center py-3 text">CEASEFIRE NOW!</h1> | |
</div> | |
<img style="max-height: 220px" src="/images/logo.png" alt="CeasefireMV: Islanders for Palestine" class="img-fluid"> | |
</div> | |
<?= $output ?> | |
<p>My preference is</p> | |
<form id="form-vote" class="" action="/survey/index.php" method="post"> | |
<input type="radio" name="vote" value="2"> groups.io for email management<br> | |
<input type="radio" name="vote" value="3"> ordinary email messages to 30+ recipients<br> | |
<input type="radio" name="vote" value="1"> I don't care<br> | |
<input id="btn-submit" class="btn btn-primary mt-2" type="submit" value="submit"> | |
</form> | |
</div> | |
</div> | |
</main> | |
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script> | |
</body> | |
<script> | |
const button = document.getElementById("btn-submit"); | |
const status_element = document.getElementById('token-element'); | |
const token = status_element.getAttribute("data-token"); | |
if (! token) { | |
document.getElementById('btn-submit').setAttribute("disabled", "disabled"); | |
//console.log("SHIT SHOULD BE disabled"); | |
} | |
button.addEventListener("click", function(e){ | |
e.preventDefault(); | |
let form_data = new FormData(document.getElementById("form-vote")); | |
axios.post("/survey/index.php",{vote: form_data.get("vote"), token }) | |
.then(function(response){ | |
let data = response.data; | |
if (data.status === 'FAILED') { | |
console.log('shit failed: '+ data.message); | |
status_element.classList.remove("alert-info"); | |
status_element.classList.add("alert-danger"); | |
} else { | |
status_element.classList.remove("alert-info"); | |
status_element.classList.remove("alert-danger"); | |
status_element.classList.add("alert-success"); | |
document.getElementById('btn-submit').setAttribute("disabled", "disabled"); | |
} | |
status_element.innerText = data.message; | |
}); | |
}); | |
</script> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment