Merge branch 'feature/private-polls' into 'develop'

Feature : private polls

Allowing to protect a poll by password .

An option can allow the user which doesn't know the password to see (and only see) the poll and its votes.

(Sorry for the double commits...)

See merge request !79
This commit is contained in:
Olivier Perez 2015-10-30 22:04:56 +01:00
commit f0a45bd203
34 changed files with 816 additions and 274 deletions

View File

@ -21,6 +21,7 @@ use Framadate\Services\PollService;
use Framadate\Services\InputService;
use Framadate\Services\MailService;
use Framadate\Services\NotificationService;
use Framadate\Services\SecurityService;
use Framadate\Message;
use Framadate\Utils;
use Framadate\Editable;
@ -34,6 +35,7 @@ $poll_id = null;
$poll = null;
$message = null;
$result = false;
$comments = array();
/* Services */
/*----------*/
@ -43,7 +45,7 @@ $pollService = new PollService($connect, $logService);
$inputService = new InputService();
$mailService = new MailService($config['use_smtp']);
$notificationService = new NotificationService($mailService);
$securityService = new SecurityService();
/* PAGE */
/* ---- */
@ -57,6 +59,8 @@ if (!empty($_POST['poll'])) {
if (!$poll) {
$message = new Message('error', __('Error', 'This poll doesn\'t exist !'));
} else if ($poll && !$securityService->canAccessPoll($poll)) {
$message = new Message('error', __('Password', 'Wrong password'));
} else {
$name = $inputService->filterName($_POST['name']);
$comment = $inputService->filterComment($_POST['comment']);
@ -75,10 +79,9 @@ if (!$poll) {
$message = new Message('danger', __('Error', 'Comment failed'));
}
}
$comments = $pollService->allCommentsByPollId($poll_id);
}
$comments = $pollService->allCommentsByPollId($poll_id);
$smarty->error_reporting = E_ALL & ~E_NOTICE;
$smarty->assign('comments', $comments);
$comments_html = $smarty->fetch('part/comments_list.tpl');

View File

@ -25,6 +25,7 @@ use Framadate\Migration\AddColumn_hidden_In_poll_For_0_9;
use Framadate\Migration\Alter_Comment_table_for_name_length;
use Framadate\Migration\Alter_Comment_table_adding_date;
use Framadate\Migration\Generate_uniqId_for_old_votes;
use Framadate\Migration\AddColumns_password_hash_And_results_publicly_visible_In_poll_For_0_9;
use Framadate\Migration\Migration;
use Framadate\Migration\RPadVotes_from_0_8;
use Framadate\Utils;
@ -43,7 +44,8 @@ $migrations = [
new Generate_uniqId_for_old_votes(),
new RPadVotes_from_0_8(),
new Alter_Comment_table_for_name_length(),
new Alter_Comment_table_adding_date()
new Alter_Comment_table_adding_date(),
new AddColumns_password_hash_And_results_publicly_visible_In_poll_For_0_9(),
];
// ---------------------------------------

View File

