diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index b0b051119..b69c09127 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -83,6 +83,11 @@ class FreshRSS_index_Controller extends Minz_ActionController { $nb = Minz_Request::param ('nb', $this->view->conf->posts_per_page); $first = Minz_Request::param ('next', ''); + $ajax_request = Minz_Request::param('ajax', false); + if ($ajax_request == 1 && $this->view->conf->display_posts) { + $nb = max(1, round($nb / 2)); + } + if ($this->view->state === FreshRSS_Entry::STATE_NOT_READ) { //Any unread article in this category at all? switch ($getType) { case 'a': @@ -415,4 +420,75 @@ class FreshRSS_index_Controller extends Minz_ActionController { self::deleteLongTermCookie(); Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); } + + public function resetAuthAction() { + Minz_View::prependTitle(_t('auth_reset') . ' · '); + Minz_View::appendScript(Minz_Url::display( + '/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js') + )); + + $this->view->no_form = false; + // Enable changement of auth only if Persona! + if (Minz_Configuration::authType() != 'persona') { + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('auth_not_persona') + ); + $this->view->no_form = true; + return; + } + + $conf = new FreshRSS_Configuration(Minz_Configuration::defaultUser()); + // Admin user must have set its master password. + if (!$conf->passwordHash) { + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('auth_no_password_set') + ); + $this->view->no_form = true; + return; + } + + invalidateHttpCache(); + + if (Minz_Request::isPost()) { + $nonce = Minz_Session::param('nonce'); + $username = Minz_Request::param('username', ''); + $c = Minz_Request::param('challenge', ''); + if (!(ctype_alnum($username) && ctype_graph($c) && ctype_alnum($nonce))) { + Minz_Log::debug('Invalid credential parameters:' . + ' user=' . $username . + ' challenge=' . $c . + ' nonce=' . $nonce); + Minz_Request::bad(_t('invalid_login'), + array('c' => 'index', 'a' => 'resetAuth')); + } + + if (!function_exists('password_verify')) { + include_once(LIB_PATH . '/password_compat.php'); + } + + $s = $conf->passwordHash; + $ok = password_verify($nonce . $s, $c); + if ($ok) { + Minz_Configuration::_authType('form'); + $ok = Minz_Configuration::writeFile(); + + if ($ok) { + Minz_Request::good(_t('auth_form_set')); + } else { + Minz_Request::bad(_t('auth_form_not_set'), + array('c' => 'index', 'a' => 'resetAuth')); + } + } else { + Minz_Log::debug('Password mismatch for user ' . $username . + ', nonce=' . $nonce . ', c=' . $c); + + Minz_Request::bad(_t('invalid_login'), + array('c' => 'index', 'a' => 'resetAuth')); + } + } + } } diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php index 72244e9c7..da5bddc65 100644 --- a/app/Controllers/updateController.php +++ b/app/Controllers/updateController.php @@ -10,7 +10,10 @@ class FreshRSS_update_Controller extends Minz_ActionController { ); } + invalidateHttpCache(); + Minz_View::prependTitle(_t('update_system') . ' · '); + $this->view->update_to_apply = false; $this->view->last_update_time = 'unknown'; $this->view->check_last_hour = false; $timestamp = (int)@file_get_contents(DATA_PATH . '/last_update.txt'); @@ -29,10 +32,11 @@ class FreshRSS_update_Controller extends Minz_ActionController { ); } elseif (file_exists(UPDATE_FILENAME)) { // There is an update file to apply! + $this->view->update_to_apply = true; $this->view->message = array( 'status' => 'good', 'title' => _t('ok'), - 'body' => _t('update_can_apply', _url('update', 'apply')) + 'body' => _t('update_can_apply') ); } } diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 6cca27f78..cdf8962cb 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -6,7 +6,7 @@ class FreshRSS extends Minz_FrontController { } $loginOk = $this->accessControl(Minz_Session::param('currentUser', '')); $this->loadParamsView(); - if (Minz_Request::isPost() && !Minz_Request::isRefererFromSameDomain()) { + if (Minz_Request::isPost() && !is_referer_from_same_domain()) { $loginOk = false; //Basic protection against XSRF attacks Minz_Error::error( 403, @@ -143,11 +143,12 @@ class FreshRSS extends Minz_FrontController { $theme = FreshRSS_Themes::load($this->conf->theme); if ($theme) { foreach($theme['files'] as $file) { - $theme_id = $theme['id']; - $filename = $file; - if ($file[0] == '_') { + if ($file[0] === '_') { $theme_id = 'base-theme'; $filename = substr($file, 1); + } else { + $theme_id = $theme['id']; + $filename = $file; } $filetime = @filemtime(PUBLIC_PATH . '/themes/' . $theme_id . '/' . $filename); Minz_View::appendStyle(Minz_Url::display( diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 830d39f0d..f94d82402 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -142,7 +142,18 @@ class FreshRSS_Configuration { } } public function _default_view ($value) { - $this->data['default_view'] = $value === FreshRSS_Entry::STATE_ALL ? FreshRSS_Entry::STATE_ALL : FreshRSS_Entry::STATE_NOT_READ; + switch ($value) { + case FreshRSS_Entry::STATE_ALL: + // left blank on purpose + case FreshRSS_Entry::STATE_NOT_READ: + // left blank on purpose + case FreshRSS_Entry::STATE_NOT_READ_STRICT: + $this->data['default_view'] = $value; + break; + default: + $this->data['default_view'] = FreshRSS_Entry::STATE_ALL; + break; + } } public function _display_posts ($value) { $this->data['display_posts'] = ((bool)$value) && $value !== 'no'; diff --git a/app/Models/Entry.php b/app/Models/Entry.php index 0bf1f2616..5f1c8abc4 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -6,6 +6,8 @@ class FreshRSS_Entry extends Minz_Model { const STATE_NOT_READ = 2; const STATE_FAVORITE = 4; const STATE_NOT_FAVORITE = 8; + const STATE_READ_STRICT = 16; + const STATE_NOT_READ_STRICT = 32; private $id = 0; private $guid; diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 75a8aeba4..dee49212d 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -338,6 +338,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { elseif ($state & FreshRSS_Entry::STATE_READ) { $where .= 'AND e1.is_read=1 '; } + elseif ($state & FreshRSS_Entry::STATE_NOT_READ_STRICT) { + $where .= 'AND e1.is_read=0 '; + } if ($state & FreshRSS_Entry::STATE_FAVORITE) { if (!($state & FreshRSS_Entry::STATE_NOT_FAVORITE)) { $where .= 'AND e1.is_favorite=1 '; diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index 756b1f008..b89ae2045 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -331,7 +331,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $id_max = intval($date_min) . '000000'; $stm->bindParam(':id_feed', $id, PDO::PARAM_INT); - $stm->bindParam(':id_max', $id_max, PDO::PARAM_INT); + $stm->bindParam(':id_max', $id_max, PDO::PARAM_STR); $stm->bindParam(':keep', $keep, PDO::PARAM_INT); if ($stm && $stm->execute()) { diff --git a/app/i18n/en.php b/app/i18n/en.php index ca31c4bfc..8598e61cb 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -5,6 +5,7 @@ return array ( 'login' => 'Login', 'keep_logged_in' => 'Keep me logged in (1 month)', 'login_with_persona' => 'Login with Persona', + 'login_persona_problem' => 'Problem of connection with Persona?', 'logout' => 'Logout', 'search' => 'Search words or #tags', 'search_short' => 'Search', @@ -92,6 +93,7 @@ return array ( 'rss_view' => 'RSS feed', 'show_all_articles' => 'Show all articles', 'show_not_reads' => 'Show only unread', + 'show_adaptive' => 'Adjust showing', 'show_read' => 'Show only read', 'show_favorite' => 'Show only favorites', 'show_not_favorite' => 'Show all but favorites', @@ -158,6 +160,7 @@ return array ( 'save' => 'Save', 'delete' => 'Delete', 'cancel' => 'Cancel', + 'submit' => 'Submit', 'back_to_rss_feeds' => '← Go back to your RSS feeds', 'feeds_moved_category_deleted' => 'When you delete a category, their feeds are automatically classified under %s.', @@ -203,6 +206,7 @@ return array ( 'informations' => 'Information', 'damn' => 'Damn!', 'ok' => 'Ok!', + 'attention' => 'Be careful!', 'feed_in_error' => 'This feed has encountered a problem. Please verify that it is always reachable then actualize it.', 'feed_empty' => 'This feed is empty. Please verify that it is still maintained.', 'feed_description' => 'Description', @@ -254,6 +258,7 @@ return array ( 'users_list' => 'List of users', 'create_user' => 'Create new user', 'username' => 'Username', + 'username_admin' => 'Administrator username', 'password' => 'Password', 'create' => 'Create', 'user_created' => 'User %s has been created', @@ -269,7 +274,9 @@ return array ( 'reading_configuration' => 'Reading', 'display_configuration' => 'Display', 'articles_per_page' => 'Number of articles per page', + 'number_divided_when_unfolded' => 'Divided by 2 during loading of unfolded articles.', 'default_view' => 'Default view', + 'articles_to_display' => 'Articles to display', 'sort_order' => 'Sort order', 'auto_load_more' => 'Load next articles at the page bottom', 'display_articles_unfolded' => 'Show articles unfolded by default', @@ -427,9 +434,17 @@ return array ( 'update_system' => 'Update system', 'update_check' => 'Check for new updates', 'update_last' => 'Last verification: %s', - 'update_can_apply' => 'There is an available update. Apply', + 'update_can_apply' => 'An update is available.', + 'update_apply' => 'Apply', 'update_server_not_found' => 'Update server cannot be found. [%s]', 'no_update' => 'No update to apply', - 'update_problem' => 'Update has encountered an error: %s', - 'update_finished' => 'Update is now finished!', + 'update_problem' => 'The update process has encountered an error: %s', + 'update_finished' => 'Update completed!', + + 'auth_reset' => 'Authentication reset', + 'auth_will_reset' => 'Authentication system will be reseted: form will be used instead of Persona.', + 'auth_not_persona' => 'Only Persona system can be reseted.', + 'auth_no_password_set' => 'Administrator password hasn’t been set. This feature isn’t available.', + 'auth_form_set' => 'Form is now your default authentication system.', + 'auth_form_not_set' => 'A problem occured during authentication system configuration. Please retry later.', ); diff --git a/app/i18n/fr.php b/app/i18n/fr.php index d6c0118e7..4af819cac 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -5,6 +5,7 @@ return array ( 'login' => 'Connexion', 'keep_logged_in' => 'Rester connecté (1 mois)', 'login_with_persona' => 'Connexion avec Persona', + 'login_persona_problem' => 'Problème de connexion à Persona ?', 'logout' => 'Déconnexion', 'search' => 'Rechercher des mots ou des #tags', 'search_short' => 'Rechercher', @@ -92,6 +93,7 @@ return array ( 'rss_view' => 'Flux RSS', 'show_all_articles' => 'Afficher tous les articles', 'show_not_reads' => 'Afficher les non lus', + 'show_adaptive' => 'Adapter l’affichage', 'show_read' => 'Afficher les lus', 'show_favorite' => 'Afficher les favoris', 'show_not_favorite' => 'Afficher tout sauf les favoris', @@ -158,6 +160,7 @@ return array ( 'save' => 'Enregistrer', 'delete' => 'Supprimer', 'cancel' => 'Annuler', + 'submit' => 'Valider', 'back_to_rss_feeds' => '← Retour à vos flux RSS', 'feeds_moved_category_deleted' => 'Lors de la suppression d’une catégorie, ses flux seront automatiquement classés dans %s.', @@ -203,6 +206,7 @@ return array ( 'informations' => 'Informations', 'damn' => 'Arf !', 'ok' => 'Ok !', + 'attention' => 'Attention !', 'feed_in_error' => 'Ce flux a rencontré un problème. Veuillez vérifier qu’il est toujours accessible puis actualisez-le.', 'feed_empty' => 'Ce flux est vide. Veuillez vérifier qu’il est toujours maintenu.', 'feed_description' => 'Description', @@ -254,6 +258,7 @@ return array ( 'users_list' => 'Liste des utilisateurs', 'create_user' => 'Créer un nouvel utilisateur', 'username' => 'Nom d’utilisateur', + 'username_admin' => 'Nom d’utilisateur administrateur', 'password' => 'Mot de passe', 'create' => 'Créer', 'user_created' => 'L’utilisateur %s a été créé.', @@ -269,7 +274,9 @@ return array ( 'reading_configuration' => 'Lecture', 'display_configuration' => 'Affichage', 'articles_per_page' => 'Nombre d’articles par page', + 'number_divided_when_unfolded' => 'Divisé par 2 lors du chargement d’articles dépliés.', 'default_view' => 'Vue par défaut', + 'articles_to_display' => 'Articles à afficher', 'sort_order' => 'Ordre de tri', 'auto_load_more' => 'Charger les articles suivants en bas de page', 'display_articles_unfolded' => 'Afficher les articles dépliés par défaut', @@ -427,9 +434,17 @@ return array ( 'update_system' => 'Système de mise à jour', 'update_check' => 'Vérifier les mises à jour', 'update_last' => 'Dernière vérification : %s', - 'update_can_apply' => 'Il y’a une mise à jour à appliquer. Appliquer la mise à jour', + 'update_can_apply' => 'Une mise à jour est disponible.', + 'update_apply' => 'Appliquer la mise à jour', 'update_server_not_found' => 'Le serveur de mise à jour n’a pas été trouvé. [%s]', 'no_update' => 'Aucune mise à jour à appliquer', 'update_problem' => 'La mise à jour a rencontré un problème : %s', 'update_finished' => 'La mise à jour est terminée !', + + 'auth_reset' => 'Reset de l’authentification', + 'auth_will_reset' => 'Le système d’authentification va être remis à zéro : le formulaire sera utilisé à la place de Persona.', + 'auth_not_persona' => 'Seul le système d’authentification Persona peut être remis à zéro.', + 'auth_no_password_set' => 'Aucun mot de passe administrateur n’a été précisé. Cette fonctionnalité n’est pas disponible.', + 'auth_form_set' => 'Le formulaire est désormais votre système d’authentification.', + 'auth_form_not_set' => 'Un problème est survenu lors de la configuration de votre système d’authentification. Veuillez réessayer plus tard.', ); diff --git a/app/i18n/install.en.php b/app/i18n/install.en.php index 50208fcef..c422de90f 100644 --- a/app/i18n/install.en.php +++ b/app/i18n/install.en.php @@ -42,6 +42,8 @@ return array ( 'data_is_ok' => 'Permissions on data directory are good', 'persona_is_ok' => 'Permissions on Mozilla Persona directory are good', 'file_is_nok' => 'Check permissions on %s directory. HTTP server must have rights to write into', + 'http_referer_is_ok' => 'Your HTTP REFERER is known and corresponds to your server.', + 'http_referer_is_nok' => 'Please check that you are not altering your HTTP REFERER.', 'fix_errors_before' => 'Fix errors before skip to the next step.', 'general_conf_is_ok' => 'General configuration has been saved.', diff --git a/app/i18n/install.fr.php b/app/i18n/install.fr.php index 9c039f904..785c02459 100644 --- a/app/i18n/install.fr.php +++ b/app/i18n/install.fr.php @@ -42,6 +42,8 @@ return array ( 'data_is_ok' => 'Les droits sur le répertoire de data sont bons', 'persona_is_ok' => 'Les droits sur le répertoire de Mozilla Persona sont bons', 'file_is_nok' => 'Veuillez vérifier les droits sur le répertoire %s. Le serveur HTTP doit être capable d’écrire dedans', + 'http_referer_is_ok' => 'Le HTTP REFERER est connu et semble correspondre à votre serveur.', + 'http_referer_is_nok' => 'Veuillez vérifier que vous ne modifiez pas votre HTTP REFERER.', 'fix_errors_before' => 'Veuillez corriger les erreurs avant de passer à l’étape suivante.', 'general_conf_is_ok' => 'La configuration générale a été enregistrée.', diff --git a/app/install.php b/app/install.php index 8986e9965..4449cd063 100644 --- a/app/install.php +++ b/app/install.php @@ -149,7 +149,7 @@ function saveStep2() { $config_array = array( 'language' => $_SESSION['language'], - 'theme' => $_SESSION['theme'], + 'theme' => 'Origine', 'old_entries' => $_SESSION['old_entries'], 'mail_login' => $_SESSION['mail_login'], 'passwordHash' => $_SESSION['passwordHash'], @@ -307,6 +307,7 @@ function checkStep1() { $log = LOG_PATH && is_writable(LOG_PATH); $favicons = is_writable(DATA_PATH . '/favicons'); $persona = is_writable(DATA_PATH . '/persona'); + $http_referer = is_referer_from_same_domain(); return array( 'php' => $php ? 'ok' : 'ko', @@ -323,8 +324,10 @@ function checkStep1() { 'log' => $log ? 'ok' : 'ko', 'favicons' => $favicons ? 'ok' : 'ko', 'persona' => $persona ? 'ok' : 'ko', + 'http_referer' => $http_referer ? 'ok' : 'ko', 'all' => $php && $minz && $curl && $pdo && $pcre && $ctype && $dom && - $data && $cache && $log && $favicons && $persona ? 'ok' : 'ko' + $data && $cache && $log && $favicons && $persona && $http_referer ? + 'ok' : 'ko' ); } @@ -334,9 +337,15 @@ function checkStep2() { isset($_SESSION['mail_login']) && !empty($_SESSION['default_user']); - $form = $_SESSION['auth_type'] != 'form' || !empty($_SESSION['passwordHash']); + $form = ( + isset($_SESSION['auth_type']) && + ($_SESSION['auth_type'] != 'form' || !empty($_SESSION['passwordHash'])) + ); - $persona = $_SESSION['auth_type'] != 'persona' || !empty($_SESSION['mail_login']); + $persona = ( + isset($_SESSION['auth_type']) && + ($_SESSION['auth_type'] != 'persona' || !empty($_SESSION['mail_login'])) + ); $defaultUser = empty($_POST['default_user']) ? null : $_POST['default_user']; if ($defaultUser === null) { @@ -548,6 +557,12 @@ function printStep1() {

+ +

+ +

+ + @@ -591,16 +606,17 @@ function printStep2() {
- - - - - + + + +
@@ -609,7 +625,7 @@ function printStep2() {
- /> + />
@@ -619,7 +635,7 @@ function printStep2() {
- /> + />
@@ -644,7 +660,7 @@ function printStep2() { toggles[i].addEventListener('click', toggle_password); } - function auth_type_change() { + function auth_type_change(focus) { var auth_value = document.getElementById('auth_type').value, password_input = document.getElementById('passwordPlain'), mail_input = document.getElementById('mail_login'); @@ -652,15 +668,21 @@ function printStep2() { if (auth_value === 'form') { password_input.required = true; mail_input.required = false; + if (focus) { + password_input.focus(); + } } else if (auth_value === 'persona') { password_input.required = false; mail_input.required = true; + if (focus) { + mail_input.focus(); + } } else { password_input.required = false; mail_input.required = false; } } - auth_type_change(); + auth_type_change(false);
diff --git a/app/layout/aside_configure.phtml b/app/layout/aside_configure.phtml index 03bea4836..d5c9bf4c9 100644 --- a/app/layout/aside_configure.phtml +++ b/app/layout/aside_configure.phtml @@ -1,32 +1,32 @@
@@ -31,14 +34,17 @@ - - +
+ + +
+ +
+
diff --git a/app/views/helpers/pagination.phtml b/app/views/helpers/pagination.phtml index f6fcbc701..1b15cc632 100755 --- a/app/views/helpers/pagination.phtml +++ b/app/views/helpers/pagination.phtml @@ -9,7 +9,10 @@