Multiple choices and redirections

+ Now possible to allow multiple choices;
* Add partial settings support;
* Fixed and moved some redirections to appropriate pages;
* CSS adjustments.
This commit is contained in:
Madeorsk 2018-08-16 14:09:37 +02:00
parent b7ece5cdad
commit 7b0a619b0e
6 changed files with 52 additions and 29 deletions

View File

@ -41,8 +41,14 @@ Flight::route("GET /polls/@id:[a-fA-F0-9]+", function ($id) {
Flight::json(format_poll($poll)); Flight::json(format_poll($poll));
else else
{ {
Flight::render("poll", ["poll" => $poll], "body_content"); // If unique_ip option is enabled => Only allow unregistered IPs.
Flight::render("layout"); if (!$poll->settings->unique_ip && isset($poll->ips[Flight::request()->ip]))
Flight::redirect("/polls/$id/results"); // A vote is already registered with this IP: redirect.
else
{
Flight::render("poll", ["poll" => $poll], "body_content");
Flight::render("layout");
}
} }
} }
else else
@ -59,17 +65,14 @@ Flight::route("POST /polls/@id:[a-fA-F0-9]+/vote", function ($id) {
{ {
if (isset(Flight::request()->data["options"]) && is_array(Flight::request()->data["options"])) if (isset(Flight::request()->data["options"]) && is_array(Flight::request()->data["options"]))
{ // Check that an options id array exists. { // Check that an options id array exists.
//TODO Check that only the authorized number of options are selected. if ($poll->vote(Flight::request()->data["options"])) // Vote for the given options.
if($poll->vote(Flight::request()->data["options"]) === false) // Vote for the given options.
{
Flight::halt(403, "<h1>403 Forbidden</h1><h3>Too many votes for this IP address.</h3>");
}
else
{ {
// Then save and show poll data. // Then save and show poll data.
$poll->save(); $poll->save();
Flight::json(format_poll($poll)); Flight::json(format_poll($poll));
} }
else
Flight::halt(403, "<h1>403 Forbidden</h1><h3>Too many votes for this IP address or too many options selected.</h3>");
} }
else else
Flight::halt(403, "<h1>403 Forbidden</h1><h3>Invalid data.</h3>"); Flight::halt(403, "<h1>403 Forbidden</h1><h3>Invalid data.</h3>");
@ -79,18 +82,16 @@ Flight::route("POST /polls/@id:[a-fA-F0-9]+/vote", function ($id) {
if (isset(Flight::request()->data["options"])) if (isset(Flight::request()->data["options"]))
{ // Check that any data has been sent. { // Check that any data has been sent.
$selected_options = Flight::request()->data["options"]; $selected_options = Flight::request()->data["options"];
if (is_string($selected_options)) if (is_array($selected_options))
{ // If it is a string, input[type="radio"] were used so only one option is selected. {
if($poll->vote([intval($selected_options)]) === false) // Vote for the selected option. if($poll->vote($selected_options)) // Vote for the selected option.
{
Flight::redirect('/', 401);
}
else
{ {
$poll->save(); $poll->save();
Flight::redirect("/polls/$id/results"); // Redirect to the results. Flight::redirect("/polls/$id/results"); // Redirect to the results.
} }
} //TODO: Multiple options case. else
Flight::redirect("/polls/$id"); // Error: Redirect to the vote page.
}
else else
Flight::redirect("/polls/$id"); // Error: Redirect to the vote page. Flight::redirect("/polls/$id"); // Error: Redirect to the vote page.
} }

View File

@ -4,6 +4,16 @@ require __DIR__ . "/../config/app.php";
define("SAVE_PATH", __DIR__ . "/../db"); define("SAVE_PATH", __DIR__ . "/../db");
define("DEFAULT_SETTINGS", ["unique_ip" => true, "multiple_choices" => false]);
function settings_or_default($settings)
{
return [
"unique_ip" => isset($settings["unique_ip"]) ? $settings["unique_ip"] : DEFAULT_SETTINGS["unique_ip"],
"multiple_choices" => isset($settings["multiple_choices"]) ? $settings["multiple_choices"] : DEFAULT_SETTINGS["multiple_choices"],
];
}
class Poll class Poll
{ {
/** /**
@ -23,7 +33,7 @@ class Poll
"votes" => 0, "votes" => 0,
]; ];
} }
$poll->settings = $request_data->settings; $poll->settings = isset($request_data->settings) ? settings_or_default($request_data->settings) : DEFAULT_SETTINGS;
$poll->gen_new_id(); $poll->gen_new_id();
$poll->delete_token = bin2hex(openssl_random_pseudo_bytes(16)); $poll->delete_token = bin2hex(openssl_random_pseudo_bytes(16));
$poll->save(); $poll->save();
@ -70,7 +80,7 @@ class Poll
public $title; public $title;
public $creation_date; public $creation_date;
public $options = []; public $options = [];
public $settings = []; public $settings;
public $ips = []; public $ips = [];
public $delete_token; public $delete_token;
@ -98,14 +108,20 @@ class Poll
*/ */
public function vote(array $options) public function vote(array $options)
{ {
if($this->settings->unique_ip === false) if (empty($options))
{ return false; // Disallow void selection vote. TODO: Allow it in settings?
if(isset($this->ips[Flight::request()->ip]))
return false; if (!$this->settings->unique_ip)
{ // If unique_ip option is enabled => Only allow unregistered IPs.
if (isset($this->ips[Flight::request()->ip]))
return false; // A vote is already registered with this IP: error.
else else
$this->ips[Flight::request()->ip] = true; $this->ips[Flight::request()->ip] = true; // We register the IP in the used IPs array.
} }
if (!$this->settings->multiple_choices && count($options) > 1)
return false; // If multiple_choices is not selected, disallow an options array with more than one selected option.
// For each option in the list, add 1 to the vote number in the poll data. // For each option in the list, add 1 to the vote number in the poll data.
foreach ($options as $option) foreach ($options as $option)
if (isset($this->options[intval($option)])) // Check invalid options id. if (isset($this->options[intval($option)])) // Check invalid options id.
@ -125,7 +141,7 @@ class Poll
"options" => $this->options, "options" => $this->options,
"delete_token" => $this->delete_token, "delete_token" => $this->delete_token,
"ips" => $this->ips, "ips" => $this->ips,
"settings" => $this->settings "settings" => $this->settings,
]), $db); ]), $db);
dba_close($db); dba_close($db);
} }