@ -24,7 +24,7 @@ use Framadate\Services\LogService;
use Framadate\Services\MailService;
use Framadate\Services\PollService;
use Framadate\Services\NotificationService;
use Framadate\Utils;
use Framadate\Security\PasswordHasher;
include_once __DIR__ . '/app/inc/init.php';
@ -71,7 +71,8 @@ if ($poll) {
if (isset($_POST['update_poll_info'])) {
$updated = false;
$field = $inputService->filterAllowedValues($_POST['update_poll_info'], ['title', 'admin_mail', 'description', 'rules', 'expiration_date', 'name', 'hidden']);
$field = $inputService->filterAllowedValues($_POST['update_poll_info'], ['title', 'admin_mail', 'description',
'rules', 'expiration_date', 'name', 'hidden', 'removePassword', 'password']);
// Update the right poll field
if ($field == 'title') {
@ -135,6 +136,24 @@ if (isset($_POST['update_poll_info'])) {
$poll->hidden = $hidden;
$updated = true;
}
} elseif ($field == 'removePassword') {
$removePassword = isset($_POST['removePassword']) ? $inputService->filterBoolean($_POST['removePassword']) : false;
if ($removePassword) {
$poll->results_publicly_visible = false;
$poll->password_hash = null;
$updated = true;
}
} elseif ($field == 'password') {
$password = isset($_POST['password']) ? $_POST['password'] : null;
$resultsPubliclyVisible = isset($_POST['resultsPubliclyVisible']) ? $inputService->filterBoolean($_POST['resultsPubliclyVisible']) : false;
if (!empty($password)) {
$poll->password_hash = PasswordHasher::hash($password);
$updated = true;
}
if ($resultsPubliclyVisible != $poll->results_publicly_visible) {
$poll->results_publicly_visible = $resultsPubliclyVisible;
$updated = true;
}
}
// Update poll in database
@ -234,30 +253,6 @@ if (isset($_POST['confirm_remove_all_votes'])) {
}
}
// -------------------------------
// Add a comment
// -------------------------------
if (isset($_POST['add_comment'])) {
$name = $inputService->filterName($_POST['name']);
$comment = $inputService->filterComment($_POST['comment']);
if ($name == null) {
$message = new Message('danger', __('Error', 'The name is invalid.'));
}
if ($message == null) {
// Add comment
$result = $pollService->addComment($poll_id, $name, $comment);
if ($result) {
$message = new Message('success', __('Comments', 'Comment added'));
} else {
$message = new Message('danger', __('Error', 'Comment failed'));
}
}
}
// -------------------------------
// Delete a comment
// -------------------------------
@ -395,5 +390,7 @@ $smarty->assign('editingVoteId', $editingVoteId);
$smarty->assign('message', $message);
$smarty->assign('admin', true);
$smarty->assign('hidden', false);
$smarty->assign('accessGranted', true);
$smarty->assign('resultPubliclyVisible', true);
$smarty->display('studs.tpl');

View File

@ -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, hashed. Only used if $use_password is set to true
* @var string
*/
public $password_hash;
/**
* If true, the polls results will be also visible for those without password
* @var boolean
*/
public $results_publicly_visible;
/**
* List of available choices
*/

View File

@ -57,7 +57,7 @@ class AddColumn_hidden_In_poll_For_0_9 implements Migration {
}
/**
* This methode is called only one time in the migration page.
* This method is called only one time in the migration page.
*
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded

View File

@ -57,7 +57,7 @@ class AddColumn_receiveNewComments_For_0_9 implements Migration {
}
/**
* This methode is called only one time in the migration page.
* This method is called only one time in the migration page.
*
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded

View File

@ -57,7 +57,7 @@ class AddColumn_uniqId_In_vote_For_0_9 implements Migration {
}
/**
* This methode is called only one time in the migration page.
* This method is called only one time in the migration page.
*
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded

View File

@ -0,0 +1,78 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondate: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Migration;
use Framadate\Utils;
/**
* This migration adds the fields password_hash and results_publicly_visible on the poll table.
*
* @package Framadate\Migration
* @version 0.9
*/
class AddColumns_password_hash_And_results_publicly_visible_In_poll_For_0_9 implements Migration {
function __construct() {
}
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
function description() {
return 'Add columns "password_hash" and "results_publicly_visible" in table "vote" for version 0.9';
}
/**
* This method could check if the execute method should be called.
* It is called before the execute method.
*
* @param \PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
function preCondition(\PDO $pdo) {
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(\PDO::FETCH_COLUMN);
// Check if tables of v0.9 are presents
$diff = array_diff([Utils::table('poll'), Utils::table('slot'), Utils::table('vote'), Utils::table('comment')], $tables);
return count($diff) === 0;
}
/**
* This method is called only one time in the migration page.
*
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
function execute(\PDO $pdo) {
$this->alterPollTable($pdo);
return true;
}
private function alterPollTable(\PDO $pdo) {
$pdo->exec('
ALTER TABLE `' . Utils::table('poll') . '`
ADD `password_hash` VARCHAR(255) NULL DEFAULT NULL ,
ADD `results_publicly_visible` TINYINT(1) NULL DEFAULT NULL');
}
}

View File

@ -57,7 +57,7 @@ class From_0_0_to_0_8_Migration implements Migration {
}
/**
* This methode is called only one time in the migration page.
* This method is called only one time in the migration page.
*
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded

View File

@ -57,7 +57,7 @@ class From_0_8_to_0_9_Migration implements Migration {
}
/**
* This methode is called only one time in the migration page.
* This method is called only one time in the migration page.
*
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded

View File

@ -13,10 +13,10 @@ class PollRepository extends AbstractRepository {
public function insertPoll($poll_id, $admin_poll_id, $form) {
$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, $form->password_hash, $form->results_publicly_visible));
}
function findById($poll_id) {
@ -48,9 +48,9 @@ class PollRepository extends AbstractRepository {
}
function update($poll) {
$prepared = $this->prepare('UPDATE `' . Utils::table('poll') . '` SET title=?, admin_name=?, admin_mail=?, description=?, end_date=?, active=?, editable=?, hidden=? WHERE id = ?');
$prepared = $this->prepare('UPDATE `' . Utils::table('poll') . '` SET title=?, admin_name=?, admin_mail=?, description=?, end_date=?, active=?, editable=?, hidden=?, password_hash=?, results_publicly_visible=? WHERE id = ?');
return $prepared->execute([$poll->title, $poll->admin_name, $poll->admin_mail, $poll->description, $poll->end_date, $poll->active, $poll->editable, $poll->hidden, $poll->id]);
return $prepared->execute([$poll->title, $poll->admin_name, $poll->admin_mail, $poll->description, $poll->end_date, $poll->active, $poll->editable, $poll->hidden, $poll->password_hash, $poll->results_publicly_visible, $poll->id]);
}
function deleteById($poll_id) {

View File

@ -0,0 +1,35 @@
<?php
namespace Framadate\Security;
/**
* Class PasswordHasher
*
* Used to abstract the password hash logic
*
* @package Framadate\Security
*/
class PasswordHasher {
/**
* Hash a password
*
* @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) {
return password_hash($password, PASSWORD_DEFAULT);
}
/**
* Verify a password with a hash
*
* @param string $password the password to verify
* @param string $hash the hash to compare.
* @return bool
*/
public static function verify($password, $hash) {
return password_verify($password, $hash);
}
}

View File

@ -20,8 +20,9 @@ namespace Framadate\Services;
use Framadate\Form;
use Framadate\FramaDB;
use Framadate\Repositories\RepositoryFactory;
use Framadate\Utils;
use Framadate\Security\Token;
use Framadate\Repositories\RepositoryFactory;
class PollService {

View File

@ -2,6 +2,7 @@
namespace Framadate\Services;
use Framadate\Security\Token;
use Framadate\Security\PasswordHasher;
class SecurityService {
@ -48,5 +49,46 @@ class SecurityService {
return $checked;
}
/**
* Verify if the current session allows to access given poll.
*
* @param $poll \stdClass The poll which we seek access
* @return bool true if the current session can access this poll
*/
public function canAccessPoll($poll) {
if (is_null($poll->password_hash)) {
return true;
}
$this->ensureSessionPollSecurityIsCreated();
$currentPassword = isset($_SESSION['poll_security'][$poll->id]) ? $_SESSION['poll_security'][$poll->id] : null;
if (!empty($currentPassword) && PasswordHasher::verify($currentPassword, $poll->password_hash)) {
return true;
} else {
unset($_SESSION['poll_security'][$poll->id]);
return false;
}
}
/**
* Submit to the session a poll password
*
* @param $poll \stdClass The poll which we seek access
* @param $password string the password to compare
*/
public function submitPollAccess($poll, $password) {
if (!empty($password)) {
$this->ensureSessionPollSecurityIsCreated();
$_SESSION['poll_security'][$poll->id] = $password;
}
}
private function ensureSessionPollSecurityIsCreated() {
if (!isset($_SESSION['poll_security'])) {
$_SESSION['poll_security'] = array();
}
}
}

View File

@ -12,7 +12,8 @@
"require": {
"smarty/smarty": "3.1.21",
"o80/i18n": "dev-develop",
"phpmailer/phpmailer": "~5.2"
"phpmailer/phpmailer": "~5.2",
"ircmaxell/password-compat": "dev-master"
},
"require-dev": {

142
composer.lock generated
View File

@ -4,20 +4,63 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "80025abeaccbfee148a75dd3ff34c19e",
"hash": "41953f68c6dc68f014c6072de985a81d",
"content-hash": "c85ba8e14cce189aef869998ce0e2430",
"packages": [
{
"name": "ircmaxell/password-compat",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/ircmaxell/password_compat.git",
"reference": "1ecb013b51756effed3a3c446a314084b54c9916"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ircmaxell/password_compat/zipball/1ecb013b51756effed3a3c446a314084b54c9916",
"reference": "1ecb013b51756effed3a3c446a314084b54c9916",
"shasum": ""
},
"require-dev": {
"phpunit/phpunit": "4.*"
},
"type": "library",
"autoload": {
"files": [
"lib/password.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Anthony Ferrara",
"email": "ircmaxell@php.net",
"homepage": "http://blog.ircmaxell.com"
}
],
"description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash",
"homepage": "https://github.com/ircmaxell/password_compat",
"keywords": [
"hashing",
"password"
],
"time": "2015-08-11 14:39:38"
},
{
"name": "o80/i18n",
"version": "dev-develop",
"source": {
"type": "git",
"url": "https://github.com/olivierperez/o80-i18n.git",
"reference": "9b57197b395add3afc62fd674b03926230819b97"
"reference": "8f3fbc7c965559802ed4eda602528a24d641ea15"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/olivierperez/o80-i18n/zipball/9b57197b395add3afc62fd674b03926230819b97",
"reference": "9b57197b395add3afc62fd674b03926230819b97",
"url": "https://api.github.com/repos/olivierperez/o80-i18n/zipball/8f3fbc7c965559802ed4eda602528a24d641ea15",
"reference": "8f3fbc7c965559802ed4eda602528a24d641ea15",
"shasum": ""
},
"require": {
@ -50,20 +93,20 @@
"internationalization",
"php"
],
"time": "2015-05-09 22:08:09"
"time": "2015-09-21 21:18:45"
},
{
"name": "phpmailer/phpmailer",
"version": "v5.2.10",
"version": "v5.2.13",
"source": {
"type": "git",
"url": "https://github.com/PHPMailer/PHPMailer.git",
"reference": "07005ecbb80d11ec8c0f067bb37e8909cc7fcbb7"
"reference": "45df3a88f7f46071e10d0b600f228d19f95911b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/07005ecbb80d11ec8c0f067bb37e8909cc7fcbb7",
"reference": "07005ecbb80d11ec8c0f067bb37e8909cc7fcbb7",
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/45df3a88f7f46071e10d0b600f228d19f95911b3",
"reference": "45df3a88f7f46071e10d0b600f228d19f95911b3",
"shasum": ""
},
"require": {
@ -71,12 +114,17 @@
},
"require-dev": {
"phpdocumentor/phpdocumentor": "*",
"phpunit/phpunit": "4.3.*"
"phpunit/phpunit": "4.7.*"
},
"suggest": {
"league/oauth2-client": "Needed for Gmail's XOAUTH2 authentication system"
},
"type": "library",
"autoload": {
"classmap": [
"class.phpmailer.php",
"class.phpmaileroauth.php",
"class.phpmaileroauthgoogle.php",
"class.smtp.php",
"class.pop3.php",
"extras/EasyPeasyICS.php",
@ -105,7 +153,7 @@
}
],
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
"time": "2015-05-04 12:37:21"
"time": "2015-09-14 09:18:12"
},
{
"name": "smarty/smarty",
@ -137,7 +185,7 @@
"libs/sysplugins/smarty_security.php"
]
},
"notification-url": "http://packagist.org/downloads/",
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0"
],
@ -329,16 +377,16 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "2.2.2",
"version": "2.2.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c"
"reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2d7c03c0e4e080901b8f33b2897b0577be18a13c",
"reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
"reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
"shasum": ""
},
"require": {
@ -387,7 +435,7 @@
"testing",
"xunit"
],
"time": "2015-08-04 03:42:39"
"time": "2015-10-06 15:47:00"
},
{
"name": "phpunit/php-file-iterator",
@ -520,16 +568,16 @@
},
{
"name": "phpunit/php-token-stream",
"version": "1.4.6",
"version": "1.4.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-token-stream.git",
"reference": "3ab72c62e550370a6cd5dc873e1a04ab57562f5b"
"reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3ab72c62e550370a6cd5dc873e1a04ab57562f5b",
"reference": "3ab72c62e550370a6cd5dc873e1a04ab57562f5b",
"url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
"reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
"shasum": ""
},
"require": {
@ -565,20 +613,20 @@
"keywords": [
"tokenizer"
],
"time": "2015-08-16 08:51:00"
"time": "2015-09-15 10:49:45"
},
{
"name": "phpunit/phpunit",
"version": "4.8.4",
"version": "4.8.16",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "55bf1d6092b0e13a1f26bd5eaffeef3d8ad85ea7"
"reference": "625f8c345606ed0f3a141dfb88f4116f0e22978e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/55bf1d6092b0e13a1f26bd5eaffeef3d8ad85ea7",
"reference": "55bf1d6092b0e13a1f26bd5eaffeef3d8ad85ea7",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/625f8c345606ed0f3a141dfb88f4116f0e22978e",
"reference": "625f8c345606ed0f3a141dfb88f4116f0e22978e",
"shasum": ""
},
"require": {
@ -637,24 +685,24 @@
"testing",
"xunit"
],
"time": "2015-08-15 04:21:23"
"time": "2015-10-23 06:48:33"
},
{
"name": "phpunit/phpunit-mock-objects",
"version": "2.3.6",
"version": "2.3.8",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
"reference": "18dfbcb81d05e2296c0bcddd4db96cade75e6f42"
"reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/18dfbcb81d05e2296c0bcddd4db96cade75e6f42",
"reference": "18dfbcb81d05e2296c0bcddd4db96cade75e6f42",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
"reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
"shasum": ""
},
"require": {
"doctrine/instantiator": "~1.0,>=1.0.2",
"doctrine/instantiator": "^1.0.2",
"php": ">=5.3.3",
"phpunit/php-text-template": "~1.2",
"sebastian/exporter": "~1.2"
@ -693,7 +741,7 @@
"mock",
"xunit"
],
"time": "2015-07-10 06:54:24"
"time": "2015-10-02 06:51:40"
},
{
"name": "sebastian/comparator",
@ -929,16 +977,16 @@
},
{
"name": "sebastian/global-state",
"version": "1.0.0",
"version": "1.1.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01"
"reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/c7428acdb62ece0a45e6306f1ae85e1c05b09c01",
"reference": "c7428acdb62ece0a45e6306f1ae85e1c05b09c01",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
"reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
"shasum": ""
},
"require": {
@ -976,7 +1024,7 @@
"keywords": [
"global state"
],
"time": "2014-10-06 09:23:50"
"time": "2015-10-12 03:26:01"
},
{
"name": "sebastian/recursion-context",
@ -1068,24 +1116,21 @@
},
{
"name": "symfony/yaml",
"version": "v2.7.3",
"version": "v2.7.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "71340e996171474a53f3d29111d046be4ad8a0ff"
"url": "https://github.com/symfony/yaml.git",
"reference": "eca9019c88fbe250164affd107bc8057771f3f4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/71340e996171474a53f3d29111d046be4ad8a0ff",
"reference": "71340e996171474a53f3d29111d046be4ad8a0ff",
"url": "https://api.github.com/repos/symfony/yaml/zipball/eca9019c88fbe250164affd107bc8057771f3f4d",
"reference": "eca9019c88fbe250164affd107bc8057771f3f4d",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7"
},
"type": "library",
"extra": {
"branch-alias": {
@ -1113,13 +1158,14 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2015-07-28 14:07:07"
"time": "2015-10-11 09:39:48"
}
],
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"o80/i18n": 20
"o80/i18n": 20,
"ircmaxell/password-compat": 20
},
"prefer-stable": false,
"prefer-lowest": false,

