[Big] Move displaying dates from libc to PHP-intl / ICU

* Make sure we always work only with DateTimes
* Support slots again under SQLite
* Small fixes

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2019-04-19 20:20:55 +02:00
parent 9aaf5b3bbd
commit bc964e87a7
No known key found for this signature in database
GPG Key ID: A061B9DDE0CA0773
31 changed files with 456 additions and 154 deletions

View File

@ -16,11 +16,14 @@
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
use Doctrine\DBAL\DBALException;
use Framadate\Editable;
use Framadate\Exception\AlreadyExistsException;
use Framadate\Exception\ConcurrentEditionException;
use Framadate\Exception\ConcurrentVoteException;
use Framadate\Exception\MomentAlreadyExistsException;
use Framadate\Exception\SlotAlreadyExistsException;
use Framadate\Message;
use Framadate\Security\PasswordHasher;
use Framadate\Services\AdminPollService;
@ -465,7 +468,7 @@ if (isset($_POST['confirm_add_column'])) {
exit_displaying_add_column(new Message('danger', __('Error', "Can't create an empty column.")));
}
if ($poll->format === 'D') {
$date = DateTime::createFromFormat(__('Date', 'Y-m-d'), $_POST['newdate'])->setTime(0, 0, 0);
$date = $inputService->filterDate($_POST['newdate']);
$time = $date->getTimestamp();
$newmoment = strip_tags($_POST['newmoment']);
$adminPollService->addDateSlot($poll_id, $time, $newmoment);
@ -475,8 +478,12 @@ if (isset($_POST['confirm_add_column'])) {
}
$message = new Message('success', __('adminstuds', 'Choice added'));
} catch (SlotAlreadyExistsException $e) {
exit_displaying_add_column(new Message('danger', __f('Error', 'The column %s already exists', $e->getSlot())));
} catch (MomentAlreadyExistsException $e) {
exit_displaying_add_column(new Message('danger', __('Error', 'The column already exists')));
exit_displaying_add_column(new Message('danger', __f('Error', 'The column %s already exists with %s', $e->getSlot(), $e->getMoment())));
} catch (DBALException $e) {
exit_displaying_add_column(new Message('danger', __('Error', 'Error while adding a column')));
}
}
@ -484,14 +491,16 @@ if (isset($_POST['confirm_add_column'])) {
$slots = $pollService->allSlotsByPoll($poll);
$votes = $pollService->allVotesByPollId($poll_id);
$comments = $pollService->allCommentsByPollId($poll_id);
$deletion_date = clone $poll->end_date;
$deletion_date->add(new DateInterval('P' . PURGE_DELAY . 'D'));
// Assign data to template
$smarty->assign('poll_id', $poll_id);
$smarty->assign('admin_poll_id', $admin_poll_id);
$smarty->assign('poll', $poll);
$smarty->assign('title', __('Generic', 'Poll') . ' - ' . $poll->title);
$smarty->assign('expired', strtotime($poll->end_date) < time());
$smarty->assign('deletion_date', strtotime($poll->end_date) + PURGE_DELAY * 86400);
$smarty->assign('expired', $poll->end_date < new DateTime());
$smarty->assign('deletion_date', $deletion_date);
$smarty->assign('slots', $poll->format === 'D' ? $pollService->splitSlots($slots) : $slots);
$smarty->assign('slots_hash', $pollService->hashSlots($slots));
$smarty->assign('votes', $pollService->splitVotes($votes));

View File

@ -24,34 +24,34 @@ class Choice
* Name of the Choice
*/
private $name;
/**
* All availables slots for this Choice.
* All available slots for this Choice.
*/
private $slots;
public function __construct($name='')
public function __construct($name = '')
{
$this->name = $name;
$this->slots = [];
}
public function addSlot($slot)
{
$this->slots[] = $slot;
}
public function getName()
{
return $this->name;
}
public function getSlots()
{
return $this->slots;
}
static function compare(Choice $a, Choice $b)
public static function compare(Choice $a, Choice $b)
{
return strcmp($a->name, $b->name);
}

View File

@ -1,7 +1,20 @@
<?php
namespace Framadate\Exception;
class MomentAlreadyExistsException extends \Exception {
function __construct() {
class MomentAlreadyExistsException extends SlotAlreadyExistsException {
public $moment;
public function __construct($slot, $moment, $message = '')
{
parent::__construct($slot, $message);
$this->moment = $moment;
}
/**
* @return mixed
*/
public function getMoment()
{
return $this->moment;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace Framadate\Exception;
class SlotAlreadyExistsException extends \Exception {
public $slot;
public function __construct($slot, $message = '')
{
parent::__construct($message);
$this->slot = $slot;
}
/**
* @return mixed
*/
public function getSlot()
{
return $this->slot;
}
}

View File

@ -18,6 +18,8 @@
*/
namespace Framadate;
use DateTime;
class Form
{
public $title;
@ -26,7 +28,17 @@ class Form
public $admin_name;
public $admin_mail;
public $format;
/**
* @var DateTime
*/
public $end_date;
/**
* @var DateTime
*/
public $creation_date;
public $choix_sondage;
public $ValueMax;

View File

@ -2,6 +2,11 @@
namespace Framadate\Repositories;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ConnectionException;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Query\QueryBuilder;
use PDOStatement;
abstract class AbstractRepository {
/**
@ -24,7 +29,7 @@ abstract class AbstractRepository {
}
/**
* @throws \Doctrine\DBAL\ConnectionException
* @throws ConnectionException
*/
public function commit()
{
@ -32,17 +37,25 @@ abstract class AbstractRepository {
}
/**
* @throws \Doctrine\DBAL\ConnectionException
* @throws ConnectionException
*/
public function rollback()
{
$this->connect->rollback();
}
/**
* @return QueryBuilder
*/
public function createQueryBuilder()
{
return $this->connect->createQueryBuilder();
}
/**
* @param string $sql
* @throws \Doctrine\DBAL\DBALException
* @return bool|\Doctrine\DBAL\Driver\Statement|\PDOStatement
*@throws DBALException
* @return bool|Statement|PDOStatement
*/
public function prepare($sql)
{
@ -51,8 +64,8 @@ abstract class AbstractRepository {
/**
* @param string $sql
* @throws \Doctrine\DBAL\DBALException
* @return bool|\Doctrine\DBAL\Driver\Statement|\PDOStatement
*@throws DBALException
* @return bool|Statement|PDOStatement
*/
public function query($sql)
{

View File

@ -1,19 +1,30 @@
<?php
namespace Framadate\Repositories;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\DBAL\Types\Type;
use Framadate\Utils;
class CommentRepository extends AbstractRepository {
/**
* @param $poll_id
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return array
*/
public function findAllByPollId($poll_id) {
$prepared = $this->prepare('SELECT * FROM ' . Utils::table('comment') . ' WHERE poll_id = ? ORDER BY id');
$prepared->execute([$poll_id]);
return $prepared->fetchAll();
$comments = $prepared->fetchAll();
/**
* Hack to make date a proper DateTime
*/
return array_map(function($comment) {
$comment->date = Type::getType(Type::DATETIME)->convertToPhpValue($comment->date, $this->connect->getDatabasePlatform());
return $comment;
}, $comments);
}
/**
@ -24,7 +35,7 @@ class CommentRepository extends AbstractRepository {
* @param $comment
* @return bool
*/
function insert($poll_id, $name, $comment)
public function insert($poll_id, $name, $comment)
{
return $this->connect->insert(Utils::table('comment'), ['poll_id' => $poll_id, 'name' => $name, 'comment' => $comment]) > 0;
}
@ -32,10 +43,10 @@ class CommentRepository extends AbstractRepository {
/**
* @param $poll_id
* @param $comment_id
* @throws \Doctrine\DBAL\Exception\InvalidArgumentException
* @throws InvalidArgumentException
* @return bool
*/
function deleteById($poll_id, $comment_id)
public function deleteById($poll_id, $comment_id)
{
return $this->connect->delete(Utils::table('comment'), ['poll_id' => $poll_id, 'id' => $comment_id]) > 0;
}
@ -44,10 +55,10 @@ class CommentRepository extends AbstractRepository {
* Delete all comments of a given poll.
*
* @param $poll_id int The ID of the given poll.
* @throws \Doctrine\DBAL\Exception\InvalidArgumentException
* @throws InvalidArgumentException
* @return bool true if action succeeded.
*/
function deleteByPollId($poll_id)
public function deleteByPollId($poll_id)
{
return $this->connect->delete(Utils::table('comment'), ['poll_id' => $poll_id]) > 0;
}
@ -56,7 +67,7 @@ class CommentRepository extends AbstractRepository {
* @param $poll_id
* @param $name
* @param $comment
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return bool
*/
public function exists($poll_id, $name, $comment)

View File

@ -1,6 +1,11 @@
<?php
namespace Framadate\Repositories;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Exception\InvalidArgumentException;
use Doctrine\DBAL\Types\Type;
use Exception;
use Framadate\Form;
use Framadate\Utils;
use PDO;
@ -8,9 +13,10 @@ class PollRepository extends AbstractRepository {
/**
* @param $poll_id
* @param $admin_poll_id
* @param $form
* @param Form $form
* @throws Exception
*/
public function insertPoll($poll_id, $admin_poll_id, $form)
public function insertPoll($poll_id, $admin_poll_id, Form $form)
{
$this->connect->insert(Utils::table('poll'), [
'id' => $poll_id,
@ -19,7 +25,7 @@ class PollRepository extends AbstractRepository {
'description' => $form->description,
'admin_name' => $form->admin_name,
'admin_mail' => $form->admin_mail,
'end_date' => (new \DateTime)->setTimestamp($form->end_date)->format('Y-m-d H:i:s'),
'end_date' => $form->end_date->format('Y-m-d H:i:s'),
'format' => $form->format,
'editable' => ($form->editable>=0 && $form->editable<=2) ? $form->editable : 0,
'receiveNewVotes' => $form->receiveNewVotes ? 1 : 0,
@ -34,7 +40,7 @@ class PollRepository extends AbstractRepository {
/**
* @param $poll_id
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return mixed
*/
public function findById($poll_id)
@ -44,12 +50,18 @@ class PollRepository extends AbstractRepository {
$poll = $prepared->fetch();
$prepared->closeCursor();
/**
* Hack to make date a proper DateTime
*/
$poll->creation_date = Type::getType(Type::DATETIME)->convertToPhpValue($poll->creation_date, $this->connect->getDatabasePlatform());
$poll->end_date = Type::getType(Type::DATETIME)->convertToPhpValue($poll->end_date, $this->connect->getDatabasePlatform());
return $poll;
}
/**
* @param $admin_poll_id
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return mixed
*/
public function findByAdminId($admin_poll_id) {
@ -58,12 +70,15 @@ class PollRepository extends AbstractRepository {
$poll = $prepared->fetch();
$prepared->closeCursor();
$poll->creation_date = Type::getType(Type::DATETIME)->convertToPhpValue($poll->creation_date, $this->connect->getDatabasePlatform());
$poll->end_date = Type::getType(Type::DATETIME)->convertToPhpValue($poll->end_date, $this->connect->getDatabasePlatform());
return $poll;
}
/**
* @param $poll_id
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return bool
*/
public function existsById($poll_id) {
@ -76,7 +91,7 @@ class PollRepository extends AbstractRepository {
/**
* @param $admin_poll_id
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return bool
*/
public function existsByAdminId($admin_poll_id) {
@ -98,7 +113,7 @@ class PollRepository extends AbstractRepository {
'admin_name' => $poll->admin_name,
'admin_mail' => $poll->admin_mail,
'description' => $poll->description,
'end_date' => $poll->end_date, # TODO : Harmonize dates between here and insert
'end_date' => $poll->end_date->format('Y-m-d H:i:s'), # TODO : Harmonize dates between here and insert
'active' => $poll->active,
'editable' => $poll->editable >= 0 && $poll->editable <= 2 ? $poll->editable : 0,
'hidden' => $poll->hidden ? 1 : 0,
@ -111,7 +126,7 @@ class PollRepository extends AbstractRepository {
/**
* @param $poll_id
* @throws \Doctrine\DBAL\Exception\InvalidArgumentException
* @throws InvalidArgumentException
* @return bool
*/
public function deleteById($poll_id)
@ -122,7 +137,7 @@ class PollRepository extends AbstractRepository {
/**
* Find old polls. Limit: 20.
*
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return array Array of old polls
*/
public function findOldPolls()
@ -138,7 +153,7 @@ class PollRepository extends AbstractRepository {
* @param array $search Array of search : ['id'=>..., 'title'=>..., 'name'=>..., 'mail'=>...]
* @param int $start The number of first entry to select
* @param int $limit The number of entries to find
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return array The found polls
*/
public function findAll($search, $start, $limit) {
@ -194,7 +209,7 @@ class PollRepository extends AbstractRepository {
* Find all polls that are created with the given admin mail.
*
* @param string $mail Email address of the poll admin
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return array The list of matching polls
*/
public function findAllByAdminMail($mail) {
@ -208,7 +223,7 @@ class PollRepository extends AbstractRepository {
* Get the total number of polls in database.
*
* @param array $search Array of search : ['id'=>..., 'title'=>..., 'name'=>...]
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return int The number of polls
*/
public function count($search = null) {

View File

@ -1,12 +1,15 @@
<?php
namespace Framadate\Repositories;
use Doctrine\DBAL\DBALException;
use Doctrine\DBAL\Driver\Statement;
use Doctrine\DBAL\Query\QueryBuilder;
use Framadate\Utils;
class VoteRepository extends AbstractRepository {
/**
* @param $poll_id
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return array
*/
public function allUserVotesByPollId($poll_id)
@ -18,17 +21,24 @@ class VoteRepository extends AbstractRepository {
}
/**
* @param $poll_id
* @param $insert_position
* @throws \Doctrine\DBAL\DBALException
* @return bool
* Insert default values for slots
*
* @param string $poll_id
* @param integer $insert_position
* @return Statement|int
*/
public function insertDefault($poll_id, $insert_position)
{
# TODO : Handle this on PHP's side
$prepared = $this->prepare('UPDATE ' . Utils::table('vote') . ' SET choices = CONCAT(SUBSTRING(choices, 1, ?), " ", SUBSTRING(choices, ?)) WHERE poll_id = ?'); //#51 : default value for unselected vote
return $prepared->execute([$insert_position, $insert_position + 1, $poll_id]);
$qb = $this->createQueryBuilder();
$sql = $this->connect->getDatabasePlatform()->getName() === 'sqlite' ?
$this->generateSQLForInsertDefaultSQLite($qb, $insert_position) :
$this->generateSQLForInsertDefault($qb, $insert_position)
;
$query = $qb->update(Utils::table('vote'))
->set('choices', $sql)
->where($qb->expr()->eq('poll_id', $qb->createNamedParameter($poll_id)))
;
return $query->execute();
}
function insert($poll_id, $name, $choices, $token, $mail) {
@ -48,7 +58,7 @@ class VoteRepository extends AbstractRepository {
/**
* @param $poll_id
* @param $vote_id
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return bool
*/
public function deleteById($poll_id, $vote_id)
@ -66,7 +76,7 @@ class VoteRepository extends AbstractRepository {
* Delete all votes of a given poll.
*
* @param $poll_id int The ID of the given poll.
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return bool|null true if action succeeded.
*/
public function deleteByPollId($poll_id)
@ -79,14 +89,20 @@ class VoteRepository extends AbstractRepository {
*
* @param $poll_id int The ID of the poll
* @param $index int The index of the vote into the poll
* @throws \Doctrine\DBAL\DBALException
* @return bool|null true if action succeeded.
*/
public function deleteByIndex($poll_id, $index)
{
$prepared = $this->prepare('UPDATE ' . Utils::table('vote') . ' SET choices = CONCAT(SUBSTR(choices, 1, ?), SUBSTR(choices, ?)) WHERE poll_id = ?');
return $prepared->execute([$index, $index + 2, $poll_id]);
$qb = $this->createQueryBuilder();
$sql = $this->connect->getDatabasePlatform()->getName() === 'sqlite' ?
$this->generateSQLForInsertDefaultSQLite($qb, $index, true) :
$this->generateSQLForInsertDefault($qb, $index, true)
;
$query = $qb->update(Utils::table('vote'))
->set('choices', $sql)
->where($qb->expr()->eq('poll_id', $qb->createNamedParameter($poll_id)))
;
return $query->execute();
}
/**
@ -113,7 +129,7 @@ class VoteRepository extends AbstractRepository {
*
* @param int $poll_id ID of the poll
* @param string $name Name of the vote
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return bool true if vote already exists
*/
public function existsByPollIdAndName($poll_id, $name) {
@ -128,7 +144,7 @@ class VoteRepository extends AbstractRepository {
* @param int $poll_id ID of the poll
* @param string $name Name of the vote
* @param int $vote_id ID of the current vote
* @throws \Doctrine\DBAL\DBALException
* @throws DBALException
* @return bool true if vote already exists
*/
public function existsByPollIdAndNameAndVoteId($poll_id, $name, $vote_id) {
@ -136,5 +152,37 @@ class VoteRepository extends AbstractRepository {
$prepared->execute([$poll_id, $name, $vote_id]);
return $prepared->rowCount() > 0;
}
/**
* @param QueryBuilder $qb
* @param $insert_position
* @param bool $delete
* @return string
*/
private function generateSQLForInsertDefaultSQLite($qb, $insert_position, $delete = false)
{
$position = $insert_position + ($delete ? 2 : 1);
return 'SUBSTR(choices, 1, '
. $qb->createNamedParameter($insert_position)
. ') || " " || SUBSTR(choices, '
. $qb->createNamedParameter($position)
. ')';
}
/**
* @param QueryBuilder $qb
* @param int $insert_position
* @param bool $delete
* @return string
*/
private function generateSQLForInsertDefault($qb, $insert_position, $delete = false)
{
$position = $insert_position + ($delete ? 2 : 1);
return 'CONCAT(SUBSTR(choices, 1, '
. $qb->createNamedParameter($insert_position)
. '), " ", SUBSTR(choices, '
. $qb->createNamedParameter($position)
. '))';
}
}

View File

@ -1,6 +1,5 @@
<?php
namespace Framadate\Security;
/**

View File

@ -2,9 +2,12 @@
namespace Framadate\Services;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ConnectionException;
use Doctrine\DBAL\DBALException;
use Framadate\Exception\MomentAlreadyExistsException;
use Framadate\Form;
use Framadate\Repositories\RepositoryFactory;
use stdClass;
/**
* Class AdminPollService
@ -31,15 +34,15 @@ class AdminPollService {
$this->commentRepository = RepositoryFactory::commentRepository();
}
function updatePoll($poll) {
global $config;
$end_date = strtotime($poll->end_date);
if ($end_date < strtotime($poll->creation_date)) {
/**
* @param Form $poll
* @return bool
*/
public function updatePoll($poll) {
if ($poll->end_date < $poll->creation_date) {
$poll->end_date = $poll->creation_date;
} elseif ($end_date > $this->pollService->maxExpiryDate()) {
$poll->end_date = utf8_encode(strftime('%Y-%m-%d', $this->pollService->maxExpiryDate()));
} elseif ($poll->end_date > $this->pollService->maxExpiryDate()) {
$poll->end_date = $this->pollService->maxExpiryDate();
}
return $this->pollRepository->update($poll);
@ -216,7 +219,7 @@ class AdminPollService {
* @param $datetime int The datetime
* @param $new_moment string The moment's name
* @throws MomentAlreadyExistsException When the moment to add already exists in database
* @throws \Doctrine\DBAL\ConnectionException
* @throws ConnectionException
*/
public function addDateSlot($poll_id, $datetime, $new_moment) {
$this->logService->logEntries(
@ -242,7 +245,7 @@ class AdminPollService {
// Check if moment already exists (maybe not necessary)
if (in_array($new_moment, $moments, true)) {
throw new MomentAlreadyExistsException();
throw new MomentAlreadyExistsException($slot, $new_moment);
}
// Update found slot
@ -257,6 +260,7 @@ class AdminPollService {
// Commit transaction
$this->connect->commit();
} catch (DBALException $e) {
$this->logService->log('ERROR', "Database error, couldn't insert date slot" . $e->getMessage());
$this->connect->rollBack();
}
}
@ -270,7 +274,7 @@ class AdminPollService {
* @param $poll_id int The ID of the poll
* @param $title int The title
* @throws MomentAlreadyExistsException When the moment to add already exists in database
* @throws \Doctrine\DBAL\ConnectionException
* @throws ConnectionException
* @throws \Doctrine\DBAL\DBALException
*/
public function addClassicSlot($poll_id, $title) {
@ -309,10 +313,10 @@ class AdminPollService {
*
* @param $slots array All the slots of the poll
* @param $datetime int The datetime of the new slot
* @return \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 = new stdClass();
$result->slot = null;
$result->insert = 0;
@ -329,11 +333,13 @@ class AdminPollService {
$result->insert += count($moments);
$result->slot = $slot;
break;
} elseif ($datetime < $rowDatetime) {
}
if ($datetime < $rowDatetime) {
// We have to insert before this slot
break;
}
$result->insert += count($moments);
$result->insert += count($moments);
}
return $result;

View File

@ -20,6 +20,7 @@ namespace Framadate\Services;
use DateTime;
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\RFCValidation;
use InvalidArgumentException;
/**
* This class helps to clean all inputs from the users or external services.
@ -124,9 +125,16 @@ class InputService {
return $this->returnIfNotBlank($comment);
}
/**
* @param string $date
* @return DateTime
*/
public function filterDate($date) {
$dDate = DateTime::createFromFormat(__('Date', 'Y-m-d'), $date)->setTime(0, 0, 0);
return $dDate->format('Y-m-d H:i:s');
$dDate = parse_translation_date($date);
if ($dDate) {
return $dDate;
}
throw new InvalidArgumentException('Invalid date');
}
/**

View File

@ -1,6 +1,5 @@
<?php
namespace Framadate\Services;
use \stdClass;

View File

@ -17,32 +17,43 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Services;
use DateInterval;
use DateTime;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ConnectionException;
use Doctrine\DBAL\DBALException;
use Exception;
use Framadate\Exception\AlreadyExistsException;
use Framadate\Exception\ConcurrentEditionException;
use Framadate\Exception\ConcurrentVoteException;
use Framadate\Form;
use Framadate\Repositories\RepositoryFactory;
use Framadate\Security\Token;
use Framadate\Services\LogService;
use Framadate\Services\NotificationService;
use Framadate\Services\PurgeService;
use Framadate\Services\SessionService;
use Framadate\Utils;
use RuntimeException;
use stdClass;
class PollService {
private $connect;
private $logService;
private $pollRepository;
private $slotRepository;
private $voteRepository;
private $commentRepository;
/**
* @var NotificationService
*/
private $notificationService;
/**
* @var SessionService
*/
private $sessionService;
/**
* @var PurgeService
*/
private $purgeService;
function __construct(Connection $connect, LogService $logService, NotificationService $notificationService) {
$this->connect = $connect;
public function __construct(Connection $connect, LogService $logService, NotificationService $notificationService) {
$this->logService = $logService;
$this->notificationService = $notificationService;
$this->sessionService = new SessionService();
@ -57,7 +68,7 @@ class PollService {
* Find a poll from its ID.
*
* @param $poll_id int The ID of the poll
* @return \stdClass|null The found poll, or null
* @return stdClass|null The found poll, or null
*/
function findById($poll_id) {
try {
@ -142,7 +153,7 @@ class PollService {
* @throws AlreadyExistsException
* @throws ConcurrentEditionException
* @throws ConcurrentVoteException
* @return \stdClass
* @return stdClass
*/
function addVote($poll_id, $name, $choices, $slots_hash, $mail) {
$this->checkVoteConstraints($choices, $poll_id, $slots_hash, $name);
@ -218,9 +229,10 @@ class PollService {
/**
* @param Form $form
* @throws ConnectionException
* @return array
*/
function createPoll(Form $form) {
public function createPoll(Form $form) {
// Generate poll IDs, loop while poll ID already exists
$this->pollRepository->beginTransaction();
@ -230,7 +242,7 @@ class PollService {
$poll_id = $this->random(16);
} while ($this->pollRepository->existsById($poll_id));
$admin_poll_id = $poll_id . $this->random(8);
} else { // User have choosen the poll id
} else { // User have chosen the poll id
$poll_id = $form->id;
do {
$admin_poll_id = $this->random(24);
@ -261,7 +273,7 @@ class PollService {
/**
* @param array $votes
* @param \stdClass $poll
* @param stdClass $poll
* @return array
*/
public function computeBestChoices($votes, $poll) {
@ -293,8 +305,8 @@ class PollService {
function splitSlots($slots) {
$splitted = [];
foreach ($slots as $slot) {
$obj = new \stdClass();
$obj->day = $slot->title;
$obj = new stdClass();
$obj->day = (new DateTime())->setTimestamp((int) $slot->title);
$obj->moments = explode(',', $slot->moments);
$splitted[] = $obj;
@ -316,7 +328,7 @@ class PollService {
function splitVotes($votes) {
$splitted = [];
foreach ($votes as $vote) {
$obj = new \stdClass();
$obj = new stdClass();
$obj->id = $vote->id;
$obj->name = $vote->name;
$obj->uniqId = $vote->uniqId;
@ -330,18 +342,25 @@ class PollService {
}
/**
* @return int The max timestamp allowed for expiry date
* @throws Exception
* @return DateTime The max timestamp allowed for expiry date
*/
public function maxExpiryDate() {
global $config;
return time() + (86400 * $config['default_poll_duration']);
try {
global $config;
$default_poll_duration = isset($config['default_poll_duration']) ? $config['default_poll_duration'] : 60;
return (new DateTime())->add(new DateInterval('P' . $default_poll_duration . 'D'));
} catch (Exception $e) {
throw new RuntimeException('Configuration Exception');
}
}
/**
* @return int The min timestamp allowed for expiry date
* @throws Exception
* @return DateTime The min timestamp allowed for expiry date
*/
public function minExpiryDate() {
return time() + 86400;
return (new DateTime())->add(new DateInterval('P1D'));
}
/**
@ -355,7 +374,7 @@ class PollService {
}
/**
* @param \stdClass $poll
* @param stdClass $poll
* @return array
*/
private function computeEmptyBestChoices($poll)
@ -440,7 +459,7 @@ class PollService {
* This method checks if the votes doesn't conflicts the maximum votes constraint
*
* @param $user_choice
* @param \stdClass $poll
* @param stdClass $poll
* @param string $poll_id
* @throws ConcurrentVoteException
*/

View File

@ -1,6 +1,5 @@
<?php
namespace Framadate\Services;
class SessionService {

View File

@ -17,9 +17,16 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
const DATE_FORMAT_FULL = 'EEEE d MMMM y';
const DATE_FORMAT_SHORT = 'EEEE d MMMM y';
const DATE_FORMAT_DAY = 'E d';
const DATE_FORMAT_DATE = 'dd-MM-y';
const DATE_FORMAT_MONTH_YEAR = 'MMMM y';
const DATE_FORMAT_DATETIME_SHORT = 'EEEE d';
// Change session language when requested
if (isset($_REQUEST['lang'])
&& in_array($_REQUEST['lang'], array_keys($ALLOWED_LANGUAGES), true)) {
&& array_key_exists($_REQUEST['lang'], $ALLOWED_LANGUAGES)) {
$_SESSION['lang'] = $_REQUEST['lang'];
}
@ -32,6 +39,79 @@ if (isset($_SESSION['lang'])) {
// Use the best available locale.
$locale = locale_lookup(array_keys($ALLOWED_LANGUAGES), $wanted_locale, false, DEFAULT_LANGUAGE);
/**
* Formats a DateTime according to the IntlDateFormatter
*
* @param DateTime $date
* @param string $pattern
* @param $forceLocale
* @return string
*/
function date_format_intl(DateTime $date, $pattern = DATE_FORMAT_FULL, $forceLocale = null) {
global $locale;
$local_locale = $forceLocale || $locale;
$dateFormatter = IntlDateFormatter::create(
$local_locale,
IntlDateFormatter::FULL,
IntlDateFormatter::FULL,
date_default_timezone_get(),
IntlDateFormatter::GREGORIAN,
$pattern
);
return $dateFormatter->format($date);
}
/**
* Formats a DateTime according to a translated format
*
* @param DateTime $date
* @param string $pattern
* @return string
*/
function date_format_translation(DateTime $date, $pattern = 'Y-m-d') {
return $date->format(__('Date', $pattern));
}
/**
* Converts a string into a DateTime according to the IntlDateFormatter
*
* @param $dateString
* @param string $pattern
* @param string|null $forceLocale
* @return DateTime|null
*/
function parse_intl_date($dateString, $pattern = DATE_FORMAT_DATE, $forceLocale = null) {
global $locale;
$local_locale = $forceLocale || $locale;
$dateFormatter = IntlDateFormatter::create(
$local_locale,
IntlDateFormatter::FULL,
IntlDateFormatter::FULL,
date_default_timezone_get(),
IntlDateFormatter::GREGORIAN,
$pattern
);
$timestamp = $dateFormatter->parse($dateString);
try {
return (new DateTime())->setTimestamp($timestamp);
} catch (Exception $e) {
return null;
}
}
/**
* Converts a string into a DateTime according to a translated format
*
* @param string $dateString
* @param string $pattern
* @return DateTime
*/
function parse_translation_date($dateString, $pattern = 'Y-m-d') {
return DateTime::createFromFormat(__('Date', $pattern), $dateString);
}
/* i18n helper functions */
use Symfony\Component\Translation\Loader\PoFileLoader;
use Symfony\Component\Translation\Translator;
@ -39,7 +119,7 @@ use Symfony\Component\Translation\Translator;
class __i18n {
private static $translator;
private static $fallbacktranslator;
public static function init($locale) {
self::$translator = new Translator($locale);
self::$translator->addLoader('pofile', new PoFileLoader());
@ -51,7 +131,7 @@ class __i18n {
self::$fallbacktranslator->addLoader('pofile', new PoFileLoader());
self::$fallbacktranslator->addResource('pofile', ROOT_DIR . "po/" . DEFAULT_LANGUAGE . ".po", DEFAULT_LANGUAGE);
}
public static function translate($key) {
return self::$translator->trans($key)
?: self::$fallbacktranslator->trans($key);
@ -68,16 +148,3 @@ function __f($section, $key, $args) {
$args = array_slice(func_get_args(), 2);
return vsprintf($msg, $args);
}
/* Date Format */
$date_format['txt_full'] = __('Date', '%A, %B %e, %Y'); //summary in create_date_poll.php and removal date in choix_(date|autre).php
$date_format['txt_short'] = __('Date', '%A %e %B %Y'); // radio title
$date_format['txt_day'] = __('Date', '%a %e');
$date_format['txt_date'] = __('Date', '%Y-%m-%d');
$date_format['txt_month_year'] = __('Date', '%B %Y');
$date_format['txt_datetime_short'] = __('Date', '%m/%d/%Y %H:%M');
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { //%e can't be used on Windows platform, use %#d instead
foreach ($date_format as $k => $v) {
$date_format[$k] = preg_replace('#(?<!%)((?:%%)*)%e#', '\1%#d', $v); //replace %e by %#d for windows
}
}

View File

@ -66,7 +66,7 @@ if (is_file(CONF_FILENAME)) {
];
if (DB_DRIVER === 'pdo_sqlite') {
$connectionParams['path'] = 'test_database.sqlite';
$connectionParams['path'] = ROOT_DIR . '/test_database.sqlite';
}
try {

View File

@ -37,7 +37,6 @@ $smarty->assign('use_nav_js', strstr($serverName, 'framadate.org'));
$smarty->assign('provide_fork_awesome', !isset($config['provide_fork_awesome']) || $config['provide_fork_awesome']);
$smarty->assign('locale', $locale);
$smarty->assign('langs', $ALLOWED_LANGUAGES);
$smarty->assign('date_format', $date_format);
if (isset($config['tracking_code'])) {
$smarty->assign('tracking_code', $config['tracking_code']);
}
@ -46,7 +45,8 @@ if (defined('FAVICON')) {
}
// Dev Mode
if (isset($_SERVER['FRAMADATE_DEVMODE']) && $_SERVER['FRAMADATE_DEVMODE'] || php_sapi_name() === 'cli-server') {
if (php_sapi_name() === 'cli-server') {
$smarty->caching = 0;
$smarty->force_compile = true;
$smarty->compile_check = true;
} else {
@ -125,3 +125,38 @@ function smarty_modifier_locale_2_lang($locale) {
function path_for_datepicker_locale($lang) {
return __DIR__ . '/../../js/locales/bootstrap-datepicker.' . $lang . '.js';
}
/**
* @param $date
* @param string $pattern
* @return string
*/
function smarty_modifier_date_format_intl(DateTime $date, $pattern) {
return date_format_intl($date, $pattern);
}
/**
* @param DateTime $date
* @return int
*/
function smarty_modifier_date_to_timestamp(DateTime $date) {
return $date->getTimestamp();
}
/**
* @param integer $timestamp
* @throws Exception
* @return DateTime
*/
function smarty_modifier_timestamp_to_date($timestamp) {
return (new DateTime())->setTimestamp((int) $timestamp);
}
/**
* @param DateTime $date
* @param string $pattern
* @return bool|DateTime
*/
function smarty_modifier_date_format_translation(DateTime $date, $pattern = 'Y-m-d') {
return date_format_translation($date, $pattern);
}

View File

@ -56,10 +56,10 @@
"require": {
"php": ">=5.6.0",
"ext-pdo": "*",
"ext-intl": "*",
"smarty/smarty": "^3.1",
"phpmailer/phpmailer": "~6.0",
"ircmaxell/password-compat": "dev-master",
"roave/security-advisories": "dev-master",
"erusev/parsedown": "^1.7",
"egulias/email-validator": "~2.1",
"doctrine/dbal": "^2.5",
@ -70,7 +70,8 @@
"require-dev": {
"phpunit/phpunit": "^5.7",
"friendsofphp/php-cs-fixer": "~2.0"
"friendsofphp/php-cs-fixer": "~2.0",
"roave/security-advisories": "dev-master"
},
"autoload": {

View File

@ -41,7 +41,7 @@ $sessionService = new SessionService();
if (is_file('bandeaux_local.php')) {
include_once('bandeaux_local.php');
} else {
include_once('bandeaux.php');
include_once 'bandeaux.php';
}
$max_expiry_time = $pollService->maxExpiryDate();
@ -142,7 +142,7 @@ switch ($step) {
}
$summary .= '</ol>';
$end_date_str = utf8_encode(strftime($date_format['txt_date'], $max_expiry_time)); //textual date
$end_date_str = date_format_intl($max_expiry_time); //textual date
$_SESSION['form'] = serialize($form);

View File

@ -166,7 +166,8 @@ switch ($step) {
$choices = $form->getChoices();
foreach ($choices as $choice) {
/** @var Choice $choice */
$summary .= '<li>' . strftime($date_format['txt_full'], $choice->getName());
$date = (new DateTime())->setTimestamp((int) $choice->getName());
$summary .= '<li>' . $end_date_str = date_format_intl($date); //textual date
$first = true;
foreach ($choice->getSlots() as $slots) {
$summary .= $first ? ': ' : ', ';
@ -177,7 +178,7 @@ switch ($step) {
}
$summary .= '</ul>';
$end_date_str = utf8_encode(strftime($date_format['txt_date'], $max_expiry_time)); // textual date
$end_date_str = date_format_intl($max_expiry_time); //textual date
$_SESSION['form'] = serialize($form);

View File

@ -80,7 +80,7 @@ if ($poll->format === 'D') {
$titles_line = ',';
$moments_line = ',';
foreach ($slots as $slot) {
$title = Utils::csvEscape(strftime($date_format['txt_date'], $slot->title));
$title = Utils::csvEscape($dateFormatter->format($slot->title));
$moments = explode(',', $slot->moments);
$titles_line .= str_repeat($title . ',', count($moments));

View File

@ -218,7 +218,7 @@ function checkCommentSending() {
button.data("textSend", button.text());
}
if (!form.get(0).checkValidity()) {
if (form.get(0) && !form.get(0).checkValidity()) {
button.prop("disabled", true);
button.text(button.data("textWait"));
} else {

View File

@ -232,12 +232,15 @@ if ($resultPubliclyVisible || $accessGranted) {
$comments = $pollService->allCommentsByPollId($poll_id);
}
$deletion_date = clone $poll->end_date;
$deletion_date->add(new DateInterval('P' . PURGE_DELAY . 'D'));
// Assign data to template
$smarty->assign('poll_id', $poll_id);
$smarty->assign('poll', $poll);
$smarty->assign('title', __('Generic', 'Poll') . ' - ' . $poll->title);
$smarty->assign('expired', strtotime($poll->end_date) < time());
$smarty->assign('deletion_date', strtotime($poll->end_date) + PURGE_DELAY * 86400);
$smarty->assign('expired', $poll->end_date < new DateTime());
$smarty->assign('deletion_date', $deletion_date);
$smarty->assign('slots', $poll->format === 'D' ? $pollService->splitSlots($slots) : $slots);
$smarty->assign('slots_hash', $pollService->hashSlots($slots));
$smarty->assign('votes', $pollService->splitVotes($votes));

View File

@ -93,10 +93,10 @@
<td>{$poll->admin_name|html}</td>
<td>{$poll->admin_mail|html}</td>
{if strtotime($poll->end_date) > time()}
<td>{date('d/m/y', strtotime($poll->end_date))}</td>
{if $poll->end_date > date_create()}
<td>{$poll->end_date|date_format_intl:'d/m/Y'}</td>
{else}
<td><span class="text-danger">{strtotime($poll->end_date)|date_format:'d/m/Y'}</span></td>
<td><span class="text-danger">{$poll->end_date|date_format_intl:'d/m/Y'}</span></td>
{/if}
<td>{$poll->votes|html}</td>
<td>{$poll->id|html}</td>

View File

@ -39,7 +39,7 @@
<div id="days_container">
{foreach $choices as $i=>$choice}
{if $choice->getName()}
{$day_value = $choice->getName()|date_format:$date_format['txt_date']}
{$day_value = $choice->getName()|timestamp_to_date|date_format_translation}
{else}
{$day_value = ''}
{/if}

View File

@ -3,7 +3,7 @@
{foreach $polls as $poll}
<li>
<a href="{poll_url id=$poll->admin_id admin=true}">{$poll->title|html}</a>
({__('Generic', 'Creation date:')} {$poll->creation_date|date_format:$date_format['txt_full']})
({__('Generic', 'Creation date:')} {$poll->creation_date|date_format_intl:DATE_FORMAT_FULL})
</li>
{/foreach}
</ul>

View File

@ -7,7 +7,9 @@
{if $admin && !$expired}
<button type="submit" name="delete_comment" value="{$comment->id|html}" class="btn btn-link" title="{__('Comments', 'Remove comment')}"><span class="glyphicon glyphicon-remove text-danger"></span><span class="sr-only">{__('Generic', 'Remove')}</span></button>
{/if}
<span class="comment_date">{$comment->date|date_format:$date_format['txt_datetime_short']}</span>
{* <span>{$comment->date}</span>*}
{* <span>{$comment->date|date_format_intl}</span>*}
<span class="comment_date">{$comment->date|date_format_intl:DATE_FORMAT_SHORT}</span>
<b>{$comment->name|html}</b>&nbsp;
<span>{$comment->comment|escape|nl2br}</span>
</div>

View File

@ -116,15 +116,37 @@
</div>
<div id="expiration-form" class="form-group col-md-4">
<label class="control-label">{__('Generic', 'Expiry date')}</label>
<p>{$poll->end_date|date_format:$date_format['txt_date']|html} <button class="btn btn-link btn-sm btn-edit" title="{__('PollInfo', 'Edit the expiry date')}"><span class="glyphicon glyphicon-pencil"></span><span class="sr-only">{__('Generic', 'Edit')}</span></button></p>
<p>{$poll->end_date|date_format_intl:DATE_FORMAT_FULL|html}
<button class="btn btn-link btn-sm btn-edit" title="{__('PollInfo', 'Edit the expiry date')}">
<span class="glyphicon glyphicon-pencil"></span><span class="sr-only">{__('Generic', 'Edit')}</span>
</button>
</p>
<div class="hidden js-expiration">
<label class="sr-only" for="newexpirationdate">{__('Generic', 'Expiry date')}</label>
<div class="input-group">
<input type="text" class="form-control" id="newexpirationdate" name="expiration_date" size="40" value="{$poll->end_date|date_format:$date_format['txt_date']|html}" />
<input type="text"
class="form-control"
id="newexpirationdate"
name="expiration_date"
size="40"
value="{$poll->end_date|date_format_translation|html}"
/>
<span class="input-group-btn">
<button type="submit" class="btn btn-success" name="update_poll_info" value="expiration_date" title="{__('PollInfo', 'Save the new expiration date')}"><span class="glyphicon glyphicon-ok"></span><span class="sr-only">{__('Generic', 'Save')}</span></button>
<button class="btn btn-link btn-cancel" title="{__('PollInfo', 'Cancel the expiration date edit')}"><span class="glyphicon glyphicon-remove"></span><span class="sr-only">{__('Generic', 'Cancel')}</span></button>
<button type="submit"
class="btn btn-success"
name="update_poll_info"
value="expiration_date"
title="{__('PollInfo', 'Save the new expiration date')}"
>
<span class="glyphicon glyphicon-ok"></span>
<span class="sr-only">{__('Generic', 'Save')}</span>
</button>
<button class="btn btn-link btn-cancel"
title="{__('PollInfo', 'Cancel the expiration date edit')}">
<span class="glyphicon glyphicon-remove"></span>
<span class="sr-only">{__('Generic', 'Cancel')}</span>
</button>
</span>
</div>
</div>

View File

@ -26,16 +26,16 @@
{foreach $slots as $slot}
{foreach $slot->moments as $id=>$moment}
<td headers="M{$slot@key} D{$headersDCount} H{$headersDCount}">
<a href="{poll_url id=$admin_poll_id admin=true action='delete_column' action_value=$slot->day|cat:'@'|cat:$moment}"
<a href="{poll_url id=$admin_poll_id admin=true action='delete_column' action_value=($slot->day|date_to_timestamp)|cat:'@'|cat:$moment}"
data-remove-confirmation="{__('adminstuds', 'Confirm removal of the column.')}"
class="btn btn-link btn-sm remove-column"
title="{__('adminstuds', 'Remove column')} {$slot->day|date_format:$date_format.txt_short|html} - {$moment|html}">
title="{__('adminstuds', 'Remove column')} {$slot->day|date_format_intl:DATE_FORMAT_SHORT|html} - {$moment|html}">
<i class="glyphicon glyphicon-remove text-danger"></i><span class="sr-only">{__('Generic', 'Remove')}</span>
</a>
{if $poll->collect_users_mail != constant("Framadate\CollectMail::NO_COLLECT")}
<a href="{poll_url id=$admin_poll_id admin=true action='collect_mail' action_value=($headersDCount)}"
class="btn btn-link btn-sm collect-mail"
title="{__('adminstuds', 'Collect the emails of the polled users for the choice')} {$slot->day|date_format:$date_format.txt_short|html} - {$moment|html}">
title="{__('adminstuds', 'Collect the emails of the polled users for the choice')} {$slot->day|date_format_intl:DATE_FORMAT_SHORT|html} - {$moment|html}">
<i class="glyphicon glyphicon-envelope"></i><span class="sr-only">{__('Generic', 'Collect emails')}</span>
</a>
{/if}
@ -56,7 +56,7 @@
{$count_same = 0}
{$previous = 0}
{foreach $slots as $id=>$slot}
{$display = $slot->day|date_format:$date_format.txt_month_year|html}
{$display = $slot->day|date_format_intl:DATE_FORMAT_MONTH_YEAR|html}
{if $previous !== 0 && $previous != $display}
<th colspan="{$count_same}" class="bg-primary month" id="M{$id}">{$previous}</th>
{$count_same = 0}
@ -79,7 +79,7 @@
<tr>
<th role="presentation"></th>
{foreach $slots as $id=>$slot}
<th colspan="{$slot->moments|count}" class="bg-primary day" id="D{$id}">{$slot->day|date_format:$date_format.txt_day|html}</th>
<th colspan="{$slot->moments|count}" class="bg-primary day" id="D{$id}">{$slot->day|date_format_intl:DATE_FORMAT_DAY|html}</th>
{for $foo=0 to ($slot->moments|count)-1}
{append var='headersD' value=$id}
{/for}
@ -95,7 +95,7 @@
<th colspan="1" class="bg-info" id="H{$headersDCount}">{$moment|html}</th>
{append var='headersH' value=$headersDCount}
{$headersDCount = $headersDCount+1}
{$slots_raw[] = $slot->day|date_format:$date_format.txt_full|cat:' - '|cat:$moment}
{$slots_raw[] = $slot->day|date_format_intl:DATE_FORMAT_FULL|cat:' - '|cat:$moment}
{/foreach}
{/foreach}
<th></th>
@ -258,12 +258,12 @@
<td class="bg-info" headers="M{$headersM[$i]} D{$headersD[$i]} H{$headersH[$i]}">
<ul class="list-unstyled choice">
{if $poll->valuemax eq NULL || $best_choices['y'][$i] lt $poll->valuemax}
{if $poll->ValueMax eq NULL || $best_choices['y'][$i] lt $poll->ValueMax}
<li class="yes">
<input type="radio" id="y-choice-{$i}" name="choices[{$i}]" value="2"
{(!isset($selectedNewVotes[$i]) || ("2" !== $selectedNewVotes[$i])) ? "" : " checked"}
/>
<label class="btn btn-default btn-xs" for="y-choice-{$i}" title="{__('Poll results', 'Vote "yes" for')|html} {$slot->day|date_format:$date_format.txt_short|html} - {$moment|html}">
<label class="btn btn-default btn-xs" for="y-choice-{$i}" title="{__('Poll results', 'Vote "yes" for')|html} {$slot->day|date_format_intl:DATE_FORMAT_SHORT|html} - {$moment|html}">
<i class="glyphicon glyphicon-ok"></i><span class="sr-only">{__('Generic', 'Yes')}</span>
</label>
</li>
@ -271,7 +271,7 @@
<input type="radio" id="i-choice-{$i}" name="choices[{$i}]" value="1"
{(!isset($selectedNewVotes[$i]) || ("1" !== $selectedNewVotes[$i])) ? "" : " checked"}
/>
<label class="btn btn-default btn-xs" for="i-choice-{$i}" title="{__('Poll results', 'Votes under reserve for')|html} {$slot->day|date_format:$date_format.txt_short|html} - {$moment|html}">
<label class="btn btn-default btn-xs" for="i-choice-{$i}" title="{__('Poll results', 'Votes under reserve for')|html} {$slot->day|date_format_intl:DATE_FORMAT_SHORT|html} - {$moment|html}">
(<i class="glyphicon glyphicon-ok"></i>)<span class="sr-only">{__('Generic', 'Under reserve')}</span>
</label>
</li>
@ -281,7 +281,7 @@
<input type="radio" id="n-choice-{$i}" name="choices[{$i}]" value="0"
{(!isset($selectedNewVotes[$i]) || ("0" !== $selectedNewVotes[$i])) ? "" : " checked"}
/>
<label class="btn btn-default btn-xs {(!isset($selectedNewVotes[$i]) || ("0" !== $selectedNewVotes[$i])) ? "startunchecked" : ""}" for="n-choice-{$i}" title="{__('Poll results', 'Vote "no" for')|html} {$slot->day|date_format:$date_format.txt_short|html} - {$moment|html}">
<label class="btn btn-default btn-xs {(!isset($selectedNewVotes[$i]) || ("0" !== $selectedNewVotes[$i])) ? "startunchecked" : ""}" for="n-choice-{$i}" title="{__('Poll results', 'Vote "no" for')|html} {$slot->day|date_format_intl:DATE_FORMAT_SHORT|html} - {$moment|html}">
<i class="glyphicon glyphicon-ban-circle"></i><span class="sr-only">{__('Generic', 'No')}</span>
</label>
</li>
@ -364,7 +364,7 @@
var cols = [
{foreach $slots as $slot}
{foreach $slot->moments as $moment}
$('<div/>').html('{$slot->day|date_format:$date_format.txt_short|html} - {$moment|html}').text(),
$('<div/>').html('{$slot->day|date_format_intl:DATE_FORMAT_SHORT|html} - {$moment|html}').text(),
{/foreach}
{/foreach}
];
@ -424,7 +424,7 @@
{foreach $slots as $slot}
{foreach $slot->moments as $moment}
{if $best_choices['y'][$i] == $max}
<li><strong>{$slot->day|date_format:$date_format.txt_full|html} - {$moment|html}</strong></li>
<li><strong>{$slot->day|date_format_intl:DATE_FORMAT_SHORT|html} - {$moment|html}</strong></li>
{/if}
{$i = $i+1}
{/foreach}

View File

@ -34,7 +34,7 @@
{if $expired}
<div class="alert alert-danger">
<p>{__('studs', 'The poll has expired, it will soon be deleted.')}</p>
<p>{__('studs', 'Deletion date:')} {$deletion_date|date_format:$date_format['txt_short']|html}</p>
<p>{__('studs', 'Deletion date:')} {$deletion_date|date_format_intl:DATE_FORMAT_SHORT|html}</p>
</div>
{else}
{if $admin}