Install: Create installation page

This commit is contained in:
Olivier PEREZ 2015-08-31 08:56:26 +02:00
parent a6d8342e77
commit a7727d8523
16 changed files with 642 additions and 15 deletions

61
admin/install.php Normal file
View File

@ -0,0 +1,61 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondate: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
use Framadate\Services\InstallService;
use Framadate\Utils;
// Define values in place of config.php (that does not exists yet)
const NOMAPPLICATION = 'Framadate';
const DEFAULT_LANGUAGE = 'fr';
const IMAGE_TITRE = 'images/logo-framadate.png';
const LOG_FILE = 'admin/stdout.log';
$ALLOWED_LANGUAGES = [
'fr' => 'Français',
'en' => 'English',
'es' => 'Español',
'de' => 'Deutsch',
'it' => 'Italiano',
];
require_once '../app/inc/init.php';
define('CONF_FILENAME', ROOT_DIR . '/app/inc/config.php');
if (file_exists(CONF_FILENAME)) {
header(('Location: ' . Utils::get_server_name()));
exit;
}
$error = null;
if (!empty($_POST)) {
$installService = new InstallService();
$result = $installService->install($_POST, $smarty);
if ($result['status'] === 'OK') {
header(('Location: ' . Utils::get_server_name() . '/admin/migration.php'));
exit;
} else {
$error = __('Error', $result['code']);
}
}
$smarty->assign('error', $error);
$smarty->assign('title', __('Admin', 'Installation'));
$smarty->assign('logsAreReadable', is_readable('../' . LOG_FILE));
$smarty->display('admin/install.tpl');

View File

@ -0,0 +1,136 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondate: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
namespace Framadate\Services;
use Framadate\Utils;
use Smarty;
/**
* This class helps to clean all inputs from the users or external services.
*/
class InstallService {
private $fields = array(
'General' =>
array(
'appName' => 'Framadate',
'appMail' => '',
'responseMail' => '',
'defaultLanguage' => 'fr',
'cleanUrl' => true
),
'Database configuration' =>
array(
'dbConnectionString' => 'mysql:host=HOST;dbname=SCHEMA;port=3306',
'dbUser' => 'root',
'dbPassword' => '',
'dbPrefix' => 'fd_',
'migrationTable' => 'framadate_migration'
)
);
function __construct() {}
public function install($data, Smarty &$smarty) {
// Check values are present
if (empty($data['appName']) || empty($data['appMail']) || empty($data['defaultLanguage']) || empty($data['dbConnectionString']) || empty($data['dbUser'])) {
return $this->error('MISSING_VALUES');
}
// Connect to database
$connect = $this->connectTo($data['dbConnectionString'], $data['dbUser'], $data['dbPassword']);
if (!$connect) {
return $this->error('CANT_CONNECT_TO_DATABASE');
}
// Create database schema
$this->createDatabaseSchema($connect);
// Write configuration to conf.php file
$this->writeConfiguration($data, $smarty);
return $this->ok();
}
function connectTo($connectionString, $user, $password) {
try {
$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;
} catch(\Exception $e) {
return null;
}
}
function writeConfiguration($data, Smarty &$smarty) {
foreach($this->fields as $groupKey=>$group) {
foreach ($group as $field=>$value) {
$smarty->assign($field, $data[$field]);
}
}
$content = $smarty->fetch('admin/config.tpl');
$this->writeToFile($content);
}
/**
* @param $content
*/
function writeToFile($content) {
file_put_contents(CONF_FILENAME, $content);
}
/**
* Execute SQL installation scripts.
*
* @param \PDO $connect
*/
function createDatabaseSchema($connect) {
$dir = opendir(ROOT_DIR . '/install/');
while ($dir && ($file = readdir($dir)) !== false) {
if ($file !== '.' && $file !== '..' && strpos($file, '.mysql.auto.sql')) {
$statement = file_get_contents(ROOT_DIR . '/install/' . $file);
$connect->exec($statement);
}
}
}
/**
* @return array
*/
function ok() {
return array(
'status' => 'OK',
'msg' => __f('Installation', 'Ended', Utils::get_server_name())
);
}
/**
* @param $msg
* @return array
*/
function error($msg) {
return array(
'status' => 'ERROR',
'code' => $msg
);
}
}

88
app/inc/XXconfig.php Normal file
View File