View File

@ -19,7 +19,9 @@
use Framadate\Form;
use Framadate\Services\InputService;
use Framadate\Editable;
use Framadate\Utils;
use Framadate\Security\PasswordHasher;
include_once __DIR__ . '/app/inc/init.php';
@ -60,12 +62,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 +83,9 @@ if ($goToStep2) {
$_SESSION['form']->receiveNewVotes = $receiveNewVotes;
$_SESSION['form']->receiveNewComments = $receiveNewComments;
$_SESSION['form']->hidden = $hidden;
$_SESSION['form']->use_password = ($use_password !== null);
$_SESSION['form']->results_publicly_visible = ($results_publicly_visible !== null);
if ($config['use_smtp'] == true) {
if (empty($mail)) {
@ -101,7 +112,24 @@ 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 no errors, we hash the password if needed
if ($_SESSION['form']->use_password) {
$_SESSION['form']->password_hash = PasswordHasher::hash($password);
} else {
$_SESSION['form']->password_hash = null;
$_SESSION['form']->results_publicly_visible = null;
}
if ($goToStep2 == 'date') {
header('Location:create_date_poll.php');
@ -143,6 +171,16 @@ $errors = array(
'msg' => '',
'aria' => '',
'class' => ''
),
'password' => array(
'msg' => '',
'aria' => '',
'class' => ''
),
'password_repeat' => array(
'msg' => '',
'aria' => '',
'class' => ''
)
);
@ -182,6 +220,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 +250,8 @@ $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('form', $_SESSION['form']);
$smarty->display('create_poll.tpl');

View File

@ -167,6 +167,7 @@ caption {
#poll-rules-form .btn-edit,
#poll-hidden-form .btn-edit,
#expiration-form .btn-edit,
#password-form .btn-edit,
#name-form .btn-edit {
position:absolute;
left:-2000px;
@ -184,6 +185,8 @@ caption {
#poll-hidden-form:hover .btn-edit,
#expiration-form .btn-edit:focus,
#expiration-form:hover .btn-edit,
#password-form .btn-edit:focus,
#password-form:hover .btn-edit,
#name-form .btn-edit:focus,
#name-form:hover .btn-edit {
position:relative !important;
@ -488,4 +491,14 @@ table.results > tbody > tr:hover > td .glyphicon {
/* Admin */
#poll_search {
cursor: pointer;
}
/* Studs */
.password_request {
padding-top: 15px;
padding-bottom: 15px;
}
#password-form .btn-cancel {
float: right;
}

View File

@ -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,17 @@ $(document).ready(function () {
}
});
/**
* Hide/Show password options
*/
$("#use_password").change(function(){
if ($(this).prop("checked")) {
$("#password_options").removeClass("hidden");
} else {
$("#password_options").addClass("hidden");
}
});
// Check cookies are enabled too
var cookieEnabled = function () {
var cookieEnabled = navigator.cookieEnabled;

View File

@ -49,40 +49,45 @@ $(document).ready(function () {
form.submit(function(event) {
event.preventDefault();
$.ajax({
type: 'POST',
url: form.attr('action'),
data: form.serialize(),
dataType: 'json',
success: function(data)
{
$('#comment').val('');
if (data.result) {
$('#comments_list')
.replaceWith(data.comments);
var lastComment = $('#comments_list')
.find('div.comment')
.last();
lastComment.effect('highlight', {color: 'green'}, 401);
$('html, body').animate({
scrollTop: lastComment.offset().top
}, 750);
} else {
var newMessage = $('#genericErrorTemplate').clone();
newMessage
.find('.contents')
.text(data.message.message);
var commentsAlert = $('#comments_alerts');
commentsAlert
.empty()
.append(newMessage);
$('html, body').animate({
scrollTop: commentsAlert.offset().top
}, 750);
if ($('#comment').val()) {
$('#add_comment').attr("disabled", "disabled");
$.ajax({
type: 'POST',
url: form.attr('action'),
data: form.serialize(),
dataType: 'json',
success: function(data)
{
$('#comment').val('');
if (data.result) {
$('#comments_list')
.replaceWith(data.comments);
var lastComment = $('#comments_list')
.find('div.comment')
.last();
lastComment.effect('highlight', {color: 'green'}, 401);
$('html, body').animate({
scrollTop: lastComment.offset().top
}, 750);
} else {
var newMessage = $('#genericErrorTemplate').clone();
newMessage
.find('.contents')
.text(data.message.message);
var commentsAlert = $('#comments_alerts');
commentsAlert
.empty()
.append(newMessage);
$('html, body').animate({
scrollTop: commentsAlert.offset().top
}, 750);
}
},
complete: function() {
$('#add_comment').removeAttr("disabled");
}
}
});
});
}
return false;
});

View File

@ -104,6 +104,35 @@ $(document).ready(function() {
return false;
});
$('#password-form .btn-edit').on('click', function() {
$('#password-form p').hide();
$('#password-form .js-password').removeClass('hidden');
$('#password').focus();
return false;
});
$('#password-form .btn-cancel').on('click', function() {
$('#password-form p').show();
$('#password-form .js-password').addClass('hidden');
$('.js-password .btn-edit').focus();
return false;
});
// Hiding other field when the admin wants to remove the password protection
var removePassword = $('#removePassword');
removePassword.on('click', function() {
var removeButton = removePassword.siblings('button');
if (removePassword.is(":checked")) {
$('#password_information').addClass('hidden');
removeButton.removeClass('hidden');
} else {
$('#password_information').removeClass('hidden');
removeButton.addClass('hidden');
}
removeButton.focus();
});
// Horizontal scroll buttons
if($('.results').width() > $('.container').width()) {
$('.scroll-buttons').removeClass('hidden');

View File

@ -125,7 +125,11 @@
"Save the new rules": "Neue Regeln speichern",
"Cancel the rules edit": "Abbruch, Regeln nicht ändern",
"Results are hidden.": "Ergebnisse werden ausgeblendet.",
"Results are visible.": "Ergebnisse sind sichtbar."
"Results are visible.": "Ergebnisse sind sichtbar.",
"Password protected.": "DE_Protégé par mot de passe.",
"Votes protected by password.": "DE_Votes protégés par mot de passe.",
"No password.": "DE_Pas de mot de passe",
"Remove password.": "DE_Supprimer le mot de passe."
},
"Poll results": {
"Votes of the poll": "Stimmabgaben zur Umfrage",
@ -156,6 +160,13 @@
"anonyme": "anonym",
"Comment added": "Kommentar hinzugefügt"
},
"Password": {
"Password": "DE_Mot de passe",
"Wrong password": "DE_Mot de passe incorrect.",
"Submit access": "DE_Accèder",
"You have to provide a password to access the poll.": "DE_Vous devez donner le mot de passe pour avoir accès à ce sondage.",
"You have to provide a password so you can participate to the poll.": "DE_Vous devez donner le mot de passe pour pouvoir participer à ce sondage."
},
"studs": {
"If you want to vote in this poll, you have to give your name, choose the values that fit best for you and validate with the plus button at the end of the line.": "Wenn Sie an dieser Umfrage teilnehmen möchten, müssen Sie ihren Namen angeben, die Auswahl treffen, die Ihnen am ehesten zusagt und mit dem Plus-Button am Ende der Zeile bestätigen.",
"POLL_LOCKED_WARNING": "Die Abstimmung wurde vom Administrator gesperrt. Bewertungen und Kommentare werden eingefroren; es ist nicht mehr möglich, teilzunehmen",
@ -210,7 +221,11 @@
"Voters can modify their vote themselves": "Teilnehmer können ihre Wertungen verändern",
"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.",
"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 +353,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."
}
}

View File

@ -125,7 +125,11 @@
"Save the new rules": "Save the new rules",
"Cancel the rules edit": "Cancel the rules edit",
"Results are hidden.": "Results are hidden.",
"Results are visible.": "Results are visible."
"Results are visible.": "Results are visible.",
"Password protected.": "Password protected.",
"Votes protected by password.": "Votes protected by password.",
"No password.": "No password.",
"Remove password.": "Remove password."
},
"Poll results": {
"Votes of the poll": "Votes",
@ -156,6 +160,13 @@
"anonyme": "anonyme",
"Comment added": "Comment saved"
},
"Password": {
"Password": "Password",
"Wrong password": "Wrong password",
"Submit access": "Submit access",
"You have to provide a password to access the poll.": "You have to provide a password to access the poll.",
"You have to provide a password so you can participate to the poll.": "You have to provide a password so you can participate to the poll."
},
"studs": {
"If you want to vote in this poll, you have to give your name, choose the values that fit best for you and validate with the plus button at the end of the line.": "If you want to vote in this poll, you have to give your name, make your choice, and submit it with the plus button at the end of the line.",
"POLL_LOCKED_WARNING": "The administrator locked this poll. Votes and comments are frozen, it is no longer possible to participate",
@ -211,6 +222,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 +287,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 +353,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."
}
}

View File

@ -125,7 +125,11 @@
"Save the new rules": "ES_Enregistrer les nouvelles permissions",
"Cancel the rules edit": "ES_Annuler le changement de permissions",
"Results are hidden.": "ES_Les résultats sont cachés.",
"Results are visible.": "ES_Les résultats sont visibles."
"Results are visible.": "ES_Les résultats sont visibles.",
"Password protected.": "ES_Protégé par mot de passe.",
"Votes protected by password.": "ES_Votes protégés par mot de passe.",
"No password.": "ES_Pas de mot de passe",
"Remove password.": "ES_Supprimer le mot de passe."
},
"Poll results": {
"Votes of the poll": "ES_Votes du sondage",
@ -156,6 +160,13 @@
"anonyme": "ES_anonyme",
"Comment added": "ES_Commentaire ajouté"
},
"Password": {
"Password": "ES_Mot de passe",
"Wrong password": "ES_Mot de passe incorrect.",
"Submit access": "ES_Accèder",
"You have to provide a password to access the poll.": "ES_Vous devez donner le mot de passe pour avoir accès à ce sondage.",
"You have to provide a password so you can participate to the poll.": "ES_Vous devez donner le mot de passe pour pouvoir participer à ce sondage."
},
"studs": {
"If you want to vote in this poll, you have to give your name, choose the values that fit best for you and validate with the plus button at the end of the line.": "Para participar a esta encuesta, introduzca su nombre, elige todas las valores que son apriopriadas y validar su seleccion con el botón verde a la fin de línea.",
"POLL_LOCKED_WARNING": "ES_L'administrateur a verrouillé ce sondage. Les votes et commentaires sont gelés, il n'est plus possible de participer",
@ -211,6 +222,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 +353,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."
}
}

View File

@ -125,7 +125,11 @@
"Save the new rules": "Enregistrer les nouvelles permissions",
"Cancel the rules edit": "Annuler le changement de permissions",
"Results are hidden.": "Les résultats sont cachés.",
"Results are visible.": "Les résultats sont visibles."
"Results are visible.": "Les résultats sont visibles.",
"Password protected.": "Protégé par mot de passe.",
"Votes protected by password.": "Votes protégés par mot de passe.",
"No password.": "Pas de mot de passe",
"Remove password.": "Supprimer le mot de passe."
},
"Poll results": {
"Votes of the poll": "Votes du sondage",
@ -156,6 +160,13 @@
"anonyme": "anonyme",
"Comment added": "Commentaire ajouté"
},
"Password": {
"Password": "Mot de passe",
"Wrong password": "Mot de passe incorrect.",
"Submit access": "Accèder",
"You have to provide a password to access the poll.": "Vous devez donner le mot de passe pour avoir accès à ce sondage.",
"You have to provide a password so you can participate to the poll.": "Vous devez donner le mot de passe pour pouvoir participer à ce sondage."
},
"studs": {
"If you want to vote in this poll, you have to give your name, choose the values that fit best for you and validate with the plus button at the end of the line.": "Pour participer à ce sondage, veuillez entrer votre nom, choisir toutes les valeurs qui vous conviennent et valider votre choix avec le bouton en bout de ligne.",
"POLL_LOCKED_WARNING": "L'administrateur a verrouillé ce sondage. Les votes et commentaires sont gelés, il n'est plus possible de participer",
@ -211,6 +222,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 +278,6 @@
"Migration": "Migration",
"Purge": "Purge",
"Logs": "Historique",
"Installation": "Installation",
"Poll ID": "ID sondage",
"Format": "Format",
"Title": "Titre",
@ -353,6 +367,8 @@
"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",
"MISSING_VALUES": "Il manque des valeurs",
"CANT_CONNECT_TO_DATABASE": "Impossible de se connecter à la base de données"
"CANT_CONNECT_TO_DATABASE": "Impossible de se connecter à la base de données",
"Password is empty": "Le mot de passe est vide.",
"Passwords do not match": "Les mots de passe ne correspondent pas."
}
}

