diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a11800..cc61662 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,15 @@ - Amélioration : Les commentaires sont horodatés - Amélioration : Auto wrapping de la description du sondage +## Version 0.9.3 (Antonin - Olivier - Nikos) + - Fix : Traduction de textes en Italien + - Fix : Empêchement de la suppression de la dernière colonne + - Fix : Possiblité de supprimer des colonnes contenant des caractères spéciaux (par exemple "&") + - Fix : Correction de l'exemple d'URL rewriting (des efforts restent à faire) + - Amélioration : (Mode chacun son vote) Possiblité d'éditer son vote directement après un vote + - Amélioration : Message plus parlant lors de la création d'une colonne +## Version 0.9.2 (Olivier) + - Fix : Completion d'un manque de contrôle sur les ID ## Version 0.9.1 (JosephK - Olivier - Antonin - Michael - Paul) - Fix : Correction des lenteurs de défilement - Fix : Arrêt du défilement auto à gauche qu'on clique sur un choix diff --git a/adminstuds.php b/adminstuds.php index ca920dd..aa7eb24 100644 --- a/adminstuds.php +++ b/adminstuds.php @@ -17,6 +17,7 @@ * Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft) */ use Framadate\Editable; +use Framadate\Exception\MomentAlreadyExistsException; use Framadate\Message; use Framadate\Services\AdminPollService; use Framadate\Services\InputService; @@ -227,6 +228,7 @@ if (!empty($_POST['save'])) { // Save edition of an old vote if (!empty($_GET['delete_vote'])) { $vote_id = filter_input(INPUT_GET, 'delete_vote', FILTER_VALIDATE_INT); + $vote_id = Utils::base64url_decode($vote_id); if ($adminPollService->deleteVote($poll_id, $vote_id)) { $message = new Message('success', __('adminstuds', 'Vote deleted')); } else { @@ -318,6 +320,7 @@ if (isset($_POST['confirm_delete_poll'])) { if (!empty($_GET['delete_column'])) { $column = filter_input(INPUT_GET, 'delete_column', FILTER_DEFAULT); + $column = Utils::base64url_decode($column); if ($poll->format === 'D') { $ex = explode('@', $column); @@ -342,30 +345,30 @@ if (!empty($_GET['delete_column'])) { // Add a slot // ------------------------------- -if (isset($_GET['add_slot'])) { +if (isset($_GET['add_column'])) { $smarty->assign('poll_id', $poll_id); $smarty->assign('admin_poll_id', $admin_poll_id); $smarty->assign('format', $poll->format); $smarty->assign('title', __('Generic', 'Poll') . ' - ' . $poll->title); - $smarty->display('add_slot.tpl'); + $smarty->display('add_column.tpl'); exit; } -if (isset($_POST['confirm_add_slot'])) { - if ($poll->format === 'D') { - $newdate = strip_tags($_POST['newdate']); - $newmoment = str_replace(',', '-', strip_tags($_POST['newmoment'])); +if (isset($_POST['confirm_add_column'])) { + try { + if ($poll->format === 'D') { + $newdate = strip_tags($_POST['newdate']); + $newmoment = str_replace(',', '-', strip_tags($_POST['newmoment'])); - $ex = explode('/', $newdate); - $result = $adminPollService->addDateSlot($poll_id, mktime(0, 0, 0, $ex[1], $ex[0], $ex[2]), $newmoment); - } else { - $newslot = str_replace(',', '-', strip_tags($_POST['choice'])); - $result = $adminPollService->addClassicSlot($poll_id, $newslot); - } + $ex = explode('/', $newdate); + $adminPollService->addDateSlot($poll_id, mktime(0, 0, 0, $ex[1], $ex[0], $ex[2]), $newmoment); + } else { + $newslot = str_replace(',', '-', strip_tags($_POST['choice'])); + $adminPollService->addClassicSlot($poll_id, $newslot); + } - if ($result) { $message = new Message('success', __('adminstuds', 'Choice added')); - } else { - $message = new Message('danger', __('Error', 'Failed to add the column')); + } catch (MomentAlreadyExistsException $e) { + $message = new Message('danger', __('Error', 'The column already exists')); } } diff --git a/app/classes/Framadate/Exception/MomentAlreadyExistsException.php b/app/classes/Framadate/Exception/MomentAlreadyExistsException.php new file mode 100644 index 0000000..11723e9 --- /dev/null +++ b/app/classes/Framadate/Exception/MomentAlreadyExistsException.php @@ -0,0 +1,9 @@ +pollService->allSlotsByPoll($poll); // We can't delete the last slot - if ($poll->format == 'D' && count($slots) === 1 && strpos($slots[0]->moments, ',') === -1) { + if ($poll->format == 'D' && count($slots) === 1 && strpos($slots[0]->moments, ',') === false) { return false; } elseif ($poll->format == 'A' && count($slots) === 1) { return false; @@ -196,10 +197,10 @@ class AdminPollService { * @param $poll_id int The ID of the poll * @param $datetime int The datetime * @param $new_moment string The moment's name - * @return bool true if added + * @throws MomentAlreadyExistsException When the moment to add already exists in database */ public function addDateSlot($poll_id, $datetime, $new_moment) { - $this->logService->log('ADD_SLOT', 'id:' . $poll_id . ', datetime:' . $datetime . ', moment:' . $new_moment); + $this->logService->log('ADD_COLUMN', 'id:' . $poll_id . ', datetime:' . $datetime . ', moment:' . $new_moment); $slots = $this->slotRepository->listByPollId($poll_id); $result = $this->findInsertPosition($slots, $datetime); @@ -207,16 +208,13 @@ class AdminPollService { // Begin transaction $this->connect->beginTransaction(); - if ($result == null) { - // The moment already exists - return false; - } elseif ($result->slot != null) { + if ($result->slot != null) { $slot = $result->slot; $moments = explode(',', $slot->moments); // Check if moment already exists (maybe not necessary) if (in_array($new_moment, $moments)) { - return false; + throw new MomentAlreadyExistsException(); } // Update found slot @@ -232,8 +230,6 @@ class AdminPollService { // Commit transaction $this->connect->commit(); - return true; - } /** @@ -244,10 +240,10 @@ class AdminPollService { * * @param $poll_id int The ID of the poll * @param $title int The title - * @return bool true if added + * @throws MomentAlreadyExistsException When the moment to add already exists in database */ public function addClassicSlot($poll_id, $title) { - $this->logService->log('ADD_SLOT', 'id:' . $poll_id . ', title:' . $title); + $this->logService->log('ADD_COLUMN', 'id:' . $poll_id . ', title:' . $title); $slots = $this->slotRepository->listByPollId($poll_id); @@ -257,7 +253,7 @@ class AdminPollService { }, $slots); if (in_array($title, $titles)) { // The moment already exists - return false; + throw new MomentAlreadyExistsException(); } @@ -272,8 +268,6 @@ class AdminPollService { // Commit transaction $this->connect->commit(); - return true; - } /** @@ -283,34 +277,29 @@ class AdminPollService { * * @param $slots array All the slots of the poll * @param $datetime int The datetime of the new slot - * @return null|\stdClass An object like this one: {insert:X, slot:Y} where Y can be null. + * @return \stdClass An object like this one: {insert:X, slot:Y} where Y can be null. */ private function findInsertPosition($slots, $datetime) { $result = new \stdClass(); $result->slot = null; - $result->insert = -1; - - $i = 0; + $result->insert = 0; foreach ($slots as $slot) { $rowDatetime = $slot->title; $moments = explode(',', $slot->moments); if ($datetime == $rowDatetime) { - $i += count($moments); - // Here we have to insert at the end of a slot + $result->insert += count($moments); $result->slot = $slot; - $result->insert = $i; break; } elseif ($datetime < $rowDatetime) { - // Here we have to insert a new slot + // We have to insert before this slot break; } else { - $i += count($moments); + $result->insert += count($moments); } } - $result->insert = $i; return $result; } diff --git a/app/classes/Framadate/Utils.php b/app/classes/Framadate/Utils.php index d2357ef..50d117c 100644 --- a/app/classes/Framadate/Utils.php +++ b/app/classes/Framadate/Utils.php @@ -104,6 +104,9 @@ class Utils { * @return string The poll's URL. */ public static function getUrlSondage($id, $admin = false, $vote_id = '', $action = null, $action_value = null) { + // URL-Encode $action_value + $action_value = $action_value == null ? null : Utils::base64url_encode($action_value); + if (URL_PROPRE) { if ($admin === true) { $url = self::get_server_name() . $id . '/admin'; @@ -112,8 +115,12 @@ class Utils { } if ($vote_id != '') { $url .= '/vote/' . $vote_id . "#edit"; - } elseif ($action != null && $action_value != null) { - $url .= '/action/' . $action . '/' . $action_value; + } elseif ($action != null) { + if ($action_value != null) { + $url .= '/action/' . $action . '/' . $action_value; + } else { + $url .= '/action/' . $action; + } } } else { if ($admin === true) { @@ -123,8 +130,12 @@ class Utils { } if ($vote_id != '') { $url .= '&vote=' . $vote_id . "#edit"; - } elseif ($action != null && $action_value != null) { - $url .= '&' . $action . "=" . $action_value; + } elseif ($action != null) { + if ($action_value != null) { + $url .= '&' . $action . "=" . $action_value; + } else { + $url .= '&' . $action . "="; + } } } @@ -197,4 +208,12 @@ class Utils { public static function fromPostOrDefault($postKey, $default = '') { return !empty($_POST[$postKey]) ? Utils::htmlEscape($_POST[$postKey]) : $default; } + + public static function base64url_encode($input) { + return rtrim(strtr(base64_encode($input), '+/', '-_'), '='); + } + + public static function base64url_decode($input) { + return base64_decode(str_pad(strtr($input, '-_', '+/'), strlen($input) % 4, '=', STR_PAD_RIGHT)); + } } diff --git a/app/inc/smarty.php b/app/inc/smarty.php index c265bf1..7dfb2a4 100644 --- a/app/inc/smarty.php +++ b/app/inc/smarty.php @@ -49,7 +49,7 @@ function smarty_function_poll_url($params, Smarty_Internal_Template $template) { $poll_id = filter_var($params['id'], FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => POLL_REGEX]]); $admin = (isset($params['admin']) && $params['admin']) ? true : false; $action = (isset($params['action']) && !empty($params['action'])) ? Utils::htmlEscape($params['action']) : false; - $action_value = (isset($params['action_value']) && !empty($params['action_value'])) ? Utils::htmlEscape($params['action_value']) : false; + $action_value = (isset($params['action_value']) && !empty($params['action_value'])) ? $params['action_value'] : false; $vote_unique_id = isset($params['vote_id']) ? filter_var($params['vote_id'], FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => POLL_REGEX]]) : ''; // If filter_var fails (i.e.: hack tentative), it will return false. At least no leak is possible from this. diff --git a/htaccess.txt b/htaccess.txt index 103cc1d..640cb13 100644 --- a/htaccess.txt +++ b/htaccess.txt @@ -6,10 +6,12 @@ RewriteEngine On RewriteCond %{REQUEST_FILENAME} -f [OR] RewriteCond %{REQUEST_FILENAME} -d + RewriteRule . - [L] - RewriteRule ^([a-zA-Z0-9]{16})$ studs.php?poll=$1 + RewriteRule ^([a-zA-Z0-9]{16})$ studs.php?poll=$1 [L] RewriteRule ^([a-zA-Z0-9]{16})/action/([a-zA-Z_-]+)/(.+)$ studs.php?poll=$1&$2=$3 - RewriteRule ^([a-zA-Z0-9]{16})/vote/([a-zA-Z0-9]{16})$ studs.php?poll=$1&vote_id=$2 + RewriteRule ^([a-zA-Z0-9]{16})/vote/([a-zA-Z0-9]{16})$ studs.php?poll=$1&vote=$2 RewriteRule ^([a-zA-Z0-9]{24})/admin$ adminstuds.php?poll=$1 - RewriteRule ^([a-zA-Z0-9]{24})/admin/action/([a-zA-Z_-]+)/(.+)$ adminstuds.php?poll=$1&$2=$3 - + RewriteRule ^([a-zA-Z0-9]{24})/admin/vote/([a-zA-Z0-9]{16})$ adminstuds.php?poll=$1&vote=$2 + RewriteRule ^([a-zA-Z0-9]{24})/admin/action/([a-zA-Z_-]+)(/(.+))?$ adminstuds.php?poll=$1&$2=$4 + \ No newline at end of file diff --git a/locale/de.json b/locale/de.json index bc2ba23..43bd686 100644 --- a/locale/de.json +++ b/locale/de.json @@ -352,6 +352,7 @@ "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", + "The column already exists": "DE_La colonne existe déjà", "MISSING_VALUES": "Fehlende Werte", "CANT_CONNECT_TO_DATABASE": "Kann nicht mit der Datenbank verbinden", "Password is empty": "DE_Le mot de passe est vide.", diff --git a/locale/en.json b/locale/en.json index 3ed4ad9..c83615e 100644 --- a/locale/en.json +++ b/locale/en.json @@ -352,6 +352,7 @@ "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", + "The column already exists": "The column already exists", "MISSING_VALUES": "Missing values", "CANT_CONNECT_TO_DATABASE": "Unable to connect to database", "Password is empty": "Password is empty.", diff --git a/locale/es.json b/locale/es.json index a12a988..195ec65 100644 --- a/locale/es.json +++ b/locale/es.json @@ -352,6 +352,7 @@ "Comment failed": "ES_Commentaire échoué", "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", + "The column already exists": "ES_La colonne existe déjà", "MISSING_VALUES": "Los valores perdidos", "CANT_CONNECT_TO_DATABASE": "No se puede conectar a la base de datos", "Password is empty": "ES_Le mot de passe est vide.", diff --git a/locale/fr.json b/locale/fr.json index 40d9349..2fcbb16 100644 --- a/locale/fr.json +++ b/locale/fr.json @@ -366,6 +366,7 @@ "Comment failed": "Commentaire échoué", "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'éditions suivantes : ", "Failed to delete column": "Échec de la suppression de colonne", + "The column already exists": "La colonne existe déjà", "MISSING_VALUES": "Il manque des valeurs", "CANT_CONNECT_TO_DATABASE": "Impossible de se connecter à la base de données", "Password is empty": "Le mot de passe est vide.", diff --git a/locale/it.json b/locale/it.json index e8a549f..e6ac2a5 100644 --- a/locale/it.json +++ b/locale/it.json @@ -35,7 +35,7 @@ "seconds": "secondi", "Choice": "Scelta", "Link": "Link", - "Search": "Ricerca", + "Search": "Cercare", "Creation date:": "Data di creazione:", "Caption": "Titolo", "ASTERISK": "*" @@ -100,9 +100,9 @@ "Save the new title": "Salvare il nuovo titolo", "Cancel the title edit": "Annullare la modifica del titolo", "Initiator of the poll": "Autore del sondaggio", - "Edit the name": "Modifica dell'autore", - "Save the new name": "Salvare l'autore", - "Cancel the name edit": "Annulla il cambio di dell'autore", + "Edit the name": "Modifica il nome", + "Save the new name": "Salvare il nuovo nome", + "Cancel the name edit": "Annulla il cambio di nome", "Email": "Email", "Edit the email adress": "Modificare l'email", "Save the email address": "Salvare l'email", @@ -146,9 +146,9 @@ "The bests choices at this time are:": "Le migliori scelte per ora sono :", "Scroll to the left": "Spostare a sinistra", "Scroll to the right": "Spostare a destra", - "polled user": "votante", - "polled users": "votanti", - "Display the chart of the results": "Visualizzare il grafico dei risultati", + "polled user": "Votante", + "polled users": "Votanti", + "Display the chart of the results": "Mostra il grafico dei risultati", "Chart": "Grafico" }, "Comments": { @@ -272,11 +272,11 @@ "Archiving date:": "Archivio Data:" }, "Admin": { - "Back to administration": "Ritornare all'amministrazione", + "Back to administration": "Ritorna all'amministrazione", "Administration": "Amministrazione", "Polls": "Sondaggi", "Migration": "Migrazione", - "Purge": "Depurazione", + "Purge": "Depurare", "Logs": "Log", "Installation": "Installazione", "Poll ID": "ID del sondaggio", @@ -317,7 +317,7 @@ "Someone just change your poll available at the following link %s.": "Qualcuno ha appena cambiato il vostro sondaggio disponibile al seguente link %1$s.", "Someone just delete your poll %s.": "Qualcuno ha appena cancellato il vostro sondaggio \"%s\".", "Thanks for your trust.": "Grazie per la vostra fiducia.", - "FOOTER": "«La strada è lunga, ma la via è libera… »\nFramasoft vive solo grazie alle vostre donazioni (deducibili dalle imposte).\nGrazie in anticipo per il vostro sostegno http://soutenir.framasoft.org.", + "FOOTER": "« La strada è lunga, ma la via è libera… »\nFramasoft vive solo grazie alle vostre donazioni (deducibili dalle imposte).\nGrazie in anticipo per il vostro sostegno http://soutenir.framasoft.org.", "[ADMINISTRATOR] New settings for your poll": "[AMMINISTRATORE] Modifica di configurazione del sondaggio", "You have changed the settings of your poll. \nYou can modify this poll with this link": "Avete modificato la configurazione del vostro sondaggio. \nPotete modificare questo sondaggio con le Link seguente", "This is the message you have to send to the people you want to poll. \nNow, you have to send this message to everyone you want to poll.": "Questo è il messaggio che deve essere inviato ai partecipanti. \nPotete ora inviare questo messaggio a tutte le persone a cui volete chiedere il voto.", @@ -341,17 +341,18 @@ "Cookies are disabled on your browser. Theirs activation is required to create a poll.": "I cookies non sono permessi sul vostro browser. E' necessario permetterli per creare un sondaggio.", "This poll doesn't exist !": "Questo sondaggio non esiste più !", "Enter a name": "Non avete inserito il nome !", - "The name is invalid.": "Le nome non è valido.", + "The name is invalid.": "Nome non valido.", "The name you've chosen already exist in this poll!": "Il nome che avete scelto esiste già !", "Enter a name and a comment!": "Inserire un nome e un commento!", "Failed to insert the comment!": "Errore nell'inserimento del commento !", "Framadate is not properly installed, please check the \"INSTALL\" to setup the database before continuing.": "Framadate non è installato correttamente, leggete la cartella INSTALL per configurare il database prima di continuare.", "Failed to save poll": "Errore nel salvataggio del sondaggio", "Update vote failed": "Aggiornamento del voto fallito", - "Adding vote failed": "Aggiunta del voto fallita", + "Adding vote failed": "Aggiunta del voto fallito", "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: : ", + "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", + "The column already exists": "IT_La colonne existe déjà", "MISSING_VALUES": "Valori mancanti", "CANT_CONNECT_TO_DATABASE": "Impossibile connettersi al database" } diff --git a/studs.php b/studs.php index 793ff01..47a5626 100644 --- a/studs.php +++ b/studs.php @@ -40,6 +40,7 @@ $resultPubliclyVisible = true; $slots = array(); $votes = array(); $comments = array(); +$editedVoteUniqueId = null; /* Services */ /*----------*/ @@ -189,5 +190,6 @@ $smarty->assign('admin', false); $smarty->assign('hidden', $poll->hidden); $smarty->assign('accessGranted', $accessGranted); $smarty->assign('resultPubliclyVisible', $resultPubliclyVisible); +$smarty->assign('editedVoteUniqueId', $editedVoteUniqueId); $smarty->display('studs.tpl'); diff --git a/tpl/add_slot.tpl b/tpl/add_column.tpl similarity index 93% rename from tpl/add_slot.tpl rename to tpl/add_column.tpl index 3ef5e5e..8fa0c55 100644 --- a/tpl/add_slot.tpl +++ b/tpl/add_column.tpl @@ -32,7 +32,7 @@ {/if}
- +
diff --git a/tpl/part/vote_table_classic.tpl b/tpl/part/vote_table_classic.tpl index 660dc10..1a3df94 100644 --- a/tpl/part/vote_table_classic.tpl +++ b/tpl/part/vote_table_classic.tpl @@ -26,8 +26,8 @@ {/foreach} - + {__('Poll results', 'Add a column')} @@ -99,7 +99,14 @@ {/foreach} - {if $active && !$expired && ($poll->editable == constant('Framadate\Editable::EDITABLE_BY_ALL') or $admin) && $accessGranted} + {if $active && !$expired && $accessGranted && + ( + $poll->editable == constant('Framadate\Editable::EDITABLE_BY_ALL') + or ($poll->editable == constant('Framadate\Editable::EDITABLE_BY_OWN') && $editedVoteUniqueId == $vote->uniqId) + or $admin + ) + } + {__('Generic', 'Edit')} diff --git a/tpl/part/vote_table_date.tpl b/tpl/part/vote_table_date.tpl index 975d42b..8bed591 100644 --- a/tpl/part/vote_table_date.tpl +++ b/tpl/part/vote_table_date.tpl @@ -22,7 +22,7 @@ {foreach $slots as $slot} {foreach $slot->moments as $id=>$moment} - {__('Generic', 'Remove')} @@ -32,8 +32,8 @@ {/foreach} {/foreach} - + {__('Poll results', 'Add a column')} @@ -150,7 +150,13 @@ {/foreach} - {if $active && !$expired && ($poll->editable == constant('Framadate\Editable::EDITABLE_BY_ALL') or $admin) && $accessGranted} + {if $active && !$expired && $accessGranted && + ( + $poll->editable == constant('Framadate\Editable::EDITABLE_BY_ALL') + or ($poll->editable == constant('Framadate\Editable::EDITABLE_BY_OWN') && $editedVoteUniqueId == $vote->uniqId) + or $admin + ) + } {__('Generic', 'Edit')} @@ -172,7 +178,7 @@ {* Line to add a new vote *} - {if $active && $editingVoteId === 0 && !$expired && $accessGranted} + {if $active && $editingVoteId === 0 && !$expired}