From ff47a21ac292b190a4558256a9ef761e91536033 Mon Sep 17 00:00:00 2001
From: Thomas Citharel <tcit@tcit.fr>
Date: Tue, 20 Feb 2018 16:47:10 +0100
Subject: [PATCH] Check that conditions on valueMax are acceptable before
 adding the vote

Handles people voting when the answers valueMax has been reached by someone else in the background

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
---
 adminstuds.php                                |  5 +++
 .../Exception/ConcurrentVoteException.php     | 12 +++++
 .../Framadate/Services/PollService.php        | 45 +++++++++++++++++++
 locale/br.json                                |  1 +
 locale/de.json                                |  1 +
 locale/en.json                                |  1 +
 locale/es.json                                |  1 +
 locale/fr.json                                |  1 +
 locale/it.json                                |  1 +
 locale/nl.json                                |  1 +
 locale/oc.json                                |  3 +-
 studs.php                                     |  5 +++
 12 files changed, 76 insertions(+), 1 deletion(-)
 create mode 100644 app/classes/Framadate/Exception/ConcurrentVoteException.php

diff --git a/adminstuds.php b/adminstuds.php
index 0d030590..74a205a8 100644
--- a/adminstuds.php
+++ b/adminstuds.php
@@ -19,6 +19,7 @@
 use Framadate\Editable;
 use Framadate\Exception\AlreadyExistsException;
 use Framadate\Exception\ConcurrentEditionException;
+use Framadate\Exception\ConcurrentVoteException;
 use Framadate\Exception\MomentAlreadyExistsException;
 use Framadate\Message;
 use Framadate\Security\PasswordHasher;
@@ -224,6 +225,8 @@ if (!empty($_POST['save'])) { // Save edition of an old vote
             }
         } catch (ConcurrentEditionException $cee) {
             $message = new Message('danger', __('Error', 'Poll has been updated before you vote'));
+        } catch (ConcurrentVoteException $cve) {
+            $message = new Message('danger', __('Error', "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry."));
         }
     }
 } elseif (isset($_POST['save'])) { // Add a new vote
@@ -251,6 +254,8 @@ if (!empty($_POST['save'])) { // Save edition of an old vote
             $message = new Message('danger', __('Error', 'You already voted'));
         } catch (ConcurrentEditionException $cee) {
             $message = new Message('danger', __('Error', 'Poll has been updated before you vote'));
+        } catch (ConcurrentVoteException $cve) {
+            $message = new Message('danger', __('Error', "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry."));
         }
     }
 }
diff --git a/app/classes/Framadate/Exception/ConcurrentVoteException.php b/app/classes/Framadate/Exception/ConcurrentVoteException.php
new file mode 100644
index 00000000..26b29ecc
--- /dev/null
+++ b/app/classes/Framadate/Exception/ConcurrentVoteException.php
@@ -0,0 +1,12 @@
+<?php
+namespace Framadate\Exception;
+
+/**
+ * Class ConcurrentVoteException
+ *
+ * Thrown when a poll has a maximum votes constraint for options, and a vote happened since the poll was rendered
+ */
+class ConcurrentVoteException extends \Exception {
+    function __construct() {
+    }
+}
diff --git a/app/classes/Framadate/Services/PollService.php b/app/classes/Framadate/Services/PollService.php
index c36c55ca..a47edc20 100644
--- a/app/classes/Framadate/Services/PollService.php
+++ b/app/classes/Framadate/Services/PollService.php
@@ -20,6 +20,7 @@ namespace Framadate\Services;
 
 use Framadate\Exception\AlreadyExistsException;
 use Framadate\Exception\ConcurrentEditionException;
+use Framadate\Exception\ConcurrentVoteException;
 use Framadate\Form;
 use Framadate\FramaDB;
 use Framadate\Repositories\RepositoryFactory;
@@ -82,9 +83,22 @@ class PollService {
         return $slots;
     }
 