View File

@ -125,7 +125,11 @@
"Save the new rules": "Salvare i nuovi permessi",
"Cancel the rules edit": "Annullare le modifica dei permessi",
"Results are hidden.": "I risultati sono nascosti.",
"Results are visible.": "I risultati sono visibili."
"Results are visible.": "I risultati sono visibili.",
"Password protected.": "IT_Protégé par mot de passe.",
"Votes protected by password.": "IT_Votes protégés par mot de passe.",
"No password.": "IT_Pas de mot de passe",
"Remove password.": "IT_Supprimer le mot de passe."
},
"Poll results": {
"Votes of the poll": "Voti del sondaggio ",
@ -156,6 +160,13 @@
"anonyme": "anonimo",
"Comment added": "Commento aggiunto"
},
"Password": {
"Password": "IT_Mot de passe",
"Wrong password": "IT_Mot de passe incorrect.",
"Submit access": "IT_Accèder",
"You have to provide a password to access the poll.": "IT_Vous devez donner le mot de passe pour avoir accès à ce sondage.",
"You have to provide a password so you can participate to the poll.": "IT_Vous devez donner le mot de passe pour pouvoir participer à ce sondage."
},
"studs": {
"If you want to vote in this poll, you have to give your name, choose the values that fit best for you and validate with the plus button at the end of the line.": "Per partecipare a questo sondaggio, è necessario inserire vostro nome, scegliere tutti i parametri che preferite e confermare la vostra scelta con il pulsante alla fine della riga.",
"POLL_LOCKED_WARNING": "L'amministratore ha bloccato questa indagine. Voti e commenti sono congelati, non è più possibile partecipare",
@ -211,6 +222,10 @@
"To receive an email for each new vote": "Per ricevere un'email per ogni voto nuovo",
"To receive an email for each new comment": "Ricevi una e-mail ogni commento",
"Only the poll maker can see the poll's results": "Solo il creatore sondaggio possono vedere i risultati",
"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": "Andare al punto 2"
},
"Step 2": {

169
studs.php
View File

@ -21,6 +21,7 @@ use Framadate\Services\PollService;
use Framadate\Services\InputService;
use Framadate\Services\MailService;
use Framadate\Services\NotificationService;
use Framadate\Services\SecurityService;
use Framadate\Message;
use Framadate\Utils;
use Framadate\Editable;
@ -34,6 +35,11 @@ $poll_id = null;
$poll = null;
$message = null;
$editingVoteId = 0;
$accessGranted = true;
$resultPubliclyVisible = true;
$slots = array();
$votes = array();
$comments = array();
/* Services */
/*----------*/
@ -43,6 +49,7 @@ $pollService = new PollService($connect, $logService);
$inputService = new InputService();
$mailService = new MailService($config['use_smtp']);
$notificationService = new NotificationService($mailService);
$securityService = new SecurityService();
/* PAGE */
@ -62,101 +69,109 @@ if (!$poll) {
}
// -------------------------------
// A vote is going to be edited
// Password verification
// -------------------------------
if (!empty($_GET['vote'])) {
$editingVoteId = filter_input(INPUT_GET, 'vote', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => POLL_REGEX]]);
}
if (!is_null($poll->password_hash)) {
// -------------------------------
// Something to save (edit or add)
// -------------------------------
if (!empty($_POST['save'])) { // Save edition of an old vote
$name = $inputService->filterName($_POST['name']);
$editedVote = filter_input(INPUT_POST, 'save', FILTER_VALIDATE_INT);
$choices = $inputService->filterArray($_POST['choices'], FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => CHOICE_REGEX]]);
if (empty($editedVote)) {
$message = new Message('danger', __('Error', 'Something is going wrong...'));
}
if (count($choices) != count($_POST['choices'])) {
$message = new Message('danger', __('Error', 'There is a problem with your choices'));
// If we came from password submission
$password = isset($_POST['password']) ? $_POST['password'] : null;
if (!empty($password)) {
$securityService->submitPollAccess($poll, $password);
}
if ($message == null) {
// Update vote
$result = $pollService->updateVote($poll_id, $editedVote, $name, $choices);
if ($result) {
if ($poll->editable == Editable::EDITABLE_BY_OWN) {
$editedVoteUniqId = filter_input(INPUT_POST, 'edited_vote', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => POLL_REGEX]]);
$urlEditVote = Utils::getUrlSondage($poll_id, false, $editedVoteUniqId);
$message = new Message('success', __('studs', 'Your vote has been registered successfully, but be careful: regarding this poll options, you need to keep this personal link to edit your own vote:'), $urlEditVote);
} else {
$message = new Message('success', __('studs', 'Update vote succeeded'));
}
$notificationService->sendUpdateNotification($poll, NotificationService::UPDATE_VOTE, $name);
} else {
$message = new Message('danger', __('Error', 'Update vote failed'));
}
if (!$securityService->canAccessPoll($poll)) {
$accessGranted = false;
}
} elseif (isset($_POST['save'])) { // Add a new vote
$name = $inputService->filterName($_POST['name']);
$choices = $inputService->filterArray($_POST['choices'], FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => CHOICE_REGEX]]);
$resultPubliclyVisible = $poll->results_publicly_visible;
if ($name == null) {
$message = new Message('danger', __('Error', 'The name is invalid.'));
}
if (count($choices) != count($_POST['choices'])) {
$message = new Message('danger', __('Error', 'There is a problem with your choices'));
}
if ($message == null) {
// Add vote
$result = $pollService->addVote($poll_id, $name, $choices);
if ($result) {
if ($poll->editable == Editable::EDITABLE_BY_OWN) {
$urlEditVote = Utils::getUrlSondage($poll_id, false, $result->uniqId);
$message = new Message('success', __('studs', 'Your vote has been registered successfully, but be careful: regarding this poll options, you need to keep this personal link to edit your own vote:'), $urlEditVote);
} else {
$message = new Message('success', __('studs', 'Adding the vote succeeded'));
}
$notificationService->sendUpdateNotification($poll, NotificationService::ADD_VOTE, $name);
} else {
$message = new Message('danger', __('Error', 'Adding vote failed'));
}
if (!$accessGranted && !empty($password)) {
$message = new Message('danger', __('Password', 'Wrong password'));
} else if (!$accessGranted && !$resultPubliclyVisible) {
$message = new Message('danger', __('Password', 'You have to provide a password to access the poll.'));
} else if (!$accessGranted && $resultPubliclyVisible) {
$message = new Message('danger', __('Password', 'You have to provide a password so you can participate to the poll.'));
}
}
// -------------------------------
// Add a comment
// -------------------------------
// We allow actions only if access is granted
if ($accessGranted) {
if (isset($_POST['add_comment'])) {
$name = $inputService->filterName($_POST['name']);
$comment = $inputService->filterComment($_POST['comment']);
// -------------------------------
// A vote is going to be edited
// -------------------------------
if ($name == null) {
$message = new Message('danger', __('Error', 'The name is invalid.'));
if (!empty($_GET['vote'])) {
$editingVoteId = filter_input(INPUT_GET, 'vote', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => POLL_REGEX]]);
}
if ($message == null) {
// Add comment
$result = $pollService->addComment($poll_id, $name, $comment);
if ($result) {
$message = new Message('success', __('Comments', 'Comment added'));
$notificationService->sendUpdateNotification($poll, NotificationService::ADD_COMMENT, $name);
} else {
$message = new Message('danger', __('Error', 'Comment failed'));
// -------------------------------
// Something to save (edit or add)
// -------------------------------
if (!empty($_POST['save'])) { // Save edition of an old vote
$name = $inputService->filterName($_POST['name']);
$editedVote = filter_input(INPUT_POST, 'save', FILTER_VALIDATE_INT);
$choices = $inputService->filterArray($_POST['choices'], FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => CHOICE_REGEX]]);
if (empty($editedVote)) {
$message = new Message('danger', __('Error', 'Something is going wrong...'));
}
if (count($choices) != count($_POST['choices'])) {
$message = new Message('danger', __('Error', 'There is a problem with your choices'));
}
if ($message == null) {
// Update vote
$result = $pollService->updateVote($poll_id, $editedVote, $name, $choices);
if ($result) {
if ($poll->editable == Editable::EDITABLE_BY_OWN) {
$editedVoteUniqId = filter_input(INPUT_POST, 'edited_vote', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => POLL_REGEX]]);
$urlEditVote = Utils::getUrlSondage($poll_id, false, $editedVoteUniqId);
$message = new Message('success', __('studs', 'Your vote has been registered successfully, but be careful: regarding this poll options, you need to keep this personal link to edit your own vote:'), $urlEditVote);
} else {
$message = new Message('success', __('studs', 'Update vote succeeded'));
}
$notificationService->sendUpdateNotification($poll, NotificationService::UPDATE_VOTE, $name);
} else {
$message = new Message('danger', __('Error', 'Update vote failed'));
}
}
} elseif (isset($_POST['save'])) { // Add a new vote
$name = $inputService->filterName($_POST['name']);
$choices = $inputService->filterArray($_POST['choices'], FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => CHOICE_REGEX]]);
if ($name == null) {
$message = new Message('danger', __('Error', 'The name is invalid.'));
}
if (count($choices) != count($_POST['choices'])) {
$message = new Message('danger', __('Error', 'There is a problem with your choices'));
}
if ($message == null) {
// Add vote
$result = $pollService->addVote($poll_id, $name, $choices);
if ($result) {
if ($poll->editable == Editable::EDITABLE_BY_OWN) {
$urlEditVote = Utils::getUrlSondage($poll_id, false, $result->uniqId);
$message = new Message('success', __('studs', 'Your vote has been registered successfully, but be careful: regarding this poll options, you need to keep this personal link to edit your own vote:'), $urlEditVote);
} else {
$message = new Message('success', __('studs', 'Adding the vote succeeded'));
}
$notificationService->sendUpdateNotification($poll, NotificationService::ADD_VOTE, $name);
} else {
$message = new Message('danger', __('Error', 'Adding vote failed'));
}
}
}
}
// Retrieve data
$slots = $pollService->allSlotsByPoll($poll);
$votes = $pollService->allVotesByPollId($poll_id);
$comments = $pollService->allCommentsByPollId($poll_id);
if ($resultPubliclyVisible) {
$slots = $pollService->allSlotsByPoll($poll);
$votes = $pollService->allVotesByPollId($poll_id);
$comments = $pollService->allCommentsByPollId($poll_id);
}
// Assign data to template
$smarty->assign('poll_id', $poll_id);
@ -172,5 +187,7 @@ $smarty->assign('editingVoteId', $editingVoteId);
$smarty->assign('message', $message);
$smarty->assign('admin', false);
$smarty->assign('hidden', $poll->hidden);
$smarty->assign('accessGranted', $accessGranted);
$smarty->assign('resultPubliclyVisible', $resultPubliclyVisible);
$smarty->display('studs.tpl');

