Compare commits

..

No commits in common. "chapril-1.1.19" and "chapril-1.1.10" have entirely different histories.

130 changed files with 12815 additions and 6398 deletions

5
.gitignore vendored
View File

@ -10,7 +10,7 @@ vendor
cache/
tpl_c/*
!tpl_c/.gitkeep
.php-cs-fixer.cache
.php_cs.cache
.zanata-cache/
# Temp files
@ -25,6 +25,3 @@ Thumbs.db
.project
.idea/
*.iml
#ics temp file
out.ics

View File

@ -1,7 +1,9 @@
image: framasoft/framadate-ci:7.3-pdo_mysql
image: framasoft/framadate-ci
stages:
- test
- deploy
- beta
- funky
# Run php-cs-fixer and phpunit on all branches
test:
@ -10,18 +12,24 @@ test:
- composer install -o --no-interaction --no-progress --prefer-dist
- php vendor/bin/php-cs-fixer fix --verbose --dry-run
- vendor/bin/phpunit --bootstrap app/tests/bootstrap.php --debug app/tests
image: framasoft/framadate-ci:${PHP_VERSION}-pdo_mysql
parallel:
matrix:
- PHP_VERSION:
- "7.3"
- "7.4"
- "8.0"
- "8.1"
cache:
paths:
- vendor/
check-trad:
stage: test
allow_failure: true
script:
- if [ -z ${ZANATA_CONFIG_FRAMABOT+x} ]; then echo "*** Unable to check if translations need to be pulled, exiting ***"; exit 1; fi
- export ORIG=$(git diff-files --shortstat)
- if [ ! -z ${ZANATA_CONFIG_FRAMABOT+x} ]; then mkdir -p ${HOME}/.config; echo -e "${ZANATA_CONFIG_FRAMABOT}" > ${HOME}/.config/zanata.ini; fi
- if [ ! -z ${ZANATA_CONFIG_FRAMABOT+x} ]; then make push-locales; fi
- git status > /dev/null 2>&1
- export CHANGES=$(git diff-files --shortstat)
- if [[ $CHANGES != $ORIG ]]; then echo "*** There is changes in locales ***"; echo "*** You need to do `make pull-locales` in your repo ***"; exit 1; fi
only:
- develop
# Create artifacts on master
pages:
stage: deploy
@ -32,7 +40,6 @@ pages:
- composer dump-autoload --optimize --no-dev --classmap-authoritative
- mkdir framadate
- mv `ls -A | grep -v framadate` ./framadate
- echo $latesttag > framadate/VERSION
- find framadate/ -type d -exec chmod 750 {} \;
- find framadate/ -type f -exec chmod 640 {} \;
- rm -rf framadate/.git
@ -60,3 +67,54 @@ pages:
- tags
except:
- (beta|alpha)
# Deploy on develop
beta:
stage: beta
script:
- git checkout develop
- composer install -o --no-interaction --no-progress --prefer-dist --no-dev
- composer dump-autoload --optimize --no-dev --classmap-authoritative
- if [ ! -z ${ZANATA_CONFIG_FRAMABOT+x} ]; then mkdir -p ${HOME}/.config; echo -e "${ZANATA_CONFIG_FRAMABOT}" > ${HOME}/.config/zanata.ini; fi
- if [ ! -z ${ZANATA_CONFIG_FRAMABOT+x} ]; then make pull-locales; fi
- mkdir .public
- cp -r * .public
- cp -r .git .public
- mv .public public
- mkdir "${HOME}/.ssh"
- chmod 700 "${HOME}/.ssh"
- if [ ! -z ${DEPLOYEMENT_KNOWN_HOSTS+x} ]; then echo -e "${DEPLOYEMENT_KNOWN_HOSTS}" > ${HOME}/.ssh/known_hosts; fi
- eval `ssh-agent -s`
- if [ ! -z ${BETA_KEY+x} ]; then ssh-add <(echo "${BETA_KEY}" | base64 --decode -i); fi
- if [ ! -z ${BETA_KEY+x} ]; then rsync -a --delete --exclude admin/.stdout.log --exclude admin/.htpasswd --exclude app/inc/config.php --exclude stats/ --exclude error/ public/ ${BETA_USER}@${DEPLOYEMENT_HOST}:../../web/; fi
only:
- develop
# Deploy on funky
funky:
stage: funky
script:
- git checkout funky
- composer install
- mkdir tpl_c
- mkdir .public
- cp -r * .public
- mv .public public
- mkdir "${HOME}/.ssh"
- chmod 700 "${HOME}/.ssh"
- if [ ! -z ${DEPLOYEMENT_KNOWN_HOSTS+x} ]; then echo -e "${DEPLOYEMENT_KNOWN_HOSTS}" > ${HOME}/.ssh/known_hosts; fi
- eval `ssh-agent -s`
- if [ ! -z ${DEPLOYEMENT_KEY+x} ]; then ssh-add <(echo "${DEPLOYEMENT_KEY}" | base64 --decode -i); fi
- if [ ! -z ${DEPLOYEMENT_KEY+x} ]; then rsync -a --delete --exclude admin/.stdout.log --exclude admin/.htpasswd --exclude app/inc/config.php --exclude stats/ --exclude error/ public/ ${DEPLOYEMENT_USER}@${DEPLOYEMENT_HOST}:../../web/; fi
only:
- funky
# Push new translations strings to https://trad.framasoft.org
trads:
stage: deploy
image: framasoft/push-trad:latest
script:
- if [ ! -z ${ZANATA_CONFIG_FRAMABOT+x} ]; then mkdir -p ${HOME}/.config; echo -e "${ZANATA_CONFIG_FRAMABOT}" > ${HOME}/.config/zanata.ini; fi
- if [ ! -z ${ZANATA_CONFIG_FRAMABOT+x} ]; then make push-locales; fi
only:
- develop

View File

@ -1,6 +1,6 @@
<?php
return (new PhpCsFixer\Config())
return PhpCsFixer\Config::create()
->setRiskyAllowed(true)
->setRules([
'array_syntax' => [
@ -8,18 +8,16 @@ return (new PhpCsFixer\Config())
],
'combine_consecutive_unsets' => true,
'heredoc_to_nowdoc' => true,
'no_extra_blank_lines' => [
'tokens' => [
'break',
'continue',
'extra',
'return',
'throw',
'use',
'parenthesis_brace_block',
'square_brace_block',
'curly_brace_block'
]
'no_extra_consecutive_blank_lines' => [
'break',
'continue',
'extra',
'return',
'throw',
'use',
'parenthesis_brace_block',
'square_brace_block',
'curly_brace_block'
],
'no_unreachable_default_argument_value' => true,
'no_useless_else' => true,
@ -40,8 +38,7 @@ return (new PhpCsFixer\Config())
->exclude([
'vendor',
'var',
'web',
'tpl_c'
'web'
])
->in(__DIR__)
)

View File

@ -1,215 +1,5 @@
# Changelog de framadate
## 1.1.19
23-12-2021
### Fixed
- Remove the X-Mailer header in e-mails, as this causes some email servers to see emails sent by Framadate as spam
## 1.1.18
20-12-2021
### Changed
- Dependency updates
- Replace abandonned SimpleMDE with EasyMDE fork
### Fixed
- Enforce the instance expiration limits when editing the poll expiration date once created, from poll admin
- Fixed some HTML markup validity
### Translations
- Fixed a missing french language key
- Enable Catalan language
## 1.1.17
18-10-2021
### Added
- Allow to export to ICS the best choices
### Changed
- Allow configuring AuthType for MailService
### Security
- Fix an XSS possibility in the result graph
## 1.1.16
22-03-2021
### Changed
- **Framadate now requires the `mbstring` PHP extension.** Make sure it's installed and activated before updating.
### Fixed
- Handle poll creator names being too long properly
## 1.1.15
22-03-2021
### Security
- Fixed cross-site scripting (XSS) attacks in poll description markdown preview. All administrators are encouraged to upgrade, especially if you have sensitive services and data on the same domain name.
This was reported by @martgil
https://framagit.org/framasoft/framadate/framadate/-/issues/546
## 1.1.14
08-03-2021
### Fixed
- Avoid error with a name too long https://framagit.org/framasoft/framadate/framadate/-/issues/530
## 1.1.13
08-03-2021
### Fixed
- Fixed error when closing a poll https://framagit.org/framasoft/framadate/framadate/-/issues/532
## 1.1.12
18-12-2020
### Changed
* Framadate now requires PHP 7.3
## 1.1.11
18-12-2020
### Fixed
- Fixed translations keys missing into emails https://framagit.org/framasoft/framadate/framadate/-/issues/463
### Translations
- Added Catalan translation
## 1.1.10
### Fixed
* Remove .git folder inside releases.
* Create releases through CI
## 1.1.9
### Fixed
- Fixes session issue https://framagit.org/framasoft/framadate/framadate/issues/255
- Fixes bug when editing column https://framagit.org/framasoft/framadate/framadate/issues/379
- Fix mail subject escaping https://framagit.org/framasoft/framadate/framadate/issues/375
## 1.1.8
### Fixed
- Stop creating `tpl_c` directory in releases and add a `.gitkeep`
- Show database connection issue details on installation panel
- Set the proper file rights on release packages
- Added `session.cookie_httponly = 1` to local php.ini file
## 1.1.7
### Fixed
- Fix issue with maximum number of participants https://framagit.org/framasoft/framadate/issues/353 (thanks to @lohmeyer for reporting it)
## 1.1.6
### Fixed
- Bump dependencies, including PHPMailer to version 6.x
- Fix an small issue with Smarty template
## 1.1.5
### Fixed
- Restrict custom poll URLs against app urls (thanks @mosterdt)
- Add a parameter to disable build-in font-awesome (thanks @mm)
- Fix an XSS security issue with time slots (thanks https://bitsoffreedom.nl for responsibly disclosing it).
## 1.1.4
### Fixed
* Add Fork-awesome, remove dependency to Font-Awesome Bootstrap CDN, add an option to disable it (https://framagit.org/framasoft/framadate/merge_requests/300 - @tcit)
## 1.1.3
### Fixed
* Fixing issue when no choice is selected introducted in https://framagit.org/framasoft/framadate/merge_requests/284 (https://framagit.org/framasoft/framadate/merge_requests/298 - @mm)
## 1.1.2
### Fixed
- Use Parsedown's Safe Mode
## 1.1.1
### Bug fixes
- Send email with correct vote address (thanks to @lohmeyer for finding it)
## 1.1.0
### Warning
**Framadate now requires PHP 5.6** to be used (it should still work under 5.4 but will not be supported anymore).
### Features
- Markdown editor for descriptions ! (@Antonin)
- Adding a maximum participants number (@SuperNach0)
- Allow setting SMTP config (Simon LEBLANC)
- Allow admins to give the vote link back to the voters (@mm, @tcit)
- Sending voters emails to remind themselves their voting url now works (@mm)
### Enhancements
- UI improvements for responsive design (@marjolaine-v)
- Better coherence for visible results and passwords (@TDavid)
- Added an edit button on the right when too many options (@SuperNach0)
- Emails with international characters are now allowed (added an unit test) (@mm)
### Translations
**New strings are available, don't hesitate to head to <https://trad.framasoft.org/zanata/project/view/framadate> to translate them into your language !**
### Fixed
- Reschedule function (https://framagit.org/framasoft/framadate/issues/203) (@TDavid)
- lang attribute must be a valid IETF language tag (@Rudloff)
- Fix datepicker js locale file path
- Fix everyone can always vote #267
- Fix MySQL error with `NO_ZERO_DATE` #224
- SimpleMDE Markdown Editor has been updated the latest version to remove console.log calls
- Fix width of `if need be` vote option and missing parenthesis
- Remove autocomplete on date fields
- Various fixes for value max error handling
- New error strings for bad formatted inputs (admin name, wrong value max option)
- Email is now a email field (better for virtual keyboards) and is html required as well as title
- Advanced settings for poll are now opened if there's error within them
- css fixes for pictures inside columns, and little space between editor and description text area (@marjolaine-v)
- released zip files now have proper chmod rights (@tcit)
- Best choices now work properly when there's no votes (@mm)
- Don't allow an existing name when updating a vote (@mm)
- Keep vote selections when there's an error on the name (@mm)
- Add a message « Your poll has been created » at the end of the poll form process (@mm)
### Documentation
- Move everything to wiki, translate everything to English
### Technical
- Continuous Integration handles the release process
- Translations with Zanata : https://trad.framasoft.org/zanata/project/view/framadate (@luc)
- Style fixes with PHP-CS
- Libraries updated
- Improved a few docs
- Use own Framadate Docker Image for CI
- https://beta.framadate.org now gets the latest translations for each deployment (@luc)
- A CI job tells if translations strings are up-to-date (@luc)
## 1.0.3
- Corrections de wording (fr / en)
## Version 1.0 (Erik - Markus - Ecmu - Julien - Imre - Luc - Pierre - Antonin - Olivier)
- Amélioration : Conserver les votes en cours lors que l'utilisateur envoie un commentaire
- Amélioration : Les mails sont envoyés en multipart pour les lecteurs ne supportant pas HTML
@ -315,7 +105,7 @@
- Fix : Bug à la création d'un sondage sans Javascript ou sans Cookies
- Fix : Erreur d'url avec les noms de domaine contenant "admin"
- Fix : Mise à jour de la doc d'installation
## Version 0.8 (juillet 2014 Pascal Chevrel - Armony Altinier - JosephK)
- Améliorations sur l'accessibilité
- Améliorations sur l'ergonomie
@ -349,7 +139,7 @@
## Changelog des 22 et 23 juin (pyg@framasoft.net)
- très nombreuses modifications CSS
- ajout de buttons.css pour des boutons plus propres
- ajout de buttons.css pour des boutons plus propres
- ajout de print.css pour une impression sans la classe "corps"
- refonte de la page d'accueil
- ajout de la framanav
@ -415,7 +205,7 @@
- Traduction de STUdS en anglais, allemand et espagnol,
- Changement de la CSS avec ajout du logo de l'Université de Strasbourg,
- Possibilité d'ajouter un commentaire pour les sondés.
Changelog version 0.4 (janvier 2009) :
- Possibilité de faire un export PDF pour envoyer la lettre de convocation à la date de réunion,
- Possibilité de rajouter des colonnes dans la partie administration de sondage,
@ -426,7 +216,7 @@
- Mise en place d'un repository Subversion pour partager les nouvelles versions de STUdS,
- Amélioration de la CSS pour un meilleur affichage,
- Modification du code source pour le rendre portable vers une autre machine.
Changelog version 0.2 (novembre 2008) :
- Lors de la création d'un sondage DATE, classement des dates par ordre croissant,
- Lors de la création d'un sondage DATE, accepter les horaires au format "8h" ou "8H",

View File

@ -4,28 +4,26 @@
![Français](https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Flag_of_France.svg/20px-Flag_of_France.svg.png) Framadate est un service en ligne permettant de planifier un rendez-vous ou prendre des décisions rapidement et simplement. Aucune inscription préalable nest nécessaire.
**Framadate is now in maintenance mode.** [Read more](https://framagit.org/framasoft/framadate/framadate/-/issues/545#note_920869)
---
# Installation
Follow the instructions on our Wiki : <https://framagit.org/framasoft/framadate/framadate/-/wikis/home>
Follow the instructions on our Wiki : <https://framagit.org/framasoft/framadate/wikis/home>
# Contribute
## Code
Follow the instructions on <https://framagit.org/framasoft/framadate/framadate/-/wikis/coding>
Follow the instructions on <https://framagit.org/framasoft/framadate/wikis/coding>
# Traductions
Follow the instructions on <https://framagit.org/framasoft/framadate/framadate/-/wikis/translating>
Follow the instructions on <https://framagit.org/framasoft/framadate/wikis/translating>
# Used libraries
* PHP [PHP 7.3](http://php.net)
* PHP [PHP 5.6](http://php.net)
* Templating [Smarty](http://www.smarty.net/),
* I18N [o80-i18n](https://framagit.org/framasoft/framadate/o80-i18nn)
* Database: MySQL or MariaDB.
* I18N [o80-i18n](https://github.com/olivierperez/o80-i18n)
* Database: PostgreSQL ou [MySQL 5.5](https://dev.mysql.com/downloads/mysql/5.5.html)
---

View File

@ -40,7 +40,7 @@ $is_admin = false;
/*----------*/
$logService = new LogService();
$pollService = new PollService($logService);
$pollService = new PollService($connect, $logService);
$inputService = new InputService();
$mailService = new MailService($config['use_smtp'], $config['smtp_options']);
$notificationService = new NotificationService($mailService);
@ -63,7 +63,7 @@ if (!empty($_POST['poll_admin'])) {
if (!$poll) {
$message = new Message('error', __('Error', 'This poll doesn\'t exist !'));
} else if (!$is_admin && !$securityService->canAccessPoll($poll)) {
} else if ($poll && !$securityService->canAccessPoll($poll) && !$is_admin) {
$message = new Message('error', __('Password', 'Wrong password'));
} else {
$name = $inputService->filterName($_POST['name']);
@ -88,10 +88,8 @@ if (!$poll) {
$smarty->error_reporting = E_ALL & ~E_NOTICE;
$smarty->assign('comments', $comments);
$smarty->assign('poll_id', $poll_id);
$smarty->assign('admin_poll_id', $admin_poll_id);
$comments_html = $smarty->fetch('part/comments_list.tpl');
$response = ['result' => $result, 'message' => $message, 'comments' => $comments_html];
echo json_encode($response, JSON_THROW_ON_ERROR);
echo json_encode($response);

View File

@ -29,7 +29,7 @@ include_once __DIR__ . '/../app/inc/init.php';
$logService = new LogService();
$sessionService = new SessionService();
$mailService = new MailService($config['use_smtp'], $config['smtp_options']);
$pollService = new PollService($logService);
$pollService = new PollService($connect, $logService);
$result = false;
$message = null;
@ -45,7 +45,7 @@ if (!empty($_POST['poll'])) {
$token = $sessionService->get("Common", SESSION_EDIT_LINK_TOKEN);
$token_form_value = empty($_POST['token']) ? null : $_POST['token'];
$editedVoteUniqueId = filter_input(INPUT_POST, 'editedVoteUniqueId', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => POLL_REGEX]]);
if ($config['use_smtp'] === false || is_null($poll) || is_null($token) || is_null($token_form_value)
if (is_null($poll) || $config['use_smtp'] === false || is_null($token) || is_null($token_form_value)
|| !$token->check($token_form_value) || is_null($editedVoteUniqueId)) {
$message = new Message('error', __('Error', 'Something is going wrong...'));
}
@ -91,4 +91,4 @@ $smarty->error_reporting = E_ALL & ~E_NOTICE;
$response = ['result' => $result, 'message' => $message];
echo json_encode($response, JSON_THROW_ON_ERROR);
echo json_encode($response);

View File

@ -20,7 +20,7 @@
use Framadate\Message;
use Framadate\Utils;
const ROOT_DIR = __DIR__ . '/../';
define('ROOT_DIR', __DIR__ . '/../');
/**
* Checking for missing vendors.
@ -46,7 +46,6 @@ $ALLOWED_LANGUAGES = [
'de' => 'Deutsch',
'it' => 'Italiano',
'br' => 'Brezhoneg',
'ca' => 'Català',
];
const DEFAULT_LANGUAGE = 'en';
require_once ROOT_DIR . 'app/inc/i18n.php';
@ -58,7 +57,7 @@ require_once ROOT_DIR . 'app/inc/i18n.php';
* @param Message $b
* @return int
*/
function compareCheckMessage(Message $a, Message $b): int
function compareCheckMessage(Message $a, Message $b)
{
$values = [
'danger' => 0,
@ -90,7 +89,7 @@ $conf_filename = $inc_directory . 'config.php';
if (version_compare(PHP_VERSION, PHP_NEEDED_VERSION) >= 0) {
$messages[] = new Message('info', __f('Check','PHP version %s is enough (needed at least PHP %s).', PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION, PHP_NEEDED_VERSION));
} else {
$messages[] = new Message('danger', __f('Check','Your PHP version (%s) is too old. This application needs at least PHP %s.', PHP_VERSION, PHP_NEEDED_VERSION));
$messages[] = new Message('danger', __f('Check','Your PHP version (%s) is too old. This application needs at least PHP %s.', phpversion(), PHP_NEEDED_VERSION));
}
// INTL extension
@ -100,13 +99,6 @@ if (extension_loaded('intl')) {
$messages[] = new Message('danger', __('Check','You need to enable the PHP Intl extension.'));
}
// mbstring extension
if (extension_loaded('mbstring')) {
$messages[] = new Message('info', __('Check','PHP mbstring extension is enabled.'));
} else {
$messages[] = new Message('danger', __('Check','You need to enable the PHP mbstring extension.'));
}
// Is template compile dir exists and writable ?
if (!file_exists(ROOT_DIR . COMPILE_DIR)) {
$messages[] = new Message('danger', __f('Check','The template compile directory (%s) doesn\'t exist in "%s". Retry the installation process.', COMPILE_DIR, realpath(ROOT_DIR)));
@ -120,7 +112,7 @@ if (!file_exists(ROOT_DIR . COMPILE_DIR)) {
if (file_exists($conf_filename)) {
$messages[] = new Message('info', __('Check','The config file exists.'));
} elseif (is_writable($inc_directory)) {
$messages[] = new Message('info', __f('Check','The config file directory (%s) is writable.', $inc_directory));
$messages[] = new Message('info', __('Check','The config file directory (%s) is writable.', $inc_directory));
} else {
$messages[] = new Message('danger', __f('Check','The config file directory (%s) is not writable and the config file (%s) does not exists.', $inc_directory, $conf_filename));
}
@ -183,11 +175,11 @@ usort($messages, 'compareCheckMessage');
<body>
<div class="container ombre">
<div class="row">
<form method="get" class="hidden-print">
<form method="get" action="" class="hidden-print">
<div class="input-group input-group-sm pull-right col-xs-12 col-sm-2">
<select name="lang" class="form-control" title="<?=__('Language selector', 'Select the language')?>" >
<?php foreach ($ALLOWED_LANGUAGES as $lang_key => $language) { ?>
<option lang="fr" <?php if (strpos($lang_key, $locale) === 0) { echo 'selected';} ?> value="<?=substr($lang_key, 0, 2)?>"><?=$language?></option>
<option lang="fr" <?php if (substr($lang_key, 0, 2)===$locale) { echo 'selected';} ?> value="<?=substr($lang_key, 0, 2)?>"><?=$language?></option>
<?php } ?>
</select>
<span class="input-group-btn">

View File

@ -29,7 +29,6 @@ if (is_file(CONF_FILENAME)) {
$error = null;
$installService = new InstallService();
$result['details'] = null;
if (!empty($_POST)) {
$installService->updateFields($_POST);

View File

@ -17,7 +17,6 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
use Framadate\FramaDB;
use Framadate\Migration\AddColumn_hidden_In_poll_For_0_9;
use Framadate\Migration\AddColumn_receiveNewComments_For_0_9;
use Framadate\Migration\AddColumn_uniqId_In_vote_For_0_9;
@ -58,7 +57,7 @@ $migrations = [
// ---------------------------------------
// Check if MIGRATION_TABLE already exists
/** @var FramaDB $connect */
/** @var \Framadate\FramaDB $connect */
$tables = $connect->allTables();
$pdo = $connect->getPDO();
$prefixedMigrationTable = Utils::table(MIGRATION_TABLE);

View File

@ -50,7 +50,7 @@ $poll_to_delete = null;
/*----------*/
$logService = new LogService();
$pollService = new PollService($logService);
$pollService = new PollService($connect, $logService);
$adminPollService = new AdminPollService($connect, $pollService, $logService);
$superAdminService = new SuperAdminService();
$securityService = new SecurityService();

View File

@ -34,14 +34,14 @@ $message = null;
/*----------*/
$logService = new LogService();
$purgeService = new PurgeService($logService);
$purgeService = new PurgeService($connect, $logService);
$securityService = new SecurityService();
$inputService = new InputService();
/* POST */
/*-----*/
$action = $inputService->filterName($_POST['action'] ?? null);
$action = $inputService->filterName(isset($_POST['action']) ? $_POST['action'] : null);
/* PAGE */
/* ---- */
@ -57,4 +57,4 @@ $smarty->assign('crsf', $securityService->getToken('admin'));
$smarty->assign('title', __('Admin', 'Purge'));
$smarty->display('admin/purge.tpl');
$smarty->display('admin/purge.tpl');

View File

@ -47,7 +47,7 @@ $editingVoteId = 0;
/*----------*/
$logService = new LogService();
$pollService = new PollService($logService);
$pollService = new PollService($connect, $logService);
$adminPollService = new AdminPollService($connect, $pollService, $logService);
$inputService = new InputService();
$mailService = new MailService($config['use_smtp'], $config['smtp_options']);
@ -80,7 +80,7 @@ $messagePollCreated = $sessionService->get("Framadate", "messagePollCreated", FA
if ($messagePollCreated) {
$sessionService->remove("Framadate", "messagePollCreated");
$message = new Message('success', __('adminstuds', 'The poll is created.'));
}
@ -113,7 +113,7 @@ if (isset($_POST['update_poll_info'])) {
$updated = true;
}
} elseif ($field === 'rules') {
$rules = (int) strip_tags($_POST['rules']);
$rules = strip_tags($_POST['rules']);
switch ($rules) {
case 0:
$poll->active = false;
@ -137,41 +137,38 @@ if (isset($_POST['update_poll_info'])) {
break;
}
} elseif ($field === 'expiration_date') {
$givenExpirationDate = $inputService->parseDate($_POST['expiration_date']);
$expiration_date = $inputService->validateDate($givenExpirationDate, $pollService->minExpiryDate(), $pollService->maxExpiryDate());
if ($poll->end_date !== $expiration_date->format('Y-m-d H:i:s')) {
$poll->end_date = $expiration_date->format('Y-m-d H:i:s');
$expiration_date = $inputService->filterDate($_POST['expiration_date']);
if ($expiration_date) {
$poll->end_date = $expiration_date;
$updated = true;
}
} elseif ($field === 'name') {
$admin_name = $_POST['name'];
$admin_name = mb_substr($admin_name, 0, 32);
$admin_name = $inputService->filterName($admin_name);
$admin_name = $inputService->filterName($_POST['name']);
if ($admin_name) {
$poll->admin_name = $admin_name;
$updated = true;
}
} elseif ($field === 'hidden') {
$hidden = isset($_POST['hidden']) && $inputService->filterBoolean($_POST['hidden']);
$hidden = isset($_POST['hidden']) ? $inputService->filterBoolean($_POST['hidden']) : false;
if ($hidden !== $poll->hidden) {
$poll->hidden = $hidden;
$poll->results_publicly_visible = false;
$updated = true;
}
} elseif ($field === 'removePassword') {
$removePassword = isset($_POST['removePassword']) && $inputService->filterBoolean($_POST['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 = $_POST['password'] ?? null;
$password = isset($_POST['password']) ? $_POST['password'] : null;
/**
* Did the user choose results to be publicly visible ?
*/
$resultsPubliclyVisible = isset($_POST['resultsPubliclyVisible']) && $inputService->filterBoolean($_POST['resultsPubliclyVisible']);
$resultsPubliclyVisible = isset($_POST['resultsPubliclyVisible']) ? $inputService->filterBoolean($_POST['resultsPubliclyVisible']) : false;
/**
* If there's one, save the password
*/

View File

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

View File

@ -23,13 +23,14 @@ namespace Framadate;
* Class Editable
*
* Is used to specify the poll's edition permissions.
* @TODO : wait to use the SplEnum
*
* @package Framadate
*/
class Editable { // extends SplEnum
const __default = self::EDITABLE_BY_ALL;
public const NOT_EDITABLE = 0;
public const EDITABLE_BY_ALL = 1;
public const EDITABLE_BY_OWN = 2;
}
const NOT_EDITABLE = 0;
const EDITABLE_BY_ALL = 1;
const EDITABLE_BY_OWN = 2;
}

View File

@ -2,4 +2,6 @@
namespace Framadate\Exception;
class AlreadyExistsException extends \Exception {
function __construct() {
}
}

View File

@ -2,4 +2,6 @@
namespace Framadate\Exception;
class ConcurrentEditionException extends \Exception {
function __construct() {
}
}

View File

@ -7,4 +7,6 @@ namespace Framadate\Exception;
* Thrown when a poll has a maximum votes constraint for options, and a vote happened since the poll was rendered
*/
class ConcurrentVoteException extends \Exception {
function __construct() {
}
}

View File

@ -2,4 +2,6 @@
namespace Framadate\Exception;
class MomentAlreadyExistsException extends \Exception {
function __construct() {
}
}

View File

@ -1,10 +0,0 @@
<?php
namespace Framadate\Exception;
/**
* Class PollNotFoundException
*
* Thrown when a poll isn't found in a critical process
*/
class PollNotFoundException extends \Exception {
}

View File

@ -32,7 +32,7 @@ class Form
/**
* Tells if users can modify their choices.
* @var int
* @var \Framadate\Editable
*/
public $editable;
@ -92,12 +92,11 @@ class Form
$this->clearChoices();
}
public function clearChoices(): void
{
public function clearChoices() {
$this->choices = [];
}
public function addChoice(Choice $choice): void
public function addChoice(Choice $choice)
{
$this->choices[] = $choice;
}
@ -107,8 +106,8 @@ class Form
return $this->choices;
}
public function sortChoices(): void
public function sortChoices()
{
usort($this->choices, [Choice::class, 'compare']);
usort($this->choices, ['Framadate\Choice', 'compare']);
}
}

View File

@ -23,21 +23,19 @@ use PDO;
class FramaDB {
/**
* PDO Object, connection to database.
* @var PDO
*/
private $pdo;
private $pdo = null;
public function __construct(string $connection_string, string $user, string $password) {
$this->pdo = new PDO($connection_string, $user, $password);
$this->pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
function __construct($connection_string, $user, $password) {
$this->pdo = new \PDO($connection_string, $user, $password);
$this->pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_OBJ);
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
}
/**
* @return PDO Connection to database
* @return \PDO Connection to database
*/
public function getPDO(): PDO
{
function getPDO() {
return $this->pdo;
}
@ -46,50 +44,42 @@ class FramaDB {
*
* @return array The array of table names
*/
public function allTables(): array
{
return $this->pdo->query('SHOW TABLES')->fetchAll(PDO::FETCH_COLUMN);
function allTables() {
$result = $this->pdo->query('SHOW TABLES');
$schemas = $result->fetchAll(\PDO::FETCH_COLUMN);
return $schemas;
}
/**
* @return \PDOStatement|false
*/
public function prepare(string $sql) {
function prepare($sql) {
return $this->pdo->prepare($sql);
}
public function beginTransaction(): void
{
function beginTransaction() {
$this->pdo->beginTransaction();
}
public function commit(): void
{
function commit() {
$this->pdo->commit();
}
public function rollback(): void
{
function rollback() {
$this->pdo->rollback();
}
public function errorCode(): ?string {
function errorCode() {
return $this->pdo->errorCode();
}
public function errorInfo(): array
{
function errorInfo() {
return $this->pdo->errorInfo();
}
/**
* @return \PDOStatement|false
*/
public function query($sql) {
function query($sql) {
return $this->pdo->query($sql);
}
public function lastInsertId(): string {
public function lastInsertId() {
return $this->pdo->lastInsertId();
}
}

View File

@ -1,37 +1,38 @@
<?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/OpenSondage: 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;
<?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/OpenSondage: 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;
class Message {
var $type;
var $message;
var $link;
var $linkTitle;
var $linkIcon;
var $includeTemplate;
public function __construct($type, $message, $link=null, $linkTitle=null, $linkIcon=null, $includeTemplate=null) {
$this->type = $type;
$this->message = $message;
$this->link = $link;
$this->linkTitle = $linkTitle;
$this->linkIcon = $linkIcon;
var $type;
var $message;
var $link;
var $linkTitle;
var $linkIcon;
var $includeTemplate;
function __construct($type, $message, $link=null, $linkTitle=null, $linkIcon=null, $includeTemplate=null) {
$this->type = $type;
$this->message = $message;
$this->link = $link;
$this->linkTitle = $linkTitle;
$this->linkIcon = $linkIcon;
$this->includeTemplate = $includeTemplate;
}
}
}

View File

@ -19,7 +19,6 @@
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This migration adds the field Value_Max on the poll table.
@ -28,7 +27,7 @@ use PDO;
* @version 0.9
*/
class AddColumn_ValueMax_In_poll_For_1_1 implements Migration {
public function __construct() {
function __construct() {
}
/**
@ -36,7 +35,7 @@ class AddColumn_ValueMax_In_poll_For_1_1 implements Migration {
*
* @return string The description of the migration class
*/
public function description():string {
function description() {
return 'Add column "ValueMax" in table "vote" for version 0.9';
}
@ -44,27 +43,26 @@ class AddColumn_ValueMax_In_poll_For_1_1 implements Migration {
* 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
* @param \PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool {
function preCondition(\PDO $pdo) {
return true;
}
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool {
function execute(\PDO $pdo) {
$this->alterPollTable($pdo);
return true;
}
private function alterPollTable(PDO $pdo): void
{
private function alterPollTable(\PDO $pdo) {
$pdo->exec('
ALTER TABLE `' . Utils::table('poll') . '`
ADD `ValueMax` TINYINT NULL;');

View File

@ -19,7 +19,6 @@
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This migration adds the field hidden on the poll table.
@ -28,7 +27,7 @@ use PDO;
* @version 0.9
*/
class AddColumn_hidden_In_poll_For_0_9 implements Migration {
public function __construct() {
function __construct() {
}
/**
@ -36,8 +35,7 @@ class AddColumn_hidden_In_poll_For_0_9 implements Migration {
*
* @return string The description of the migration class
*/
public function description(): string
{
function description() {
return 'Add column "hidden" in table "vote" for version 0.9';
}
@ -45,13 +43,12 @@ class AddColumn_hidden_In_poll_For_0_9 implements Migration {
* 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
* @param \PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool
{
function preCondition(\PDO $pdo) {
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
$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);
@ -61,18 +58,16 @@ class AddColumn_hidden_In_poll_For_0_9 implements Migration {
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool
{
function execute(\PDO $pdo) {
$this->alterPollTable($pdo);
return true;
}
private function alterPollTable(PDO $pdo): void
{
private function alterPollTable(\PDO $pdo) {
$pdo->exec('
ALTER TABLE `' . Utils::table('poll') . '`
ADD `hidden` TINYINT( 1 ) NOT NULL DEFAULT "0"');

View File

@ -19,7 +19,6 @@
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This migration adds the field receiveNewComments on the poll table.
@ -28,7 +27,7 @@ use PDO;
* @version 0.9
*/
class AddColumn_receiveNewComments_For_0_9 implements Migration {
public function __construct() {
function __construct() {
}
/**
@ -36,8 +35,7 @@ class AddColumn_receiveNewComments_For_0_9 implements Migration {
*
* @return string The description of the migration class
*/
public function description(): string
{
function description() {
return 'Add column "receiveNewComments" for version 0.9';
}
@ -45,13 +43,12 @@ class AddColumn_receiveNewComments_For_0_9 implements Migration {
* 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
* @param \PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool
{
function preCondition(\PDO $pdo) {
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
$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);
@ -61,18 +58,16 @@ class AddColumn_receiveNewComments_For_0_9 implements Migration {
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool
{
function execute(\PDO $pdo) {
$this->alterPollTable($pdo);
return true;
}
private function alterPollTable(PDO $pdo): void
{
private function alterPollTable(\PDO $pdo) {
$pdo->exec('
ALTER TABLE `' . Utils::table('poll') . '`
ADD `receiveNewComments` TINYINT(1) DEFAULT \'0\'

View File

@ -19,7 +19,6 @@
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This migration adds the field uniqId on the vote table.
@ -28,7 +27,7 @@ use PDO;
* @version 0.9
*/
class AddColumn_uniqId_In_vote_For_0_9 implements Migration {
public function __construct() {
function __construct() {
}
/**
@ -36,7 +35,7 @@ class AddColumn_uniqId_In_vote_For_0_9 implements Migration {
*
* @return string The description of the migration class
*/
public function description(): string {
function description() {
return 'Add column "uniqId" in table "vote" for version 0.9';
}
@ -44,12 +43,12 @@ class AddColumn_uniqId_In_vote_For_0_9 implements Migration {
* 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
* @param \PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool {
function preCondition(\PDO $pdo) {
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
$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);
@ -59,17 +58,16 @@ class AddColumn_uniqId_In_vote_For_0_9 implements Migration {
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool {
function execute(\PDO $pdo) {
$this->alterPollTable($pdo);
return true;
}
private function alterPollTable(PDO $pdo): void
{
private function alterPollTable(\PDO $pdo) {
$pdo->exec('
ALTER TABLE `' . Utils::table('vote') . '`
ADD `uniqId` CHAR(16) NOT NULL

View File

@ -19,7 +19,6 @@
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This migration adds the fields password_hash and results_publicly_visible on the poll table.
@ -28,7 +27,7 @@ use PDO;
* @version 0.9
*/
class AddColumns_password_hash_And_results_publicly_visible_In_poll_For_0_9 implements Migration {
public function __construct() {
function __construct() {
}
/**
@ -36,7 +35,7 @@ class AddColumns_password_hash_And_results_publicly_visible_In_poll_For_0_9 impl
*
* @return string The description of the migration class
*/
function description(): string {
function description() {
return 'Add columns "password_hash" and "results_publicly_visible" in table "vote" for version 0.9';
}
@ -44,12 +43,12 @@ class AddColumns_password_hash_And_results_publicly_visible_In_poll_For_0_9 impl
* 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
* @param \PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool {
function preCondition(\PDO $pdo) {
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
$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);
@ -59,17 +58,16 @@ class AddColumns_password_hash_And_results_publicly_visible_In_poll_For_0_9 impl
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool {
function execute(\PDO $pdo) {
$this->alterPollTable($pdo);
return true;
}
private function alterPollTable(PDO $pdo): void
{
private function alterPollTable(\PDO $pdo) {
$pdo->exec('
ALTER TABLE `' . Utils::table('poll') . '`
ADD `password_hash` VARCHAR(255) NULL DEFAULT NULL ,

View File

@ -19,7 +19,6 @@
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This migration alter the comment table to add a date column.
@ -28,7 +27,7 @@ use PDO;
* @version 1.0
*/
class Alter_Comment_table_adding_date implements Migration {
public function __construct() {
function __construct() {
}
/**
@ -36,7 +35,7 @@ class Alter_Comment_table_adding_date implements Migration {
*
* @return string The description of the migration class
*/
public function description():string {
function description() {
return 'Alter the comment table to add a date column.';
}
@ -44,27 +43,26 @@ class Alter_Comment_table_adding_date implements Migration {
* 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
* @param \PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool {
function preCondition(\PDO $pdo) {
return true;
}
/**
* This methode is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool {
function execute(\PDO $pdo) {
$this->alterCommentTable($pdo);
return true;
}
private function alterCommentTable(PDO $pdo): void
{
private function alterCommentTable(\PDO $pdo) {
$pdo->exec('
ALTER TABLE `' . Utils::table('comment') . '`
ADD `date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ;');

View File

@ -19,7 +19,6 @@
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This migration alter the comment table to set a length to the name column.
@ -28,7 +27,7 @@ use PDO;
* @version 1.0
*/
class Alter_Comment_table_for_name_length implements Migration {
public function __construct() {
function __construct() {
}
/**
@ -36,7 +35,7 @@ class Alter_Comment_table_for_name_length implements Migration {
*
* @return string The description of the migration class
*/
public function description(): string {
function description() {
return 'Alter the comment table to set a length to the name column.';
}
@ -44,27 +43,26 @@ class Alter_Comment_table_for_name_length implements Migration {
* 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
* @param \PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool {
function preCondition(\PDO $pdo) {
return true;
}
/**
* This methode is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool {
function execute(\PDO $pdo) {
$this->alterCommentTable($pdo);
return true;
}
private function alterCommentTable(PDO $pdo): void
{
private function alterCommentTable(\PDO $pdo) {
$pdo->exec('
ALTER TABLE `' . Utils::table('comment') . '`
CHANGE `name` `name` VARCHAR( 64 ) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL ;');

View File

@ -19,7 +19,6 @@
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This migration sets Poll.end_date to NULL by default
@ -28,7 +27,7 @@ use PDO;
* @version 1.1
*/
class Fix_MySQL_No_Zero_Date implements Migration {
public function __construct() {
function __construct() {
}
/**
@ -36,7 +35,7 @@ class Fix_MySQL_No_Zero_Date implements Migration {
*
* @return string The description of the migration class
*/
public function description(): string {
function description() {
return 'Sets Poll end_date to NULL by default (work around MySQL NO_ZERO_DATE)';
}
@ -44,17 +43,17 @@ class Fix_MySQL_No_Zero_Date implements Migration {
* 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
* @param \PDO $pdo The connection to database
* @return bool true if the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool {
function preCondition(\PDO $pdo) {
$stmt = $pdo->prepare("SELECT Column_Default from Information_Schema.Columns where Table_Name = ? AND Column_Name = ?;");
$stmt->bindValue(1, Utils::table('poll'));
$stmt->bindValue(2, 'end_date');
$stmt->execute();
$default = $stmt->fetch(PDO::FETCH_COLUMN);
$default = $stmt->fetch(\PDO::FETCH_COLUMN);
$driver_name = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
$driver_name = $pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
return $default !== null && $driver_name === 'mysql';
}
@ -62,11 +61,10 @@ class Fix_MySQL_No_Zero_Date implements Migration {
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @return bool if the execution succeeded
* @param \PDO $pdo The connection to database
* @return bool|void if the execution succeeded
*/
public function execute(PDO $pdo): bool {
function execute(\PDO $pdo) {
$pdo->exec('ALTER TABLE ' . Utils::table('poll') . ' MODIFY end_date TIMESTAMP NULL DEFAULT NULL;');
return true;
}
}

View File

@ -19,7 +19,6 @@
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* Class From_0_0_to_0_8_Migration
@ -28,7 +27,7 @@ use PDO;
* @version 0.8
*/
class From_0_0_to_0_8_Migration implements Migration {
public function __construct() {
function __construct() {
}
/**
@ -36,7 +35,7 @@ class From_0_0_to_0_8_Migration implements Migration {
*
* @return string The description of the migration class
*/
public function description(): string {
function description() {
return 'First installation of the Framadate application (v0.8)';
}
@ -44,12 +43,12 @@ class From_0_0_to_0_8_Migration implements Migration {
* 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
* @param \PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool {
function preCondition(\PDO $pdo) {
$stmt = $pdo->query('SHOW TABLES like \'' . TABLENAME_PREFIX . '%\''); //issue187 : pouvoir installer framadate dans une base contenant d'autres tables.
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
$tables = $stmt->fetchAll(\PDO::FETCH_COLUMN);
// Check if there is no tables but the MIGRATION_TABLE one
$diff = array_diff($tables, [Utils::table(MIGRATION_TABLE)]);
@ -59,10 +58,10 @@ class From_0_0_to_0_8_Migration implements Migration {
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool {
function execute(\PDO $pdo) {
$pdo->exec('
CREATE TABLE IF NOT EXISTS `sondage` (
`id_sondage` char(16) NOT NULL,
@ -105,6 +104,5 @@ CREATE TABLE IF NOT EXISTS `user_studs` (
PRIMARY KEY (`id_users`),
KEY `id_sondage` (`id_sondage`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;');
return true;
}
}

View File

@ -19,7 +19,6 @@
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
/**
* This class executes the aciton in database to migrate data from version 0.8 to 0.9.
@ -28,7 +27,7 @@ use PDO;
* @version 0.9
*/
class From_0_8_to_0_9_Migration implements Migration {
public function __construct() {
function __construct() {
}
/**
@ -36,7 +35,7 @@ class From_0_8_to_0_9_Migration implements Migration {
*
* @return string The description of the migration class
*/
public function description(): string {
function description() {
return 'From 0.8 to 0.9';
}
@ -44,12 +43,12 @@ class From_0_8_to_0_9_Migration implements Migration {
* 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
* @param \PDO $pdo The connection to database
* @return bool true is the Migration should be executed.
*/
public function preCondition(PDO $pdo): bool {
function preCondition(\PDO $pdo) {
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
$tables = $stmt->fetchAll(\PDO::FETCH_COLUMN);
// Check if tables of v0.8 are presents
$diff = array_diff(['sondage', 'sujet_studs', 'comments', 'user_studs'], $tables);
@ -59,10 +58,10 @@ class From_0_8_to_0_9_Migration implements Migration {
/**
* This method is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool {
function execute(\PDO $pdo) {
$this->createPollTable($pdo);
$this->createCommentTable($pdo);
$this->createSlotTable($pdo);
@ -80,8 +79,7 @@ class From_0_8_to_0_9_Migration implements Migration {
return true;
}
private function createPollTable(PDO $pdo): void
{
private function createPollTable(\PDO $pdo) {
$pdo->exec('
CREATE TABLE IF NOT EXISTS `' . Utils::table('poll') . '` (
`id` CHAR(16) NOT NULL,
@ -102,8 +100,7 @@ CREATE TABLE IF NOT EXISTS `' . Utils::table('poll') . '` (
DEFAULT CHARSET = utf8');
}
private function migrateFromSondageToPoll(PDO $pdo): void
{
private function migrateFromSondageToPoll(\PDO $pdo) {
$select = $pdo->query('
SELECT
`id_sondage`,
@ -129,7 +126,7 @@ INSERT INTO `' . Utils::table('poll') . '`
(`id`, `admin_id`, `title`, `description`, `admin_name`, `admin_mail`, `creation_date`, `end_date`, `format`, `editable`, `receiveNewVotes`, `active`)
VALUE (?,?,?,?,?,?,?,?,?,?,?,?)');
while ($row = $select->fetch(PDO::FETCH_OBJ)) {
while ($row = $select->fetch(\PDO::FETCH_OBJ)) {
$insert->execute([
$row->id_sondage,
$row->id_sondage_admin,
@ -147,8 +144,7 @@ VALUE (?,?,?,?,?,?,?,?,?,?,?,?)');
}
}
private function createSlotTable(PDO $pdo): void
{
private function createSlotTable(\PDO $pdo) {
$pdo->exec('
CREATE TABLE IF NOT EXISTS `' . Utils::table('slot') . '` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
@ -162,8 +158,7 @@ CREATE TABLE IF NOT EXISTS `' . Utils::table('slot') . '` (
DEFAULT CHARSET = utf8');
}
private function migrateFromSujetStudsToSlot(PDO $pdo): void
{
private function migrateFromSujetStudsToSlot(\PDO $pdo) {
$stmt = $pdo->query('SELECT * FROM sujet_studs');
$sujets = $stmt->fetchAll();
$slots = [];
@ -185,8 +180,7 @@ CREATE TABLE IF NOT EXISTS `' . Utils::table('slot') . '` (
}
}
private function createCommentTable(PDO $pdo): void
{
private function createCommentTable(\PDO $pdo) {
$pdo->exec('
CREATE TABLE IF NOT EXISTS `' . Utils::table('comment') . '` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
@ -200,8 +194,7 @@ CREATE TABLE IF NOT EXISTS `' . Utils::table('comment') . '` (
DEFAULT CHARSET = utf8');
}
private function migrateFromCommentsToComment(PDO $pdo): void
{
private function migrateFromCommentsToComment(\PDO $pdo) {
$select = $pdo->query('
SELECT
`id_sondage`,
@ -213,7 +206,7 @@ SELECT
INSERT INTO `' . Utils::table('comment') . '` (`poll_id`, `name`, `comment`)
VALUE (?,?,?)');
while ($row = $select->fetch(PDO::FETCH_OBJ)) {
while ($row = $select->fetch(\PDO::FETCH_OBJ)) {
$insert->execute([
$row->id_sondage,
$this->unescape($row->usercomment),
@ -222,8 +215,7 @@ VALUE (?,?,?)');
}
}
private function createVoteTable(PDO $pdo): void
{
private function createVoteTable(\PDO $pdo) {
$pdo->exec('
CREATE TABLE IF NOT EXISTS `' . Utils::table('vote') . '` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
@ -237,8 +229,7 @@ CREATE TABLE IF NOT EXISTS `' . Utils::table('vote') . '` (
DEFAULT CHARSET = utf8');
}
private function migrateFromUserStudsToVote(PDO $pdo): void
{
private function migrateFromUserStudsToVote(\PDO $pdo) {
$select = $pdo->query('
SELECT
`id_sondage`,
@ -250,7 +241,7 @@ SELECT
INSERT INTO `' . Utils::table('vote') . '` (`poll_id`, `name`, `choices`)
VALUE (?,?,?)');
while ($row = $select->fetch(PDO::FETCH_OBJ)) {
while ($row = $select->fetch(\PDO::FETCH_OBJ)) {
$insert->execute([
$row->id_sondage,
$this->unescape($row->nom),
@ -259,8 +250,7 @@ VALUE (?,?,?)');
}
}
private function transformSujetToSlot($sujet): array
{
private function transformSujetToSlot($sujet) {
$slots = [];
$ex = explode(',', $sujet->sujet);
$isDatePoll = strpos($sujet->sujet, '@');
@ -289,16 +279,14 @@ VALUE (?,?,?)');
return $slots;
}
private function dropOldTables(PDO $pdo): void
{
private function dropOldTables(\PDO $pdo) {
$pdo->exec('DROP TABLE `comments`');
$pdo->exec('DROP TABLE `sujet_studs`');
$pdo->exec('DROP TABLE `user_studs`');
$pdo->exec('DROP TABLE `sondage`');
}
private function unescape(string $value): string
{
private function unescape($value) {
return stripslashes(html_entity_decode($value, ENT_QUOTES));
}
}

View File

@ -20,7 +20,6 @@ namespace Framadate\Migration;
use Framadate\Security\Token;
use Framadate\Utils;
use PDO;
/**
* This migration generate uniqId for all legacy votes.
@ -29,16 +28,16 @@ use PDO;
* @version 0.9
*/
class Generate_uniqId_for_old_votes implements Migration {
public function __construct() {
function __construct() {
}
public function description(): string {
function description() {
return 'Generate "uniqId" in "vote" table for all legacy votes';
}
public function preCondition(PDO $pdo): bool {
function preCondition(\PDO $pdo) {
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(PDO::FETCH_COLUMN);
$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);
@ -48,10 +47,10 @@ class Generate_uniqId_for_old_votes implements Migration {
/**
* This methode is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @param \PDO $pdo The connection to database
* @return bool true is the execution succeeded
*/
public function execute(PDO $pdo): bool {
function execute(\PDO $pdo) {
$pdo->beginTransaction();
$this->generateUniqIdsForEmptyOnes($pdo);
$pdo->commit();
@ -59,8 +58,7 @@ class Generate_uniqId_for_old_votes implements Migration {
return true;
}
private function generateUniqIdsForEmptyOnes(PDO $pdo): void
{
private function generateUniqIdsForEmptyOnes($pdo) {
$select = $pdo->query('
SELECT `id`
FROM `' . Utils::table('vote') . '`
@ -71,7 +69,7 @@ UPDATE `' . Utils::table('vote') . '`
SET `uniqid` = :uniqid
WHERE `id` = :id');
while ($row = $select->fetch(PDO::FETCH_OBJ)) {
while ($row = $select->fetch(\PDO::FETCH_OBJ)) {
$token = Token::getToken(16);
$update->execute([
'uniqid' => $token,

View File

@ -2,10 +2,9 @@
namespace Framadate\Migration;
use Framadate\Utils;
use PDO;
class Increase_pollId_size implements Migration {
public function __construct() {
function __construct() {
}
/**
@ -13,7 +12,7 @@ class Increase_pollId_size implements Migration {
*
* @return string The description of the migration class
*/
public function description(): string {
function description() {
return 'Increase the size of id column in poll table';
}
@ -21,50 +20,45 @@ class Increase_pollId_size implements Migration {
* 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
* @param \PDO $pdo The connection to database
* @return bool true if the Migration should be executed
*/
public function preCondition(PDO $pdo): bool {
function preCondition(\PDO $pdo) {
return true;
}
/**
* This methode is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @param \PDO $pdo The connection to database
* @return bool true if the execution succeeded
*/
public function execute(PDO $pdo): bool {
function execute(\PDO $pdo) {
$this->alterCommentTable($pdo);
$this->alterPollTable($pdo);
$this->alterSlotTable($pdo);
$this->alterVoteTable($pdo);
return true;
}
private function alterCommentTable(PDO $pdo): void
{
private function alterCommentTable(\PDO $pdo) {
$pdo->exec('
ALTER TABLE `' . Utils::table('comment') . '`
CHANGE `poll_id` `poll_id` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;');
}
private function alterPollTable(PDO $pdo): void
{
private function alterPollTable(\PDO $pdo) {
$pdo->exec('
ALTER TABLE `' . Utils::table('poll') . '`
CHANGE `id` `id` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;');
}
private function alterSlotTable(PDO $pdo): void
{
private function alterSlotTable(\PDO $pdo) {
$pdo->exec('
ALTER TABLE `' . Utils::table('slot') . '`
CHANGE `poll_id` `poll_id` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;');
}
private function alterVoteTable(PDO $pdo): void
{
private function alterVoteTable(\PDO $pdo) {
$pdo->exec('
ALTER TABLE `' . Utils::table('vote') . '`
CHANGE `poll_id` `poll_id` VARCHAR(64) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;');

View File

@ -18,30 +18,29 @@
*/
namespace Framadate\Migration;
use PDO;
interface Migration {
/**
* This method should describe in english what is the purpose of the migration class.
*
* @return string The description of the migration class
*/
public function description(): string;
function description();
/**
* 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
* @param \PDO $pdo The connection to database
* @return bool true if the Migration should be executed
*/
public function preCondition(PDO $pdo): bool;
function preCondition(\PDO $pdo);
/**
* This methode is called only one time in the migration page.
*
* @param PDO $pdo The connection to database
* @param \PDO $pdo The connection to database
* @return bool true if the execution succeeded
*/
public function execute(PDO $pdo): bool;
function execute(\PDO $pdo);
}

View File

@ -4,16 +4,16 @@
* 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 Raphael DROZ
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* 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 Raphael DROZ
* 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;
@ -28,11 +28,11 @@ use Framadate\Utils;
* @version 0.9
*/
class RPadVotes_from_0_8 implements Migration {
public function description(): string {
function description() {
return 'RPad votes from version 0.8.';
}
public function preCondition(\PDO $pdo): bool {
function preCondition(\PDO $pdo) {
$stmt = $pdo->query('SHOW TABLES');
$tables = $stmt->fetchAll(\PDO::FETCH_COLUMN);
@ -41,7 +41,7 @@ class RPadVotes_from_0_8 implements Migration {
return count($diff) === 0;
}
public function execute(\PDO $pdo): bool {
function execute(\PDO $pdo) {
$pdo->beginTransaction();
$this->rpadVotes($pdo);
$pdo->commit();
@ -49,8 +49,7 @@ class RPadVotes_from_0_8 implements Migration {
return true;
}
private function rpadVotes(\PDO $pdo): void
{
private function rpadVotes($pdo) {
$pdo->exec('UPDATE ' . Utils::table('vote') . ' fv
INNER JOIN (
SELECT v.id, RPAD(v.choices, inn.slots_count, \'0\') new_choices
@ -64,4 +63,4 @@ INNER JOIN (
) computed ON fv.id = computed.id
SET fv.choices = computed.new_choices');
}
}
}

View File

@ -13,40 +13,31 @@ abstract class AbstractRepository {
* PollRepository constructor.
* @param FramaDB $connect
*/
public function __construct(FramaDB $connect) {
function __construct(FramaDB $connect) {
$this->connect = $connect;
}
public function beginTransaction(): void
{
public function beginTransaction() {
$this->connect->beginTransaction();
}
public function commit(): void
{
public function commit() {
$this->connect->commit();
}
public function rollback(): void
{
function rollback() {
$this->connect->rollback();
}
/**
* @return \PDOStatement|false
*/
public function prepare(string $sql) {
public function prepare($sql) {
return $this->connect->prepare($sql);
}
/**
* @return \PDOStatement|false
*/
public function query($sql) {
function query($sql) {
return $this->connect->query($sql);
}
public function lastInsertId(): string {
function lastInsertId() {
return $this->connect->lastInsertId();
}
}

View File

@ -1,13 +1,15 @@
<?php
namespace Framadate\Repositories;
use Framadate\FramaDB;
use Framadate\Utils;
class CommentRepository extends AbstractRepository {
/**
* @return array|false
*/
public function findAllByPollId(string $poll_id) {
function __construct(FramaDB $connect) {
parent::__construct($connect);
}
function findAllByPollId($poll_id) {
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('comment') . '` WHERE poll_id = ? ORDER BY id');
$prepared->execute([$poll_id]);
@ -17,20 +19,18 @@ class CommentRepository extends AbstractRepository {
/**
* Insert a new comment.
*
* @param string $poll_id
* @param string $name
* @param string $comment
* @param $poll_id
* @param $name
* @param $comment
* @return bool
*/
public function insert(string $poll_id, string $name, string $comment): bool
{
function insert($poll_id, $name, $comment) {
$prepared = $this->prepare('INSERT INTO `' . Utils::table('comment') . '` (poll_id, name, comment) VALUES (?,?,?)');
return $prepared->execute([$poll_id, $name, $comment]);
}
public function deleteById(string $poll_id, int $comment_id): bool
{
function deleteById($poll_id, $comment_id) {
$prepared = $this->prepare('DELETE FROM `' . Utils::table('comment') . '` WHERE poll_id = ? AND id = ?');
return $prepared->execute([$poll_id, $comment_id]);
@ -39,18 +39,16 @@ class CommentRepository extends AbstractRepository {
/**
* Delete all comments of a given poll.
*
* @param string $poll_id The ID of the given poll.
* @param $poll_id int The ID of the given poll.
* @return bool|null true if action succeeded.
*/
public function deleteByPollId(string $poll_id): ?bool
{
function deleteByPollId($poll_id) {
$prepared = $this->prepare('DELETE FROM `' . Utils::table('comment') . '` WHERE poll_id = ?');
return $prepared->execute([$poll_id]);
}
public function exists(string $poll_id, string $name, string $comment): bool
{
public function exists($poll_id, $name, $comment) {
$prepared = $this->prepare('SELECT 1 FROM `' . Utils::table('comment') . '` WHERE poll_id = ? AND name = ? AND comment = ?');
$prepared->execute([$poll_id, $name, $comment]);

View File

@ -6,8 +6,11 @@ use Framadate\Utils;
use PDO;
class PollRepository extends AbstractRepository {
public function insertPoll(string $poll_id, string $admin_poll_id, $form): void
{
function __construct(FramaDB $connect) {
parent::__construct($connect);
}
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, hidden, password_hash, results_publicly_visible,ValueMax)
VALUES (?,?,?,?,?,?,FROM_UNIXTIME(?),?,?,?,?,?,?,?,?)';
@ -15,7 +18,7 @@ class PollRepository extends AbstractRepository {
$prepared->execute([$poll_id, $admin_poll_id, $form->title, $form->description, $form->admin_name, $form->admin_mail, $form->end_date, $form->format, ($form->editable>=0 && $form->editable<=2) ? $form->editable : 0, $form->receiveNewVotes ? 1 : 0, $form->receiveNewComments ? 1 : 0, $form->hidden ? 1 : 0, $form->password_hash, $form->results_publicly_visible ? 1 : 0,$form->ValueMax]);
}
public function findById(string $poll_id) {
function findById($poll_id) {
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('poll') . '` WHERE id = ?');
$prepared->execute([$poll_id]);
@ -25,7 +28,7 @@ class PollRepository extends AbstractRepository {
return $poll;
}
public function findByAdminId(string $admin_poll_id) {
public function findByAdminId($admin_poll_id) {
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('poll') . '` WHERE admin_id = ?');
$prepared->execute([$admin_poll_id]);
@ -35,8 +38,7 @@ class PollRepository extends AbstractRepository {
return $poll;
}
public function existsById(string $poll_id): bool
{
public function existsById($poll_id) {
$prepared = $this->prepare('SELECT 1 FROM `' . Utils::table('poll') . '` WHERE id = ?');
$prepared->execute([$poll_id]);
@ -44,8 +46,7 @@ class PollRepository extends AbstractRepository {
return $prepared->rowCount() > 0;
}
public function existsByAdminId(string $admin_poll_id): bool
{
public function existsByAdminId($admin_poll_id) {
$prepared = $this->prepare('SELECT 1 FROM `' . Utils::table('poll') . '` WHERE admin_id = ?');
$prepared->execute([$admin_poll_id]);
@ -53,15 +54,13 @@ class PollRepository extends AbstractRepository {
return $prepared->rowCount() > 0;
}
public function update($poll): bool
{
function update($poll) {
$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 ? 1 : 0, ($poll->editable>=0 && $poll->editable<=2) ? $poll->editable : 0, $poll->hidden ? 1 : 0, $poll->password_hash, $poll->results_publicly_visible ? 1 : 0, $poll->id]);
return $prepared->execute([$poll->title, $poll->admin_name, $poll->admin_mail, $poll->description, $poll->end_date, $poll->active, ($poll->editable>=0 && $poll->editable<=2) ? $poll->editable : 0, $poll->hidden ? 1 : 0, $poll->password_hash, $poll->results_publicly_visible ? 1 : 0, $poll->id]);
}
public function deleteById($poll_id): bool
{
function deleteById($poll_id) {
$prepared = $this->prepare('DELETE FROM `' . Utils::table('poll') . '` WHERE id = ?');
return $prepared->execute([$poll_id]);
@ -72,8 +71,7 @@ class PollRepository extends AbstractRepository {
*
* @return array Array of old polls
*/
public function findOldPolls(): array
{
public function findOldPolls() {
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('poll') . '` WHERE DATE_ADD(`end_date`, INTERVAL ' . PURGE_DELAY . ' DAY) < NOW() AND `end_date` != 0 LIMIT 20');
$prepared->execute([]);
@ -88,50 +86,50 @@ class PollRepository extends AbstractRepository {
* @param int $limit The number of entries to find
* @return array The found polls
*/
public function findAll(array $search, int $start, int $limit): array
{
public function findAll($search, $start, $limit) {
// Polls
$request = "SELECT p.*,";
$request = "";
$request .= "SELECT p.*,";
$request .= " (SELECT count(1) FROM `" . Utils::table('vote') . "` v WHERE p.id=v.poll_id) votes";
$request .= " FROM `" . Utils::table('poll') . "` p";
$request .= " WHERE 1";
$values = [];
if (!empty($search["poll"])) {
$request .= " AND p.id LIKE :poll";
$values["poll"] = "{$search["poll"]}%";
}
$fields = [
// key of $search => column name
"title" => "title",
"name" => "admin_name",
"mail" => "admin_mail",
];
foreach ($fields as $searchKey => $columnName) {
if (empty($search[$searchKey])) {
continue;
}
$request .= " AND p.$columnName LIKE :$searchKey";
$values[$searchKey] = "%$search[$searchKey]%";
$values[$searchKey] = "%{$search[$searchKey]}%";
}
$request .= " ORDER BY p.title ASC";
$request .= " LIMIT :start, :limit";
$prepared = $this->prepare($request);
foreach ($values as $searchKey => $value) {
$prepared->bindParam(":$searchKey", $value, PDO::PARAM_STR);
}
$prepared->bindParam(':start', $start, PDO::PARAM_INT);
$prepared->bindParam(':limit', $limit, PDO::PARAM_INT);
$prepared->execute();
return $prepared->fetchAll();
@ -143,8 +141,7 @@ class PollRepository extends AbstractRepository {
* @param string $mail Email address of the poll admin
* @return array The list of matching polls
*/
public function findAllByAdminMail(string $mail): array
{
public function findAllByAdminMail($mail) {
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('poll') . '` WHERE admin_mail = :admin_mail');
$prepared->execute(['admin_mail' => $mail]);
@ -152,13 +149,12 @@ class PollRepository extends AbstractRepository {
}
/**
* Get the total number of polls in database.
* Get the total number of polls in databse.
*
* @param array|null $search Array of search : ['id'=>..., 'title'=>..., 'name'=>...]
* @param array $search Array of search : ['id'=>..., 'title'=>..., 'name'=>...]
* @return int The number of polls
*/
public function count(array $search = null): int
{
public function count($search = null) {
// Total count
$prepared = $this->prepare('
SELECT count(1) nb
@ -176,7 +172,13 @@ SELECT count(1) nb
$prepared->bindParam(':name', $name, PDO::PARAM_STR);
$prepared->execute();
$count = $prepared->fetch();
return $prepared->fetch()->nb;
/*echo '---';
print_r($count);
echo '---';
exit;*/
return $count->nb;
}
}

View File

@ -31,15 +31,14 @@ class RepositoryFactory {
/**
* @param FramaDB $connect
*/
public static function init(FramaDB $connect): void {
static function init(FramaDB $connect) {
self::$connect = $connect;
}
/**
* @return PollRepository The singleton of PollRepository
*/
public static function pollRepository(): PollRepository
{
static function pollRepository() {
if (self::$pollRepository === null) {
self::$pollRepository = new PollRepository(self::$connect);
}
@ -50,8 +49,7 @@ class RepositoryFactory {
/**
* @return SlotRepository The singleton of SlotRepository
*/
public static function slotRepository(): SlotRepository
{
static function slotRepository() {
if (self::$slotRepository === null) {
self::$slotRepository = new SlotRepository(self::$connect);
}
@ -62,8 +60,7 @@ class RepositoryFactory {
/**
* @return VoteRepository The singleton of VoteRepository
*/
public static function voteRepository(): VoteRepository
{
static function voteRepository() {
if (self::$voteRepository === null) {
self::$voteRepository = new VoteRepository(self::$connect);
}
@ -74,8 +71,7 @@ class RepositoryFactory {
/**
* @return CommentRepository The singleton of CommentRepository
*/
public static function commentRepository(): CommentRepository
{
static function commentRepository() {
if (self::$commentRepository === null) {
self::$commentRepository = new CommentRepository(self::$connect);
}

View File

@ -4,16 +4,16 @@
* 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 Raphael DROZ
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondage: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* 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 Raphael DROZ
* 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\Repositories;
@ -22,14 +22,17 @@ use Framadate\FramaDB;
use Framadate\Utils;
class SlotRepository extends AbstractRepository {
function __construct(FramaDB $connect) {
parent::__construct($connect);
}
/**
* Insert a bulk of slots.
*
* @param string $poll_id
* @param int $poll_id
* @param array $choices
*/
public function insertSlots(string $poll_id, array $choices): void
{
public function insertSlots($poll_id, $choices) {
$prepared = $this->prepare('INSERT INTO `' . Utils::table('slot') . '` (poll_id, title, moments) VALUES (?, ?, ?)');
foreach ($choices as $choice) {
@ -54,10 +57,7 @@ class SlotRepository extends AbstractRepository {
}
}
/**
* @return array|false
*/
public function listByPollId(string $poll_id) {
function listByPollId($poll_id) {
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('slot') . '` WHERE poll_id = ? ORDER BY id');
$prepared->execute([$poll_id]);
@ -67,11 +67,11 @@ class SlotRepository extends AbstractRepository {
/**
* Find the slot into poll for a given datetime.
*
* @param string $poll_id The ID of the poll
* @param $poll_id int The ID of the poll
* @param $datetime int The datetime of the slot
* @return mixed Object The slot found, or null
*/
public function findByPollIdAndDatetime(string $poll_id, $datetime) {
function findByPollIdAndDatetime($poll_id, $datetime) {
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('slot') . '` WHERE poll_id = ? AND SUBSTRING_INDEX(title, \'@\', 1) = ?');
$prepared->execute([$poll_id, $datetime]);
@ -84,13 +84,12 @@ class SlotRepository extends AbstractRepository {
/**
* Insert a new slot into a given poll.
*
* @param string $poll_id The ID of the poll
* @param $poll_id int The ID of the poll
* @param $title mixed The title of the slot
* @param $moments mixed|null The moments joined with ","
* @return bool true if action succeeded
*/
public function insert(string $poll_id, string $title, ?string $moments): bool
{
function insert($poll_id, $title, $moments) {
$prepared = $this->prepare('INSERT INTO `' . Utils::table('slot') . '` (poll_id, title, moments) VALUES (?,?,?)');
return $prepared->execute([$poll_id, $title, $moments]);
@ -99,13 +98,12 @@ class SlotRepository extends AbstractRepository {
/**
* Update a slot into a poll.
*
* @param string $poll_id The ID of the poll
* @param $poll_id int The ID of the poll
* @param $datetime int The datetime of the slot to update
* @param $newMoments mixed The new moments
* @return bool|null true if action succeeded.
*/
public function update(string $poll_id, $datetime, $newMoments): ?bool
{
function update($poll_id, $datetime, $newMoments) {
$prepared = $this->prepare('UPDATE `' . Utils::table('slot') . '` SET moments = ? WHERE poll_id = ? AND title = ?');
return $prepared->execute([$newMoments, $poll_id, $datetime]);
@ -114,17 +112,15 @@ class SlotRepository extends AbstractRepository {
/**
* Delete a entire slot from a poll.
*
* @param string $poll_id int The ID of the poll
* @param $poll_id int The ID of the poll
* @param $datetime mixed The datetime of the slot
*/
public function deleteByDateTime(string $poll_id, $datetime): void
{
function deleteByDateTime($poll_id, $datetime) {
$prepared = $this->prepare('DELETE FROM `' . Utils::table('slot') . '` WHERE poll_id = ? AND title = ?');
$prepared->execute([$poll_id, $datetime]);
}
public function deleteByPollId(string $poll_id): bool
{
function deleteByPollId($poll_id) {
$prepared = $this->prepare('DELETE FROM `' . Utils::table('slot') . '` WHERE poll_id = ?');
return $prepared->execute([$poll_id]);

View File

@ -5,24 +5,24 @@ use Framadate\FramaDB;
use Framadate\Utils;
class VoteRepository extends AbstractRepository {
/**
* @return array|false
*/
public function allUserVotesByPollId(string $poll_id) {
function __construct(FramaDB $connect) {
parent::__construct($connect);
}
function allUserVotesByPollId($poll_id) {
$prepared = $this->prepare('SELECT * FROM `' . Utils::table('vote') . '` WHERE poll_id = ? ORDER BY id');
$prepared->execute([$poll_id]);
return $prepared->fetchAll();
}
public function insertDefault(string $poll_id, int $insert_position): bool
{
function insertDefault($poll_id, $insert_position) {
$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]);
}
public function insert(string $poll_id, string $name, string $choices, string $token): \stdClass {
function insert($poll_id, $name, $choices, $token) {
$prepared = $this->prepare('INSERT INTO `' . Utils::table('vote') . '` (poll_id, name, choices, uniqId) VALUES (?,?,?,?)');
$prepared->execute([$poll_id, $name, $choices, $token]);
@ -36,8 +36,7 @@ class VoteRepository extends AbstractRepository {
return $newVote;
}
public function deleteById(string $poll_id, int $vote_id): bool
{
function deleteById($poll_id, $vote_id) {
$prepared = $this->prepare('DELETE FROM `' . Utils::table('vote') . '` WHERE poll_id = ? AND id = ?');
return $prepared->execute([$poll_id, $vote_id]);
@ -46,11 +45,10 @@ class VoteRepository extends AbstractRepository {
/**
* Delete all votes of a given poll.
*
* @param string $poll_id The ID of the given poll.
* @param $poll_id int The ID of the given poll.
* @return bool|null true if action succeeded.
*/
public function deleteByPollId(string $poll_id): ?bool
{
function deleteByPollId($poll_id) {
$prepared = $this->prepare('DELETE FROM `' . Utils::table('vote') . '` WHERE poll_id = ?');
return $prepared->execute([$poll_id]);
@ -59,19 +57,17 @@ class VoteRepository extends AbstractRepository {
/**
* Delete all votes made on given moment index.
*
* @param string $poll_id The ID of the poll
* @param $poll_id int The ID of the poll
* @param $index int The index of the vote into the poll
* @return bool|null true if action succeeded.
*/
public function deleteByIndex(string $poll_id, int $index): ?bool
{
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]);
}
public function update(string $poll_id, string $vote_id, string $name, $choices): bool
{
function update($poll_id, $vote_id, $name, $choices) {
$prepared = $this->prepare('UPDATE `' . Utils::table('vote') . '` SET choices = ?, name = ? WHERE poll_id = ? AND id = ?');
return $prepared->execute([$choices, $name, $poll_id, $vote_id]);
@ -80,27 +76,25 @@ class VoteRepository extends AbstractRepository {
/**
* Check if name is already used for the given poll.
*
* @param string $poll_id ID of the poll
* @param int $poll_id ID of the poll
* @param string $name Name of the vote
* @return bool true if vote already exists
*/
public function existsByPollIdAndName(string $poll_id, string $name): bool
{
public function existsByPollIdAndName($poll_id, $name) {
$prepared = $this->prepare('SELECT 1 FROM `' . Utils::table('vote') . '` WHERE poll_id = ? AND name = ?');
$prepared->execute([$poll_id, $name]);
return $prepared->rowCount() > 0;
}
/**
* Check if name is already used for the given poll and another vote.
*
* @param string $poll_id ID of the poll
* @param int $poll_id ID of the poll
* @param string $name Name of the vote
* @param int $vote_id ID of the current vote
* @return bool true if vote already exists
*/
public function existsByPollIdAndNameAndVoteId(string $poll_id, string $name, int $vote_id): bool
{
public function existsByPollIdAndNameAndVoteId($poll_id, $name, $vote_id) {
$prepared = $this->prepare('SELECT 1 FROM `' . Utils::table('vote') . '` WHERE poll_id = ? AND name = ? AND id != ?');
$prepared->execute([$poll_id, $name, $vote_id]);
return $prepared->rowCount() > 0;

View File

@ -1,5 +1,6 @@
<?php
namespace Framadate\Security;
/**
@ -16,7 +17,7 @@ class PasswordHasher {
* @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(string $password) {
public static function hash($password) {
return password_hash($password, PASSWORD_DEFAULT);
}
@ -27,8 +28,7 @@ class PasswordHasher {
* @param string $hash the hash to compare.
* @return bool
*/
public static function verify(string $password, string $hash): bool
{
public static function verify($password, $hash) {
return password_verify($password, $hash);
}
}
}

View File

@ -2,35 +2,31 @@
namespace Framadate\Security;
class Token {
public const DEFAULT_LENGTH = 64;
const DEFAULT_LENGTH = 64;
private $time;
private $value;
private $length;
private static $codeAlphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789';
public function __construct($length = self::DEFAULT_LENGTH) {
function __construct($length = self::DEFAULT_LENGTH) {
$this->length = $length;
$this->time = time() + TOKEN_TIME;
$this->value = $this->generate();
}
public function getTime(): int
{
public function getTime() {
return $this->time;
}
public function getValue(): string
{
public function getValue() {
return $this->value;
}
public function isGone(): bool
{
public function isGone() {
return $this->time < time();
}
public function check($value): bool
{
public function check($value) {
return $value === $this->value;
}
@ -41,8 +37,7 @@ class Token {
* @param bool $crypto_strong If passed, tells if the token is "cryptographically strong" or not.
* @return string
*/
public static function getToken(int $length = self::DEFAULT_LENGTH, bool &$crypto_strong = false): string
{
public static function getToken($length = self::DEFAULT_LENGTH, &$crypto_strong = false) {
if (function_exists('openssl_random_pseudo_bytes')) {
openssl_random_pseudo_bytes(1, $crypto_strong); // Fake use to see if the algorithm used was "cryptographically strong"
return self::getSecureToken($length);
@ -50,8 +45,7 @@ class Token {
return self::getUnsecureToken($length);
}
public static function getUnsecureToken(int $length): string
{
public static function getUnsecureToken($length) {
$string = '';
mt_srand();
for ($i = 0; $i < $length; $i++) {
@ -64,8 +58,7 @@ class Token {
/**
* @author http://stackoverflow.com/a/13733588
*/
public static function getSecureToken(int $length): string
{
public static function getSecureToken($length){
$token = "";
for($i=0;$i<$length;$i++){
$token .= self::$codeAlphabet[self::crypto_rand_secure(0,strlen(self::$codeAlphabet))];
@ -73,33 +66,25 @@ class Token {
return $token;
}
private function generate(): string
{
private function generate() {
return self::getToken($this->length);
}
/**
* @author http://us1.php.net/manual/en/function.openssl-random-pseudo-bytes.php#104322
*
* @param int $max
*
* @psalm-param 0 $min
* @psalm-param 0|positive-int $max
*/
private static function crypto_rand_secure(int $min, $max): int {
private static function crypto_rand_secure($min, $max) {
$range = $max - $min;
// not so random...
if ($range < 0) {
return $min;
}
if ($range < 0) return $min; // not so random...
$log = log($range, 2);
$bytes = (int) ($log / 8) + 1; // length in bytes
$bits = (int) $log + 1; // length in bits
$filter = (int) (1 << $bits) - 1; // set all lower bits to 1
do {
$rnd = hexdec(bin2hex(openssl_random_pseudo_bytes($bytes)));
$rnd &= $filter; // discard irrelevant bits
$rnd = $rnd & $filter; // discard irrelevant bits
} while ($rnd >= $range);
return $min + $rnd;
}
}

View File

@ -21,7 +21,7 @@ class AdminPollService {
private $voteRepository;
private $commentRepository;
public function __construct(FramaDB $connect, PollService $pollService, LogService $logService) {
function __construct(FramaDB $connect, PollService $pollService, LogService $logService) {
$this->connect = $connect;
$this->pollService = $pollService;
$this->logService = $logService;
@ -31,38 +31,38 @@ class AdminPollService {
$this->commentRepository = RepositoryFactory::commentRepository();
}
public function updatePoll($poll): bool
{
function updatePoll($poll) {
global $config;
if ($poll->end_date < $poll->creation_date) {
$poll->end_date = $poll->creation_date;
} elseif ($poll->end_date > $this->pollService->maxExpiryDate()) {
$poll->end_date = $this->pollService->maxExpiryDate();
}
$end_date = strtotime($poll->end_date);
return $this->pollRepository->update($poll);
if ($end_date < strtotime($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()));
}
return $this->pollRepository->update($poll);
}
/**
* Delete a comment from a poll.
*
* @param string $poll_id The ID of the poll
* @param $poll_id int The ID of the poll
* @param $comment_id int The ID of the comment
* @return mixed true is action succeeded
*/
public function deleteComment(string $poll_id, int $comment_id) {
function deleteComment($poll_id, $comment_id) {
return $this->commentRepository->deleteById($poll_id, $comment_id);
}
/**
* Remove all comments of a poll.
*
* @param string $poll_id The ID a the poll
* @param $poll_id int The ID a the poll
* @return bool|null true is action succeeded
*/
public function cleanComments(string $poll_id): ?bool
{
function cleanComments($poll_id) {
$this->logService->log("CLEAN_COMMENTS", "id:$poll_id");
return $this->commentRepository->deleteByPollId($poll_id);
}
@ -70,23 +70,21 @@ class AdminPollService {
/**
* Delete a vote from a poll.
*
* @param string $poll_id The ID of the poll
* @param $poll_id int The ID of the poll
* @param $vote_id int The ID of the vote
* @return bool true is action succeeded
* @return mixed true is action succeeded
*/
public function deleteVote(string $poll_id, int $vote_id): bool
{
function deleteVote($poll_id, $vote_id) {
return $this->voteRepository->deleteById($poll_id, $vote_id);
}
/**
* Remove all votes of a poll.
*
* @param string $poll_id The ID of the poll
* @param $poll_id int The ID of the poll
* @return bool|null true is action succeeded
*/
public function cleanVotes(string $poll_id): ?bool
{
function cleanVotes($poll_id) {
$this->logService->log('CLEAN_VOTES', 'id:' . $poll_id);
return $this->voteRepository->deleteByPollId($poll_id);
}
@ -94,11 +92,10 @@ class AdminPollService {
/**
* Delete the entire given poll.
*
* @param $poll_id string The ID of the poll
* @param $poll_id int The ID of the poll
* @return bool true is action succeeded
*/
public function deleteEntirePoll(string $poll_id): bool
{
function deleteEntirePoll($poll_id) {
$poll = $this->pollRepository->findById($poll_id);
$this->logService->log('DELETE_POLL', "id:$poll->id, format:$poll->format, admin:$poll->admin_name, mail:$poll->admin_mail");
@ -118,8 +115,7 @@ class AdminPollService {
* @param object $slot The slot informations (datetime + moment)
* @return bool true if action succeeded
*/
public function deleteDateSlot(object $poll, object $slot): bool
{
public function deleteDateSlot($poll, $slot) {
$this->logService->log('DELETE_SLOT', 'id:' . $poll->id . ', slot:' . json_encode($slot));
$datetime = $slot->title;
@ -130,9 +126,7 @@ class AdminPollService {
// We can't delete the last slot
if ($poll->format === 'D' && count($slots) === 1 && strpos($slots[0]->moments, ',') === false) {
return false;
}
if ($poll->format === 'A' && count($slots) === 1) {
} elseif ($poll->format === 'A' && count($slots) === 1) {
return false;
}
@ -169,8 +163,7 @@ class AdminPollService {
return true;
}
public function deleteClassicSlot($poll, string $slot_title): bool
{
public function deleteClassicSlot($poll, $slot_title) {
$this->logService->log('DELETE_SLOT', 'id:' . $poll->id . ', slot:' . $slot_title);
$slots = $this->pollService->allSlotsByPoll($poll);
@ -206,13 +199,12 @@ class AdminPollService {
* <li>Create a new moment if a slot already exists for the given date</li>
* </ul>
*
* @param string $poll_id The ID of the poll
* @param $poll_id int The ID of the poll
* @param $datetime int The datetime
* @param $new_moment string The moment's name
* @throws MomentAlreadyExistsException When the moment to add already exists in database
*/
public function addDateSlot(string $poll_id, int $datetime, string $new_moment): void
{
public function addDateSlot($poll_id, $datetime, $new_moment) {
$this->logService->log('ADD_COLUMN', 'id:' . $poll_id . ', datetime:' . $datetime . ', moment:' . $new_moment);
$slots = $this->slotRepository->listByPollId($poll_id);
@ -249,18 +241,17 @@ class AdminPollService {
* <li>Create a new slot if no one exists for the given title</li>
* </ul>
*
* @param string $poll_id The ID of the poll
* @param string $title The title
* @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
*/
public function addClassicSlot(string $poll_id, string $title): void
{
public function addClassicSlot($poll_id, $title) {
$this->logService->log('ADD_COLUMN', 'id:' . $poll_id . ', title:' . $title);
$slots = $this->slotRepository->listByPollId($poll_id);
// Check if slot already exists
$titles = array_map(static function ($slot) {
$titles = array_map(function ($slot) {
return $slot->title;
}, $slots);
if (in_array($title, $titles, true)) {
@ -289,7 +280,7 @@ class AdminPollService {
* @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.
*/
private function findInsertPosition(array $slots, int $datetime) {
private function findInsertPosition($slots, $datetime) {
$result = new \stdClass();
$result->slot = null;
$result->insert = 0;
@ -307,15 +298,14 @@ class AdminPollService {
$result->insert += count($moments);
$result->slot = $slot;
break;
}
if ($datetime < $rowDatetime) {
} elseif ($datetime < $rowDatetime) {
// We have to insert before this slot
break;
}
$result->insert += count($moments);
}
$result->insert += count($moments);
}
return $result;
}
}

View File

@ -1,182 +0,0 @@
<?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/OpenSondage: 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\Services;
use DateTime;
use Framadate\Utils;
use Sabre\VObject;
class ICalService {
/**
* Creates an ical-File and initiates the download. If possible, the provided time is used, else an all day event is created.
*/
public function getEvent($poll, string $start_day, string $start_time): void
{
if(!$this->dayIsReadable($start_day)) {
return;
}
$ical_text = "";
$elements = explode("-", $start_time);
$end_time = null;
if(count($elements) === 2) {
$start_time = trim($elements[0]);
$end_time = trim($elements[1]);
}
$start_time = $this->reviseTimeString($start_time);
if($end_time !== null) {
$end_time = $this->reviseTimeString($end_time);
}
if($start_time !== null) {
if($end_time !== null) {
$ical_text = $this->getTimedEvent($poll, $start_day . " " . $start_time, $start_day . " " . $end_time);
} else {
$ical_text = $this->getTimedEvent1Hour($poll, $start_day . " " . $start_time);
}
}
else {
$date = DateTime::createFromFormat('d-m-Y', $start_day);
$day = $date->format('Ymd');
$ical_text = $this->getAllDayEvent($poll, $day);
}
$this->provideFile($poll->title, $ical_text);
}
/**
* Calls getTimedEvent with one hour as a time slot, starting at $start_daytime
*/
public function getTimedEvent1Hour($poll, string $start_daytime): string
{
$end_daytime = date(DATE_ATOM, strtotime('+1 hours', strtotime($start_daytime)));
return $this->getTimedEvent($poll, $start_daytime, $end_daytime);
}
/**
* Generates the text for an ical event including the time
*/
public function getTimedEvent($poll, string $start_daytime, string $end_daytime): string
{
$vcalendar = new VObject\Component\VCalendar([
'VEVENT' => [
'SUMMARY' => $poll->title,
'DESCRIPTION' => $this->stripMD($poll->description),
'DTSTART' => new DateTime($start_daytime),
'DTEND' => new DateTime($end_daytime)
],
'PRODID' => ICAL_PRODID
]);
return $vcalendar->serialize();
}
/**
* Generates the text for an ical event if the time is not known
*/
public function getAllDayEvent($poll, string $day): string
{
$vcalendar = new VObject\Component\VCalendar();
$vevent = $vcalendar->add('VEVENT');
$vevent->add('SUMMARY', $poll->title);
$vevent->add('DESCRIPTION', $this->stripMD($poll->description));
$dtstart = $vevent->add('DTSTART', $day);
$dtstart['VALUE'] = 'DATE';
unset($vcalendar->PRODID);
$vcalendar->add('PRODID', ICAL_PRODID);
return $vcalendar->serialize();
}
/**
* Creates a file and initiates the download
* @param string $title
* @param string $ical_text
*/
public function provideFile(string $title, string $ical_text): void
{
header('Content-Description: File Transfer');
header('Content-Disposition: attachment; filename=' . $this->stripTitle($title) . ICAL_ENDING);
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header("Content-Type: text/calendar");
echo $ical_text;
exit;
}
/**
* Reformats a string value into a time readable by DateTime
* @param string $time
* @return string the corrected value, null if the format is unknown
*/
public function reviseTimeString(string $time): ?string
{
// 24-hour clock / international format
if (preg_match('/^\d\d(:)\d\d$/', $time)) {
return $time;
}
// 12-hour clock / using am and pm
if (preg_match('/^\d[0-2]?:?\d{0,2}\s?[aApP][mM]$/', $time)) {
return $this->formatTime($time);
}
// french format HHhMM or HHh
if (preg_match('/^\d\d?[hH]\d?\d?$/', $time)) {
return $this->formatTime(str_pad(str_ireplace("H", ":", $time), 5, "0"));
}
// Number only
if (preg_match('/^\d{1,4}$/', $time)) {
return $this->formatTime(str_pad(str_pad($time, 2, "0", STR_PAD_LEFT), 4, "0"));
}
return null;
}
/**
* @param string $day
* @return false|int 1 if the day string can be parsed, 0 if not and false if an error occured
*/
public function dayIsReadable(string $day) {
return preg_match('/^\d{2}-\d{2}-\d{4}$/', $day);
}
/**
* @param string $time
* @return string date string in format H:i (e.g. 19:00)
*/
public function formatTime(string $time): string
{
return date("H:i", strtotime($time));
}
/**
* Converts MD Code to HTML, then strips HTML away
*/
public function stripMD(string $string): string
{
return strip_tags(Utils::markdown($string));
}
/**
* Strips a string so it's usable as a file name (only digits, letters and underline allowed)
*
* @return null|string
*/
public function stripTitle(string $string): ?string {
return preg_replace('/[^a-z0-9_]+/', '-', strtolower($string));
}
}

View File

@ -17,28 +17,25 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Services;
use function __;
use DateTime;
use Egulias\EmailValidator\EmailValidator;
use Egulias\EmailValidator\Validation\RFCValidation;
use o80\i18n\CantLoadDictionaryException;
/**
* This class helps to clean all inputs from the users or external services.
*/
class InputService {
public function __construct() {}
function __construct() {}
/**
* This method filter an array calling "filter_var" on each items.
* Only items validated are added at their own indexes, the others are not returned.
* @param array $arr The array to filter
* @param int $type The type of filter to apply
* @param array|int $options The associative array of options
* @param array|null $options The associative array of options
* @return array The filtered array
*/
public function filterArray(array $arr, int $type, $options = 0): array
{
function filterArray(array $arr, $type, $options = null) {
$newArr = [];
foreach($arr as $id=>$item) {
@ -51,32 +48,24 @@ class InputService {
return $newArr;
}
public function filterAllowedValues($value, array $allowedValues) {
function filterAllowedValues($value, array $allowedValues) {
return in_array($value, $allowedValues, true) ? $value : null;
}
public function filterTitle($title): ?string
{
public function filterTitle($title) {
return $this->returnIfNotBlank($title);
}
/**
* @return false|string
*/
public function filterId($id) {
$filtered = filter_var($id, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => POLL_REGEX]]);
return $filtered ? substr($filtered, 0, 64) : false;
}
public function filterName($name): ?string
{
public function filterName($name) {
$filtered = trim($name);
return $this->returnIfNotBlank($filtered);
}
/**
* @return false|string
*/
public function filterMail($mail) {
///////////////////////////////////////////////////////////////////////////////////////
// formatting
@ -100,67 +89,40 @@ class InputService {
return $resultat;
}
public function filterDescription($description): string {
return str_replace("\r\n", "\n", $description);
public function filterDescription($description) {
$description = str_replace("\r\n", "\n", $description);
return $description;
}
/**
* @return false|string
*/
public function filterMD5($control) {
return filter_var($control, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => MD5_REGEX]]);
}
/**
* @return false|int
*/
public function filterInteger($int) {
return filter_var($int, FILTER_VALIDATE_INT);
}
/**
* @return false|int
*/
public function filterValueMax($int)
{
return $this->filterInteger($int) >= 1 ? $this->filterInteger($int) : false;
}
public function filterBoolean($boolean): bool
{
return (bool)filter_var($boolean, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => BOOLEAN_TRUE_REGEX]]);
public function filterBoolean($boolean) {
return !!filter_var($boolean, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => BOOLEAN_TRUE_REGEX]]);
}
/**
* @return false|string
*/
public function filterEditable($editable) {
return filter_var($editable, FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => EDITABLE_CHOICE_REGEX]]);
}
public function filterComment($comment): ?string
{
public function filterComment($comment) {
$comment = str_replace("\r\n", "\n", $comment);
return $this->returnIfNotBlank($comment);
}
public function validateDate(DateTime $date, DateTime $minDate, DateTime $maxDate): DateTime {
if ($date < $minDate) {
return $minDate;
}
if ($maxDate < $date) {
return $maxDate;
}
return $date;
}
/**
* @throws CantLoadDictionaryException
* @return DateTime|false
*/
public function parseDate(string $date) {
return DateTime::createFromFormat(__('Date', 'datetime_parseformat'), $date)->setTime(0, 0);
public function filterDate($date) {
$dDate = DateTime::createFromFormat(__('Date', 'datetime_parseformat'), $date)->setTime(0, 0, 0);
return $dDate->format('Y-m-d H:i:s');
}
/**
@ -169,8 +131,7 @@ class InputService {
* @param string $filtered The value
* @return string|null
*/
private function returnIfNotBlank(string $filtered): ?string
{
private function returnIfNotBlank($filtered) {
if ($filtered) {
$withoutSpaces = str_replace(' ', '', $filtered);
if (!empty($withoutSpaces)) {

View File

@ -17,10 +17,7 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Services;
use function __f;
use Exception;
use Framadate\Utils;
use PDO;
use Smarty;
/**
@ -43,17 +40,15 @@ class InstallService {
'migrationTable' => 'framadate_migration'
];
public function __construct() {}
function __construct() {}
public function updateFields($data): void
{
public function updateFields($data) {
foreach ($data as $field => $value) {
$this->fields[$field] = $value;
}
}
public function install(Smarty &$smarty): array
{
public function install(Smarty &$smarty) {
// Check values are present
if (empty($this->fields['appName']) || empty($this->fields['appMail']) || empty($this->fields['defaultLanguage']) || empty($this->fields['dbConnectionString']) || empty($this->fields['dbUser'])) {
return $this->error('MISSING_VALUES');
@ -62,7 +57,7 @@ class InstallService {
// Connect to database
try {
$connect = $this->connectTo($this->fields['dbConnectionString'], $this->fields['dbUser'], $this->fields['dbPassword']);
} catch(Exception $e) {
} catch(\Exception $e) {
return $this->error('CANT_CONNECT_TO_DATABASE', $e->getMessage());
}
@ -80,20 +75,16 @@ class InstallService {
* @param string $connectionString
* @param string $user
* @param string $password
* @return PDO
* @return \PDO
*/
public function connectTo(string $connectionString, string $user, string $password): PDO
{
$pdo = @new PDO($connectionString, $user, $password);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
function connectTo($connectionString, $user, $password) {
$pdo = @new \PDO($connectionString, $user, $password);
$pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_OBJ);
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
return $pdo;
}
/**
* @return false|int
*/
public function writeConfiguration(Smarty &$smarty) {
function writeConfiguration(Smarty &$smarty) {
foreach($this->fields as $field=>$value) {
$smarty->assign($field, $value);
}
@ -105,17 +96,15 @@ class InstallService {
/**
* @param $content
* @return false|int
*/
public function writeToFile(string $content) {
function writeToFile($content) {
return @file_put_contents(CONF_FILENAME, $content);
}
/**
* @return array
*/
public function ok(): array
{
function ok() {
return [
'status' => 'OK',
'msg' => __f('Installation', 'Ended', Utils::get_server_name())
@ -123,12 +112,10 @@ class InstallService {
}
/**
* @param string $msg
* @param string $details
* @param $msg
* @return array
*/
public function error(string $msg, string $details = ''): array
{
function error($msg, $details = '') {
return [
'status' => 'ERROR',
'code' => $msg,
@ -136,8 +123,7 @@ class InstallService {
];
}
public function getFields(): array
{
public function getFields() {
return $this->fields;
}
}

View File

@ -7,7 +7,7 @@ namespace Framadate\Services;
* @package Framadate\Services
*/
class LogService {
public function __construct() {
function __construct() {
}
/**
@ -16,8 +16,8 @@ class LogService {
* @param $tag string A tag is used to quickly found a message when reading log file
* @param $message string some message
*/
public function log(string $tag, string $message): void
{
function log($tag, $message) {
error_log(date('Ymd His') . ' [' . $tag . '] ' . $message . "\n", 3, ROOT_DIR . LOG_FILE);
}
}

View File

@ -1,13 +1,12 @@
<?php
namespace Framadate\Services;
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\PHPMailer;
class MailService {
public const DELAY_BEFORE_RESEND = 300;
const DELAY_BEFORE_RESEND = 300;
public const MAILSERVICE_KEY = 'mailservice';
const MAILSERVICE_KEY = 'mailservice';
private $smtp_allowed;
@ -15,7 +14,7 @@ class MailService {
private $logService;
public function __construct($smtp_allowed, $smtp_options = []) {
function __construct($smtp_allowed, $smtp_options = []) {
$this->logService = new LogService();
$this->smtp_allowed = $smtp_allowed;
if (true === is_array($smtp_options)) {
@ -23,18 +22,11 @@ class MailService {
}
}
/**
* @return false|string
*/
public function isValidEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL);
}
/**
* @throws Exception
*/
public function send(string $to, string $subject, string $body, ?string $msgKey = null): void
{
public function send($to, $subject, $body, $msgKey = null) {
if ($this->smtp_allowed === true && $this->canSendMsg($msgKey)) {
$mail = new PHPMailer(true);
$this->configureMailer($mail);
@ -53,7 +45,7 @@ class MailService {
$mail->Subject = $subject;
// Bodies
$body .= ' <br/><br/>' . __('Mail', 'Thanks for your trust.') . ' <br/>' . NOMAPPLICATION . ' <hr/>' . __('Mail', 'FOOTER');
$body = $body . ' <br/><br/>' . __('Mail', 'Thanks for your trust.') . ' <br/>' . NOMAPPLICATION . ' <hr/>' . __('Mail', 'FOOTER');
$mail->isHTML(true);
$mail->msgHTML($body, ROOT_DIR, true);
@ -61,7 +53,6 @@ class MailService {
$mail->CharSet = 'UTF-8';
$mail->addCustomHeader('Auto-Submitted', 'auto-generated');
$mail->addCustomHeader('Return-Path', '<>');
$mail->XMailer = ' ';
// Send mail
$mail->send();
@ -70,25 +61,19 @@ class MailService {
$this->logService->log('MAIL', 'Mail sent to: ' . $to . ', key: ' . $msgKey);
// Store the mail sending date
$this->initializeSession();
$_SESSION[self::MAILSERVICE_KEY][$msgKey] = time();
}
}
public function canSendMsg(?string $msgKey): bool
{
public function canSendMsg($msgKey) {
if ($msgKey === null) {
return true;
}
$this->initializeSession();
return !isset($_SESSION[self::MAILSERVICE_KEY][$msgKey]) || time() - $_SESSION[self::MAILSERVICE_KEY][$msgKey] > self::DELAY_BEFORE_RESEND;
}
private function initializeSession(): void {
if (!isset($_SESSION[self::MAILSERVICE_KEY])) {
$_SESSION[self::MAILSERVICE_KEY] = [];
}
return !isset($_SESSION[self::MAILSERVICE_KEY][$msgKey]) || time() - $_SESSION[self::MAILSERVICE_KEY][$msgKey] > self::DELAY_BEFORE_RESEND;
}
/**
@ -96,14 +81,12 @@ class MailService {
*
* @param PHPMailer $mailer
*/
private function configureMailer(PHPMailer $mailer): void
{
private function configureMailer(PHPMailer $mailer) {
$mailer->isSMTP();
$available_options = [
'host' => 'Host',
'auth' => 'SMTPAuth',
'authtype' => 'AuthType',
'username' => 'Username',
'password' => 'Password',
'secure' => 'SMTPSecure',

View File

@ -1,24 +1,22 @@
<?php
namespace Framadate\Services;
use \stdClass;
use function __;
use function __f;
use Framadate\Services\MailService;
use Framadate\Utils;
use o80\i18n\CantLoadDictionaryException;
use PHPMailer\PHPMailer\Exception;
class NotificationService {
public const UPDATE_VOTE = 1;
public const ADD_VOTE = 2;
public const ADD_COMMENT = 3;
public const UPDATE_POLL = 10;
public const DELETED_POLL = 11;
const UPDATE_VOTE = 1;
const ADD_VOTE = 2;
const ADD_COMMENT = 3;
const UPDATE_POLL = 10;
const DELETED_POLL = 11;
private $mailService;
public function __construct(MailService $mailService) {
function __construct(MailService $mailService) {
$this->mailService = $mailService;
}
@ -28,10 +26,8 @@ class NotificationService {
* @param $poll stdClass The poll
* @param $name string The name user who triggered the notification
* @param $type int cf: Constants on the top of this page
* @throws Exception|CantLoadDictionaryException
*/
public function sendUpdateNotification($poll, int $type, string $name=''): void
{
function sendUpdateNotification(stdClass $poll, $type, $name='') {
if (!isset($_SESSION['mail_sent'])) {
$_SESSION['mail_sent'] = [];
}
@ -41,7 +37,7 @@ class NotificationService {
$isOtherType = $type !== self::UPDATE_VOTE && $type !== self::ADD_VOTE && $type !== self::ADD_COMMENT;
if ($isVoteAndCanSendIt || $isCommentAndCanSendIt || $isOtherType) {
if ($this->isParticipation($type)) {
if (self::isParticipation($type)) {
$translationString = 'Poll\'s participation: %s';
} else {
$translationString = 'Notification of poll: %s';
@ -79,13 +75,11 @@ class NotificationService {
}
$messageTypeKey = $type . '-' . $poll->id;
if ($poll->admin_mail) {
$this->mailService->send($poll->admin_mail, $subject, $message, $messageTypeKey);
}
$this->mailService->send($poll->admin_mail, $subject, $message, $messageTypeKey);
}
}
public function isParticipation(int $type): bool
function isParticipation($type)
{
return $type >= self::UPDATE_POLL;
}

View File

@ -18,19 +18,16 @@
*/
namespace Framadate\Services;
use DateInterval;
use DateTime;
use Exception;
use Framadate\Exception\AlreadyExistsException;
use Framadate\Exception\ConcurrentEditionException;
use Framadate\Exception\ConcurrentVoteException;
use Framadate\Exception\PollNotFoundException;
use Framadate\Form;
use Framadate\FramaDB;
use Framadate\Repositories\RepositoryFactory;
use Framadate\Security\Token;
use stdClass;
class PollService {
private $connect;
private $logService;
private $pollRepository;
@ -38,7 +35,8 @@ class PollService {
private $voteRepository;
private $commentRepository;
public function __construct(LogService $logService) {
function __construct(FramaDB $connect, LogService $logService) {
$this->connect = $connect;
$this->logService = $logService;
$this->pollRepository = RepositoryFactory::pollRepository();
$this->slotRepository = RepositoryFactory::slotRepository();
@ -49,10 +47,10 @@ class PollService {
/**
* Find a poll from its ID.
*
* @param string $poll_id The ID of the poll
* @return stdClass|null The found poll, or null
* @param $poll_id int The ID of the poll
* @return \stdClass|null The found poll, or null
*/
public function findById(string $poll_id) {
function findById($poll_id) {
if (preg_match(POLL_REGEX, $poll_id)) {
return $this->pollRepository->findById($poll_id);
}
@ -60,7 +58,7 @@ class PollService {
return null;
}
public function findByAdminId(string $admin_poll_id) {
public function findByAdminId($admin_poll_id) {
if (preg_match(ADMIN_POLL_REGEX, $admin_poll_id)) {
return $this->pollRepository->findByAdminId($admin_poll_id);
}
@ -68,15 +66,15 @@ class PollService {
return null;
}
public function allCommentsByPollId(string $poll_id) {
function allCommentsByPollId($poll_id) {
return $this->commentRepository->findAllByPollId($poll_id);
}
public function allVotesByPollId(string $poll_id) {
function allVotesByPollId($poll_id) {
return $this->voteRepository->allUserVotesByPollId($poll_id);
}
public function allSlotsByPoll(stdClass $poll) {
function allSlotsByPoll($poll) {
$slots = $this->slotRepository->listByPollId($poll->id);
if ($poll->format === 'D') {
$this->sortSlorts($slots);
@ -85,49 +83,48 @@ class PollService {
}
/**
* @param string $poll_id
* @param int $vote_id
* @param string $name
* @param array $choices
* @param string $slots_hash
* @param $poll_id
* @param $vote_id
* @param $name
* @param $choices
* @param $slots_hash
* @throws AlreadyExistsException
* @throws ConcurrentEditionException
* @throws ConcurrentVoteException
* @return bool
*/
public function updateVote(string $poll_id, int $vote_id, string $name, array $choices, string $slots_hash): bool
{
public function updateVote($poll_id, $vote_id, $name, $choices, $slots_hash) {
$this->checkVoteConstraints($choices, $poll_id, $slots_hash, $name, $vote_id);
// Update vote
return $this->voteRepository->update($poll_id, $vote_id, $name, implode($choices));
$choices = implode($choices);
return $this->voteRepository->update($poll_id, $vote_id, $name, $choices);
}
/**
* @param string $poll_id
* @param string $name
* @param array $choices
* @param string $slots_hash
* @param $poll_id
* @param $name
* @param $choices
* @param $slots_hash
* @throws AlreadyExistsException
* @throws ConcurrentEditionException
* @throws ConcurrentVoteException
* @throws PollNotFoundException
* @throws AlreadyExistsException
* @return stdClass
* @return \stdClass
*/
public function addVote(string $poll_id, string $name, array $choices, string $slots_hash): stdClass
{
function addVote($poll_id, $name, $choices, $slots_hash) {
$this->checkVoteConstraints($choices, $poll_id, $slots_hash, $name);
// Insert new vote
return $this->voteRepository->insert($poll_id, $name, implode($choices), $this->random(16));
$choices = implode($choices);
$token = $this->random(16);
return $this->voteRepository->insert($poll_id, $name, $choices, $token);
}
public function addComment($poll_id, $name, $comment): bool
{
function addComment($poll_id, $name, $comment) {
if ($this->commentRepository->exists($poll_id, $name, $comment)) {
return true;
}
return $this->commentRepository->insert($poll_id, $name, $comment);
}
@ -135,8 +132,7 @@ class PollService {
* @param Form $form
* @return array
*/
public function createPoll(Form $form): array
{
function createPoll(Form $form) {
// Generate poll IDs, loop while poll ID already exists
if (empty($form->id)) { // User want us to generate an id for him
@ -162,18 +158,16 @@ class PollService {
return [$poll_id, $admin_poll_id];
}
public function findAllByAdminMail($mail): array
{
public function findAllByAdminMail($mail) {
return $this->pollRepository->findAllByAdminMail($mail);
}
/**
* @param array $votes
* @param stdClass $poll
* @param \stdClass $poll
* @return array
*/
public function computeBestChoices(array $votes, $poll): array
{
public function computeBestChoices($votes, $poll) {
if (0 === count($votes)) {
return $this->computeEmptyBestChoices($poll);
}
@ -199,11 +193,10 @@ class PollService {
return $result;
}
public function splitSlots($slots): array
{
function splitSlots($slots) {
$splitted = [];
foreach ($slots as $slot) {
$obj = new stdClass();
$obj = new \stdClass();
$obj->day = $slot->title;
$obj->moments = explode(',', $slot->moments);
@ -217,18 +210,16 @@ class PollService {
* @param $slots array The slots to hash
* @return string The hash
*/
public function hashSlots(array $slots): string
{
return md5(array_reduce($slots, static function($carry, $item) {
public function hashSlots($slots) {
return md5(array_reduce($slots, function($carry, $item) {
return $carry . $item->id . '@' . $item->moments . ';';
}));
}
public function splitVotes(array $votes): array
{
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;
@ -241,40 +232,35 @@ class PollService {
}
/**
* @throws Exception
* @return DateTime The max date allowed for expiry date
* @return int The max timestamp allowed for expiry date
*/
public function maxExpiryDate(): DateTime {
public function maxExpiryDate() {
global $config;
return (new DateTime())->add(new DateInterval('P' . $config['default_poll_duration'] . 'D'));
return time() + (86400 * $config['default_poll_duration']);
}
/**
* @return DateTime The min date allowed for expiry date
* @return int The min timestamp allowed for expiry date
*/
public function minExpiryDate(): DateTime
{
return (new DateTime())->add(new DateInterval('P1D'));
public function minExpiryDate() {
return time() + 86400;
}
/**
* @return mixed
*/
public function sortSlorts(array &$slots): array {
uasort($slots, static function ($a, $b) {
if ($a->title === $b->title) {
return 0;
}
return $a->title > $b->title ? 1 : -1;
public function sortSlorts(&$slots) {
uasort($slots, function ($a, $b) {
return $a->title > $b->title;
});
return $slots;
}
/**
* @param stdClass $poll
* @param \stdClass $poll
* @return array
*/
private function computeEmptyBestChoices($poll): array
private function computeEmptyBestChoices($poll)
{
$result = ['y' => [], 'inb' => []];
// if there is no votes, calculates the number of slot
@ -284,7 +270,7 @@ class PollService {
if ($poll->format === 'A') {
// poll format classic
for ($i = 0, $iMax = count($slots); $i < $iMax; $i++) {
for ($i = 0; $i < count($slots); $i++) {
$result['y'][] = 0;
$result['inb'][] = 0;
}
@ -294,7 +280,7 @@ class PollService {
$slots = $this->splitSlots($slots);
foreach ($slots as $slot) {
for ($i = 0, $iMax = count($slot->moments); $i < $iMax; $i++) {
for ($i = 0; $i < count($slot->moments); $i++) {
$result['y'][] = 0;
$result['inb'][] = 0;
}
@ -303,48 +289,41 @@ class PollService {
return $result;
}
private function random(int $length): string
{
private function random($length) {
return Token::getToken($length);
}
/**
* @param array $choices
* @param string $poll_id
* @param string $slots_hash
* @param string $name
* @param bool|int $vote_id
* @param $choices
* @param $poll_id
* @param $slots_hash
* @param $name
* @param string $vote_id
* @throws AlreadyExistsException
* @throws ConcurrentEditionException
* @throws ConcurrentVoteException
* @throws PollNotFoundException
* @throws ConcurrentEditionException
*/
private function checkVoteConstraints(array $choices, string $poll_id, string $slots_hash, string $name, $vote_id = false): void
{
private function checkVoteConstraints($choices, $poll_id, $slots_hash, $name, $vote_id = FALSE) {
// Check if vote already exists with the same name
if (false === $vote_id) {
if (FALSE === $vote_id) {
$exists = $this->voteRepository->existsByPollIdAndName($poll_id, $name);
} else {
$exists = $this->voteRepository->existsByPollIdAndNameAndVoteId($poll_id, $name, $vote_id);
}
if ($exists) {
throw new AlreadyExistsException();
}
$poll = $this->findById($poll_id);
if (!$poll) {
throw new PollNotFoundException();
}
// Check that no-one voted in the meantime and it conflicts the maximum votes constraint
$this->checkMaxVotes($choices, $poll, $poll_id);
// Check if slots are still the same
$this->checkThatSlotsDidntChanged($poll, $slots_hash);
}
/**
* This method checks if the hash send by the user is the same as the computed hash.
*
@ -352,8 +331,7 @@ class PollService {
* @param $slots_hash string The hash sent by the user
* @throws ConcurrentEditionException Thrown when hashes are differents
*/
private function checkThatSlotsDidntChanged(stdClass $poll, string $slots_hash): void
{
private function checkThatSlotsDidntChanged($poll, $slots_hash) {
$slots = $this->allSlotsByPoll($poll);
if ($slots_hash !== $this->hashSlots($slots)) {
throw new ConcurrentEditionException();
@ -364,12 +342,11 @@ 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
*/
private function checkMaxVotes(array $user_choice, $poll, string $poll_id): void
{
private function checkMaxVotes($user_choice, $poll, $poll_id) {
$votes = $this->allVotesByPollId($poll_id);
if (count($votes) <= 0) {
return;

View File

@ -15,7 +15,7 @@ class PurgeService {
private $voteRepository;
private $commentRepository;
public function __construct(LogService $logService) {
function __construct(FramaDB $connect, LogService $logService) {
$this->logService = $logService;
$this->pollRepository = RepositoryFactory::pollRepository();
$this->slotRepository = RepositoryFactory::slotRepository();
@ -26,10 +26,9 @@ class PurgeService {
/**
* This methode purges all old polls (the ones with end_date in past).
*
* @return int number of purged polls
* @return bool true is action succeeded
*/
public function purgeOldPolls(): int
{
function purgeOldPolls() {
$oldPolls = $this->pollRepository->findOldPolls();
$count = count($oldPolls);
@ -51,11 +50,10 @@ class PurgeService {
/**
* This methode delete all data about a poll.
*
* @param string $poll_id The ID of the poll
* @param $poll_id int The ID of the poll
* @return bool true is action succeeded
*/
public function purgePollById(string $poll_id): bool
{
function purgePollById($poll_id) {
$done = true;
$this->pollRepository->beginTransaction();
@ -73,3 +71,4 @@ class PurgeService {
return $done;
}
}

View File

@ -5,7 +5,7 @@ use Framadate\Security\PasswordHasher;
use Framadate\Security\Token;
class SecurityService {
public function __construct() {
function __construct() {
}
/**
@ -18,10 +18,9 @@ class SecurityService {
* </ul>
*
* @param $tokan_name string The name of the CSRF token
* @return string The token
* @return Token The token
*/
function getToken(string $tokan_name): string
{
function getToken($tokan_name) {
if (!isset($_SESSION['tokens'])) {
$_SESSION['tokens'] = [];
}
@ -39,8 +38,7 @@ class SecurityService {
* @param $csrf string Value to check
* @return bool true if the token is well checked
*/
public function checkCsrf(string $tokan_name, string $csrf): bool
{
public function checkCsrf($tokan_name, $csrf) {
$checked = $_SESSION['tokens'][$tokan_name]->getValue() === $csrf;
if($checked) {
@ -56,18 +54,17 @@ class SecurityService {
* @param $poll \stdClass The poll which we seek access
* @return bool true if the current session can access this poll
*/
public function canAccessPoll($poll): bool
{
public function canAccessPoll($poll) {
if (is_null($poll->password_hash)) {
return true;
}
$this->ensureSessionPollSecurityIsCreated();
$currentPassword = $_SESSION['poll_security'][$poll->id] ?? null;
$currentPassword = isset($_SESSION['poll_security'][$poll->id]) ? $_SESSION['poll_security'][$poll->id] : null;
if (!empty($currentPassword) && PasswordHasher::verify($currentPassword, $poll->password_hash)) {
return true;
}
}
unset($_SESSION['poll_security'][$poll->id]);
return false;
}
@ -78,18 +75,17 @@ class SecurityService {
* @param $poll \stdClass The poll which we seek access
* @param $password string the password to compare
*/
public function submitPollAccess($poll, string $password): void
{
public function submitPollAccess($poll, $password) {
if (!empty($password)) {
$this->ensureSessionPollSecurityIsCreated();
$_SESSION['poll_security'][$poll->id] = $password;
}
}
private function ensureSessionPollSecurityIsCreated(): void
{
private function ensureSessionPollSecurityIsCreated() {
if (!isset($_SESSION['poll_security'])) {
$_SESSION['poll_security'] = [];
}
}
}

View File

@ -1,5 +1,6 @@
<?php
namespace Framadate\Services;
class SessionService {
@ -17,7 +18,12 @@ class SessionService {
$this->initSectionIfNeeded($section);
return $_SESSION[$section][$key] ?? $defaultValue;
$returnValue = $defaultValue;
if (isset($_SESSION[$section][$key])) {
$returnValue = $_SESSION[$section][$key];
}
return $returnValue;
}
/**
@ -27,8 +33,7 @@ class SessionService {
* @param $key
* @param $value
*/
public function set($section, $key, $value): void
{
public function set($section, $key, $value) {
assert(!empty($key));
assert(!empty($section));
@ -43,18 +48,16 @@ class SessionService {
* @param $section
* @param $key
*/
public function remove($section, $key): void
{
public function remove($section, $key) {
assert(!empty($key));
assert(!empty($section));
unset($_SESSION[$section][$key]);
}
private function initSectionIfNeeded($section): void
{
private function initSectionIfNeeded($section) {
if (!isset($_SESSION[$section])) {
$_SESSION[$section] = [];
}
}
}
}

View File

@ -11,7 +11,7 @@ use Framadate\Repositories\RepositoryFactory;
class SuperAdminService {
private $pollRepository;
public function __construct() {
function __construct() {
$this->pollRepository = RepositoryFactory::pollRepository();
}
@ -23,8 +23,7 @@ class SuperAdminService {
* @param int $limit The limit size
* @return array ['polls' => The {$limit} polls, 'count' => Entries found by the query, 'total' => Total count]
*/
public function findAllPolls(array $search, int $page, int $limit): array
{
public function findAllPolls($search, $page, $limit) {
$start = $page * $limit;
$polls = $this->pollRepository->findAll($search, $start, $limit);
$count = $this->pollRepository->count($search);
@ -33,3 +32,4 @@ class SuperAdminService {
return ['polls' => $polls, 'count' => $count, 'total' => $total];
}
}

View File

@ -24,13 +24,13 @@ class Utils {
/**
* @return string Server name
*/
public static function get_server_name(): string
{
public static function get_server_name() {
$scheme = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https')) ? 'https' : 'http';
$port = in_array($_SERVER['SERVER_PORT'], ['80', '443'], true) ? '' : ':' . $_SERVER['SERVER_PORT'];
$dirname = dirname($_SERVER['SCRIPT_NAME']);
$dirname = $dirname === '\\' ? '/' : $dirname . '/';
$dirname = str_replace(['/admin', '/action'], '', $dirname);
$dirname = str_replace('/admin', '', $dirname);
$dirname = str_replace('/action', '', $dirname);
$server_name = (defined('APP_URL') ? APP_URL : $_SERVER['SERVER_NAME']) . $port . $dirname;
return $scheme . '://' . preg_replace('#//+#', '/', $server_name);
@ -38,10 +38,9 @@ class Utils {
/**
* @param string $title
*
* @deprecated
*/
public static function print_header($title = ''): void {
public static function print_header($title = '') {
global $locale;
echo '<!DOCTYPE html>
@ -61,17 +60,17 @@ class Utils {
<link rel="stylesheet" href="' . self::get_server_name() . 'css/style.css" />
<link rel="stylesheet" href="' . self::get_server_name() . 'css/frama.css" />
<link rel="stylesheet" href="' . self::get_server_name() . 'css/print.css" media="print" />
<script src="' . self::get_server_name() . 'js/jquery-3.6.0.min.js"></script>
<script src="' . self::get_server_name() . 'js/bootstrap.min.js"></script>
<script src="' . self::get_server_name() . 'js/bootstrap-datepicker.js"></script>';
<script type="text/javascript" src="' . self::get_server_name() . 'js/jquery-1.12.4.min.js"></script>
<script type="text/javascript" src="' . self::get_server_name() . 'js/bootstrap.min.js"></script>
<script type="text/javascript" src="' . self::get_server_name() . 'js/bootstrap-datepicker.js"></script>';
if ('en' !== $locale) {
echo '
<script src="' . self::get_server_name() . 'js/locales/bootstrap-datepicker.' . $locale . '.js"></script>';
<script type="text/javascript" src="' . self::get_server_name() . 'js/locales/bootstrap-datepicker.' . $locale . '.js"></script>';
}
echo '
<script src="' . self::get_server_name() . 'js/core.js"></script>';
<script type="text/javascript" src="' . self::get_server_name() . 'js/core.js"></script>';
if (is_file($_SERVER['DOCUMENT_ROOT'] . "/nav/nav.js")) {
echo '<script src="/nav/nav.js" id="nav_js" charset="utf-8"></script><!-- /Framanav -->';
echo '<script src="/nav/nav.js" id="nav_js" type="text/javascript" charset="utf-8"></script><!-- /Framanav -->';
}
echo '
@ -82,17 +81,16 @@ class Utils {
/**
* Function allowing to generate poll's url
* @param string $id The poll's id
* @param bool $admin True to generate an admin URL, false for a public one
* @param string $vote_id (optional) The vote's unique id
* @param string|null $action
* @param string|null $action_value
* @return string The poll's URL.
* @param string $id The poll's id
* @param bool $admin True to generate an admin URL, false for a public one
* @param string $vote_id (optional) The vote's unique id
* @param null $action
* @param null $action_value
* @return string The poll's URL.
*/
public static function getUrlSondage(string $id, bool $admin = false, string $vote_id = '', string $action = null, string $action_value = null): string
{
public static function getUrlSondage($id, $admin = false, $vote_id = '', $action = null, $action_value = null) {
// URL-Encode $action_value
$action_value = $action_value ? self::base64url_encode($action_value) : null;
$action_value = $action_value ? Utils::base64url_encode($action_value) : null;
if (URL_PROPRE) {
if ($admin === true) {
@ -134,20 +132,17 @@ class Utils {
*
* @param mixed $object The object to print.
*/
public static function debug($object): void
{
public static function debug($object) {
echo '<pre>';
print_r($object);
echo '</pre>';
}
public static function table(string $tableName): string
{
public static function table($tableName) {
return TABLENAME_PREFIX . $tableName;
}
public static function markdown(string $md, bool $clear=false, bool $line=true): string
{
public static function markdown($md, $clear=false, $line=true) {
$parseDown = new Parsedown();
$parseDown
@ -160,7 +155,7 @@ class Utils {
} else {
$md = preg_replace_callback(
'#( ){2,}#',
static function ($m) {
function ($m) {
return str_repeat('&nbsp;', strlen($m[0]));
},
$md
@ -173,38 +168,39 @@ class Utils {
return $clear ? $text : $html;
}
public static function htmlEscape(string $html): string {
public static function htmlEscape($html) {
return htmlentities($html, ENT_HTML5 | ENT_QUOTES);
}
public static function htmlMailEscape(string $html): string
{
public static function htmlMailEscape($html) {
return htmlspecialchars($html, ENT_HTML5 | ENT_QUOTES);
}
public static function csvEscape(string $text): string
{
$escaped = str_replace(['"', "\r\n", "\n"], ['""', '', ''], $text);
public static function csvEscape($text) {
$escaped = str_replace('"', '""', $text);
$escaped = str_replace("\r\n", '', $escaped);
$escaped = str_replace("\n", '', $escaped);
$escaped = preg_replace("/^(=|\+|\-|\@)/", "'$1", $escaped);
return '"' . $escaped . '"';
}
public static function cleanFilename(string $title): string {
public static function cleanFilename($title) {
$cleaned = preg_replace('[^a-zA-Z0-9._-]', '_', $title);
return preg_replace(' {2,}', ' ', $cleaned);
$cleaned = preg_replace(' {2,}', ' ', $cleaned);
return $cleaned;
}
public static function fromPostOrDefault(string $postKey, ?string $default = '') {
public static function fromPostOrDefault($postKey, $default = '') {
return !empty($_POST[$postKey]) ? $_POST[$postKey] : $default;
}
public static function base64url_encode(string $input): string
{
public static function base64url_encode($input) {
return rtrim(strtr(base64_encode($input), '+/', '-_'), '=');
}
public static function base64url_decode(string $input): string {
public static function base64url_decode($input) {
return base64_decode(str_pad(strtr($input, '-_', '+/'), strlen($input) % 4, '=', STR_PAD_RIGHT), true);
}
}

View File

@ -18,10 +18,10 @@
*/
// FRAMADATE version
const VERSION = '1.1.19';
const VERSION = '1.1.10';
// PHP Needed version
const PHP_NEEDED_VERSION = '7.3';
const PHP_NEEDED_VERSION = '5.6';
// Config constants
const COMPILE_DIR = '/tpl_c/';
@ -42,6 +42,3 @@ const SESSION_EDIT_LINK_TIME = "EditLinkMail";
// CSRF (300s = 5min)
const TOKEN_TIME = 300;
const ICAL_ENDING = ".ics";
const ICAL_PRODID = "-//Framasoft//Framadate//EN";

View File

@ -18,14 +18,12 @@
*/
// Prepare I18N instance
use o80\i18n\I18N;
$i18n = I18N::instance();
$i18n = \o80\i18n\I18N::instance();
$i18n->setDefaultLang(DEFAULT_LANGUAGE);
$i18n->setPath(__DIR__ . '/../../locale');
// Change language when user asked for it
if (isset($_POST['lang']) && is_string($_POST['lang']) && array_key_exists($_POST['lang'], $ALLOWED_LANGUAGES)) {
// Change langauge when user asked for it
if (isset($_POST['lang']) && is_string($_POST['lang']) && in_array($_POST['lang'], array_keys($ALLOWED_LANGUAGES), true)) {
$_SESSION['lang'] = $_POST['lang'];
}
@ -40,7 +38,7 @@ $date_format['txt_day'] = __('Date', 'DAY');
$date_format['txt_date'] = __('Date', 'DATE');
$date_format['txt_month_year'] = __('Date', 'MONTH_YEAR');
$date_format['txt_datetime_short'] = __('Date', 'DATETIME');
if (PHP_OS_FAMILY === 'Windows') { //%e can't be used on Windows platform, use %#d instead
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

@ -18,7 +18,6 @@
*/
use Framadate\FramaDB;
use Framadate\Repositories\RepositoryFactory;
use Framadate\Utils;
// Autoloading of dependencies with Composer
require_once __DIR__ . '/../../vendor/autoload.php';
@ -40,17 +39,10 @@ require_once __DIR__ . '/constants.php';
if (is_file(CONF_FILENAME)) {
@include_once __DIR__ . '/config.php';
try {
// Connection to database
$connect = new FramaDB(DB_CONNECTION_STRING, DB_USER, DB_PASSWORD);
RepositoryFactory::init($connect);
} catch (PDOException $e) {
if ($_SERVER['SCRIPT_NAME'] !== '/maintenance.php') {
header(('Location: ' . Utils::get_server_name() . 'maintenance.php'));
exit;
}
$error = $e->getMessage();
}
// Connection to database
$connect = new FramaDB(DB_CONNECTION_STRING, DB_USER, DB_PASSWORD);
RepositoryFactory::init($connect);
$err = 0;
} else {
define('NOMAPPLICATION', 'Framadate');
define('DEFAULT_LANGUAGE', 'fr');
@ -63,7 +55,6 @@ if (is_file(CONF_FILENAME)) {
'de' => 'Deutsch',
'it' => 'Italiano',
'br' => 'Brezhoneg',
'ca' => 'Català'
];
}

View File

@ -50,10 +50,9 @@ if (isset($_SERVER['FRAMADATE_DEVMODE']) && $_SERVER['FRAMADATE_DEVMODE']) {
$smarty->compile_check = false;
}
function smarty_function_poll_url($params, Smarty_Internal_Template $template): string
{
function smarty_function_poll_url($params, Smarty_Internal_Template $template) {
$poll_id = filter_var($params['id'], FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => POLL_REGEX]]);
$admin = isset($params['admin']) && $params['admin'];
$admin = (isset($params['admin']) && $params['admin']) ? true : false;
$action = (isset($params['action']) && !empty($params['action'])) ? Utils::htmlEscape($params['action']) : false;
$action_value = (isset($params['action_value']) && !empty($params['action_value'])) ? $params['action_value'] : false;
$vote_unique_id = isset($params['vote_id']) ? filter_var($params['vote_id'], FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => POLL_REGEX]]) : '';
@ -63,40 +62,26 @@ function smarty_function_poll_url($params, Smarty_Internal_Template $template):
return Utils::getUrlSondage($poll_id, $admin, $vote_unique_id, $action, $action_value);
}
function smarty_modifier_markdown(string $md, bool $clear = false, bool $inline=true): string
{
function smarty_modifier_markdown($md, $clear = false, $inline=true) {
return Utils::markdown($md, $clear, $inline);
}
function smarty_modifier_resource(string $link): string
{
function smarty_modifier_resource($link) {
return Utils::get_server_name() . $link;
}
function smarty_modifier_addslashes_single_quote(string $string): string
{
function smarty_modifier_addslashes_single_quote($string) {
return addcslashes($string, '\\\'');
}
function smarty_modifier_addslashes(string $string): string
{
return addslashes($string);
}
function smarty_modifier_html(?string $html): string
{
if (!$html) {
return '';
}
function smarty_modifier_html($html) {
return Utils::htmlEscape($html);
}
function smarty_modifier_html_special_chars(string $html): string
{
function smarty_modifier_html_special_chars($html) {
return Utils::htmlMailEscape($html);
}
function smarty_modifier_datepicker_path(string $lang): string
{
function smarty_modifier_datepicker_path($lang) {
$i = 0;
while (!is_file(path_for_datepicker_locale($lang)) && $i < 3) {
$lang_arr = explode('-', $lang);
@ -105,13 +90,12 @@ function smarty_modifier_datepicker_path(string $lang): string
} else {
$lang = 'en';
}
++$i;
$i += 1;
}
return 'js/locales/bootstrap-datepicker.' . $lang . '.js';
}
function smarty_modifier_locale_2_lang(string $locale): string
{
function smarty_modifier_locale_2_lang($locale) {
$lang_arr = explode('-', $locale);
if ($lang_arr && count($lang_arr) > 1) {
return $lang_arr[0];
@ -119,10 +103,6 @@ function smarty_modifier_locale_2_lang(string $locale): string
return $locale;
}
function path_for_datepicker_locale(string $lang): string
{
function path_for_datepicker_locale($lang) {
return __DIR__ . '/../../js/locales/bootstrap-datepicker.' . $lang . '.js';
}
# Customization #4871 par Didier le 28/08/2021.
$smarty->assign('VERSION',VERSION);

View File

@ -4,12 +4,11 @@ namespace Framadate;
use PHPUnit\Framework\TestCase;
abstract class FramaTestCase extends TestCase {
protected function getTestResourcePath(string $resourcepath): string
{
protected function getTestResourcePath($resourcepath) {
return __DIR__ . '/../resources/' . $resourcepath;
}
protected function readTestResource(string $resourcepath) {
protected function readTestResource($resourcepath) {
return file_get_contents($this->getTestResourcePath($resourcepath));
}

View File

@ -5,8 +5,7 @@ use Framadate\FramaTestCase;
class InputServiceUnitTest extends FramaTestCase
{
public function liste_emails(): array
{
public function liste_emails() {
return [
// valids addresses
"valid address" => ["example@example.com", "example@example.com"],
@ -24,8 +23,7 @@ class InputServiceUnitTest extends FramaTestCase
/**
* @dataProvider liste_emails
*/
public function test_filterMail($email, $expected): void
{
public function test_filterMail($email, $expected) {
$inputService = new InputService();
$filtered = $inputService->filterMail($email);

View File

@ -4,10 +4,9 @@ namespace Framadate\Services;
use Framadate\FramaTestCase;
class MailServiceUnitTest extends FramaTestCase {
public const MSG_KEY = '666';
const MSG_KEY = '666';
public function test_should_send_a_2nd_mail_after_a_good_interval(): void
{
public function test_should_send_a_2nd_mail_after_a_good_interval() {
// Given
$mailService = new MailService(true);
$_SESSION[MailService::MAILSERVICE_KEY] = [self::MSG_KEY => time() - 1000];
@ -16,11 +15,10 @@ class MailServiceUnitTest extends FramaTestCase {
$canSendMsg = $mailService->canSendMsg(self::MSG_KEY);
// Then
$this->assertTrue($canSendMsg);
$this->assertSame(true, $canSendMsg);
}
public function test_should_not_send_2_mails_in_a_short_interval(): void
{
public function test_should_not_send_2_mails_in_a_short_interval() {
// Given
$mailService = new MailService(true);
$_SESSION[MailService::MAILSERVICE_KEY] = [self::MSG_KEY => time()];
@ -29,6 +27,6 @@ class MailServiceUnitTest extends FramaTestCase {
$canSendMsg = $mailService->canSendMsg(self::MSG_KEY);
// Then
$this->assertFalse($canSendMsg);
$this->assertSame(false, $canSendMsg);
}
}

View File

@ -28,7 +28,7 @@ function bandeau_titre($titre)
echo '
<header role="banner">';
if(count($ALLOWED_LANGUAGES) > 1){
echo '<form method="post" class="hidden-print">
echo '<form method="post" action="" class="hidden-print">
<div class="input-group input-group-sm pull-right col-md-2 col-xs-4">
<select name="lang" class="form-control" title="' . __('Language selector', 'Select the language') . '" >' . liste_lang() . '</select>
<span class="input-group-btn">
@ -43,7 +43,7 @@ function bandeau_titre($titre)
<hr class="trait" role="presentation" />
</header>
<main role="main">';
global $connect;
$tables = $connect->allTables();
$diff = array_diff([Utils::table('comment'), Utils::table('poll'), Utils::table('slot'), Utils::table('vote')], $tables);
@ -54,14 +54,14 @@ function bandeau_titre($titre)
}
}
function liste_lang(): string
function liste_lang()
{
global $ALLOWED_LANGUAGES; global $locale;
$str = '';
foreach ($ALLOWED_LANGUAGES as $k => $v ) {
if (strpos($k, $locale) === 0) {
if (substr($k,0,2)===$locale) {
$str .= '<option lang="' . substr($k,0,2) . '" selected value="' . $k . '">' . $v . '</option>' . "\n" ;
} else {
$str .= '<option lang="' . substr($k,0,2) . '" value="' . $k . '">' . $v . '</option>' . "\n" ;

View File

@ -10,8 +10,8 @@ include_once __DIR__ . '/app/inc/init.php';
$goodLang = $_GET['good'];
$otherLang = $_GET['other'];
$good = json_decode(file_get_contents(__DIR__ . '/locale/' . $goodLang . '.json'), true, 512, JSON_THROW_ON_ERROR);
$other = json_decode(file_get_contents(__DIR__ . '/locale/' . $otherLang . '.json'), true, 512, JSON_THROW_ON_ERROR);
$good = json_decode(file_get_contents(__DIR__ . '/locale/' . $goodLang . '.json'), true);
$other = json_decode(file_get_contents(__DIR__ . '/locale/' . $otherLang . '.json'), true);
foreach ($good as $sectionName => $section) {
foreach ($section as $key => $value) {
@ -19,15 +19,15 @@ include_once __DIR__ . '/app/inc/init.php';
}
}
echo json_encode($good, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | ~(JSON_ERROR_UTF8 | JSON_HEX_QUOT | JSON_HEX_APOS));
echo json_encode($good, JSON_PRETTY_PRINT | ~(JSON_ERROR_UTF8 | JSON_HEX_QUOT | JSON_HEX_APOS));
function getFromOther($other, $goodKey, $default, $otherLang): string {
function getFromOther($other, $goodKey, $default, $otherLang) {
foreach ($other as $sectionName => $section) {
foreach ($section as $key => $value) {
if (
strtolower($key) === strtolower($goodKey) ||
stripos($key, strtolower($goodKey)) === 0 ||
strtolower(trim($key)) === strtolower($goodKey) ||
strtolower(substr($key, 0, strlen($key) - 1)) === strtolower($goodKey) ||
strtolower(trim(substr(trim($key), 0, strlen($key) - 1))) === strtolower($goodKey)
) {
return $value;

View File

@ -10,8 +10,8 @@ include_once __DIR__ . '/app/inc/init.php';
$goodLang = $_GET['good'];
$testLang = $_GET['test'];
$good = json_decode(file_get_contents(__DIR__ . '/locale/' . $goodLang . '.json'), true, 512, JSON_THROW_ON_ERROR);
$test = json_decode(file_get_contents(__DIR__ . '/locale/' . $testLang . '.json'), true, 512, JSON_THROW_ON_ERROR);
$good = json_decode(file_get_contents(__DIR__ . '/locale/' . $goodLang . '.json'), true);
$test = json_decode(file_get_contents(__DIR__ . '/locale/' . $testLang . '.json'), true);
$diffSection = false;
@ -46,8 +46,8 @@ include_once __DIR__ . '/app/inc/init.php';
}
}
if (!$diffSection and array_keys($section) !== array_keys($test[$sectionName])) {
$diff[$sectionName]['order_good'] = array_keys($section);
if (!$diffSection and array_keys($good[$sectionName]) !== array_keys($test[$sectionName])) {
$diff[$sectionName]['order_good'] = array_keys($good[$sectionName]);
$diff[$sectionName]['order_test'] = array_keys($test[$sectionName]);
}
}

View File

@ -2,12 +2,10 @@
"name": "framasoft/framadate",
"description": "Application to facilitate the schedule of events or classic polls",
"homepage": "https://framadate.org/",
"keywords": [
"poll",
"framadate"
],
"keywords": ["poll", "framadate"],
"version": "0.9.0",
"license": "CECILL-B",
"type": "project",
"support": {
"issues": "https://framagit.org/framasoft/framadate/issues"
@ -54,43 +52,33 @@
"email": "raphael.droz@gmail.com"
}
],
"scripts": {
"cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:fix": "php-cs-fixer fix",
"lint": "find . -name \\*.php -not -path './vendor/*' -not -path './build/*' -not -path './tests/integration/vendor/*' -print0 | xargs -0 -n1 php -l"
},
"require": {
"php": ">=7.3.0",
"php": ">=5.6.0",
"ext-pdo": "*",
"ext-json": "*",
"smarty/smarty": "^4.0",
"smarty/smarty": "^3.1",
"o80/i18n": "dev-develop",
"phpmailer/phpmailer": "~6.2",
"phpmailer/phpmailer": "~6.0",
"ircmaxell/password-compat": "dev-master",
"roave/security-advisories": "dev-master",
"erusev/parsedown": "^1.7",
"egulias/email-validator": "^3.1",
"sabre/vobject": "~4.1"
"egulias/email-validator": "~2.1"
},
"require-dev": {
"phpunit/phpunit": "^9",
"friendsofphp/php-cs-fixer": "^3.2",
"vimeo/psalm": "^4.15"
"phpunit/phpunit": "^5.7",
"friendsofphp/php-cs-fixer": "~2.0"
},
"repositories": [
{
"type": "git",
"url": "https://framagit.org/framasoft/framadate/o80-i18n"
}
],
"autoload": {
"psr-4": {
"Framadate\\": "app/classes/Framadate/"
}
},
"config": {
"platform": {
"php": "7.3.0"
"php": "5.6.0"
}
}
}

4593
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,6 @@
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
use Framadate\Choice;
use Framadate\Services\InputService;
use Framadate\Services\LogService;
use Framadate\Services\MailService;
use Framadate\Services\PollService;
@ -30,11 +29,10 @@ include_once __DIR__ . '/app/inc/init.php';
/* Service */
/*---------*/
$logService = new LogService();
$pollService = new PollService($logService);
$pollService = new PollService($connect, $logService);
$mailService = new MailService($config['use_smtp'], $config['smtp_options']);
$purgeService = new PurgeService($logService);
$purgeService = new PurgeService($connect, $logService);
$sessionService = new SessionService();
$inputService = new InputService();
if (is_file('bandeaux_local.php')) {
include_once('bandeaux_local.php');
@ -45,12 +43,16 @@ if (is_file('bandeaux_local.php')) {
$form = unserialize($_SESSION['form']);
// Step 1/4 : error if $_SESSION from info_sondage are not valid
if (empty($form->title) || empty($form->admin_name) || ($config['use_smtp'] && empty($form->admin_mail))) {
if (empty($form->title) || empty($form->admin_name) || (($config['use_smtp']) ? empty($form->admin_mail) : false)) {
$smarty->assign('title', __('Error', 'Error!'));
$smarty->assign('error', __('Error', 'You haven\'t filled the first section of the poll creation.'));
$smarty->display('error.tpl');
exit;
}
// Min/Max archive date
$min_expiry_time = $pollService->minExpiryDate();
$max_expiry_time = $pollService->maxExpiryDate();
// The poll format is other (A) if we are in this file
if (!isset($form->format)) {
$form->format = 'A';
@ -64,8 +66,28 @@ if (empty($form->title) || empty($form->admin_name) || ($config['use_smtp'] && e
// Step 4 : Data prepare before insert in DB
if (isset($_POST['confirmation'])) {
// Define expiration date
$expiration_date = $inputService->parseDate($_POST['enddate']);
$form->end_date = $inputService->validateDate($expiration_date, $pollService->minExpiryDate(), $pollService->maxExpiryDate())->getTimestamp();
$enddate = filter_input(INPUT_POST, 'enddate', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '#^[0-9]{2}/[0-9]{2}/[0-9]{4}$#']]);
if (!empty($enddate)) {
$registredate = explode('/', $enddate);
if (is_array($registredate) && count($registredate) === 3) {
$time = mktime(0, 0, 0, $registredate[1], $registredate[0], $registredate[2]);
if ($time < $min_expiry_time) {
$form->end_date = $min_expiry_time;
} elseif ($max_expiry_time < $time) {
$form->end_date = $max_expiry_time;
} else {
$form->end_date = $time;
}
}
}
if (empty($form->end_date)) {
// By default, expiration date is 6 months after last day
$form->end_date = $max_expiry_time;
}
// Insert poll in database
$ids = $pollService->createPoll($form);
@ -83,8 +105,8 @@ if (empty($form->title) || empty($form->admin_name) || ($config['use_smtp'] && e
$message_admin .= sprintf(' :<br/><br/><a href="%1$s">%1$s</a>', Utils::getUrlSondage($admin_poll_id, true));
if ($mailService->isValidEmail($form->admin_mail)) {
$mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'Author\'s message') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message_admin);
$mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'For sending to the polled users') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message);
$mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'Author\'s message') . '] ' . __('Generic', 'Poll') . ': ' . $form->title, $message_admin);
$mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'For sending to the polled users') . '] ' . __('Generic', 'Poll') . ': ' . $form->title, $message);
}
}
@ -115,7 +137,7 @@ if (empty($form->title) || empty($form->admin_name) || ($config['use_smtp'] && e
}
// Expiration date is initialised with config parameter. Value will be modified in step 4 if user has defined an other date
$form->end_date = $pollService->maxExpiryDate()->format('Y-m-d H:i:s');
$form->end_date = $max_expiry_time;
// Summary
$summary = '<ol>';
@ -123,7 +145,7 @@ if (empty($form->title) || empty($form->admin_name) || ($config['use_smtp'] && e
preg_match_all('/\[!\[(.*?)\]\((.*?)\)\]\((.*?)\)/', $choice->getName(), $md_a_img); // Markdown [![alt](src)](href)
preg_match_all('/!\[(.*?)\]\((.*?)\)/', $choice->getName(), $md_img); // Markdown ![alt](src)
preg_match_all('/\[(.*?)\]\((.*?)\)/', $choice->getName(), $md_a); // Markdown [text](href)
if (isset($md_a_img[2][0], $md_a_img[3][0]) && $md_a_img[2][0] !== '' && $md_a_img[3][0] !== '') { // [![alt](src)](href)
if (isset($md_a_img[2][0]) && $md_a_img[2][0] !== '' && isset($md_a_img[3][0]) && $md_a_img[3][0] !== '') { // [![alt](src)](href)
$li_subject_text = (isset($md_a_img[1][0]) && $md_a_img[1][0] !== '') ? stripslashes($md_a_img[1][0]) : __('Generic', 'Choice') . ' ' . ($i + 1);
$li_subject_html = '<a href="' . $md_a_img[3][0] . '"><img src="' . $md_a_img[2][0] . '" class="img-responsive" alt="' . $li_subject_text . '" /></a>';
} elseif (isset($md_img[2][0]) && $md_img[2][0] !== '') { // ![alt](src)
@ -141,7 +163,7 @@ if (empty($form->title) || empty($form->admin_name) || ($config['use_smtp'] && e
}
$summary .= '</ol>';
$end_date_str = utf8_encode(strftime($date_format['txt_date'], $pollService->maxExpiryDate()->getTimestamp())); //textual date
$end_date_str = utf8_encode(strftime($date_format['txt_date'], $max_expiry_time)); //textual date
$_SESSION['form'] = serialize($form);
@ -159,7 +181,7 @@ if (empty($form->title) || empty($form->admin_name) || ($config['use_smtp'] && e
bandeau_titre(__('Step 2 classic', 'Poll subjects (2 on 3)'));
echo '
<form name="formulaire" action="' . Utils::get_server_name() . 'create_classic_poll.php" method="POST" class="form-horizontal">
<form name="formulaire" action="' . Utils::get_server_name() . 'create_classic_poll.php" method="POST" class="form-horizontal" role="form">
<div class="row">
<div class="col-md-8 col-md-offset-2">';
echo '
@ -175,7 +197,7 @@ if (empty($form->title) || empty($form->admin_name) || ($config['use_smtp'] && e
$choices = $form->getChoices();
$nb_choices = max(count($choices), 5);
for ($i = 0; $i < $nb_choices; $i++) {
$choice = $choices[$i] ?? new Choice();
$choice = isset($choices[$i]) ? $choices[$i] : new Choice();
echo '
<div class="form-group choice-field">
<label for="choice' . $i . '" class="col-sm-2 control-label">' . __('Generic', 'Choice') . ' ' . ($i + 1) . '</label>
@ -233,8 +255,8 @@ if (empty($form->title) || empty($form->admin_name) || ($config['use_smtp'] && e
</div>
</form>
<script src="js/app/framadatepicker.js"></script>
<script src="js/app/classic_poll.js"></script>
<script type="text/javascript" src="js/app/framadatepicker.js"></script>
<script type="text/javascript" src="js/app/classic_poll.js"></script>
' . "\n";
bandeau_pied();

View File

@ -30,9 +30,9 @@ include_once __DIR__ . '/app/inc/init.php';
/* Service */
/*---------*/
$logService = new LogService();
$pollService = new PollService($logService);
$pollService = new PollService($connect, $logService);
$mailService = new MailService($config['use_smtp'], $config['smtp_options']);
$purgeService = new PurgeService($logService);
$purgeService = new PurgeService($connect, $logService);
$inputService = new InputService();
$sessionService = new SessionService();
@ -40,6 +40,10 @@ if (is_readable('bandeaux_local.php')) {
include_once('bandeaux_local.php');
}
// Min/Max archive date
$min_expiry_time = $pollService->minExpiryDate();
$max_expiry_time = $pollService->maxExpiryDate();
$form = unserialize($_SESSION['form']);
// The poll format is DATE if we are in this file
@ -52,7 +56,7 @@ if (isset($form->format) && $form->format !== 'D') {
$form->clearChoices();
}
if (!isset($form->title, $form->admin_name) || ($config['use_smtp'] && !isset($form->admin_mail))) {
if (!isset($form->title) || !isset($form->admin_name) || ($config['use_smtp'] && !isset($form->admin_mail))) {
$step = 1;
} else if (!empty($_POST['confirmation'])) {
$step = 4;
@ -107,7 +111,7 @@ switch ($step) {
// Handle Step2 submission
if (!empty($_POST['days'])) {
// Remove empty dates
$_POST['days'] = array_filter($_POST['days'], static function ($d) {
$_POST['days'] = array_filter($_POST['days'], function ($d) {
return !empty($d);
});
@ -135,7 +139,7 @@ switch ($step) {
$i++;
}
for ($i = 0, $iMax = count($_POST['days']); $i < $iMax; $i++) {
for ($i = 0; $i < count($_POST['days']); $i++) {
$day = $_POST['days'][$i];
if (!empty($day)) {
@ -146,7 +150,7 @@ switch ($step) {
$form->addChoice($choice);
$schedules = $inputService->filterArray($moments[$i], FILTER_DEFAULT);
for ($j = 0, $jMax = count($schedules); $j < $jMax; $j++) {
for ($j = 0; $j < count($schedules); $j++) {
if (!empty($schedules[$j])) {
$choice->addSlot(strip_tags($schedules[$j]));
}
@ -171,7 +175,7 @@ switch ($step) {
}
$summary .= '</ul>';
$end_date_str = utf8_encode(strftime($date_format['txt_date'], $pollService->maxExpiryDate()->getTimestamp())); // textual date
$end_date_str = utf8_encode(strftime($date_format['txt_date'], $max_expiry_time)); // textual date
$_SESSION['form'] = serialize($form);
@ -188,8 +192,28 @@ switch ($step) {
// Step 4 : Data prepare before insert in DB
// Define expiration date
$expiration_date = $inputService->parseDate($_POST['enddate']);
$form->end_date = $inputService->validateDate($expiration_date, $pollService->minExpiryDate(), $pollService->maxExpiryDate())->getTimestamp();
$enddate = filter_input(INPUT_POST, 'enddate', FILTER_VALIDATE_REGEXP, ['options' => ['regexp' => '#^[0-9]{2}/[0-9]{2}/[0-9]{4}$#']]);
if (!empty($enddate)) {
$registredate = explode('/', $enddate);
if (is_array($registredate) && count($registredate) === 3) {
$time = mktime(0, 0, 0, $registredate[1], $registredate[0], $registredate[2]);
if ($time < $min_expiry_time) {
$form->end_date = $min_expiry_time;
} elseif ($max_expiry_time < $time) {
$form->end_date = $max_expiry_time;
} else {
$form->end_date = $time;
}
}
}
if (empty($form->end_date)) {
// By default, expiration date is 6 months after last day
$form->end_date = $max_expiry_time;
}
// Insert poll in database
$ids = $pollService->createPoll($form);
@ -210,8 +234,8 @@ switch ($step) {
$message_admin = sprintf($message_admin, Utils::getUrlSondage($admin_poll_id, true));
if ($mailService->isValidEmail($form->admin_mail)) {
$mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'Author\'s message') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message_admin);
$mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'For sending to the polled users') . '] ' . __('Generic', 'Poll') . ': ' . Utils::htmlEscape($form->title), $message);
$mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'Message for the author') . '] ' . __('Generic', 'Poll') . ': ' . $form->title, $message_admin);
$mailService->send($form->admin_mail, '[' . NOMAPPLICATION . '][' . __('Mail', 'Participant link') . '] ' . __('Generic', 'Poll') . ': ' . $form->title, $message);
}
}

View File

@ -42,8 +42,8 @@ if ($form === null && !($form instanceof Form)) {
}
// Type de sondage
if ((isset($_GET['type']) && $_GET['type'] === 'date') ||
(isset($_POST['type']) && $_POST['type'] === 'date')
if (isset($_GET['type']) && $_GET['type'] === 'date' ||
isset($_POST['type']) && $_POST['type'] === 'date'
) {
$poll_type = 'date';
$form->choix_sondage = $poll_type;
@ -57,21 +57,21 @@ $goToStep2 = filter_input(INPUT_POST, GO_TO_STEP_2, FILTER_VALIDATE_REGEXP, ['op
if ($goToStep2) {
$title = $inputService->filterTitle($_POST['title']);
$use_ValueMax = isset($_POST['use_ValueMax']) && $inputService->filterBoolean($_POST['use_ValueMax']);
$use_ValueMax = isset($_POST['use_ValueMax']) ? $inputService->filterBoolean($_POST['use_ValueMax']) : false;
$ValueMax = $use_ValueMax === true ? $inputService->filterValueMax($_POST['ValueMax']) : null;
$use_customized_url = isset($_POST['use_customized_url']) && $inputService->filterBoolean($_POST['use_customized_url']);
$use_customized_url = isset($_POST['use_customized_url']) ? $inputService->filterBoolean($_POST['use_customized_url']) : false;
$customized_url = $use_customized_url === true ? $inputService->filterId($_POST['customized_url']) : null;
$name = mb_substr($inputService->filterName($_POST['name']), 0, 32);
$name = $inputService->filterName($_POST['name']);
$mail = $config['use_smtp'] === true ? $inputService->filterMail($_POST['mail']) : null;
$description = $inputService->filterDescription($_POST['description']);
$editable = $inputService->filterEditable($_POST['editable']);
$receiveNewVotes = isset($_POST['receiveNewVotes']) && $inputService->filterBoolean($_POST['receiveNewVotes']);
$receiveNewComments = isset($_POST['receiveNewComments']) && $inputService->filterBoolean($_POST['receiveNewComments']);
$hidden = isset($_POST['hidden']) && $inputService->filterBoolean($_POST['hidden']);
$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 = $_POST['password'] ?? null;
$password_repeat = $_POST['password_repeat'] ?? null;
$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
@ -96,6 +96,7 @@ if ($goToStep2) {
$form->receiveNewVotes = $receiveNewVotes;
$form->receiveNewComments = $receiveNewComments;
$form->hidden = $hidden;
$form->collect_users_mail = $collect_users_mail;
$form->use_password = ($use_password !== null);
$form->results_publicly_visible = ($results_publicly_visible !== null);
@ -235,7 +236,7 @@ if (!empty($_POST[GO_TO_STEP_2])) {
if ($error_on_customized_url) {
$errors['customized_url']['aria'] = 'aria-describeby="customized_url" ';
$errors['customized_url']['class'] = ' has-error';
$errors['customized_url']['msg'] = $error_on_customized_url_msg ?? __('Error', "Something is wrong with the format: customized urls should only consist of alphanumeric characters and hyphens.");
$errors['customized_url']['msg'] = isset($error_on_customized_url_msg) ? $error_on_customized_url_msg : __('Error', "Something is wrong with the format: customized urls should only consist of alphanumeric characters and hyphens.");
}
if ($error_on_description) {
@ -248,10 +249,6 @@ if (!empty($_POST[GO_TO_STEP_2])) {
$errors['name']['aria'] = 'aria-describeby="poll_name_error" ';
$errors['name']['class'] = ' has-error';
$errors['name']['msg'] = __('Error', 'Enter a name');
} elseif (mb_strlen($inputService->filterName($_POST['name'])) > 32) {
$errors['name']['aria'] = 'aria-describeby="poll_name_error" ';
$errors['name']['class'] = ' has-error';
$errors['name']['msg'] = __('Error', "Name is limited to 32 characters");
} elseif ($error_on_name) {
$errors['name']['aria'] = 'aria-describeby="poll_name_error" ';
$errors['name']['class'] = ' has-error';
@ -301,6 +298,7 @@ $smarty->assign('customized_url', Utils::fromPostOrDefault('customized_url', $fo
$smarty->assign('use_customized_url', Utils::fromPostOrDefault('use_customized_url', $form->use_customized_url));
$smarty->assign('ValueMax', Utils::fromPostOrDefault('ValueMax', $form->ValueMax));
$smarty->assign('use_ValueMax', Utils::fromPostOrDefault('use_ValueMax', $form->use_ValueMax));
$smarty->assign('collect_users_mail', Utils::fromPostOrDefault('collect_users_mail', $form->collect_users_mail));
$smarty->assign('poll_description', !empty($_POST['description']) ? $_POST['description'] : $form->description);
$smarty->assign('poll_name', Utils::fromPostOrDefault('name', $form->admin_name));
$smarty->assign('poll_mail', Utils::fromPostOrDefault('mail', $form->admin_mail));

7
css/easymde.min.css vendored

File diff suppressed because one or more lines are too long

7
css/simplemde.min.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -64,6 +64,8 @@ a:focus { /* a11y */
outline:#000 dotted 1px;
}
header, footer {
}
main {
margin-top: 20px;
}
@ -183,7 +185,7 @@ caption {
font-weight:bold;
}
.results a.btn-default.btn-sm, .best-choice .list-unstyled a.btn-default.btn-sm {
.results a.btn-default.btn-sm {
padding: 3px 7px;
font-size: 0.7em;
}

View File

@ -35,7 +35,7 @@ $poll = null;
/*----------*/
$logService = new LogService();
$pollService = new PollService($logService);
$pollService = new PollService($connect, $logService);
$securityService = new SecurityService();
/* PAGE */
@ -110,7 +110,7 @@ foreach ($votes as $vote) {
$text = __('Generic', 'Yes');
break;
default:
$text = __('Generic', 'Unknown');
$text = 'unkown';
}
echo Utils::csvEscape($text);
echo ',';

View File

@ -27,7 +27,7 @@ include_once __DIR__ . '/app/inc/init.php';
/* SERVICES */
/* -------- */
$logService = new LogService();
$pollService = new PollService($logService);
$pollService = new PollService($connect, $logService);
$mailService = new MailService($config['use_smtp'], $config['smtp_options']);
/* PAGE */

View File

@ -1,6 +1,4 @@
<?php
use Framadate\Services\LogService;
/**
* 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
@ -31,7 +29,8 @@ if (!is_file(CONF_FILENAME)) {
/* SERVICES */
/* -------- */
$pollService = new PollService(new LogService());
$logService = '\Framadate\Services\LogService';
$pollService = new PollService($connect, new $logService());
/* PAGE */
/* ---- */

View File

@ -17,13 +17,13 @@
*/
$(document).ready(function () {
/**
* Error check when submitting form
*/
$("#formulaire").submit(function (event) {
var isHidden = $("#hidden").prop("checked");
var isOptionAllUserCanModifyEverything =
$("#editableByAll").is(":checked");
var isHidden = $("#hidden").prop('checked');
var isOptionAllUserCanModifyEverything = $("#editableByAll").is(":checked");
if (isHidden && isOptionAllUserCanModifyEverything) {
event.preventDefault();
@ -44,23 +44,22 @@ $(document).ready(function () {
}
});
/**
/**
* Enable/Disable ValueMax options
*/
const useValueMax = document.querySelector("#use_ValueMax");
useValueMax.addEventListener("change", function () {
const valueMaxOptions = document.querySelector("#value_max_options");
if (useValueMax.checked) {
valueMaxOptions.classList.remove("hidden");
$("#use_ValueMax").change(function () {
if ($(this).prop("checked")) {
$("#ValueMax").removeClass("hidden");
} else {
valueMaxOptions.classList.add("hidden");
$("#ValueMax").addClass("hidden");
}
});
/**
* Hide/Show password options
*/
$("#use_password").change(function () {
$("#use_password").change(function(){
if ($(this).prop("checked")) {
$("#password_options").removeClass("hidden");
} else {
@ -89,12 +88,9 @@ $(document).ready(function () {
document.getElementById("cookie-warning").setAttribute("style", "");
}
var wrapper = new MDEWrapper(
$("#poll_comments")[0],
$("#rich-editor-button"),
$("#simple-editor-button")
);
if ($("#rich-editor-button").hasClass("active")) {
var wrapper = new MDEWrapper($('#poll_comments')[0], $('#rich-editor-button'), $('#simple-editor-button'));
if ($('#rich-editor-button').hasClass('active')) {
wrapper.enable();
}
});

View File

@ -66,7 +66,8 @@ $(document).ready(function () {
url: form.attr('action'),
data: form.serialize(),
dataType: 'json',
success: function(data) {
success: function(data)
{
$('#comment').val('');
if (data.result) {
$('#comments_list')
@ -94,9 +95,6 @@ $(document).ready(function () {
}, 750);
}
},
error: function (data) {
console.error(data);
},
complete: function() {
$('#add_comment').removeAttr("disabled");
}

7
js/easymde.min.js vendored

File diff suppressed because one or more lines are too long

11008
js/jquery-1.12.4.js vendored Normal file

File diff suppressed because it is too large Load Diff

5
js/jquery-1.12.4.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,17 +0,0 @@
/**
* Occitan translation for bootstrap-datepicker
*/
;(function($){
$.fn.datepicker.dates['oc'] = {
days: ["Dimenge", "Diluns", "Dimars", "Dimècres", "Dijòus", "Divendres", "Dissabte"],
daysShort: ["Dim", "Dil", "Dmr", "Dmc", "Dij", "Div", "Dis"],
daysMin: ["dg", "dl", "dr", "dc", "dj", "dv", "ds"],
months: ["Genièr", "Febrièr", "Març", "Abrial", "Mai", "Junh", "Julhet", "Agost", "Setembre", "Octobre", "Novembre", "Decembre"],
monthsShort: ["Gen", "Feb", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Oct", "Nov", "Dec"],
today: "Uèi",
monthsTitle: "Meses",
clear: "Escafar",
weekStart: 1,
format: "dd/mm/yyyy"
};
}(jQuery));

View File

@ -1,12 +1,12 @@
function myPreviewRender(text) {
text = text.replace(/[\u00A0-\u9999<>\&]/gim, function (i) {
return "&#" + i.charCodeAt(0) + ";";
function myPreviewRender (text) {
text = text.replace(/[\u00A0-\u9999<>\&]/gim, function(i) {
return '&#'+i.charCodeAt(0)+';';
});
text = EasyMDE.prototype.markdown(text);
text = DOMPurify.sanitize(text);
text = SimpleMDE.prototype.markdown(text);
text = text.replace(/ /g, '&nbsp;');
return text;
}
};
function MDEWrapper(textarea, enableButton, disableButton) {
this.element = textarea;
@ -17,58 +17,47 @@ function MDEWrapper(textarea, enableButton, disableButton) {
var wrapper = this;
if (this.enableButton) {
this.enableButton.on("click", function () {
wrapper.enable();
});
this.enableButton.on('click', function() {wrapper.enable()});
}
if (this.disableButton) {
this.disableButton.on("click", function () {
wrapper.disable();
});
this.disableButton.on('click', function() {wrapper.disable()});
}
}
MDEWrapper.prototype.enable = function () {
MDEWrapper.prototype.enable = function() {
var wrapper = this;
if (this.simplemde == null) {
this.simplemde = new EasyMDE({
this.simplemde = new SimpleMDE({
element: wrapper.element,
forceSync: true,
status: true,
// previewRender: myPreviewRender,
renderingConfig: {
sanitizerFunction: function (text) {
return DOMPurify.sanitize(text);
},
},
previewRender: myPreviewRender,
spellChecker: false,
promptURLs: true,
minHeight: "200px",
maxHeight: "300px",
autoDownloadFontAwesome: false,
autoDownloadFontAwesome: false
});
if (this.enableButton) {
this.enableButton.addClass("active");
this.enableButton.addClass('active');
}
if (this.disableButton) {
this.disableButton.removeClass("active");
this.disableButton.removeClass('active');
}
}
};
}
MDEWrapper.prototype.disable = function () {
MDEWrapper.prototype.disable = function() {
if (this.simplemde != null) {
this.simplemde.toTextArea();
this.simplemde = null;
if (this.disableButton) {
this.disableButton.addClass("active");
this.disableButton.addClass('active');
}
if (this.enableButton) {
this.enableButton.removeClass("active");
this.enableButton.removeClass('active');
}
}
};
}
MDEWrapper.prototype.isEnabled = function () {
MDEWrapper.prototype.isEnabled = function() {
return this.simplemde != null;
};
}

3
js/purify.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

15
js/simplemde.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -208,8 +208,7 @@
"seconds": "ثواني",
"vote": "vote",
"votes": "أصوات",
"with": "with",
"Unknown": "مجهول"
"with": "with"
},
"Homepage": {
"Make a classic poll": "Make a standard poll",

View File

@ -208,8 +208,7 @@
"seconds": "eilenn",
"vote": "vouezh",
"votes": "a vouezhioù",
"with": "gant",
"Unknown": "Dianav"
"with": "gant"
},
"Homepage": {
"Make a classic poll": "Krouiñ ur sontadeg klasel",

View File

@ -1,456 +0,0 @@
{
"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:": "S'ha desat el vostre vot, però tingueu en compte que cal que conserveu aquest enllaç personalitzat per a poder-lo modificar.",
"Update vote succeeded": "S'ha actualitzat el vot",
"The poll is expired, it will be deleted soon.": "L'enquesta ha caducat, aviat se suprimirà.",
"POLL_LOCKED_WARNING": "L'administrador ha blocat aquesta enquesta. Els vots i els comentaris estan congelats, ja no és possible de participar-hi",
"Deletion date:": "Data de supressió:",
"Adding the vote succeeded": "S'ha afegit el vot",
"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.": "Si voleu votar en aquesta enquesta, heu de donar el vostre nom, triar i enviar-lo amb el botó més al final de la línia."
},
"adminstuds": {
"remove a column or a line with": "treu una columna o una línia amb",
"and add a new column with": "i afegeix una columna nova amb",
"Your poll has been removed!": "S'ha eliminat l'enquesta!",
"You can add a new scheduling date to your poll.": "Podeu afegir una altra data de programació a l'enquesta.",
"Vote updated": "S'ha actualitzat el vot",
"Vote deleted": "S'ha suprimit el vot",
"Vote added": "S'ha afegit el vot",
"The poll is created.": "S'ha creat l'enquesta.",
"Remove the votes": "Elimina els vots",
"Remove the comments": "Elimina els comentaris",
"Remove the column": "Elimina la columna",
"Poll saved": "S'ha desat la votació",
"Poll fully deleted": "L'enquesta s'ha eliminat completament",
"Keep votes": "Conserva els vots",
"Keep this poll": "Conserva aquesta enquesta",
"Keep the votes": "Conserva els vots",
"Keep the poll": "Conserva l'enquesta",
"Keep the comments": "Conserva els comentaris",
"Keep comments": "Conserva els comentaris",
"Finally, you can change the informations of this poll like the title, the comments or your email address.": "Finalment, podeu canviar les propietats d'aquesta enquesta, com ara el títol, els comentaris o l'adreça de correu electrònic.",
"Confirm removal of the poll": "Confirmeu l'eliminació de l'enquesta",
"Confirm removal of the column.": "Confirmeu l'eliminació de la columna.",
"Confirm removal of all votes of the poll": "Confirma l'eliminació de tots els vots",
"Confirm removal of all comments of the poll": "Confirmeu l'eliminació de tots els comentaris",
"Comment deleted": "Comentaris eliminats",
"Column's adding": "S'està afegint una columna",
"Column removed": "S'ha suprimit la columna",
"Choice added": "S'ha afegit una opció",
"Back to the poll": "Torna a l'enquesta",
"As poll administrator, you can change all the lines of this poll with this button": "Com a administrador, podeu canviar totes les línies d'aquesta enquesta amb aquest botó",
"All votes deleted": "S'han suprimit tots els vots",
"All comments deleted": "S'han suprimit tots els comentaris",
"Add a column": "Afegeix una columna",
"If you just want to add a new hour to an existant date, put the same date and choose a new hour.": "Si només voleu afegir una franja horària nova a una data existent, afegiu aquesta data i escolliu un interval horari.",
"Delete the poll": "Elimina la enquesta"
},
"Step 3": {
"after the last date of your poll.": "després de l'última data de l'enquesta.",
"Your poll will be automatically archived in %d days.": "L'enquesta s'arxivarà automàticament d'aquí a %d dies.",
"Your poll will automatically be archived": "L'enquesta s'arxivarà automàticament",
"You can set a closer archiving date for it.": "Podeu establir una data de venciment específica de l'enquesta.",
"Then, you will receive quickly two emails: one contening the link of your poll for sending it to the voters, the other contening the link to the administration page of your poll.": "A continuació, rebreu dos correus electrònics: un que contindrà l'enllaç de l'enquesta per a enviar-la als participants i l'altre que contindrà l'enllaç a la pàgina d'administració de l'enquesta.",
"Removal date and confirmation (3 on 3)": "Data de supressió i confirmació (3 de 3)",
"Once you have confirmed the creation of your poll, you will be automatically redirected on the administration page of your poll.": "Un cop hagis confirmat la creació de l'enquesta, se us redirigirà automàticament a la pàgina d'administració de l'enquesta.",
"List of your choices": "Llista d'opcions",
"Create the poll": "Crea l'enquesta",
"Confirm the creation of your poll": "Confirmeu la creació de l'enquesta",
"Back to step 2": "Torna al pas 2",
"Archiving date:": "Data de venciment:"
},
"Step 2 date": {
"You can add or remove additionnal days and hours with the buttons": "Podeu afegir o treure dies i hores addicionals amb els botons",
"To schedule an event you need to propose at least two choices (two hours for one day or two days).": "Per a programar un esdeveniment, cal que proporcioneu almenys dues opcions (per exemple, dos espais horaris en un o dos dies).",
"Remove this day": "Elimina aquest dia",
"Remove an hour": "Elimina un espai horari",
"Remove all hours": "Elimina tots els temps",
"Remove all days": "Elimina tots els dies",
"Remove a day": "Elimina un dia",
"Poll dates (2 on 3)": "Dates de l'enquesta (2 de 3)",
"For each selected day, you can choose, or not, meeting hours (e.g.: \"8h\", \"8:30\", \"8h-10h\", \"evening\", etc.)": "Per cada dia seleccionat, podeu suggerir temps de trobada (per exemple, \"8h\", \"8:30\", \"8h-10h\", \"tarda\", etc.)",
"Copy hours of the first day": "Copia els temps del primer dia",
"Choose the dates of your poll": "Tria dates per a l'enquesta",
"Add an hour": "Afegeix un espai horari",
"Add a day": "Afegeix un dia"
},
"Step 2 classic": {
"the Markdown syntax": "Sintaxi Markdown",
"You can add or remove additional choices with the buttons": "Podeu afegir o eliminar opcions amb els botons",
"URL of the image": "URL de la imatge",
"To make a generic poll you need to propose at least two choices between differents subjects.": "Per a crear una enquesta, heu de proporcionar almenys dues opcions diferents.",
"These fields are optional. You can add a link, an image or both.": "Aquests camps són opcionals. Podeu afegir un enllaç, una imatge o ambdós.",
"Remove a choice": "Elimina una elecció",
"Poll subjects (2 on 3)": "Opcions de votació (2 de 3)",
"It's possible to propose links or images by using": "Es poden incloure enllaços o imatges utilitzant",
"Alternative text": "Text alternatiu",
"Add a link or an image": "Afegeix un enllaç o una imatge",
"Add a choice": "Afegeix una elecció"
},
"Step 2": {
"Go to step 3": "Vés al pas 3",
"Back to step 1": "Torna al pas 1"
},
"Step 1": {
"You can enable or disable the editor at will.": "Podeu habilitar o deshabilitar l'editor a voluntat.",
"You are in the poll creation section.": "Sou a la secció de creació d'enquestes.",
"Votes cannot be modified": "Els vots no es poden modificar",
"Voters can modify their vote themselves": "Els votants poden modificar el seu vot ells mateixos",
"Value Max": "Valor màxim",
"Use a password to restrict access": "Utilitzeu una contrasenya per restringir l'accés",
"To receive an email for each new vote": "Rebeu un correu electrònic per a cada nou vot",
"To receive an email for each new comment": "Rebreu un correu electrònic per a cada comentari nou",
"To make the description more attractive, you can use the Markdown format.": "Per a fer que la descripció sigui més atractiva, podeu utilitzar el format Markdown.",
"The results are publicly visible": "Els resultats són visibles públicament",
"Required fields cannot be left blank.": "Els camps obligatoris no es poden deixar en blanc.",
"Poll title": "Títol de l'enquesta",
"Poll password": "Contrasenya",
"Poll id warning": "Si definiu un identificador per a l'enquesta, podríeu facilitar-hi l'accés per a persones no desitjades. Es recomana protegir-la amb una contrasenya.",
"Poll id rules": "L'identificador pot contenir lletres, números i guions \"-\".",
"Poll id": "Enllaç de l'enquesta",
"Poll creation (1 on 3)": "Creació d'enquestes (1 de 3)",
"Permissions": "Permisos",
"Password confirmation": "Confirmació",
"Password choice": "Elecció",
"Optional parameters": "Paràmetres opcionals",
"Only the poll maker can see the poll's results": "Només el creador de l'enquesta pot veure els resultats de l'enquesta",
"More informations here:": "Més informació aquí:",
"Limit the amount of voters per option": "Limita la quantitat de votants per opció",
"Go to step 2": "Ves al pas 2",
"Customize the URL": "Personalitza l'URL",
"All voters can modify any vote": "Tots els votants poden modificar qualsevol vot",
"ValueMax instructions": "votants per opcions "
},
"PollInfo": {
"Votes protected by password": "Vots protegits amb contrasenya",
"Votes and comments are locked": "Els vots i els comentaris estan blocats",
"Title": "Títol de l'enquesta",
"Simple editor": "Editor simple",
"Save the new title": "Desa el títol nou",
"Save the new rules": "Desa les regles noves",
"Save the new name": "Desa el nom nou",
"Save the new expiration date": "Desa la nova data de venciment",
"Save the email address": "Desa l'adreça de correu electrònic",
"Save the description": "Desa la descripció",
"Rich editor": "Editor enriquit",
"Results are visible": "Els resultats són visibles",
"Results are hidden": "Els resultats són ocults",
"Remove the poll": "Elimina l'enquesta",
"Remove password": "Elimina la contrasenya",
"Remove all the votes": "Elimina tots els vots",
"Remove all the comments": "Elimina tots els comentaris",
"Public link of the poll": "Enllaç públic de l'enquesta",
"Print": "Imprimeix",
"Poll rules": "Regles de l'enquesta",
"Password protected": "Contrasenya protegida",
"Only votes are protected": "Només els vots estan protegits",
"No password": "Sense contrasenya",
"Initiator of the poll": "Creador de l'enquesta",
"Export to CSV": "Exporta a CSV",
"Expiration date": "Data de caducitat",
"Email": "Adreça electrònica",
"Edit the title": "Edita el títol",
"Edit the poll rules": "Edita les regles d'enquesta",
"Edit the name": "Edita el nom",
"Edit the expiration date": "Edita la data de venciment",
"Edit the email adress": "Edita l'adreça de correu electrònic",
"Edit the description": "Edita la descripció",
"Cancel the title edit": "Cancel·la l'edició del títol",
"Cancel the rules edit": "Cancel·la l'edició de les regles",
"Cancel the name edit": "Cancel·la l'edició del nom",
"Cancel the expiration date edit": "Cancel·la l'edició de la data de venciment",
"Cancel the email address edit": "Cancel·la l'edició de l'adreça electrònica",
"Cancel the description edit": "Cancel·la l'edició de la descripció",
"Admin link of the poll": "Enllaç d'administració per a l'enquesta"
},
"Poll results": {
"polled users": "usuaris enquestats",
"polled user": "usuari enquestat",
"Votes of the poll": "Vots",
"Vote yes for": "Vota «sí» per a",
"Vote no for": "Vota «no» per a",
"The bests choices at this time are:": "Ara per ara, les opcions més votades són:",
"The best choice at this time is:": "Ara per ara, l'opció més votada és:",
"Scroll to the right": "Desplaça't cap a la dreta",
"Scroll to the left": "Desplaça't cap a l'esquerra",
"Save the choices": "Desa les opcions",
"Remove the line:": "Suprimeix la línia:",
"Link to edit this particular line has been copied inside the clipboard!": "L'enllaç per a editar aquesta línia en particular s'ha copiat al porta-retalls!",
"Link to edit this particular line": "Enllaç per a editar aquesta línia en particular",
"Edit the line: %s": "Edita la línia: %s",
"Display the chart of the results": "Mostra el gràfic dels resultats",
"Chart": "Gràfic",
"Best choices": "Millors eleccions",
"Best choice": "Millor elecció",
"Addition": "Total",
"Vote ifneedbe for": "Vota en reserva de"
},
"Password": {
"You have to provide a password to access the poll.": "Cal que proporcioneu una contrasenya per a accedir a l'enquesta.",
"You have to provide a password so you can participate to the poll.": "Cal que proporcioneu una contrasenya perquè pugeu participar en l'enquesta.",
"Wrong password": "La contrasenya és errònia",
"Submit access": "Enviar accés",
"Password": "Contrasenya"
},
"Maintenance": {
"is currently under maintenance.": "actualment està en manteniment.",
"The application": "L'aplicació",
"Thank you for your understanding.": "Gràcies per la vostra comprensió."
},
"Mail": {
"wrote a comment.\nYou can find your poll at the link": "ha escrit un comentari. <br/> Podeu visitar l'enquesta a l'enllaç",
"updated a vote.\nYou can find your poll at the link": "s'ha actualitzat una votació. <br/> Podeu visitar l'enquesta en l'enllaç",
"hast just created a poll called": "acaba de crear una enquesta anomenada",
"filled a vote.\nYou can find your poll at the link": "ha votat. <br/> Podeu visitar l'enquesta a l'enllaç",
"This is the message you have to send to the people you want to poll. \nNow, you have to send this message to everyone you want to poll.": "Aquest és el missatge que es reenviarà als participants de l'enquesta.",
"Thanks for your trust.": "Gràcies per la vostra confiança.",
"Someone just delete your poll %s.": "Algú acaba d'eliminar la vostra enquesta «%s».",
"Someone just change your poll available at the following link %s.": "Algú ha canviat l'enquesta de l'enllaç següent <a href=\"%1$s\">%1$s</a>.",
"Poll's participation: %s": "Participació en l'enquesta: %s",
"Notification of poll: %s": "Notificació de l'enquesta: %s",
"For sending to the polled users": "Enllaç per als participants",
"FOOTER": "\"El camí és llarg, però el camí està clar ...\" <br/> Framasoft només viu de les vostres donacions. <br/> Gràcies per endavant pel vostre suport https://soutenir.framasoft.org",
"[ADMINISTRATOR] New settings for your poll": "[ADMINISTRATOR] Configuració nova de la vostra enquesta",
"You have changed the settings of your poll. \nYou can modify this poll with this link": "Heu canviat la configuració de la vostra enquesta. <br/>Podeu modificar aquesta enquesta amb aquest enllaç",
"This message should NOT be sent to the polled people. It is private for the poll's creator.\n\nYou can now modify it at the link above": "Aquest missatge NO sha denviar als participants a lenquesta. Hauríeu de mantenir-lo privat. <br/><br/>Podeu modificar la vostra enquesta a lenllaç anterior",
"Thanks for filling the poll at the link above": "Si us plau, ompliu lenquesta a lenllaç anterior",
"Author's message": "Missatge de lautor"
},
"Language selector": {
"Select the language": "Trieu la llengua",
"Change the language": "Canvia la llengua"
},
"Installation": {
"ResponseMail": "Respon a l'adreça de correu",
"MigrationTable": "Taula de migració",
"Install": "Instal·la",
"General": "General",
"DefaultLanguage": "Llengua predeterminada",
"DbUser": "Usuari",
"DbPrefix": "Prefix",
"DbPassword": "Contrasenya",
"Database": "Nom de la base de dades",
"CleanUrl": "Neteja l'URL",
"AppName": "Nom de l'aplicació",
"AppMail": "Adreça de correu d'administrador",
"DbConnectionString": "Cadena de connexió"
},
"Homepage": {
"Where are my polls": "On són les enquestes?",
"Schedule an event": "Programeu un esdeveniment",
"Make a classic poll": "Feu una enquesta estàndard"
},
"Generic": {
"with": "amb",
"votes": "vots",
"vote": "vota",
"seconds": "segons",
"months": "mesos",
"for": "per a",
"days": "dies",
"Your name": "El vostre nom",
"Your email address": "La vostra adreça de correu electrònic",
"Yes": "Sí",
"Validate": "Valida",
"Time": "Hora",
"Search": "Cerca",
"Save": "Desa",
"Remove": "Elimina",
"Poll": "Enquesta",
"Page generated in": "Pàgina generada en",
"No": "No",
"Next": "Pròxim",
"Markdown": "Markdown",
"Link": "Enllaç",
"Legend:": "Llegenda:",
"Ifneedbe": "Si és necessari",
"Home": "Inici",
"Edit": "Edita",
"Description": "Descripció",
"Day": "Dia",
"Date": "Data",
"Creation date:": "Data de creació:",
"Close": "Tanca",
"Classic": "Clàssic",
"Choice": "Elecció",
"Cancel": "Cancel·la",
"Back to the homepage of": "Torna a la pàgina d'inici de",
"Back": "Enrere",
"Add": "Afegeix",
"(in the format name@mail.com)": "(en format nom@mail.com)",
"Make your polls": "Feu el vostre sondeig",
"Framadate is an online service for planning an appointment or make a decision quickly and easily.": "Framadate és un servei en línia per planificar una cita o prendre una decisió de forma ràpida i senzilla.",
"Caption": "Títol",
"ASTERISK": "*",
"Unknown": "Desconegut"
},
"FindPolls": {
"Send me my polls": "Envia'm les enquestes",
"Polls sent": "Enquestes enviades",
"PS: this email has been sent because you or someone else asked to get back the polls created with your email address.": "PS: aquest correu electrònic s'ha enviat perquè heu sol·licitat (o algú altre) recuperar les enquestes creades amb la vostra adreça de correu electrònic.",
"List of your polls": "Llista de les vostres enquestes",
"Here is the list of the polls that you manage on %s:": "Aquesta és la llista de les enquestes que gestioneu a %s:",
"Have a good day!": "Que tingueu un bon dia!",
"If you weren't the source of this action and if you think this is an abuse of the service, please notify the administrator on %s.": "Si no éreu lorigen daquesta acció i creieu que es tracta dun abús del servei, notifiqueu ladministrador al %s."
},
"Error": {
"Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.": "El vostre vot no es tindrà en compte, perquè algú ha votat en el mateix moment i això entra en conflicte amb les vostres opcions i les condicions de l'enquesta. Torneu-ho a provar.",
"You can't select more than %d dates": "No podeu seleccionar més de %d dates",
"You can't create a poll with hidden results with the following edition option:": "No podeu crear una enquesta amb resultats ocults amb la següent opció:· ",
"You already voted": "Ja heu votat",
"Update vote failed": "S'ha produït un error en actualitzar el vot",
"This poll doesn't exist !": "Aquesta enquesta no existeix!",
"There is a problem with your choices": "Hi ha un problema amb les vostres eleccions",
"The name you've chosen already exist in this poll!": "El nom que heu introduït ja existeix!",
"The name is invalid.": "El nom no és vàlid.",
"The column already exists": "La columna ja existeix",
"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.": "L'adreça no és correcta. Has d'introduir una adreça de correu electrònic vàlida (com ara r.stallman@outlock.com) per a rebre l'enllaç a l'enquesta.",
"Something is wrong with the format: name shouldn't have any spaces before or after": "Alguna cosa està malament amb el format: el nom no hauria de tenir cap espai abans ni després",
"Something is wrong with the format: customized urls should only consist of alphanumeric characters and hyphens.": "Alguna cosa està malament amb el format: els URL personalitzats només han de consistir en caràcters alfanumèrics i guions.",
"Something is wrong with the format": "Alguna cosa està malament amb el format",
"Something is going wrong...":"Alguna cosa no ha anat bé...",
"Poll id already used": "Ja s'utilitza l'identificador",
"Poll has been updated before you vote": "L'enquesta s'ha actualitzat abans que votéssiu",
"Passwords do not match": "Les contrasenyes no coincideixen.",
"Password is empty": "La contrasenya és buida.",
"No polls found": "No s'ha trobat cap enquesta",
"MISSING_VALUES": "Valors que manquen",
"Javascript is disabled on your browser. Its activation is required to create a poll": "JavaScript està desactivat al vostre navegador. Cal activar-lo per a crear una enquesta.",
"Forbidden!": "Prohibit!",
"Failed to save poll": "No ha pogut desar l'enquesta",
"Failed to insert the comment!": "No s'ha pogut inserí el comentari!",
"Failed to delete the vote!": "No s'ha pogut eliminar el vot!",
"Failed to delete the poll": "No s'ha pogut eliminar l'enquesta",
"Failed to delete the comment": "No s'han pogut eliminar el comentari",
"Failed to delete column": "No s'ha pogut eliminar la columna",
"Failed to delete all votes": "No s'han pogut eliminar tots els vots",
"Failed to delete all comments": "No s'han pogut eliminar tots els comentaris",
"Error!": "Error!",
"Enter an email address": "Introduïu una adreça de correu electrònic",
"Enter a title": "Intrdoduïu un títol",
"Enter a name and a comment!": "Introduiïu un nom i un comentari!",
"Enter a name": "Introduïu un nom",
"Cookies are disabled on your browser. Theirs activation is required to create a poll.": "Les galetes estan desactivades al vostre navegador. Cal que estiguin activades per a poder crear una enquesta.",
"Comment failed": "Error de comentari",
"Can't create the config.php file in '%s'.": "No es pot crear el fitxer config.php a «%s».",
"Can't create an empty column.": "No es pot crear una columna buida.",
"CANT_CONNECT_TO_DATABASE": "No es pot connectar a la base de dades",
"Adding vote failed": "S'ha produït un error en afegir el vot",
"You haven't filled the first section of the poll creation.": "No heu omplert la primera secció de la enquesta.",
"This id is not allowed": "Aquest identificador no està permès",
"Framadate is not properly installed, please check the \"INSTALL\" to setup the database before continuing.": "Framadate no està instal·lat correctament, consulteu el fitxer “INSTAL·LAR” per obtenir instruccions sobre la configuració de la base de dades abans de continuar.",
"Error on amount of voters limitation : value must be an integer greater than 0": "Error per la quantitat de votants: el valor ha de ser un nombre enter superior a 0"
},
"1st section": {
"Discuss and make a decision": "Discutiu i preneu una decisió",
"Define dates or subjects to choose": "Definiu les dates o els temes per triar",
"view an example?": "Voleu veure un exemple?",
"What is that?": "Què és Framadate?",
"Send the poll link to your friends or colleagues": "Envieu l'enllaç de l'enquesta als amics o col·legues",
"Make a poll": "Creeu una enquesta",
"Here is how it works:": "Així és com funciona:",
"Framadate is an online service for planning an appointment or make a decision quickly and easily. No registration is required.": "Framadate és un servei en línia per a planificar una cita o prendre una decisió de forma ràpida i senzilla. No cal registrar-s'hi.",
"Do you want to": "Voleu"
},
"EditLink": {
"Your reminder has been successfully sent!": "El recordatori s'ha enviat amb èxit!",
"The email address is not correct.": "L'adreça de correu electrònic no és correcta.",
"Send": "Envia",
"REMINDER": "RECORDATORI",
"Please wait %d seconds before we can send an email to you then try again.": "Espereu %d segons abans que us puguem enviar un correu electrònic i torna-ho a provar.",
"Here is the link for editing your vote:": "Aquest és l'enllaç per a editar el vostre vot:",
"Edit link for poll \"%s\"": "Edita l'enllaç de l'enquesta «%s»",
"If you don't want to lose your personalized link, we can send it to your email.": "Si no voleu perdre el vostre enllaç personalitzat, el podem enviar al vostre correu electrònic."
},
"Date": {
"dd/mm/yyyy": "dd/mm/yyyy",
"datetime_parseformat": "d/m/Y",
"datepicker": "dd/mm/yyyy",
"Start date": "Data d'inici",
"SHORT": "%A %e %B %Y",
"Max dates count": "Podeu seleccionar com a màxim 4 mesos",
"MONTH_YEAR": "%B %Y",
"FULL": "%A, %B %e, %Y",
"End date": "Data final",
"DAY": "%a %e",
"DATETIME": "%m/%d/%Y %H:%M",
"DATE": "%Y-%m-%d",
"Add range dates": "Afegeix un interval de dates"
},
"Comments": {
"Your comment": "Comentari",
"Send the comment": "Envia el comentari",
"Remove the comment": "Elimina el comentari",
"Comments of polled people": "Comentaris",
"Comment added": "S'ha desat el comentari",
"Add a comment to the poll": "Afegeix un comentari a l'enquesta",
"anonyme": "anònim"
},
"Check": {
"date.timezone is set.": "date.timezone està establert.",
"Your PHP version (%s) is too old. This application needs at least PHP %s.": "La vostra versió PHP (%s) és massa vella. Aquesta aplicació necessita almenys PHP %s.",
"You need to enable the PHP Intl extension.": "Cal que habiliteu l'extensió de PHP Intl.",
"The template compile directory (%s) is writable.": "El directori de compilació de la plantilla (%s) es pot escriure.",
"The template compile directory (%s) is not writable.": "El directori de compilació de plantilla (%s) no es pot escriure.",
"The template compile directory (%s) doesn't exist in \"%s\". Retry the installation process.": "El directori de compilació de plantilles (%s) no existeix al «%s». Torneu a intentar el procés d'instal·lació.",
"The config file exists.": "Existeix el fitxer de configuració.",
"The config file directory (%s) is writable.": "El directori de fitxers de configuració (%s) es pot escriure.",
"The config file directory (%s) is not writable and the config file (%s) does not exists.": "El directori de fitxers de configuració (%s) no es pot escriure i el fitxer de configuració (%s) no existeix.",
"PHP version %s is enough (needed at least PHP %s).": "La versió %s PHP és suficient (almenys es necessita PHP %s).",
"PHP Intl extension is enabled.": "L'extensió de PHP Intl està habilitada.",
"OpenSSL extension loaded.": "L'extensió OpenSSL s'ha carregat.",
"Installation checking": "Comprovació de la instal·lació",
"Cookies are served from HTTP only.": "Les galetes només es poden enviar des d'HTTP.",
"Continue the installation": "Continua amb la instal·lació",
"Consider setting « session.cookie_httponly = 1 » inside your php.ini or add « php_value session.cookie_httponly 1 » to your .htaccess so that cookies can't be accessed through Javascript.": "Considereu definirr «session.cookie_httponly = 1» al fitxer php.ini o afegegiu «php_value session.cookie_httponly 1» al fitxer .htaccess de forma que les galetes no siguin accessibles des de Javascript.",
"Consider setting the date.timezone in php.ini.": "Considereu definir un valor per a date.timezone al fitxer php.ini.",
"Consider enabling the PHP extension OpenSSL for increased security.": "Per a més seguretat, considereu habilitar l'extensió OpenSSL de PHP OpenSSL.",
"Check again": "Comprova-ho una altra vegada"
},
"Admin": {
"polls in the database at this time": "enquestes a la base de dades en aquest moment",
"Votes": "Vots",
"Title": "Títol",
"Summary": "Resum",
"Success": "Èxit",
"Succeeded:": "Amb èxit:",
"Skipped:": "Omesos:",
"See the poll": "Mostra l'enquesta",
"Purged:": "Purgats:",
"Purge the polls": "Purga les enquestes",
"Purge": "Purga",
"Polls": "Enquestes",
"Poll ID": "ID de l'enquesta",
"Pages:": "Pàgines:",
"Nothing": "Res",
"Migration": "Migració",
"Logs": "Registre",
"Installation": "Instal·lació",
"Format": "Format de",
"Failed:": "Ha fallat:",
"Fail": "Fallada",
"Expiration date": "Data de caducitat",
"Email": "Adreça electrònica",
"Change the poll": "Canvia l'enquesta",
"Back to administration": "Torna a l'administració",
"Author": "Autor",
"Administration": "Administració",
"Actions": "Accions",
"Deleted the poll": "S'ha suprimit el sondeig",
"Confirm removal of the poll": "Confirmeu que elimineu el sondeig "
},
"3rd section": {
"the development site": "el lloc del desenvolupament",
"If you want to install the software for your own use and thus increase your independence, we help you on:": "Si voleu instal·lar el programari per al vostre ús i, per tant, augmentar la vostra independència, us podem ajudar en:",
"Cultivate your garden": "Cultiveu el vostre jardí",
"To participate in the software development, suggest improvements or simply download it, please visit ": "Per participar en el desenvolupament del programari, suggerir millores o simplement descarregar-lo, visiteu "
},
"2nd section": {
"a software developed by the University of Strasbourg. Today, it is devevoped by the association Framasoft.": "programari desenvolupat per la Universitat d'Estrasburg. Actualment desenvolupat per l'associació Framasoft.",
"The software": "El programari",
"It is governed by the": "Framadate està llicenciat sota la",
"CeCILL-B license": "Llicència CeCILL-B",
"This software needs javascript and cookies enabled. It is compatible with the following web browsers:": "Aquest programari necessita JavaScript i cookies habilitades. És compatible amb els navegadors web següents:",
"Framadate was initially based on ": "Framadate es va basar inicialment en "
}
}

View File

@ -208,8 +208,7 @@
"seconds": "Sekunden",
"vote": "Stimme",
"votes": "Stimmen",
"with": "mit",
"Unknown": "Unbekannt"
"with": "mit"
},
"Homepage": {
"Make a classic poll": "Klassische Umfrage",
@ -446,7 +445,6 @@
"studs": {
"Adding the vote succeeded": "Die Wertung wurde hinzugefügt",
"Deletion date:": "Löschdatum:",
"Download as ical/ics file": "Als ical/ics-Datei herunterladen",
"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",
"The poll is expired, it will be deleted soon.": "Der Zeitraum für die Umfrage ist überschritten, sie wird bald gelöscht werden.",

View File

@ -66,7 +66,6 @@
"Installation checking": "Installation checking",
"OpenSSL extension loaded.": "OpenSSL extension loaded.",
"PHP Intl extension is enabled.": "PHP Intl extension is enabled.",
"PHP mbstring extension is enabled.": "PHP mbstring extension is enabled.",
"PHP version %s is enough (needed at least PHP %s).": "PHP version %s is enough (needed at least PHP %s).",
"The config file directory (%s) is not writable and the config file (%s) does not exists.": "The config file directory (%s) is not writable and the config file (%s) does not exists.",
"The config file directory (%s) is writable.": "The config file directory (%s) is writable.",
@ -75,7 +74,6 @@
"The template compile directory (%s) is not writable.": "The template compile directory (%s) is not writable.",
"The template compile directory (%s) is writable.": "The template compile directory (%s) is writable.",
"You need to enable the PHP Intl extension.": "You need to enable the PHP Intl extension.",
"You need to enable the PHP mbstring extension.": "You need to enable the PHP mbstring extension.",
"Your PHP version (%s) is too old. This application needs at least PHP %s.": "Your PHP version (%s) is too old. This application needs at least PHP %s.",
"date.timezone is set.": "date.timezone is set."
},
@ -138,7 +136,6 @@
"Framadate is not properly installed, please check the \"INSTALL\" to setup the database before continuing.": "Framadate is not properly installed, please see the 'INSTALL' file for instructions on setting up the database before continuing.",
"Javascript is disabled on your browser. Its activation is required to create a poll.": "JavaScript is disabled on your browser. It is required to create a poll.",
"MISSING_VALUES": "Missing values",
"Name is limited to 32 characters": "Name is limited to 32 characters",
"No polls found": "No polls found",
"Password is empty": "Password is empty.",
"Passwords do not match": "Passwords do not match.",
@ -213,8 +210,7 @@
"seconds": "seconds",
"vote": "vote",
"votes": "votes",
"with": "with",
"Unknown": "Unknown"
"with": "with"
},
"Homepage": {
"Make a classic poll": "Make a standard poll",
@ -274,7 +270,7 @@
"You have to provide a password to access the poll.": "You have to provide a password to access the poll."
},
"Poll results": {
"Add a column": "Add a column",
"Add a column": "Add a column"
"Addition": "Total",
"Best choice": "Best choice",
"Best choices": "Best choices",
@ -455,7 +451,6 @@
"studs": {
"Adding the vote succeeded": "Vote added",
"Deletion date:": "Deletion date:",
"Download as ical/ics file": "Download as ical/ics file",
"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",
"The poll is expired, it will be deleted soon.": "The poll has expired, it will soon be deleted.",

View File

@ -208,8 +208,7 @@
"seconds": "seconds",
"vote": "vote",
"votes": "votes",
"with": "with",
"Unknown": "Nekonata"
"with": "with"
},
"Homepage": {
"Make a classic poll": "Make a standard poll",

View File

@ -208,8 +208,7 @@
"seconds": "segundos",
"vote": "voto",
"votes": "votos",
"with": "con",
"Unknown": "Desconocido"
"with": "con"
},
"Homepage": {
"Make a classic poll": "Crear una encuesta clásica",

View File

@ -15,7 +15,7 @@
"Framadate was initially based on ": "DateChaprilOrg est un Framadate initialement basé sur ",
"It is governed by the": "Il est régi par la",
"The software": "Le logiciel",
"This software needs javascript and cookies enabled. It is compatible with the following web browsers:": "Ce logiciel requiert lactivation du JavaScript et des cookies. Il est compatible avec les navigateurs web usuels.",
"This software needs javascript and cookies enabled. It is compatible with the following web browsers:": "Ce logiciel requiert lactivation du JavaScript et des cookies. Il est compatible avec les navigateurs web usuels",
"a software developed by the University of Strasbourg. Today, it is devevoped by the association Framasoft.": "un logiciel développé par l'Université de Strasbourg. Aujourd'hui, son développement est assuré par lassociation Framasoft."
},
"3rd section": {
@ -209,8 +209,7 @@
"seconds": "secondes",
"vote": "vote",
"votes": "votes",
"with": "avec",
"Unknown": "Inconnu"
"with": "avec"
},
"Homepage": {
"Make a classic poll": "Créer un sondage classique",
@ -312,7 +311,7 @@
"Export to CSV": "Export Tableur (CSV)",
"Initiator of the poll": "Auteur·rice du sondage",
"No password": "Pas de mot de passe",
"Only votes are protected": "Seuls les votes sont protégés",
"Only votes are protected": "Seul les votes sont protégés",
"Password protected": "Protégé par mot de passe",
"Poll rules": "Permissions du sondage",
"Print": "Imprimer",
@ -323,14 +322,14 @@
"Remove the poll": "Supprimer le sondage",
"Results are hidden": "Les résultats sont cachés",
"Results are visible": "Les résultats sont visibles",
"Rich editor": "Éditeur avancé",
"Rich editor": "Editeur avancé",
"Save the description": "Enregistrer la description",
"Save the email address": "Enregistrer le courriel",
"Save the new expiration date": "Enregistrer la date d'expiration",
"Save the new name": "Enregistrer l'auteur·rice",
"Save the new rules": "Enregistrer les nouvelles permissions",
"Save the new title": "Enregistrer le nouveau titre",
"Simple editor": "Éditeur simple",
"Simple editor": "Editeur simple",
"Title": "Titre du sondage",
"Votes and comments are locked": "Il n'est plus possible de voter",
"Votes protected by password": "Votes protégés par mot de passe"
@ -439,7 +438,7 @@
"Remove the column": "Effacer la colonne",
"Remove the comments": "Supprimer les commentaires",
"Remove the votes": "Supprimer les votes",
"The poll is created.": "Le sondage a été créé.",
"The poll is created.": "The poll was created.",
"Vote added": "Vote ajouté",
"Vote deleted": "Vote supprimé",
"Vote updated": "Vote mis à jour",

View File

@ -66,7 +66,6 @@
"Installation checking": "Vérifications de l'installation",
"OpenSSL extension loaded.": "L'extension PHP OpenSSL est chargée.",
"PHP Intl extension is enabled.": "L'extension PHP Intl est activée.",
"PHP mbstring extension is enabled.": "L'extension PHP mbstring est activée.",
"PHP version %s is enough (needed at least PHP %s).": "Version de PHP %s suffisante (nécessite au moins PHP %s).",
"The config file directory (%s) is not writable and the config file (%s) does not exists.": "Le dossier du fichier de configuration (%s) n'est pas accessible en écriture et le fichier de configuration (%s) n'existe pas.",
"The config file directory (%s) is writable.": "Le dossier du fichier de configuration (%s) est accessible en écriture.",
@ -75,7 +74,6 @@
"The template compile directory (%s) is not writable.": "Le dossier de compilation des templates (%s) n'est pas accessible en écriture.",
"The template compile directory (%s) is writable.": "Le dossier de compilation des templates (%s) est accessible en écriture.",
"You need to enable the PHP Intl extension.": "Vous devez activer l'extension PHP Intl.",
"You need to enable the PHP mbstring extension.": "Vous devez activer l'extension PHP mbstring.",
"Your PHP version (%s) is too old. This application needs at least PHP %s.": "Votre version de PHP (%s) est trop vieille. Cette application a besoin de PHP %s au moins.",
"date.timezone is set.": "date.timezone est défini."
},
@ -138,7 +136,6 @@
"Framadate is not properly installed, please check the \"INSTALL\" to setup the database before continuing.": "Framadate n'est pas installé correctement, lisez le fichier INSTALL pour configurer la base de données avant de continuer.",
"Javascript is disabled on your browser. Its activation is required to create a poll.": "JavaScript est désactivé sur votre navigateur. Son activation est requise pour la création d'un sondage.",
"MISSING_VALUES": "Il manque des valeurs",
"Name is limited to 32 characters": "Le nom est limité à 32 caractères",
"No polls found": "Aucun sondage n'a été trouvé",
"Password is empty": "Le mot de passe est vide.",
"Passwords do not match": "Les mots de passe ne correspondent pas.",
@ -212,8 +209,7 @@
"seconds": "secondes",
"vote": "vote",
"votes": "votes",
"with": "avec",
"Unknown": "Inconnu"
"with": "avec"
},
"Homepage": {
"Make a classic poll": "Créer un sondage classique",
@ -315,7 +311,7 @@
"Export to CSV": "Export Tableur (CSV)",
"Initiator of the poll": "Auteur·rice du sondage",
"No password": "Pas de mot de passe",
"Only votes are protected": "Seuls les votes sont protégés",
"Only votes are protected": "Seul les votes sont protégés",
"Password protected": "Protégé par mot de passe",
"Poll rules": "Permissions du sondage",
"Print": "Imprimer",
@ -326,14 +322,14 @@
"Remove the poll": "Supprimer le sondage",
"Results are hidden": "Les résultats sont cachés",
"Results are visible": "Les résultats sont visibles",
"Rich editor": "Éditeur avancé",
"Rich editor": "Editeur avancé",
"Save the description": "Enregistrer la description",
"Save the email address": "Enregistrer le courriel",
"Save the new expiration date": "Enregistrer la date d'expiration",
"Save the new name": "Enregistrer l'auteur·rice",
"Save the new rules": "Enregistrer les nouvelles permissions",
"Save the new title": "Enregistrer le nouveau titre",
"Simple editor": "Éditeur simple",
"Simple editor": "Editeur simple",
"Title": "Titre du sondage",
"Votes and comments are locked": "Il n'est plus possible de voter",
"Votes protected by password": "Votes protégés par mot de passe"
@ -442,7 +438,7 @@
"Remove the column": "Effacer la colonne",
"Remove the comments": "Supprimer les commentaires",
"Remove the votes": "Supprimer les votes",
"The poll is created.": "Le sondage a été créé.",
"The poll is created.": "The poll was created.",
"Vote added": "Vote ajouté",
"Vote deleted": "Vote supprimé",
"Vote updated": "Vote mis à jour",
@ -454,7 +450,6 @@
"studs": {
"Adding the vote succeeded": "Ajout du vote réussi",
"Deletion date:": "Date de suppression :",
"Download as ical/ics file": "Télécharger en tant que fichier ical/ics",
"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·rice a verrouillé ce sondage. Les votes et commentaires sont gelés, il n'est plus possible de participer",
"The poll is expired, it will be deleted soon.": "Le sondage a expiré, il sera bientôt supprimé.",

View File

@ -1,454 +0,0 @@
{
"1st section": {
"Define dates or subjects to choose": "Define dates or subjects to choose from",
"Discuss and make a decision": "Discuss and make a decision",
"Do you want to": "Do you want to",
"Framadate is an online service for planning an appointment or make a decision quickly and easily. No registration is required.": "Framadate is an online service for planning an appointment or making a decision quickly and easily. No registration is required.",
"Here is how it works:": "Here is how it works:",
"Make a poll": "Create a poll",
"Send the poll link to your friends or colleagues": "Send the poll link to your friends or colleagues",
"What is that?": "What is Framadate?",
"view an example?": "view an example?"
},
"2nd section": {
"CeCILL-B license": "CeCILL-B license",
"Framadate was initially based on ": "Framadate was initially based on ",
"It is governed by the": "Framadate is licensed under the",
"The software": "The software",
"This software needs javascript and cookies enabled. It is compatible with the following web browsers:": "This software needs JavaScript and cookies enabled. It is compatible with the following web browsers:",
"a software developed by the University of Strasbourg. Today, it is devevoped by the association Framasoft.": "software developed by the University of Strasbourg. These days, it is developed by the Framasoft association."
},
"3rd section": {
"Cultivate your garden": "Cultivate your garden",
"If you want to install the software for your own use and thus increase your independence, we help you on:": "If you want to install the software for your own use and thus increase your independence, we can help you at:",
"To participate in the software development, suggest improvements or simply download it, please visit ": "To participate in the software development, suggest improvements or simply download it, please visit ",
"the development site": "the development site"
},
"Admin": {
"Actions": "Accións",
"Administration": "Administración",
"Author": "Autor",
"Back to administration": "Volver á admininistración",
"Change the poll": "Mudar enquisa",
"Confirm removal of the poll": "Confirm removal of your poll",
"Deleted the poll": "Eliminar a enquisa",
"Email": "Email",
"Expiration date": "Expiry date",
"Fail": "Fallo",
"Failed:": "Con fallo:",
"Format": "Formato",
"Installation": "Instalación",
"Logs": "Rexistros",
"Migration": "Migración",
"Nothing": "Nada",
"Pages:": "Páxinas:",
"Poll ID": "ID da enquisa",
"Polls": "Enquisas",
"Purge": "Purgar",
"Purge the polls": "Purgar as enquisas",
"Purged:": "Purgado:",
"See the poll": "Ver a enquisa",
"Skipped:": "Ignorado:",
"Succeeded:": "Con éxito:",
"Success": "Éxito",
"Summary": "Sumario",
"Title": "Title of the poll",
"Votes": "Votos",
"polls in the database at this time": "enquisas na base de datos neste momento"
},
"Check": {
"Check again": "Check again",
"Consider enabling the PHP extension OpenSSL for increased security.": "Consider enabling the PHP extension OpenSSL for increased security.",
"Consider setting the date.timezone in php.ini.": "Consider setting the date.timezone in php.ini.",
"Consider setting « session.cookie_httponly = 1 » inside your php.ini or add « php_value session.cookie_httponly 1 » to your .htaccess so that cookies can't be accessed through Javascript.": "Consider setting « session.cookie_httponly = 1 » inside your php.ini or add « php_value session.cookie_httponly 1 » to your .htaccess so that cookies can't be accessed through Javascript.",
"Continue the installation": "Continue the installation",
"Cookies are served from HTTP only.": "Cookies are served from HTTP only.",
"Installation checking": "Installation checking",
"OpenSSL extension loaded.": "OpenSSL extension loaded.",
"PHP Intl extension is enabled.": "PHP Intl extension is enabled.",
"PHP version %s is enough (needed at least PHP %s).": "PHP version %s is enough (needed at least PHP %s).",
"The config file directory (%s) is not writable and the config file (%s) does not exists.": "The config file directory (%s) is not writable and the config file (%s) does not exists.",
"The config file directory (%s) is writable.": "The config file directory (%s) is writable.",
"The config file exists.": "The config file exists.",
"The template compile directory (%s) doesn't exist in \"%s\". Retry the installation process.": "The template compile directory (%s) doesn't exist in \"%s\". Retry the installation process.",
"The template compile directory (%s) is not writable.": "The template compile directory (%s) is not writable.",
"The template compile directory (%s) is writable.": "The template compile directory (%s) is writable.",
"You need to enable the PHP Intl extension.": "You need to enable the PHP Intl extension.",
"Your PHP version (%s) is too old. This application needs at least PHP %s.": "Your PHP version (%s) is too old. This application needs at least PHP %s.",
"date.timezone is set.": "date.timezone is set."
},
"Comments": {
"Add a comment to the poll": "Add a comment to the poll",
"Comment added": "Comment saved",
"Comments of polled people": "Comments",
"Remove the comment": "Remove comment",
"Send the comment": "Submit comment",
"Your comment": "Comment",
"anonyme": "anonyme"
},
"Date": {
"Add range dates": "Add range dates",
"DATE": "%Y-%m-%d",
"DATETIME": "%m/%d/%Y %H:%M",
"DAY": "%a %e",
"End date": "End date",
"FULL": "%A, %B %e, %Y",
"MONTH_YEAR": "%B %Y",
"Max dates count": "You can select at most 4 months",
"SHORT": "%A %e %B %Y",
"Start date": "Start date",
"datepicker": "yyyy-mm-dd",
"datetime_parseformat": "Y-m-d",
"dd/mm/yyyy": "jj/mm/aaaa"
},
"EditLink": {
"Edit link for poll \"%s\"": "Edit link for poll \"%s\"",
"Here is the link for editing your vote:": "Here is the link for editing your vote:",
"If you don't want to lose your personalized link, we can send it to your email.": "If you don't want to lose your personalized link, we can send it to your email.",
"Please wait %d seconds before we can send an email to you then try again.": "Please wait %d seconds before we can send an email to you then try again.",
"REMINDER": "REMINDER",
"Send": "Send",
"The email address is not correct.": "The email address is not correct.",
"Your reminder has been successfully sent!": "Your reminder has been successfully sent!"
},
"Error": {
"Adding vote failed": "Adding vote failed",
"CANT_CONNECT_TO_DATABASE": "Unable to connect to database",
"Can't create an empty column.": "Can't create an empty column.",
"Can't create the config.php file in '%s'.": "Can't create the config.php file in '%s'.",
"Comment failed": "Comment failed",
"Cookies are disabled on your browser. Theirs activation is required to create a poll.": "Cookies are disabled on your browser. They are required to be able to create a poll.",
"Enter a name": "Enter a name",
"Enter a name and a comment!": "Enter a name and a comment!",
"Enter a title": "Enter a title",
"Enter an email address": "Enter an email address",
"Error on amount of voters limitation : value must be an integer greater than 0": "Error on amount of voters limitation: Value must be an integer greater than 0",
"Error!": "Error!",
"Failed to delete all comments": "Failed to delete all comments",
"Failed to delete all votes": "Failed to delete all votes",
"Failed to delete column": "Failed to delete column",
"Failed to delete the comment": "Failed to delete the comment",
"Failed to delete the poll": "Failed to delete the poll",
"Failed to delete the vote!": "Failed to delete the vote!",
"Failed to insert the comment!": "Failed to insert the comment!",
"Failed to save poll": "Failed to save poll",
"Forbidden!": "Forbidden!",
"Framadate is not properly installed, please check the \"INSTALL\" to setup the database before continuing.": "Framadate is not properly installed, please see the 'INSTALL' file for instructions on setting up the database before continuing.",
"Javascript is disabled on your browser. Its activation is required to create a poll.": "JavaScript is disabled on your browser. It is required to create a poll.",
"MISSING_VALUES": "Missing values",
"No polls found": "No polls found",
"Password is empty": "Password is empty.",
"Passwords do not match": "Passwords do not match.",
"Poll has been updated before you vote": "Poll has been updated before you vote",
"Poll id already used": "Identifier is already used",
"Something is going wrong...": "Something has gone wrong...",
"Something is wrong with the format": "Something is wrong with the format",
"Something is wrong with the format: customized urls should only consist of alphanumeric characters and hyphens.": "Something is wrong with the format: Customized URLs should only consist of alphanumeric characters and hyphens.",
"Something is wrong with the format: name shouldn't have any spaces before or after": "Something is wrong with the format: name shouldn't have any spaces before or after",
"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.": "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.",
"The column already exists": "The column already exists",
"The name is invalid.": "The name is invalid.",
"The name you've chosen already exist in this poll!": "The name you've chosen already exists in this poll!",
"There is a problem with your choices": "There is a problem with your choices",
"This poll doesn't exist !": "This poll doesn't exist!",
"Update vote failed": "Update vote failed",
"You already voted": "You already voted",
"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: ",
"You can't select more than %d dates": "You can't select more than %d dates",
"You haven't filled the first section of the poll creation.": "You haven't filled in the first section of the poll.",
"Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry.": "Your vote wasn't counted, because someone voted in the meantime and it conflicted with your choices and the poll conditions. Please retry."
},
"FindPolls": {
"Have a good day!": "Have a good day!",
"Here is the list of the polls that you manage on %s:": "Here is the list of the polls that you manage on %s:",
"If you weren't the source of this action and if you think this is an abuse of the service, please notify the administrator on %s.": "If you weren't the source of this action and if you think this is an abuse of the service, please notify the administrator on %s.",
"List of your polls": "List of your polls",
"PS: this email has been sent because you or someone else asked to get back the polls created with your email address.": "PS: this email has been sent because you or someone else asked to get back the polls created with your email address.",
"Polls sent": "Enquisas enviadas",
"Send me my polls": "Envíame as miñas enquisas"
},
"Generic": {
"(in the format name@mail.com)": "(in the format name@mail.com)",
"ASTERISK": "*",
"Add": "Engadir",
"Back": "Volver",
"Back to the homepage of": "Volver á páxina de inicio de",
"Cancel": "Cancelar",
"Caption": "Caption",
"Choice": "Escolla",
"Classic": "Clásica",
"Close": "Pechar",
"Creation date:": "Data de creación:",
"Date": "Data",
"Day": "Día",
"Description": "Descrición",
"Edit": "Modificar",
"Framadate is an online service for planning an appointment or make a decision quickly and easily.": "Framadate is an online service for planning an appointment or make a decision quickly and easily.",
"Home": "Inicio",
"Ifneedbe": "Se é preciso",
"Legend:": "Lenda:",
"Link": "Ligazón",
"Make your polls": "Fai as túas enquisas",
"Markdown": "Markdown",
"Next": "Seguinte",
"No": "Non",
"Page generated in": "Páxina xerada en",
"Poll": "Enquisa",
"Remove": "Eliminar",
"Save": "Gardar",
"Search": "Buscar",
"Time": "Hora",
"Validate": "Validar",
"Yes": "Si",
"Your email address": "Teu enderezo de correo electrónico",
"Your name": "Teu nome",
"days": "días",
"for": "for",
"months": "meses",
"seconds": "segundos",
"vote": "voto",
"votes": "votos",
"with": "con"
},
"Homepage": {
"Make a classic poll": "Facer unha enquisa clásica",
"Schedule an event": "Programar unha actividade",
"Where are my polls": "Onde están as miñas enquisas?"
},
"Installation": {
"AppMail": "Administrator mail address",
"AppName": "Application name",
"CleanUrl": "Clean URL",
"Database": "Database name",
"DbConnectionString": "Connection string",
"DbPassword": "Password",
"DbPrefix": "Prefix",
"DbUser": "User",
"DefaultLanguage": "Default language",
"General": "General",
"Install": "Install",
"MigrationTable": "Migration table",
"ResponseMail": "Respond-to mail address"
},
"Language selector": {
"Change the language": "Mudar idioma",
"Select the language": "Escoller idioma"
},
"Mail": {
"Author's message": "Author's message",
"FOOTER": "",
"For sending to the polled users": "Participant link",
"Notification of poll: %s": "Notification of poll: %s",
"Poll's participation: %s": "Poll participation: %s",
"Someone just change your poll available at the following link %s.": "Someone just changed your poll at the following link <a href=\"%1$s\">%1$s</a>.",
"Someone just delete your poll %s.": "Someone just deleted your poll \"%s\".",
"Thanks for filling the poll at the link above": "Please fill in the poll at the link above",
"Thanks for your trust.": "Thank you for your trust.",
"This is the message you have to send to the people you want to poll. \nNow, you have to send this message to everyone you want to poll.": "This is the message to forward to the poll participants.",
"This message should NOT be sent to the polled people. It is private for the poll's creator.\n\nYou can now modify it at the link above": "This message should NOT be sent to the poll participants. You should keep it private. <br/><br/>You can modify your poll at the link above",
"You have changed the settings of your poll. \nYou can modify this poll with this link": "You have changed the settings of your poll.<br/>You can modify this poll with this link",
"[ADMINISTRATOR] New settings for your poll": "[ADMINISTRATOR] New settings for your poll",
"filled a vote.\nYou can find your poll at the link": "added a vote.<br/>You can visit your poll at the link",
"hast just created a poll called": "has just created a poll called",
"updated a vote.\nYou can find your poll at the link": "updated a vote.<br/>You can visit your poll at the link",
"wrote a comment.\nYou can find your poll at the link": "wrote a comment.<br/>You can visit your poll at the link"
},
"Maintenance": {
"Thank you for your understanding.": "Grazas pola túa comprensión.",
"The application": "O aplicativo",
"is currently under maintenance.": "está neste momento en mantemento."
},
"Password": {
"Password": "Password",
"Submit access": "Submit access",
"Wrong password": "Wrong password",
"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.",
"You have to provide a password to access the poll.": "You have to provide a password to access the poll."
},
"Poll results": {
"Addition": "Total",
"Best choice": "Best choice",
"Best choices": "Best choices",
"Chart": "Chart",
"Display the chart of the results": "Display the chart of the results",
"Edit the line: %s": "Edit line: %s",
"Link to edit this particular line": "Link to edit this particular line",
"Link to edit this particular line has been copied inside the clipboard!": "The link to edit this particular line has been copied to the clipboard!",
"Remove the line:": "Remove line:",
"Save the choices": "Save choices",
"Scroll to the left": "Scroll to the left",
"Scroll to the right": "Scroll to the right",
"The best choice at this time is:": "The current best choice is:",
"The bests choices at this time are:": "The current best choices are:",
"Vote ifneedbe for": "Vote \"ifneedbe\" for",
"Vote no for": "Vote \"no\" for",
"Vote yes for": "Vote \"yes\" for",
"Votes of the poll": "Votes",
"polled user": "polled user",
"polled users": "polled users"
},
"PollInfo": {
"Admin link of the poll": "Admin link for the poll",
"Cancel the description edit": "Cancel the description edit",
"Cancel the email address edit": "Cancel the email address edit",
"Cancel the expiration date edit": "Cancel the expiration date edit",
"Cancel the name edit": "Cancel the name edit",
"Cancel the rules edit": "Cancel the rules edit",
"Cancel the title edit": "Cancel the title edit",
"Edit the description": "Edit the description",
"Edit the email adress": "Edit the email address",
"Edit the expiration date": "Edit the expiry date",
"Edit the name": "Edit name",
"Edit the poll rules": "Edit the poll rules",
"Edit the title": "Edit title",
"Email": "Email",
"Expiration date": "Expiry date",
"Export to CSV": "Export to CSV",
"Initiator of the poll": "Initiator of the poll",
"No password": "No password",
"Only votes are protected": "Only votes are protected",
"Password protected": "Password protected",
"Poll rules": "Poll rules",
"Print": "Print",
"Public link of the poll": "Public link to the poll",
"Remove all the comments": "Remove all the comments",
"Remove all the votes": "Remove all the votes",
"Remove password": "Remove password",
"Remove the poll": "Remove the poll",
"Results are hidden": "Results are hidden",
"Results are visible": "Results are visible",
"Rich editor": "Rich editor",
"Save the description": "Save the description",
"Save the email address": "Save the email address",
"Save the new expiration date": "Save the new expiration date",
"Save the new name": "Save the new name",
"Save the new rules": "Save the new rules",
"Save the new title": "Save the new title",
"Simple editor": "Simple editor",
"Title": "Title of the poll",
"Votes and comments are locked": "Votes and comments are locked",
"Votes protected by password": "Votes protected by password"
},
"Step 1": {
"All voters can modify any vote": "All voters can modify any vote",
"Customize the URL": "Customize the URL",
"Go to step 2": "Go to step 2",
"Limit the amount of voters per option": "Limit the amount of voters per option",
"More informations here:": "More informations here:",
"Only the poll maker can see the poll's results": "Only the poll maker can see the poll results",
"Optional parameters": "Optional parameters",
"Password choice": "Choice",
"Password confirmation": "Confirmation",
"Permissions": "Permissions",
"Poll creation (1 on 3)": "Poll creation (1 of 3)",
"Poll id": "Poll link",
"Poll id rules": "The identifier can contain letters, numbers and dashes \"-\".",
"Poll id warning": "By defining an identifier that can facilitate access to the poll for unwanted people. It is recommended to protect it with a password.",
"Poll password": "Password",
"Poll title": "Poll title",
"Required fields cannot be left blank.": "Required fields cannot be left blank.",
"The results are publicly visible": "The results are publicly visible",
"To make the description more attractive, you can use the Markdown format.": "To make the description more attractive, you can use the Markdown format.",
"To receive an email for each new comment": "Receive an email for each new comment",
"To receive an email for each new vote": "Receive an email for each new vote",
"Use a password to restrict access": "Use a password to restrict access",
"Value Max": "Value Max",
"ValueMax instructions": "voters per options ",
"Voters can modify their vote themselves": "Voters can modify their vote themselves",
"Votes cannot be modified": "Votes cannot be modified",
"You are in the poll creation section.": "You are in the poll creation section.",
"You can enable or disable the editor at will.": "You can enable or disable the editor at will."
},
"Step 2": {
"Back to step 1": "Return to step 1",
"Go to step 3": "Go to step 3"
},
"Step 2 classic": {
"Add a choice": "Add a choice",
"Add a link or an image": "Add a link or an image",
"Alternative text": "Alternative text",
"It's possible to propose links or images by using": "Links or images can be included using",
"Poll subjects (2 on 3)": "Poll options (2 of 3)",
"Remove a choice": "Remove a choice",
"These fields are optional. You can add a link, an image or both.": "These fields are optional. You can add a link, an image or both.",
"To make a generic poll you need to propose at least two choices between differents subjects.": "To create a poll you should provide at least two different choices.",
"URL of the image": "URL of the image",
"You can add or remove additional choices with the buttons": "You can add or remove choices with the buttons",
"the Markdown syntax": "Markdown syntax"
},
"Step 2 date": {
"Add a day": "Add a day",
"Add an hour": "Add a time slot",
"Choose the dates of your poll": "Choose dates for your poll",
"Copy hours of the first day": "Copy times from the first day",
"For each selected day, you can choose, or not, meeting hours (e.g.: \"8h\", \"8:30\", \"8h-10h\", \"evening\", etc.)": "For each selected day, you are free to suggest meeting times (e.g., \"8h\", \"8:30\", \"8h-10h\", \"evening\", etc.)",
"Poll dates (2 on 3)": "Poll dates (2 of 3)",
"Remove a day": "Remove a day",
"Remove all days": "Remove all days",
"Remove all hours": "Remove all times",
"Remove an hour": "Remove a time slot",
"Remove this day": "Remove this day",
"To schedule an event you need to propose at least two choices (two hours for one day or two days).": "To schedule an event you need to provide at least two choices (e.g., two time slots on one day or two days).",
"You can add or remove additionnal days and hours with the buttons": "You can add or remove additional days and times with the buttons"
},
"Step 3": {
"Archiving date:": "Expiry date:",
"Back to step 2": "Back to step 2",
"Confirm the creation of your poll": "Confirm the creation of your poll",
"Create the poll": "Create the poll",
"List of your choices": "List of options",
"Once you have confirmed the creation of your poll, you will be automatically redirected on the administration page of your poll.": "Once you have confirmed the creation of your poll, you will automatically be redirected to the poll's administration page.",
"Removal date and confirmation (3 on 3)": "Removal date and confirmation (1 of 3)",
"Then, you will receive quickly two emails: one contening the link of your poll for sending it to the voters, the other contening the link to the administration page of your poll.": "Then you will receive two emails: one containing the link of your poll for sending to the participants, the other containing the link to the poll administration page.",
"You can set a closer archiving date for it.": "You can set a specific expiry date for the poll.",
"Your poll will automatically be archived": "Your poll will automatically be archived",
"Your poll will be automatically archived in %d days.": "Your poll will be automatically archived in %d days.",
"after the last date of your poll.": "after the last date of your poll."
},
"adminstuds": {
"Add a column": "Add a column",
"All comments deleted": "All comments deleted",
"All votes deleted": "All votes deleted",
"As poll administrator, you can change all the lines of this poll with this button": "As poll administrator, you can change all the lines of this poll with this button",
"Back to the poll": "Back to the poll",
"Choice added": "Choice added",
"Column removed": "Column deleted",
"Column's adding": "Adding a column",
"Comment deleted": "Comment deleted",
"Confirm removal of all comments of the poll": "Confirm removal of all comments",
"Confirm removal of all votes of the poll": "Confirm removal of all votes",
"Confirm removal of the column.": "Confirm removal of the column.",
"Confirm removal of the poll": "Confirm removal of your poll",
"Delete the poll": "Delete the poll",
"Finally, you can change the informations of this poll like the title, the comments or your email address.": "Finally, you can change the properties of this poll such as the title, the comments or your email address.",
"If you just want to add a new hour to an existant date, put the same date and choose a new hour.": "If you just want to add a new time slot to an existing date, add that date here and choose a new time slot.",
"Keep comments": "Keep comments",
"Keep the comments": "Keep the comments",
"Keep the poll": "Keep the poll",
"Keep the votes": "Keep the votes",
"Keep this poll": "Keep this poll",
"Keep votes": "Keep votes",
"Poll fully deleted": "Poll fully deleted",
"Poll saved": "Poll saved",
"Remove the column": "Remove column",
"Remove the comments": "Remove the comments",
"Remove the votes": "Remove the votes",
"The poll is created.": "The poll was created.",
"Vote added": "Vote added",
"Vote deleted": "Vote deleted",
"Vote updated": "Vote updated",
"You can add a new scheduling date to your poll.": "You can add a new scheduling date to your poll.",
"Your poll has been removed!": "Your poll has been removed!",
"and add a new column with": "and add a new column with",
"remove a column or a line with": "remove a column or a line with"
},
"studs": {
"Adding the vote succeeded": "Vote added",
"Deletion date:": "Deletion date:",
"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",
"The poll is expired, it will be deleted soon.": "The poll has expired, it will soon be deleted.",
"Update vote succeeded": "Vote updated",
"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:": "Your vote has been saved, but please note: you need to keep this personalised link to be able to edit your vote."
}
}

Some files were not shown because too many files have changed in this diff Show More