View File

@ -83,7 +83,7 @@ main a.button:hover
main hr main hr
{ {
border: solid #343434 thin; border: solid #0088E5 thin;
width: 25rem; width: 25rem;
} }

View File

@ -37,9 +37,10 @@ document.addEventListener("DOMContentLoaded", () => {
body: JSON.stringify({ body: JSON.stringify({
title: form.querySelector(`input[name="title"]`).value, title: form.querySelector(`input[name="title"]`).value,
options: get_choices(form), options: get_choices(form),
settings: { settings: {
"unique_ip": form.querySelector(`input[name="unique_ip"]`).checked, unique_ip: form.querySelector(`input[name="unique_ip"]`).checked,
} multiple_choices: form.querySelector(`input[name="multiple_choices"]`).checked,
}
}), }),
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",

View File

@ -15,6 +15,11 @@
<label for="unique_ip" class="check"></label> <label for="unique_ip" class="check"></label>
<label for="unique_ip">Allow multiple votes from a single IP</label> <label for="unique_ip">Allow multiple votes from a single IP</label>
</div> </div>
<div class="option">
<input type="checkbox" name="multiple_choices" value="multiple_choices" id="multiple_choices" />
<label for="multiple_choices" class="check"></label>
<label for="multiple_choices">Allow to select multiple choices in one vote</label>
</div>
<input type="submit" value="Create poll" /> <input type="submit" value="Create poll" />
</form> </form>
<div id="result" hidden> <div id="result" hidden>

View File

@ -3,7 +3,7 @@
<form action="/polls/<?= $poll->id ?>/vote" method="POST" id="poll"> <form action="/polls/<?= $poll->id ?>/vote" method="POST" id="poll">
<?php foreach ($poll->options as $id => $option): ?> <?php foreach ($poll->options as $id => $option): ?>
<div class="option"> <div class="option">
<input type="radio" name="options" value="<?= $id ?>" id="option-<?= $id ?>" /> <input type="<?= $poll->settings->multiple_choices ? "checkbox" : "radio" ?>" name="options[]" value="<?= $id ?>" id="option-<?= $id ?>" />
<label for="option-<?= $id ?>" class="check"></label> <label for="option-<?= $id ?>" class="check"></label>
<label for="option-<?= $id ?>"><?= $option->label ?></label> <label for="option-<?= $id ?>"><?= $option->label ?></label>
</div> </div>