View File

@ -49,7 +49,7 @@
</div>
{/if}
<div class="form-group '.$errors['name']['class'].'">
<div class="form-group {$errors['name']['class']}">
<label for="yourname" class="col-sm-4 control-label">{__('Generic', 'Your name')} *</label>
<div class="col-sm-8">
@ -69,7 +69,7 @@
{/if}
{if $use_smtp}
<div class="form-group '.$errors['email']['class'].'">
<div class="form-group {$errors['email']['class']}">
<label for="email" class="col-sm-4 control-label">
{__('Generic', 'Your email address')} *<br/>
<span class="small">{__('Generic', '(in the format name@mail.com)')}</span>
@ -144,18 +144,61 @@
<label>
<input type="checkbox" name="hidden" {if $poll_hidden}checked{/if}
id="hidden">
{__('Step 1', 'Only the poll maker can see the poll\'s results')}
{__('Step 1', "Only the poll maker can see the poll's results")}
</label>
</div>
<div id="hiddenWithBadEditionModeError" class="alert alert-danger hidden">
<p>
{__('Error', 'You can\'t create a poll with hidden results with the following edition option:')}"{__('Step 1', 'All voters can modify any vote')}"
{__('Error', "You can't create a poll with hidden results with the following edition option:")}"{__('Step 1', 'All voters can modify any vote')}"
</p>
</div>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<div class="checkbox">
<label>
<input type="checkbox" name="use_password" {if $poll_use_password}checked{/if}
id="use_password">
{__('Step 1', "Use a password to restrict access")}
</label>
</div>
</div>
<div id="password_options"{if !$poll_use_password} class="hidden"{/if}>
<label for="poll_password" class="col-sm-4 control-label">{__('Step 1', 'Poll password')}</label>
<div class="col-sm-8">
<input id="poll_password" type="password" name="password" class="form-control" {$errors['password']['aria']}/>
</div>
{if !empty($errors['password']['msg'])}
<div class="alert alert-danger">
<p id="poll_password_error">
{$errors['password']['msg']}
</p>
</div>
{/if}
<label for="poll_password_repeat" class="col-sm-4 control-label">{__('Step 1', 'Confirm password')}</label>
<div class="col-sm-8">
<input id="poll_password_repeat" type="password" name="password_repeat" class="form-control" {$errors['password_repeat']['aria']}/>
</div>
{if !empty($errors['password_repeat']['msg'])}
<div class="alert alert-danger">
<p id="poll_password_repeat_error">
{$errors['password_repeat']['msg']}
</p>
</div>
{/if}
<div class="col-sm-offset-4 col-sm-8">
<div class="checkbox">
<label>
<input type="checkbox" name="results_publicly_visible"
{if $poll_results_publicly_visible}checked{/if} id="results_publicly_visible"/>
{__('Step 1', "The results are publicly visible")}
</label>
</div>
</div>
</div>
</div>