@ -0,0 +1,88 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondate: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
// Fully qualified domain name of your webserver.
// If this is unset or empty, the servername is determined automatically.
// You *have to set this* if you are running Framedate behind a reverse proxy.
// const APP_URL = '<www.mydomain.fr>';
// Application name
const NOMAPPLICATION = 'Développement OPZ';
// Database administrator email
const ADRESSEMAILADMIN = 'framadate-dev@olivierperez.fr';
// Email for automatic responses (you should set it to "no-reply")
const ADRESSEMAILREPONSEAUTO = 'no-reply@olivierperez.fr';
// Database user
const DB_USER= 'dev_framadate';
// Database password
const DB_PASSWORD = 'dev_framadate';
// Database server name, leave empty to use a socket
const DB_CONNECTION_STRING = 'mysql:host=localhost;dbname=framadate_dev;port=3306';
// Name of the table that store migration script already executed
const MIGRATION_TABLE = 'framadate_migration';
// Table name prefix
const TABLENAME_PREFIX = 'fd_';
// Default Language using POSIX variant of BC P47 standard (choose in $ALLOWED_LANGUAGES)
const DEFAULT_LANGUAGE = 'fr';
// List of supported languages, fake constant as arrays can be used as constants only in PHP >=5.6
$ALLOWED_LANGUAGES = [
'fr' => 'Français',
'en' => 'English',
'es' => 'Español',
'de' => 'Deutsch',
'it' => 'Italiano',
];
// Nom et emplacement du fichier image contenant le titre
const IMAGE_TITRE = 'images/logo-framadate.png';
// Clean URLs, boolean
const URL_PROPRE = false;
// Use REMOTE_USER data provided by web server
const USE_REMOTE_USER = true;
// Path to the log file
const LOG_FILE = 'admin/stdout.log';
// Days (after expiration date) before purge a poll
const PURGE_DELAY = 60;
// Config
$config = [
/* general config */
'use_smtp' => false, // use email for polls creation/modification/responses notification
/* home */
'show_what_is_that' => true, // display "how to use" section
'show_the_software' => true, // display technical information about the software
'show_cultivate_your_garden' => true, // display "developpement and administration" information
/* create_classic_poll.php / create_date_poll.php */
'default_poll_duration' => 180, // default values for the new poll duration (number of days).
/* create_classic_poll.php */
'user_can_add_img_or_link' => true, // user can add link or URL when creating his poll.
];

View File

@ -58,12 +58,6 @@ $ALLOWED_LANGUAGES = [
'it' => 'Italiano',
];
// Path to logo
const LOGOBANDEAU = '<relative path to the logo file>';
// Path to logo in PDF export
const LOGOLETTRE = '<relative path to the logo file for pdf>';
// Nom et emplacement du fichier image contenant le titre
const IMAGE_TITRE = 'images/logo-framadate.png';

View File

@ -34,13 +34,15 @@ if (ini_get('date.timezone') == '') {
define('ROOT_DIR', __DIR__ . '/../../');
require_once __DIR__ . '/constants.php';
require_once __DIR__ . '/config.php';
@include_once __DIR__ . '/config.php';
require_once __DIR__ . '/i18n.php';
// Smarty
require_once __DIR__ . '/smarty.php';
// Connection to database
$connect = new FramaDB(DB_CONNECTION_STRING, DB_USER, DB_PASSWORD);
RepositoryFactory::init($connect);
if (is_file(__DIR__ . '/config.php')) {
$connect = new FramaDB(DB_CONNECTION_STRING, DB_USER, DB_PASSWORD);
RepositoryFactory::init($connect);
}
$err = 0;

46
buildlang.php Normal file
View File

@ -0,0 +1,46 @@
<?php
include_once __DIR__ . '/app/inc/init.php';
?>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body><pre><?php
$goodLang = $_GET['good'];
$otherLang = $_GET['other'];
$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) {
$good[$sectionName][$key] = getFromOther($other, $key, $value, $otherLang);
}
}
echo json_encode($good, JSON_PRETTY_PRINT | ~(JSON_ERROR_UTF8 | JSON_HEX_QUOT | JSON_HEX_APOS));
function getFromOther($other, $goodKey, $default, $otherLang) {
foreach ($other as $sectionName => $section) {
foreach ($section as $key => $value) {
if (
strtolower($key) === strtolower($goodKey) ||
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;
}
}
}
echo '[-]' . $goodKey . "\n";
return strtoupper($otherLang) . '_' . $default;
}
?>
</pre>
</body>
</html>

69
compare.php Normal file
View File

