diff options
| author | 2014-01-19 22:25:49 +0100 | |
|---|---|---|
| committer | 2014-01-19 22:25:49 +0100 | |
| commit | 4e4b0f717fe0a8378e2b751393a8c19d1ada6eb2 (patch) | |
| tree | 6b268cfe46eb3eee8ebaddcd1e17067eaca0214f | |
| parent | 12e0b6b094041fc22937520b3218e4f61915fd50 (diff) | |
| parent | 79d4893fc792119c390d2f744246df210b74f637 (diff) | |
Merge remote-tracking branch 'origin/dev' into beta
71 files changed, 2646 insertions, 486 deletions
@@ -5,12 +5,19 @@ * Nouveau mode multi-utilisateur * L’utilisateur par défaut (administrateur) peut créer et supprimer d’autres utilisateurs * Nécessite un contrôle d’accès, soit : + * par le nouveau mode de connexion par formulaire (nom d’utilisateur + mot de passe) + * relativement sûr même sans HTTPS (le mot de passe n’est pas transmis en clair) + * requiert JavaScript et PHP 5.3+ * par HTTP (par exemple sous Apache en créant un fichier ./p/i/.htaccess et .htpasswd) * le nom d’utilisateur HTTP doit correspondre au nom d’utilisateur FreshRSS * par Mozilla Persona, en renseignant l’adresse courriel des utilisateurs * Installateur supportant les mises à jour : - * Depuis une v0.6, placer application.ini et Configuration.array.php dans le nouveau répertoire “./data/” (voir réorganisation ci-dessous) - * Pour les versions suivantes, juste garder “./data/config.php” “./data/*_user.php” + * Depuis une v0.6, placer application.ini et Configuration.array.php dans le nouveau répertoire “./data/” + (voir réorganisation ci-dessous) + * Pour les versions suivantes, juste garder “./data/config.php” et “./data/*_user.php”, + éventuellement “./data/persona/*” +* Rafraîchissement automatique du nombre d’articles non lus toutes les minutes (utilise le cache HTTP à bon escient) + * Permet aussi de conserver la session valide, surtout dans le cas de Persona * Importation OPML instantanée et plus tolérante * Nouvelle gestion des favicons avec téléchargement en parallèle * Nouvelles options @@ -24,11 +31,13 @@ * Permet de modifier la description et l’adresse d’un flux RSS ainsi que le site Web associé * Nouveau raccourci pour ouvrir/fermer un article (‘c’ par défaut) * Boutons pour effacer les logs et pour purger les vieux articles + * Nouveaux filtres d’affichage : seulement les articles favoris, et seulement les articles lus * SQL : * Nouveau moteur de recherche, aussi accessible depuis la vue mobile * Mots clefs de recherche “intitle:”, “inurl:”, “author:” * Les articles sont triés selon la date de leur ajout dans FreshRSS plutôt que la date déclarée (souvent erronée) * Permet de marquer tout comme lu sans affecter les nouveaux articles arrivés en cours de lecture + * Permet une pagination efficace * Refactorisation * Les tables sont préfixées avec le nom d’utilisateur afin de permettre le mode multi-utilisateurs * Amélioration des performances @@ -38,16 +47,19 @@ * Affichage de la taille de la base de données dans FreshRSS * Correction problème de marquage de tous les favoris comme lus * HTML5 : - * Support des balises HTML5 audio, video, et éléments associés (preload="none", et réécriture correcte des adresses, aussi en HTTPS) + * Support des balises HTML5 audio, video, et éléments associés + * Utilisation de preload="none", et réécriture correcte des adresses, aussi en HTTPS * Protection HTML5 des iframe (sandbox="allow-scripts allow-same-origin") * Filtrage des object et embed * Chargement différé HTML5 (postpone="") pour iframe et video * Chargement différé JavaScript pour iframe * CSS : + * Nouveau thème sombre + * Chargement plus robuste des thèmes * Meilleur support des longs titres d’articles sur des écrans étroits * Meilleure accessibilité * FreshRSS fonctionne aussi en mode dégradé sans images (alternatives Unicode) et/ou sans CSS - * Diverses améliorations mineures + * Diverses améliorations * PHP : * Encore plus tolérant pour les flux comportant des erreurs * Mise à jour automatique de l’URL du flux (en base de données) lorsque SimplePie découvre qu’elle a changé @@ -56,15 +68,16 @@ * Amélioration des performances * Chargement automatique des classes * Alternative dans le cas d’absence de librairie JSON - * Pour le développement, le cache HTTP peut être désactivé en créant un fichier “./no-cache.txt” + * Pour le développement, le cache HTTP peut être désactivé en créant un fichier “./data/no-cache.txt” * Réorganisation des fichiers et répertoires, en particulier : * Tous les fichiers utilisateur sont dans “./data/” (y compris “cache”, “favicons”, et “log”) * Déplacement de “./app/configuration/application.ini” vers “./data/config.php” + * Meilleure sécurité et compatibilité * Déplacement de “./public/data/Configuration.array.php” vers “./data/*_user.php” * Déplacement de “./public/” vers “./p/” * Déplacement de “./public/index.php” vers “./p/i/index.php” (voir cookie ci-dessous) * Déplacement de “./actualize_script.php” vers “./app/actualize_script.php” (pour une meilleure sécurité) - * Pensez à mettre à jour votre CRON ! + * Pensez à mettre à jour votre Cron ! * Divers : * Nouvelle politique de cookie de session (témoin de connexion) * Utilise un nom poli “FreshRSS” (évite des problèmes avec certains filtres) @@ -8,7 +8,7 @@ Il permet de gérer plusieurs utilisateurs, et dispose d’un mode de lecture an * Site officiel : http://freshrss.org * Démo : http://marienfressinaud.fr/projets/freshrss/ * Développeur : Marien Fressinaud <dev@marienfressinaud.fr> -* Version actuelle : 0.7-beta4 +* Version actuelle : 0.7-RC1 * Date de publication 2014-01-xx * License [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html) @@ -25,7 +25,7 @@ Privilégiez pour cela des demandes sur GitHub * Serveur modeste, par exemple sous Linux ou Windows * Fonctionne même sur un Raspberry Pi avec des temps de réponse < 1s (testé sur 150 flux, 22k articles, soit 32Mo de données partiellement compressées) * Serveur Web Apache2 ou Nginx (non testé sur les autres) -* PHP 5.2+ (PHP 5.3.4+ recommandé) +* PHP 5.2+ (PHP 5.3.7+ recommandé) * Requis : [PDO_MySQL](http://php.net/pdo-mysql), [cURL](http://php.net/curl), [LibXML](http://php.net/xml), [PCRE](http://php.net/pcre), [ctype](http://php.net/ctype) * Recommandés : [JSON](http://php.net/json), [zlib](http://php.net/zlib), [mbstring](http://php.net/mbstring), [iconv](http://php.net/iconv) * MySQL 5.0.3+ (ou SQLite 3.7.4+ à venir) @@ -72,3 +72,20 @@ Par exemple, pour exécuter le script toutes les heures : ```bash mysqldump -u utilisateur -p --databases freshrss > freshrss.sql ``` + + +# Bibliothèques incluses +* [SimplePie](http://simplepie.org/) +* [MINZ](https://github.com/marienfressinaud/MINZ) +* [php-http-304](http://alexandre.alapetite.fr/doc-alex/php-http-304/) +* [jQuery](http://jquery.com/) +* [keyboard_shortcuts](http://www.openjs.com/scripts/events/keyboard_shortcuts/) + +## Uniquement pour certaines options +* [bcrypt.js](https://github.com/dcodeIO/bcrypt.js) +* [phpQuery](http://code.google.com/p/phpquery/) +* [Lazy Load](http://www.appelsiini.net/projects/lazyload) + +## Si les fonctions natives ne sont pas disponibles +* [Services_JSON](http://pear.php.net/pepr/pepr-proposal-show.php?id=198) +* [password_compat](https://github.com/ircmaxell/password_compat) diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 0a403fc2d..70144a8db 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -66,7 +66,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $this->view->feeds = $feedDAO->listFeeds (); $this->view->flux = false; - Minz_View::prependTitle (Minz_Translate::t ('categories_management') . ' - '); + Minz_View::prependTitle (Minz_Translate::t ('categories_management') . ' · '); } public function feedAction () { @@ -133,10 +133,10 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => array ('id' => $id)), true); } - Minz_View::prependTitle (Minz_Translate::t ('rss_feed_management') . ' - ' . $this->view->flux->name () . ' - '); + Minz_View::prependTitle (Minz_Translate::t ('rss_feed_management') . ' — ' . $this->view->flux->name () . ' · '); } } else { - Minz_View::prependTitle (Minz_Translate::t ('rss_feed_management') . ' - '); + Minz_View::prependTitle (Minz_Translate::t ('rss_feed_management') . ' · '); } } @@ -157,7 +157,11 @@ class FreshRSS_configure_Controller extends Minz_ActionController { 'scroll' => Minz_Request::param('mark_scroll', false), 'reception' => Minz_Request::param('mark_upon_reception', false), )); - $this->view->conf->_theme(Minz_Request::param('theme', 'default')); + $themeId = Minz_Request::param('theme', ''); + if ($themeId == '') { + $themeId = FreshRSS_Themes::defaultTheme; + } + $this->view->conf->_theme($themeId); $this->view->conf->_topline_read(Minz_Request::param('topline_read', false)); $this->view->conf->_topline_favorite(Minz_Request::param('topline_favorite', false)); $this->view->conf->_topline_date(Minz_Request::param('topline_date', false)); @@ -185,7 +189,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $this->view->themes = FreshRSS_Themes::get(); - Minz_View::prependTitle (Minz_Translate::t ('reading_configuration') . ' - '); + Minz_View::prependTitle (Minz_Translate::t ('reading_configuration') . ' · '); } public function sharingAction () { @@ -212,7 +216,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_Request::forward (array ('c' => 'configure', 'a' => 'sharing'), true); } - Minz_View::prependTitle (Minz_Translate::t ('sharing_management') . ' - '); + Minz_View::prependTitle (Minz_Translate::t ('sharing') . ' · '); } public function importExportAction () { @@ -277,7 +281,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { // au niveau de la vue, permet de ne pas voir un flux sélectionné dans la liste $this->view->flux = false; - Minz_View::prependTitle (Minz_Translate::t ('import_export_opml') . ' - '); + Minz_View::prependTitle (Minz_Translate::t ('import_export_opml') . ' · '); } public function shortcutAction () { @@ -313,11 +317,11 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_Request::forward (array ('c' => 'configure', 'a' => 'shortcut'), true); } - Minz_View::prependTitle (Minz_Translate::t ('shortcuts_management') . ' - '); + Minz_View::prependTitle (Minz_Translate::t ('shortcuts') . ' · '); } public function usersAction() { - Minz_View::prependTitle(Minz_Translate::t ('users') . ' - '); + Minz_View::prependTitle(Minz_Translate::t ('users') . ' · '); } public function archivingAction () { @@ -339,7 +343,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_Request::forward(array('c' => 'configure', 'a' => 'archiving'), true); } - Minz_View::prependTitle(Minz_Translate::t('archiving_configuration') . ' - '); + Minz_View::prependTitle(Minz_Translate::t('archiving_configuration') . ' · '); $entryDAO = new FreshRSS_EntryDAO(); $this->view->nb_total = $entryDAO->count(); diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php index a24dfe6d6..1756c91e5 100755 --- a/app/Controllers/entryController.php +++ b/app/Controllers/entryController.php @@ -10,6 +10,11 @@ class FreshRSS_entry_Controller extends Minz_ActionController { } $this->params = array (); + $output = Minz_Request::param('output', ''); + if (($output != '') && ($this->view->conf->view_mode !== $output)) { + $this->params['output'] = $output; + } + $this->redirect = false; $ajax = Minz_Request::param ('ajax'); if ($ajax) { @@ -34,13 +39,10 @@ class FreshRSS_entry_Controller extends Minz_ActionController { $this->redirect = true; $id = Minz_Request::param ('id'); - $is_read = Minz_Request::param ('is_read'); $get = Minz_Request::param ('get'); $nextGet = Minz_Request::param ('nextGet', $get); $idMax = Minz_Request::param ('idMax', 0); - $is_read = (bool)$is_read; - $entryDAO = new FreshRSS_EntryDAO (); if ($id == false) { if (!$get) { @@ -63,7 +65,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController { break; } if ($nextGet !== 'a') { - $this->params = array ('get' => $nextGet); + $this->params['get'] = $nextGet; } } @@ -73,6 +75,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController { ); Minz_Session::_param ('notification', $notif); } else { + $is_read = (bool)(Minz_Request::param ('is_read', true)); $entryDAO->markRead ($id, $is_read); } } @@ -83,7 +86,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController { $id = Minz_Request::param ('id'); if ($id) { $entryDAO = new FreshRSS_EntryDAO (); - $entryDAO->markFavorite ($id, Minz_Request::param ('is_favorite')); + $entryDAO->markFavorite ($id, (bool)(Minz_Request::param ('is_favorite', true))); } } diff --git a/app/Controllers/errorController.php b/app/Controllers/errorController.php index d1c2f8fec..dc9a2ee25 100644 --- a/app/Controllers/errorController.php +++ b/app/Controllers/errorController.php @@ -21,6 +21,6 @@ class FreshRSS_error_Controller extends Minz_ActionController { $this->view->logs = Minz_Request::param ('logs'); - Minz_View::prependTitle ($this->view->code . ' - '); + Minz_View::prependTitle ($this->view->code . ' · '); } } diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 2d7c0ab43..c40b3c400 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -30,8 +30,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $cat = $def_cat->id (); } - $user = Minz_Request::param ('username'); - $pass = Minz_Request::param ('password'); + $user = Minz_Request::param ('http_user'); + $pass = Minz_Request::param ('http_pass'); $params = array (); $transactionStarted = false; @@ -164,6 +164,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feedDAO = new FreshRSS_FeedDAO (); $entryDAO = new FreshRSS_EntryDAO (); + Minz_Session::_param('actualize_feeds', false); $id = Minz_Request::param ('id'); $force = Minz_Request::param ('force', false); diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 690ca57be..45ded6fd4 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -46,12 +46,8 @@ class FreshRSS_index_Controller extends Minz_ActionController { // no layout for RSS output $this->view->_useLayout (false); header('Content-Type: application/rss+xml; charset=utf-8'); - } else { - Minz_View::appendScript (Minz_Url::display ('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js'))); - - if ($output === 'global') { - Minz_View::appendScript (Minz_Url::display ('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js'))); - } + } elseif ($output === 'global') { + Minz_View::appendScript (Minz_Url::display ('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js'))); } $this->view->cat_aside = $this->catDAO->listCategories (); @@ -83,7 +79,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { Minz_View::prependTitle ( $this->view->currentName . ($this->nb_not_read_cat > 0 ? ' (' . $this->nb_not_read_cat . ')' : '') . - ' - ' + ' · ' ); // On récupère les différents éléments de filtrage @@ -204,7 +200,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { } public function aboutAction () { - Minz_View::prependTitle (Minz_Translate::t ('about') . ' - '); + Minz_View::prependTitle (Minz_Translate::t ('about') . ' · '); } public function logsAction () { @@ -215,7 +211,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { ); } - Minz_View::prependTitle (Minz_Translate::t ('logs') . ' - '); + Minz_View::prependTitle (Minz_Translate::t ('logs') . ' · '); if (Minz_Request::isPost ()) { FreshRSS_LogDAO::truncate(); @@ -290,8 +286,56 @@ class FreshRSS_index_Controller extends Minz_ActionController { } public function logoutAction () { - $this->view->_useLayout (false); - Minz_Session::_param ('mail'); + $this->view->_useLayout(false); + invalidateHttpCache(); + Minz_Session::_param('currentUser'); + Minz_Session::_param('mail'); + Minz_Session::_param('passwordHash'); + } + + public function formLoginAction () { + if (Minz_Request::isPost()) { + $ok = false; + $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)) { + if (!function_exists('password_verify')) { + include_once(LIB_PATH . '/password_compat.php'); + } + try { + $conf = new FreshRSS_Configuration($username); + $s = $conf->passwordHash; + $ok = password_verify($nonce . $s, $c); + if ($ok) { + Minz_Session::_param('currentUser', $username); + Minz_Session::_param('passwordHash', $s); + } else { + Minz_Log::record('Password mismatch for user ' . $username . ', nonce=' . $nonce . ', c=' . $c, Minz_Log::WARNING); + } + } catch (Minz_Exception $me) { + Minz_Log::record('Login failure: ' . $me->getMessage(), Minz_Log::WARNING); + } + } + if (!$ok) { + $notif = array( + 'type' => 'bad', + 'content' => Minz_Translate::t('invalid_login') + ); + Minz_Session::_param('notification', $notif); + } + $this->view->_useLayout(false); + Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); + } + invalidateHttpCache(); + } + + public function formLogoutAction () { + $this->view->_useLayout(false); invalidateHttpCache(); + Minz_Session::_param('currentUser'); + Minz_Session::_param('mail'); + Minz_Session::_param('passwordHash'); + Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); } } diff --git a/app/Controllers/javascriptController.php b/app/Controllers/javascriptController.php index e7e25f656..02e424437 100755 --- a/app/Controllers/javascriptController.php +++ b/app/Controllers/javascriptController.php @@ -3,11 +3,44 @@ class FreshRSS_javascript_Controller extends Minz_ActionController { public function firstAction () { $this->view->_useLayout (false); - header('Content-type: text/javascript'); } public function actualizeAction () { + header('Content-Type: text/javascript; charset=UTF-8'); $feedDAO = new FreshRSS_FeedDAO (); $this->view->feeds = $feedDAO->listFeeds (); } + + public function nbUnreadsPerFeedAction() { + header('Content-Type: application/json; charset=UTF-8'); + $catDAO = new FreshRSS_CategoryDAO(); + $this->view->categories = $catDAO->listCategories(true, false); + } + + //For Web-form login + public function nonceAction() { + header('Content-Type: application/json; charset=UTF-8'); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s \G\M\T')); + header('Expires: 0'); + header('Cache-Control: private, no-cache, no-store, must-revalidate'); + header('Pragma: no-cache'); + + $user = isset($_GET['user']) ? $_GET['user'] : ''; + if (ctype_alnum($user)) { + try { + $conf = new FreshRSS_Configuration($user); + $s = $conf->passwordHash; + if (strlen($s) >= 60) { + $this->view->salt1 = substr($s, 0, 29); //CRYPT_BLOWFISH Salt: "$2a$", a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z". + $this->view->nonce = sha1(Minz_Configuration::salt() . uniqid(mt_rand(), true)); + Minz_Session::_param('nonce', $this->view->nonce); + return; //Success + } + } catch (Minz_Exception $me) { + Minz_Log::record('Login failure: ' . $me->getMessage(), Minz_Log::WARNING); + } + } + $this->view->nonce = ''; //Failure + $this->view->salt1 = ''; + } } diff --git a/app/Controllers/usersController.php b/app/Controllers/usersController.php index 482e35c3e..a044cd25b 100644 --- a/app/Controllers/usersController.php +++ b/app/Controllers/usersController.php @@ -1,6 +1,9 @@ <?php class FreshRSS_users_Controller extends Minz_ActionController { + + const BCRYPT_COST = 9; //Will also have to be computed client side on mobile devices, so do not use a too high cost + public function firstAction() { if (!$this->view->loginOk) { Minz_Error::error( @@ -14,13 +17,29 @@ class FreshRSS_users_Controller extends Minz_ActionController { if (Minz_Request::isPost()) { $ok = true; - $mail = Minz_Request::param('mail_login', false); - $this->view->conf->_mail_login($mail); - $ok &= $this->view->conf->save(); + $passwordPlain = Minz_Request::param('passwordPlain', false); + if ($passwordPlain != '') { + Minz_Request::_param('passwordPlain'); //Discard plain-text password ASAP + $_POST['passwordPlain'] = ''; + if (!function_exists('password_hash')) { + include_once(LIB_PATH . '/password_compat.php'); + } + $passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST)); + $passwordPlain = ''; + $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js + $ok &= ($passwordHash != ''); + $this->view->conf->_passwordHash($passwordHash); + } + Minz_Session::_param('passwordHash', $this->view->conf->passwordHash); + if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { + $this->view->conf->_mail_login(Minz_Request::param('mail_login', false)); + } $email = $this->view->conf->mail_login; Minz_Session::_param('mail', $email); + $ok &= $this->view->conf->save(); + if ($email != '') { $personaFile = DATA_PATH . '/persona/' . $email . '.txt'; @unlink($personaFile); @@ -38,8 +57,8 @@ class FreshRSS_users_Controller extends Minz_ActionController { $auth_type = Minz_Request::param('auth_type', 'none'); if ($anon != Minz_Configuration::allowAnonymous() || $auth_type != Minz_Configuration::authType()) { - Minz_Configuration::_allowAnonymous($anon); Minz_Configuration::_authType($auth_type); + Minz_Configuration::_allowAnonymous($anon); $ok &= Minz_Configuration::writeFile(); } } @@ -76,10 +95,26 @@ class FreshRSS_users_Controller extends Minz_ActionController { $ok &= !file_exists($configPath); } if ($ok) { + + $passwordPlain = Minz_Request::param('new_user_passwordPlain', false); + $passwordHash = ''; + if ($passwordPlain != '') { + Minz_Request::_param('new_user_passwordPlain'); //Discard plain-text password ASAP + $_POST['new_user_passwordPlain'] = ''; + if (!function_exists('password_hash')) { + include_once(LIB_PATH . '/password_compat.php'); + } + $passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST)); + $passwordPlain = ''; + $ok &= ($passwordHash != ''); + } + if (empty($passwordHash)) { + $passwordHash = ''; + } + $new_user_email = filter_var($_POST['new_user_email'], FILTER_VALIDATE_EMAIL); if (empty($new_user_email)) { $new_user_email = ''; - $ok &= Minz_Configuration::authType() !== 'persona'; } else { $personaFile = DATA_PATH . '/persona/' . $new_user_email . '.txt'; @unlink($personaFile); @@ -89,6 +124,7 @@ class FreshRSS_users_Controller extends Minz_ActionController { if ($ok) { $config_array = array( 'language' => $new_user_language, + 'passwordHash' => $passwordHash, 'mail_login' => $new_user_email, ); $ok &= (file_put_contents($configPath, "<?php\n return " . var_export($config_array, true) . ';') !== false); diff --git a/app/FreshRSS.php b/app/FreshRSS.php index f9857a4cb..40e1d23db 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -4,15 +4,20 @@ class FreshRSS extends Minz_FrontController { if (!isset($_SESSION)) { Minz_Session::init('FreshRSS'); } - $this->accessControl(Minz_Session::param('currentUser', '')); + $loginOk = $this->accessControl(Minz_Session::param('currentUser', '')); $this->loadParamsView(); - $this->loadStylesAndScripts(); //TODO: Do not load that when not needed, e.g. some Ajax requests + $this->loadStylesAndScripts($loginOk); //TODO: Do not load that when not needed, e.g. some Ajax requests $this->loadNotifications(); } private function accessControl($currentUser) { if ($currentUser == '') { switch (Minz_Configuration::authType()) { + case 'form': + $currentUser = Minz_Configuration::defaultUser(); + Minz_Session::_param('passwordHash'); + $loginOk = false; + break; case 'http_auth': $currentUser = httpAuthUser(); $loginOk = $currentUser != ''; @@ -73,6 +78,9 @@ class FreshRSS extends Minz_FrontController { if ($loginOk) { switch (Minz_Configuration::authType()) { + case 'form': + $loginOk = Minz_Session::param('passwordHash') === $this->conf->passwordHash; + break; case 'http_auth': $loginOk = strcasecmp($currentUser, httpAuthUser()) === 0; break; @@ -92,6 +100,7 @@ class FreshRSS extends Minz_FrontController { } } Minz_View::_param ('loginOk', $loginOk); + return $loginOk; } private function loadParamsView () { @@ -104,22 +113,30 @@ class FreshRSS extends Minz_FrontController { } } - private function loadStylesAndScripts () { - $theme = FreshRSS_Themes::get_infos($this->conf->theme); + private function loadStylesAndScripts ($loginOk) { + $theme = FreshRSS_Themes::load($this->conf->theme); if ($theme) { foreach($theme['files'] as $file) { - Minz_View::appendStyle (Minz_Url::display ('/themes/' . $theme['path'] . '/' . $file . '?' . @filemtime(PUBLIC_PATH . '/themes/' . $theme['path'] . '/' . $file))); + Minz_View::appendStyle (Minz_Url::display ('/themes/' . $theme['id'] . '/' . $file . '?' . @filemtime(PUBLIC_PATH . '/themes/' . $theme['id'] . '/' . $file))); } } - if (Minz_Configuration::authType() === 'persona') { - Minz_View::appendScript ('https://login.persona.org/include.js'); + switch (Minz_Configuration::authType()) { + case 'form': + if (!$loginOk) { + Minz_View::appendScript(Minz_Url::display ('/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js'))); + } + break; + case 'persona': + Minz_View::appendScript('https://login.persona.org/include.js'); + break; } $includeLazyLoad = $this->conf->lazyload && ($this->conf->display_posts || Minz_Request::param ('output') === 'reader'); Minz_View::appendScript (Minz_Url::display ('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js')), false, !$includeLazyLoad, !$includeLazyLoad); if ($includeLazyLoad) { Minz_View::appendScript (Minz_Url::display ('/scripts/jquery.lazyload.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.lazyload.min.js'))); } + Minz_View::appendScript (Minz_Url::display ('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js'))); Minz_View::appendScript (Minz_Url::display ('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js'))); } diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index c29e74603..f3fb76e72 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -9,6 +9,7 @@ class FreshRSS_Configuration { 'keep_history_default' => 0, 'mail_login' => '', 'token' => '', + 'passwordHash' => '', //CRYPT_BLOWFISH 'posts_per_page' => 20, 'view_mode' => 'normal', 'default_view' => 'not_read', @@ -24,7 +25,7 @@ class FreshRSS_Configuration { 'scroll' => false, 'reception' => false, ), - 'theme' => 'default', + 'theme' => 'Origine', 'shortcuts' => array( 'mark_read' => 'r', 'mark_favorite' => 'f', @@ -162,6 +163,9 @@ class FreshRSS_Configuration { } } } + public function _passwordHash ($value) { + $this->data['passwordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : ''; + } public function _mail_login ($value) { $value = filter_var($value, FILTER_VALIDATE_EMAIL); if ($value) { diff --git a/app/Models/Entry.php b/app/Models/Entry.php index 83f68ce78..a6c67221b 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -159,7 +159,7 @@ class FreshRSS_Entry extends Minz_Model { try { // l'article n'est pas en BDD, on va le chercher sur le site $this->content = get_content_by_parsing( - $this->link(), $pathEntries + htmlspecialchars_decode($this->link(), ENT_QUOTES), $pathEntries ); } catch (Exception $e) { // rien à faire, on garde l'ancien contenu (requête a échoué) diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 9070ae426..aaf4dcf6a 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -293,6 +293,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { case 'read': $where .= 'AND e1.is_read = 1 '; break; + case 'favorite': + $where .= 'AND e1.is_favorite = 1 '; + break; default: throw new FreshRSS_EntriesGetter_Exception ('Bad state in Entry->listByType: [' . $state . ']!'); } diff --git a/app/Models/Themes.php b/app/Models/Themes.php index a52812339..c7099a1df 100644 --- a/app/Models/Themes.php +++ b/app/Models/Themes.php @@ -3,13 +3,17 @@ class FreshRSS_Themes extends Minz_Model { private static $themesUrl = '/themes/'; private static $defaultIconsUrl = '/themes/icons/'; + public static $defaultTheme = 'Origine'; - public static function get() { - $themes_list = array_diff( + public static function getList() { + return array_values(array_diff( scandir(PUBLIC_PATH . self::$themesUrl), array('..', '.') - ); + )); + } + public static function get() { + $themes_list = self::getList(); $list = array(); foreach ($themes_list as $theme_dir) { $theme = self::get_infos($theme_dir); @@ -28,7 +32,7 @@ class FreshRSS_Themes extends Minz_Model { $content = file_get_contents($json_filename); $res = json_decode($content, true); if ($res && isset($res['files']) && is_array($res['files'])) { - $res['path'] = $theme_id; + $res['id'] = $theme_id; return $res; } } @@ -39,12 +43,26 @@ class FreshRSS_Themes extends Minz_Model { private static $themeIconsUrl; private static $themeIcons; - public static function setThemeId($theme_id) { + public static function load($theme_id) { + $infos = self::get_infos($theme_id); + if (!$infos) { + if ($theme_id !== self::$defaultTheme) { //Fall-back to default theme + return self::load(self::$defaultTheme); + } + $themes_list = self::getList(); + if (!empty($themes_list)) { + if ($theme_id !== $themes_list[0]) { //Fall-back to first theme + return self::load($themes_list[0]); + } + } + return false; + } self::$themeIconsUrl = self::$themesUrl . $theme_id . '/icons/'; self::$themeIcons = is_dir(PUBLIC_PATH . self::$themeIconsUrl) ? array_fill_keys(array_diff( scandir(PUBLIC_PATH . self::$themeIconsUrl), array('..', '.') ), 1) : array(); + return $infos; } public static function icon($name, $urlOnly = false) { diff --git a/app/i18n/en.php b/app/i18n/en.php index 06bbf48e6..bb983d397 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -44,6 +44,8 @@ return array ( 'rss_view' => 'RSS feed', 'show_all_articles' => 'Show all articles', 'show_not_reads' => 'Show only unread', + 'show_read' => 'Show only read', + 'show_favorite' => 'Show favorites', 'older_first' => 'Oldest first', 'newer_first' => 'Newer first', @@ -155,21 +157,26 @@ return array ( 'not_yet_implemented' => 'Not yet implemented', 'access_protected_feeds' => 'Connection allows to access HTTP protected RSS feeds', 'no_selected_feed' => 'No feed selected.', - 'think_to_add' => 'Think to add RSS feeds!', + 'think_to_add' => '<a href="./?c=configure&a=feed">Remember to add some RSS feeds!</a>', 'current_user' => 'Current user', - 'default_user' => 'Username of the default user (maximum 16 alphanumeric characters)', - 'persona_connection_email' => 'Login mail address (for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)', - 'allow_anonymous' => 'Allow anonymous reading for the default user (%s)', + 'default_user' => 'Username of the default user <small>(maximum 16 alphanumeric characters)</small>', + 'password_form' => 'Password<br /><small>(for the Web-form login method)</small>', + 'persona_connection_email' => 'Login mail address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>', + 'allow_anonymous' => 'Allow anonymous reading of the articles of the default user (%s)', 'auth_token' => 'Authentication token', - 'explain_token' => 'Allows to access RSS output of the default user without authentication.<br /><kbd>%s?token=%s</kbd>', + 'explain_token' => 'Allows to access RSS output of the default user without authentication.<br /><kbd>%s?output=rss&token=%s</kbd>', 'login_configuration' => 'Login', 'is_admin' => 'is administrator', 'auth_type' => 'Authentication method', 'auth_none' => 'None (dangerous)', + 'auth_form' => 'Web form (traditional, requires JavaScript)', + 'http_auth' => 'HTTP (for advanced users with HTTPS)', + 'auth_persona' => 'Mozilla Persona (modern, requires JavaScript)', 'users_list' => 'List of users', 'create_user' => 'Create new user', 'username' => 'Username', + 'password' => 'Password', 'create' => 'Create', 'user_created' => 'User %s has been created', 'user_deleted' => 'User %s has been deleted', @@ -236,7 +243,7 @@ return array ( 'before_yesterday' => 'Before yesterday', 'by_author' => 'By <em>%s</em>', 'related_tags' => 'Related tags', - 'no_feed_to_display' => 'No feed to show.', + 'no_feed_to_display' => 'There is no feed to show yet.', 'about_freshrss' => 'About FreshRSS', 'project_website' => 'Project website', @@ -255,8 +262,8 @@ return array ( 'logs_empty' => 'Log file is empty', 'clear_logs' => 'Clear the logs', - 'forbidden_access' => 'Forbidden access', - 'forbidden_access_description' => 'Access is password protected, please <a class="signin" href="#">sign in</a> to read your feeds.', + 'forbidden_access' => 'Access forbidden! (%s)', + 'login_required' => 'Login required:', 'confirm_action' => 'Are you sure you want to perform this action? It cannot be cancelled!', @@ -287,6 +294,6 @@ return array ( 'Nov' => '\N\o\v\e\m\b\e\r', 'Dec' => '\D\e\c\e\m\b\e\r', // format for date() function, %s allows to indicate month in letter - 'format_date' => '%s dS Y', - 'format_date_hour' => '%s dS Y \a\t H\.i', + 'format_date' => '%s j\<\s\u\p\>S\<\/\s\u\p\> Y', + 'format_date_hour' => '%s j\<\s\u\p\>S\<\/\s\u\p\> Y \a\t H\.i', ); diff --git a/app/i18n/fr.php b/app/i18n/fr.php index cb689610b..2c44aa8d0 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -44,6 +44,8 @@ return array ( 'rss_view' => 'Flux RSS', 'show_all_articles' => 'Afficher tous les articles', 'show_not_reads' => 'Afficher les non lus', + 'show_read' => 'Afficher les lus', + 'show_favorite' => 'Afficher les favoris', 'older_first' => 'Plus anciens en premier', 'newer_first' => 'Plus récents en premier', @@ -155,21 +157,26 @@ return array ( 'not_yet_implemented' => 'Pas encore implémenté', 'access_protected_feeds' => 'La connexion permet d’accéder aux flux protégés par une authentification HTTP', 'no_selected_feed' => 'Aucun flux sélectionné.', - 'think_to_add' => 'Pensez à en ajouter !', + 'think_to_add' => '<a href="./?c=configure&a=feed">Pensez à en ajouter !</a>', 'current_user' => 'Utilisateur actuel', - 'default_user' => 'Nom de l’utilisateur par défaut (16 caractères alphanumériques maximum)', - 'persona_connection_email' => 'Adresse courriel de connexion (pour <a href="https://persona.org/" rel="external">Mozilla Persona</a>)', - 'allow_anonymous' => 'Autoriser la lecture anonyme pour l’utilisateur par défaut (%s)', + 'password_form' => 'Mot de passe<br /><small>(pour connexion par formulaire)</small>', + 'default_user' => 'Nom de l’utilisateur par défaut <small>(16 caractères alphanumériques maximum)</small>', + 'persona_connection_email' => 'Adresse courriel de connexion<br /><small>(pour <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>', + 'allow_anonymous' => 'Autoriser la lecture anonyme des articles de l’utilisateur par défaut (%s)', 'auth_token' => 'Jeton d’identification', 'explain_token' => 'Permet d’accéder à la sortie RSS de l’utilisateur par défaut sans besoin de s’authentifier.<br /><kbd>%s?output=rss&token=%s</kbd>', 'login_configuration' => 'Identification', 'is_admin' => 'est administrateur', 'auth_type' => 'Méthode d’authentification', 'auth_none' => 'Aucune (dangereux)', + 'auth_form' => 'Formulaire (traditionnel, requiert JavaScript)', + 'http_auth' => 'HTTP (pour utilisateurs avancés avec HTTPS)', + 'auth_persona' => 'Mozilla Persona (moderne, requiert JavaScript)', 'users_list' => 'Liste des utilisateurs', 'create_user' => 'Créer un nouvel utilisateur', 'username' => 'Nom d’utilisateur', + 'password' => 'Mot de passe', 'create' => 'Créer', 'user_created' => 'L’utilisateur %s a été créé', 'user_deleted' => 'L’utilisateur %s a été supprimé', @@ -236,7 +243,7 @@ return array ( 'before_yesterday' => 'À partir d’avant-hier', 'by_author' => 'Par <em>%s</em>', 'related_tags' => 'Tags associés', - 'no_feed_to_display' => 'Il n’y a aucun flux à afficher.', + 'no_feed_to_display' => 'Il n’y a aucun flux à afficher pour l’instant.', 'about_freshrss' => 'À propos de FreshRSS', 'project_website' => 'Site du projet', @@ -255,8 +262,8 @@ return array ( 'logs_empty' => 'Les logs sont vides', 'clear_logs' => 'Effacer les logs', - 'forbidden_access' => 'Accès interdit', - 'forbidden_access_description' => 'L’accès est protégé par un mot de passe, veuillez <a class="signin" href="#">vous connecter</a> pour accéder aux flux.', + 'forbidden_access' => 'Accès interdit ! (%s)', + 'login_required' => 'Accès protégé par mot de passe :', 'confirm_action' => 'Êtes-vous sûr(e) de vouloir continuer ? Cette action ne peut être annulée !', @@ -287,6 +294,6 @@ return array ( 'Nov' => '\n\o\v\e\m\b\r\e', 'Dec' => '\d\é\c\e\m\b\r\e', // format pour la fonction date(), %s permet d'indiquer le mois en toutes lettres - 'format_date' => 'd %s Y', - 'format_date_hour' => '\l\e d %s Y \à H\:i', + 'format_date' => 'j %s Y', + 'format_date_hour' => '\l\e j %s Y \à H\:i', ); diff --git a/app/i18n/install.en.php b/app/i18n/install.en.php index 4d8006977..1c24c7d7e 100644 --- a/app/i18n/install.en.php +++ b/app/i18n/install.en.php @@ -1,8 +1,8 @@ <?php return array ( - 'freshrss_installation' => 'Installation - FreshRSS', + 'freshrss_installation' => 'Installation · FreshRSS', 'freshrss' => 'FreshRSS', - 'installation_step' => 'Installation - step %d', + 'installation_step' => 'Installation — step %d · FreshRSS', 'steps' => 'Steps', 'checks' => 'Checks', 'general_configuration' => 'General configuration', @@ -40,6 +40,7 @@ return array ( 'log_is_ok' => 'Permissions on logs directory are good', 'favicons_is_ok' => 'Permissions on favicons directory are good', '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 <em>%s</em> directory. HTTP server must have rights to write into', 'fix_errors_before' => 'Fix errors before skip to the next step.', @@ -52,8 +53,6 @@ return array ( 'bdd_conf_is_ok' => 'Database configuration has been saved.', 'bdd_conf_is_ko' => 'Verify your database information.', 'host' => 'Host', - 'username' => 'Username', - 'password' => 'Password', 'bdd' => 'Database', 'prefix' => 'Table prefix', diff --git a/app/i18n/install.fr.php b/app/i18n/install.fr.php index e9dba7c23..68927df6d 100644 --- a/app/i18n/install.fr.php +++ b/app/i18n/install.fr.php @@ -1,8 +1,8 @@ <?php return array ( - 'freshrss_installation' => 'Installation - FreshRSS', + 'freshrss_installation' => 'Installation · FreshRSS', 'freshrss' => 'FreshRSS', - 'installation_step' => 'Installation - étape %d', + 'installation_step' => 'Installation — étape %d · FreshRSS', 'steps' => 'Étapes', 'checks' => 'Vérifications', 'general_configuration' => 'Configuration générale', @@ -40,6 +40,7 @@ return array ( 'log_is_ok' => 'Les droits sur le répertoire des logs sont bons', 'favicons_is_ok' => 'Les droits sur le répertoire des favicons sont bons', '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 <em>%s</em>. Le serveur HTTP doit être capable d’écrire dedans', 'fix_errors_before' => 'Veuillez corriger les erreurs avant de passer à l’étape suivante.', @@ -52,8 +53,6 @@ return array ( 'bdd_conf_is_ok' => 'La configuration de la base de données a été enregistrée.', 'bdd_conf_is_ko' => 'Vérifiez les informations d’accès à la base de données.', 'host' => 'Hôte', - 'username' => 'Nom utilisateur', - 'password' => 'Mot de passe', 'bdd' => 'Base de données', 'prefix' => 'Préfixe des tables', diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml index 7fbccce1e..2446e72cb 100644 --- a/app/layout/aside_feed.phtml +++ b/app/layout/aside_feed.phtml @@ -1,7 +1,7 @@ <ul class="nav nav-list aside aside_feed"> <li class="nav-header"><?php echo Minz_Translate::t ('your_rss_feeds'); ?></li> - <li class="nav-form"><form id="add_rss" method="post" action="<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'add')); ?>"> + <li class="nav-form"><form id="add_rss" method="post" action="<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'add')); ?>" autocomplete="off"> <div class="stick"> <input type="url" name="url_rss" placeholder="<?php echo Minz_Translate::t ('add_rss_feed'); ?>" /> <div class="dropdown"> @@ -27,10 +27,10 @@ <li class="dropdown-header"><?php echo Minz_Translate::t ('http_authentication'); ?></li> <li class="input"> - <input type="text" name="username" id="username" placeholder="<?php echo Minz_Translate::t ('username'); ?>" /> + <input type="text" name="http_user" id="http_user" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('username'); ?>" /> </li> <li class="input"> - <input type="password" name="password" id="password" placeholder="<?php echo Minz_Translate::t ('password'); ?>" /> + <input type="password" name="http_pass" id="http_pass" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('password'); ?>" /> </li> </ul> </div> diff --git a/app/layout/aside_flux.phtml b/app/layout/aside_flux.phtml index 8730baf0e..f7d8b12b9 100644 --- a/app/layout/aside_flux.phtml +++ b/app/layout/aside_flux.phtml @@ -13,9 +13,15 @@ <li><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Minz_Translate::t ('about_freshrss'); ?></a></li> <?php } ?> + <?php + $arUrl = array('c' => 'index', 'a' => 'index', 'params' => array()); + if ($this->conf->view_mode !== Minz_Request::param('output', 'normal')) { + $arUrl['params']['output'] = 'normal'; + } + ?> <li> <div class="category all"> - <a data-unread="<?php echo $this->nb_not_read; ?>" class="btn<?php echo $this->get_c == 'a' ? ' active' : ''; ?>" href="<?php echo _url ('index', 'index'); ?>"> + <a data-unread="<?php echo $this->nb_not_read; ?>" class="btn<?php echo $this->get_c == 'a' ? ' active' : ''; ?>" href="<?php echo Minz_Url::display($arUrl); ?>"> <?php echo FreshRSS_Themes::icon('all'); ?> <?php echo Minz_Translate::t ('all_feeds'); ?> </a> @@ -24,43 +30,46 @@ <li> <div class="category favorites"> - <a data-unread="<?php echo $this->nb_favorites['unread']; ?>" class="btn<?php echo $this->get_c == 's' ? ' active' : ''; ?>" href="<?php echo _url ('index', 'index', 'get', 's'); ?>"> + <a data-unread="<?php echo $this->nb_favorites['unread']; ?>" class="btn<?php echo $this->get_c == 's' ? ' active' : ''; ?>" href="<?php $arUrl['params']['get'] = 's'; echo Minz_Url::display($arUrl); ?>"> <?php echo FreshRSS_Themes::icon('bookmark'); ?> <?php echo Minz_Translate::t ('favorite_feeds', $this->nb_favorites['all']); ?> </a> </div> </li> - <?php foreach ($this->cat_aside as $cat) { ?> - <?php $feeds = $cat->feeds (); ?> - <?php if (!empty ($feeds)) { ?> - <li> - <?php $c_active = false; if ($this->get_c == $cat->id ()) { $c_active = true; } ?> - <div class="category stick<?php echo $c_active ? ' active' : ''; ?>"> - <a data-unread="<?php echo $cat->nbNotRead (); ?>" class="btn<?php echo $c_active ? ' active' : ''; ?>" href="<?php echo _url ('index', 'index', 'get', 'c_' . $cat->id ()); ?>"><?php echo $cat->name (); ?></a> - <a class="btn dropdown-toggle" href="#"><?php echo FreshRSS_Themes::icon($c_active ? 'up' : 'down'); ?></a> - </div> - - <ul class="feeds<?php echo $c_active ? ' active' : ''; ?>"> - <?php foreach ($feeds as $feed) { - $feed_id = $feed->id (); $nbEntries = $feed->nbEntries (); - $f_active = ($this->get_f == $feed_id); - ?> - <li id="f_<?php echo $feed_id; ?>" class="item<?php echo $f_active ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>"> - <div class="dropdown"> - <div class="dropdown-target"></div> - <a class="dropdown-toggle" data-fweb="<?php echo $feed->website (); ?>"><?php echo FreshRSS_Themes::icon('configure'); ?></a> -<?php /* feed_config_template */ ?> - </div> - <img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> - <a class="feed" data-unread="<?php echo $feed->nbNotRead (); ?>" data-priority="<?php echo $feed->priority (); ?>" href="<?php echo _url ('index', 'index', 'get', 'f_' . $feed_id); ?>"><?php echo $feed->name(); ?></a> - </li> - <?php } ?> - </ul> - </li> - <?php } } ?> + <?php + foreach ($this->cat_aside as $cat) { + $feeds = $cat->feeds (); + if (!empty ($feeds)) { + ?><li><?php + $c_active = false; + if ($this->get_c == $cat->id ()) { + $c_active = true; + } + ?><div class="category stick<?php echo $c_active ? ' active' : ''; ?>"><?php + ?><a data-unread="<?php echo $cat->nbNotRead (); ?>" class="btn<?php echo $c_active ? ' active' : ''; ?>" href="<?php $arUrl['params']['get'] = 'c_' . $cat->id(); echo Minz_Url::display($arUrl); ?>"><?php echo $cat->name (); ?></a><?php + ?><a class="btn dropdown-toggle" href="#"><?php echo FreshRSS_Themes::icon($c_active ? 'up' : 'down'); ?></a><?php + ?></div><?php + ?><ul class="feeds<?php echo $c_active ? ' active' : ''; ?>"><?php + foreach ($feeds as $feed) { + $feed_id = $feed->id (); + $nbEntries = $feed->nbEntries (); + $f_active = ($this->get_f == $feed_id); + ?><li id="f_<?php echo $feed_id; ?>" class="item<?php echo $f_active ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>"><?php + ?><div class="dropdown"><?php + ?><div class="dropdown-target"></div><?php + ?><a class="dropdown-toggle" data-fweb="<?php echo $feed->website (); ?>"><?php echo FreshRSS_Themes::icon('configure'); ?></a><?php + /* feed_config_template */ + ?></div><?php + ?> <img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> <?php + ?><a class="feed" data-unread="<?php echo $feed->nbNotRead (); ?>" data-priority="<?php echo $feed->priority (); ?>" href="<?php $arUrl['params']['get'] = 'f_' . $feed_id; echo Minz_Url::display($arUrl); ?>"><?php echo $feed->name(); ?></a><?php + ?></li><?php + } + ?></ul><?php + ?></li><?php + } + } ?> </ul> - <span class="aside_flux_ender"><!-- For fixed menu --></span> </div> @@ -73,7 +82,7 @@ <li class="separator"></li> <li class="item"><a href="<?php echo _url ('configure', 'feed', 'id', '!!!!!!'); ?>"><?php echo Minz_Translate::t ('administration'); ?></a></li> <li class="item"><a href="<?php echo _url ('feed', 'actualize', 'id', '!!!!!!'); ?>"><?php echo Minz_Translate::t ('actualize'); ?></a></li> - <li class="item"><a href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', 'f_!!!!!!'); ?>"><?php echo Minz_Translate::t ('mark_read'); ?></a></li> + <li class="item"><a href="<?php echo _url ('entry', 'read', 'get', 'f_!!!!!!'); ?>"><?php echo Minz_Translate::t ('mark_read'); ?></a></li> <?php } ?> </ul> </script> diff --git a/app/layout/header.phtml b/app/layout/header.phtml index 0f2c524c4..d43f682b0 100644 --- a/app/layout/header.phtml +++ b/app/layout/header.phtml @@ -1,12 +1,25 @@ -<?php if (Minz_Configuration::canLogIn()) { ?> -<ul class="nav nav-head nav-login"> - <?php if ($this->loginOk) { ?> - <li class="item"><?php echo FreshRSS_Themes::icon('logout'); ?> <a class="signout" href="#"><?php echo Minz_Translate::t ('logout'); ?></a></li> - <?php } else { ?> - <li class="item"><?php echo FreshRSS_Themes::icon('login'); ?> <a class="signin" href="#"><?php echo Minz_Translate::t ('login'); ?></a></li> - <?php } ?> -</ul> -<?php } ?> +<?php +if (Minz_Configuration::canLogIn()) { + ?><ul class="nav nav-head nav-login"><?php + switch (Minz_Configuration::authType()) { + case 'form': + if ($this->loginOk) { + ?><li class="item"><?php echo FreshRSS_Themes::icon('logout'); ?> <a class="signout" href="<?php echo _url ('index', 'formLogout'); ?>"><?php echo Minz_Translate::t ('logout'); ?></a></li><?php + } else { + ?><li class="item"><?php echo FreshRSS_Themes::icon('login'); ?> <a class="signin" href="<?php echo _url ('index', 'formLogin'); ?>"><?php echo Minz_Translate::t ('login'); ?></a></li><?php + } + break; + case 'persona': + if ($this->loginOk) { + ?><li class="item"><?php echo FreshRSS_Themes::icon('logout'); ?> <a class="signout" href="#"><?php echo Minz_Translate::t ('logout'); ?></a></li><?php + } else { + ?><li class="item"><?php echo FreshRSS_Themes::icon('login'); ?> <a class="signin" href="#"><?php echo Minz_Translate::t ('login'); ?></a></li><?php + } + break; + } + ?></ul><?php +} +?> <div class="header"> <div class="item title"> @@ -62,16 +75,31 @@ <li class="separator"></li> <li class="item"><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Minz_Translate::t ('about'); ?></a></li> <li class="item"><a href="<?php echo _url ('index', 'logs'); ?>"><?php echo Minz_Translate::t ('logs'); ?></a></li> - <?php if (Minz_Configuration::canLogIn()) { ?> - <li class="separator"></li> - <li class="item"><a class="signout" href="#"><?php echo FreshRSS_Themes::icon('logout'); ?> <?php echo Minz_Translate::t ('logout'); ?></a></li> - <?php } ?> + <?php + if (Minz_Configuration::canLogIn()) { + ?><li class="separator"></li><?php + switch (Minz_Configuration::authType()) { + case 'form': + ?><li class="item"><a class="signout" href="<?php echo _url ('index', 'formLogout'); ?>"><?php echo FreshRSS_Themes::icon('logout'), ' ', Minz_Translate::t ('logout'); ?></a></li><?php + break; + case 'persona': + ?><li class="item"><a class="signout" href="#"><?php echo FreshRSS_Themes::icon('logout'), ' ', Minz_Translate::t ('logout'); ?></a></li><?php + break; + } + } ?> </ul> </div> </div> - <?php } elseif (Minz_Configuration::canLogIn()) { ?> - <div class="item configure"> - <?php echo FreshRSS_Themes::icon('login'); ?> <a class="signin" href="#"><?php echo Minz_Translate::t ('login'); ?></a> - </div> - <?php } ?> + <?php } elseif (Minz_Configuration::canLogIn()) { + ?><div class="item configure"><?php + switch (Minz_Configuration::authType()) { + case 'form': + echo FreshRSS_Themes::icon('login'); ?><a class="signin" href="<?php echo _url ('index', 'formLogin'); ?>"><?php echo Minz_Translate::t ('login'); ?></a></li><?php + break; + case 'persona': + echo FreshRSS_Themes::icon('login'); ?><a class="signin" href="#"><?php echo Minz_Translate::t ('login'); ?></a></li><?php + break; + } + ?></div><?php + } ?> </div> diff --git a/app/layout/nav_menu.phtml b/app/layout/nav_menu.phtml index 44b49b10c..b9ce33295 100644 --- a/app/layout/nav_menu.phtml +++ b/app/layout/nav_menu.phtml @@ -56,7 +56,13 @@ } $p = isset($this->entries[0]) ? $this->entries[0] : null; $idMax = $p === null ? '0' : $p->id(); - $markReadUrl = _url ('entry', 'read', 'is_read', 1, 'get', $get, 'nextGet', $nextGet, 'idMax', $idMax); + + $arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('get' => $get, 'nextGet' => $nextGet, 'idMax' => $idMax)); + $output = Minz_Request::param('output', ''); + if (($output != '') && ($this->conf->view_mode !== $output)) { + $arUrl['params']['output'] = $output; + } + $markReadUrl = Minz_Url::display($arUrl); Minz_Session::_param ('markReadUrl', $markReadUrl); ?> @@ -103,21 +109,21 @@ $url_output = $url; $actual_view = Minz_Request::param('output', 'normal'); ?> - <?php if($actual_view != 'normal') { ?> + <?php if($actual_view !== 'normal') { ?> <li class="item"> <?php $url_output['params']['output'] = 'normal'; ?> <a class="view_normal" href="<?php echo Minz_Url::display ($url_output); ?>"> <?php echo Minz_Translate::t ('normal_view'); ?> </a> </li> - <?php } if($actual_view != 'reader') { ?> + <?php } if($actual_view !== 'reader') { ?> <li class="item"> <?php $url_output['params']['output'] = 'reader'; ?> <a class="view_normal" href="<?php echo Minz_Url::display ($url_output); ?>"> <?php echo Minz_Translate::t ('reader_view'); ?> </a> </li> - <?php } if($actual_view != 'global') { ?> + <?php } if($actual_view !== 'global') { ?> <li class="item"> <?php $url_output['params']['output'] = 'global'; ?> <a class="view_normal" href="<?php echo Minz_Url::display ($url_output); ?>"> @@ -128,24 +134,41 @@ <li class="separator"></li> - <li class="item"> - <?php - $url_state = $url; - if ($this->state == 'not_read') { - $url_state['params']['state'] = 'all'; - ?> + <?php + $url_state = $url; + $url_state['params']['state'] = 'all'; + ?> + <li class="item" role="checkbox" aria-checked="<?php echo ($this->state === 'all') ? 'true' :'false'; ?>"> <a class="print_all" href="<?php echo Minz_Url::display ($url_state); ?>"> <?php echo Minz_Translate::t ('show_all_articles'); ?> </a> - <?php - } else { - $url_state['params']['state'] = 'not_read'; - ?> + </li> + <?php + $url_state['params']['state'] = 'not_read'; + ?> + <li class="item" role="checkbox" aria-checked="<?php echo ($this->state === 'not_read') ? 'true' :'false'; ?>"> <a class="print_non_read" href="<?php echo Minz_Url::display ($url_state); ?>"> <?php echo Minz_Translate::t ('show_not_reads'); ?> </a> - <?php } ?> </li> + <?php + $url_state['params']['state'] = 'read'; + ?> + <li class="item" role="checkbox" aria-checked="<?php echo ($this->state === 'read') ? 'true' :'false'; ?>"> + <a class="print_read" href="<?php echo Minz_Url::display ($url_state); ?>"> + <?php echo Minz_Translate::t ('show_read'); ?> + </a> + </li> + <?php + $url_state['params']['state'] = 'favorite'; + ?> + <li class="item" role="checkbox" aria-checked="<?php echo ($this->state === 'favorite') ? 'true' :'false'; ?>"> + <a class="print_favorite" href="<?php echo Minz_Url::display ($url_state); ?>"> + <?php echo Minz_Translate::t ('show_favorite'); ?> + </a> + </li> + + <li class="separator"></li> <li class="item"> <?php diff --git a/app/views/configure/display.phtml b/app/views/configure/display.phtml index 11a987610..725356c8d 100644 --- a/app/views/configure/display.phtml +++ b/app/views/configure/display.phtml @@ -21,10 +21,11 @@ <div class="form-group"> <label class="group-name" for="theme"><?php echo Minz_Translate::t ('theme'); ?></label> <div class="group-controls"> - <select name="theme" id="theme"> + <select name="theme" id="theme" required=""> + <option></option> <?php foreach ($this->themes as $theme) { ?> - <option value="<?php echo $theme['path']; ?>"<?php echo $this->conf->theme === $theme['path'] ? ' selected="selected"' : ''; ?>> - <?php echo $theme['name'] . ' ' . Minz_Translate::t ('by') . ' ' . $theme['author']; ?> + <option value="<?php echo $theme['id']; ?>"<?php echo $this->conf->theme === $theme['id'] ? ' selected="selected"' : ''; ?>> + <?php echo $theme['name'] . ' — ' . Minz_Translate::t ('by') . ' ' . $theme['author']; ?> </option> <?php } ?> </select> @@ -81,7 +82,7 @@ <label class="checkbox" for="auto_load_more"> <input type="checkbox" name="auto_load_more" id="auto_load_more" value="1"<?php echo $this->conf->auto_load_more ? ' checked="checked"' : ''; ?> /> <?php echo Minz_Translate::t ('auto_load_more'); ?> - <noscript> - <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> + <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> </label> </div> </div> @@ -91,7 +92,7 @@ <label class="checkbox" for="display_posts"> <input type="checkbox" name="display_posts" id="display_posts" value="1"<?php echo $this->conf->display_posts ? ' checked="checked"' : ''; ?> /> <?php echo Minz_Translate::t ('display_articles_unfolded'); ?> - <noscript> - <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> + <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> </label> </div> </div> @@ -101,7 +102,7 @@ <label class="checkbox" for="lazyload"> <input type="checkbox" name="lazyload" id="lazyload" value="1"<?php echo $this->conf->lazyload ? ' checked="checked"' : ''; ?> /> <?php echo Minz_Translate::t ('img_with_lazyload'); ?> - <noscript> - <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> + <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> </label> </div> </div> diff --git a/app/views/configure/feed.phtml b/app/views/configure/feed.phtml index a0fe39f8a..fc26ab58b 100644 --- a/app/views/configure/feed.phtml +++ b/app/views/configure/feed.phtml @@ -11,7 +11,7 @@ <p class="alert alert-error"><span class="alert-head"><?php echo Minz_Translate::t ('damn'); ?></span> <?php echo Minz_Translate::t ('feed_in_error'); ?></p> <?php } ?> - <form method="post" action="<?php echo _url ('configure', 'feed', 'id', $this->flux->id ()); ?>"> + <form method="post" action="<?php echo _url ('configure', 'feed', 'id', $this->flux->id ()); ?>" autocomplete="off"> <legend><?php echo Minz_Translate::t ('informations'); ?></legend> <div class="form-group"> <label class="group-name" for="name"><?php echo Minz_Translate::t ('title'); ?></label> diff --git a/app/views/configure/users.phtml b/app/views/configure/users.phtml index d40a3ad5b..990c80acc 100644 --- a/app/views/configure/users.phtml +++ b/app/views/configure/users.phtml @@ -18,6 +18,14 @@ </div> <div class="form-group"> + <label class="group-name" for="passwordPlain"><?php echo Minz_Translate::t('password_form'); ?></label> + <div class="group-controls"> + <input type="password" id="passwordPlain" name="passwordPlain" pattern=".{7,}" /> + <noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript> + </div> + </div> + + <div class="form-group"> <label class="group-name" for="mail_login"><?php echo Minz_Translate::t('persona_connection_email'); ?></label> <?php $mail = $this->conf->mail_login; ?> <div class="group-controls"> @@ -26,14 +34,12 @@ </div> </div> - <?php if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { ?> <div class="form-group form-actions"> <div class="group-controls"> <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button> <button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button> </div> </div> - <?php } ?> <?php if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { ?> @@ -44,41 +50,35 @@ <div class="group-controls"> <select id="auth_type" name="auth_type" required="required"> <option value=""></option> + <option value="form"<?php echo Minz_Configuration::authType() === 'form' ? ' selected="selected"' : '', version_compare(PHP_VERSION, '5.3', '<') ? ' disabled="disabled"' : ''; ?>><?php echo Minz_Translate::t('auth_form'); ?></option> + <option value="persona"<?php echo Minz_Configuration::authType() === 'persona' ? ' selected="selected"' : '', $this->conf->mail_login == '' ? ' disabled="disabled"' : ''; ?>><?php echo Minz_Translate::t('auth_persona'); ?></option> + <option value="http_auth"<?php echo Minz_Configuration::authType() === 'http_auth' ? ' selected="selected"' : '', httpAuthUser() == '' ? ' disabled="disabled"' : ''; ?>><?php echo Minz_Translate::t('http_auth'); ?> (REMOTE_USER = '<?php echo httpAuthUser(); ?>')</option> <option value="none"<?php echo Minz_Configuration::authType() === 'none' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t('auth_none'); ?></option> - <option value="http_auth"<?php echo Minz_Configuration::authType() === 'http_auth' ? ' selected="selected"' : '', httpAuthUser() == '' ? ' disabled="disabled"' : ''; ?>>HTTP Auth</option> - <option value="persona"<?php echo Minz_Configuration::authType() === 'persona' ? ' selected="selected"' : '', $this->conf->mail_login == '' ? ' disabled="disabled"' : ''; ?>>Mozilla Persona</option> </select> - <code>$_SERVER['REMOTE_USER'] = `<?php echo httpAuthUser(); ?>`</code> - </div> - </div> - - <div class="form-group form-actions"> - <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button> - <button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button> </div> </div> - <?php if (Minz_Configuration::authType() === 'persona') { ?> - - <legend>Mozilla Persona</legend> <div class="form-group"> <div class="group-controls"> <label class="checkbox" for="anon_access"> - <input type="checkbox" name="anon_access" id="anon_access" value="1"<?php echo Minz_Configuration::allowAnonymous() ? ' checked="checked"' : ''; ?> /> + <input type="checkbox" name="anon_access" id="anon_access" value="1"<?php echo Minz_Configuration::allowAnonymous() ? ' checked="checked"' : '', + Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> /> <?php echo Minz_Translate::t('allow_anonymous', Minz_Configuration::defaultUser()); ?> </label> </div> </div> + <?php if (Minz_Configuration::canLogIn()) { ?> <div class="form-group"> <label class="group-name" for="token"><?php echo Minz_Translate::t('auth_token'); ?></label> <?php $token = $this->conf->token; ?> <div class="group-controls"> - <input type="text" id="token" name="token" value="<?php echo $token; ?>" placeholder="<?php echo Minz_Translate::t('blank_to_disable'); ?>"/> + <input type="text" id="token" name="token" value="<?php echo $token; ?>" placeholder="<?php echo Minz_Translate::t('blank_to_disable'); ?>"<?php + echo Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> /> <?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('explain_token', Minz_Url::display(null, 'html', true), $token); ?> </div> </div> + <?php } ?> <div class="form-group form-actions"> <div class="group-controls"> @@ -86,8 +86,6 @@ <button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button> </div> </div> - - <?php } ?> </form> <form method="post" action="<?php echo _url('users', 'delete'); ?>"> @@ -134,6 +132,14 @@ </div> <div class="form-group"> + <label class="group-name" for="new_user_passwordPlain"><?php echo Minz_Translate::t('password_form'); ?></label> + <div class="group-controls"> + <input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" pattern=".{7,}" /> + <noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript> + </div> + </div> + + <div class="form-group"> <label class="group-name" for="new_user_email"><?php echo Minz_Translate::t('persona_connection_email'); ?></label> <?php $mail = $this->conf->mail_login; ?> <div class="group-controls"> diff --git a/app/views/entry/bookmark.phtml b/app/views/entry/bookmark.phtml index 55b2d3d3d..c1fc32b7f 100755 --- a/app/views/entry/bookmark.phtml +++ b/app/views/entry/bookmark.phtml @@ -1,7 +1,7 @@ <?php header('Content-Type: application/json; charset=UTF-8'); -if (Minz_Request::param ('is_favorite')) { +if (Minz_Request::param ('is_favorite', true)) { Minz_Request::_param ('is_favorite', 0); } else { Minz_Request::_param ('is_favorite', 1); diff --git a/app/views/entry/read.phtml b/app/views/entry/read.phtml index d063ba3d5..9e79d4c07 100755 --- a/app/views/entry/read.phtml +++ b/app/views/entry/read.phtml @@ -1,7 +1,7 @@ <?php header('Content-Type: application/json; charset=UTF-8'); -if (Minz_Request::param ('is_read')) { +if (Minz_Request::param ('is_read', true)) { Minz_Request::_param ('is_read', 0); } else { Minz_Request::_param ('is_read', 1); diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml index 92c068f7e..0ecdc1bca 100644 --- a/app/views/helpers/javascript_vars.phtml +++ b/app/views/helpers/javascript_vars.phtml @@ -1,47 +1,44 @@ <?php - echo '"use strict";', "\n"; - $mark = $this->conf->mark_when; - echo 'var ', - 'hide_posts=', ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'false' : 'true', - ',auto_mark_article=', $mark['article'] ? 'true' : 'false', - ',auto_mark_site=', $mark['site'] ? 'true' : 'false', - ',auto_mark_scroll=', $mark['scroll'] ? 'true' : 'false', - ',auto_load_more=', $this->conf->auto_load_more ? 'true' : 'false', - ',full_lazyload=', $this->conf->lazyload && ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'true' : 'false', - ',does_lazyload=', $this->conf->lazyload ? 'true' : 'false'; - - $s = $this->conf->shortcuts; - echo ',shortcuts={', - 'mark_read:"', $s['mark_read'], '",', - 'mark_favorite:"', $s['mark_favorite'], '",', - 'go_website:"', $s['go_website'], '",', - 'prev_entry:"', $s['prev_entry'], '",', - 'next_entry:"', $s['next_entry'], '",', - 'collapse_entry:"', $s['collapse_entry'], '",', - 'load_more:"', $s['load_more'], '",', - 'auto_share:"', $s['auto_share'], '"', - "},\n"; - - if (Minz_Request::param ('output') === 'global') { - echo "iconClose='", FreshRSS_Themes::icon('close'), "',\n"; - } - - $mail = Minz_Session::param ('mail', 'null'); - if ($mail != 'null') { - $mail = '"' . $mail . '"'; - } - echo 'use_persona=', Minz_Configuration::authType() === 'persona' ? 'true' : 'false', - ',url_freshrss="', _url ('index', 'index'), '",', - 'url_login="', _url ('index', 'login'), '",', - 'url_logout="', _url ('index', 'logout'), '",', - 'current_user_mail=', $mail, ",\n"; - - echo 'load_shortcuts=', Minz_Request::controllerName () === 'index' && Minz_Request::actionName () === 'index' ? 'true' : 'false', ",\n"; - - echo 'str_confirmation="', Minz_Translate::t('confirm_action'), '"', ",\n"; - - $autoActualise = Minz_Session::param('actualize_feeds', false); - echo 'auto_actualize_feeds=', $autoActualise ? 'true' : 'false', ";\n"; - if ($autoActualise) { - Minz_Session::_param('actualize_feeds', false); - } + +echo '"use strict";', "\n"; + +$mark = $this->conf->mark_when; +echo 'var ', + 'hide_posts=', ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'false' : 'true', + ',auto_mark_article=', $mark['article'] ? 'true' : 'false', + ',auto_mark_site=', $mark['site'] ? 'true' : 'false', + ',auto_mark_scroll=', $mark['scroll'] ? 'true' : 'false', + ',auto_load_more=', $this->conf->auto_load_more ? 'true' : 'false', + ',full_lazyload=', $this->conf->lazyload && ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'true' : 'false', + ',does_lazyload=', $this->conf->lazyload ? 'true' : 'false'; + +$s = $this->conf->shortcuts; +echo ',shortcuts={', + 'mark_read:"', $s['mark_read'], '",', + 'mark_favorite:"', $s['mark_favorite'], '",', + 'go_website:"', $s['go_website'], '",', + 'prev_entry:"', $s['prev_entry'], '",', + 'next_entry:"', $s['next_entry'], '",', + 'collapse_entry:"', $s['collapse_entry'], '",', + 'load_more:"', $s['load_more'], '",', + 'auto_share:"', $s['auto_share'], '"', +"},\n"; + +if (Minz_Request::param ('output') === 'global') { + echo "iconClose='", FreshRSS_Themes::icon('close'), "',\n"; +} + +$authType = Minz_Configuration::authType(); +if ($authType === 'persona') { + echo 'current_user_mail="' . Minz_Session::param ('mail', '') . '",'; +} + +echo 'authType="', $authType, '",', + 'url_freshrss="', _url ('index', 'index'), '",', + 'url_login="', _url ('index', 'login'), '",', + 'url_logout="', _url ('index', 'logout'), '",'; + +echo 'str_confirmation="', Minz_Translate::t('confirm_action'), '"', ",\n"; + +$autoActualise = Minz_Session::param('actualize_feeds', false); +echo 'auto_actualize_feeds=', $autoActualise ? 'true' : 'false', ";\n"; diff --git a/app/views/helpers/view/global_view.phtml b/app/views/helpers/view/global_view.phtml index 58ff13d4e..4fb807649 100644 --- a/app/views/helpers/view/global_view.phtml +++ b/app/views/helpers/view/global_view.phtml @@ -2,13 +2,22 @@ <div id="stream" class="global categories"> <?php + $arUrl = array('c' => 'index', 'a' => 'index', 'params' => array()); + if ($this->conf->view_mode !== 'normal') { + $arUrl['params']['output'] = 'normal'; + } + $p = Minz_Request::param('state', ''); + if (($p != '') && ($this->conf->default_view !== $p)) { + $arUrl['params']['state'] = $p; + } + foreach ($this->cat_aside as $cat) { $feeds = $cat->feeds (); if (!empty ($feeds)) { ?> <div class="box-category"> <div class="category"> - <a data-unread="<?php echo $cat->nbNotRead (); ?>" class="btn" href="<?php echo _url ('index', 'index', 'get', 'c_' . $cat->id (), 'output', 'normal'); ?>"> + <a data-unread="<?php echo $cat->nbNotRead (); ?>" class="btn" href="<?php $arUrl['params']['get'] = 'c_' . $cat->id (); echo Minz_Url::display($arUrl); ?>"> <?php echo $cat->name(); ?> </a> </div> @@ -17,7 +26,7 @@ <?php $not_read = $feed->nbNotRead (); ?> <li id="f_<?php echo $feed->id (); ?>" class="item<?php echo $feed->inError () ? ' error' : ''; ?><?php echo $feed->nbEntries () == 0 ? ' empty' : ''; ?>"> <img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> - <a class="feed" data-unread="<?php echo $feed->nbNotRead (); ?>" data-priority="<?php echo $feed->priority (); ?>" href="<?php echo _url ('index', 'index', 'get', 'f_' . $feed->id (), 'output', 'normal'); ?>"> + <a class="feed" data-unread="<?php echo $feed->nbNotRead (); ?>" data-priority="<?php echo $feed->priority (); ?>" href="<?php $arUrl['params']['get'] = 'f_' . $feed->id(); echo Minz_Url::display($arUrl); ?>"> <?php echo $feed->name(); ?> </a> </li> diff --git a/app/views/helpers/view/normal_view.phtml b/app/views/helpers/view/normal_view.phtml index 5e46f3c8e..a1df87579 100644 --- a/app/views/helpers/view/normal_view.phtml +++ b/app/views/helpers/view/normal_view.phtml @@ -37,50 +37,59 @@ if (!empty($this->entries)) { $bottomline_link = $this->conf->bottomline_link; ?> -<div id="stream" class="normal<?php echo $hidePosts ? ' hide_posts' : ''; ?>"> - <?php foreach ($this->entries as $item) { ?> +<div id="stream" class="normal<?php echo $hidePosts ? ' hide_posts' : ''; ?>"><?php + foreach ($this->entries as $item) { + if ($display_today && $item->isDay (FreshRSS_Days::TODAY, $this->today)) { + ?><div class="day" id="day_today"><?php + echo Minz_Translate::t ('today'); + ?><span class="date"> — <?php echo timestamptodate (time (), false); ?></span><?php + ?><span class="name"><?php echo $this->currentName; ?></span><?php + ?></div><?php + $display_today = false; + } + if ($display_yesterday && $item->isDay (FreshRSS_Days::YESTERDAY, $this->today)) { + ?><div class="day" id="day_yesterday"><?php + echo Minz_Translate::t ('yesterday'); + ?><span class="date"> — <?php echo timestamptodate (time () - 86400, false); ?></span><?php + ?><span class="name"><?php echo $this->currentName; ?></span><?php + ?></div><?php + $display_yesterday = false; + } + if ($display_others && $item->isDay (FreshRSS_Days::BEFORE_YESTERDAY, $this->today)) { + ?><div class="day" id="day_before_yesterday"><?php + echo Minz_Translate::t ('before_yesterday'); + ?><span class="name"><?php echo $this->currentName; ?></span><?php + ?></div><?php + $display_others = false; + } - <?php if ($display_today && $item->isDay (FreshRSS_Days::TODAY, $this->today)) { ?> - <div class="day" id="day_today"> - <?php echo Minz_Translate::t ('today'); ?> - <span class="date"> - <?php echo timestamptodate (time (), false); ?></span> - <span class="name"><?php echo $this->currentName; ?></span> - </div> - <?php $display_today = false; } ?> - <?php if ($display_yesterday && $item->isDay (FreshRSS_Days::YESTERDAY, $this->today)) { ?> - <div class="day" id="day_yesterday"> - <?php echo Minz_Translate::t ('yesterday'); ?> - <span class="date"> - <?php echo timestamptodate (time () - 86400, false); ?></span> - <span class="name"><?php echo $this->currentName; ?></span> - </div> - <?php $display_yesterday = false; } ?> - <?php if ($display_others && $item->isDay (FreshRSS_Days::BEFORE_YESTERDAY, $this->today)) { ?> - <div class="day" id="day_before_yesterday"> - <?php echo Minz_Translate::t ('before_yesterday'); ?> - <span class="name"><?php echo $this->currentName; ?></span> - </div> - <?php $display_others = false; } ?> - - <div class="flux<?php echo !$item->isRead () ? ' not_read' : ''; ?><?php echo $item->isFavorite () ? ' favorite' : ''; ?>" id="flux_<?php echo $item->id (); ?>"> + ?><div class="flux<?php echo !$item->isRead () ? ' not_read' : ''; ?><?php echo $item->isFavorite () ? ' favorite' : ''; ?>" id="flux_<?php echo $item->id (); ?>"> <ul class="horizontal-list flux_header"><?php if ($this->loginOk) { if ($topline_read) { ?><li class="item manage"><?php - ?><a class="read" href="<?php echo _url ('entry', 'read', 'id', $item->id (), 'is_read', $item->isRead () ? 0 : 1); ?>"><?php + $arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('id' => $item->id ())); + if ($item->isRead()) { + $arUrl['params']['is_read'] = 0; + } + ?><a class="read" href="<?php echo Minz_Url::display($arUrl); ?>"><?php echo FreshRSS_Themes::icon($item->isRead () ? 'read' : 'unread'); ?></a><?php ?></li><?php } if ($topline_favorite) { - ?><li class="item manage"><?php - ?><a class="bookmark" href="<?php echo _url ('entry', 'bookmark', 'id', $item->id (), 'is_favorite', $item->isFavorite () ? 0 : 1); ?>"><?php + ?><li class="item manage"><?php + $arUrl = array('c' => 'entry', 'a' => 'bookmark', 'params' => array('id' => $item->id ())); + if ($item->isFavorite()) { + $arUrl['params']['is_favorite'] = 0; + } + ?><a class="bookmark" href="<?php echo Minz_Url::display($arUrl); ?>"><?php echo FreshRSS_Themes::icon($item->isFavorite () ? 'starred' : 'non-starred'); ?></a><?php ?></li><?php } } $feed = FreshRSS_CategoryDAO::findFeed($this->cat_aside, $item->feed ()); //We most likely already have the feed object in cache if (empty($feed)) $feed = $item->feed (true); - ?> - <li class="item website"><a href="<?php echo _url ('index', 'index', 'get', 'f_' . $feed->id ()); ?>"><img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span></a></li> + ?><li class="item website"><a href="<?php echo _url ('index', 'index', 'get', 'f_' . $feed->id ()); ?>"><img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span></a></li> <li class="item title"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo $item->title (); ?></a></li> <?php if ($topline_date) { ?><li class="item date"><?php echo $item->date (); ?> </li><?php } ?> <?php if ($topline_link) { ?><li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a></li><?php } ?> @@ -103,24 +112,30 @@ if (!empty($this->entries)) { if ($this->loginOk) { if ($bottomline_read) { ?><li class="item manage"><?php - ?><a class="read" href="<?php echo _url ('entry', 'read', 'id', $item->id (), 'is_read', $item->isRead () ? 0 : 1); ?>"><?php + $arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('id' => $item->id ())); + if ($item->isRead()) { + $arUrl['params']['is_read'] = 0; + } + ?><a class="read" href="<?php echo Minz_Url::display($arUrl); ?>"><?php echo FreshRSS_Themes::icon($item->isRead () ? 'read' : 'unread'); ?></a><?php ?></li><?php } if ($bottomline_favorite) { ?><li class="item manage"><?php - ?><a class="bookmark" href="<?php echo _url ('entry', 'bookmark', 'id', $item->id (), 'is_favorite', $item->isFavorite () ? 0 : 1); ?>"><?php + $arUrl = array('c' => 'entry', 'a' => 'bookmark', 'params' => array('id' => $item->id ())); + if ($item->isFavorite()) { + $arUrl['params']['is_favorite'] = 0; + } + ?><a class="bookmark" href="<?php echo Minz_Url::display($arUrl); ?>"><?php echo FreshRSS_Themes::icon($item->isFavorite () ? 'starred' : 'non-starred'); ?></a><?php ?></li><?php } } ?> - <li class="item"> - <?php + <li class="item"><?php if ($bottomline_sharing) { $link = urlencode ($item->link ()); - $title = urlencode ($item->title () . ' - ' . $feed->name ()); - ?> - <div class="dropdown"> + $title = urlencode ($item->title () . ' · ' . $feed->name ()); + ?><div class="dropdown"> <div id="dropdown-share-<?php echo $item->id ();?>" class="dropdown-target"></div> <a class="dropdown-toggle" href="#dropdown-share-<?php echo $item->id ();?>"> <?php echo FreshRSS_Themes::icon('share'); ?> @@ -181,29 +196,30 @@ if (!empty($this->entries)) { </ul> </div> <?php } ?> - </li> - <?php - $tags = $bottomline_tags ? $item->tags() : null; - if (!empty($tags)) { - ?> - <li class="item"> + </li><?php + $tags = $bottomline_tags ? $item->tags() : null; + if (!empty($tags)) { + ?><li class="item"> <div class="dropdown"> <div id="dropdown-tags-<?php echo $item->id ();?>" class="dropdown-target"></div> - <a class="dropdown-toggle" href="#dropdown-tags-<?php echo $item->id ();?>"> - <?php echo FreshRSS_Themes::icon('tag'); ?> - <?php echo Minz_Translate::t ('related_tags'); ?> - </a> + <a class="dropdown-toggle" href="#dropdown-tags-<?php echo $item->id ();?>"><?php + echo FreshRSS_Themes::icon('tag'), Minz_Translate::t ('related_tags'); + ?></a> <ul class="dropdown-menu"> - <li class="dropdown-close"><a href="#close">❌</a></li> - <?php foreach($tags as $tag) { ?> - <li class="item"><a href="<?php echo _url ('index', 'index', 'search', urlencode ('#' . $tag)); ?>"><?php echo $tag; ?></a></li> - <?php } ?> + <li class="dropdown-close"><a href="#close">❌</a></li><?php + foreach($tags as $tag) { + ?><li class="item"><a href="<?php echo _url ('index', 'index', 'search', urlencode ('#' . $tag)); ?>"><?php echo $tag; ?></a></li><?php + } ?> </ul> </div> - </li> - <?php } ?> - <?php if ($bottomline_date) { ?><li class="item date"><?php echo $item->date (); ?> </li><?php } ?> - <?php if ($bottomline_link) { ?><li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a></li><?php } ?> + </li><?php + } + if ($bottomline_date) { + ?><li class="item date"><?php echo $item->date (); ?></li><?php + } + if ($bottomline_link) { + ?><li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a></li><?php + } ?> </ul> </div> </div> @@ -217,5 +233,6 @@ if (!empty($this->entries)) { <?php } else { ?> <div id="stream" class="alert alert-warn normal"> <span class="alert-head"><?php echo Minz_Translate::t ('no_feed_to_display'); ?></span> + <?php echo Minz_Translate::t ('think_to_add'); ?> </div> -<?php } ?>
\ No newline at end of file +<?php } ?> diff --git a/app/views/helpers/view/reader_view.phtml b/app/views/helpers/view/reader_view.phtml index 2f64e672a..bda96e86d 100644 --- a/app/views/helpers/view/reader_view.phtml +++ b/app/views/helpers/view/reader_view.phtml @@ -22,7 +22,7 @@ if (!empty($this->entries)) { <div class="author"> <?php $author = $item->author (); ?> - <?php echo $author != '' ? Minz_Translate::t ('by_author', $author) . ' - ' : ''; ?> + <?php echo $author != '' ? Minz_Translate::t ('by_author', $author) . ' — ' : ''; ?> <?php echo $item->date (); ?> </div> @@ -44,5 +44,6 @@ if (!empty($this->entries)) { <?php } else { ?> <div id="stream" class="alert alert-warn reader"> <span class="alert-head"><?php echo Minz_Translate::t ('no_feed_to_display'); ?></span> + <?php echo Minz_Translate::t ('think_to_add'); ?> </div> -<?php } ?>
\ No newline at end of file +<?php } ?> diff --git a/app/views/index/about.phtml b/app/views/index/about.phtml index ae64727d7..76ff804d8 100644 --- a/app/views/index/about.phtml +++ b/app/views/index/about.phtml @@ -8,7 +8,7 @@ <dd><a href="<?php echo FRESHRSS_WEBSITE; ?>"><?php echo FRESHRSS_WEBSITE; ?></a></dd> <dt><?php echo Minz_Translate::t ('lead_developer'); ?></dt> - <dd><a href="mailto:contact@marienfressinaud.fr">Marien Fressinaud</a> - <a href="http://marienfressinaud.fr"><?php echo Minz_Translate::t ('website'); ?></a></dd> + <dd><a href="mailto:contact@marienfressinaud.fr">Marien Fressinaud</a> — <a href="http://marienfressinaud.fr"><?php echo Minz_Translate::t ('website'); ?></a></dd> <dt><?php echo Minz_Translate::t ('bugs_reports'); ?></dt> <dd><?php echo Minz_Translate::t ('github_or_email'); ?></dd> diff --git a/app/views/index/formLogin.phtml b/app/views/index/formLogin.phtml new file mode 100644 index 000000000..cc43bcd19 --- /dev/null +++ b/app/views/index/formLogin.phtml @@ -0,0 +1,43 @@ +<div class="post content"> + +<?php +if (Minz_Configuration::canLogIn()) { + ?><h1><?php echo Minz_Translate::t('login'); ?></h1><?php + switch (Minz_Configuration::authType()) { + + case 'form': + ?><form id="loginForm" method="post" action="<?php echo _url('index', 'formLogin'); ?>"> + <div class="form-group"> + <label class="group-name" for="username"><?php echo Minz_Translate::t('username'); ?></label> + <div class="group-controls"> + <input type="text" id="username" name="username" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" autofocus="autofocus" /> + </div> + </div> + <div class="form-group"> + <label class="group-name" for="passwordPlain"><?php echo Minz_Translate::t('password'); ?></label> + <div class="group-controls"> + <input type="password" id="passwordPlain" required="required" /> + <input type="hidden" id="challenge" name="challenge" /> + <noscript><strong><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></strong></noscript> + </div> + </div> + <div class="form-group form-actions"> + <div class="group-controls"> + <button id="loginButton" type="submit" class="btn btn-important"><?php echo Minz_Translate::t('login'); ?></button> + </div> + </div> + </form><?php + break; + + case 'persona': + ?><p><?php echo FreshRSS_Themes::icon('login'); ?> <a class="signin" href="#"><?php echo Minz_Translate::t('login'); ?></a></p><?php + break; + } +} else { + ?><h1>FreshRSS</h1> + <p><?php echo Minz_Translate::t('forbidden_access', Minz_Configuration::authType()); ?></p><?php +} +?> + + <p><a href="<?php echo _url('index', 'about'); ?>"><?php echo Minz_Translate::t('about_freshrss'); ?></a></p> +</div> diff --git a/app/views/index/index.phtml b/app/views/index/index.phtml index 549d0b61e..9a7c9f3b9 100644 --- a/app/views/index/index.phtml +++ b/app/views/index/index.phtml @@ -1,15 +1,5 @@ <?php -function showForbidden() { -?><div class="post content"> - <h1><?php echo Minz_Translate::t ('forbidden_access'); ?></h1> - <p><?php echo Minz_Configuration::canLogIn() ? - Minz_Translate::t ('forbidden_access_description') : - Minz_Translate::t ('forbidden_access') . ' (' . Minz_Configuration::authType() . ')'; ?></p> - <p><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Minz_Translate::t ('about_freshrss'); ?></a></p> -</div><?php -} - $output = Minz_Request::param ('output', 'normal'); if ($this->loginOk || Minz_Configuration::allowAnonymous()) { @@ -31,8 +21,8 @@ if ($this->loginOk || Minz_Configuration::allowAnonymous()) { if ($token_is_ok) { $this->renderHelper ('view/rss_view'); } else { - showForbidden(); + Minz_Request::forward(array('c' => 'index', 'a' => 'formLogin'), true); } } else { - showForbidden(); + Minz_Request::forward(array('c' => 'index', 'a' => 'formLogin'), true); } diff --git a/app/views/javascript/nbUnreadsPerFeed.phtml b/app/views/javascript/nbUnreadsPerFeed.phtml new file mode 100644 index 000000000..68f98ce9e --- /dev/null +++ b/app/views/javascript/nbUnreadsPerFeed.phtml @@ -0,0 +1,8 @@ +<?php +$result = array(); +foreach ($this->categories as $cat) { + foreach ($cat->feeds() as $feed) { + $result[$feed->id()] = $feed->nbNotRead(); + } +} +echo json_encode($result); diff --git a/app/views/javascript/nonce.phtml b/app/views/javascript/nonce.phtml new file mode 100644 index 000000000..4ac46c8fc --- /dev/null +++ b/app/views/javascript/nonce.phtml @@ -0,0 +1,2 @@ +<?php +echo json_encode(array('salt1' => $this->salt1, 'nonce' => $this->nonce)); diff --git a/constants.php b/constants.php index cb5068da0..0faac7c5f 100644 --- a/constants.php +++ b/constants.php @@ -1,5 +1,5 @@ <?php -define('FRESHRSS_VERSION', '0.7-beta4'); +define('FRESHRSS_VERSION', '0.7-RC1'); define('FRESHRSS_WEBSITE', 'http://freshrss.org'); // Constantes de chemins diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php index 2c30661ed..72e2cedc0 100644 --- a/lib/Minz/Configuration.php +++ b/lib/Minz/Configuration.php @@ -109,21 +109,23 @@ class Minz_Configuration { return self::$auth_type !== 'none'; } public static function canLogIn() { - return self::$auth_type === 'persona'; + return self::$auth_type === 'form' || self::$auth_type === 'persona'; } public static function _allowAnonymous($allow = false) { - self::$allow_anonymous = (bool)$allow; + self::$allow_anonymous = ((bool)$allow) && self::canLogIn(); } public static function _authType($value) { $value = strtolower($value); switch ($value) { + case 'form': case 'http_auth': case 'persona': case 'none': self::$auth_type = $value; break; } + self::_allowAnonymous(self::$allow_anonymous); } /** @@ -251,12 +253,12 @@ class Minz_Configuration { if (isset ($general['default_user'])) { self::$default_user = $general['default_user']; } - if (isset ($general['allow_anonymous'])) { - self::$allow_anonymous = ((bool)($general['allow_anonymous'])) && ($general['allow_anonymous'] !== 'no'); - } if (isset ($general['auth_type'])) { self::_authType($general['auth_type']); } + if (isset ($general['allow_anonymous'])) { + self::$allow_anonymous = ((bool)($general['allow_anonymous'])) && ($general['allow_anonymous'] !== 'no'); + } // Base de données if (isset ($ini_array['db'])) { diff --git a/lib/Minz/FrontController.php b/lib/Minz/FrontController.php index 7b8526bc8..80eda8877 100644 --- a/lib/Minz/FrontController.php +++ b/lib/Minz/FrontController.php @@ -34,7 +34,7 @@ class Minz_FrontController { */ public function __construct () { if (LOG_PATH === false) { - $this->killApp ('Path doesn’t exist : LOG_PATH'); + $this->killApp ('Path not found: LOG_PATH'); } try { diff --git a/lib/Minz/View.php b/lib/Minz/View.php index ba9555cd7..e170bd406 100644 --- a/lib/Minz/View.php +++ b/lib/Minz/View.php @@ -63,7 +63,7 @@ class Minz_View { * Affiche la Vue en elle-même */ public function render () { - if ((@include($this->view_filename)) === false) { + if ((include($this->view_filename)) === false) { Minz_Log::record ('File not found: `' . $this->view_filename . '`', Minz_Log::NOTICE); @@ -79,7 +79,7 @@ class Minz_View { . self::LAYOUT_PATH_NAME . '/' . $part . '.phtml'; - if ((@include($fic_partial)) === false) { + if ((include($fic_partial)) === false) { Minz_Log::record ('File not found: `' . $fic_partial . '`', Minz_Log::WARNING); @@ -95,7 +95,7 @@ class Minz_View { . '/views/helpers/' . $helper . '.phtml'; - if ((@include($fic_helper)) === false) {; + if ((include($fic_helper)) === false) {; Minz_Log::record ('File not found: `' . $fic_helper . '`', Minz_Log::WARNING); diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 4f98ed14a..33d7ebc32 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -110,6 +110,7 @@ function sanitizeHTML($data) { static $simplePie = null; if ($simplePie == null) { $simplePie = new SimplePie(); + $simplePie->init(); } return html_only_entity_decode($simplePie->sanitize->sanitize($data, SIMPLEPIE_CONSTRUCT_MAYBE_HTML)); } @@ -118,22 +119,13 @@ function sanitizeHTML($data) { function get_content_by_parsing ($url, $path) { require_once (LIB_PATH . '/lib_phpQuery.php'); + syslog(LOG_INFO, 'FreshRSS GET ' . $url); $html = file_get_contents ($url); if ($html) { $doc = phpQuery::newDocument ($html); $content = $doc->find ($path); - $content->find ('*')->removeAttr ('style') - ->removeAttr ('id') - ->removeAttr ('class') - ->removeAttr ('onload') - ->removeAttr ('target'); - $content->removeAttr ('style') - ->removeAttr ('id') - ->removeAttr ('class') - ->removeAttr ('onload') - ->removeAttr ('target'); - return $content->__toString (); + return sanitizeHTML($content->__toString()); } else { throw new Exception (); } diff --git a/lib/password_compat.php b/lib/password_compat.php new file mode 100644 index 000000000..e8ec02885 --- /dev/null +++ b/lib/password_compat.php @@ -0,0 +1,279 @@ +<?php +/** + * A Compatibility library with PHP 5.5's simplified password hashing API. + * + * @author Anthony Ferrara <ircmaxell@php.net> + * @license http://www.opensource.org/licenses/mit-license.html MIT License + * @copyright 2012 The Authors + */ + +namespace { + +if (!defined('PASSWORD_DEFAULT')) { + + define('PASSWORD_BCRYPT', 1); + define('PASSWORD_DEFAULT', PASSWORD_BCRYPT); + + /** + * Hash the password using the specified algorithm + * + * @param string $password The password to hash + * @param int $algo The algorithm to use (Defined by PASSWORD_* constants) + * @param array $options The options for the algorithm to use + * + * @return string|false The hashed password, or false on error. + */ + function password_hash($password, $algo, array $options = array()) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_hash to function", E_USER_WARNING); + return null; + } + if (!is_string($password)) { + trigger_error("password_hash(): Password must be a string", E_USER_WARNING); + return null; + } + if (!is_int($algo)) { + trigger_error("password_hash() expects parameter 2 to be long, " . gettype($algo) . " given", E_USER_WARNING); + return null; + } + $resultLength = 0; + switch ($algo) { + case PASSWORD_BCRYPT: + // Note that this is a C constant, but not exposed to PHP, so we don't define it here. + $cost = 10; + if (isset($options['cost'])) { + $cost = $options['cost']; + if ($cost < 4 || $cost > 31) { + trigger_error(sprintf("password_hash(): Invalid bcrypt cost parameter specified: %d", $cost), E_USER_WARNING); + return null; + } + } + // The length of salt to generate + $raw_salt_len = 16; + // The length required in the final serialization + $required_salt_len = 22; + $hash_format = sprintf("$2y$%02d$", $cost); + // The expected length of the final crypt() output + $resultLength = 60; + break; + default: + trigger_error(sprintf("password_hash(): Unknown password hashing algorithm: %s", $algo), E_USER_WARNING); + return null; + } + $salt_requires_encoding = false; + if (isset($options['salt'])) { + switch (gettype($options['salt'])) { + case 'NULL': + case 'boolean': + case 'integer': + case 'double': + case 'string': + $salt = (string) $options['salt']; + break; + case 'object': + if (method_exists($options['salt'], '__tostring')) { + $salt = (string) $options['salt']; + break; + } + case 'array': + case 'resource': + default: + trigger_error('password_hash(): Non-string salt parameter supplied', E_USER_WARNING); + return null; + } + if (PasswordCompat\binary\_strlen($salt) < $required_salt_len) { + trigger_error(sprintf("password_hash(): Provided salt is too short: %d expecting %d", PasswordCompat\binary\_strlen($salt), $required_salt_len), E_USER_WARNING); + return null; + } elseif (0 == preg_match('#^[a-zA-Z0-9./]+$#D', $salt)) { + $salt_requires_encoding = true; + } + } else { + $buffer = ''; + $buffer_valid = false; + if (function_exists('mcrypt_create_iv') && !defined('PHALANGER')) { + $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) { + $buffer = openssl_random_pseudo_bytes($raw_salt_len); + if ($buffer) { + $buffer_valid = true; + } + } + if (!$buffer_valid && @is_readable('/dev/urandom')) { + $f = fopen('/dev/urandom', 'r'); + $read = PasswordCompat\binary\_strlen($buffer); + while ($read < $raw_salt_len) { + $buffer .= fread($f, $raw_salt_len - $read); + $read = PasswordCompat\binary\_strlen($buffer); + } + fclose($f); + if ($read >= $raw_salt_len) { + $buffer_valid = true; + } + } + if (!$buffer_valid || PasswordCompat\binary\_strlen($buffer) < $raw_salt_len) { + $bl = PasswordCompat\binary\_strlen($buffer); + for ($i = 0; $i < $raw_salt_len; $i++) { + if ($i < $bl) { + $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); + } else { + $buffer .= chr(mt_rand(0, 255)); + } + } + } + $salt = $buffer; + $salt_requires_encoding = true; + } + if ($salt_requires_encoding) { + // encode string with the Base64 variant used by crypt + $base64_digits = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + $bcrypt64_digits = + './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + $base64_string = base64_encode($salt); + $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits); + } + $salt = PasswordCompat\binary\_substr($salt, 0, $required_salt_len); + + $hash = $hash_format . $salt; + + $ret = crypt($password, $hash); + + if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != $resultLength) { + return false; + } + + return $ret; + } + + /** + * Get information about the password hash. Returns an array of the information + * that was used to generate the password hash. + * + * array( + * 'algo' => 1, + * 'algoName' => 'bcrypt', + * 'options' => array( + * 'cost' => 10, + * ), + * ) + * + * @param string $hash The password hash to extract info from + * + * @return array The array of information about the hash. + */ + function password_get_info($hash) { + $return = array( + 'algo' => 0, + 'algoName' => 'unknown', + 'options' => array(), + ); + if (PasswordCompat\binary\_substr($hash, 0, 4) == '$2y$' && PasswordCompat\binary\_strlen($hash) == 60) { + $return['algo'] = PASSWORD_BCRYPT; + $return['algoName'] = 'bcrypt'; + list($cost) = sscanf($hash, "$2y$%d$"); + $return['options']['cost'] = $cost; + } + return $return; + } + + /** + * Determine if the password hash needs to be rehashed according to the options provided + * + * If the answer is true, after validating the password using password_verify, rehash it. + * + * @param string $hash The hash to test + * @param int $algo The algorithm used for new password hashes + * @param array $options The options array passed to password_hash + * + * @return boolean True if the password needs to be rehashed. + */ + function password_needs_rehash($hash, $algo, array $options = array()) { + $info = password_get_info($hash); + if ($info['algo'] != $algo) { + return true; + } + switch ($algo) { + case PASSWORD_BCRYPT: + $cost = isset($options['cost']) ? $options['cost'] : 10; + if ($cost != $info['options']['cost']) { + return true; + } + break; + } + return false; + } + + /** + * Verify a password against a hash using a timing attack resistant approach + * + * @param string $password The password to verify + * @param string $hash The hash to verify against + * + * @return boolean If the password matches the hash + */ + function password_verify($password, $hash) { + if (!function_exists('crypt')) { + trigger_error("Crypt must be loaded for password_verify to function", E_USER_WARNING); + return false; + } + $ret = crypt($password, $hash); + if (!is_string($ret) || PasswordCompat\binary\_strlen($ret) != PasswordCompat\binary\_strlen($hash) || PasswordCompat\binary\_strlen($ret) <= 13) { + return false; + } + + $status = 0; + for ($i = 0; $i < PasswordCompat\binary\_strlen($ret); $i++) { + $status |= (ord($ret[$i]) ^ ord($hash[$i])); + } + + return $status === 0; + } +} + +} + +namespace PasswordCompat\binary { + /** + * Count the number of bytes in a string + * + * We cannot simply use strlen() for this, because it might be overwritten by the mbstring extension. + * In this case, strlen() will count the number of *characters* based on the internal encoding. A + * sequence of bytes might be regarded as a single multibyte character. + * + * @param string $binary_string The input string + * + * @internal + * @return int The number of bytes + */ + function _strlen($binary_string) { + if (function_exists('mb_strlen')) { + return mb_strlen($binary_string, '8bit'); + } + return strlen($binary_string); + } + + /** + * Get a substring based on byte limits + * + * @see _strlen() + * + * @param string $binary_string The input string + * @param int $start + * @param int $length + * + * @internal + * @return string The substring + */ + function _substr($binary_string, $start, $length) { + if (function_exists('mb_substr')) { + return mb_substr($binary_string, $start, $length, '8bit'); + } + return substr($binary_string, $start, $length); + } + +} @@ -7,13 +7,6 @@ function download_favicon ($website, $dest) { $ok = false; $url = 'http://g.etfv.co/' . $website; - /*if (!is_dir ($favicons_dir)) { - if (!mkdir ($favicons_dir, 0755, true)) { - header('Location: ' . $url); - return false; - } - }*/ - $c = curl_init ($url); curl_setopt ($c, CURLOPT_HEADER, false); curl_setopt ($c, CURLOPT_RETURNTRANSFER, true); @@ -36,13 +29,7 @@ function download_favicon ($website, $dest) { return true; } -if (isset($_SERVER['PATH_INFO'])) { - $id = substr($_SERVER['PATH_INFO'], 1); -} elseif (isset($_SERVER['QUERY_STRING'])) { - $id = $_SERVER['QUERY_STRING']; -} else { - $id = '0'; -} +$id = isset($_SERVER['QUERY_STRING']) ? $_SERVER['QUERY_STRING'] : '0'; if (!ctype_xdigit($id)) { $id = '0'; diff --git a/p/i/index.php b/p/i/index.php index 3dcf659c9..187bbabe8 100755 --- a/p/i/index.php +++ b/p/i/index.php @@ -26,6 +26,7 @@ if (file_exists ('install.php')) { session_cache_limiter(''); Minz_Session::init('FreshRSS'); + Minz_Session::_param('keepAlive', 1); //For Persona if (!file_exists(DATA_PATH . '/no-cache.txt')) { require(LIB_PATH . '/http-conditional.php'); diff --git a/p/i/install.php b/p/i/install.php index 85dfa3a66..7ce4a5ccc 100644 --- a/p/i/install.php +++ b/p/i/install.php @@ -1,5 +1,11 @@ <?php +if (function_exists('opcache_reset')) { + opcache_reset(); +} + require('../../constants.php'); +const BCRYPT_COST = 9; + include(LIB_PATH . '/lib_rss.php'); session_name('FreshRSS'); @@ -149,6 +155,7 @@ function saveStep2 () { if (!empty ($_POST)) { if (empty ($_POST['title']) || empty ($_POST['old_entries']) || + empty ($_POST['auth_type']) || empty ($_POST['default_user'])) { return false; } @@ -161,6 +168,12 @@ function saveStep2 () { } $_SESSION['mail_login'] = filter_var($_POST['mail_login'], FILTER_VALIDATE_EMAIL); $_SESSION['default_user'] = substr(preg_replace('/[^a-zA-Z0-9]/', '', $_POST['default_user']), 0, 16); + $_SESSION['auth_type'] = $_POST['auth_type']; + if (!empty($_POST['passwordPlain'])) { + $passwordHash = password_hash($_POST['passwordPlain'], PASSWORD_BCRYPT, array('cost' => BCRYPT_COST)); + $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js + $_SESSION['passwordHash'] = $passwordHash; + } $token = ''; if ($_SESSION['mail_login']) { @@ -169,8 +182,10 @@ function saveStep2 () { $config_array = array ( 'language' => $_SESSION['language'], + 'theme' => $_SESSION['theme'], 'old_entries' => $_SESSION['old_entries'], 'mail_login' => $_SESSION['mail_login'], + 'passwordHash' => $_SESSION['passwordHash'], 'token' => $token, ); @@ -213,6 +228,8 @@ function saveStep3 () { 'base_url' => '', 'title' => $_SESSION['title'], 'default_user' => $_SESSION['default_user'], + 'auth_type' => $_SESSION['auth_type'], + 'allow_anonymous' => isset($_SESSION['allow_anonymous']) ? $_SESSION['allow_anonymous'] : false, ), 'db' => array( 'type' => $_SESSION['bd_type'], @@ -423,8 +440,9 @@ function checkStep0 () { if (file_exists(DATA_PATH . '/config.php')) { $ini_array = include(DATA_PATH . '/config.php'); - } elseif (file_exists(DATA_PATH . '/application.ini')) { + } elseif (file_exists(DATA_PATH . '/application.ini')) { //v0.6 $ini_array = parse_ini_file(DATA_PATH . '/application.ini', true); + $ini_array['general']['title'] = empty($ini_array['general']['title']) ? '' : stripslashes($ini_array['general']['title']); } else { $ini_array = null; } @@ -432,7 +450,7 @@ function checkStep0 () { if ($ini_array) { $ini_general = isset($ini_array['general']) ? $ini_array['general'] : null; if ($ini_general) { - $keys = array('environment', 'salt', 'title', 'default_user'); + $keys = array('environment', 'salt', 'title', 'default_user', 'allow_anonymous', 'auth_type'); foreach ($keys as $key) { if ((empty($_SESSION[$key])) && isset($ini_general[$key])) { $_SESSION[$key] = $ini_general[$key]; @@ -454,11 +472,20 @@ function checkStep0 () { $userConfig = include(DATA_PATH . '/' . $_SESSION['default_user'] . '_user.php'); } elseif (file_exists(DATA_PATH . '/Configuration.array.php')) { $userConfig = include(DATA_PATH . '/Configuration.array.php'); //v0.6 + if (empty($_SESSION['auth_type'])) { + $_SESSION['auth_type'] = empty($userConfig['mail_login']) ? 'none' : 'persona'; + } + if (!isset($_SESSION['allow_anonymous'])) { + $_SESSION['allow_anonymous'] = empty($userConfig['anon_access']) ? false : ($userConfig['anon_access'] === 'yes'); + } } else { $userConfig = array(); } + if (empty($_SESSION['auth_type'])) { //v0.7b + $_SESSION['auth_type'] = ''; + } - $keys = array('language', 'old_entries', 'mail_login'); + $keys = array('language', 'theme', 'old_entries', 'mail_login', 'passwordHash'); foreach ($keys as $key) { if ((!isset($_SESSION[$key])) && isset($userConfig[$key])) { $_SESSION[$key] = $userConfig[$key]; @@ -469,6 +496,25 @@ function checkStep0 () { $language = isset ($_SESSION['language']) && isset ($languages[$_SESSION['language']]); + if (empty($_SESSION['passwordHash'])) { //v0.7b + $_SESSION['passwordHash'] = ''; + } + if (empty($_SESSION['theme'])) { + $_SESSION['theme'] = 'Origine'; + } else { + switch (strtolower($_SESSION['theme'])) { + case 'default': //v0.7b + $_SESSION['theme'] = 'Origine'; + break; + case 'flat-design': //v0.7b + $_SESSION['theme'] = 'Flat'; + break; + case 'default_dark': //v0.7b + $_SESSION['theme'] = 'Dark'; + break; + } + } + return array ( 'language' => $language ? 'ok' : 'ko', 'all' => $language ? 'ok' : 'ko' @@ -486,6 +532,7 @@ function checkStep1 () { $cache = CACHE_PATH && is_writable (CACHE_PATH); $log = LOG_PATH && is_writable (LOG_PATH); $favicons = is_writable (DATA_PATH . '/favicons'); + $persona = is_writable (DATA_PATH . '/persona'); return array ( 'php' => $php ? 'ok' : 'ko', @@ -499,7 +546,8 @@ function checkStep1 () { 'cache' => $cache ? 'ok' : 'ko', 'log' => $log ? 'ok' : 'ko', 'favicons' => $favicons ? 'ok' : 'ko', - 'all' => $php && $minz && $curl && $pdo && $pcre && $ctype && $dom && $data && $cache && $log && $favicons ? 'ok' : 'ko' + 'persona' => $persona ? 'ok' : 'ko', + 'all' => $php && $minz && $curl && $pdo && $pcre && $ctype && $dom && $data && $cache && $log && $favicons && $persona ? 'ok' : 'ko' ); } @@ -709,6 +757,12 @@ function printStep1 () { <p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('file_is_nok', DATA_PATH . '/favicons'); ?></p> <?php } ?> + <?php if ($res['persona'] == 'ok') { ?> + <p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('persona_is_ok'); ?></p> + <?php } else { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('file_is_nok', DATA_PATH . '/persona'); ?></p> + <?php } ?> + <?php if ($res['all'] == 'ok') { ?> <a class="btn btn-important next-step" href="?step=2"><?php echo _t ('next_step'); ?></a> <?php } else { ?> @@ -748,9 +802,30 @@ function printStep2 () { </div> <div class="form-group"> + <label class="group-name" for="auth_type"><?php echo _t('auth_type'); ?></label> + <div class="group-controls"> + <select id="auth_type" name="auth_type" required="required"> + <option value=""></option> + <option value="form"<?php echo $_SESSION['auth_type'] === 'form' ? ' selected="selected"' : '', version_compare(PHP_VERSION, '5.3', '<') ? ' disabled="disabled"' : ''; ?>><?php echo _t('auth_form'); ?></option> + <option value="persona"<?php echo $_SESSION['auth_type'] === 'persona' ? ' selected="selected"' : ''; ?>><?php echo _t('auth_persona'); ?></option> + <option value="http_auth"<?php echo $_SESSION['auth_type'] === 'http_auth' ? ' selected="selected"' : '', httpAuthUser() == '' ? ' disabled="disabled"' : ''; ?>><?php echo _t('http_auth'); ?> (REMOTE_USER = '<?php echo httpAuthUser(); ?>')</option> + <option value="none"<?php echo $_SESSION['auth_type'] === 'none' ? ' selected="selected"' : ''; ?>><?php echo _t('auth_none'); ?></option> + </select> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="passwordPlain"><?php echo _t('password_form'); ?></label> + <div class="group-controls"> + <input type="password" id="passwordPlain" name="passwordPlain" pattern=".{7,}" autocomplete="off" /> + <noscript><b><?php echo _t('javascript_should_be_activated'); ?></b></noscript> + </div> + </div> + + <div class="form-group"> <label class="group-name" for="mail_login"><?php echo _t ('persona_connection_email'); ?></label> <div class="group-controls"> - <input type="email" id="mail_login" name="mail_login" value="<?php echo isset ($_SESSION['mail_login']) ? $_SESSION['mail_login'] : ''; ?>" placeholder="<?php echo _t ('blank_to_disable'); ?>" /> + <input type="email" id="mail_login" name="mail_login" value="<?php echo isset ($_SESSION['mail_login']) ? $_SESSION['mail_login'] : ''; ?>" placeholder="alice@example.net" /> <noscript><b><?php echo _t ('javascript_should_be_activated'); ?></b></noscript> </div> </div> @@ -910,8 +985,8 @@ case 6: <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1.0"> <title><?php echo _t ('freshrss_installation'); ?></title> - <link rel="stylesheet" type="text/css" media="all" href="../themes/default/global.css" /> - <link rel="stylesheet" type="text/css" media="all" href="../themes/default/freshrss.css" /> + <link rel="stylesheet" type="text/css" media="all" href="../themes/Origine/global.css" /> + <link rel="stylesheet" type="text/css" media="all" href="../themes/Origine/freshrss.css" /> </head> <body> diff --git a/p/index.html b/p/index.html index 5adacab2c..260f437bd 100644 --- a/p/index.html +++ b/p/index.html @@ -22,6 +22,9 @@ h1 a { color: #0062BE; text-decoration: none; } +img { + border: 0; +} </style> </head> diff --git a/p/scripts/bcrypt.min.js b/p/scripts/bcrypt.min.js new file mode 100644 index 000000000..6892caddc --- /dev/null +++ b/p/scripts/bcrypt.min.js @@ -0,0 +1,41 @@ +/* + bcrypt.js (c) 2013 Daniel Wirtz <dcode@dcode.io> + Released under the Apache License, Version 2.0 + see: https://github.com/dcodeIO/bcrypt.js for details +*/ +function p(n){throw n;}var q=null; +(function(n){function u(c,a,b,f){for(var d,e=c[a],l=c[a+1],e=e^b[0],h=0;14>=h;)d=f[e>>24&255],d+=f[256|e>>16&255],d^=f[512|e>>8&255],d+=f[768|e&255],l^=d^b[++h],d=f[l>>24&255],d+=f[256|l>>16&255],d^=f[512|l>>8&255],d+=f[768|l&255],e^=d^b[++h];c[a]=l^b[17];c[a+1]=e;return c}function s(c,a){var b,f=0;for(b=0;4>b;b++)f=f<<8|c[a]&255,a=(a+1)%c.length;return{key:f,a:a}}function y(c,a,b){for(var f=0,d=[0,0],e=a.length,l=b.length,h=0;h<e;h++){var g=s(c,f),f=g.a;a[h]^=g.key}for(h=0;h<e;h+=2)d=u(d,0,a,b), +a[h]=d[0],a[h+1]=d[1];for(h=0;h<l;h+=2)d=u(d,0,a,b),b[h]=d[0],b[h+1]=d[1]}function C(c,a,b,f){for(var d=0,e=[0,0],l=b.length,h=f.length,g,k=0;k<l;k++)g=s(a,d),d=g.a,b[k]^=g.key;for(k=d=0;k<l;k+=2)g=s(c,d),d=g.a,e[0]^=g.key,g=s(c,d),d=g.a,e[1]^=g.key,e=u(e,0,b,f),b[k]=e[0],b[k+1]=e[1];for(k=0;k<h;k+=2)g=s(c,d),d=g.a,e[0]^=g.key,g=s(c,d),d=g.a,e[1]^=g.key,e=u(e,0,b,f),f[k]=e[0],f[k+1]=e[1]}function z(c){"undefined"!==typeof process&&"function"===typeof process.nextTick?process.nextTick(c):setTimeout(c, +0)}function A(c,a,b,f){function d(){if(k<b)for(var n=new Date;k<b&&!(k+=1,y(c,h,g),y(a,h,g),100<Date.now()-n););else{for(k=0;64>k;k++)for(m=0;m<l>>1;m++)u(e,m<<1,h,g);n=[];for(k=0;k<l;k++)n.push((e[k]>>24&255)>>>0),n.push((e[k]>>16&255)>>>0),n.push((e[k]>>8&255)>>>0),n.push((e[k]&255)>>>0);return f?(f(q,n),q):n}f&&z(d);return q}var e=B.slice(),l=e.length;(4>b||31<b)&&p(Error("Illegal number of rounds: "+b));16!=a.length&&p(Error("Illegal salt length: "+a.length+" != 16"));b=1<<b;var h=D.slice(),g= +E.slice();C(a,c,h,g);var k=0,m;if("undefined"!==typeof f)return d(),q;for(var n;;)if((n=d())!==q)return n}function F(c){for(var a,b,f=[],d=0;d<c.length;d++){a=c.charCodeAt(d);b=[];do b.push(a&255),a>>=8;while(a);f=f.concat(b.reverse())}return f}function w(c,a,b){function f(a){var b=[];b.push("$2");"a"<=d&&b.push(d);b.push("$");10>l&&b.push("0");b.push(l.toString());b.push("$");b.push(v.b(h,h.length));b.push(v.b(a,4*B.length-1));return b.join("")}var d,e;("$"!=a.charAt(0)||"2"!=a.charAt(1))&&p(Error("Invalid salt version: "+ +a.substring(0,2)));"$"==a.charAt(2)?(d=String.fromCharCode(0),e=3):(d=a.charAt(2),("a"!=d||"$"!=a.charAt(3))&&p(Error("Invalid salt revision: "+a.substring(2,4))),e=4);"$"<a.charAt(e+2)&&p(Error("Missing salt rounds"));var l=10*parseInt(a.substring(e,e+1),10)+parseInt(a.substring(e+1,e+2),10);a=a.substring(e+3,e+25);c=F(c+("a"<=d?"\x00":""));var h=[],h=v.c(a,16);if("undefined"==typeof b)return f(A(c,h,l));A(c,h,l,function(a,c){a?b(a,q):b(q,f(c))});return q}function G(){if("undefined"!==typeof module&& +module.exports)return require("crypto").randomBytes(16);var c=new Uint32Array(16);n.crypto&&"function"===typeof n.crypto.getRandomValues?n.crypto.getRandomValues(c):"function"===typeof x?x(c):p(Error("Failed to generate random values: Web Crypto API not available / no polyfill set"));return Array.prototype.slice.call(c)}var t="./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""),r=[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, +-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,54,55,56,57,58,59,60,61,62,63,-1,-1,-1,-1,-1,-1,-1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,-1,-1,-1,-1,-1,-1,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,-1,-1,-1,-1,-1],v={b:function(c,a){var b=0,f=[],d,e;for((0>=a||a>c.length)&&p(Error("Invalid 'len': "+a));b<a;){d=c[b++]&255;f.push(t[d>>2&63]);d=(d&3)<<4;if(b>=a){f.push(t[d&63]);break}e=c[b++]&255;d|=e>>4&15;f.push(t[d&63]);d=(e&15)<< +2;if(b>=a){f.push(t[d&63]);break}e=c[b++]&255;d|=e>>6&3;f.push(t[d&63]);f.push(t[e&63])}return f.join("")},c:function(c,a){var b=0,f=c.length,d=0,e=[],l,h,g;for(0>=a&&p(Error("Illegal 'len': "+a));b<f-1&&d<a;){g=c.charCodeAt(b++);l=g<r.length?r[g]:-1;g=c.charCodeAt(b++);h=g<r.length?r[g]:-1;if(-1==l||-1==h)break;g=l<<2>>>0;g|=(h&48)>>4;e.push(String.fromCharCode(g));if(++d>=a||b>=f)break;g=c.charCodeAt(b++);l=g<r.length?r[g]:-1;if(-1==l)break;g=(h&15)<<4>>>0;g|=(l&60)>>2;e.push(String.fromCharCode(g)); +if(++d>=a||b>=f)break;g=c.charCodeAt(b++);h=g<r.length?r[g]:-1;g=(l&3)<<6>>>0;g|=h;e.push(String.fromCharCode(g));++d}f=[];for(b=0;b<d;b++)f.push(e[b].charCodeAt(0));return f}},m={},D=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,3041331479,2450970073,2306472731],E=[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374, +1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131, +1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968, +3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899, +3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005,3532540348,1274779536,3789419226,2764799539,1660621633,3471099624,4011903706,913787905,3497959166,737222580,2514213453,2928710040,3937242737,1804850592,3499020752,2949064160,2386320175,2390070455,2415321851,4061277028,2290661394,2416832540,1336762016,1754252060,3520065937,3014181293,791618072,3188594551,3933548030,2332172193, +3852520463,3043980520,413987798,3465142937,3030929376,4245938359,2093235073,3534596313,375366246,2157278981,2479649556,555357303,3870105701,2008414854,3344188149,4221384143,3956125452,2067696032,3594591187,2921233993,2428461,544322398,577241275,1471733935,610547355,4027169054,1432588573,1507829418,2025931657,3646575487,545086370,48609733,2200306550,1653985193,298326376,1316178497,3007786442,2064951626,458293330,2589141269,3591329599,3164325604,727753846,2179363840,146436021,1461446943,4069977195, +705550613,3059967265,3887724982,4281599278,3313849956,1404054877,2845806497,146425753,1854211946,1266315497,3048417604,3681880366,3289982499,290971E4,1235738493,2632868024,2414719590,3970600049,1771706367,1449415276,3266420449,422970021,1963543593,2690192192,3826793022,1062508698,1531092325,1804592342,2583117782,2714934279,4024971509,1294809318,4028980673,1289560198,2221992742,1669523910,35572830,157838143,1052438473,1016535060,1802137761,1753167236,1386275462,3080475397,2857371447,1040679964,2145300060, +2390574316,1461121720,2956646967,4031777805,4028374788,33600511,2920084762,1018524850,629373528,3691585981,3515945977,2091462646,2486323059,586499841,988145025,935516892,3367335476,2599673255,2839830854,265290510,3972581182,2759138881,3795373465,1005194799,847297441,406762289,1314163512,1332590856,1866599683,4127851711,750260880,613907577,1450815602,3165620655,3734664991,3650291728,3012275730,3704569646,1427272223,778793252,1343938022,2676280711,2052605720,1946737175,3164576444,3914038668,3967478842, +3682934266,1661551462,3294938066,4011595847,840292616,3712170807,616741398,312560963,711312465,1351876610,322626781,1910503582,271666773,2175563734,1594956187,70604529,3617834859,1007753275,1495573769,4069517037,2549218298,2663038764,504708206,2263041392,3941167025,2249088522,1514023603,1998579484,1312622330,694541497,2582060303,2151582166,1382467621,776784248,2618340202,3323268794,2497899128,2784771155,503983604,4076293799,907881277,423175695,432175456,1378068232,4145222326,3954048622,3938656102, +3820766613,2793130115,2977904593,26017576,3274890735,3194772133,1700274565,1756076034,4006520079,3677328699,720338349,1533947780,354530856,688349552,3973924725,1637815568,332179504,3949051286,53804574,2852348879,3044236432,1282449977,3583942155,3416972820,4006381244,1617046695,2628476075,3002303598,1686838959,431878346,2686675385,1700445008,1080580658,1009431731,832498133,3223435511,2605976345,2271191193,2516031870,1648197032,4164389018,2548247927,300782431,375919233,238389289,3353747414,2531188641, +2019080857,1475708069,455242339,2609103871,448939670,3451063019,1395535956,2413381860,1841049896,1491858159,885456874,4264095073,4001119347,1565136089,3898914787,1108368660,540939232,1173283510,2745871338,3681308437,4207628240,3343053890,4016749493,1699691293,1103962373,3625875870,2256883143,3830138730,1031889488,3479347698,1535977030,4236805024,3251091107,2132092099,1774941330,1199868427,1452454533,157007616,2904115357,342012276,595725824,1480756522,206960106,497939518,591360097,863170706,2375253569, +3596610801,1814182875,2094937945,3421402208,1082520231,3463918190,2785509508,435703966,3908032597,1641649973,2842273706,3305899714,1510255612,2148256476,2655287854,3276092548,4258621189,236887753,3681803219,274041037,1734335097,3815195456,3317970021,1899903192,1026095262,4050517792,356393447,2410691914,3873677099,3682840055,3913112168,2491498743,4132185628,2489919796,1091903735,1979897079,3170134830,3567386728,3557303409,857797738,1136121015,1342202287,507115054,2535736646,337727348,3213592640,1301675037, +2528481711,1895095763,1721773893,3216771564,62756741,2142006736,835421444,2531993523,1442658625,3659876326,2882144922,676362277,1392781812,170690266,3921047035,1759253602,3611846912,1745797284,664899054,1329594018,3901205900,3045908486,2062866102,2865634940,3543621612,3464012697,1080764994,553557557,3656615353,3996768171,991055499,499776247,1265440854,648242737,3940784050,980351604,3713745714,1749149687,3396870395,4211799374,3640570775,1161844396,3125318951,1431517754,545492359,4268468663,3499529547, +1437099964,2702547544,3433638243,2581715763,2787789398,1060185593,1593081372,2418618748,4260947970,69676912,2159744348,86519011,2512459080,3838209314,1220612927,3339683548,133810670,1090789135,1078426020,1569222167,845107691,3583754449,4072456591,1091646820,628848692,1613405280,3757631651,526609435,236106946,48312990,2942717905,3402727701,1797494240,859738849,992217954,4005476642,2243076622,3870952857,3732016268,765654824,3490871365,2511836413,1685915746,3888969200,1414112111,2273134842,3281911079, +4080962846,172450625,2569994100,980381355,4109958455,2819808352,2716589560,2568741196,3681446669,3329971472,1835478071,660984891,3704678404,4045999559,3422617507,3040415634,1762651403,1719377915,3470491036,2693910283,3642056355,3138596744,1364962596,2073328063,1983633131,926494387,3423689081,2150032023,4096667949,1749200295,3328846651,309677260,2016342300,1779581495,3079819751,111262694,1274766160,443224088,298511866,1025883608,3806446537,1145181785,168956806,3641502830,3584813610,1689216846,3666258015, +3200248200,1692713982,2646376535,4042768518,1618508792,1610833997,3523052358,4130873264,2001055236,3610705100,2202168115,4028541809,2961195399,1006657119,2006996926,3186142756,1430667929,3210227297,1314452623,4074634658,4101304120,2273951170,1399257539,3367210612,3027628629,1190975929,2062231137,2333990788,2221543033,2438960610,1181637006,548689776,2362791313,3372408396,3104550113,3145860560,296247880,1970579870,3078560182,3769228297,1714227617,3291629107,3898220290,166772364,1251581989,493813264, +448347421,195405023,2709975567,677966185,3703036547,1463355134,2715995803,1338867538,1343315457,2802222074,2684532164,233230375,2599980071,2000651841,3277868038,1638401717,4028070440,3237316320,6314154,819756386,300326615,590932579,1405279636,3267499572,3150704214,2428286686,3959192993,3461946742,1862657033,1266418056,963775037,2089974820,2263052895,1917689273,448879540,3550394620,3981727096,150775221,3627908307,1303187396,508620638,2975983352,2726630617,1817252668,1876281319,1457606340,908771278, +3720792119,3617206836,2455994898,1729034894,1080033504,976866871,3556439503,2881648439,1522871579,1555064734,1336096578,3548522304,2579274686,3574697629,3205460757,3593280638,3338716283,3079412587,564236357,2993598910,1781952180,1464380207,3163844217,3332601554,1699332808,1393555694,1183702653,3581086237,1288719814,691649499,2847557200,2895455976,3193889540,2717570544,1781354906,1676643554,2592534050,3230253752,1126444790,2770207658,2633158820,2210423226,2615765581,2414155088,3127139286,673620729, +2805611233,1269405062,4015350505,3341807571,4149409754,1057255273,2012875353,2162469141,2276492801,2601117357,993977747,3918593370,2654263191,753973209,36408145,2530585658,25011837,3520020182,2088578344,530523599,2918365339,1524020338,1518925132,3760827505,3759777254,1202760957,3985898139,3906192525,674977740,4174734889,2031300136,2019492241,3983892565,4153806404,3822280332,352677332,2297720250,60907813,90501309,3286998549,1016092578,2535922412,2839152426,457141659,509813237,4120667899,652014361, +1966332200,2975202805,55981186,2327461051,676427537,3255491064,2882294119,3433927263,1307055953,942726286,933058658,2468411793,3933900994,4215176142,1361170020,2001714738,2830558078,3274259782,1222529897,1679025792,2729314320,3714953764,1770335741,151462246,3013232138,1682292957,1483529935,471910574,1539241949,458788160,3436315007,1807016891,3718408830,978976581,1043663428,3165965781,1927990952,4200891579,2372276910,3208408903,3533431907,1412390302,2931980059,4132332400,1947078029,3881505623,4168226417, +2941484381,1077988104,1320477388,886195818,18198404,3786409E3,2509781533,112762804,3463356488,1866414978,891333506,18488651,661792760,1628790961,3885187036,3141171499,876946877,2693282273,1372485963,791857591,2686433993,3759982718,3167212022,3472953795,2716379847,445679433,3561995674,3504004811,3574258232,54117162,3331405415,2381918588,3769707343,4154350007,1140177722,4074052095,668550556,3214352940,367459370,261225585,2610173221,4209349473,3468074219,3265815641,314222801,3066103646,3808782860,282218597, +3406013506,3773591054,379116347,1285071038,846784868,2669647154,3771962079,3550491691,2305946142,453669953,1268987020,3317592352,3279303384,3744833421,2610507566,3859509063,266596637,3847019092,517658769,3462560207,3443424879,370717030,4247526661,2224018117,4143653529,4112773975,2788324899,2477274417,1456262402,2901442914,1517677493,1846949527,2295493580,3734397586,2176403920,1280348187,1908823572,3871786941,846861322,1172426758,3287448474,3383383037,1655181056,3139813346,901632758,1897031941,2986607138, +3066810236,3447102507,1393639104,373351379,950779232,625454576,3124240540,4148612726,2007998917,544563296,2244738638,2330496472,2058025392,1291430526,424198748,50039436,29584100,3605783033,2429876329,2791104160,1057563949,3255363231,3075367218,3463963227,1469046755,985887462],B=[1332899944,1700884034,1701343084,1684370003,1668446532,1869963892],x=q;m.setRandomPolyfill=function(c){x=c};m.genSaltSync=function(c){c||(c=10);var a;c=c||10;(4>c||31<c)&&p(Error("Illegal number of rounds: "+c));var b=[]; +b.push("$2a$");10>c&&b.push("0");b.push(c.toString());b.push("$");try{b.push(v.b(G(),16)),a=b.join("")}catch(f){p(f)}return a};m.genSalt=function(c,a,b){"function"==typeof a&&(b=a,a=-1);var f;"function"==typeof c?(b=c,f=10):f=parseInt(c,10);"function"!=typeof b&&p(Error("Illegal or missing 'callback': "+b));z(function(){try{var a=m.genSaltSync(f);b(q,a)}catch(c){b(c,q)}})};m.hashSync=function(c,a){a||(a=10);"number"==typeof a&&(a=m.genSaltSync(a));return w(c,a)};m.hash=function(c,a,b){"function"!= +typeof b&&p(Error("Illegal 'callback': "+b));"number"==typeof a?m.genSalt(a,function(a,d){w(c,d,b)}):w(c,a,b)};m.compareSync=function(c,a){("string"!=typeof c||"string"!=typeof a)&&p(Error("Illegal argument types: "+typeof c+", "+typeof a));60!=a.length&&p(Error("Illegal hash length: "+a.length+" != 60"));for(var b=m.hashSync(c,a.substr(0,a.length-31)),f=b.length==a.length,d=b.length<a.length?b.length:a.length,e=0;e<d;++e)b.length>=e&&(a.length>=e&&b[e]!=a[e])&&(f=!1);return f};m.compare=function(c, +a,b){"function"!=typeof b&&p(Error("Illegal 'callback': "+b));m.hash(c,a.substr(0,29),function(c,d){b(c,a===d)})};m.getRounds=function(c){"string"!=typeof c&&p(Error("Illegal type of 'hash': "+typeof c));return parseInt(c.split("$")[2],10)};m.getSalt=function(c){"string"!=typeof c&&p(Error("Illegal type of 'hash': "+typeof c));60!=c.length&&p(Error("Illegal hash length: "+c.length+" != 60"));return c.substring(0,29)};"undefined"!=typeof module&&module.exports?module.exports=m:"undefined"!=typeof define&& +define.amd?define("bcrypt",function(){return m}):(n.dcodeIO||(n.dcodeIO={}),n.dcodeIO.bcrypt=m)})(this); diff --git a/p/scripts/global_view.js b/p/scripts/global_view.js index 0cdcdd3fa..7105520a6 100644 --- a/p/scripts/global_view.js +++ b/p/scripts/global_view.js @@ -24,6 +24,16 @@ function load_panel(link) { // en en ouvrant une autre ensuite, on se retrouve au même point de scroll $("#panel").scrollTop(0); + $('#panel').on('click', '#nav_menu_read_all > a, #nav_menu_read_all .item > a, #bigMarkAsRead', function () { + $.ajax({ + url: $(this).attr("href"), + async: false + }); + //$("#panel .close").first().click(); + window.location.reload(false); + return false; + }); + panel_loading = false; }); } @@ -50,11 +60,14 @@ function init_global_view() { $(".nav_menu #nav_menu_read_all, .nav_menu .toggle_aside").remove(); - init_stream_delegates($("#panel")); + init_stream($("#panel")); } function init_all_global_view() { - if (!(window.$ && window.init_stream_delegates)) { + if (!(window.$ && window.init_stream)) { + if (window.console) { + console.log('FreshRSS Global view waiting for JS…'); + } window.setTimeout(init_all_global_view, 50); //Wait for all js to be loaded return; } diff --git a/p/scripts/main.js b/p/scripts/main.js index 1a41dac9c..3f6352baf 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -25,8 +25,52 @@ function incLabel(p, inc) { return i > 0 ? ' (' + i + ')' : ''; } +function incUnreadsFeed(article, feed_id, nb) { + //Update unread: feed + var elem = $('#' + feed_id + '>.feed').get(0), + feed_unreads = elem ? (parseInt(elem.getAttribute('data-unread'), 10) || 0) : 0, + feed_priority = elem ? (parseInt(elem.getAttribute('data-priority'), 10) || 0) : 0; + if (elem) { + elem.setAttribute('data-unread', Math.max(0, feed_unreads + nb)); + } + + //Update unread: category + elem = $('#' + feed_id).parent().prevAll('.category').children(':first').get(0); + feed_unreads = elem ? (parseInt(elem.getAttribute('data-unread'), 10) || 0) : 0; + if (elem) { + elem.setAttribute('data-unread', Math.max(0, feed_unreads + nb)); + } + + //Update unread: all + if (feed_priority > 0) { + elem = $('#aside_flux .all').children(':first').get(0); + if (elem) { + feed_unreads = elem ? (parseInt(elem.getAttribute('data-unread'), 10) || 0) : 0; + elem.setAttribute('data-unread', Math.max(0, feed_unreads + nb)); + } + } + + //Update unread: favourites + if (article && article.closest('div').hasClass('favorite')) { + elem = $('#aside_flux .favorites').children(':first').get(0); + if (elem) { + feed_unreads = elem ? (parseInt(elem.getAttribute('data-unread'), 10) || 0) : 0; + elem.setAttribute('data-unread', Math.max(0, feed_unreads + nb)); + } + } + + //Update unread: title + document.title = document.title.replace(/((?: \(\d+\))?)( · .*?)((?: \(\d+\))?)$/, function (m, p1, p2, p3) { + if (article || ($('#' + feed_id).closest('.active').length > 0)) { + return incLabel(p1, nb) + p2 + incLabel(p3, feed_priority > 0 ? nb : 0); + } else { + return p1 + p2 + incLabel(p3, feed_priority > 0 ? nb : 0); + } + }); +} + function mark_read(active, only_not_read) { - if (active[0] === undefined || (only_not_read === true && !active.hasClass("not_read"))) { + if (active.length === 0 || (only_not_read === true && !active.hasClass("not_read"))) { return false; } @@ -51,50 +95,14 @@ function mark_read(active, only_not_read) { } $r.find('.icon').replaceWith(data.icon); - //Update unread: feed var feed_url = active.find(".website>a").attr("href"), - feed_id = feed_url.substr(feed_url.lastIndexOf('f_')), - elem = $('#' + feed_id + ' .feed').get(0), - feed_unread = elem ? (parseInt(elem.getAttribute('data-unread'), 10) || 0) : 0, - feed_priority = elem ? (parseInt(elem.getAttribute('data-priority'), 10) || 0) : 0; - if (elem) { - elem.setAttribute('data-unread', Math.max(0, feed_unread + inc)); - } - - //Update unread: category - elem = $('#' + feed_id).parent().prevAll('.category').children(':first').get(0); - feed_unread = elem ? (parseInt(elem.getAttribute('data-unread'), 10) || 0) : 0; - if (elem) { - elem.setAttribute('data-unread', Math.max(0, feed_unread + inc)); - } - - //Update unread: all - if (feed_priority > 0) { - elem = $('#aside_flux .all').children(':first').get(0); - if (elem) { - feed_unread = elem ? (parseInt(elem.getAttribute('data-unread'), 10) || 0) : 0; - elem.setAttribute('data-unread', Math.max(0, feed_unread + inc)); - } - } - - //Update unread: favourites - if (active.closest('div').hasClass('favorite')) { - elem = $('#aside_flux .favorites').children(':first').get(0); - if (elem) { - feed_unread = elem ? (parseInt(elem.getAttribute('data-unread'), 10) || 0) : 0; - elem.setAttribute('data-unread', Math.max(0, feed_unread + inc)); - } - } - - //Update unread: title - document.title = document.title.replace(/((?: \(\d+\))?)( - .*?)((?: \(\d+\))?)$/, function (m, p1, p2, p3) { - return incLabel(p1, inc) + p2 + incLabel(p3, feed_priority > 0 ? inc : 0); - }); + feed_id = feed_url.substr(feed_url.lastIndexOf('f_')); + incUnreadsFeed(active, feed_id, inc); }); } function mark_favorite(active) { - if (active[0] === undefined) { + if (active.length === 0) { return false; } @@ -128,13 +136,21 @@ function mark_favorite(active) { if (active.closest('div').hasClass('not_read')) { var elem = $('#aside_flux .favorites').children(':first').get(0), - feed_unread = elem ? (parseInt(elem.getAttribute('data-unread'), 10) || 0) : 0; - elem.setAttribute('data-unread', Math.max(0, feed_unread + inc)); + feed_unreads = elem ? (parseInt(elem.getAttribute('data-unread'), 10) || 0) : 0; + if (elem) { + elem.setAttribute('data-unread', Math.max(0, feed_unreads + inc)); + } } }); } function toggleContent(new_active, old_active) { + old_active.removeClass("active").removeClass("current"); + + if (new_active.length === 0) { + return; + } + if (does_lazyload) { new_active.find('img[data-original], iframe[data-original]').each(function () { this.setAttribute('src', this.getAttribute('data-original')); @@ -142,7 +158,6 @@ function toggleContent(new_active, old_active) { }); } - old_active.removeClass("active").removeClass("current"); if (old_active[0] !== new_active[0]) { if (isCollapsed) { new_active.addClass("active"); @@ -188,30 +203,20 @@ function toggleContent(new_active, old_active) { function prev_entry() { var old_active = $(".flux.current"), - last_active = $(".flux:last"), - new_active = old_active.prevAll(".flux:first"); - - if (new_active.hasClass("flux")) { - toggleContent(new_active, old_active); - } else if (old_active[0] === undefined && new_active[0] === undefined) { - toggleContent(last_active, old_active); - } + new_active = old_active.length === 0 ? $(".flux:last") : old_active.prevAll(".flux:first"); + toggleContent(new_active, old_active); } function next_entry() { var old_active = $(".flux.current"), - first_active = $(".flux:first"), - last_active = $(".flux:last"), - new_active = old_active.nextAll(".flux:first"); + new_active = old_active.length === 0 ? $(".flux:first") : old_active.nextAll(".flux:first"); + toggleContent(new_active, old_active); - if (new_active.hasClass("flux")) { - toggleContent(new_active, old_active); - } else if (old_active[0] === undefined && new_active[0] === undefined) { - toggleContent(first_active, old_active); - } - - if ((!auto_load_more) && (last_active.attr("id") === new_active.attr("id"))) { - load_more_posts(); + if (!auto_load_more) { + var last_active = $(".flux:last"); + if (last_active.attr("id") === new_active.attr("id")) { + load_more_posts(); + } } } @@ -308,6 +313,14 @@ function init_column_categories() { $(this).parent().next(".feeds").slideToggle(); return false; }); + $('#aside_flux').on('click', '.feeds .dropdown-toggle', function () { + if ($(this).nextAll('.dropdown-menu').length === 0) { + var feed_id = $(this).closest('li').attr('id').substr(2), + feed_web = $(this).data('fweb'), + template = $('#feed_config_template').html().replace(/!!!!!!/g, feed_id).replace('http://example.net/', feed_web); + $(this).attr('href', '#dropdown-' + feed_id).prev('.dropdown-target').attr('id', 'dropdown-' + feed_id).parent().append(template); + } + }); } function init_shortcuts() { @@ -399,8 +412,11 @@ function init_shortcuts() { }); } -function init_stream_delegates(divStream) { +function init_stream(divStream) { divStream.on('click', '.flux_header', function (e) { //flux_header_toggle + if ($(e.target).closest('.item.website > a').length > 0) { + return; + } var old_active = $(".flux.current"), new_active = $(this).parent(); isCollapsed = true; @@ -416,21 +432,15 @@ function init_stream_delegates(divStream) { divStream.on('click', '.flux a.read', function () { var active = $(this).parents(".flux"); mark_read(active, false); - return false; }); divStream.on('click', '.flux a.bookmark', function () { var active = $(this).parents(".flux"); mark_favorite(active); - return false; }); - divStream.on('click', '.flux .content a', function () { - $(this).attr('target', '_blank'); - }); - divStream.on('click', '.item.title>a', function (e) { if (e.ctrlKey) { return true; //Allow default control-click behaviour such as open in backround-tab @@ -439,6 +449,10 @@ function init_stream_delegates(divStream) { return false; }); + divStream.on('click', '.flux .content a', function () { + $(this).attr('target', '_blank'); + }); + if (auto_mark_site) { divStream.on('click', '.flux .link a', function () { mark_read($(this).parent().parent().parent(), true); @@ -470,17 +484,6 @@ function init_nav_entries() { }); } -function init_templates() { - $('#aside_flux').on('click', '.feeds .dropdown-toggle', function () { - if ($(this).nextAll('.dropdown-menu').length === 0) { - var feed_id = $(this).closest('li').attr('id').substr(2), - feed_web = $(this).data('fweb'), - template = $('#feed_config_template').html().replace(/!!!!!!/g, feed_id).replace('http://example.net/', feed_web); - $(this).attr('href', '#dropdown-' + feed_id).prev('.dropdown-target').attr('id', 'dropdown-' + feed_id).parent().append(template); - } - }); -} - function init_actualize() { $("#actualize").click(function () { $.getScript('./?c=javascript&a=actualize').done(function () { @@ -504,7 +507,7 @@ function closeNotification() { function init_notifications() { var notif = $(".notification"); - if (notif[0] !== undefined) { + if (notif.length > 0) { window.setInterval(closeNotification, 4000); notif.find("a.close").click(function () { @@ -514,6 +517,17 @@ function init_notifications() { } } +function refreshUnreads() { + $.getJSON('./?c=javascript&a=nbUnreadsPerFeed').done(function (data) { + $.each(data, function(feed_id, nbUnreads) { + feed_id = 'f_' + feed_id; + var elem = $('#' + feed_id + '>.feed').get(0), + feed_unreads = elem ? (parseInt(elem.getAttribute('data-unread'), 10) || 0) : 0; + incUnreadsFeed(null, feed_id, nbUnreads - feed_unreads); + }); + }); +} + //<endless_mode> var url_load_more = "", load_more = false, @@ -547,6 +561,8 @@ function load_more_posts() { } function init_load_more(box) { + box_load_more = box; + var $next_link = $("#load_more"); if (!$next_link.length) { // no more article to load @@ -554,8 +570,6 @@ function init_load_more(box) { return; } - box_load_more = box; - url_load_more = $next_link.attr("href"); var $prefetch = $('#prefetch'); if ($prefetch.attr('href') !== url_load_more) { @@ -571,6 +585,58 @@ function init_load_more(box) { } //</endless_mode> +//<Web login form> +function poormanSalt() { //If crypto.getRandomValues is not available + var text = '$2a$04$', + base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789/abcdefghijklmnopqrstuvwxyz'; + for (var i = 22; i > 0; i--) { + text += base.charAt(Math.floor(Math.random() * 64)); + } + return text; +} + +function init_loginForm() { + var $loginForm = $('#loginForm'); + if ($loginForm.length === 0) { + return; + } + if (!(window.dcodeIO)) { + if (window.console) { + console.log('FreshRSS waiting for bcrypt.js…'); + } + window.setTimeout(init_loginForm, 100); + return; + } + $loginForm.on('submit', function() { + $('#loginButton').attr('disabled', ''); + var success = false; + $.ajax({ + url: './?c=javascript&a=nonce&user=' + $('#username').val(), + dataType: 'json', + async: false + }).done(function (data) { + if (data.salt1 == '' || data.nonce == '') { + alert('Invalid user!'); + } else { + var strong = window.Uint32Array && window.crypto && (typeof window.crypto.getRandomValues === 'function'), + s = dcodeIO.bcrypt.hashSync($('#passwordPlain').val(), data.salt1), + c = dcodeIO.bcrypt.hashSync(data.nonce + s, strong ? 4 : poormanSalt()); + $('#challenge').val(c); + if (s == '' || c == '') { + alert('Crypto error!'); + } else { + success = true; + } + } + }).fail(function() { + alert('Communication error!'); + }); + $('#loginButton').removeAttr('disabled'); + return success; + }); +} +//</Web login form> + //<persona> function init_persona() { if (!(navigator.id)) { @@ -668,23 +734,29 @@ function init_all() { window.setTimeout(init_all, 50); return; } - $stream = $('#stream'); - init_posts(); - init_column_categories(); - if (load_shortcuts) { - init_shortcuts(); - } - init_stream_delegates($stream); - init_nav_entries(); - init_templates(); init_notifications(); - init_actualize(); - init_load_more($stream); - if (use_persona) { - init_persona(); + switch (authType) { + case 'form': + init_loginForm(); + break; + case 'persona': + init_persona(); + break; } init_confirm_action(); - init_print_action(); + $stream = $('#stream'); + if ($stream.length > 0) { + init_actualize(); + init_column_categories(); + init_load_more($stream); + init_posts(); + init_stream($stream); + init_nav_entries(); + init_shortcuts(); + init_print_action(); + window.setInterval(refreshUnreads, 120000); + } + if (window.console) { console.log('FreshRSS init done.'); } diff --git a/p/themes/Dark/freshrss.css b/p/themes/Dark/freshrss.css new file mode 100644 index 000000000..e9eb2c705 --- /dev/null +++ b/p/themes/Dark/freshrss.css @@ -0,0 +1,862 @@ +@charset "UTF-8"; + +/* STRUCTURE */ +.header { + display: table; + width: 100%; + background: #1c1c1c; + table-layout: fixed; +} + .header > .item { + display: table-cell; + padding: 10px 0; + border-bottom: 1px solid #2f2f2f; + vertical-align: middle; + text-align: center; + } + .header > .item.title { + width: 250px; + white-space: nowrap; + } + .logo { + display: inline-block; + font-size: 48px; + height: 32px; + width: 32px; + padding: 10px; + } + .header > .item.title h1 { + display: inline-block; + margin: 0; + } + .header > .item.search input { + width: 230px; + transition: width 200ms linear; + } + .header .item.search input:focus { + width: 330px; + } + .header > .item.configure { + width: 100px; + } + +.item a:hover { + text-decoration: none; +} + +#global { + display: table; + width: 100%; + height: 100%; + background: #1c1c1c; + table-layout: fixed; +} + .aside { + display: table-cell; + height: 100%; + width: 250px; + vertical-align: top; + border-right: 1px solid #2f2f2f; + background: #1c1c1c; + } + .aside .nav-form input { + width: 180px; + } + .aside.aside_flux { + padding: 10px 0 40px; + } + .aside.aside_feed .nav-form input { + width: 140px; + } + .aside.aside_feed .nav-form .dropdown .dropdown-menu { + right: -20px; + } + .aside.aside_feed .nav-form .dropdown .dropdown-menu:after { + right: 33px; + } + + .nav-login { + display: none; + } + + .nav_menu { + width: 100%; + background: #1c1c1c; + border-bottom: 1px solid #2f2f2f; + text-align: center; + padding: 5px 0; + } + .nav_menu .search { + display:none; + } + +.favicon { + height: 16px; + width: 16px; +} + +.categories { + margin: 0; + padding: 0; + text-align: center; + list-style: none; +} + .category { + display: block; + width: 220px; + margin: 10px auto; + text-align: left; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .category .btn:first-child { + width: 195px; + position: relative; + } + .category.stick .btn:first-child { + width:160px; + } + .category .btn:first-child:not([data-unread="0"]):after { + content: attr(data-unread); + position: absolute; + top: 3px; right: 3px; + padding: 1px 5px; + background: #1a1a1a; + color: #888; + font-size: 90%; + border: 1px solid #000; + border-radius: 5px; + } + .category + .feeds:not(.active) { + display:none; + } + .categories .feeds { + width: 100%; + margin: 0; + list-style: none; + } + .categories .feeds .item.active { + background: #26303F; + } + .categories .feeds .item.active .feed { + color: #888; + } + .categories .feeds .item.empty .feed { + color: #e67e22; + } + .categories .feeds .item.empty.active { + background: #e67e22; + } + .categories .feeds .item.empty.active .feed { + color: #fff; + } + .categories .feeds .item.error .feed { + color: #BD362F; + } + .categories .feeds .item .feed { + display: inline-block; + margin: 0; + width: 165px; + line-height: 35px; + font-size: 90%; + vertical-align: middle; + text-align: left; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .feed:not([data-unread="0"]):before { + content: "(" attr(data-unread) ") "; + } + .categories .feeds .dropdown-menu { + left: 0; + } + .categories .feeds .dropdown-menu:after { + left: 2px; + } + .categories .feeds .item .dropdown-toggle > .icon { + visibility: hidden; + cursor: pointer; + } + .categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, + .categories .feeds .item:hover .dropdown-toggle > .icon, + .categories .feeds .item.active .dropdown-toggle > .icon { + background-color: #1c1c1c; + border-radius: 3px; + visibility: visible; + } + +.post { + padding: 10px 50px; +} + .post form { + margin: 10px 0; + } + +.day { + min-height: 50px; + padding: 0 10px; + font-size: 130%; + font-weight: bold; + line-height: 50px; + background: #1c1c1c; + border-top: 1px solid #888; + border-bottom: 1px solid #888; +} + .day:first-child { + border-top: none; + } + .day .name { + position: absolute; + right: 0; + width: 50%; + height: 1.5em; + padding: 0 10px 0 0; + overflow: hidden; + color: #aab; + font-size: 1.8em; + opacity: .3; + text-shadow: 0px -1px 0px #333; + font-style: italic; + white-space: nowrap; + text-overflow: ellipsis; + text-align: right; + } + +.flux { + border-left: 3px solid #aaa; + background: #1c1c1c; +} + .flux.not_read { + border-left: 3px solid #FF5300; + background: #1c1c1c; + } + .flux.favorite { + border-left: 3px solid #FFC300; + background: #1c1c1c; + } + .flux.current { + border-left: 3px solid #0062BE; + background: #1a1a1a; + } + + .flux_header { + background: inherit; + height: 25px; + font-size: 12px; + border-top: 1px solid #2f2f2f; + cursor: pointer; + } + .flux .item { + line-height: 40px; + white-space: nowrap; + } + .flux_header > .item { + overflow: hidden; + text-overflow: ellipsis; + } + .flux .item.manage { + width: 40px; + text-align: center; + } + .flux .item.website { + width: 200px; + } + .website .favicon { + padding: 5px; + } + .flux .item.title { + background: inherit; + } + .flux:hover .item.title { + border-right: 2px solid rgba(127, 127, 127, 0.1); + padding-right: 1em; + position: absolute; + } + .flux .item.title a { + color: #888; + outline: none; + } + .flux.not_read .item.title, + .flux.current .item.title { + font-weight: bold; + } + .flux .item.date { + width: 200px; + padding:0 5px 0 0; + text-align: right; + font-size: 10px; + color: #666; + } + .link { + width: 40px; + text-align: center; + } + +#stream.reader .flux { + padding: 0 0 30px; + border: none; + background: #1c1c1c; + color: #888; +} + #stream.reader .flux .author { + margin: 0 0 10px; + font-size: 90%; + color: #666; + } + +#stream.global { + text-align: center; +} + #stream.global .box-category { + display: inline-block; + width: 280px; + margin: 20px 10px; + vertical-align: top; + background: #1a1a1a; + border: 1px solid #000; + border-radius: 5px; + text-align: left; + box-shadow: 0 0 5px #2f2f2f; + } + #stream.global .category { + width: 100%; + margin: 0; + } + #stream.global .btn { + display: block; + width: auto; + height: 35px; + margin: 0; + padding: 0 10px; + background: #26303F; + border: none; + border-bottom: 1px solid #2f2f2f; + border-radius: 5px 5px 0 0; + line-height: 35px; + font-size: 120%; + } + #stream.global .btn:not([data-unread="0"]) { + font-weight:bold; + } + #stream.global .btn:first-child:not([data-unread="0"]):after { + top: 0; right: 5px; + border: 0; + background: none; + color: #666; + font-weight: bold; + box-shadow: none; + } + #stream.global .box-category .feeds { + display: block; + max-height: 250px; + margin: 0; + list-style: none; + overflow: auto; + } + #stream.global .box-category .feeds .item { + padding: 2px 10px; + font-size: 90%; + } + #stream.global .box-category .feed { + width: 220px; + } + +.content { + min-height: 150px; + max-width: 550px; + margin: 0 auto; + padding: 20px 10px; + line-height: 170%; + word-wrap: break-word; +} + .content h1, .content h2, .content h3 { + margin: 20px 0 5px; + } + .content > .title { + font-size: x-large; + margin: 0; + } + + .content p { + margin: 0 0 20px; + } + img.big { + display: block; + margin: 10px auto; + } + figure img.big { + margin: 0; + } + .content hr { + margin: 30px 0; + height: 1px; + background: #ddd; + border: 0; + } + .content pre { + margin: 10px auto; + padding: 10px; + overflow: auto; + background: #000; + color: #fff; + font-size: 110%; + } + .content q, .content blockquote { + display: block; + margin: 5px 0; + padding: 5px 20px; + font-style: italic; + border-left: 4px solid #ccc; + color: #666; + } + .content blockquote p { + margin: 0; + } + +#panel { + display: none; + position: fixed; + top: 10px; bottom: 10px; + left: 100px; right: 100px; + overflow: auto; + background: #1c1c1c; + border: 1px solid #95a5a6; + border-radius: 5px; +} + #panel .close { + position: fixed; + top: 10px; right: 0; + display: inline-block; + width: 26px; + height: 26px; + margin: 0 10px 0 0; + border: 1px solid #ccc; + border-radius: 20px; + text-align: center; + line-height: 26px; + background: #fff; + } + +#overlay { + display: none; + position: fixed; + top: 0; bottom: 0; + left: 0; right: 0; + background: rgba(0, 0, 0, 0.9); +} + +.flux_content .bottom { + font-size: 90%; + text-align: center; +} + +.hide_posts > :not(.active) > .flux_content { + display:none; +} + +/*** PAGINATION ***/ +.pagination { + display: table; + width: 100%; + margin: 0; + background: #1a1a1a; + text-align: center; + color: #888; + font-size: 80%; + line-height: 200%; + table-layout: fixed; +} + .pagination .item { + display: table-cell; + line-height: 40px; + } + .pagination .item.pager-current { + font-weight: bold; + font-size: 140%; + } + .pagination .pager-first, + .pagination .pager-previous, + .pagination .pager-next, + .pagination .pager-last { + width: 100px; + } + .pagination .item a { + display: block; + color: #333; + font-style: italic; + } + .pagination:first-child .item { + border-bottom: 1px solid #aaa; + } + .pagination:last-child .item { + border-top: 1px solid #aaa; + } + +#nav_entries { + display: table; + width: 250px; + height: 40px; + position: fixed; + bottom: 0; + left: 0; + margin: 0; + background: #1c1c1c; + border-top: 1px solid #2f2f2f; + text-align: center; + line-height: 40px; + table-layout: fixed; +} + #nav_entries .item { + display: table-cell; + width: 30%; + } + #nav_entries a { + display: block; + } + #nav_entries .i_up { + margin: 5px 0 0; + vertical-align: top; + } + +.loading { + background: url("loader.gif") center center no-repeat; + font-size: 0; +} + +#bigMarkAsRead { + display: block; + font-style: normal; + padding: 32px 0 64px 0; + text-align: center; + text-decoration: none; +} + #bigMarkAsRead:hover { + background: #1c1c1c; + color: #888; + } + .bigTick { + font-size: 72pt; + line-height: 1.6em; + } + +/*** NOTIFICATION ***/ +.notification { + position: absolute; + top: 10px; + left: 25%; right: 25%; + min-height: 30px; + padding: 10px; + line-height: 30px; + text-align: center; + border-radius: 5px; + box-shadow: 0 0 5px #666; + background: #1a1a1a; + color: #888; + font-weight: bold; + z-index: 10; +} + .notification.good { + border:1px solid #f4f899; + } + .notification.bad { + border:1px solid #f4a899; + } + .notification a.close { + display: inline-block; + width: 16px; + height: 16px; + float: right; + margin: -20px -20px 0 0; + padding: 5px; + background: #1a1a1a; + border-radius: 50px; + line-height: 16px; + } + .notification.good a.close{ + border:1px solid #f4f899; + } + .notification.bad a.close{ + border:1px solid #f4a899; + } + +.toggle_aside, .btn.toggle_aside { + display: none; +} + +.actualizeProgress { + position: fixed; + top: 10px; + left: 25%; right: 25%; + padding: 5px; + background: #1a1a1a; + text-align: center; + border: 1px solid #2f2f2f; + border-radius: 5px; +} + .actualizeProgress progress { + max-width: 100%; + vertical-align: middle; + } + .actualizeProgress .progress { + color: #888; + font-size: 90%; + vertical-align: middle; + } + +.logs { + border: 1px solid #aaa; +} + .log { + padding: 5px 2%; + overflow: auto; + background: #fafafa; + border-bottom: 1px solid #999; + color: #333; + font-size: 90%; + } + .log .date { + display: block; + } + .log.error { + background: #fdd; + color: #844; + } + .log.warning { + background: #ffe; + color: #c95; + } + .log.notice { + background: #f4f4f4; + color: #aaa; + } + .log.debug { + background: #111; + color: #eee; + } + +.form-group table { + border-collapse:collapse; + margin:10px 0 0 220px; + text-align:center; +} + +.form-group tr, .form-group th, .form-group td { + border:1px solid #2f2f2f; + font-weight:normal; + padding:.5em; +} + +select.number option { + text-align:right; +} + +@media(max-width: 840px) { + .header, + .aside .btn-important, + .aside .feeds .dropdown, + .flux_header .item.website span, + .item.date { + display: none; + } + .flux_header .item.website { + width: 40px; + text-align: center; + } + .flux_header .item.website .favicon { + padding: 12px; + } + + .nav-login { + display: block; + } + + .content { + font-size: 120%; + padding: 0; + } + + .pagination { + margin: 0 0 40px; + } + .pagination .pager-previous, .pagination .pager-next { + width: 100px; + } + + .toggle_aside, .btn.toggle_aside { + display: inline-block; + } + .aside { + position: fixed; + top: 0; left: 0; + width: 0; + overflow: hidden; + border-right: none; + z-index: 10; + transition: width 200ms linear; + } + .aside.aside_flux { + padding: 10px 0 0; + } + .aside:target { + width: 80%; + border-right: 1px solid #aaa; + overflow: auto; + } + .aside .toggle_aside { + position: absolute; + right: 0; + display: inline-block; + width: 26px; + height: 26px; + margin: 0 10px 0 0; + border: 1px solid #ccc; + border-radius: 20px; + text-align: center; + line-height: 26px; + } + .aside .categories { + margin: 30px 0; + } + + #nav_entries { + width: 100%; + } + + .nav_menu .btn { + margin: 5px 10px; + } + .nav_menu .stick { + margin: 0 10px; + } + .nav_menu .stick .btn { + margin: 5px 0; + } + .nav_menu .search { + display: inline-block; + max-width: 97%; + } + .nav_menu .search input { + max-width: 97%; + width: 90px; + } + .nav_menu .search input:focus { + width: 400px; + } + + #panel { + left: 5px; right: 5px; + } + + .day .date { + display: none; + } + .day .name { + height: 2.6em; + font-size: 1em; + text-shadow: none; + } + + .notification, + .actualizeProgress { + left: 10px; + right: 10px; + } +} + +/*** FALLBACK ***/ +.btn { + background: #1c1c1c; +} + .btn:hover { + background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -webkit-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -o-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -ms-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + } + .btn-important { + background: #26303F; + } + .btn-important:hover { + background: -moz-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -webkit-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -o-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + background: -ms-linear-gradient(top, #4A5D7A 0%, #26303F 100%); + } + .btn-attention { + background: #880011; + } + .btn-attention:hover { + background: -moz-linear-gradient(top, #cc0044 0%, #880011 100%); + background: -webkit-linear-gradient(top, #cc0044 0%, #880011 100%); + background: -o-linear-gradient(top, #cc0044 0%, #880011 100%); + background: -ms-linear-gradient(top, #cc0044 0%, #880011 100%); + } + +.dropdown-menu:after { + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); +} + +.nav-head { + background: #fff; + background: -moz-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -webkit-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -o-linear-gradient(top, #fff 0%, #f0f0f0 100%); + background: -ms-linear-gradient(top, #fff 0%, #f0f0f0 100%); +} + +.header > .item.search input { + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; +} + +@media(max-width: 840px) { + .aside { + -moz-transition: width 200ms linear; + -webkit-transition: width 200ms linear; + -o-transition: width 200ms linear; + -ms-transition: width 200ms linear; + } +} + +@media print { + .header, + .aside, + .nav_menu, + .day, + .flux_header, + .flux_content .bottom, + .pagination { + display: none; + } + + html, body { + background: #fff; + color: #000; + font-family: Serif; + font-size: 12pt; + } + + #global, + .flux_content { + display: block !important; + } + + .flux_content .content { + width: 100% !important; + text-align: justify; + } + + .flux_content .content a { + color: #000; + } + .flux_content .content a:after { + content: " (" attr(href) ") "; + text-decoration: underline; + } +} diff --git a/p/themes/Dark/global.css b/p/themes/Dark/global.css new file mode 100644 index 000000000..c76e3b1ba --- /dev/null +++ b/p/themes/Dark/global.css @@ -0,0 +1,491 @@ +@charset "UTF-8"; + +/* FONTS */ +@font-face { + font-family: "OpenSans"; + src: url("../fonts/openSans.woff") format("woff"); +} + + +* { + margin: 0; + padding: 0; +} +html, body { + height: 100%; + font-size: 95%; + font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif; + color: #888; +} + +/* LIENS */ +a { + color: #6986B2; + text-decoration: none; +} + a:hover { + text-decoration: underline; + } + +/* LISTES */ +ul, ol, dl { + margin: 10px 0 10px 30px; + line-height: 190%; +} + dd { + margin: 0 0 10px 30px; + } + +/* TITRES */ +h1, h2, h3 { + min-height: 40px; + margin: 15px 0 5px; + line-height: 40px; +} + +/* IMG */ +figure { + margin: 5px 0 10px; + text-align: center; +} + figcaption { + display: inline-block; + padding: 3px 20px; + color: #999; + font-style: italic; + border-bottom: 1px solid #ccc; + } +img { + height: auto; + max-width: 100%; + vertical-align: middle; +} + a img { + border: none; + } + +/* VIDEOS */ +iframe, embed, object, video { + max-width: 100%; +} + +/* FORMULAIRES */ +legend { + display: block; + width: 100%; + margin: 20px 0 5px; + padding: 5px 0; + border-bottom: 1px solid #2f2f2f; + font-size: 150%; + clear: both; +} +label { + display: block; + min-height: 25px; + padding: 5px 0; + font-size: 14px; + line-height: 25px; + cursor: pointer; +} +input, select, textarea { + display: inline-block; + max-width: 100%; + min-height: 25px; + padding: 5px; + background: #333; + border: 1px solid #000; + border-radius: 3px; + color: #999; + line-height: 25px; + vertical-align: middle; + box-shadow: 0 2px 2px #1d1d1d inset; +} + option { + padding:0 .5em 0 .5em; + } + input[type="radio"], + input[type="checkbox"] { + width: 15px !important; + min-height: 15px !important; + } + input:focus, select:focus, textarea:focus { + color: #6986b2; + border-color: #2f2f2f; + } + input:invalid, select:invalid { + border-color: red; + box-shadow: 0 0 2px 1px red; + } + +.form-group { + margin: 0; +} + .form-group:after { + content: ""; + display: block; + clear: both; + } + .form-group.form-actions { + min-width: 250px; + padding: 5px 0; + background: #1a1a1a; + border-top: 1px solid #2f2f2f; + } + .form-group.form-actions .btn { + margin: 0 10px; + } + .form-group .group-name { + display: block; + float: left; + width: 200px; + padding: 10px 0; + text-align: right; + } + .form-group .group-controls { + min-width: 250px; + min-height: 25px; + margin: 0 0 0 220px; + padding: 5px 0; + } + .form-group .group-controls .control { + display: block; + min-height: 30px; + padding: 5px 0; + line-height: 25px; + font-size: 14px; + } + +.stick { + display: inline-block; + white-space: nowrap; + font-size: 0px; + vertical-align: middle; +} + .stick input, + .stick .btn { + border-radius: 0; + font-size: 14px; + } + .stick .btn:first-child, + .stick input:first-child { + border-radius: 3px 0 0 3px; + } + .stick .btn-important:first-child { + border-right: 1px solid #000; + } + .stick .btn:last-child, + .stick input:last-child { + border-radius: 0 3px 3px 0; + } + .stick .btn + .btn, + .stick .btn + input, + .stick input + .btn, + .stick input + input { + border-left: none; + } + .stick .btn + .dropdown > .btn { + border-left: none; + border-radius: 0 3px 3px 0; + } + .stick .btn + .dropdown a { + font-size: 12px; + } + +.btn { + display: inline-block; + min-height: 37px; + min-width: 15px; + padding: 5px 10px; + background: linear-gradient(to bottom, #fff 0%, #eee 100%); + border-radius: 3px; + border: 1px solid #000; + color: #888; + line-height: 20px; + vertical-align: middle; + cursor: pointer; + overflow: hidden; +} + a.btn { + min-height: 25px; + line-height: 25px; + } + .btn:hover { + background: linear-gradient(to bottom, #4A5D7A, #26303F); + text-decoration: none; + } + .btn.active, + .btn:active, + .dropdown-target:target ~ .btn.dropdown-toggle { + background: #26303F; + } + + .btn-important { + background: linear-gradient(to bottom, #0084CC, #0045CC); + color: #888888; + border: 1px solid #000000; + } + .btn-important:hover { + background: linear-gradient(to bottom, #0066CC, #0045CC); + } + .btn-important:active { + background: #0044CB; + box-shadow: none; + } + + .btn-attention { + background: linear-gradient(to bottom, #E95B57, #BD362F); + color: #888888; + border: 1px solid #000000; + } + .btn-attention:hover { + background: linear-gradient(to bottom, #D14641, #BD362F); + } + .btn-attention:active { + background: #BD362F; + box-shadow: none; + } + +/* NAVIGATION */ +.nav-list .nav-header, +.nav-list .item { + display: block; + height: 35px; + line-height: 35px; +} + .nav-list .item:hover { + background: #1a1a1a; + } + .nav-list .item:hover a { + color: #26303F; + } + .nav-list .item.active { + background: #26303F; + color: #1a1a1a; + } + .nav-list .item.active a { + color: #888; + } + .nav-list .disable { + color: #aaa; + background: #fafafa; + text-align: center; + } + .nav-list .item > * { + display: block; + padding: 0 10px; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + .nav-list a:hover { + text-decoration: none; + } + .nav-list .item.error a { + color: #BD362F; + } + .nav-list .item.active.error a { + color: #fff; + background: #BD362F; + } + .nav-list .item.empty a { + color: #f39c12; + } + .nav-list .item.active.empty a { + color: #fff; + background: #f39c12; + } + + .nav-list .nav-header { + padding: 0 10px; + background: #1a1a1a; + border-bottom: 1px solid #2f2f2f; + font-weight: bold; + } + .nav-list .separator { + display: block; + height: 0; + margin: 5px 0; + border-bottom: 1px solid #ddd; + } + + .nav-list .nav-form { + padding: 3px; + text-align: center; + } + +.nav-head { + display: block; + margin: 0; + background: linear-gradient(to bottom, #fff, #f0f0f0); + border-bottom: 1px solid #ddd; + text-align: right; +} + .nav-head .item { + display: inline-block; + padding: 5px 10px; + } + +/* HORIZONTAL-LIST */ +.horizontal-list { + display: table; + table-layout: fixed; + margin: 0; + padding: 0; + width: 100%; +} + .horizontal-list .item { + display: table-cell; + vertical-align: middle; + } + +/* DROPDOWN */ +.dropdown { + position: relative; + display: inline-block; +} + .dropdown-target { + display: none; + } + + .dropdown-menu { + display: none; + min-width: 200px; + margin: 5px 0 0; + padding: 5px 0; + position: absolute; + right: 0px; + background: #1a1a1a; + border: 1px solid #888; + border-radius: 5px; + text-align: left; + } + .dropdown-menu:after { + content: ""; + position: absolute; + top: -6px; + right: 13px; + width: 10px; + height: 10px; + background: #1a1a1a; + border-top: 1px solid #888; + border-left: 1px solid #888; + z-index: -10; + transform: rotate(45deg); + } + .dropdown-header { + display: block; + padding: 0 5px; + color: #888; + font-weight: bold; + font-size: 14px; + line-height: 30px; + } + .dropdown-menu .item { + display: block; + height: 30px; + font-size: 90%; + line-height: 30px; + } + .dropdown-menu > .item > a { + display: block; + padding: 0 25px; + line-height: 30px; + } + .dropdown-menu > .item:hover { + background: #26303F; + color: #888; + } + .dropdown-menu > .item[aria-checked="true"] > a:before { + content: '✓ '; + font-weight: bold; + margin: 0 0 0 -1.2em; + padding: 0 0.2em 0 0; + } + .dropdown-menu > .item:hover > a { + color: #888; + text-decoration: none; + } + .dropdown-menu .input { + display: block; + height: 40px; + font-size: 90%; + line-height: 30px; + } + .dropdown-menu .input select, + .dropdown-menu .input input { + display: block; + height: 20px; + width: 95%; + margin: auto; + padding: 2px 5px; + border-radius: 3px; + } + .dropdown-menu .input select { + width: 70%; + height: auto; + } + .dropdown-menu .separator { + display: block; + height: 0; + margin: 5px 0; + border-bottom: 1px solid #888; + } + .dropdown-target:target ~ .dropdown-menu { + display: block; + z-index: 10; + } + .dropdown-close { + display: inline; + } + .dropdown-close a { + font-size: 0; + position: fixed; + top: 0; bottom: 0; + left: 0; right: 0; + display: block; + z-index: -10; + } + +/* ALERTS */ +.alert { + display: block; + width: 90%; + margin: 15px auto; + padding: 10px 15px; + background: #1a1a1a; + border: 1px solid #ccc; + border-right: 1px solid #aaa; + border-bottom: 1px solid #aaa; + border-radius: 5px; + color: #aaa; +} + .alert-head { + margin: 0; + font-weight: bold; + font-size: 110%; + } + .alert > a { + color: inherit; + text-decoration: underline; + } + .alert-warn { + border: 1px solid #c95; + color: #c95; + } + .alert-success { + border: 1px solid #484; + color: #484; + } + .alert-error { + border: 1px solid #844; + color: #844; + } + +/* ICÔNES */ +.icon { + display: inline-block; + width: 16px; + height: 16px; + vertical-align: middle; + line-height: 16px; +} diff --git a/p/themes/Dark/loader.gif b/p/themes/Dark/loader.gif Binary files differnew file mode 100644 index 000000000..86022be71 --- /dev/null +++ b/p/themes/Dark/loader.gif diff --git a/p/themes/Dark/metadata.json b/p/themes/Dark/metadata.json new file mode 100644 index 000000000..27cae27df --- /dev/null +++ b/p/themes/Dark/metadata.json @@ -0,0 +1,7 @@ +{ + "name": "Dark", + "author": "AD", + "description": "Le coté obscur du thème “Origine”", + "version": 0.1, + "files": ["global.css", "freshrss.css"] +} diff --git a/p/themes/flat-design/freshrss.css b/p/themes/Flat/freshrss.css index 7a1682370..dca1b3f28 100644 --- a/p/themes/flat-design/freshrss.css +++ b/p/themes/Flat/freshrss.css @@ -243,33 +243,26 @@ body { border-top: 1px solid #ecf0f1; cursor: pointer; } - .flux .flux_header > .item > a, - .flux .bottom > .item > a { - display: inline-block; - height: 40px; - width: 100%; + .flux .item { line-height: 40px; + white-space: nowrap; + } + .flux_header > .item { + overflow: hidden; + text-overflow: ellipsis; } .flux .item.manage { width: 40px; - white-space: nowrap; text-align: center; } .flux .item.website { width: 200px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - line-height: 40px; } - .flux .item.website .favicon { + .website .favicon { padding: 5px; } .flux .item.title { background: inherit; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; } .flux:hover .item.title { border-right: 2px solid rgba(127, 127, 127, 0.1); @@ -286,10 +279,7 @@ body { } .flux .item.date { width: 200px; - overflow: hidden; padding:0 5px 0 0; - white-space: nowrap; - text-overflow: ellipsis; text-align: right; font-size: 10px; color: #666; diff --git a/p/themes/flat-design/global.css b/p/themes/Flat/global.css index c24a9f481..df9c9f8c9 100644 --- a/p/themes/flat-design/global.css +++ b/p/themes/Flat/global.css @@ -381,22 +381,28 @@ input, select, textarea { font-size: 14px; line-height: 30px; } - .dropdown-menu .item { + .dropdown-menu > .item { display: block; height: 30px; font-size: 90%; line-height: 30px; } - .dropdown-menu .item > * { + .dropdown-menu > .item > a { display: block; padding: 0 25px; line-height: 30px; } - .dropdown-menu .item:hover { + .dropdown-menu > .item:hover > a { background: #2980b9; color: #fff; } - .dropdown-menu .item:hover > * { + .dropdown-menu > .item[aria-checked="true"] > a:before { + content: '✓ '; + font-weight: bold; + margin: 0 0 0 -1.2em; + padding: 0 0.2em 0 0; + } + .dropdown-menu > .item:hover > a { color: #fff; text-decoration: none; } @@ -463,6 +469,10 @@ input, select, textarea { font-weight: bold; font-size: 110%; } + .alert > a { + color: inherit; + text-decoration: underline; + } .alert-warn { background: #ffe; border: 1px solid #eeb; diff --git a/p/themes/flat-design/icons/add.svg b/p/themes/Flat/icons/add.svg index 15767a3ad..15767a3ad 100644 --- a/p/themes/flat-design/icons/add.svg +++ b/p/themes/Flat/icons/add.svg diff --git a/p/themes/flat-design/icons/all.svg b/p/themes/Flat/icons/all.svg index d20e0f5bf..d20e0f5bf 100644 --- a/p/themes/flat-design/icons/all.svg +++ b/p/themes/Flat/icons/all.svg diff --git a/p/themes/flat-design/icons/close.svg b/p/themes/Flat/icons/close.svg index 629fda7ff..629fda7ff 100644 --- a/p/themes/flat-design/icons/close.svg +++ b/p/themes/Flat/icons/close.svg diff --git a/p/themes/flat-design/icons/configure.svg b/p/themes/Flat/icons/configure.svg index 969c5719f..969c5719f 100644 --- a/p/themes/flat-design/icons/configure.svg +++ b/p/themes/Flat/icons/configure.svg diff --git a/p/themes/flat-design/icons/down.svg b/p/themes/Flat/icons/down.svg index 31730626f..31730626f 100644 --- a/p/themes/flat-design/icons/down.svg +++ b/p/themes/Flat/icons/down.svg diff --git a/p/themes/flat-design/icons/next.svg b/p/themes/Flat/icons/next.svg index d75cc40f5..d75cc40f5 100644 --- a/p/themes/flat-design/icons/next.svg +++ b/p/themes/Flat/icons/next.svg diff --git a/p/themes/flat-design/icons/prev.svg b/p/themes/Flat/icons/prev.svg index 9ba03ceb2..9ba03ceb2 100644 --- a/p/themes/flat-design/icons/prev.svg +++ b/p/themes/Flat/icons/prev.svg diff --git a/p/themes/flat-design/icons/refresh.svg b/p/themes/Flat/icons/refresh.svg index 8f95bf443..8f95bf443 100644 --- a/p/themes/flat-design/icons/refresh.svg +++ b/p/themes/Flat/icons/refresh.svg diff --git a/p/themes/flat-design/icons/search.svg b/p/themes/Flat/icons/search.svg index bca7571b4..bca7571b4 100644 --- a/p/themes/flat-design/icons/search.svg +++ b/p/themes/Flat/icons/search.svg diff --git a/p/themes/flat-design/icons/up.svg b/p/themes/Flat/icons/up.svg index 3ab11b168..3ab11b168 100644 --- a/p/themes/flat-design/icons/up.svg +++ b/p/themes/Flat/icons/up.svg diff --git a/p/themes/flat-design/loader.gif b/p/themes/Flat/loader.gif Binary files differindex ce36565b3..ce36565b3 100644 --- a/p/themes/flat-design/loader.gif +++ b/p/themes/Flat/loader.gif diff --git a/p/themes/flat-design/metadata.json b/p/themes/Flat/metadata.json index 6b94d11c2..6b94d11c2 100644 --- a/p/themes/flat-design/metadata.json +++ b/p/themes/Flat/metadata.json diff --git a/p/themes/default/freshrss.css b/p/themes/Origine/freshrss.css index 86925accc..593f21d30 100644 --- a/p/themes/default/freshrss.css +++ b/p/themes/Origine/freshrss.css @@ -257,37 +257,26 @@ border-top: 1px solid #ddd; cursor: pointer; } - .flux .flux_header > .item > a, - .flux .bottom > .item > a { - display: inline-block; - height: 40px; - width: 100%; + .flux .item { line-height: 40px; + white-space: nowrap; + } + .flux_header > .item { + overflow: hidden; + text-overflow: ellipsis; } .flux .item.manage { width: 40px; - white-space: nowrap; text-align: center; } .flux .item.website { width: 200px; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - line-height: 40px; } - .flux .item.website .favicon { + .website .favicon { padding: 5px; } - .flux .item.website a { - display: block; - height: 40px; - } .flux .item.title { background: inherit; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; } .flux:hover .item.title { border-right: 2px solid rgba(127, 127, 127, 0.1); @@ -304,10 +293,7 @@ } .flux .item.date { width: 200px; - overflow: hidden; padding:0 5px 0 0; - white-space: nowrap; - text-overflow: ellipsis; text-align: right; font-size: 10px; color: #666; diff --git a/p/themes/default/global.css b/p/themes/Origine/global.css index 2cc195f90..23e190330 100644 --- a/p/themes/default/global.css +++ b/p/themes/Origine/global.css @@ -392,22 +392,28 @@ input, select, textarea { font-size: 14px; line-height: 30px; } - .dropdown-menu .item { + .dropdown-menu > .item { display: block; height: 30px; font-size: 90%; line-height: 30px; } - .dropdown-menu .item > * { + .dropdown-menu > .item > a { display: block; padding: 0 25px; line-height: 30px; } - .dropdown-menu .item:hover { + .dropdown-menu > .item:hover { background: #0062BE; color: #fff; } - .dropdown-menu .item:hover > * { + .dropdown-menu > .item[aria-checked="true"] > a:before { + content: '✓ '; + font-weight: bold; + margin: 0 0 0 -1.2em; + padding: 0 0.2em 0 0; + } + .dropdown-menu > .item:hover > a { color: #fff; text-decoration: none; } @@ -471,6 +477,10 @@ input, select, textarea { font-weight: bold; font-size: 110%; } + .alert > a { + color: inherit; + text-decoration: underline; + } .alert-warn { background: #ffe; border: 1px solid #eeb; diff --git a/p/themes/default/loader.gif b/p/themes/Origine/loader.gif Binary files differindex 5ff26f0e3..5ff26f0e3 100644 --- a/p/themes/default/loader.gif +++ b/p/themes/Origine/loader.gif diff --git a/p/themes/default/metadata.json b/p/themes/Origine/metadata.json index d316ec517..f93dcbc3f 100644 --- a/p/themes/default/metadata.json +++ b/p/themes/Origine/metadata.json @@ -1,5 +1,5 @@ { - "name": "Default", + "name": "Origine", "author": "Marien Fressinaud", "description": "Le thème par défaut pour FreshRSS", "version": 0.1, |