View File

@ -3,12 +3,11 @@
{* Comment list *}
{include 'part/comments_list.tpl'}
<form action="action/add_comment.php" method="POST" id="comment_form">
{* Add comment form *}
{if $active && !$expired && $accessGranted}
<form action="action/add_comment.php" method="POST" id="comment_form">
<input type="hidden" name="poll" value="{$poll_id}"/>
{* Add comment form *}
{if $active && !$expired}
<input type="hidden" name="poll" value="{$poll_id}"/>
<div class="hidden-print jumbotron">
<div class="col-md-6 col-md-offset-3">
<fieldset id="add-comment"><legend>{__('Comments', 'Add a comment to the poll')}</legend>
@ -21,11 +20,11 @@
<textarea name="comment" id="comment" class="form-control" rows="2" cols="40"></textarea>
</div>
<div class="pull-right">
<input type="submit" name="add_comment" value="{__('Comments', 'Send the comment')}" class="btn btn-success">
<input type="submit" id="add_comment" name="add_comment" value="{__('Comments', 'Send the comment')}" class="btn btn-success">
</div>
</fieldset>
</div>
<div class="clearfix"></div>
</div>
{/if}
</form>
</form>
{/if}

View File

@ -0,0 +1,18 @@
{if !$expired && ($active || $resultPubliclyVisible)}
<hr role="presentation" id="password_request" class="hidden-print"/>
<div class="panel panel-danger password_request alert-danger">
<div class="col-md-6 col-md-offset-3">
<form action="" method="POST" class="form-inline">
<input type="hidden" name="poll" value="{$poll_id}"/>
<div class="form-group">
<label for="password" class="control-label">{__('Password', 'Password')}</label>
<input type="password" name="password" id="password" class="form-control" />
<input type="submit" value="{__('Password', 'Submit access')}" class="btn btn-success">
</div>
</form>
</div>
<div class="clearfix"></div>
</div>
{/if}