@ -0,0 +1,69 @@
<?php
include_once __DIR__ . '/app/inc/init.php';
?>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body><pre><?php
$goodLang = $_GET['good'];
$testLang = $_GET['test'];
$good = json_decode(file_get_contents(__DIR__ . '/locale/' . $goodLang . '.json'), true);
$test = json_decode(file_get_contents(__DIR__ . '/locale/' . $testLang . '.json'), true);
$diffSection = false;
foreach ($good as $sectionName => $section) {
if (!isset($test[$sectionName])) {
echo '- section: ' . $sectionName . "\n";
$diffSection = true;
}
}
foreach ($test as $sectionName => $section) {
if (!isset($good[$sectionName])) {
echo '+ section: ' . $sectionName . "\n";
$diffSection = true;
}
}
if (!$diffSection and array_keys($good)!=array_keys($test)) {
var_dump(array_keys($good));
var_dump(array_keys($test));
} else {
echo 'All sections are in two langs.' . "\n";
}
$diff = array();
foreach ($good as $sectionName => $section) {
$diffSection = false;
foreach($section as $key=>$value) {
if (!isset($test[$sectionName][$key])) {
$diff[$sectionName]['-'][] = $key;
$diffSection = true;
}
}
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]);
}
}
foreach ($test as $sectionName => $section) {
foreach($section as $key=>$value) {
if (!isset($good[$sectionName][$key])) {
$diff[$sectionName]['+'][] = $key;
}
}
}
if (count($diff) > 0) {
var_dump($diff);
}
?>
</pre>
</body>
</html>

View File

@ -261,6 +261,7 @@
"Migration": "Migration",
"Purge": "Säuberung",
"Logs": "Verlauf",
"Installation": "Installation",
"Poll ID": "Umfrage-ID",
"Format": "Format",
"Title": "Titel",

View File

@ -259,6 +259,7 @@
"Migration": "Migration",
"Purge": "Purge",
"Logs": "Logs",
"Installation": "Installation",
"Poll ID": "Poll ID",
"Format": "Format",
"Title": "Title",

View File

@ -261,6 +261,7 @@
"Migration": "ES_Migration",
"Purge": "ES_Purge",
"Logs": "Histórico",
"Installation": "Instalación",
"Poll ID": "ES_ID sondage",
"Format": "ES_Format",
"Title": "ES_Titre",

View File

@ -36,7 +36,8 @@
"Choice": "Choix",
"Link": "Lien",
"Search": "Chercher",
"Creation date:": "Date de création :"
"Creation date:": "Date de création :",
"ASTERISK": "*"
},
"Date": {
"dd/mm/yyyy": "jj/mm/aaaa",
@ -261,6 +262,7 @@
"Migration": "Migration",
"Purge": "Purge",
"Logs": "Historique",
"Installation": "Installation",
"Poll ID": "ID sondage",
"Format": "Format",
"Title": "Titre",
@ -309,6 +311,21 @@
"Author's message": "Réservé à l'auteur",
"For sending to the polled users": "Pour diffusion aux sondés"
},
"Installation": {
"AppMail": "Adresse mail de l'application",
"AppName": "Nom de l'application",
"CleanUrl": "URL propres",
"Database": "Base de données",
"DbConnectionString": "Chaîne de connexion",
"DbPassword": "Mot de passe",
"DbPrefix": "Préfixe",
"DbUser": "Utilisateur",
"DefaultLanguage": "Langue par défaut",
"General": "Général",
"Install": "Installer",
"MigrationTable": "Table de migration",
"ResponseMail": "Mail de réponse"
},
"Error": {
"Error!": "Erreur !",
"Enter a title": "Il faut saisir un titre !",
@ -333,6 +350,8 @@
"Adding vote failed": "Ajout d'un vote échoué",
"Comment failed": "Commentaire échoué",
"You can't create a poll with hidden results with the following edition option:": "Vous ne pouvez pas créer de sondage avec résulats cachés avec les options d'éditions suivantes : ",
"Failed to delete column": "Échec de la suppression de colonne"
"Failed to delete column": "Échec de la suppression de colonne",
"MISSING_VALUES": "Il manque des valeurs",
"CANT_CONNECT_TO_DATABASE": "Impossible de se connecter à la base de données"
}
}

View File

@ -261,6 +261,7 @@
"Migration": "IT_Migration",
"Purge": "IT_Purge",
"Logs": "Log",
"Installation": "Installazione",
"Poll ID": "ID sondaggio",
"Format": "Formato",
"Title": "Titolo",