+    /**
+     * @param $poll_id
+     * @param $vote_id
+     * @param $name
+     * @param $choices
+     * @param $slots_hash
+     * @return bool
+     * @throws ConcurrentEditionException
+     * @throws ConcurrentVoteException
+     */
     public function updateVote($poll_id, $vote_id, $name, $choices, $slots_hash) {
         $poll = $this->findById($poll_id);
 
+        // Check that no-one voted in the meantime and it conflicts the maximum votes constraint
+        $this->checkMaxVotes($choices, $poll, $poll_id);
+
         // Check if slots are still the same
         $this->checkThatSlotsDidntChanged($poll, $slots_hash);
 
@@ -93,9 +107,22 @@ class PollService {
         return $this->voteRepository->update($poll_id, $vote_id, $name, $choices);
     }
 
+    /**
+     * @param $poll_id
+     * @param $name
+     * @param $choices
+     * @param $slots_hash
+     * @return \stdClass
+     * @throws AlreadyExistsException
+     * @throws ConcurrentEditionException
+     * @throws ConcurrentVoteException
+     */
     function addVote($poll_id, $name, $choices, $slots_hash) {
         $poll = $this->findById($poll_id);
 
+        // Check that no-one voted in the meantime and it conflicts the maximum votes constraint
+        $this->checkMaxVotes($choices, $poll, $poll_id);
+
         // Check if slots are still the same
         $this->checkThatSlotsDidntChanged($poll, $slots_hash);
 
@@ -252,4 +279,22 @@ class PollService {
             throw new ConcurrentEditionException();
         }
     }
+
+    /**
+     * This method checks if the votes doesn't conflicts the maximum votes constraint
+     *
+     * @param $user_choice
+     * @param \stdClass $poll
+     * @param string $poll_id
+     * @throws ConcurrentVoteException
+     */
+    private function checkMaxVotes($user_choice, $poll, $poll_id) {
+        $best_choices = $this->computeBestChoices($this->allVotesByPollId($poll_id));
+        foreach ($best_choices['y'] as $i => $nb_choice) {
+            // if for this option we have reached maximum value and user wants to add itself too
+            if ($nb_choice >= $poll->ValueMax && $user_choice[$i] === "2") {
+                throw new ConcurrentVoteException();
+            }
+        }
+    }
 }
diff --git a/locale/br.json b/locale/br.json
index 9f8990a3..b20e00e7 100644
--- a/locale/br.json
+++ b/locale/br.json
@@ -395,6 +395,7 @@
         "Adding vote failed": "C'hwitadenn war ouzhpennadenn ar vouezh",
         "You already voted": "Mouezhzt ho peus endeo",
         "Poll has been updated before you vote": "Hizivaet eo bet ar sontadeg a-raok ho mouezh",
+        "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.": "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.",
         "Comment failed": "C'hiwtadenn war an evezhiadenn",
         "You can't create a poll with hidden results with the following edition option:": "N'hallit ket krouiñ ur sontadeg gant respontoù kuzhet gant an dibarzhioù embann da heul:",
         "Failed to delete column": "C'hwitadenn war zilemel ar bann",
diff --git a/locale/de.json b/locale/de.json
index 8b32bb73..1e100a5a 100644
--- a/locale/de.json
+++ b/locale/de.json
@@ -396,6 +396,7 @@
         "Adding vote failed": "Stimmabgabe fehlgeschlagen",
         "You already voted": "Sie haben bereits abgestimmt",
         "Poll has been updated before you vote": "Die Abstimmung wurde vor Ihrer Stimmabgabe aktualisiert",
+        "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.": "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.",
         "Comment failed": "Abgabe des Kommentars gescheitert",
         "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",
diff --git a/locale/en.json b/locale/en.json
index c412a76d..c487a841 100644
--- a/locale/en.json
+++ b/locale/en.json
@@ -399,6 +399,7 @@
         "Adding vote failed": "Adding vote failed",
         "You already voted": "You already voted",
         "Poll has been updated before you vote": "Poll has been updated before you vote",
+        "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.": "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.",
         "Comment failed": "Comment failed",
         "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",
diff --git a/locale/es.json b/locale/es.json
index a2675243..3c0ebc9d 100644
--- a/locale/es.json
+++ b/locale/es.json
@@ -396,6 +396,7 @@
         "Adding vote failed": "Error al crear el voto",
         "You already voted": "Usted ya votó",
         "Poll has been updated before you vote": "La encuesta fue actualizada antes de su voto",
+        "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.": "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.",
         "Comment failed": "Error al crear el comentario",
         "You can't create a poll with hidden results with the following edition option:": "No puede crear una encuesta con resultados no visibles con los siguientes opciones de edición",
         "Failed to delete column": "Error al eliminar la columna",