View File

@ -120,7 +120,42 @@
</div>
{if $admin}
<div class="row">
<div class="col-md-4 col-md-offset-4" >
<div class="col-md-4">
<div id="password-form">
{if !empty($poll->password_hash) && !$poll->results_publicly_visible}
{$password_text = __('PollInfo', 'Password protected.')}
{elseif !empty($poll->password_hash) && $poll->results_publicly_visible}
{$password_text = __('PollInfo', 'Votes protected by password.')}
{else}
{$password_text = __('PollInfo', 'No password.')}
{/if}
<p class=""><span class="glyphicon glyphicon-lock"> </span> {$password_text}<button class="btn btn-link btn-sm btn-edit" title="{__('PollInfo', 'Edit the poll rules')}"><span class="glyphicon glyphicon-pencil"></span><span class="sr-only">{__('Generic', 'Edit')}</span></button></p>
<div class="hidden js-password">
<button class="btn btn-link btn-cancel" title="{__('PollInfo', 'Cancel the rules edit')}"><span class="glyphicon glyphicon-remove"></span><span class="sr-only">{__('Generic', 'Cancel')}</span></button>
{if !empty($poll->password_hash)}
<div class="input-group">
<input type="checkbox" id="removePassword" name="removePassword"/>
<label for="removePassword">{__('PollInfo', 'Remove password.')}</label>
<button type="submit" name="update_poll_info" value="removePassword" class="btn btn-success hidden" title="{__('PollInfo', 'Save the new rules')}"><span class="glyphicon glyphicon-ok"></span><span class="sr-only">{__('Generic', 'Remove password.')}</span></button>
</div>
{/if}
<div id="password_information">
<div class="input-group">
<input type="checkbox" id="resultsPubliclyVisible" name="resultsPubliclyVisible" {if $poll->results_publicly_visible}checked="checked"{/if}/>
<label for="resultsPubliclyVisible">{__('PollInfo', 'Results are visible.')}</label>
</div>
<div class="input-group">
<input type="text" class="form-control" id="password" name="password"/>
<span class="input-group-btn">
<button type="submit" name="update_poll_info" value="password" class="btn btn-success" title="{__('PollInfo', 'Save the new rules')}"><span class="glyphicon glyphicon-ok"></span><span class="sr-only">{__('Generic', 'Save')}</span></button>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-4 ">
<div id="poll-hidden-form">
{if $poll->hidden}
{$hidden_icon = "glyphicon-eye-close"}

View File

