Provide email address verification feature (#2481)

* Add an email field to the profile page

I reuse the `mail_login` from the configuration. I'm not sure if it's
useful today (I would say it was used when Persona login was available).

A good improvement would be to rename `mail_login` into `email` so it
would be more intuitive to use.

* Add boolean to the conf to force email validation

This commit only adds a configuration item.

* Add email during registration if email must be validated

* Set email token to validate when email changes

* Block access to FreshRSS if email is not validated

* Send email when address is changed

* Allow to resend the validation email

* Allow the user to change its email while blocked

* Document the email validation feature

* fixup! Allow the user to change its email while blocked

* tec: Autoload PHPMailer lib

* Validate email address format

* Add feedback on validation email resend action

* Allow to logout when user is blocked

* fix: Change default email "from"

* Reorganize i18n keys

* Complete all the locales with default english

* Hide sidebar (profile page) if email is not validated

* Check email requirements on registration

* Allow admin to specify email when creating users

* Don't check email format if value is empty

* Remove trailing comma in userController

Co-Authored-By: Alexandre Alapetite <alexandre@alapetite.fr>

* Set PHPMailer validator to html5 before sending email

* fixup! Remove trailing comma in userController
This commit is contained in:
Marien Fressinaud
2019-08-29 12:02:05 +02:00
committed by Alexandre Alapetite
parent ad44ff8169
commit 75632e70f0
81 changed files with 1002 additions and 18 deletions

View File

@@ -205,6 +205,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
Minz_Error::error(403);
}
$this->view->show_email_field = FreshRSS_Context::$system_conf->force_email_validation;
Minz_View::prependTitle(_t('gen.auth.registration.title') . ' · ');
}
}

View File