15
po2json.php Normal file
View File

@ -0,0 +1,15 @@
<?php
include_once __DIR__ . '/app/inc/init.php';
?>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body><pre><?php
$lang = 'fr_FR';
$po = file_get_contents(__DIR__ . '/locale/' . $lang . '/LC_MESSAGES/Studs.po');
$converter = new \o80\convert\Po2JsonConverter();
$json = $converter->convert($po);
print_r($json);
?></pre></body>
</html>

89
tpl/admin/config.tpl Normal file
View File

@ -0,0 +1,89 @@
<?php
/**
* This software is governed by the CeCILL-B license. If a copy of this license
* is not distributed with this file, you can obtain one at
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.txt
*
* Authors of STUdS (initial project): Guilhem BORGHESI (borghesi@unistra.fr) and Raphaël DROZ
* Authors of Framadate/OpenSondate: Framasoft (https://github.com/framasoft)
*
* =============================
*
* Ce logiciel est régi par la licence CeCILL-B. Si une copie de cette licence
* ne se trouve pas avec ce fichier vous pouvez l'obtenir sur
* http://www.cecill.info/licences/Licence_CeCILL-B_V1-fr.txt
*
* Auteurs de STUdS (projet initial) : Guilhem BORGHESI (borghesi@unistra.fr) et Raphaël DROZ
* Auteurs de Framadate/OpenSondage : Framasoft (https://github.com/framasoft)
*/
// Fully qualified domain name of your webserver.
// If this is unset or empty, the servername is determined automatically.
// You *have to set this* if you are running Framedate behind a reverse proxy.
// const APP_URL = '<www.mydomain.fr>';
// Application name
const NOMAPPLICATION = '{$appName}';
// Database administrator email
const ADRESSEMAILADMIN = '{$appMail}';
// Email for automatic responses (you should set it to "no-reply")
const ADRESSEMAILREPONSEAUTO = '{$responseMail}';
// Database server name, leave empty to use a socket
const DB_CONNECTION_STRING = '{$dbConnectionString}';
// Database user
const DB_USER= '{$dbUser}';
// Database password
const DB_PASSWORD = '{$dbPassword}';
// Table name prefix
const TABLENAME_PREFIX = '{$dbPrefix}';
// Name of the table that store migration script already executed
const MIGRATION_TABLE = '{$migrationTable}';
// Default Language
const DEFAULT_LANGUAGE = '{$defaultLanguage}';
// List of supported languages, fake constant as arrays can be used as constants only in PHP >=5.6
$ALLOWED_LANGUAGES = [
'fr' => 'Français',
'en' => 'English',
'es' => 'Español',
'de' => 'Deutsch',
'it' => 'Italiano',
];
// Nom et emplacement du fichier image contenant le titre
const IMAGE_TITRE = 'images/logo-framadate.png';
// Clean URLs, boolean
const URL_PROPRE = {if in_array($cleanUrl, array('1', 'on', 'true'))}true{else}false{/if};
// Use REMOTE_USER data provided by web server
const USE_REMOTE_USER = true;
// Path to the log file
const LOG_FILE = 'admin/stdout.log';
// Days (after expiration date) before purge a poll
const PURGE_DELAY = 60;
// Config
$config = [
/* general config */
'use_smtp' => true, // use email for polls creation/modification/responses notification
/* home */
'show_what_is_that' => true, // display "how to use" section
'show_the_software' => true, // display technical information about the software
'show_cultivate_your_garden' => true, // display "developpement and administration" information
/* create_classic_poll.php / create_date_poll.php */
'default_poll_duration' => 180, // default values for the new poll duration (number of days).
/* create_classic_poll.php */
'user_can_add_img_or_link' => true, // user can add link or URL when creating his poll.
];

104
tpl/admin/install.tpl Normal file
View File