@ -4,7 +4,9 @@
<h3>
{__('Poll results', 'Votes of the poll')} {if $hidden}<i>({__('PollInfo', 'Results are hidden.')})</i>{/if}
<a href="" data-toggle="modal" data-target="#hint_modal"><i class="glyphicon glyphicon-info-sign"></i></a>
{if $accessGranted}
<a href="" data-toggle="modal" data-target="#hint_modal"><i class="glyphicon glyphicon-info-sign"></i></a>
{/if}
</h3>
<div id="tableContainer" class="tableContainer">
@ -97,7 +99,7 @@
{/foreach}
{if $active && !$expired && ($poll->editable == constant('Framadate\Editable::EDITABLE_BY_ALL') or $admin)}
{if $active && !$expired && ($poll->editable == constant('Framadate\Editable::EDITABLE_BY_ALL') or $admin) && $accessGranted}
<td class="hidden-print">
<a href="{if $admin}{poll_url id=$poll->admin_id vote_id=$vote->uniqId admin=true}{else}{poll_url id=$poll->id vote_id=$vote->uniqId}{/if}" class="btn btn-default btn-sm" title="{__('Poll results', 'Edit the line:')|html} {$vote->name|html}">
<i class="glyphicon glyphicon-pencil"></i><span class="sr-only">{__('Generic', 'Edit')}</span>
@ -119,7 +121,7 @@
{* Line to add a new vote *}
{if $active && $editingVoteId === 0 && !$expired}
{if $active && $editingVoteId === 0 && !$expired && $accessGranted}
<tr id="vote-form" class="hidden-print">
<td class="bg-info" style="padding:5px">
<div class="input-group input-group-sm">

View File

@ -4,7 +4,9 @@
<h3>
{__('Poll results', 'Votes of the poll')} {if $hidden}<i>({__('PollInfo', 'Results are hidden.')})</i>{/if}
<a href="" data-toggle="modal" data-target="#hint_modal"><i class="glyphicon glyphicon-info-sign"></i></a>
{if $accessGranted}
<a href="" data-toggle="modal" data-target="#hint_modal"><i class="glyphicon glyphicon-info-sign"></i></a>
{/if}
</h3>
@ -148,7 +150,7 @@
{/foreach}
{if $active && !$expired && ($poll->editable == constant('Framadate\Editable::EDITABLE_BY_ALL') or $admin)}
{if $active && !$expired && ($poll->editable == constant('Framadate\Editable::EDITABLE_BY_ALL') or $admin) && $accessGranted}
<td class="hidden-print">
<a href="{if $admin}{poll_url id=$poll->admin_id vote_id=$vote->uniqId admin=true}{else}{poll_url id=$poll->id vote_id=$vote->uniqId}{/if}" class="btn btn-default btn-sm" title="{__('Poll results', 'Edit the line:')|escape} {$vote->name|html}">
<i class="glyphicon glyphicon-pencil"></i><span class="sr-only">{__('Generic', 'Edit')}</span>
@ -170,7 +172,7 @@
{* Line to add a new vote *}
{if $active && $editingVoteId === 0 && !$expired}
{if $active && $editingVoteId === 0 && !$expired && $accessGranted}
<tr id="vote-form" class="hidden-print">
<td class="bg-info" style="padding:5px">
<div class="input-group input-group-sm">

View File

@ -12,6 +12,7 @@
{block name=main}
{* Messages *}
<div id="message-container">
{if !empty($message)}
<div class="alert alert-dismissible alert-{$message->type|html} hidden-print" role="alert">{$message->message|html}{if $message->link != null}<br/><a href="{$message->link}">{$message->link}</a>{/if}<button type="button" class="close" data-dismiss="alert" aria-label="{__('Generic', 'CLose')}"><span aria-hidden="true">&times;</span></button></div>
@ -20,49 +21,55 @@
<div id="nameErrorMessage" class="hidden alert alert-dismissible alert-danger hidden-print" role="alert">{__('Error', 'The name is invalid.')}<button type="button" class="close" data-dismiss="alert" aria-label="{__('Generic', 'CLose')}"><span aria-hidden="true">&times;</span></button></div>
<div id="genericErrorTemplate" class="hidden alert alert-dismissible alert-danger hidden-print" role="alert"><span class="contents"></span><button type="button" class="close" data-dismiss="alert" aria-label="{__('Generic', 'CLose')}"><span aria-hidden="true">&times;</span></button></div>
{if !$accessGranted && !$resultPubliclyVisible}
{* Global informations about the current poll *}
{include 'part/password_request.tpl' active=$poll->active}
{include 'part/poll_info.tpl' admin=$admin}
{* Information about voting *}
{if $expired}
<div class="alert alert-danger">
<p>{__('studs', 'The poll is expired, it will be deleted soon.')}</p>
<p>{__('studs', 'Deletion date:')} {$deletion_date|date_format:$date_format['txt_short']|html}</p>
</div>
{else}
{if $admin}
{include 'part/poll_hint_admin.tpl'}
{else}
{include 'part/poll_hint.tpl' active=$poll->active}
{* Global informations about the current poll *}
{include 'part/poll_info.tpl' admin=$admin}
{* Information about voting *}
{if $expired}
<div class="alert alert-danger">
<p>{__('studs', 'The poll is expired, it will be deleted soon.')}</p>
<p>{__('studs', 'Deletion date:')} {$deletion_date|date_format:$date_format['txt_short']|html}</p>
</div>
{else}
{if $admin}
{include 'part/poll_hint_admin.tpl'}
{else}
{include 'part/poll_hint.tpl' active=$poll->active}
{/if}
{/if}
{* Scroll left and right *}
<div class="hidden row scroll-buttons" aria-hidden="true">
<div class="btn-group pull-right">
<button class="btn btn-sm btn-link scroll-left" title="{__('Poll results', 'Scroll to the left')}">
<span class="glyphicon glyphicon-chevron-left"></span>
</button>
<button class="btn btn-sm btn-link scroll-right" title="{__('Poll results', 'Scroll to the right')}">
<span class="glyphicon glyphicon-chevron-right"></span>
</button>
</div>
</div>
{if !$accessGranted && $resultPubliclyVisible}
{include 'part/password_request.tpl' active=$poll->active}
{/if}
{* Vote table *}
{if $poll->format === 'D'}
{include 'part/vote_table_date.tpl' active=$poll->active}
{else}
{include 'part/vote_table_classic.tpl' active=$poll->active}
{/if}
{* Comments *}
{include 'part/comments.tpl' active=$poll->active comments=$comments}
{/if}
{/if}
{* Scroll left and right *}
<div class="hidden row scroll-buttons" aria-hidden="true">
<div class="btn-group pull-right">
<button class="btn btn-sm btn-link scroll-left" title="{__('Poll results', 'Scroll to the left')}">
<span class="glyphicon glyphicon-chevron-left"></span>
</button>
<button class="btn btn-sm btn-link scroll-right" title="{__('Poll results', 'Scroll to the right')}">
<span class="glyphicon glyphicon-chevron-right"></span>
</button>
</div>
</div>
{* Vote table *}
{if $poll->format === 'D'}
{include 'part/vote_table_date.tpl' active=$poll->active}
{else}
{include 'part/vote_table_classic.tpl' active=$poll->active}
{/if}
{* Comments *}
{include 'part/comments.tpl' active=$poll->active comments=$comments}
{/block}