diff --git a/locale/fr.json b/locale/fr.json
index 6486504a..1de86bb7 100644
--- a/locale/fr.json
+++ b/locale/fr.json
@@ -399,6 +399,7 @@
         "Adding vote failed": "Échec de l'ajout d'un vote",
         "You already voted": "Vous avez déjà voté",
         "Poll has been updated before you vote": "Le sondage a été mis à jour avant votre vote",
+        "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.": "Votre vote n'a pas été pris en compte, car quelqu'un a voté entre temps et cela entre en conflit avec vos choix et les conditions du sondage. Merci de réessayer.",
         "Comment failed": "Échec du commentaire",
         "You can't create a poll with hidden results with the following edition option:": "Vous ne pouvez pas créer de sondage avec résulats cachés avec les options d'édition suivantes : ",
         "Failed to delete column": "Échec de la suppression de colonne",
diff --git a/locale/it.json b/locale/it.json
index 112a881d..83f13680 100644
--- a/locale/it.json
+++ b/locale/it.json
@@ -396,6 +396,7 @@
         "Adding vote failed": "Aggiunta del voto fallito",
         "You already voted": "IT_Vous avez déjà voté",
         "Poll has been updated before you vote": "IT_Le sondage a été mis à jour avant votre vote",
+        "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.": "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.",
         "Comment failed": "Commento fallito",
         "You can't create a poll with hidden results with the following edition option:": "Non potete creare un sondaggio con i risultati nascosti con queste opzioni: ",
         "Failed to delete column": "Impossibile eliminare la colonna",
diff --git a/locale/nl.json b/locale/nl.json
index 3f17a531..14499e04 100644
--- a/locale/nl.json
+++ b/locale/nl.json
@@ -396,6 +396,7 @@
         "Adding vote failed": "Toevoegen stem gefaald",
         "You already voted": "Je hebt al gestemd",
         "Poll has been updated before you vote": "De poll is gewijzigd voordat je stemde",
+        "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.": "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.",
         "Comment failed": "Opmerking mislukt",
         "You can't create a poll with hidden results with the following edition option:": "Je kan geen poll met verborgen resultaten maken met de volgende optie: ",
         "Failed to delete column": "Kolom verwijderen mislukt",
diff --git a/locale/oc.json b/locale/oc.json
index e2a9811c..8bdac4b1 100644
--- a/locale/oc.json
+++ b/locale/oc.json
@@ -396,6 +396,7 @@
         "Adding vote failed": "Fracàs de l’apondon d’un vòte",
         "You already voted": "Avètz ja votat",
         "Poll has been updated before you vote": "Lo sondatge es estat mes a jorn abans vòstre vòte",
+        "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.": "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.",
         "Comment failed": "Fracàs del comentari",
         "You can't create a poll with hidden results with the following edition option:": "Podètz pas crear de sondatges amb de resultats amagats amb las opcions d’edicion seguentas : ",
         "Failed to delete column": "Fracàs de la supression de colomna",
@@ -428,4 +429,4 @@
         "Check again": "Tornar verificar",
         "Continue the installation": "Contunhar l’installacion"
     }
-}
\ No newline at end of file
+}
diff --git a/studs.php b/studs.php
index 64d1a892..b9a30b2f 100644
--- a/studs.php
+++ b/studs.php
@@ -19,6 +19,7 @@
 use Framadate\Editable;
 use Framadate\Exception\AlreadyExistsException;
 use Framadate\Exception\ConcurrentEditionException;
+use Framadate\Exception\ConcurrentVoteException;
 use Framadate\Message;
 use Framadate\Security\Token;
 use Framadate\Services\InputService;
@@ -146,6 +147,8 @@ if ($accessGranted) {
                 }
             } catch (ConcurrentEditionException $cee) {
                 $message = new Message('danger', __('Error', 'Poll has been updated before you vote'));
+            } catch (ConcurrentVoteException $cve) {
+                $message = new Message('danger', __('Error', "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry."));
             }
         }
     } elseif (isset($_POST['save'])) { // Add a new vote
@@ -179,6 +182,8 @@ if ($accessGranted) {
                 $message = new Message('danger', __('Error', 'You already voted'));
             } catch (ConcurrentEditionException $cee) {
                 $message = new Message('danger', __('Error', 'Poll has been updated before you vote'));
+            } catch (ConcurrentVoteException $cve) {
+                $message = new Message('danger', __('Error', "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry."));
             }
         }
     }
-- 
GitLab