@ -0,0 +1,104 @@
{extends 'admin/admin_page.tpl'}
{block 'main'}
<div class="row">
<div class="col-md-12">
<form action="" method="POST">
{if $error}
<div id="result" class="alert alert-danger">{$error}</div>
{/if}
<fieldset>
<legend>{__('Installation', 'General')}</legend>
<div class="form-group">
<div class="form-group">
<div class="input-group">
<label for="appName" class="input-group-addon">{__('Generic', 'ASTERISK')} {__('Installation', 'AppName')}</label>
<input type="text" class="form-control" id="appName" name="appName" value="Framadate" autofocus>
</div>
</div>
<div class="form-group">
<div class="input-group">
<label for="appMail" class="input-group-addon">{__('Generic', 'ASTERISK')} {__('Installation', 'AppMail')}</label>
<input type="email" class="form-control" id="appMail" name="appMail" value="">
</div>
</div>
<div class="form-group">
<div class="input-group">
<label for="responseMail" class="input-group-addon">{__('Installation', 'ResponseMail')}</label>
<input type="email" class="form-control" id="responseMail" name="responseMail" value="">
</div>
</div>
<div class="form-group">
<div class="input-group">
<label for="defaultLanguage" class="input-group-addon">{__('Generic', 'ASTERISK')} {__('Installation', 'DefaultLanguage')}</label>
<select type="email" class="form-control" id="defaultLanguage" name="defaultLanguage">
<option value="de">{$langs['de']}</option>
<option value="en">{$langs['es']}</option>
<option value="es">{$langs['es']}</option>
<option value="fr" selected>{$langs['fr']}</option>
<option value="it">{$langs['it']}</option>
</select>
</div>
</div>
<div class="input-group">
<label for="cleanUrl" class="input-group-addon">{__('Installation', 'CleanUrl')}</label>
<div class="form-control">
<input type="checkbox" id="cleanUrl" name="cleanUrl" checked>
</div>
</div>
</div>
</fieldset>
<fieldset>
<legend>{__('Installation', 'Database')}</legend>
<div class="form-group">
<div class="input-group">
<label for="dbConnectionString" class="input-group-addon">{__('Generic', 'ASTERISK')} {__('Installation', 'DbConnectionString')}</label>
<input type="text" class="form-control" id="dbConnectionString" name="dbConnectionString" value="mysql:host=HOST;dbname=SCHEMA;port=3306">
</div>
</div>
<div class="form-group">
<div class="input-group">
<label for="dbUser" class="input-group-addon">{__('Generic', 'ASTERISK')} {__('Installation', 'DbUser')}</label>
<input type="text" class="form-control" id="dbUser" name="dbUser" value="root">
</div>
</div>
<div class="form-group">
<div class="input-group">
<label for="dbPassword" class="input-group-addon">{__('Installation', 'DbPassword')}</label>
<input type="password" class="form-control" id="dbPassword" name="dbPassword">
</div>
</div>
<div class="form-group">
<div class="input-group">
<label for="dbPrefix" class="input-group-addon">{__('Installation', 'DbPrefix')}</label>
<input type="text" class="form-control" id="dbPrefix" name="dbPrefix" value="fd_">
</div>
</div>
<div class="form-group">
<div class="input-group">
<label for="migrationTable" class="input-group-addon">{__('Generic', 'ASTERISK')} {__('Installation', 'MigrationTable')}</label>
<input type="text" class="form-control" id="migrationTable" name="migrationTable" value="framadate_migration">
</div>
</div>
</fieldset>
<div class="text-center form-group">
<button type="submit" class="btn btn-primary">{__('Installation', 'Install')}</button>
</div>
</form>
</div>
</div>
{/block}

View File

@ -96,10 +96,6 @@
<div class="form-group">
<div class="col-sm-offset-4 col-sm-8">
<div class="radio">
<label>
<input type="radio" name="editable" {if empty($poll_editable) or $poll_editable==constant("Framadate\Editable::NOT_EDITABLE")}checked{/if} value="{constant("Framadate\Editable::NOT_EDITABLE")}">
{__('Step 1', 'Votes cannot be modified.')}
</label>
<label>
<input type="radio" name="editable" id="editableByAll" {if $poll_editable==constant("Framadate\Editable::EDITABLE_BY_ALL")}checked{/if} value="{constant("Framadate\Editable::EDITABLE_BY_ALL")}">
{__('Step 1', 'All voters can modify any vote')}
@ -108,6 +104,10 @@
<input type="radio" name="editable" {if $poll_editable==constant("Framadate\Editable::EDITABLE_BY_OWN")}checked{/if} value="{constant("Framadate\Editable::EDITABLE_BY_OWN")}">
{__('Step 1', 'Voters can modify their vote themselves')}
</label>
<label>
<input type="radio" name="editable" {if empty($poll_editable) or $poll_editable==constant("Framadate\Editable::NOT_EDITABLE")}checked{/if} value="{constant("Framadate\Editable::NOT_EDITABLE")}">
{__('Step 1', 'Votes cannot be modified.')}
</label>
</div>
</div>
</div>