diff --git a/app/classes/Framadate/Form.php b/app/classes/Framadate/Form.php index 339f9e3..cd50e6d 100644 --- a/app/classes/Framadate/Form.php +++ b/app/classes/Framadate/Form.php @@ -51,6 +51,24 @@ class Form */ public $hidden; + /** + * If true, a password will be needed to access the poll + * @var boolean + */ + public $use_password; + + /** + * The password needed to access the poll, if $use_password is set to true + * @var string + */ + public $password; + + /** + * If true, the polls results will be also visible for those without password + * @var boolean + */ + public $results_publicly_visible; + /** * List of available choices */ diff --git a/app/classes/Framadate/Repositories/PollRepository.php b/app/classes/Framadate/Repositories/PollRepository.php index d0e3db9..3554d66 100644 --- a/app/classes/Framadate/Repositories/PollRepository.php +++ b/app/classes/Framadate/Repositories/PollRepository.php @@ -11,12 +11,12 @@ class PollRepository extends AbstractRepository { parent::__construct($connect); } - public function insertPoll($poll_id, $admin_poll_id, $form) { + public function insertPoll($poll_id, $admin_poll_id, $form, $password_hash, $results_publicly_visible) { $sql = 'INSERT INTO `' . Utils::table('poll') . '` - (id, admin_id, title, description, admin_name, admin_mail, end_date, format, editable, receiveNewVotes, receiveNewComments) - VALUES (?,?,?,?,?,?,FROM_UNIXTIME(?),?,?,?,?)'; + (id, admin_id, title, description, admin_name, admin_mail, end_date, format, editable, receiveNewVotes, receiveNewComments, hidden, password_hash, results_publicly_visible) + VALUES (?,?,?,?,?,?,FROM_UNIXTIME(?),?,?,?,?,?,?,?)'; $prepared = $this->prepare($sql); - $prepared->execute(array($poll_id, $admin_poll_id, $form->title, $form->description, $form->admin_name, $form->admin_mail, $form->end_date, $form->format, $form->editable, $form->receiveNewVotes, $form->receiveNewComments)); + $prepared->execute(array($poll_id, $admin_poll_id, $form->title, $form->description, $form->admin_name, $form->admin_mail, $form->end_date, $form->format, $form->editable, $form->receiveNewVotes, $form->receiveNewComments, $form->hidden, $password_hash, $results_publicly_visible)); } function findById($poll_id) { diff --git a/app/classes/Framadate/Security/PasswordHasher.php b/app/classes/Framadate/Security/PasswordHasher.php index 44f4729..4d1156f 100644 --- a/app/classes/Framadate/Security/PasswordHasher.php +++ b/app/classes/Framadate/Security/PasswordHasher.php @@ -15,7 +15,7 @@ class PasswordHasher { /** * Hash a password * - * @param $password the password to hash. + * @param string $password the password to hash. * @return false|string the hashed password, or false on failure. The used algorithm, cost and salt are returned as part of the hash. */ public static function hash($password) { @@ -25,8 +25,8 @@ class PasswordHasher { /** * Verify a password with a hash * - * @param $password the password to verify - * @param $hash the hash to compare. + * @param string $password the password to verify + * @param string $hash the hash to compare. * @return bool */ public static function verify($password, $hash) { diff --git a/app/classes/Framadate/Services/PollService.php b/app/classes/Framadate/Services/PollService.php index a666041..1be93cd 100644 --- a/app/classes/Framadate/Services/PollService.php +++ b/app/classes/Framadate/Services/PollService.php @@ -20,8 +20,10 @@ namespace Framadate\Services; use Framadate\Form; use Framadate\FramaDB; -use Framadate\Repositories\RepositoryFactory; +use Framadate\Utils; use Framadate\Security\Token; +use Framadate\Security\PasswordHasher; +use Framadate\Repositories\RepositoryFactory; class PollService { @@ -118,9 +120,18 @@ class PollService { } while ($this->pollRepository->existsById($poll_id)); $admin_poll_id = $poll_id . $this->random(8); + // Password hash, if needed + if ($form->use_password) { + $password_hash = PasswordHasher::hash($form->password); + $results_publicly_visible = $form->results_publicly_visible; + } else { + $password_hash = null; + $results_publicly_visible = null; + } + // Insert poll + slots $this->pollRepository->beginTransaction(); - $this->pollRepository->insertPoll($poll_id, $admin_poll_id, $form); + $this->pollRepository->insertPoll($poll_id, $admin_poll_id, $form, $password_hash, $results_publicly_visible); $this->slotRepository->insertSlots($poll_id, $form->getChoices()); $this->pollRepository->commit(); diff --git a/create_poll.php b/create_poll.php index 43c5b4a..3faa240 100644 --- a/create_poll.php +++ b/create_poll.php @@ -19,6 +19,7 @@ use Framadate\Form; use Framadate\Services\InputService; +use Framadate\Editable; use Framadate\Utils; include_once __DIR__ . '/app/inc/init.php'; @@ -60,12 +61,18 @@ if ($goToStep2) { $receiveNewVotes = isset($_POST['receiveNewVotes']) ? $inputService->filterBoolean($_POST['receiveNewVotes']) : false; $receiveNewComments = isset($_POST['receiveNewComments']) ? $inputService->filterBoolean($_POST['receiveNewComments']) : false; $hidden = isset($_POST['hidden']) ? $inputService->filterBoolean($_POST['hidden']) : false; + $use_password = filter_input(INPUT_POST, 'use_password', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => BOOLEAN_REGEX]]); + $password = isset($_POST['password'])?$_POST['password']:null; + $password_repeat = isset($_POST['password_repeat'])?$_POST['password_repeat']:null; + $results_publicly_visible = filter_input(INPUT_POST, 'results_publicly_visible', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => BOOLEAN_REGEX]]); // On initialise également les autres variables $error_on_mail = false; $error_on_title = false; $error_on_name = false; $error_on_description = false; + $error_on_password = false; + $error_on_password_repeat = false; $_SESSION['form']->title = $title; $_SESSION['form']->admin_name = $name; @@ -75,6 +82,10 @@ if ($goToStep2) { $_SESSION['form']->receiveNewVotes = $receiveNewVotes; $_SESSION['form']->receiveNewComments = $receiveNewComments; $_SESSION['form']->hidden = $hidden; + $_SESSION['form']->use_password = ($use_password !== null); + $_SESSION['form']->password = $password; + $_SESSION['form']->results_publicly_visible = ($results_publicly_visible !== null); + if ($config['use_smtp'] == true) { if (empty($mail)) { @@ -101,7 +112,16 @@ if ($goToStep2) { $email_OK = true; } - if ($title && $name && $email_OK && !$error_on_title && !$error_on_description && !$error_on_name) { + if ($use_password) { + if (empty($password)) { + $error_on_password = true; + } else if ($password != $password_repeat) { + $error_on_password_repeat = true; + } + } + + if ($title && $name && $email_OK && !$error_on_title && !$error_on_description && !$error_on_name + && !$error_on_password && !$error_on_password_repeat) { if ($goToStep2 == 'date') { header('Location:create_date_poll.php'); @@ -143,6 +163,16 @@ $errors = array( 'msg' => '', 'aria' => '', 'class' => '' + ), + 'password' => array( + 'msg' => '', + 'aria' => '', + 'class' => '' + ), + 'password_repeat' => array( + 'msg' => '', + 'aria' => '', + 'class' => '' ) ); @@ -182,6 +212,17 @@ if (!empty($_POST[GO_TO_STEP_2])) { $errors['email']['class'] = ' has-error'; $errors['email']['msg'] = __('Error', 'The address is not correct! You should enter a valid email address (like r.stallman@outlock.com) in order to receive the link to your poll.'); } + + if ($error_on_password) { + $errors['password']['aria'] = 'aria-describeby="poll_password_error" '; + $errors['password']['class'] = ' has-error'; + $errors['password']['msg'] = __('Error', 'Password is empty'); + } + if ($error_on_password_repeat) { + $errors['password_repeat']['aria'] = 'aria-describeby="poll_password_repeat_error" '; + $errors['password_repeat']['class'] = ' has-error'; + $errors['password_repeat']['msg'] = __('Error', 'Passwords do not match'); + } } $useRemoteUser = USE_REMOTE_USER && isset($_SERVER['REMOTE_USER']); @@ -201,6 +242,9 @@ $smarty->assign('poll_editable', Utils::fromPostOrDefault('editable', $_SESSION[ $smarty->assign('poll_receiveNewVotes', Utils::fromPostOrDefault('receiveNewVotes', $_SESSION['form']->receiveNewVotes)); $smarty->assign('poll_receiveNewComments', Utils::fromPostOrDefault('receiveNewComments', $_SESSION['form']->receiveNewComments)); $smarty->assign('poll_hidden', Utils::fromPostOrDefault('hidden', $_SESSION['form']->hidden)); +$smarty->assign('poll_use_password', Utils::fromPostOrDefault('use_password', $_SESSION['form']->use_password)); +$smarty->assign('poll_results_publicly_visible', Utils::fromPostOrDefault('results_publicly_visible', $_SESSION['form']->results_publicly_visible)); +$smarty->assign('poll_password', Utils::fromPostOrDefault('password', $_SESSION['form']->password)); $smarty->assign('form', $_SESSION['form']); $smarty->display('create_poll.tpl'); diff --git a/js/app/create_poll.js b/js/app/create_poll.js index 66521dc..4a77ab5 100644 --- a/js/app/create_poll.js +++ b/js/app/create_poll.js @@ -18,6 +18,9 @@ $(document).ready(function () { + /** + * Error check when submitting form + */ $("#formulaire").submit(function (event) { var isHidden = $("#hidden").prop('checked'); var isOptionAllUserCanModifyEverything = $("#editableByAll").is(":checked"); @@ -30,6 +33,18 @@ $(document).ready(function () { } }); + /** + * Hide/Show password options + */ + var usePassword = $("#use_password"); + usePassword.change(function(){ + if (usePassword.prop("checked")) { + $("#password_options").removeClass("hidden"); + } else { + $("#password_options").addClass("hidden"); + } + }); + // Check cookies are enabled too var cookieEnabled = function () { var cookieEnabled = navigator.cookieEnabled; diff --git a/locale/de.json b/locale/de.json index 3147a0e..3390065 100644 --- a/locale/de.json +++ b/locale/de.json @@ -211,6 +211,10 @@ "To receive an email for each new vote": "Bei jeder neuen Wertung eine E-Mail erhalten", "To receive an email for each new comment": "Bei jedem neuen Kommentar eine E-Mail erhalten", "Only the poll maker can see the poll's results": "Einzig der Autor der Abstimmung kann die Ergebnisse einsehen.", + "Use a password to restrict access.": "DE_Utiliser un mot de passe pour restreindre l'accès au sondage.", + "The results are publicly visible.": "DE_Les résultats sont visibles sans mot de passe.", + "Poll password": "DE_Mot de passe", + "Confirm password": "DE_Confirmer votre mot de passe.", "Go to step 2": "Weiter zum 2. Schritt" }, "Step 2": { @@ -338,6 +342,9 @@ "You can't create a poll with hidden results with the following edition option:": "Sie können mit der folgenden Editier-Option keine Umfrage mit versteckten Ergebnissen erzeugen:", "Failed to delete column": "Löschen der Spalte fehlgeschlagen", "MISSING_VALUES": "Fehlende Werte", - "CANT_CONNECT_TO_DATABASE": "Kann nicht mit der Datenbank verbinden" + "CANT_CONNECT_TO_DATABASE": "Kann nicht mit der Datenbank verbinden", + "Password is empty": "DE_Le mot de passe est vide.", + "Passwords do not match": "DE_Les mot de passes ne correspondent pas." + } } diff --git a/locale/en.json b/locale/en.json index 7cf3d2c..8b004d6 100644 --- a/locale/en.json +++ b/locale/en.json @@ -211,6 +211,10 @@ "To receive an email for each new vote": "Receive an email for each new vote", "To receive an email for each new comment": "Receive an email for each new comment", "Only the poll maker can see the poll's results": "Only the poll maker can see the poll results", + "Use a password to restrict access.": "Use a password to restrict access.", + "The results are publicly visible.": "The results are publicly visible.", + "Poll password": "Password", + "Confirm password": "Confirmer votre mot de passe.", "Go to step 2": "Go to step 2" }, "Step 2": { @@ -272,9 +276,9 @@ "Expiration date": "Expiry date", "Votes": "Votes", "Actions": "Actions", - "See the poll": "Go to poll", - "Change the poll": "Change poll", - "Deleted the poll": "Delete the poll", + "See the poll": "See the poll", + "Change the poll": "Change the poll", + "Deleted the poll": "Deleted the poll", "Summary": "Summary", "Success": "Success", "Fail": "Fail", @@ -338,6 +342,8 @@ "You can't create a poll with hidden results with the following edition option:": "You can't create a poll with hidden results with the following option: ", "Failed to delete column": "Failed to delete column", "MISSING_VALUES": "Missing values", - "CANT_CONNECT_TO_DATABASE": "Unable to connect to database" + "CANT_CONNECT_TO_DATABASE": "Unable to connect to database", + "Password is empty": "Password is empty.", + "Passwords do not match": "Passwords do not match." } } diff --git a/locale/es.json b/locale/es.json index 37391f5..51a6991 100644 --- a/locale/es.json +++ b/locale/es.json @@ -211,6 +211,10 @@ "To receive an email for each new vote": "Usted quiere recibir un correo electónico cada vez que alguien participe a la encuesta", "To receive an email for each new comment": "ES_Recevoir un courriel à chaque commentaire", "Only the poll maker can see the poll's results": "ES_Seul le créateur du sondage peut voir les résultats", + "Use a password to restrict access.": "ES_Utiliser un mot de passe pour restreindre l'accès au sondage.", + "The results are publicly visible.": "ES_Les résultats sont visibles sans mot de passe.", + "Poll password": "ES_Mot de passe", + "Confirm password": "ES_Confirmer votre mot de passe.", "Go to step 2": "ES_Aller à l'étape 2" }, "Step 2": { @@ -338,6 +342,9 @@ "You can't create a poll with hidden results with the following edition option:": "ES_Vous ne pouvez pas créer de sondage avec résulats cachés avec les options d'éditions suivantes : ", "Failed to delete column": "Error al eliminar la columna", "MISSING_VALUES": "Los valores perdidos", - "CANT_CONNECT_TO_DATABASE": "No se puede conectar a la base de datos" + "CANT_CONNECT_TO_DATABASE": "No se puede conectar a la base de datos", + "Password is empty": "ES_Le mot de passe est vide.", + "Passwords do not match": "ES_Les mot de passes ne correspondent pas." + } } diff --git a/locale/fr.json b/locale/fr.json index af50990..4f3cc62 100644 --- a/locale/fr.json +++ b/locale/fr.json @@ -211,6 +211,10 @@ "To receive an email for each new vote": "Recevoir un courriel à chaque participation d'un sondé", "To receive an email for each new comment": "Recevoir un courriel à chaque commentaire", "Only the poll maker can see the poll's results": "Seul le créateur du sondage peut voir les résultats", + "Use a password to restrict access.": "Utiliser un mot de passe pour restreindre l'accès au sondage.", + "The results are publicly visible.": "Les résultats sont visibles sans mot de passe.", + "Poll password": "Mot de passe", + "Confirm password": "Confirmer votre mot de passe.", "Go to step 2": "Aller à l'étape 2" }, "Step 2": { @@ -263,7 +267,6 @@ "Migration": "Migration", "Purge": "Purge", "Logs": "Historique", - "Installation": "Installation", "Poll ID": "ID sondage", "Format": "Format", "Title": "Titre", diff --git a/tpl/create_poll.tpl b/tpl/create_poll.tpl index 8e98899..43b1fcb 100644 --- a/tpl/create_poll.tpl +++ b/tpl/create_poll.tpl @@ -49,7 +49,7 @@ {/if} -
+ {$errors['password']['msg']} +
++ {$errors['password_repeat']['msg']} +
+