@@ -293,15 +293,24 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
* configuration values then sends a notification to the user.
*
* The options available on the page are:
* - instance name (default: FreshRSS)
* - auto update URL (default: false)
* - force emails validation (default: false)
* - user limit (default: 1)
* - user category limit (default: 16384)
* - user feed limit (default: 16384)
* - user login duration for form auth (default: 2592000)
*
* The `force-email-validation` is ignored with PHP < 5.5
*/
public function systemAction() {
if (!FreshRSS_Auth::hasAccess('admin')) {
Minz_Error::error(403);
}
$can_enable_email_validation = version_compare(PHP_VERSION, '5.5') >= 0;
$this->view->can_enable_email_validation = $can_enable_email_validation;
if (Minz_Request::isPost()) {
$limits = FreshRSS_Context::$system_conf->limits;
$limits['max_registrations'] = Minz_Request::param('max-registrations', 1);
@@ -311,6 +320,9 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
FreshRSS_Context::$system_conf->limits = $limits;
FreshRSS_Context::$system_conf->title = Minz_Request::param('instance-name', 'FreshRSS');
FreshRSS_Context::$system_conf->auto_update_url = Minz_Request::param('auto-update-url', false);
if ($can_enable_email_validation) {
FreshRSS_Context::$system_conf->force_email_validation = Minz_Request::param('force-email-validation', false);
}
FreshRSS_Context::$system_conf->save();
invalidateHttpCache();

View File

@@ -33,12 +33,23 @@ class FreshRSS_user_Controller extends Minz_ActionController {
return false;
}
public static function updateUser($user, $passwordPlain, $apiPasswordPlain, $userConfigUpdated = array()) {
public static function updateUser($user, $email, $passwordPlain, $apiPasswordPlain, $userConfigUpdated = array()) {
$userConfig = get_user_configuration($user);
if ($userConfig === null) {
return false;
}
if ($email !== null && $userConfig->mail_login !== $email) {
$userConfig->mail_login = $email;
if (FreshRSS_Context::$system_conf->force_email_validation) {
$salt = FreshRSS_Context::$system_conf->salt;
$userConfig->email_validation_token = sha1($salt . uniqid(mt_rand(), true));
$mailer = new FreshRSS_User_Mailer();
$mailer->send_email_need_validation($user, $userConfig);
}
}
if ($passwordPlain != '') {
$passwordHash = self::hashPassword($passwordPlain);
$userConfig->passwordHash = $passwordHash;
@@ -84,7 +95,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
$apiPasswordPlain = Minz_Request::param('apiPasswordPlain', '', true);
$username = Minz_Request::param('username');
$ok = self::updateUser($username, $passwordPlain, $apiPasswordPlain, array(
$ok = self::updateUser($username, null, $passwordPlain, $apiPasswordPlain, array(
'token' => Minz_Request::param('token', null),
));
@@ -111,25 +122,58 @@ class FreshRSS_user_Controller extends Minz_ActionController {
Minz_Error::error(403);
}
$email_not_verified = FreshRSS_Context::$user_conf->email_validation_token !== '';
if ($email_not_verified) {
$this->view->_layout('simple');
$this->view->disable_aside = true;
}
Minz_View::prependTitle(_t('conf.profile.title') . ' · ');
Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js')));
if (Minz_Request::isPost()) {
$system_conf = FreshRSS_Context::$system_conf;
$user_config = FreshRSS_Context::$user_conf;
$old_email = $user_config->mail_login;
$email = trim(Minz_Request::param('email', ''));
$passwordPlain = Minz_Request::param('newPasswordPlain', '', true);
Minz_Request::_param('newPasswordPlain'); //Discard plain-text password ASAP
$_POST['newPasswordPlain'] = '';
$apiPasswordPlain = Minz_Request::param('apiPasswordPlain', '', true);
$ok = self::updateUser(Minz_Session::param('currentUser'), $passwordPlain, $apiPasswordPlain, array(
if ($system_conf->force_email_validation && empty($email)) {
Minz_Request::bad(
_t('user.email.feedback.required'),
array('c' => 'user', 'a' => 'profile')
);
}
if (!empty($email) && !validateEmailAddress($email)) {
Minz_Request::bad(
_t('user.email.feedback.invalid'),
array('c' => 'user', 'a' => 'profile')
);
}
$ok = self::updateUser(
Minz_Session::param('currentUser'),
$email,
$passwordPlain,
$apiPasswordPlain,
array(
'token' => Minz_Request::param('token', null),
));
)
);
Minz_Session::_param('passwordHash', FreshRSS_Context::$user_conf->passwordHash);
if ($ok) {
if ($passwordPlain == '') {
if ($system_conf->force_email_validation && $email !== $old_email) {
Minz_Request::good(_t('feedback.profile.updated'), array('c' => 'user', 'a' => 'validateEmail'));
} elseif ($passwordPlain == '') {
Minz_Request::good(_t('feedback.profile.updated'), array('c' => 'user', 'a' => 'profile'));
} else {
Minz_Request::good(_t('feedback.profile.updated'), array('c' => 'index', 'a' => 'index'));
@@ -151,6 +195,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
Minz_View::prependTitle(_t('admin.user.title') . ' · ');
$this->view->show_email_field = FreshRSS_Context::$system_conf->force_email_validation;
$this->view->current_user = Minz_Request::param('u');
$this->view->nb_articles = 0;
@@ -165,7 +210,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
}
}
public static function createUser($new_user_name, $passwordPlain, $apiPasswordPlain, $userConfig = array(), $insertDefaultFeeds = true) {
public static function createUser($new_user_name, $email, $passwordPlain, $apiPasswordPlain, $userConfig = array(), $insertDefaultFeeds = true) {
if (!is_array($userConfig)) {
$userConfig = array();
}
@@ -193,7 +238,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
if ($ok) {
$userDAO = new FreshRSS_UserDAO();
$ok &= $userDAO->createUser($new_user_name, $userConfig['language'], $insertDefaultFeeds);
$ok &= self::updateUser($new_user_name, $passwordPlain, $apiPasswordPlain);
$ok &= self::updateUser($new_user_name, $email, $passwordPlain, $apiPasswordPlain);
}
return $ok;
}
@@ -204,6 +249,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
* Request parameters are:
* - new_user_language
* - new_user_name
* - new_user_email
* - new_user_passwordPlain
* - r (i.e. a redirection url, optional)
*
@@ -216,11 +262,28 @@ class FreshRSS_user_Controller extends Minz_ActionController {
}
if (Minz_Request::isPost()) {
$system_conf = FreshRSS_Context::$system_conf;
$new_user_name = Minz_Request::param('new_user_name');
$email = Minz_Request::param('new_user_email', '');
$passwordPlain = Minz_Request::param('new_user_passwordPlain', '', true);
$new_user_language = Minz_Request::param('new_user_language', FreshRSS_Context::$user_conf->language);
$ok = self::createUser($new_user_name, $passwordPlain, '', array('language' => $new_user_language));
if ($system_conf->force_email_validation && empty($email)) {
Minz_Request::bad(
_t('user.email.feedback.required'),
array('c' => 'auth', 'a' => 'register')
);
}
if (!empty($email) && !validateEmailAddress($email)) {
Minz_Request::bad(
_t('user.email.feedback.invalid'),
array('c' => 'auth', 'a' => 'register')
);
}
$ok = self::createUser($new_user_name, $email, $passwordPlain, '', array('language' => $new_user_language));
Minz_Request::_param('new_user_passwordPlain'); //Discard plain-text password ASAP
$_POST['new_user_passwordPlain'] = '';
invalidateHttpCache();
@@ -272,6 +335,122 @@ class FreshRSS_user_Controller extends Minz_ActionController {
return $ok;
}
/**
* This action validates an email address, based on the token sent by email.
* It also serves the main page when user is blocked.
*
* Request parameters are:
* - username
* - token
*
* This route works with GET requests since the URL is provided by email.
* The security risks (e.g. forged URL by an attacker) are not very high so
* it's ok.
*
* It returns 404 error if `force_email_validation` is disabled or if the
* user doesn't exist.
*
* It returns 403 if user isn't logged in and `username` param isn't passed.
*/
public function validateEmailAction() {
if (!FreshRSS_Context::$system_conf->force_email_validation) {
Minz_Error::error(404);
}
Minz_View::prependTitle(_t('user.email.validation.title') . ' · ');
$this->view->_layout('simple');
$username = Minz_Request::param('username');
$token = Minz_Request::param('token');
if ($username) {
$user_config = get_user_configuration($username);
} elseif (FreshRSS_Auth::hasAccess()) {
$user_config = FreshRSS_Context::$user_conf;
} else {
Minz_Error::error(403);
}
if (!FreshRSS_UserDAO::exists($username) || $user_config === null) {
Minz_Error::error(404);
}
if ($user_config->email_validation_token === '') {
Minz_Request::good(
_t('user.email.validation.feedback.unnecessary'),
array('c' => 'index', 'a' => 'index')
);
}
if ($token) {
if ($user_config->email_validation_token !== $token) {
Minz_Request::bad(
_t('user.email.validation.feedback.wrong_token'),
array('c' => 'user', 'a' => 'validateEmail')
);
}
$user_config->email_validation_token = '';
if ($user_config->save()) {
Minz_Request::good(
_t('user.email.validation.feedback.ok'),
array('c' => 'index', 'a' => 'index')
);
} else {
Minz_Request::bad(
_t('user.email.validation.feedback.error'),
array('c' => 'user', 'a' => 'validateEmail')
);
}
}
}
/**
* This action resends a validation email to the current user.
*
* It only acts on POST requests but doesn't require any param (except the
* CSRF token).
*
* It returns 403 error if the user is not logged in or 404 if request is
* not POST. Else it redirects silently to the index if user has already
* validated its email, or to the user#validateEmail route.
*/
public function sendValidationEmailAction() {
if (!FreshRSS_Auth::hasAccess()) {
Minz_Error::error(403);
}
if (!Minz_Request::isPost()) {
Minz_Error::error(404);
}
$username = Minz_Session::param('currentUser', '_');
$user_config = FreshRSS_Context::$user_conf;
if ($user_config->email_validation_token === '') {
Minz_Request::forward(array(
'c' => 'index',
'a' => 'index',
), true);
}
$mailer = new FreshRSS_User_Mailer();
$ok = $mailer->send_email_need_validation($username, $user_config);
$redirect_url = array('c' => 'user', 'a' => 'validateEmail');
if ($ok) {
Minz_Request::good(
_t('user.email.validation.feedback.email_sent'),
$redirect_url
);
} else {
Minz_Request::bad(
_t('user.email.validation.feedback.email_failed'),
$redirect_url
);
}
}
/**
* This action delete an existing user.
*

View File

@@ -54,6 +54,8 @@ class FreshRSS extends Minz_FrontController {
Minz_ExtensionManager::enableByList($ext_list);
}
self::checkEmailValidated();
Minz_ExtensionManager::callHook('freshrss_init');
}
@@ -144,4 +146,20 @@ class FreshRSS extends Minz_FrontController {
FreshRSS_Share::load(join_path(APP_PATH, 'shares.php'));
self::loadStylesAndScripts();
}
private static function checkEmailValidated() {
$email_not_verified = FreshRSS_Auth::hasAccess() && FreshRSS_Context::$user_conf->email_validation_token !== '';
$action_is_allowed = (
Minz_Request::is('user', 'validateEmail') ||
Minz_Request::is('user', 'sendValidationEmail') ||
Minz_Request::is('user', 'profile') ||
Minz_Request::is('auth', 'logout')
);
if ($email_not_verified && !$action_is_allowed) {
Minz_Request::forward(array(
'c' => 'user',
'a' => 'validateEmail',
), true);
}
}
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* Manage the emails sent to the users.
*/
class FreshRSS_User_Mailer extends Minz_Mailer {
public function send_email_need_validation($username, $user_config) {
$this->view->_path('user_mailer/email_need_validation.txt');
$this->view->username = $username;
$this->view->site_title = FreshRSS_Context::$system_conf->title;
$this->view->validation_url = Minz_Url::display(
array(
'c' => 'user',
'a' => 'validateEmail',
'params' => array(
'username' => $username,
'token' => $user_config->email_validation_token
)
),
'txt',
true
);
$subject_prefix = '[' . FreshRSS_Context::$system_conf->title . ']';
return $this->mail(
$user_config->mail_login,
$subject_prefix . ' ' ._t('user.mailer.email_need_validation.title')
);
}
}

View File

@@ -389,4 +389,8 @@ class FreshRSS_ConfigurationSetter {
$data['auto_update_url'] = $value;
}
private function _force_email_validation(&$data, $value) {
$data['force_email_validation'] = $this->handleBool($value);
}
}

View File

@@ -163,6 +163,7 @@ return array(
'help' => 'in seconds', //TODO - Translation
'number' => 'Duration to keep logged in', //TODO - Translation
),
'force_email_validation' => 'Force email addresses validation', //TODO - Translation
'instance-name' => 'Instance name', //TODO - Translation
'max-categories' => 'Categories per user limit', //TODO - Translation
'max-feeds' => 'Feeds per user limit', //TODO - Translation

View File

@@ -46,6 +46,7 @@ return array(
'_' => 'Smazání účtu',
'warn' => 'Váš účet bude smazán spolu se všemi souvisejícími daty',
),
'email' => 'Email',
'password_api' => 'Password API<br /><small>(tzn. pro mobilní aplikace)</small>',
'password_form' => 'Heslo<br /><small>(pro přihlášení webovým formulářem)</small>',
'password_format' => 'Alespoň 7 znaků',

View File

@@ -3,6 +3,7 @@
return array(
'action' => array(
'actualize' => 'Aktualizovat',
'back' => '← Go back', //TODO - Translation
'back_to_rss_feeds' => '← Zpět na seznam RSS kanálů',
'cancel' => 'Zrušit',
'create' => 'Vytvořit',

32
app/i18n/cz/user.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
return array(
'email' => array(
'feedback' => array(
'invalid' => 'The email address is invalid.', //TODO - Translation
'required' => 'The email address is required.', //TODO - Translation
),
'validation' => array(
'change_email' => 'You can change your email address <a href="%s">on the profile page</a>.', //TODO - Translation
'email_sent_to' => 'We sent you an email at <strong>%s</strong>, please follow its indications to validate your address.', //TODO - Translation
'feedback' => array(
'email_failed' => 'We couldnt send you an email because of a misconfiguration of the server.', //TODO - Translation
'email_sent' => 'An email has been sent to your address.', //TODO - Translation
'error' => 'The email address failed to be validated.', //TODO - Translation
'ok' => 'The email address has been validated.', //TODO - Translation
'unneccessary' => 'The email address was already validated.', //TODO - Translation
'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation
),
'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation
'resend_email' => 'Resend the email', //TODO - Translation
'title' => 'Email address validation', //TODO - Translation
),
),
'mailer' => array(
'email_need_validation' => array(
'title' => 'You need to validate your account', //TODO - Translation
'welcome' => 'Welcome %s,', //TODO - Translation
'body' => 'Youve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation
),
),
);

View File

@@ -159,6 +159,7 @@ return array(
'system' => array(
'_' => 'Systemeinstellungen',
'auto-update-url' => 'Auto-update URL',
'force_email_validation' => 'Force email addresses validation', //TODO - Translation
'instance-name' => 'Dein Reader Name',
'max-categories' => 'Anzahl erlaubter Kategorien pro Benutzer',
'max-feeds' => 'Anzahl erlaubter Feeds pro Benutzer',

View File

@@ -46,6 +46,7 @@ return array(
'_' => 'Accountlöschung',
'warn' => 'Dein Account und alle damit bezogenen Daten werden gelöscht.',
),
'email' => 'E-Mail-Adresse',
'password_api' => 'Passwort-API<br /><small>(z. B. für mobile Anwendungen)</small>',
'password_form' => 'Passwort<br /><small>(für die Anmeldemethode per Webformular)</small>',
'password_format' => 'mindestens 7 Zeichen',

View File

@@ -3,6 +3,7 @@
return array(
'action' => array(
'actualize' => 'Aktualisieren',
'back' => '← Go back', //TODO - Translation
'back_to_rss_feeds' => '← Zurück zu Ihren RSS-Feeds gehen',
'cancel' => 'Abbrechen',
'create' => 'Erstellen',

32
app/i18n/de/user.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
return array(
'email' => array(
'feedback' => array(
'invalid' => 'The email address is invalid.', //TODO - Translation
'required' => 'The email address is required.', //TODO - Translation
),
'validation' => array(
'change_email' => 'You can change your email address <a href="%s">on the profile page</a>.', //TODO - Translation
'email_sent_to' => 'We sent you an email at <strong>%s</strong>, please follow its indications to validate your address.', //TODO - Translation
'feedback' => array(
'email_failed' => 'We couldnt send you an email because of a misconfiguration of the server.', //TODO - Translation
'email_sent' => 'An email has been sent to your address.', //TODO - Translation
'error' => 'The email address failed to be validated.', //TODO - Translation
'ok' => 'The email address has been validated.', //TODO - Translation
'unneccessary' => 'The email address was already validated.', //TODO - Translation
'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation
),
'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation
'resend_email' => 'Resend the email', //TODO - Translation
'title' => 'Email address validation', //TODO - Translation
),
),
'mailer' => array(
'email_need_validation' => array(
'title' => 'You need to validate your account', //TODO - Translation
'welcome' => 'Welcome %s,', //TODO - Translation
'body' => 'Youve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation
),
),
);

View File

@@ -159,6 +159,7 @@ return array(
'system' => array(
'_' => 'System configuration',
'auto-update-url' => 'Auto-update server URL',
'force_email_validation' => 'Force email addresses validation',
'instance-name' => 'Instance name',
'max-categories' => 'Categories per user limit',
'max-feeds' => 'Feeds per user limit',

View File

@@ -46,6 +46,7 @@ return array(
'_' => 'Account deletion',
'warn' => 'Your account and all related data will be deleted.',
),
'email' => 'Email address',
'password_api' => 'API password<br /><small>(e.g., for mobile apps)</small>',
'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',
'password_format' => 'At least 7 characters',

View File

@@ -3,6 +3,7 @@
return array(
'action' => array(
'actualize' => 'Actualize',
'back' => '← Go back',
'back_to_rss_feeds' => '← Go back to your RSS feeds',
'cancel' => 'Cancel',
'create' => 'Create',

32
app/i18n/en/user.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
return array(
'email' => array(
'feedback' => array(
'invalid' => 'The email address is invalid.',
'required' => 'The email address is required.',
),
'validation' => array(
'change_email' => 'You can change your email address <a href="%s">on the profile page</a>.',
'email_sent_to' => 'We sent you an email at <strong>%s</strong>, please follow its indications to validate your address.',
'feedback' => array(
'email_failed' => 'We couldnt send you an email because of a misconfiguration of the server.',
'email_sent' => 'An email has been sent to your address.',
'error' => 'The email address failed to be validated.',
'ok' => 'The email address has been validated.',
'unneccessary' => 'The email address was already validated.',
'wrong_token' => 'The email address failed to be validated due to a wrong token.',
),
'need_to' => 'You need to validate your email address before being able to use %s.',
'resend_email' => 'Resend the email',
'title' => 'Email address validation',
),
),
'mailer' => array(
'email_need_validation' => array(
'title' => 'You need to validate your account',
'welcome' => 'Welcome %s,',
'body' => 'Youve just registered on %s but you still need to validate your email. For that, just follow the link:',
),
),
);

View File

@@ -159,6 +159,7 @@ return array(
'system' => array(
'_' => 'Configuración del sistema',
'auto-update-url' => 'URL de auto-actualización',
'force_email_validation' => 'Force email addresses validation', //TODO - Translation
'instance-name' => 'Nombre de la fuente',
'max-categories' => 'Límite de categorías por usuario',
'max-feeds' => 'Límite de fuentes por usuario',

View File

@@ -46,6 +46,7 @@ return array(
'_' => 'Borrar cuenta',
'warn' => 'Tu cuenta y todos los datos asociados serán eliminados.',
),
'email' => 'Correo electrónico',
'password_api' => 'Contraseña API <br /><small>(para apps móviles, por ej.)</small>',
'password_form' => 'Contraseña<br /><small>(para el método de identificación por formulario web)</small>',
'password_format' => 'Mínimo de 7 caracteres',

View File

@@ -3,6 +3,7 @@
return array(
'action' => array(
'actualize' => 'Actualizar',
'back' => '← Go back', //TODO - Translation
'back_to_rss_feeds' => '← regresar a tus fuentes RSS',
'cancel' => 'Cancelar',
'create' => 'Crear',

32
app/i18n/es/user.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
return array(
'email' => array(
'feedback' => array(
'invalid' => 'The email address is invalid.', //TODO - Translation
'required' => 'The email address is required.', //TODO - Translation
),
'validation' => array(
'change_email' => 'You can change your email address <a href="%s">on the profile page</a>.', //TODO - Translation
'email_sent_to' => 'We sent you an email at <strong>%s</strong>, please follow its indications to validate your address.', //TODO - Translation
'feedback' => array(
'email_failed' => 'We couldnt send you an email because of a misconfiguration of the server.', //TODO - Translation
'email_sent' => 'An email has been sent to your address.', //TODO - Translation
'error' => 'The email address failed to be validated.', //TODO - Translation
'ok' => 'The email address has been validated.', //TODO - Translation
'unneccessary' => 'The email address was already validated.', //TODO - Translation
'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation
),
'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation
'resend_email' => 'Resend the email', //TODO - Translation
'title' => 'Email address validation', //TODO - Translation
),
),
'mailer' => array(
'email_need_validation' => array(
'title' => 'You need to validate your account', //TODO - Translation
'welcome' => 'Welcome %s,', //TODO - Translation
'body' => 'Youve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation
),
),
);

View File

@@ -159,6 +159,7 @@ return array(
'system' => array(
'_' => 'Configuration du système',
'auto-update-url' => 'URL du service de mise à jour',
'force_email_validation' => 'Forcer la validation des adresses email',
'instance-name' => 'Nom de linstance',
'max-categories' => 'Limite de catégories par utilisateur',
'max-feeds' => 'Limite de flux par utilisateur',

View File

@@ -46,6 +46,7 @@ return array(
'_' => 'Suppression du compte',
'warn' => 'Le compte et toutes les données associées vont être supprimées.',
),
'email' => 'Adresse email',
'password_api' => 'Mot de passe API<br /><small>(ex. : pour applis mobiles)</small>',
'password_form' => 'Mot de passe<br /><small>(pour connexion par formulaire)</small>',
'password_format' => '7 caractères minimum',

View File

@@ -3,6 +3,7 @@
return array(
'action' => array(
'actualize' => 'Actualiser',
'back' => '← Retour',
'back_to_rss_feeds' => '← Retour à vos flux RSS',
'cancel' => 'Annuler',
'create' => 'Créer',

32
app/i18n/fr/user.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
return array(
'email' => array(
'feedback' => array(
'invalid' => 'Ladresse email est invalide.',
'required' => 'Ladresse email est requise.',
),
'validation' => array(
'change_email' => 'Vous pouvez changer votre adresse email <a href="%s">dans votre profil</a>.',
'email_sent_to' => 'Nous venons denvoyer un email à <strong>%s</strong>, veuillez suivre ses indications pour valider votre adresse.',
'feedback' => array(
'email_failed' => 'Nous navons pas pu vous envoyer demail à cause dune mauvaise configuration du serveur.',
'email_sent' => 'Un email a été envoyé à votre adresse.',
'error' => 'Ladresse email na pas pu être validée.',
'ok' => 'Ladresse email a été validée.',
'unnecessary' => 'Ladresse email a déjà été validée.',
'wrong_token' => 'Ladresse email na pas pu être validée à cause dun mauvais token.',
),
'need_to' => 'Vous devez valider votre adresse email avant de pouvoir utiliser %s.',
'resend_email' => 'Renvoyer lemail',
'title' => 'Validation de ladresse email',
),
),
'mailer' => array(
'email_need_validation' => array(
'title' => 'Vous devez valider votre compte',
'welcome' => 'Bienvenue %s,',
'body' => 'Vous venez de vous inscrire sur %s mais vous devez encore valider votre adresse email. Pour cela, veuillez cliquer sur ce lien :',
),
),
);

View File

@@ -163,6 +163,7 @@ return array(
'help' => 'in seconds', //TODO - Translation
'number' => 'Duration to keep logged in', //TODO - Translation
),
'force_email_validation' => 'Force email addresses validation', //TODO - Translation
'instance-name' => 'Instance name', //TODO - Translation
'max-categories' => 'Categories per user limit', //TODO - Translation
'max-feeds' => 'Feeds per user limit', //TODO - Translation

View File

@@ -46,6 +46,7 @@ return array(
'_' => 'Account deletion', //TODO - Translation
'warn' => 'Your account and all related data will be deleted.', //TODO - Translation
),
'email' => 'Email address', //TODO - Translation
'password_api' => 'סיסמת API<br /><small>(לדוגמה ליישומים סלולריים)</small>',
'password_form' => 'סיסמה<br /><small>(לשימוש בטפוס ההרשמה)</small>',
'password_format' => 'At least 7 characters', //TODO - Translation

View File

@@ -3,6 +3,7 @@
return array(
'action' => array(
'actualize' => 'מימוש',
'back' => '← Go back', //TODO - Translation
'back_to_rss_feeds' => '← חזרה להזנות הRSS שלך',
'cancel' => 'ביטול',
'create' => 'יצירה',

32
app/i18n/he/user.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
return array(
'email' => array(
'feedback' => array(
'invalid' => 'The email address is invalid.', //TODO - Translation
'required' => 'The email address is required.', //TODO - Translation
),
'validation' => array(
'change_email' => 'You can change your email address <a href="%s">on the profile page</a>.', //TODO - Translation
'email_sent_to' => 'We sent you an email at <strong>%s</strong>, please follow its indications to validate your address.', //TODO - Translation
'feedback' => array(
'email_failed' => 'We couldnt send you an email because of a misconfiguration of the server.', //TODO - Translation
'email_sent' => 'An email has been sent to your address.', //TODO - Translation
'error' => 'The email address failed to be validated.', //TODO - Translation
'ok' => 'The email address has been validated.', //TODO - Translation
'unneccessary' => 'The email address was already validated.', //TODO - Translation
'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation
),
'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation
'resend_email' => 'Resend the email', //TODO - Translation
'title' => 'Email address validation', //TODO - Translation
),
),
'mailer' => array(
'email_need_validation' => array(
'title' => 'You need to validate your account', //TODO - Translation
'welcome' => 'Welcome %s,', //TODO - Translation
'body' => 'Youve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation
),
),
);

View File

@@ -159,6 +159,7 @@ return array(
'system' => array(
'_' => 'Configurazione di sistema',
'auto-update-url' => 'Auto-update server URL', //TODO - Translation
'force_email_validation' => 'Force email addresses validation', //TODO - Translation
'instance-name' => 'Nome istanza',
'max-categories' => 'Limite categorie per utente',
'max-feeds' => 'Limite feeds per utente',

View File

@@ -46,6 +46,7 @@ return array(
'_' => 'Cancellazione account',
'warn' => 'Il tuo account e tutti i dati associati saranno cancellati.',
),
'email' => 'Indirizzo email',
'password_api' => 'Password API<br /><small>(e.g., per applicazioni mobili)</small>',
'password_form' => 'Password<br /><small>(per il login classico)</small>',
'password_format' => 'Almeno 7 caratteri',

View File

@@ -3,6 +3,7 @@
return array(
'action' => array(
'actualize' => 'Aggiorna',
'back' => '← Go back', //TODO - Translation
'back_to_rss_feeds' => '← Indietro',
'cancel' => 'Annulla',
'create' => 'Crea',

32
app/i18n/it/user.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
return array(
'email' => array(
'feedback' => array(
'invalid' => 'The email address is invalid.', //TODO - Translation
'required' => 'The email address is required.', //TODO - Translation
),
'validation' => array(
'change_email' => 'You can change your email address <a href="%s">on the profile page</a>.', //TODO - Translation
'email_sent_to' => 'We sent you an email at <strong>%s</strong>, please follow its indications to validate your address.', //TODO - Translation
'feedback' => array(
'email_failed' => 'We couldnt send you an email because of a misconfiguration of the server.', //TODO - Translation
'email_sent' => 'An email has been sent to your address.', //TODO - Translation
'error' => 'The email address failed to be validated.', //TODO - Translation
'ok' => 'The email address has been validated.', //TODO - Translation
'unneccessary' => 'The email address was already validated.', //TODO - Translation
'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation
),
'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation
'resend_email' => 'Resend the email', //TODO - Translation
'title' => 'Email address validation', //TODO - Translation
),
),
'mailer' => array(
'email_need_validation' => array(
'title' => 'You need to validate your account', //TODO - Translation
'welcome' => 'Welcome %s,', //TODO - Translation
'body' => 'Youve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation
),
),
);

View File

@@ -159,6 +159,7 @@ return array(
'system' => array(
'_' => '시스템 설정',
'auto-update-url' => '자동 업데이트 서버 URL',
'force_email_validation' => 'Force email addresses validation', //TODO - Translation
'instance-name' => '인스턴스 이름',
'max-categories' => '사용자별 카테고리 개수 제한',
'max-feeds' => '사용자별 피드 개수 제한',

View File

@@ -46,6 +46,7 @@ return array(
'_' => '계정 삭제',
'warn' => '당신의 계정과 관련된 모든 데이터가 삭제됩니다.',
),
'email' => '메일 주소',
'password_api' => 'API 암호<br /><small>(예: 모바일 애플리케이션)</small>',
'password_form' => '암호<br /><small>(웹폼 로그인 방식 사용시)</small>',
'password_format' => '7 글자 이상이어야 합니다',

View File

@@ -3,6 +3,7 @@
return array(
'action' => array(
'actualize' => '새 글 가져오기',
'back' => '← Go back', //TODO - Translation
'back_to_rss_feeds' => '← RSS 피드로 돌아가기',
'cancel' => '취소',
'create' => '생성',

32
app/i18n/kr/user.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
return array(
'email' => array(
'feedback' => array(
'invalid' => 'The email address is invalid.', //TODO - Translation
'required' => 'The email address is required.', //TODO - Translation
),
'validation' => array(
'change_email' => 'You can change your email address <a href="%s">on the profile page</a>.', //TODO - Translation
'email_sent_to' => 'We sent you an email at <strong>%s</strong>, please follow its indications to validate your address.', //TODO - Translation
'feedback' => array(
'email_failed' => 'We couldnt send you an email because of a misconfiguration of the server.', //TODO - Translation
'email_sent' => 'An email has been sent to your address.', //TODO - Translation
'error' => 'The email address failed to be validated.', //TODO - Translation
'ok' => 'The email address has been validated.', //TODO - Translation
'unneccessary' => 'The email address was already validated.', //TODO - Translation
'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation
),
'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation
'resend_email' => 'Resend the email', //TODO - Translation
'title' => 'Email address validation', //TODO - Translation
),
),
'mailer' => array(
'email_need_validation' => array(
'title' => 'You need to validate your account', //TODO - Translation
'welcome' => 'Welcome %s,', //TODO - Translation
'body' => 'Youve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation
),
),
);

View File

@@ -159,6 +159,7 @@ return array(
'system' => array(
'_' => 'Systeem configuratie',
'auto-update-url' => 'Automatische update server URL',
'force_email_validation' => 'Force email addresses validation', //TODO - Translation
'instance-name' => 'Voorbeeld naam',
'max-categories' => 'Categorielimiet per gebruiker',
'max-feeds' => 'Feedlimiet per gebruiker',

View File

@@ -46,6 +46,7 @@ return array(
'_' => 'Account verwijderen',
'warn' => 'Uw account en alle gerelateerde gegvens worden verwijderd.',
),
'email' => 'Email adres',
'password_api' => 'Wachtwoord API<br /><small>(e.g., voor mobiele apps)</small>',
'password_form' => 'Wachtwoord<br /><small>(voor de Web-formulier log in methode)</small>',
'password_format' => 'Ten minste 7 tekens',

View File

@@ -3,6 +3,7 @@
return array(
'action' => array(
'actualize' => 'Actualiseren',
'back' => '← Go back', //TODO - Translation
'back_to_rss_feeds' => '← Ga terug naar je RSS feeds',
'cancel' => 'Annuleren',
'create' => 'Opslaan',

32
app/i18n/nl/user.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
return array(
'email' => array(
'feedback' => array(
'invalid' => 'The email address is invalid.', //TODO - Translation
'required' => 'The email address is required.', //TODO - Translation
),
'validation' => array(
'change_email' => 'You can change your email address <a href="%s">on the profile page</a>.', //TODO - Translation
'email_sent_to' => 'We sent you an email at <strong>%s</strong>, please follow its indications to validate your address.', //TODO - Translation
'feedback' => array(
'email_failed' => 'We couldnt send you an email because of a misconfiguration of the server.', //TODO - Translation
'email_sent' => 'An email has been sent to your address.', //TODO - Translation
'error' => 'The email address failed to be validated.', //TODO - Translation
'ok' => 'The email address has been validated.', //TODO - Translation
'unneccessary' => 'The email address was already validated.', //TODO - Translation
'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation
),
'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation
'resend_email' => 'Resend the email', //TODO - Translation
'title' => 'Email address validation', //TODO - Translation
),
),
'mailer' => array(
'email_need_validation' => array(
'title' => 'You need to validate your account', //TODO - Translation
'welcome' => 'Welcome %s,', //TODO - Translation
'body' => 'Youve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation
),
),
);

View File

@@ -163,6 +163,7 @@ return array(
'help' => 'en segondas',
'number' => 'Durada de téner désser connectat',
),
'force_email_validation' => 'Force email addresses validation', //TODO - Translation
'instance-name' => 'Nom de linstància',
'max-categories' => 'Limita de categoria per utilizaire',
'max-feeds' => 'Limita de fluxes per utilizaire',

View File

@@ -46,6 +46,7 @@ return array(
'_' => 'Supression del compte',
'warn' => 'Lo compte e totas las donadas ligadas seràn suprimits.',
),
'email' => 'Adreça de corrièl',
'password_api' => 'Senhal API<br /><small>(ex. : per las aplicacions mobil)</small>',
'password_form' => 'Senhal API<br /><small>(ex. : per la connexion via formulari)</small>',
'password_format' => 'Almens 7 caractèrs',

View File

@@ -3,6 +3,7 @@
return array(
'action' => array(
'actualize' => 'Actualizar',
'back' => '← Go back', //TODO - Translation
'back_to_rss_feeds' => '← Tornar a vòstres fluxes RSS',
'cancel' => 'Anullar',
'create' => 'Crear',

32
app/i18n/oc/user.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
return array(
'email' => array(
'feedback' => array(
'invalid' => 'The email address is invalid.', //TODO - Translation
'required' => 'The email address is required.', //TODO - Translation
),
'validation' => array(
'change_email' => 'You can change your email address <a href="%s">on the profile page</a>.', //TODO - Translation
'email_sent_to' => 'We sent you an email at <strong>%s</strong>, please follow its indications to validate your address.', //TODO - Translation
'feedback' => array(
'email_failed' => 'We couldnt send you an email because of a misconfiguration of the server.', //TODO - Translation
'email_sent' => 'An email has been sent to your address.', //TODO - Translation
'error' => 'The email address failed to be validated.', //TODO - Translation
'ok' => 'The email address has been validated.', //TODO - Translation
'unneccessary' => 'The email address was already validated.', //TODO - Translation
'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation
),
'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation
'resend_email' => 'Resend the email', //TODO - Translation
'title' => 'Email address validation', //TODO - Translation
),
),
'mailer' => array(
'email_need_validation' => array(
'title' => 'You need to validate your account', //TODO - Translation
'welcome' => 'Welcome %s,', //TODO - Translation
'body' => 'Youve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation
),
),
);

View File

@@ -159,6 +159,7 @@ return array(
'system' => array(
'_' => 'Configuração do sistema',
'auto-update-url' => 'URL do servidor para atualização automática',
'force_email_validation' => 'Force email addresses validation', //TODO - Translation
'instance-name' => 'Nome da instância',
'max-categories' => 'Limite de categorias por usuário',
'max-feeds' => 'Limite de Feeds por usuário',

View File

@@ -46,6 +46,7 @@ return array(
'_' => 'Remover conta',
'warn' => 'Sua conta e todos os dados relacionados serão removidos.',
),
'email' => 'Endereço de e-mail',
'password_api' => 'Senha da API<br /><small>(p.s., para aplicativos móveis)</small>',
'password_form' => 'Senha<br /><small>(para o método de formulário web)</small>',
'password_format' => 'Ao menos 7 caracteres',

View File

@@ -3,6 +3,7 @@
return array(
'action' => array(
'actualize' => 'Atualizar',
'back' => '← Go back', //TODO - Translation
'back_to_rss_feeds' => '← Volte para o seu feeds RSS',
'cancel' => 'Cancelar',
'create' => 'Criar',

32
app/i18n/pt-br/user.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
return array(
'email' => array(
'feedback' => array(
'invalid' => 'The email address is invalid.', //TODO - Translation
'required' => 'The email address is required.', //TODO - Translation
),
'validation' => array(
'change_email' => 'You can change your email address <a href="%s">on the profile page</a>.', //TODO - Translation
'email_sent_to' => 'We sent you an email at <strong>%s</strong>, please follow its indications to validate your address.', //TODO - Translation
'feedback' => array(
'email_failed' => 'We couldnt send you an email because of a misconfiguration of the server.', //TODO - Translation
'email_sent' => 'An email has been sent to your address.', //TODO - Translation
'error' => 'The email address failed to be validated.', //TODO - Translation
'ok' => 'The email address has been validated.', //TODO - Translation
'unneccessary' => 'The email address was already validated.', //TODO - Translation
'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation
),
'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation
'resend_email' => 'Resend the email', //TODO - Translation
'title' => 'Email address validation', //TODO - Translation
),
),
'mailer' => array(
'email_need_validation' => array(
'title' => 'You need to validate your account', //TODO - Translation
'welcome' => 'Welcome %s,', //TODO - Translation
'body' => 'Youve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation
),
),
);

View File

@@ -159,6 +159,7 @@ return array(
'system' => array(
'_' => 'Системные настройки',
'auto-update-url' => 'Адрес сервера для автоматического обновления',
'force_email_validation' => 'Force email addresses validation', //TODO - Translation
'instance-name' => 'Название этого сервера',
'max-categories' => 'Количество категорий на пользователя',
'max-feeds' => 'Количество статей на пользователя',

View File

@@ -46,6 +46,7 @@ return array(
'_' => 'Account deletion', //TODO - Translation
'warn' => 'Your account and all the related data will be deleted.', //TODO - Translation
),
'email' => 'Email address', //TODO - Translation
'password_api' => 'Password API<br /><small>(e.g., for mobile apps)</small>', //TODO - Translation
'password_form' => 'Password<br /><small>(for the Web-form login method)</small>', //TODO - Translation
'password_format' => 'At least 7 characters', //TODO - Translation

View File

@@ -3,6 +3,7 @@
return array(
'action' => array(
'actualize' => 'Actualize', //TODO - Translation
'back' => '← Go back', //TODO - Translation
'back_to_rss_feeds' => '← Go back to your RSS feeds', //TODO - Translation
'cancel' => 'Cancel', //TODO - Translation
'create' => 'Create', //TODO - Translation

32
app/i18n/ru/user.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
return array(
'email' => array(
'feedback' => array(
'invalid' => 'The email address is invalid.', //TODO - Translation
'required' => 'The email address is required.', //TODO - Translation
),
'validation' => array(
'change_email' => 'You can change your email address <a href="%s">on the profile page</a>.', //TODO - Translation
'email_sent_to' => 'We sent you an email at <strong>%s</strong>, please follow its indications to validate your address.', //TODO - Translation
'feedback' => array(
'email_failed' => 'We couldnt send you an email because of a misconfiguration of the server.', //TODO - Translation
'email_sent' => 'An email has been sent to your address.', //TODO - Translation
'error' => 'The email address failed to be validated.', //TODO - Translation
'ok' => 'The email address has been validated.', //TODO - Translation
'unneccessary' => 'The email address was already validated.', //TODO - Translation
'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation
),
'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation
'resend_email' => 'Resend the email', //TODO - Translation
'title' => 'Email address validation', //TODO - Translation
),
),
'mailer' => array(
'email_need_validation' => array(
'title' => 'You need to validate your account', //TODO - Translation
'welcome' => 'Welcome %s,', //TODO - Translation
'body' => 'Youve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation
),
),
);

View File

@@ -159,6 +159,7 @@ return array(
'system' => array(
'_' => 'Sistem yapılandırması',
'auto-update-url' => 'Otomatik güncelleme sunucu URL',
'force_email_validation' => 'Force email addresses validation', //TODO - Translation
'instance-name' => 'Örnek isim',
'max-categories' => 'Kullanıcı başına kategori limiti',
'max-feeds' => 'Kullanıcı başına akış limiti',

View File

@@ -46,6 +46,7 @@ return array(
'_' => 'Hesap silme',
'warn' => 'Hesabınız ve tüm verileriniz silinecek.',
),
'email' => 'Email adresleri',
'password_api' => 'API Şifresi<br /><small>(ör. mobil uygulamalar için)</small>',
'password_form' => 'Şifre<br /><small>(Tarayıcı girişi için)</small>',
'password_format' => 'En az 7 karakter',

View File

@@ -3,6 +3,7 @@
return array(
'action' => array(
'actualize' => 'Yenile',
'back' => '← Go back', //TODO - Translation
'back_to_rss_feeds' => '← RSS akışlarınız için geri gidin',
'cancel' => 'İptal',
'create' => 'Oluştur',

32
app/i18n/tr/user.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
return array(
'email' => array(
'feedback' => array(
'invalid' => 'The email address is invalid.', //TODO - Translation
'required' => 'The email address is required.', //TODO - Translation
),
'validation' => array(
'change_email' => 'You can change your email address <a href="%s">on the profile page</a>.', //TODO - Translation
'email_sent_to' => 'We sent you an email at <strong>%s</strong>, please follow its indications to validate your address.', //TODO - Translation
'feedback' => array(
'email_failed' => 'We couldnt send you an email because of a misconfiguration of the server.', //TODO - Translation
'email_sent' => 'An email has been sent to your address.', //TODO - Translation
'error' => 'The email address failed to be validated.', //TODO - Translation
'ok' => 'The email address has been validated.', //TODO - Translation
'unneccessary' => 'The email address was already validated.', //TODO - Translation
'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation
),
'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation
'resend_email' => 'Resend the email', //TODO - Translation
'title' => 'Email address validation', //TODO - Translation
),
),
'mailer' => array(
'email_need_validation' => array(
'title' => 'You need to validate your account', //TODO - Translation
'welcome' => 'Welcome %s,', //TODO - Translation
'body' => 'Youve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation
),
),
);

View File

@@ -159,6 +159,7 @@ return array(
'system' => array(
'_' => '系统配置',
'auto-update-url' => '自动升级服务器 URL',
'force_email_validation' => 'Force email addresses validation', //TODO - Translation
'instance-name' => '实例名称',
'max-categories' => '每用户分类限制',
'max-feeds' => '每用户 RSS 源限制',

View File

@@ -46,6 +46,7 @@ return array(
'_' => '账户删除',
'warn' => '你的帐户和所有相关数据都将被删除。',
),
'email' => 'Email 地址',
'password_api' => 'API 密码<br /><small>(例如,用于手机 APP)</small>',
'password_form' => '密码<br /><small>(用于 Web-form 登录方式)</small>',
'password_format' => '至少 7 个字符',

View File

@@ -3,6 +3,7 @@
return array(
'action' => array(
'actualize' => '获取',
'back' => '← Go back', //TODO - Translation
'back_to_rss_feeds' => '← 返回',
'cancel' => '取消',
'create' => '创建',

32
app/i18n/zh-cn/user.php Normal file
View File

@@ -0,0 +1,32 @@
<?php
return array(
'email' => array(
'feedback' => array(
'invalid' => 'The email address is invalid.', //TODO - Translation
'required' => 'The email address is required.', //TODO - Translation
),
'validation' => array(
'change_email' => 'You can change your email address <a href="%s">on the profile page</a>.', //TODO - Translation
'email_sent_to' => 'We sent you an email at <strong>%s</strong>, please follow its indications to validate your address.', //TODO - Translation
'feedback' => array(
'email_failed' => 'We couldnt send you an email because of a misconfiguration of the server.', //TODO - Translation
'email_sent' => 'An email has been sent to your address.', //TODO - Translation
'error' => 'The email address failed to be validated.', //TODO - Translation
'ok' => 'The email address has been validated.', //TODO - Translation
'unneccessary' => 'The email address was already validated.', //TODO - Translation
'wrong_token' => 'The email address failed to be validated due to a wrong token.', //TODO - Translation
),
'need_to' => 'You need to validate your email address before being able to use %s.', //TODO - Translation
'resend_email' => 'Resend the email', //TODO - Translation
'title' => 'Email address validation', //TODO - Translation
),
),
'mailer' => array(
'email_need_validation' => array(
'title' => 'You need to validate your account', //TODO - Translation
'welcome' => 'Welcome %s,', //TODO - Translation
'body' => 'Youve just registered on %s but you still need to validate your email. For that, just follow the link:', //TODO - Translation
),
),
);

66
app/layout/simple.phtml Normal file
View File

@@ -0,0 +1,66 @@
<?php FreshRSS::preLayout(); ?>
<!DOCTYPE html>
<html lang="<?php echo FreshRSS_Context::$user_conf->language; ?>" xml:lang="<?php echo FreshRSS_Context::$user_conf->language; ?>">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1.0" />
<?php echo self::headStyle(); ?>
<?php echo self::headScript(); ?>
<link rel="shortcut icon" id="favicon" type="image/x-icon" sizes="16x16 64x64" href="<?php echo Minz_Url::display('/favicon.ico'); ?>" />
<link rel="icon msapplication-TileImage apple-touch-icon" type="image/png" sizes="256x256" href="<?php echo Minz_Url::display('/themes/icons/favicon-256.png'); ?>" />
<link rel="apple-touch-icon" href="<?php echo Minz_Url::display('/themes/icons/apple-touch-icon.png'); ?>" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="<?php echo FreshRSS_Context::$system_conf->title; ?>">
<meta name="msapplication-TileColor" content="#FFF" />
<meta name="referrer" content="never" />
<meta name="robots" content="noindex,nofollow" />
<?php echo self::headTitle(); ?>
</head>
<body>
<?php flush(); ?>
<div class="app-layout app-layout-simple">
<div class="header">
<div class="item title">
<h1>
<a href="<?php echo _url('index', 'index'); ?>">
<img class="logo" src="<?php echo _i('icon', true); ?>" alt="" />
<?php echo FreshRSS_Context::$system_conf->title; ?>
</a>
</h1>
</div>
<div class="item"></div>
<div class="item">
<?php if (FreshRSS_Auth::accessNeedsAction()) { ?>
<a class="signout" href="<?php echo _url('auth', 'logout'); ?>">
<?php echo _i('logout') . _t('gen.auth.logout'); ?>
(<?php echo htmlspecialchars(Minz_Session::param('currentUser', '_'), ENT_NOQUOTES, 'UTF-8'); ?>)
</a>
<?php } ?>
</div>
</div>
<?php $this->render(); ?>
</div>
<?php
$msg = '';
$status = 'closed';
if (isset($this->notification)) {
$msg = $this->notification['content'];
$status = $this->notification['type'];
invalidateHttpCache();
}
?>
<div id="notification" class="notification <?php echo $status; ?>">
<span class="msg"><?php echo $msg; ?></span>
<a class="close" href=""><?php echo _i('close'); ?></a>
</div>
</body>
</html>

View File

@@ -8,6 +8,15 @@
<input id="new_user_name" name="new_user_name" type="text" size="16" required="required" autocomplete="off" pattern="<?php echo FreshRSS_user_Controller::USERNAME_PATTERN; ?>" />
</div>
<?php if ($this->show_email_field) { ?>
<div>
<label class="group-name" for="new_user_email">
<?php echo _t('gen.auth.email'); ?>
</label>
<input id="new_user_email" name="new_user_email" type="email" required />
</div>
<?php } ?>
<div>
<label class="group-name" for="new_user_passwordPlain"><?php echo _t('gen.auth.password'), '<br />', _i('help'), ' ', _t('gen.auth.password.format'); ?></label>
<div class="stick">

View File

@@ -38,6 +38,24 @@
</div>
</div>
<?php if ($this->can_enable_email_validation) { ?>
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="force-email-validation">
<input
type="checkbox"
name="force-email-validation"
id="force-email-validation"
value="1"
<?php echo FreshRSS_Context::$system_conf->force_email_validation ? 'checked="checked"' : ''; ?>
data-leave-validation="<?php echo FreshRSS_Context::$system_conf->force_email_validation; ?>"
/>
<?php echo _t('admin.system.force_email_validation'); ?>
</label>
</div>
</div>
<?php } ?>
<div class="form-group">
<label class="group-name" for="max-feeds"><?php echo _t('admin.system.max-feeds'); ?></label>
<div class="group-controls">
@@ -51,7 +69,7 @@
<input type="number" id="max-categories" name="max-categories" value="<?php echo FreshRSS_Context::$system_conf->limits['max_categories']; ?>" min="1" data-leave-validation="<?php echo FreshRSS_Context::$system_conf->limits['max_categories']; ?>"/>
</div>
</div>
<div class="form-group">
<label class="group-name" for="cookie-duration"><?php echo _t('admin.system.cookie-duration.number'); ?></label>
<div class="group-controls">

View File

@@ -26,6 +26,17 @@
</div>
</div>
<?php if ($this->show_email_field) { ?>
<div class="form-group">
<label class="group-name" for="new_user_email">
<?php echo _t('gen.auth.email'); ?>
</label>
<div class="group-controls">
<input id="new_user_email" name="new_user_email" type="email" required />
</div>
</div>
<?php } ?>
<div class="form-group">
<label class="group-name" for="new_user_passwordPlain"><?php echo _t('admin.user.password_form'); ?></label>
<div class="group-controls">

View File

@@ -1,4 +1,8 @@
<?php $this->partial('aside_configure'); ?>
<?php
if (!$this->disable_aside) {
$this->partial('aside_configure');
}
?>
<div class="post">
<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.back_to_rss_feeds'); ?></a>
@@ -18,6 +22,13 @@
</div>
</div>
<div class="form-group">
<label class="group-name" for="email"><?php echo _t('conf.profile.email'); ?></label>
<div class="group-controls">
<input id="email" name="email" type="email" value="<?php echo FreshRSS_Context::$user_conf->mail_login; ?>" />
</div>
</div>
<div class="form-group">
<label class="group-name" for="newPasswordPlain"><?php echo _t('conf.profile.password_form'); ?></label>
<div class="group-controls">

View File

@@ -0,0 +1,22 @@
<div class="post">
<p>
<?php echo _t('user.email.validation.need_to', FreshRSS_Context::$system_conf->title); ?>
</p>
<p>
<?php echo _t('user.email.validation.email_sent_to', FreshRSS_Context::$user_conf->mail_login); ?>
</p>
<form action="<?php echo _url('user', 'sendValidationEmail'); ?>" method="post">
<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
<button type="submit" class="btn">
<?php echo _t('user.email.validation.resend_email'); ?>
</button>
</form>
<p>
<small>
<?php echo _t('user.email.validation.change_email', _url('user', 'profile')); ?>
</small>
</p>
</div>

View File

@@ -0,0 +1,5 @@
<?php echo _t('user.mailer.email_need_validation.welcome', $this->username); ?>
<?php echo _t('user.mailer.email_need_validation.body', $this->site_title); ?>
<?php echo $this->validation_url; ?>

View File

@@ -16,11 +16,14 @@ if (preg_grep("/^$username$/i", $usernames)) {
echo 'FreshRSS creating user “', $username, "”…\n";
$ok = FreshRSS_user_Controller::createUser($username,
$ok = FreshRSS_user_Controller::createUser(
$username,
empty($options['mail_login']) ? '' : $options['mail_login'],
empty($options['password']) ? '' : $options['password'],
empty($options['api_password']) ? '' : $options['api_password'],
$values,
!isset($options['no_default_feeds']));
!isset($options['no_default_feeds'])
);
if (!$ok) {
fail('FreshRSS could not create user!');

View File

@@ -9,6 +9,7 @@ echo 'FreshRSS updating user “', $username, "”…\n";
$ok = FreshRSS_user_Controller::updateUser(
$username,
empty($options['mail_login']) ? null : $options['mail_login'],
empty($options['password']) ? '' : $options['password'],
empty($options['api_password']) ? '' : $options['api_password'],
$values);

View File

@@ -6,6 +6,7 @@ return array (
'keep_history_default' => 50,
'ttl_default' => 3600,
'mail_login' => '',
'email_validation_token' => '',
'token' => '',
'passwordHash' => '',
'apiPasswordHash' => '',

View File

@@ -33,6 +33,13 @@ return array(
# Name of the user that has administration rights.
'default_user' => '_',
# Force users to validate their email address. If `true`, an email with a
# validation URL is sent during registration, and users cannot access their
# feed if they didn't access this URL.
# Note: it is recommended to not enable it with PHP < 5.5 (emails cannot be
# sent).
'force_email_validation' => false,
# Allow or not visitors without login to see the articles
# of the default user.
'allow_anonymous' => false,
@@ -159,7 +166,7 @@ return array(
'username' => '',
'password' => '',
'secure' => '', // '', 'ssl' or 'tls'
'from' => 'noreply@localhost',
'from' => 'root@localhost',
),
# List of enabled FreshRSS extensions.

View File

@@ -5,5 +5,6 @@ Learn how to install, update and backup FreshRSS and how to use the command line
* [Install FreshRSS](02_Installation.md) on your server
* [Update your installation](03_Updating.md) to the latest stable or dev version
* [The command line interface](https://github.com/FreshRSS/FreshRSS/tree/master/cli) can be used to administrate feeds and users
* [Automatic feed updates](https://github.com/FreshRSS/FreshRSS#automatic-feed-update) using cron is the preferred way to get the latest feeds entries
* [Automatic feed updates](https://github.com/FreshRSS/FreshRSS#automatic-feed-update) using cron is the preferred way to get the latest feeds entries
* [Configuring the email address validation](05_Configuring_email_validation.md)
* [Frequently asked questions](04_Frequently_Asked_Questions.md)

View File

@@ -0,0 +1,73 @@
# Configuring the email address validation
FreshRSS can verify that users give a valid email address. It is not configured
by default so you'll have to follow these few steps to verify email addresses.
It is intended to administrators who host users and want to be sure to be able
to contact them.
Note that this feature only works with PHP >= 5.5.
## Force email validation
In your `data/config.php` file, you'll find a `force_email_validation` item:
set it to `true`. An email field now appears on the registration page and
emails are sent when users change their email.
You can also enable this feature directly in FreshRSS: `Administration` >
`System configuration` > check `Force email addresses validation`. If the
option doesn't appear, it means that you use PHP < 5.5.
## Configure the SMTP server
By default, FreshRSS will attempt to send emails with the [`mail`](https://www.php.net/manual/en/function.mail.php)
function of PHP. It is the simpler solution but it might not work as expected.
For example, we don't support (yet?) sending emails from inside our official
Docker images. We recommend to use a proper SMTP server.
To configure a SMTP server, you'll have to modify the `data/config.php` file.
First, change the `mailer` item to `smtp` (instead of the default `mail`).
Then, you should change the `smtp` options like you would do with a regular
email client. You can find the full list of options in the [`config.default.php` file](/config.default.php).
If you're not sure to what each item is corresponding, you may find useful [the
PHPMailer documentation](http://phpmailer.github.io/PHPMailer/classes/PHPMailer.PHPMailer.PHPMailer.html#properties)
(which is used by FreshRSS under the hood).
## Check your SMTP server is correctly configured
To do so, once you've enabled the `force_email_validation` option, you only
need to change your email address on the profile page and check that an email
arrives on the new address.
If it fails, you can change the environment (in `data/config.php` file, change
`production` to `development`). PHPMailer will become more verbose and you'll
be able to see what happens in the PHP logs. If something's wrong here, you'll
probably better served by asking to your favorite search engine than asking us.
If you think that something's wrong in FreshRSS code, don't hesitate to open a
ticket though.
Also, make sure the email didn't arrive in your spam.
Once you're done, don't forget to reconfigure your environment to `production`.
## Access the validation URL during development
You might find painful to configure a SMTP server when you're developping and
`mail` function will not work on your local machine. For the moment, there is
no easy way to access the validation URL unless forging it. You'll need to
information:
- the username of the user to validate (you should know it)
- its validation token, that you'll find in its configuration file:
```console
$ # For instance, for a user called `alice`
$ grep email_validation_token data/users/alice/config.php | cut -d \' -f 4 -
3d75042a4471994a0346e18ae87602f19220a795
```
Then, the validation URL should be `http://localhost:8080/i/?c=user&a=validateEmail&username=alice&token=3d75042a4471994a0346e18ae87602f19220a795`
Don't forget to adapt this URL with the correct port, username and token.

View File

@@ -3,10 +3,6 @@
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
require LIB_PATH . '/PHPMailer/PHPMailer.php';
require LIB_PATH . '/PHPMailer/Exception.php';
require LIB_PATH . '/PHPMailer/SMTP.php';
/**
* Allow to send emails.
*
@@ -78,6 +74,8 @@ class Minz_Mailer {
$body = ob_get_contents();
ob_end_clean();
PHPMailer::$validator = 'html5';
$mail = new PHPMailer(true);
try {
// Server settings

View File

@@ -98,6 +98,13 @@ class Minz_Request {
self::initJSON();
}
public static function is($controller_name, $action_name) {
return (
self::$controller_name === $controller_name &&
self::$action_name === $action_name
);
}
/**
* Return true if the request is over HTTPS, false otherwise (HTTP)
*/

View File

@@ -55,6 +55,8 @@ function classAutoloader($class) {
include(LIB_PATH . '/' . str_replace('_', '/', $class) . '.php');
} elseif (strpos($class, 'SimplePie') === 0) {
include(LIB_PATH . '/SimplePie/' . str_replace('_', '/', $class) . '.php');
} elseif (strpos($class, 'PHPMailer') === 0) {
include(LIB_PATH . '/' . str_replace('\\', '/', $class) . '.php');
}
}
@@ -277,6 +279,20 @@ function sanitizeHTML($data, $base = '') {
return html_only_entity_decode($simplePie->sanitize->sanitize($data, SIMPLEPIE_CONSTRUCT_HTML, $base));
}
/**
* Validate an email address, supports internationalized addresses.
*
* @param string $email The address to validate
*
* @return bool true if email is valid, else false
*/
function validateEmailAddress($email) {
$mailer = new PHPMailer\PHPMailer\PHPMailer();
$mailer->Charset = 'utf-8';
$punyemail = $mailer->punyencodeAddress($email);
return PHPMailer\PHPMailer\PHPMailer::validateAddress($punyemail, 'html5');
}
/**
* Add support of image lazy loading
* Move content from src attribute to data-original