aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Marien Fressinaud <dev@marienfressinaud.fr> 2014-09-08 19:26:35 +0200
committerGravatar Marien Fressinaud <dev@marienfressinaud.fr> 2014-09-08 19:26:35 +0200
commitef1b35fc4385c99c4d38e3f87e8126d0dbe21519 (patch)
treec2127f92281084c3cb28f635dea63a9a179eabbb
parent909d8747ba09f9c9a6ac895f1f4f0763bdb27a55 (diff)
parentc3fd8877c021b86180b3bea4d4260e6478f0558e (diff)
Merge branch 'dev' into 411-update-system
Conflicts: constants.php
-rw-r--r--CHANGELOG3
-rw-r--r--README.fr.md101
-rw-r--r--README.md130
-rwxr-xr-xapp/Controllers/configureController.php1
-rw-r--r--app/Controllers/importExportController.php252
-rwxr-xr-xapp/Controllers/indexController.php52
-rw-r--r--app/Controllers/statsController.php5
-rw-r--r--app/FreshRSS.php31
-rw-r--r--app/Models/Configuration.php7
-rw-r--r--app/Models/EntryDAO.php4
-rw-r--r--app/Models/Feed.php6
-rw-r--r--app/Models/StatsDAO.php62
-rw-r--r--app/i18n/de.php326
-rw-r--r--app/i18n/en.php9
-rw-r--r--app/i18n/fr.php9
-rw-r--r--app/layout/aside_flux.phtml8
-rw-r--r--app/layout/nav_menu.phtml16
-rw-r--r--app/views/configure/categorize.phtml3
-rw-r--r--app/views/configure/reading.phtml10
-rw-r--r--app/views/configure/shortcut.phtml15
-rw-r--r--app/views/helpers/javascript_vars.phtml7
-rw-r--r--app/views/helpers/view/normal_view.phtml7
-rw-r--r--app/views/importExport/index.phtml4
-rw-r--r--app/views/index/formLogin.phtml23
-rw-r--r--app/views/javascript/actualize.phtml21
-rw-r--r--app/views/stats/repartition.phtml87
-rw-r--r--constants.php1
-rw-r--r--data/tokens/.gitignore1
-rw-r--r--data/tokens/index.html13
-rw-r--r--lib/Minz/Helper.php17
-rw-r--r--lib/Minz/Request.php132
-rw-r--r--lib/Minz/Session.php74
-rw-r--r--lib/SimplePie/SimplePie/Parser.php2
-rw-r--r--lib/lib_rss.php4
-rw-r--r--p/api/greader.php1
-rw-r--r--p/i/.gitignore1
-rw-r--r--p/scripts/main.js76
-rw-r--r--p/themes/Dark/dark.css6
-rw-r--r--p/themes/Flat/flat.css11
-rw-r--r--p/themes/Origine/origine.css20
-rw-r--r--p/themes/Screwdriver/metadata.json2
-rw-r--r--p/themes/Screwdriver/screwdriver.css23
-rw-r--r--p/themes/Screwdriver/template.css3
-rw-r--r--p/themes/base-theme/base.css12
44 files changed, 1221 insertions, 377 deletions
diff --git a/CHANGELOG b/CHANGELOG
index d402c7c39..df43fe1d1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -11,9 +11,12 @@
* Improvements
* Security
* Basic protection against XSRF (Cross-Site Request Forgery) based on HTTP Referer (POST requests only)
+* API
+ * Compatible with lighttpd
* Misc.
* Changed lazyload implementation
* Support of HTML5 notifications for new upcoming articles
+ * Add option to stay logged in
* Bux fixes in export function, add/remove users, keyboard shortcuts, etc.
diff --git a/README.fr.md b/README.fr.md
new file mode 100644
index 000000000..df43ef7e8
--- /dev/null
+++ b/README.fr.md
@@ -0,0 +1,101 @@
+* [English version](README.md)
+
+# FreshRSS
+FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de [Leed](http://projet.idleman.fr/leed/) ou de [Kriss Feed](http://tontof.net/kriss/feed/).
+
+Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable.
+
+Il permet de gérer plusieurs utilisateurs, et dispose d’un mode de lecture anonyme.
+
+* Site officiel : http://freshrss.org
+* Démo : http://demo.freshrss.org/
+* Développeur : Marien Fressinaud <dev@marienfressinaud.fr>
+* Version actuelle : 0.8-dev
+* Date de publication 2014-0x-xx
+* License [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html)
+
+![Logo de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_title.png)
+
+# Note sur les branches
+**Ce logiciel est encore en développement !** Veuillez vous assurer d'utiliser la branche qui vous correspond :
+
+* Utilisez [la branche master](https://github.com/marienfressinaud/FreshRSS/tree/master/) si vous visez la stabilité.
+* [La branche beta](https://github.com/marienfressinaud/FreshRSS/tree/beta) est celle par défaut : les nouveautés y sont ajoutées environ tous les mois.
+* Pour les développeurs et ceux qui savent ce qu'ils font, [la branche dev](https://github.com/marienfressinaud/FreshRSS/tree/dev) vous ouvre les bras !
+
+# Disclaimer
+Cette application a été développée pour s’adapter à des besoins personnels et non professionnels.
+Je ne garantis en aucun cas la sécurité de celle-ci, ni son bon fonctionnement.
+Je m’engage néanmoins à répondre dans la mesure du possible aux demandes d’évolution si celles-ci me semblent justifiées.
+Privilégiez pour cela des demandes sur GitHub
+(https://github.com/marienfressinaud/FreshRSS/issues) ou par mail (dev@marienfressinaud.fr)
+
+# Pré-requis
+* 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 (recommandé), ou nginx, lighttpd (non testé sur les autres)
+* PHP 5.2.1+ (PHP 5.3.7+ recommandé)
+ * Requis : [PDO_MySQL](http://php.net/pdo-mysql) ou [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (seulement pour accès API sur platformes < 64 bits)
+ * Recommandés : [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [zlib](http://php.net/zlib), [Zip](http://php.net/zip)
+* MySQL 5.0.3+ (recommandé) ou SQLite 3.7.4+
+* Un navigateur Web récent tel Firefox 4+, Chrome, Opera, Safari, Internet Explorer 9+
+ * Fonctionne aussi sur mobile
+
+![Capture d’écran de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png)
+
+# Installation
+1. Récupérez l’application FreshRSS via la commande git ou [en téléchargeant l’archive](https://github.com/marienfressinaud/FreshRSS/archive/master.zip)
+2. Placez l’application sur votre serveur (la partie à exposer au Web est le répertoire `./p/`)
+3. Le serveur Web doit avoir les droits d’écriture dans le répertoire `./data/`
+4. Accédez à FreshRSS à travers votre navigateur Web et suivez les instructions d’installation
+5. Tout devrait fonctionner :) En cas de problème, n’hésitez pas à me contacter.
+
+# Contrôle d’accès
+Il est requis pour le mode multi-utilisateur, et recommandé dans tous les cas, de limiter l’accès à votre FreshRSS. Au choix :
+* En utilisant l’identification par formulaire (requiert JavaScript, et PHP 5.3.7+ recommandé – fonctionne avec certaines versions de PHP 5.3.3+)
+* En utilisant l’identification par [Mozilla Persona](https://login.persona.org/about) incluse dans FreshRSS
+* En utilisant un contrôle d’accès HTTP défini par votre serveur Web
+ * Voir par exemple la [documentation d’Apache sur l’authentification](http://httpd.apache.org/docs/trunk/howto/auth.html)
+ * Créer dans ce cas un fichier `./p/i/.htaccess` avec un fichier `.htpasswd` correspondant.
+
+# Rafraîchissement automatique des flux
+* Vous pouvez ajouter une tâche Cron lançant régulièrement le script d’actualisation automatique des flux.
+Consultez la documentation de Cron de votre système d’exploitation ([Debian/Ubuntu](http://doc.ubuntu-fr.org/cron), [Red Hat/Fedora](http://doc.fedora-fr.org/wiki/CRON_:_Configuration_de_t%C3%A2ches_automatis%C3%A9es), [Slackware](http://docs.slackware.com/fr:slackbook:process_control?#cron), [Gentoo](http://wiki.gentoo.org/wiki/Cron/fr), [Arch Linux](http://wiki.archlinux.fr/Cron)…).
+C’est une bonne idée d’utiliser le même utilisateur que votre serveur Web (souvent “www-data”).
+Par exemple, pour exécuter le script toutes les heures :
+
+```
+7 * * * * php /chemin/vers/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1
+```
+
+# Conseils
+* Pour une meilleure sécurité, faites en sorte que seul le répertoire `./p/` soit accessible depuis le Web, par exemple en faisant pointer un sous-domaine sur le répertoire `./p/`.
+ * En particulier, les données personnelles se trouvent dans le répertoire `./data/`.
+* Le fichier `./constants.php` définit les chemins d’accès aux répertoires clés de l’application. Si vous les bougez, tout se passe ici.
+* En cas de problème, les logs peuvent être utile à lire, soit depuis l’interface de FreshRSS, soit manuellement depuis `./data/log/*.log`.
+
+# Sauvegarde
+* Il faut conserver vos fichiers `./data/config.php` ainsi que `./data/*_user.php` et éventuellement `./data/persona/`
+* Vous pouvez exporter votre liste de flux depuis FreshRSS au format OPML
+* Pour sauvegarder les articles eux-même, vous pouvez utiliser [phpMyAdmin](http://www.phpmyadmin.net) ou les outils de MySQL :
+
+```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/)
+* [flotr2](http://www.humblesoftware.com/flotr2)
+
+## Uniquement pour certaines options
+* [bcrypt.js](https://github.com/dcodeIO/bcrypt.js)
+* [phpQuery](http://code.google.com/p/phpquery/)
+
+## 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/README.md b/README.md
index 8963e040c..0a22df8f1 100644
--- a/README.md
+++ b/README.md
@@ -1,88 +1,90 @@
+* [Version française](README.fr.md)
+
# FreshRSS
-FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de [Leed](http://projet.idleman.fr/leed/) ou de [Kriss Feed](http://tontof.net/kriss/feed/).
+FreshRSS is a self-hosted RSS feed agregator like [Leed](http://projet.idleman.fr/leed/) or [Kriss Feed](http://tontof.net/kriss/feed/).
-Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable.
+It is at the same time light-weight, easy to work with, powerful and customizable.
-Il permet de gérer plusieurs utilisateurs, et dispose d’un mode de lecture anonyme.
+It is a multi-user application with an anonymous reading mode.
-* Site officiel : http://freshrss.org
-* Démo : http://demo.freshrss.org/
-* Développeur : Marien Fressinaud <dev@marienfressinaud.fr>
-* Version actuelle : 0.8-dev
-* Date de publication 2014-0x-xx
+* Official website: http://freshrss.org
+* Demo: http://demo.freshrss.org/
+* Developer: Marien Fressinaud <dev@marienfressinaud.fr>
+* Current version: 0.8-dev
+* Publication date: 2014-0x-xx
* License [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html)
-![Logo de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_title.png)
+![FreshRSS logo](http://marienfressinaud.fr/data/images/freshrss/freshrss_title.png)
-# Note sur les branches
-**Ce logiciel est encore en développement !** Veuillez vous assurer d'utiliser la branche qui vous correspond :
+# Note on branches
+**This application is still in development!** Please use the branch that suits your needs:
-* Utilisez [la branche master](https://github.com/marienfressinaud/FreshRSS/tree/master/) si vous visez la stabilité.
-* [La branche beta](https://github.com/marienfressinaud/FreshRSS/tree/beta) est celle par défaut : les nouveautés y sont ajoutées environ tous les mois.
-* Pour les développeurs et ceux qui savent ce qu'ils font, [la branche dev](https://github.com/marienfressinaud/FreshRSS/tree/dev) vous ouvre les bras !
+* Use [the master branch](https://github.com/marienfressinaud/FreshRSS/tree/master/) if you need a stable version.
+* [The beta branch](https://github.com/marienfressinaud/FreshRSS/tree/beta) is the default branch: new features are added on a monthly basis.
+* For developers and tech savvy persons, [the dev branch](https://github.com/marienfressinaud/FreshRSS/tree/dev) is waiting for you!
# Disclaimer
-Cette application a été développée pour s’adapter à des besoins personnels et non professionnels.
-Je ne garantis en aucun cas la sécurité de celle-ci, ni son bon fonctionnement.
-Je m’engage néanmoins à répondre dans la mesure du possible aux demandes d’évolution si celles-ci me semblent justifiées.
-Privilégiez pour cela des demandes sur GitHub
-(https://github.com/marienfressinaud/FreshRSS/issues) ou par mail (dev@marienfressinaud.fr)
-
-# Pré-requis
-* 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.1+ (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), [Zip](http://php.net/zip)
-* MySQL 5.0.3+ (recommandé) ou SQLite 3.7.4+ (en bêta)
-* Un navigateur Web récent tel Firefox 4+, Chrome, Opera, Safari, Internet Explorer 9+
- * Fonctionne aussi sur mobile
-
-![Capture d’écran de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png)
+This application was developed to fulfill personal needs not professional needs.
+There is no guarantee neither on its security nor its proper functioning.
+If there is feature requests which I think are good for the project, I'll do my best to include them.
+The best way is to open issues on GitHub
+(https://github.com/marienfressinaud/FreshRSS/issues) or by email (dev@marienfressinaud.fr)
+
+# Requirements
+* Light server running Linux or Windows
+ * It even works on Raspberry Pi with response time under a second (tested with 150 feeds, 22k articles, or 32Mo of compressed data)
+* A web server: Apache2 (recommanded), nginx, lighttpd (not tested on others)
+* PHP 5.2.1+ (PHP 5.3.7+ recommanded)
+ * Required extensions: [PDO_MySQL](http://php.net/pdo-mysql) or [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (only for API access on platforms under 64 bits)
+ * Recommanded extensions : [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [zlib](http://php.net/zlib), [Zip](http://php.net/zip)
+* MySQL 5.0.3+ (recommanded) ou SQLite 3.7.4+
+* A recent browser like Firefox 4+, Chrome, Opera, Safari, Internet Explorer 9+
+ * Works on mobile
+
+![FreshRSS screenshot](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png)
# Installation
-1. Récupérez l’application FreshRSS via la commande git ou [en téléchargeant l’archive](https://github.com/marienfressinaud/FreshRSS/archive/master.zip)
-2. Placez l’application sur votre serveur (la partie à exposer au Web est le répertoire `./p/`)
-3. Le serveur Web doit avoir les droits d’écriture dans le répertoire `./data/`
-4. Accédez à FreshRSS à travers votre navigateur Web et suivez les instructions d’installation
-5. Tout devrait fonctionner :) En cas de problème, n’hésitez pas à me contacter.
-
-# Contrôle d’accès
-Il est requis pour le mode multi-utilisateur, et recommandé dans tous les cas, de limiter l’accès à votre FreshRSS. Au choix :
-* En utilisant l’identification par formulaire (requiert JavaScript, et PHP 5.3.7+ recommandé – fonctionne avec certaines versions de PHP 5.3.3+)
-* En utilisant l’identification par [Mozilla Persona](https://login.persona.org/about) incluse dans FreshRSS
-* En utilisant un contrôle d’accès HTTP défini par votre serveur Web
- * Voir par exemple la [documentation d’Apache sur l’authentification](http://httpd.apache.org/docs/trunk/howto/auth.html)
- * Créer dans ce cas un fichier `./p/i/.htaccess` avec un fichier `.htpasswd` correspondant.
-
-# Rafraîchissement automatique des flux
-* Vous pouvez ajouter une tâche Cron lançant régulièrement le script d’actualisation automatique des flux.
-Consultez la documentation de Cron de votre système d’exploitation ([Debian/Ubuntu](http://doc.ubuntu-fr.org/cron), [Red Hat/Fedora](http://doc.fedora-fr.org/wiki/CRON_:_Configuration_de_t%C3%A2ches_automatis%C3%A9es), [Slackware](http://docs.slackware.com/fr:slackbook:process_control?#cron), [Gentoo](http://wiki.gentoo.org/wiki/Cron/fr), [Arch Linux](http://wiki.archlinux.fr/Cron)…).
-C’est une bonne idée d’utiliser le même utilisateur que votre serveur Web (souvent “www-data”).
-Par exemple, pour exécuter le script toutes les heures :
+1. Get FreshRSS with git or [by downloading the archive](https://github.com/marienfressinaud/FreshRSS/archive/master.zip)
+2. Dump the application on your server (expose only the `./p/` folder)
+3. Add write access on `./data/` folder to the webserver user
+4. Access FreshRSS with your browser and follow the installation process
+5. Every thing should be working :) If you encounter any problem, feel free to contact me.
+
+# Access control
+It is needed for the multi-user mode to limit access to FreshRSS. You can:
+* use form authentication (need JavaScript and PHP 5.3.7+, works with some PHP 5.3.3+)
+* use [Mozilla Persona](https://login.persona.org/about) authentication included in FreshRSS
+* use HTTP authentication supported by your web server
+ * See [Apache documentation](http://httpd.apache.org/docs/trunk/howto/auth.html)
+ * In that case, create a `./p/i/.htaccess` file with a matching `.htpasswd` file.
+
+# Automatic feed update
+* You can add a Cron job to launch the update script.
+Check the Cron documentation related to your distribution ([Debian/Ubuntu](https://help.ubuntu.com/community/CronHowto), [Red Hat/Fedora](https://fedoraproject.org/wiki/Administration_Guide_Draft/Cron), [Slackware](http://docs.slackware.com/fr:slackbook:process_control?#cron), [Gentoo](https://wiki.gentoo.org/wiki/Cron), [Arch Linux](https://wiki.archlinux.org/index.php/Cron)…).
+It’s a good idea to use the web server user .
+For example, if you want to run the script every hour:
```
7 * * * * php /chemin/vers/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1
```
-# Conseils
-* Pour une meilleure sécurité, faites en sorte que seul le répertoire `./p/` soit accessible depuis le Web, par exemple en faisant pointer un sous-domaine sur le répertoire `./p/`.
- * En particulier, les données personnelles se trouvent dans le répertoire `./data/`.
-* Le fichier `./constants.php` définit les chemins d’accès aux répertoires clés de l’application. Si vous les bougez, tout se passe ici.
-* En cas de problème, les logs peuvent être utile à lire, soit depuis l’interface de FreshRSS, soit manuellement depuis `./data/log/*.log`.
+# Advices
+* For a better security, expose only the `./p/` folder on the web.
+ * Be aware that the `./data/` folder contain all personal data, so it is a bad idea to expose it.
+* The `./constants.php` file define access to application folder. If you want to customize your installation, every thing happens here.
+* If you encounter some problem, logs are accessibles from the interface or manually in `./data/log/*.log` files.
-# Sauvegarde
-* Il faut conserver vos fichiers `./data/config.php` ainsi que `./data/*_user.php` et éventuellement `./data/persona/`
-* Vous pouvez exporter votre liste de flux depuis FreshRSS au format OPML
-* Pour sauvegarder les articles eux-même, vous pouvez utiliser [phpMyAdmin](http://www.phpmyadmin.net) ou les outils de MySQL :
+# Backup
+* You need to keep `./data/config.php`, `./data/*_user.php` and `./data/persona/` files
+* You can export your feed list in OPML format from FreshRSS
+* To save articles, you can use [phpMyAdmin](http://www.phpmyadmin.net) or MySQL tools:
```bash
-mysqldump -u utilisateur -p --databases freshrss > freshrss.sql
+mysqldump -u user -p --databases freshrss > freshrss.sql
```
-# Bibliothèques incluses
+# Included libraries
* [SimplePie](http://simplepie.org/)
* [MINZ](https://github.com/marienfressinaud/MINZ)
* [php-http-304](http://alexandre.alapetite.fr/doc-alex/php-http-304/)
@@ -90,10 +92,10 @@ mysqldump -u utilisateur -p --databases freshrss > freshrss.sql
* [keyboard_shortcuts](http://www.openjs.com/scripts/events/keyboard_shortcuts/)
* [flotr2](http://www.humblesoftware.com/flotr2)
-## Uniquement pour certaines options
+## Only for some options
* [bcrypt.js](https://github.com/dcodeIO/bcrypt.js)
* [phpQuery](http://code.google.com/p/phpquery/)
-## Si les fonctions natives ne sont pas disponibles
+## If native functions are not available
* [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 0bf58880f..bb96bfae3 100755
--- a/app/Controllers/configureController.php
+++ b/app/Controllers/configureController.php
@@ -184,6 +184,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
$this->view->conf->_default_view((int)Minz_Request::param('default_view', FreshRSS_Entry::STATE_ALL));
$this->view->conf->_auto_load_more(Minz_Request::param('auto_load_more', false));
$this->view->conf->_display_posts(Minz_Request::param('display_posts', false));
+ $this->view->conf->_display_categories(Minz_Request::param('display_categories', false));
$this->view->conf->_hide_read_feeds(Minz_Request::param('hide_read_feeds', false));
$this->view->conf->_onread_jump_next(Minz_Request::param('onread_jump_next', false));
$this->view->conf->_lazyload(Minz_Request::param('lazyload', false));
diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php
index 2b3353d93..5adf3878a 100644
--- a/app/Controllers/importExportController.php
+++ b/app/Controllers/importExportController.php
@@ -5,7 +5,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
if (!$this->view->loginOk) {
Minz_Error::error(
403,
- array('error' => array(Minz_Translate::t('access_denied')))
+ array('error' => array(_t('access_denied')))
);
}
@@ -20,33 +20,51 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$this->view->categories = $this->catDAO->listCategories();
$this->view->feeds = $this->feedDAO->listFeeds();
- Minz_View::prependTitle(Minz_Translate::t('import_export') . ' · ');
+ Minz_View::prependTitle(_t('import_export') . ' · ');
}
public function importAction() {
- if (Minz_Request::isPost() && $_FILES['file']['error'] == 0) {
- @set_time_limit(300);
+ if (!Minz_Request::isPost()) {
+ Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
+ }
- $file = $_FILES['file'];
- $type_file = $this->guessFileType($file['name']);
+ $file = $_FILES['file'];
+ $status_file = $file['error'];
- $list_files = array(
- 'opml' => array(),
- 'json_starred' => array(),
- 'json_feed' => array()
- );
+ if ($status_file !== 0) {
+ Minz_Log::error('File cannot be uploaded. Error code: ' . $status_file);
+ Minz_Request::bad(_t('file_cannot_be_uploaded'),
+ array('c' => 'importExport', 'a' => 'index'));
+ }
- // We try to list all files according to their type
- // A zip file is first opened and then its files are listed
- $list = array();
- if ($type_file === 'zip') {
- $zip = zip_open($file['tmp_name']);
+ @set_time_limit(300);
- while (($zipfile = zip_read($zip)) !== false) {
- $type_zipfile = $this->guessFileType(
- zip_entry_name($zipfile)
- );
+ $type_file = $this->guessFileType($file['name']);
+ $list_files = array(
+ 'opml' => array(),
+ 'json_starred' => array(),
+ 'json_feed' => array()
+ );
+
+ // We try to list all files according to their type
+ $list = array();
+ if ($type_file === 'zip' && extension_loaded('zip')) {
+ $zip = zip_open($file['tmp_name']);
+
+ if (!is_resource($zip)) {
+ // zip_open cannot open file: something is wrong
+ Minz_Log::error('Zip archive cannot be imported. Error code: ' . $zip);
+ Minz_Request::bad(_t('zip_error'),
+ array('c' => 'importExport', 'a' => 'index'));
+ }
+
+ while (($zipfile = zip_read($zip)) !== false) {
+ if (!is_resource($zipfile)) {
+ // zip_entry() can also return an error code!
+ Minz_Log::error('Zip file cannot be imported. Error code: ' . $zipfile);
+ } else {
+ $type_zipfile = $this->guessFileType(zip_entry_name($zipfile));
if ($type_file !== 'unknown') {
$list_files[$type_zipfile][] = zip_entry_read(
$zipfile,
@@ -54,59 +72,37 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
);
}
}
-
- zip_close($zip);
- } elseif ($type_file !== 'unknown') {
- $list_files[$type_file][] = file_get_contents(
- $file['tmp_name']
- );
- }
-
- // Import different files.
- // OPML first(so categories and feeds are imported)
- // Starred articles then so the "favourite" status is already set
- // And finally all other files.
- $error = false;
- foreach ($list_files['opml'] as $opml_file) {
- $error = $this->importOpml($opml_file);
- }
- foreach ($list_files['json_starred'] as $article_file) {
- $error = $this->importArticles($article_file, true);
- }
- foreach ($list_files['json_feed'] as $article_file) {
- $error = $this->importArticles($article_file);
}
- // And finally, we get import status and redirect to the home page
- $notif = null;
- if ($error === true) {
- $content_notif = Minz_Translate::t(
- 'feeds_imported_with_errors'
- );
- } else {
- $content_notif = Minz_Translate::t(
- 'feeds_imported'
- );
- }
-
- Minz_Session::_param('notification', array(
- 'type' => 'good',
- 'content' => $content_notif
- ));
- Minz_Session::_param('actualize_feeds', true);
+ zip_close($zip);
+ } elseif ($type_file === 'zip') {
+ // Zip extension is not loaded
+ Minz_Request::bad(_t('no_zip_extension'),
+ array('c' => 'importExport', 'a' => 'index'));
+ } elseif ($type_file !== 'unknown') {
+ $list_files[$type_file][] = file_get_contents($file['tmp_name']);
+ }
- Minz_Request::forward(array(
- 'c' => 'index',
- 'a' => 'index'
- ), true);
+ // Import file contents.
+ // OPML first(so categories and feeds are imported)
+ // Starred articles then so the "favourite" status is already set
+ // And finally all other files.
+ $error = false;
+ foreach ($list_files['opml'] as $opml_file) {
+ $error = $this->importOpml($opml_file);
+ }
+ foreach ($list_files['json_starred'] as $article_file) {
+ $error = $this->importArticles($article_file, true);
+ }
+ foreach ($list_files['json_feed'] as $article_file) {
+ $error = $this->importArticles($article_file);
}
- // What are you doing? you have to call this controller
- // with a POST request!
- Minz_Request::forward(array(
- 'c' => 'importExport',
- 'a' => 'index'
- ));
+ // And finally, we get import status and redirect to the home page
+ Minz_Session::_param('actualize_feeds', true);
+ $content_notif = $error === true ? _t('feeds_imported_with_errors') :
+ _t('feeds_imported');
+ Minz_Request::good($content_notif);
}
private function guessFileType($filename) {
@@ -120,7 +116,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
} elseif (substr_compare($filename, '.opml', -5) === 0 ||
substr_compare($filename, '.xml', -4) === 0) {
return 'opml';
- } elseif (strcmp($filename, 'starred.json') === 0) {
+ } elseif (substr_compare($filename, '.json', -5) === 0 &&
+ strpos($filename, 'starred') !== false) {
return 'json_starred';
} elseif (substr_compare($filename, '.json', -5) === 0 &&
strpos($filename, 'feed_') === 0) {
@@ -176,15 +173,15 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
}
// We get different useful information
- $url = html_chars_utf8($feed_elt['xmlUrl']);
- $name = html_chars_utf8($feed_elt['text']);
+ $url = Minz_Helper::htmlspecialchars_utf8($feed_elt['xmlUrl']);
+ $name = Minz_Helper::htmlspecialchars_utf8($feed_elt['text']);
$website = '';
if (isset($feed_elt['htmlUrl'])) {
- $website = html_chars_utf8($feed_elt['htmlUrl']);
+ $website = Minz_Helper::htmlspecialchars_utf8($feed_elt['htmlUrl']);
}
$description = '';
if (isset($feed_elt['description'])) {
- $description = html_chars_utf8($feed_elt['description']);
+ $description = Minz_Helper::htmlspecialchars_utf8($feed_elt['description']);
}
$error = false;
@@ -210,7 +207,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
private function addCategoryOpml($cat_elt, $parent_cat) {
// Create a new Category object
- $cat = new FreshRSS_Category(html_chars_utf8($cat_elt['text']));
+ $cat = new FreshRSS_Category(Minz_Helper::htmlspecialchars_utf8($cat_elt['text']));
$id = $this->catDAO->addCategoryObject($cat);
$error = ($id === false);
@@ -287,7 +284,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$url = $origin[$key];
$name = $origin['title'];
$website = $origin['htmlUrl'];
- $error = false;
+
try {
// Create a Feed object and add it in DB
$feed = new FreshRSS_Feed($url);
@@ -311,61 +308,53 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
}
public function exportAction() {
- if (Minz_Request::isPost()) {
- $this->view->_useLayout(false);
+ if (!Minz_Request::isPost()) {
+ Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
+ }
- $export_opml = Minz_Request::param('export_opml', false);
- $export_starred = Minz_Request::param('export_starred', false);
- $export_feeds = Minz_Request::param('export_feeds', array ());
+ $this->view->_useLayout(false);
- $export_files = array ();
- if ($export_opml) {
- $export_files['feeds.opml'] = $this->generateOpml();
- }
+ $export_opml = Minz_Request::param('export_opml', false);
+ $export_starred = Minz_Request::param('export_starred', false);
+ $export_feeds = Minz_Request::param('export_feeds', array());
- if ($export_starred) {
- $export_files['starred.json'] = $this->generateArticles('starred');
- }
+ $export_files = array();
+ if ($export_opml) {
+ $export_files['feeds.opml'] = $this->generateOpml();
+ }
- foreach ($export_feeds as $feed_id) {
- $feed = $this->feedDAO->searchById($feed_id);
- if ($feed) {
- $filename = 'feed_' . $feed->category() . '_'
- . $feed->id() . '.json';
- $export_files[$filename] = $this->generateArticles(
- 'feed', $feed
- );
- }
- }
+ if ($export_starred) {
+ $export_files['starred.json'] = $this->generateArticles('starred');
+ }
- $nb_files = count($export_files);
- if ($nb_files > 1) {
- // If there are more than 1 file to export, we need an .zip
- try {
- $this->exportZip($export_files);
- } catch (Exception $e) {
- # Oops, there is no Zip extension!
- $notif = array(
- 'type' => 'bad',
- 'content' => _t('export_no_zip_extension')
- );
- Minz_Session::_param('notification', $notif);
- Minz_Request::forward(array('c' => 'importExport'), true);
- }
- } elseif ($nb_files === 1) {
- // Only one file? Guess its type and export it.
- $filename = key($export_files);
- $type = null;
- if (substr_compare($filename, '.opml', -5) === 0) {
- $type = "text/xml";
- } elseif (substr_compare($filename, '.json', -5) === 0) {
- $type = "text/json";
- }
+ foreach ($export_feeds as $feed_id) {
+ $feed = $this->feedDAO->searchById($feed_id);
+ if ($feed) {
+ $filename = 'feed_' . $feed->category() . '_'
+ . $feed->id() . '.json';
+ $export_files[$filename] = $this->generateArticles(
+ 'feed', $feed
+ );
+ }
+ }
- $this->exportFile($filename, $export_files[$filename], $type);
- } else {
- Minz_Request::forward(array('c' => 'importExport'), true);
+ $nb_files = count($export_files);
+ if ($nb_files > 1) {
+ // If there are more than 1 file to export, we need a zip archive.
+ try {
+ $this->exportZip($export_files);
+ } catch (Exception $e) {
+ # Oops, there is no Zip extension!
+ Minz_Request::bad(_t('export_no_zip_extension'),
+ array('c' => 'importExport', 'a' => 'index'));
}
+ } elseif ($nb_files === 1) {
+ // Only one file? Guess its type and export it.
+ $filename = key($export_files);
+ $type = $this->guessFileType($filename);
+ $this->exportFile('freshrss_' . $filename, $export_files[$filename], $type);
+ } else {
+ Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
}
}
@@ -384,7 +373,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$this->view->categories = $this->catDAO->listCategories();
if ($type == 'starred') {
- $this->view->list_title = Minz_Translate::t('starred_list');
+ $this->view->list_title = _t('starred_list');
$this->view->type = 'starred';
$unread_fav = $this->entryDAO->countUnreadReadFavorites();
$this->view->entries = $this->entryDAO->listWhere(
@@ -392,9 +381,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$unread_fav['all']
);
} elseif ($type == 'feed' && !is_null($feed)) {
- $this->view->list_title = Minz_Translate::t(
- 'feed_list', $feed->name()
- );
+ $this->view->list_title = _t('feed_list', $feed->name());
$this->view->type = 'feed/' . $feed->id();
$this->view->entries = $this->entryDAO->listWhere(
'f', $feed->id(), FreshRSS_Entry::STATE_ALL, 'ASC',
@@ -430,11 +417,18 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
}
private function exportFile($filename, $content, $type) {
- if (is_null($type)) {
+ if ($type === 'unknown') {
return;
}
- header('Content-Type: ' . $type . '; charset=utf-8');
+ $content_type = '';
+ if ($type === 'opml') {
+ $content_type = "text/opml";
+ } elseif ($type === 'json_feed' || $type === 'json_starred') {
+ $content_type = "text/json";
+ }
+
+ header('Content-Type: ' . $content_type . '; charset=utf-8');
header('Content-disposition: attachment; filename=' . $filename);
print($content);
}
diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php
index 3119073b8..b0b051119 100755
--- a/app/Controllers/indexController.php
+++ b/app/Controllers/indexController.php
@@ -76,14 +76,14 @@ class FreshRSS_index_Controller extends Minz_ActionController {
);
// On récupère les différents éléments de filtrage
- $this->view->state = $state = Minz_Request::param ('state', $this->view->conf->default_view);
+ $this->view->state = Minz_Request::param('state', $this->view->conf->default_view);
$state_param = Minz_Request::param ('state', null);
$filter = Minz_Request::param ('search', '');
$this->view->order = $order = Minz_Request::param ('order', $this->view->conf->sort_order);
$nb = Minz_Request::param ('nb', $this->view->conf->posts_per_page);
$first = Minz_Request::param ('next', '');
- if ($state === FreshRSS_Entry::STATE_NOT_READ) { //Any unread article in this category at all?
+ if ($this->view->state === FreshRSS_Entry::STATE_NOT_READ) { //Any unread article in this category at all?
switch ($getType) {
case 'a':
$hasUnread = $this->view->nb_not_read > 0;
@@ -104,7 +104,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
break;
}
if (!$hasUnread && ($state_param === null)) {
- $this->view->state = $state = FreshRSS_Entry::STATE_ALL;
+ $this->view->state = FreshRSS_Entry::STATE_ALL;
}
}
@@ -117,11 +117,11 @@ class FreshRSS_index_Controller extends Minz_ActionController {
$keepHistoryDefault = $this->view->conf->keep_history_default;
try {
- $entries = $entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter, $date_min, true, $keepHistoryDefault);
+ $entries = $entryDAO->listWhere($getType, $getId, $this->view->state, $order, $nb + 1, $first, $filter, $date_min, true, $keepHistoryDefault);
// Si on a récupéré aucun article "non lus"
// on essaye de récupérer tous les articles
- if ($state === FreshRSS_Entry::STATE_NOT_READ && empty($entries) && ($state_param === null) && ($filter == '')) {
+ if ($this->view->state === FreshRSS_Entry::STATE_NOT_READ && empty($entries) && ($state_param === null) && ($filter == '')) {
Minz_Log::record('Conflicting information about nbNotRead!', Minz_Log::DEBUG);
$feedDAO = FreshRSS_Factory::createFeedDao();
try {
@@ -132,6 +132,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
$this->view->state = FreshRSS_Entry::STATE_ALL;
$entries = $entryDAO->listWhere($getType, $getId, $this->view->state, $order, $nb, $first, $filter, $date_min, true, $keepHistoryDefault);
}
+ Minz_Request::_param('state', $this->view->state);
if (count($entries) <= $nb) {
$this->view->nextId = '';
@@ -295,6 +296,41 @@ class FreshRSS_index_Controller extends Minz_ActionController {
Minz_Session::_param('passwordHash');
}
+ private static function makeLongTermCookie($username, $passwordHash) {
+ do {
+ $token = sha1(Minz_Configuration::salt() . $username . uniqid(mt_rand(), true));
+ $tokenFile = DATA_PATH . '/tokens/' . $token . '.txt';
+ } while (file_exists($tokenFile));
+ if (@file_put_contents($tokenFile, $username . "\t" . $passwordHash) === false) {
+ return false;
+ }
+ $expire = time() + 2629744; //1 month //TODO: Use a configuration instead
+ Minz_Session::setLongTermCookie('FreshRSS_login', $token, $expire);
+ Minz_Session::_param('token', $token);
+ return $token;
+ }
+
+ private static function deleteLongTermCookie() {
+ Minz_Session::deleteLongTermCookie('FreshRSS_login');
+ $token = Minz_Session::param('token', null);
+ if (ctype_alnum($token)) {
+ @unlink(DATA_PATH . '/tokens/' . $token . '.txt');
+ }
+ Minz_Session::_param('token');
+ if (rand(0, 10) === 1) {
+ self::purgeTokens();
+ }
+ }
+
+ private static function purgeTokens() {
+ $oldest = time() - 2629744; //1 month //TODO: Use a configuration instead
+ foreach (new DirectoryIterator(DATA_PATH . '/tokens/') as $fileInfo) {
+ if ($fileInfo->getExtension() === 'txt' && $fileInfo->getMTime() < $oldest) {
+ @unlink($fileInfo->getPathname());
+ }
+ }
+ }
+
public function formLoginAction () {
if (Minz_Request::isPost()) {
$ok = false;
@@ -312,6 +348,11 @@ class FreshRSS_index_Controller extends Minz_ActionController {
if ($ok) {
Minz_Session::_param('currentUser', $username);
Minz_Session::_param('passwordHash', $s);
+ if (Minz_Request::param('keep_logged_in', false)) {
+ self::makeLongTermCookie($username, $s);
+ } else {
+ self::deleteLongTermCookie();
+ }
} else {
Minz_Log::record('Password mismatch for user ' . $username . ', nonce=' . $nonce . ', c=' . $c, Minz_Log::WARNING);
}
@@ -371,6 +412,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
Minz_Session::_param('currentUser');
Minz_Session::_param('mail');
Minz_Session::_param('passwordHash');
+ self::deleteLongTermCookie();
Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true);
}
}
diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php
index 06a20c2a6..000b41dd2 100644
--- a/app/Controllers/statsController.php
+++ b/app/Controllers/statsController.php
@@ -58,15 +58,20 @@ class FreshRSS_stats_Controller extends Minz_ActionController {
public function repartitionAction() {
$statsDAO = FreshRSS_Factory::createStatsDAO();
+ $categoryDAO = new FreshRSS_CategoryDAO();
$feedDAO = FreshRSS_Factory::createFeedDao();
Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
$id = Minz_Request::param ('id', null);
+ $this->view->categories = $categoryDAO->listCategories();
$this->view->feed = $feedDAO->searchById($id);
$this->view->days = $statsDAO->getDays();
$this->view->months = $statsDAO->getMonths();
$this->view->repartitionHour = $statsDAO->calculateEntryRepartitionPerFeedPerHour($id);
+ $this->view->averageHour = $statsDAO->calculateEntryAveragePerFeedPerHour($id);
$this->view->repartitionDayOfWeek = $statsDAO->calculateEntryRepartitionPerFeedPerDayOfWeek($id);
+ $this->view->averageDayOfWeek = $statsDAO->calculateEntryAveragePerFeedPerDayOfWeek($id);
$this->view->repartitionMonth = $statsDAO->calculateEntryRepartitionPerFeedPerMonth($id);
+ $this->view->averageMonth = $statsDAO->calculateEntryAveragePerFeedPerMonth($id);
}
public function firstAction() {
diff --git a/app/FreshRSS.php b/app/FreshRSS.php
index 7c333b090..cf6390f68 100644
--- a/app/FreshRSS.php
+++ b/app/FreshRSS.php
@@ -6,8 +6,7 @@ class FreshRSS extends Minz_FrontController {
}
$loginOk = $this->accessControl(Minz_Session::param('currentUser', ''));
$this->loadParamsView();
- if (Minz_Request::isPost() && (empty($_SERVER['HTTP_REFERER']) ||
- Minz_Request::getDomainName() !== parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST))) {
+ if (Minz_Request::isPost() && !Minz_Request::isRefererFromSameDomain()) {
$loginOk = false; //Basic protection against XSRF attacks
Minz_Error::error(
403,
@@ -20,13 +19,35 @@ class FreshRSS extends Minz_FrontController {
$this->loadNotifications();
}
+ private static function getCredentialsFromLongTermCookie() {
+ $token = Minz_Session::getLongTermCookie('FreshRSS_login');
+ if (!ctype_alnum($token)) {
+ return array();
+ }
+ $tokenFile = DATA_PATH . '/tokens/' . $token . '.txt';
+ $mtime = @filemtime($tokenFile);
+ if ($mtime + 2629744 < time()) { //1 month //TODO: Use a configuration instead
+ @unlink($tokenFile);
+ return array(); //Expired or token does not exist
+ }
+ $credentials = @file_get_contents($tokenFile);
+ return $credentials === false ? array() : explode("\t", $credentials, 2);
+ }
+
private function accessControl($currentUser) {
if ($currentUser == '') {
switch (Minz_Configuration::authType()) {
case 'form':
- $currentUser = Minz_Configuration::defaultUser();
- Minz_Session::_param('passwordHash');
- $loginOk = false;
+ $credentials = self::getCredentialsFromLongTermCookie();
+ if (isset($credentials[1])) {
+ $currentUser = trim($credentials[0]);
+ Minz_Session::_param('passwordHash', trim($credentials[1]));
+ }
+ $loginOk = $currentUser != '';
+ if (!$loginOk) {
+ $currentUser = Minz_Configuration::defaultUser();
+ Minz_Session::_param('passwordHash');
+ }
break;
case 'http_auth':
$currentUser = httpAuthUser();
diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php
index 2f47312c0..3a408faa5 100644
--- a/app/Models/Configuration.php
+++ b/app/Models/Configuration.php
@@ -17,6 +17,7 @@ class FreshRSS_Configuration {
'default_view' => FreshRSS_Entry::STATE_NOT_READ,
'auto_load_more' => true,
'display_posts' => false,
+ 'display_categories' => false,
'hide_read_feeds' => true,
'onread_jump_next' => true,
'lazyload' => true,
@@ -44,6 +45,8 @@ class FreshRSS_Configuration {
'load_more' => 'm',
'auto_share' => 's',
'focus_search' => 'a',
+ 'user_filter' => 'u',
+ 'help' => 'f1',
),
'topline_read' => true,
'topline_favorite' => true,
@@ -60,6 +63,7 @@ class FreshRSS_Configuration {
);
private $available_languages = array(
+ 'de' => 'Deutsch',
'en' => 'English',
'fr' => 'Français',
);
@@ -142,6 +146,9 @@ class FreshRSS_Configuration {
public function _display_posts ($value) {
$this->data['display_posts'] = ((bool)$value) && $value !== 'no';
}
+ public function _display_categories ($value) {
+ $this->data['display_categories'] = ((bool)$value) && $value !== 'no';
+ }
public function _hide_read_feeds($value) {
$this->data['hide_read_feeds'] = (bool)$value;
}
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index a9ffa138b..75a8aeba4 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -65,7 +65,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
}
if (!isset($existingGuids[$entry->guid()]) &&
- ($feedHistory != 0 || $eDate >= $date_min)) {
+ ($feedHistory != 0 || $eDate >= $date_min || $entry->isFavorite())) {
$values = $entry->toArray();
$useDeclaredDate = empty($existingGuids);
@@ -230,7 +230,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
}
$this->bd->beginTransaction();
- $sql = 'UPDATE `' . $this->prefix . 'entry` '
+ $sql = 'UPDATE `' . $this->prefix . 'entry` '
. 'SET is_read=1 '
. 'WHERE id_feed=? AND is_read=0 AND id <= ?';
$values = array($id, $idMax);
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index fe1e52ea2..2a5ea45ac 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -28,6 +28,12 @@ class FreshRSS_Feed extends Minz_Model {
}
}
+ public static function example() {
+ $f = new FreshRSS_Feed('http://example.net/', false);
+ $f->faviconPrepare();
+ return $f;
+ }
+
public function id() {
return $this->id;
}
diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php
index 89be76a26..bd4271ba8 100644
--- a/app/Models/StatsDAO.php
+++ b/app/Models/StatsDAO.php
@@ -152,6 +152,68 @@ SQL;
}
/**
+ * Calculates the average number of article per hour per feed
+ *
+ * @param integer $feed id
+ * @return integer
+ */
+ public function calculateEntryAveragePerFeedPerHour($feed = null) {
+ return $this->calculateEntryAveragePerFeedPerPeriod(1/24, $feed);
+ }
+
+ /**
+ * Calculates the average number of article per day of week per feed
+ *
+ * @param integer $feed id
+ * @return integer
+ */
+ public function calculateEntryAveragePerFeedPerDayOfWeek($feed = null) {
+ return $this->calculateEntryAveragePerFeedPerPeriod(7, $feed);
+ }
+
+ /**
+ * Calculates the average number of article per month per feed
+ *
+ * @param integer $feed id
+ * @return integer
+ */
+ public function calculateEntryAveragePerFeedPerMonth($feed = null) {
+ return $this->calculateEntryAveragePerFeedPerPeriod(30, $feed);
+ }
+
+ /**
+ * Calculates the average number of article per feed
+ *
+ * @param float $period number used to divide the number of day in the period
+ * @param integer $feed id
+ * @return integer
+ */
+ protected function calculateEntryAveragePerFeedPerPeriod($period, $feed = null) {
+ if ($feed) {
+ $restrict = "WHERE e.id_feed = {$feed}";
+ } else {
+ $restrict = '';
+ }
+ $sql = <<<SQL
+SELECT COUNT(1) AS count
+, MIN(date) AS date_min
+, MAX(date) AS date_max
+FROM {$this->prefix}entry AS e
+{$restrict}
+SQL;
+ $stm = $this->bd->prepare($sql);
+ $stm->execute();
+ $res = $stm->fetch(PDO::FETCH_NAMED);
+ $date_min = new \DateTime();
+ $date_min->setTimestamp($res['date_min']);
+ $date_max = new \DateTime();
+ $date_max->setTimestamp($res['date_max']);
+ $interval = $date_max->diff($date_min, true);
+
+ return round($res['count'] / ($interval->format('%a') / ($period)), 2);
+ }
+
+ /**
* Initialize an array for statistics depending on a range
*
* @param integer $min
diff --git a/app/i18n/de.php b/app/i18n/de.php
new file mode 100644
index 000000000..4301a8b6d
--- /dev/null
+++ b/app/i18n/de.php
@@ -0,0 +1,326 @@
+<?php
+
+return array (
+ // LAYOUT
+ 'login' => 'Login',
+ 'login_with_persona' => 'Login mit Persona',
+ 'logout' => 'Logout',
+ 'search' => 'Suche nach Worten oder #tags',
+ 'search_short' => 'Suche',
+
+ 'configuration' => 'Konfiguration',
+ 'users' => 'Nutzer',
+ 'categories' => 'Kategorien',
+ 'category' => 'Kategorie',
+ 'feed' => 'Feed',
+ 'feeds' => 'Feeds',
+ 'shortcuts' => 'Shortcuts',
+ 'about' => '&Uuml;ber',
+ 'stats' => 'Statistiken',
+
+ 'your_rss_feeds' => 'Ihre RSS Feeds',
+ 'add_rss_feed' => 'RSS-Feed hinzuf&uuml;gen',
+ 'no_rss_feed' => 'Kein RSS Feed',
+ 'import_export_opml' => 'Import / Export (OPML)',
+
+ 'subscription_management' => 'Abonnementsverwaltung',
+ 'main_stream' => 'Haupt-Nachrichtenflu&szlig;',
+ 'all_feeds' => 'Alle Feeds',
+ 'favorite_feeds' => 'Favoriten (%d)',
+ 'not_read' => '%d ungelesen',
+ 'not_reads' => '%d ungelesen',
+
+ 'filter' => 'Filter',
+ 'see_website' => 'Website ansehen',
+ 'administration' => 'Verwaltung',
+ 'actualize' => 'Aktualisierung',
+
+ 'mark_read' => 'Als gelesen markieren',
+ 'mark_favorite' => 'Als Favoriten markieren',
+ 'mark_all_read' => 'Alle als gelesen markieren',
+ 'mark_feed_read' => 'Feed als gelesen markieren',
+ 'mark_cat_read' => 'Kategorie als gelesen markieren',
+ 'before_one_day' => 'Vor einem Tag',
+ 'before_one_week' => 'Vor einer Woche',
+ 'display' => 'Anzeige',
+ 'normal_view' => 'Normale Anzeige',
+ 'reader_view' => 'Leseanzeige-Modus',
+ 'global_view' => 'Globale Anzeige',
+ 'rss_view' => 'RSS-Feed',
+ 'show_all_articles' => 'zeige alle Artikel',
+ 'show_not_reads' => 'zeige nicht gelesene',
+ 'show_read' => 'zeige nur gelesene',
+ 'show_favorite' => 'Favoriten anzeigen',
+ 'older_first' => '&Auml;lteste zuerst',
+ 'newer_first' => 'Neuere zuerst',
+
+ // Pagination
+ 'first' => 'Erste',
+ 'previous' => 'Vorherige',
+ 'next' => 'N&auml;chste',
+ 'last' => 'Letzte',
+
+ // CONTROLLERS
+ 'article_published_on' => 'Dieser Artikel erschien im Original bei <a href="%s">%s</a>',
+ 'article_published_on_author' => 'Dieser Artikel erschien im Original bei <a href="%s">%s</a> von %s',
+
+ 'access_denied' => 'Sie haben nicht die Berechtigung, diese Seite aufzurufen',
+ 'page_not_found' => 'Sie suchen nach einer Seite, die es nicht gibt',
+ 'error_occurred' => 'Es gab einen Fehler',
+ 'error_occurred_update' => 'Es wurde nichts ge&auml;ndert',
+
+ 'default_category' => 'Unkategorisiert',
+ 'categories_updated' => 'Kategorien wurden aktualisiert',
+ 'categories_management' => 'Kategorienverwaltung',
+ 'feed_updated' => 'Der Feed wurde aktualisiert',
+ 'rss_feed_management' => 'Verwaltung der RSS Feeds',
+ 'configuration_updated' => 'Die Konfiguration wurde aktualisiert',
+ 'sharing_management' => 'Verwaltung der Optionen f&uuml;r das Teilen',
+ 'bad_opml_file' => 'Ihre OPML-Datei ist ung&uuml;ltig',
+ 'shortcuts_updated' => 'Shortcuts wurden aktualisiert',
+ 'shortcuts_management' => 'Verwaltung der Shortcuts',
+ 'shortcuts_navigation' => 'Navigation',
+ 'shortcuts_navigation_help' => 'Mit der "Shift" Taste gelten die Navigations-Shortcuts f&uuml;r Feeds.<br/>Mit der "Alt" Taste gelten die Navigations-Shortcuts f&uuml;r Kategorien.',
+ 'shortcuts_article_action' => 'Artikelaktionen',
+ 'shortcuts_other_action' => 'Andere Aktionen',
+ 'feeds_marked_read' => 'Die Feeds wurden als gelesen markiert',
+ 'updated' => 'Die &Auml;nderungen wurden aktualisiert',
+
+ 'already_subscribed' => 'Sie haben bereits <em>%s</em> abonniert',
+ 'feed_added' => 'Der RSS Feed <em>%s</em> wurde hinzugef&uuml;gt',
+ 'feed_not_added' => '<em>%s</em> konnte nicht hinzugef&uuml;gt werden',
+ 'internal_problem_feed' => 'Der RSS Feed konnte nicht hinzugef&uuml;gt werden. &uuml;berpr&uuml;fen Sie die Protokolldateien von FressRSS f&uuml;r weitere Informationen.',
+ 'invalid_url' => 'URL <em>%s</em> ist ung&uuml;ltig',
+ 'feed_actualized' => '<em>%s</em> wurde aktualisiert',
+ 'n_feeds_actualized' => '%d Feeds wurden aktualisiert',
+ 'feeds_actualized' => 'RSS Feeds wurden aktualisiert',
+ 'no_feed_actualized' => 'Es wurden keine RSS Feeds aktualisiert',
+ 'n_entries_deleted' => '%d Artikel wurden gel&ouml;scht',
+ 'feeds_imported_with_errors' => 'Ihre Feeds wurden importiert, es gab aber einige Fehler',
+ 'feeds_imported' => 'Ihre Feeds wurden importiert und werden jetzt aktualisiert',
+ 'category_emptied' => 'Die Kategorie wurde geleert',
+ 'feed_deleted' => 'Der Feed wurde gel&ouml;scht',
+ 'feed_validator' => '&Üuml;berpr&uuml;fen Sie die G&uuml;ltigkeit des Feeds',
+
+ 'optimization_complete' => 'Die Optimierung ist beendet',
+
+ 'your_rss_feeds' => 'Ihre RSS Feeds',
+ 'your_favorites' => 'Ihre Favoriten',
+ 'public' => '&Ouml;ffentlich',
+ 'invalid_login' => 'Das Login ist ung&uuml;ltig',
+
+ // VIEWS
+ 'save' => 'Speichern',
+ 'delete' => 'L&ouml;schen',
+ 'cancel' => 'Abbrechen',
+
+ 'back_to_rss_feeds' => '← Zur&uuml;ck zu den RSS Feeds gehen',
+ 'feeds_moved_category_deleted' => 'Wenn Sie eine Kategorie l&ouml;schen, werden deren Feeds automatisch in die Kategorie <em>%s</em> eingef&uuml;gt.',
+ 'category_number' => 'Kategorie n°%d',
+ 'ask_empty' => 'Leeren?',
+ 'number_feeds' => '%d Feeds',
+ 'can_not_be_deleted' => 'Kann nicht gel&ouml;scht werden',
+ 'add_category' => 'F&uuml;ge eine Kategorie hinzu',
+ 'new_category' => 'Neue Kategorie',
+
+ 'javascript_for_shortcuts' => 'JavaScript muss erm&ouml;glicht werden, wenn Shortcuts verwendet werden sollen',
+ 'javascript_should_be_activated'=> 'JavaScript muss erm&ouml;glicht werden',
+ 'shift_for_all_read' => '+ <code>shift</code> um alle Artikel als gelesen zu markieren',
+ 'see_on_website' => 'Auf der Originalwebseite anschauen',
+ 'next_article' => 'Zum n&auml;chsten Artikel springen',
+ 'last_article' => 'Zum letzten Artikel springen',
+ 'previous_article' => 'Zum vorherigen Artikel springen',
+ 'first_article' => 'Zum ersten Artikel springen',
+ 'next_page' => 'Zur n&auml;chsten Seite springen',
+ 'previous_page' => 'Zur vorherigen Seite springen',
+ 'collapse_article' => 'Zusammenfalten',
+ 'auto_share' => 'Teilen',
+ 'auto_share_help' => 'Wenn es nur eine Option zum Teilen gibt, wird die verwendet. Ansonsten werden die Optionen &uuml;ber die Nummer ausgew&auml;hlt.',
+
+ 'file_to_import' => 'Datei zum importieren',
+ 'import' => 'Import',
+ 'export' => 'Export',
+ 'or' => 'oder',
+
+ 'informations' => 'Information',
+ 'damn' => 'Verdammt!',
+ 'feed_in_error' => 'Dieser Feed hat ein Problem verursacht. Bitte stellen Sie sicher, dass er immer lesbar ist und aktualisieren Sie ihn dann.',
+ 'feed_empty' => 'Dieser Feed ist leer. Bitte stellen Sie sicher, dass er noch gepflegt wird.',
+ 'feed_description' => 'Beschreibung',
+ 'website_url' => 'Webseiten-Adresse URL',
+ 'feed_url' => 'Feed URL',
+ 'articles' => 'Artikel',
+ 'number_articles' => 'Anzahl der Artikel',
+ 'by_feed' => 'per Feed',
+ 'by_default' => 'Als Vorgabe',
+ 'keep_history' => 'Kleinste Anzahl der Artikel, die behalten werden',
+ 'categorize' => 'In einer Kategorie speichern',
+ 'truncate' => 'Alle Artikel l&ouml;schen',
+ 'advanced' => 'Erweitert',
+ 'show_in_all_flux' => 'Im Hauptstrom anzeigen',
+ 'yes' => 'Ja',
+ 'no' => 'Nein',
+ 'css_path_on_website' => 'Pfad zur CSS-Datei des Artikels auf der Original Webseite',
+ 'retrieve_truncated_feeds' => 'Gek&uuml;rzte RSS Feeds abrufen (Achtung, ben&ouml;tigt mehr Zeit!)',
+ 'http_authentication' => 'HTTP Authentifizierung',
+ 'http_username' => 'HTTP Nutzername',
+ 'http_password' => 'HTTP Passwort',
+ 'blank_to_disable' => 'Zum Ausschalten frei lassen',
+ 'not_yet_implemented' => 'Noch nicht implementiert',
+ 'access_protected_feeds' => 'Die Verbindung erlaubt Zugriff zu HTTP-gesch&uuml;tzten RSS Feeds',
+ 'no_selected_feed' => 'Kein Feed ausgew&auml;hlt.',
+ 'think_to_add' => '<a href="./?c=configure&amp;a=feed">Sie k&ouml;nnen Feeds hinzuf&uuml;gen</a>.',
+
+ 'current_user' => 'Aktuelle Nutzung',
+ 'default_user' => 'Nutzername des Standardnutzers <small>(maximal 16 Zeichen - alphanumerisch)</small>',
+ 'password_form' => 'Passwort<br /><small>(f&uuml;r die Anmeldemethode per Webformular)</small>',
+ 'persona_connection_email' => 'Login E-Mail Adresse<br /><small>(f&uuml;r <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
+ 'allow_anonymous' => 'Anonymes lesen der Artikel des Standardnutzers (%s) wird erlaubt',
+ 'allow_anonymous_refresh' => 'Aktualisieren der Artikel wird anonymen Nutzern erlaubt',
+ 'auth_token' => 'Authentifizierungs-Token',
+ 'explain_token' => 'Erlaube den Zugriff auf die RSS-Ausgabe des Standardnutzers ohne Authentifizierung.<br /><kbd>%s?output=rss&token=%s</kbd>',
+ 'login_configuration' => 'Login',
+ 'is_admin' => 'ist Administrator',
+ 'auth_type' => 'Authentifizierungsmethode',
+ 'auth_none' => 'Keine (gef&auml;hrlich)',
+ 'auth_form' => 'Webformular (traditionell, JavaScript wird ben&ouml;tigt)',
+ 'http_auth' => 'HTTP (mit HTTPS f&uuml;r erfahrene Nutzer)',
+ 'auth_persona' => 'Mozilla Persona (modern, JavaScript wird ben&ouml;tigt)',
+ 'users_list' => 'Liste der Nutzer',
+ 'create_user' => 'Neuen Nutzer erstellen',
+ 'username' => 'Nutzername',
+ 'password' => 'Passwort',
+ 'create' => 'Erstellen',
+ 'user_created' => 'Nutzer %s wurde erstellt',
+ 'user_deleted' => 'Nutzer %s wurde gel&ouml;scht',
+
+ 'language' => 'Sprache',
+ 'month' => 'Monate',
+ 'archiving_configuration' => 'Archivieren',
+ 'delete_articles_every' => 'Entfernen von Artikeln nach',
+ 'purge_now' => 'Jetzt bereinigen',
+ 'purge_completed' => 'Die Bereinigung ist abgeschlossen (%d Artikel wurden gel&ouml;scht)',
+ 'archiving_configuration_help' => 'Es gibt weitere Optionen bei den Einstellungen der individuellen Nachrichtenstr&ouml;me',
+ 'reading_configuration' => 'Lesen',
+ 'articles_per_page' => 'Anzahl der Artikel pro Seite',
+ 'default_view' => 'Standard-Ansicht',
+ 'sort_order' => 'Sortierreihenfolge',
+ 'auto_load_more' => 'Die n&auml;chsten Artikel am Seitenende laden',
+ 'display_articles_unfolded' => 'Die Artikel als Standard zusammen gefaltet anzeigen',
+ 'after_onread' => 'Nach “als gelesen markieren”',
+ 'jump_next' => 'springe zum n&auml;chsten ungelesenen Geschwisterelement (Feed oder Kategorie)',
+ 'reading_icons' => 'Lese Symbol',
+ 'top_line' => 'Kopfzeile',
+ 'bottom_line' => 'Fusszeile',
+ 'img_with_lazyload' => 'Verwende die "tr&auml;ge laden" Methode zum laden von Bildern',
+ 'auto_read_when' => 'Artikel als gelesen markieren…',
+ 'article_selected' => 'wenn der Artikel ausgew&auml;hlt ist',
+ 'article_open_on_website' => 'wenn der Artikel auf der Originalwebseite ge&ouml;ffnet ist',
+ 'scroll' => 'w&auml;hrend des Seiten-Scrollens',
+ 'upon_reception' => 'beim Empfang des Artikels',
+ 'your_shaarli' => 'Ihr Shaarli',
+ 'your_wallabag' => 'Ihr wallabag',
+ 'your_diaspora_pod' => 'Ihr Diaspora* pod',
+ 'sharing' => 'Teilen',
+ 'share' => 'teile',
+ 'by_email' => 'Per E-Mail',
+ 'optimize_bdd' => 'Datenbank optimieren',
+ 'optimize_todo_sometimes' => 'Sollte gelegentlich gemacht werden, um die Gr&ouml;ße der Datenbank zu reduzieren',
+ 'theme' => 'Thema',
+ 'more_information' => 'Weitere Informationen',
+ 'activate_sharing' => 'Teilen aktivieren',
+ 'shaarli' => 'Shaarli',
+ 'wallabag' => 'wallabag',
+ 'diaspora' => 'Diaspora*',
+ 'twitter' => 'Twitter',
+ 'g+' => 'Google+',
+ 'facebook' => 'Facebook',
+ 'email' => 'E-Mail',
+ 'print' => 'Drucken',
+
+ 'article' => 'Artikel',
+ 'title' => 'Titel',
+ 'author' => 'Autor',
+ 'publication_date' => 'Datum der Ver&ouml;ffentlichung',
+ 'by' => 'von',
+
+ 'load_more' => 'Weitere Artikel laden',
+ 'nothing_to_load' => 'Es gibt keine weiteren Artikel',
+
+ 'rss_feeds_of' => 'RSS Feed von %s',
+
+ 'refresh' => 'Aktualisieren',
+ 'no_feed_to_refresh' => 'Es gibt keinen Feed zum aktualisieren',
+
+ 'today' => 'Heute',
+ 'yesterday' => 'Gestern',
+ 'before_yesterday' => 'vor Gestern',
+ 'new_article' => 'Es gibt neue Artikel. Bitte klicken Sie hier, um die Seite erneut zu laden.',
+ 'by_author' => 'Von <em>%s</em>',
+ 'related_tags' => 'Verwandte tags',
+ 'no_feed_to_display' => 'Es gibt keinen Artikel zum anzeigen.',
+
+ 'about_freshrss' => '&Uuml;ber FreshRSS',
+ 'project_website' => 'Projekt Webseite',
+ 'lead_developer' => 'Hauptentwickler',
+ 'website' => 'Webseite',
+ 'bugs_reports' => 'Fehlerberichte',
+ 'github_or_email' => '<a href="https://github.com/marienfressinaud/FreshRSS/issues">auf Github</a> oder <a href="mailto:dev@marienfressinaud.fr">per Mail</a>',
+ 'license' => 'Lizenz',
+ 'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>',
+ 'freshrss_description' => 'FreshRSS ist ein RSS Feedsaggregator zum selbst hosten wie zum Beispiel <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> oder <a href="http://projet.idleman.fr/leed/">Leed</a>. Es ist leicht und einfach zu handhaben und gleichzeitig ein leistungsstark und konfigurierbares Werkzeug.',
+ 'credits' => 'Credits',
+ 'credits_content' => 'Einige Designelemente sind von <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> obwohl FreshRSS dieses Framework nicht nutzt. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">Icons</a> sind vom <a href="https://www.gnome.org/">GNOME Projekt</a>. <em>Open Sans</em> Font police wurde von <a href="https://www.google.com/webfonts/specimen/Open+Sans">Steve Matteson erstellt</a>. Favicons wurden mit <a href="https://getfavicon.appspot.com/">getFavicon API gesammelt</a>. FreshRSS basiert auf <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, einem PHP Framework.',
+ 'version' => 'Version',
+
+ 'logs' => 'Protokolle',
+ 'logs_empty' => 'Die Protokolldatei ist leer',
+ 'clear_logs' => 'Protokolldateien leeren',
+
+ 'forbidden_access' => 'Der Zugriff ist verboten!',
+ 'login_required' => 'Das Login ist n&ouml;tig:',
+
+ 'confirm_action' => 'Sind Sie sicher, dass Sie diese Aktion durchf&uuml;hren wollen? Die Aktion kann nicht abgebrochen werden!',
+
+ // DATE
+ 'january' => 'januar',
+ 'february' => 'februar',
+ 'march' => 'm&auml;rz',
+ 'april' => 'april',
+ 'may' => 'mai',
+ 'june' => 'juni',
+ 'july' => 'juli',
+ 'august' => 'august',
+ 'september' => 'september',
+ 'october' => 'oktober',
+ 'november' => 'november',
+ 'december' => 'dezember',
+ // special format for date() function
+ 'Jan' => '\J\a\n\u\a\r',
+ 'Feb' => '\F\e\b\r\u\a\r',
+ 'Mar' => '\M\a\e\r\z',
+ 'Apr' => '\A\p\r\i\l',
+ 'May' => '\M\a\i',
+ 'Jun' => '\J\u\n\i',
+ 'Jul' => '\J\u\l\i',
+ 'Aug' => '\A\u\g\u\s\t',
+ 'Sep' => '\S\e\p\t\e\m\b\e\r',
+ 'Oct' => '\O\k\t\o\b\e\r',
+ 'Nov' => '\N\o\v\e\m\b\e\r',
+ 'Dec' => '\D\e\z\e\m\b\e\r',
+ // format for date() function, %s allows to indicate month in letter
+ 'format_date' => 'd\.\ %s Y',
+ 'format_date_hour' => 'd\.\ %s Y \u\m H\:i',
+
+ 'status_favorites' => 'Favoriten',
+ 'status_read' => 'Gelesen',
+ 'status_unread' => 'Ungelesen',
+ 'status_total' => 'Gesamt',
+
+ 'stats_entry_repartition' => 'Verteilung der Eintr&auml;ge',
+ 'stats_entry_per_day' => 'Eintr&auml;ge pro Tag (w&auml;hrend der letzten 30 Tage)',
+ 'stats_feed_per_category' => 'Feeds pro Kategorie',
+ 'stats_entry_per_category' => 'Eintr&auml;ge pro Kategorie',
+ 'stats_top_feed' => 'Top 10 Feeds',
+ 'stats_entry_count' => 'Z&auml;hler f&uuml;r Eintr&auml;ge',
+);
diff --git a/app/i18n/en.php b/app/i18n/en.php
index 6a0b4a139..95356af2c 100644
--- a/app/i18n/en.php
+++ b/app/i18n/en.php
@@ -3,6 +3,7 @@
return array (
// LAYOUT
'login' => 'Login',
+ 'keep_logged_in' => 'Keep me logged in <small>(1 month)</small>',
'login_with_persona' => 'Login with Persona',
'logout' => 'Logout',
'search' => 'Search words or #tags',
@@ -179,9 +180,16 @@ return array (
'auto_share' => 'Share',
'auto_share_help' => 'If there is only one sharing mode, it is used. Else modes are accessible by their number.',
'focus_search' => 'Access search box',
+ 'user_filter' => 'Access user filters',
+ 'user_filter_help' => 'If there is only one user filter, it is used. Else filters are accessible by their number.',
+ 'help' => 'Display documentation',
'file_to_import' => 'File to import<br />(OPML, Json or Zip)',
+ 'file_to_import_no_zip' => 'File to import<br />(OPML or Json)',
'import' => 'Import',
+ 'file_cannot_be_uploaded' => 'File cannot be uploaded!',
+ 'zip_error' => 'An error occured during Zip import.',
+ 'no_zip_extension' => 'Zip extension is not present on your server.',
'export' => 'Export',
'export_opml' => 'Export list of feeds (OPML)',
'export_starred' => 'Export your favourites',
@@ -263,6 +271,7 @@ return array (
'sort_order' => 'Sort order',
'auto_load_more' => 'Load next articles at the page bottom',
'display_articles_unfolded' => 'Show articles unfolded by default',
+ 'display_categories_unfolded' => 'Show categories folded by default',
'hide_read_feeds' => 'Hide categories &amp; feeds with no unread article (only in “unread articles” display mode)',
'after_onread' => 'After “mark all as read”,',
'jump_next' => 'jump to next unread sibling (feed or category)',
diff --git a/app/i18n/fr.php b/app/i18n/fr.php
index d0637b9f7..8437e872e 100644
--- a/app/i18n/fr.php
+++ b/app/i18n/fr.php
@@ -3,6 +3,7 @@
return array (
// LAYOUT
'login' => 'Connexion',
+ 'keep_logged_in' => 'Rester connecté <small>(1 mois)</small>',
'login_with_persona' => 'Connexion avec Persona',
'logout' => 'Déconnexion',
'search' => 'Rechercher des mots ou des #tags',
@@ -179,9 +180,16 @@ return array (
'auto_share' => 'Partager',
'auto_share_help' => 'S’il n’y a qu’un mode de partage, celui ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.',
'focus_search' => 'Accéder à la recherche',
+ 'user_filter' => 'Accéder aux filtres utilisateur',
+ 'user_filter_help' => 'S’il n’y a qu’un filtre utilisateur, celui ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.',
+ 'help' => 'Afficher la documentation',
'file_to_import' => 'Fichier à importer<br />(OPML, Json ou Zip)',
+ 'file_to_import_no_zip' => 'Fichier à importer<br />(OPML ou Json)',
'import' => 'Importer',
+ 'file_cannot_be_uploaded' => 'Le fichier ne peut pas être téléchargé!',
+ 'zip_error' => 'Une erreur est survenue durant l’import du fichier Zip.',
+ 'no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur.',
'export' => 'Exporter',
'export_opml' => 'Exporter la liste des flux (OPML)',
'export_starred' => 'Exporter les favoris',
@@ -263,6 +271,7 @@ return array (
'sort_order' => 'Ordre de tri',
'auto_load_more' => 'Charger les articles suivants en bas de page',
'display_articles_unfolded' => 'Afficher les articles dépliés par défaut',
+ 'display_categories_unfolded' => 'Afficher les catégories pliées par défaut',
'hide_read_feeds' => 'Cacher les catégories &amp; flux sans article non-lu (uniquement en affichage “articles non lus”)',
'after_onread' => 'Après “marquer tout comme lu”,',
'jump_next' => 'sauter au prochain voisin non lu (flux ou catégorie)',
diff --git a/app/layout/aside_flux.phtml b/app/layout/aside_flux.phtml
index aee8f8754..357aa1cd3 100644
--- a/app/layout/aside_flux.phtml
+++ b/app/layout/aside_flux.phtml
@@ -42,15 +42,19 @@
$feeds = $cat->feeds ();
if (!empty ($feeds)) {
$c_active = false;
+ $c_show = false;
if ($this->get_c == $cat->id ()) {
$c_active = true;
+ if (!$this->conf->display_categories || $this->get_f) {
+ $c_show = true;
+ }
}
?><li data-unread="<?php echo $cat->nbNotRead(); ?>"<?php if ($c_active) echo ' class="active"'; ?>><?php
?><div class="category stick<?php echo $c_active ? ' active' : ''; ?>"><?php
?><a data-unread="<?php echo formatNumber($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
+ ?><a class="btn dropdown-toggle" href="#"><?php echo FreshRSS_Themes::icon($c_show ? 'up' : 'down'); ?></a><?php
?></div><?php
- ?><ul class="feeds<?php echo $c_active ? ' active' : ''; ?>"><?php
+ ?><ul class="feeds<?php echo $c_show ? ' active' : ''; ?>"><?php
foreach ($feeds as $feed) {
$feed_id = $feed->id ();
$nbEntries = $feed->nbEntries ();
diff --git a/app/layout/nav_menu.phtml b/app/layout/nav_menu.phtml
index 73a921c5d..7cd15c1a3 100644
--- a/app/layout/nav_menu.phtml
+++ b/app/layout/nav_menu.phtml
@@ -96,7 +96,7 @@
<li class="dropdown-header"><?php echo Minz_Translate::t('queries'); ?> <a class="no-mobile" href="<?php echo _url('configure', 'queries'); ?>"><?php echo FreshRSS_Themes::icon('configure'); ?></a></li>
<?php foreach ($this->conf->queries as $query) { ?>
- <li class="item">
+ <li class="item query">
<a href="<?php echo $query['url']; ?>"><?php echo $query['name']; ?></a>
</li>
<?php } ?>
@@ -164,11 +164,15 @@
break;
}
}
- if ($this->order === 'ASC') {
- $idMax = 0;
- } else {
- $p = isset($this->entries[0]) ? $this->entries[0] : null;
- $idMax = $p === null ? '0' : $p->id();
+
+ $p = isset($this->entries[0]) ? $this->entries[0] : null;
+ $idMax = $p === null ? (time() - 1) . '000000' : $p->id();
+
+ if ($this->order === 'ASC') { //In this case we do not know but we guess idMax
+ $idMax2 = (time() - 1) . '000000';
+ if (strcmp($idMax2, $idMax) > 0) {
+ $idMax = $idMax2;
+ }
}
$arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('get' => $get, 'nextGet' => $nextGet, 'idMax' => $idMax));
diff --git a/app/views/configure/categorize.phtml b/app/views/configure/categorize.phtml
index 9bae99b39..2f0e554ca 100644
--- a/app/views/configure/categorize.phtml
+++ b/app/views/configure/categorize.phtml
@@ -18,6 +18,9 @@
<input type="text" id="cat_<?php echo $cat->id (); ?>" name="categories[]" value="<?php echo $cat->name (); ?>" />
<?php if ($cat->nbFeed () > 0) { ?>
+ <a class="btn" href="<?php echo _url('index', 'index', 'get', 'c_' . $cat->id ()); ?>">
+ <?php echo _i('link'); ?>
+ </a>
<button type="submit" class="btn btn-attention confirm" formaction="<?php echo _url ('feed', 'delete', 'id', $cat->id (), 'type', 'category'); ?>"><?php echo Minz_Translate::t ('ask_empty'); ?></button>
<?php } ?>
</div>
diff --git a/app/views/configure/reading.phtml b/app/views/configure/reading.phtml
index e96bcea42..5a26501a4 100644
--- a/app/views/configure/reading.phtml
+++ b/app/views/configure/reading.phtml
@@ -63,6 +63,16 @@
<div class="form-group">
<div class="group-controls">
+ <label class="checkbox" for="display_categories">
+ <input type="checkbox" name="display_categories" id="display_categories" value="1"<?php echo $this->conf->display_categories ? ' checked="checked"' : ''; ?> />
+ <?php echo Minz_Translate::t ('display_categories_unfolded'); ?>
+ <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
+ </label>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="group-controls">
<label class="checkbox" for="sticky_post">
<input type="checkbox" name="sticky_post" id="sticky_post" value="1"<?php echo $this->conf->sticky_post ? ' checked="checked"' : ''; ?> />
<?php echo Minz_Translate::t ('sticky_post'); ?>
diff --git a/app/views/configure/shortcut.phtml b/app/views/configure/shortcut.phtml
index bfb13f003..a4029b676 100644
--- a/app/views/configure/shortcut.phtml
+++ b/app/views/configure/shortcut.phtml
@@ -103,6 +103,21 @@
</div>
</div>
+ <div class="form-group">
+ <label class="group-name" for="user_filter_shortcut"><?php echo Minz_Translate::t ('user_filter'); ?></label>
+ <div class="group-controls">
+ <input type="text" id="user_filter_shortcut" name="shortcuts[user_filter]" list="keys" value="<?php echo $s['user_filter']; ?>" />
+ <?php echo Minz_Translate::t ('user_filter_help'); ?>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <label class="group-name" for="help_shortcut"><?php echo Minz_Translate::t ('help'); ?></label>
+ <div class="group-controls">
+ <input type="text" id="help_shortcut" name="shortcuts[help]" list="keys" value="<?php echo $s['help']; ?>" />
+ </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>
diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml
index 7144c519a..2144f1576 100644
--- a/app/views/helpers/javascript_vars.phtml
+++ b/app/views/helpers/javascript_vars.phtml
@@ -4,7 +4,8 @@ echo '"use strict";', "\n";
$mark = $this->conf->mark_when;
echo 'var ',
- 'hide_posts=', ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'false' : 'true',
+ 'help_url="', FRESHRSS_WIKI, '"',
+ ',hide_posts=', ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'false' : 'true',
',display_order="', Minz_Request::param('order', $this->conf->sort_order), '"',
',auto_mark_article=', $mark['article'] ? 'true' : 'false',
',auto_mark_site=', $mark['site'] ? 'true' : 'false',
@@ -25,7 +26,9 @@ echo ',shortcuts={',
'collapse_entry:"', $s['collapse_entry'], '",',
'load_more:"', $s['load_more'], '",',
'auto_share:"', $s['auto_share'], '",',
- 'focus_search:"', $s['focus_search'], '"',
+ 'focus_search:"', $s['focus_search'], '",',
+ 'user_filter:"', $s['user_filter'], '",',
+ 'help:"', $s['help'], '"',
"},\n";
if (Minz_Request::param ('output') === 'global') {
diff --git a/app/views/helpers/view/normal_view.phtml b/app/views/helpers/view/normal_view.phtml
index 55ef6bdf6..87bf2e22a 100644
--- a/app/views/helpers/view/normal_view.phtml
+++ b/app/views/helpers/view/normal_view.phtml
@@ -81,7 +81,12 @@ if (!empty($this->entries)) {
}
}
$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);
+ if ($feed == null) {
+ $feed = $item->feed(true);
+ if ($feed == null) {
+ $feed = FreshRSS_Feed::example();
+ }
+ }
?><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 } ?>
diff --git a/app/views/importExport/index.phtml b/app/views/importExport/index.phtml
index e1458e916..35371faca 100644
--- a/app/views/importExport/index.phtml
+++ b/app/views/importExport/index.phtml
@@ -6,7 +6,9 @@
<form method="post" action="<?php echo _url('importExport', 'import'); ?>" enctype="multipart/form-data">
<legend><?php echo _t('import'); ?></legend>
<div class="form-group">
- <label class="group-name" for="file"><?php echo _t('file_to_import'); ?></label>
+ <label class="group-name" for="file">
+ <?php echo extension_loaded('zip') ? _t('file_to_import') : _t('file_to_import_no_zip'); ?>
+ </label>
<div class="group-controls">
<input type="file" name="file" id="file" />
</div>
diff --git a/app/views/index/formLogin.phtml b/app/views/index/formLogin.phtml
index cc925ea59..b79c1b614 100644
--- a/app/views/index/formLogin.phtml
+++ b/app/views/index/formLogin.phtml
@@ -1,32 +1,39 @@
<div class="prompt">
- <h1><?php echo Minz_Translate::t('login'); ?></h1><?php
+ <h1><?php echo _t('login'); ?></h1><?php
switch (Minz_Configuration::authType()) {
case 'form':
?><form id="loginForm" method="post" action="<?php echo _url('index', 'formLogin'); ?>">
<div>
- <label for="username"><?php echo Minz_Translate::t('username'); ?></label>
+ <label for="username"><?php echo _t('username'); ?></label>
<input type="text" id="username" name="username" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" autofocus="autofocus" />
</div>
<div>
- <label for="passwordPlain"><?php echo Minz_Translate::t('password'); ?></label>
+ <label for="passwordPlain"><?php echo _t('password'); ?></label>
<input type="password" id="passwordPlain" required="required" />
<input type="hidden" id="challenge" name="challenge" /><br />
- <noscript><strong><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></strong></noscript>
+ <noscript><strong><?php echo _t('javascript_should_be_activated'); ?></strong></noscript>
</div>
<div>
- <button id="loginButton" type="submit" class="btn btn-important"><?php echo Minz_Translate::t('login'); ?></button>
+ <label class="checkbox" for="keep_logged_in">
+ <input type="checkbox" name="keep_logged_in" id="keep_logged_in" value="1" />
+ <?php echo _t('keep_logged_in'); ?>
+ </label>
+ <br />
+ </div>
+ <div>
+ <button id="loginButton" type="submit" class="btn btn-important"><?php echo _t('login'); ?></button>
</div>
</form><?php
break;
case 'persona':
?><p>
- <?php echo FreshRSS_Themes::icon('login'); ?>
- <a class="signin" href="#"><?php echo Minz_Translate::t('login_with_persona'); ?></a>
+ <?php echo _i('login'); ?>
+ <a class="signin" href="#"><?php echo _t('login_with_persona'); ?></a>
</p><?php
break;
} ?>
- <p><a href="<?php echo _url('index', 'about'); ?>"><?php echo Minz_Translate::t('about_freshrss'); ?></a></p>
+ <p><a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('about_freshrss'); ?></a></p>
</div>
diff --git a/app/views/javascript/actualize.phtml b/app/views/javascript/actualize.phtml
index d08dc47d1..74cef4998 100644
--- a/app/views/javascript/actualize.phtml
+++ b/app/views/javascript/actualize.phtml
@@ -1,25 +1,24 @@
"use strict";
-var feeds = [<?php
- foreach ($this->feeds as $feed) {
- echo "'", Minz_Url::display(array('c' => 'feed', 'a' => 'actualize', 'params' => array('id' => $feed->id(), 'ajax' => '1')), 'php'), "',\n";
- }
- ?>],
+var feeds = [<?php foreach ($this->feeds as $feed) { ?>{<?php
+ ?>url: "<?php echo Minz_Url::display(array('c' => 'feed', 'a' => 'actualize', 'params' => array('id' => $feed->id(), 'ajax' => '1')), 'php'); ?>",<?php
+ ?>title: "<?php echo $feed->name(); ?>"<?php
+?>},<?php } ?>],
feed_processed = 0,
feed_count = feeds.length;
function initProgressBar(init) {
if (init) {
$("body").after("\<div id=\"actualizeProgress\" class=\"notification good\">\
- <?php echo _t('refresh'); ?> <span class=\"progress\">0 / " + feed_count + "</span><br />\
- <progress id=\"actualizeProgressBar\" value=\"0\" max=\"" + feed_count + "\"></progress>\
+ <?php echo _t('refresh'); ?><br /><span class=\"title\">/</span><br />\
+ <span class=\"progress\">0 / " + feed_count + "</span>\
</div>");
} else {
window.location.reload();
}
}
-function updateProgressBar(i) {
- $("#actualizeProgressBar").val(i);
+function updateProgressBar(i, title_feed) {
$("#actualizeProgress .progress").html(i + " / " + feed_count);
+ $("#actualizeProgress .title").html(title_feed);
}
function updateFeeds() {
@@ -43,10 +42,10 @@ function updateFeed() {
$.ajax({
type: 'POST',
- url: feed,
+ url: feed['url'],
}).complete(function (data) {
feed_processed++;
- updateProgressBar(feed_processed);
+ updateProgressBar(feed_processed, feed['title']);
if (feed_processed === feed_count) {
initProgressBar(false);
diff --git a/app/views/stats/repartition.phtml b/app/views/stats/repartition.phtml
index 09892d3c5..d9dc4c89d 100644
--- a/app/views/stats/repartition.phtml
+++ b/app/views/stats/repartition.phtml
@@ -2,23 +2,38 @@
<div class="post content">
<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a>
-
+
+ <h1><?php echo _t('stats_repartition'); ?></h1>
+
+ <select id="feed_select">
+ <option data-url="<?php echo _url('stats', 'repartition')?>"><?php echo _t('all_feeds')?></option>
+ <?php foreach ($this->categories as $category) {
+ $feeds = $category->feeds();
+ if (!empty($feeds)) {
+ echo '<optgroup label="', $category->name(), '">';
+ foreach ($feeds as $feed) {
+ if ($this->feed && $feed->id() == $this->feed->id()){
+ echo '<option value="', $feed->id(), '" selected="selected" data-url="', _url('stats', 'repartition', 'id', $feed->id()), '">', $feed->name(), '</option>';
+ } else {
+ echo '<option value="', $feed->id(), '" data-url="', _url('stats', 'repartition', 'id', $feed->id()), '">', $feed->name(), '</option>';
+ }
+ }
+ echo '</optgroup>';
+ }
+ }?>
+ </select>
+
<?php if ($this->feed) {?>
- <h1>
- <?php echo _t('stats_repartition'), " - "; ?>
- <a href="<?php echo _url('configure', 'feed', 'id', $this->feed->id()); ?>">
- <?php echo $this->feed->name(); ?>
- </a>
- </h1>
- <?php } else {?>
- <h1><?php echo _t('stats_repartition'); ?></h1>
+ <a href="<?php echo _url('configure', 'feed', 'id', $this->feed->id()); ?>">
+ <?php echo _t('administration'); ?>
+ </a>
<?php }?>
-
+
<div class="stat">
<h2><?php echo _t('stats_entry_per_hour'); ?></h2>
<div id="statsEntryPerHour" style="height: 300px"></div>
</div>
-
+
<div class="stat">
<h2><?php echo _t('stats_entry_per_day_of_week'); ?></h2>
<div id="statsEntryPerDayOfWeek" style="height: 300px"></div>
@@ -41,11 +56,22 @@ function initStats() {
return;
}
// Entry per hour
+ var avg_h = [];
+ for (var i = -1; i <= 24; i++) {
+ avg_h.push([i, <?php echo $this->averageHour?>]);
+ }
Flotr.draw(document.getElementById('statsEntryPerHour'),
- [<?php echo $this->repartitionHour ?>],
+ [{
+ data: <?php echo $this->repartitionHour ?>,
+ bars: {horizontal: false, show: true}
+ }, {
+ data: avg_h,
+ lines: {show: true},
+ label: <?php echo $this->averageHour?>,
+ yaxis: 2
+ }],
{
grid: {verticalLines: false},
- bars: {horizontal: false, show: true},
xaxis: {noTicks: 23,
tickFormatter: function(x) {
var x = parseInt(x);
@@ -55,14 +81,26 @@ function initStats() {
max: 23.9,
tickDecimals: 0},
yaxis: {min: 0},
+ y2axis: {showLabels: false},
mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
});
// Entry per day of week
+ var avg_dow = [];
+ for (var i = -1; i <= 7; i++) {
+ avg_dow.push([i, <?php echo $this->averageDayOfWeek?>]);
+ }
Flotr.draw(document.getElementById('statsEntryPerDayOfWeek'),
- [<?php echo $this->repartitionDayOfWeek ?>],
+ [{
+ data: <?php echo $this->repartitionDayOfWeek ?>,
+ bars: {horizontal: false, show: true}
+ }, {
+ data: avg_dow,
+ lines: {show: true},
+ label: <?php echo $this->averageDayOfWeek?>,
+ yaxis: 2
+ }],
{
grid: {verticalLines: false},
- bars: {horizontal: false, show: true},
xaxis: {noTicks: 6,
tickFormatter: function(x) {
var x = parseInt(x),
@@ -73,14 +111,26 @@ function initStats() {
max: 6.9,
tickDecimals: 0},
yaxis: {min: 0},
+ y2axis: {showLabels: false},
mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
});
// Entry per month
+ var avg_m = [];
+ for (var i = 0; i <= 13; i++) {
+ avg_m.push([i, <?php echo $this->averageMonth?>]);
+ }
Flotr.draw(document.getElementById('statsEntryPerMonth'),
- [<?php echo $this->repartitionMonth ?>],
+ [{
+ data: <?php echo $this->repartitionMonth ?>,
+ bars: {horizontal: false, show: true}
+ }, {
+ data: avg_m,
+ lines: {show: true},
+ label: <?php echo $this->averageMonth?>,
+ yaxis: 2
+ }],
{
grid: {verticalLines: false},
- bars: {horizontal: false, show: true},
xaxis: {noTicks: 12,
tickFormatter: function(x) {
var x = parseInt(x),
@@ -91,9 +141,10 @@ function initStats() {
max: 12.9,
tickDecimals: 0},
yaxis: {min: 0},
+ y2axis: {showLabels: false},
mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}}
});
-
+
}
initStats();
</script>
diff --git a/constants.php b/constants.php
index ba9c508dc..4c515d121 100644
--- a/constants.php
+++ b/constants.php
@@ -2,6 +2,7 @@
define('FRESHRSS_VERSION', '0.8-dev');
define('FRESHRSS_WEBSITE', 'http://freshrss.org');
define('FRESHRSS_UPDATE_WEBSITE', 'https://update.freshrss.org?v=' . FRESHRSS_VERSION);
+define('FRESHRSS_WIKI', 'http://doc.freshrss.org');
// PHP text output compression http://php.net/ob_gzhandler (better to do it at Web server level)
define('PHP_COMPRESSION', false);
diff --git a/data/tokens/.gitignore b/data/tokens/.gitignore
new file mode 100644
index 000000000..2211df63d
--- /dev/null
+++ b/data/tokens/.gitignore
@@ -0,0 +1 @@
+*.txt
diff --git a/data/tokens/index.html b/data/tokens/index.html
new file mode 100644
index 000000000..85faaa37e
--- /dev/null
+++ b/data/tokens/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">
+<head>
+<meta charset="UTF-8" />
+<meta http-equiv="Refresh" content="0; url=/" />
+<title>Redirection</title>
+<meta name="robots" content="noindex" />
+</head>
+
+<body>
+<p><a href="/">Redirection</a></p>
+</body>
+</html>
diff --git a/lib/Minz/Helper.php b/lib/Minz/Helper.php
index b058211d3..f4a547c4e 100644
--- a/lib/Minz/Helper.php
+++ b/lib/Minz/Helper.php
@@ -12,11 +12,22 @@ class Minz_Helper {
* Annule les effets des magic_quotes pour une variable donnée
* @param $var variable à traiter (tableau ou simple variable)
*/
- public static function stripslashes_r ($var) {
- if (is_array ($var)){
- return array_map (array ('Helper', 'stripslashes_r'), $var);
+ public static function stripslashes_r($var) {
+ if (is_array($var)){
+ return array_map(array('Minz_Helper', 'stripslashes_r'), $var);
} else {
return stripslashes($var);
}
}
+
+ /**
+ * Wrapper for htmlspecialchars.
+ * Force UTf-8 value and can be used on array too.
+ */
+ public static function htmlspecialchars_utf8($var) {
+ if (is_array($var)) {
+ return array_map(array('Minz_Helper', 'htmlspecialchars_utf8'), $var);
+ }
+ return htmlspecialchars($var, ENT_COMPAT, 'UTF-8');
+ }
}
diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php
index 755784522..ec4e25a6b 100644
--- a/lib/Minz/Request.php
+++ b/lib/Minz/Request.php
@@ -10,7 +10,7 @@
class Minz_Request {
private static $controller_name = '';
private static $action_name = '';
- private static $params = array ();
+ private static $params = array();
private static $default_controller_name = 'index';
private static $default_action_name = 'index';
@@ -18,59 +18,53 @@ class Minz_Request {
/**
* Getteurs
*/
- public static function controllerName () {
+ public static function controllerName() {
return self::$controller_name;
}
- public static function actionName () {
+ public static function actionName() {
return self::$action_name;
}
- public static function params () {
+ public static function params() {
return self::$params;
}
- static function htmlspecialchars_utf8 ($p) {
- if (is_array($p)) {
- return array_map('self::htmlspecialchars_utf8', $p);
- }
- return htmlspecialchars($p, ENT_COMPAT, 'UTF-8');
- }
- public static function param ($key, $default = false, $specialchars = false) {
- if (isset (self::$params[$key])) {
+ public static function param($key, $default = false, $specialchars = false) {
+ if (isset(self::$params[$key])) {
$p = self::$params[$key];
- if(is_object($p) || $specialchars) {
+ if (is_object($p) || $specialchars) {
return $p;
} else {
- return self::htmlspecialchars_utf8($p);
+ return Minz_Helper::htmlspecialchars_utf8($p);
}
} else {
return $default;
}
}
- public static function defaultControllerName () {
+ public static function defaultControllerName() {
return self::$default_controller_name;
}
- public static function defaultActionName () {
+ public static function defaultActionName() {
return self::$default_action_name;
}
/**
* Setteurs
*/
- public static function _controllerName ($controller_name) {
+ public static function _controllerName($controller_name) {
self::$controller_name = $controller_name;
}
- public static function _actionName ($action_name) {
+ public static function _actionName($action_name) {
self::$action_name = $action_name;
}
- public static function _params ($params) {
+ public static function _params($params) {
if (!is_array($params)) {
- $params = array ($params);
+ $params = array($params);
}
self::$params = $params;
}
- public static function _param ($key, $value = false) {
+ public static function _param($key, $value = false) {
if ($value === false) {
- unset (self::$params[$key]);
+ unset(self::$params[$key]);
} else {
self::$params[$key] = $value;
}
@@ -79,22 +73,36 @@ class Minz_Request {
/**
* Initialise la Request
*/
- public static function init () {
- self::magicQuotesOff ();
+ public static function init() {
+ self::magicQuotesOff();
}
/**
* Retourn le nom de domaine du site
*/
- public static function getDomainName () {
+ public static function getDomainName() {
return $_SERVER['HTTP_HOST'];
}
+ public static function isRefererFromSameDomain() {
+ if (empty($_SERVER['HTTP_REFERER'])) {
+ return false;
+ }
+ $host = parse_url(((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') ? 'https://' : 'http://') .
+ (empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST']));
+ $referer = parse_url($_SERVER['HTTP_REFERER']);
+ if (empty($host['scheme']) || empty($referer['scheme']) || $host['scheme'] !== $referer['scheme'] ||
+ empty($host['host']) || empty($referer['host']) || $host['host'] !== $referer['host']) {
+ return false;
+ }
+ return (isset($host['port']) ? $host['port'] : 0) === (isset($referer['port']) ? $referer['port'] : 0);
+ }
+
/**
* Détermine la base de l'url
* @return la base de l'url
*/
- public static function getBaseUrl () {
+ public static function getBaseUrl() {
$defaultBaseUrl = Minz_Configuration::baseUrl();
if (!empty($defaultBaseUrl)) {
return $defaultBaseUrl;
@@ -109,13 +117,13 @@ class Minz_Request {
* Récupère l'URI de la requête
* @return l'URI
*/
- public static function getURI () {
- if (isset ($_SERVER['REQUEST_URI'])) {
- $base_url = self::getBaseUrl ();
+ public static function getURI() {
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $base_url = self::getBaseUrl();
$uri = $_SERVER['REQUEST_URI'];
- $len_base_url = strlen ($base_url);
- $real_uri = substr ($uri, $len_base_url);
+ $len_base_url = strlen($base_url);
+ $real_uri = substr($uri, $len_base_url);
} else {
$real_uri = '';
}
@@ -129,16 +137,16 @@ class Minz_Request {
* @param $redirect si vrai, force la redirection http
* > sinon, le dispatcher recharge en interne
*/
- public static function forward ($url = array (), $redirect = false) {
- $url = Minz_Url::checkUrl ($url);
+ public static function forward($url = array(), $redirect = false) {
+ $url = Minz_Url::checkUrl($url);
if ($redirect) {
- header ('Location: ' . Minz_Url::display ($url, 'php'));
- exit ();
+ header('Location: ' . Minz_Url::display($url, 'php'));
+ exit();
} else {
- self::_controllerName ($url['c']);
- self::_actionName ($url['a']);
- self::_params (array_merge (
+ self::_controllerName($url['c']);
+ self::_actionName($url['a']);
+ self::_params(array_merge(
self::$params,
$url['params']
));
@@ -146,6 +154,31 @@ class Minz_Request {
}
}
+
+ /**
+ * Wrappers good notifications + redirection
+ * @param $msg notification content
+ * @param $url url array to where we should be forwarded
+ */
+ public static function good($msg, $url = array()) {
+ Minz_Session::_param('notification', array(
+ 'type' => 'good',
+ 'content' => $msg
+ ));
+
+ Minz_Request::forward($url, true);
+ }
+
+ public static function bad($msg, $url = array()) {
+ Minz_Session::_param('notification', array(
+ 'type' => 'bad',
+ 'content' => $msg
+ ));
+
+ Minz_Request::forward($url, true);
+ }
+
+
/**
* Permet de récupérer une variable de type $_GET
* @param $param nom de la variable
@@ -154,10 +187,10 @@ class Minz_Request {
* $_GET si $param = false
* $default si $_GET[$param] n'existe pas
*/
- public static function fetchGET ($param = false, $default = false) {
+ public static function fetchGET($param = false, $default = false) {
if ($param === false) {
return $_GET;
- } elseif (isset ($_GET[$param])) {
+ } elseif (isset($_GET[$param])) {
return $_GET[$param];
} else {
return $default;
@@ -172,10 +205,10 @@ class Minz_Request {
* $_POST si $param = false
* $default si $_POST[$param] n'existe pas
*/
- public static function fetchPOST ($param = false, $default = false) {
+ public static function fetchPOST($param = false, $default = false) {
if ($param === false) {
return $_POST;
- } elseif (isset ($_POST[$param])) {
+ } elseif (isset($_POST[$param])) {
return $_POST[$param];
} else {
return $default;
@@ -188,15 +221,16 @@ class Minz_Request {
* $_POST
* $_COOKIE
*/
- private static function magicQuotesOff () {
- if (get_magic_quotes_gpc ()) {
- $_GET = Minz_Helper::stripslashes_r ($_GET);
- $_POST = Minz_Helper::stripslashes_r ($_POST);
- $_COOKIE = Minz_Helper::stripslashes_r ($_COOKIE);
+ private static function magicQuotesOff() {
+ if (get_magic_quotes_gpc()) {
+ $_GET = Minz_Helper::stripslashes_r($_GET);
+ $_POST = Minz_Helper::stripslashes_r($_POST);
+ $_COOKIE = Minz_Helper::stripslashes_r($_COOKIE);
}
}
- public static function isPost () {
- return $_SERVER['REQUEST_METHOD'] === 'POST';
+ public static function isPost() {
+ return isset($_SERVER['REQUEST_METHOD']) &&
+ $_SERVER['REQUEST_METHOD'] === 'POST';
}
}
diff --git a/lib/Minz/Session.php b/lib/Minz/Session.php
index ddabc4658..af4de75bb 100644
--- a/lib/Minz/Session.php
+++ b/lib/Minz/Session.php
@@ -2,28 +2,20 @@
/**
* La classe Session gère la session utilisateur
- * C'est un singleton
*/
class Minz_Session {
/**
- * $session stocke les variables de session
- */
- private static $session = array (); //TODO: Try to avoid having another local copy
-
- /**
* Initialise la session, avec un nom
- * Le nom de session est utilisé comme nom pour les cookies et les URLs (i.e. PHPSESSID).
+ * Le nom de session est utilisé comme nom pour les cookies et les URLs(i.e. PHPSESSID).
* Il ne doit contenir que des caractères alphanumériques ; il doit être court et descriptif
*/
- public static function init ($name) {
- // démarre la session
- session_name ($name);
- session_set_cookie_params (0, dirname(empty($_SERVER['REQUEST_URI']) ? '/' : dirname($_SERVER['REQUEST_URI'])), null, false, true);
- session_start ();
+ public static function init($name) {
+ $cookie = session_get_cookie_params();
+ self::keepCookie($cookie['lifetime']);
- if (isset ($_SESSION)) {
- self::$session = $_SESSION;
- }
+ // démarre la session
+ session_name($name);
+ session_start();
}
@@ -32,8 +24,8 @@ class Minz_Session {
* @param $p le paramètre à récupérer
* @return la valeur de la variable de session, false si n'existe pas
*/
- public static function param ($p, $default = false) {
- return isset(self::$session[$p]) ? self::$session[$p] : $default;
+ public static function param($p, $default = false) {
+ return isset($_SESSION[$p]) ? $_SESSION[$p] : $default;
}
@@ -42,13 +34,11 @@ class Minz_Session {
* @param $p le paramètre à créer ou modifier
* @param $v la valeur à attribuer, false pour supprimer
*/
- public static function _param ($p, $v = false) {
+ public static function _param($p, $v = false) {
if ($v === false) {
- unset ($_SESSION[$p]);
- unset (self::$session[$p]);
+ unset($_SESSION[$p]);
} else {
$_SESSION[$p] = $v;
- self::$session[$p] = $v;
}
}
@@ -57,15 +47,47 @@ class Minz_Session {
* Permet d'effacer une session
* @param $force si à false, n'efface pas le paramètre de langue
*/
- public static function unset_session ($force = false) {
- $language = self::param ('language');
+ public static function unset_session($force = false) {
+ $language = self::param('language');
session_destroy();
- self::$session = array ();
+ $_SESSION = array();
if (!$force) {
- self::_param ('language', $language);
- Minz_Translate::reset ();
+ self::_param('language', $language);
+ Minz_Translate::reset();
}
}
+
+
+ /**
+ * Spécifie la durée de vie des cookies
+ * @param $l la durée de vie
+ */
+ public static function keepCookie($l) {
+ $cookie_dir = empty($_SERVER['REQUEST_URI']) ? '' : $_SERVER['REQUEST_URI'];
+ session_set_cookie_params($l, $cookie_dir, '', false, true);
+ }
+
+
+ /**
+ * Régénère un id de session.
+ * Utile pour appeler session_set_cookie_params après session_start()
+ */
+ public static function regenerateID() {
+ session_regenerate_id(true);
+ }
+
+ public static function deleteLongTermCookie($name) {
+ setcookie($name, '', 1, '', '', false, true);
+ }
+
+ public static function setLongTermCookie($name, $value, $expire) {
+ setcookie($name, $value, $expire, '', '', false, true);
+ }
+
+ public static function getLongTermCookie($name) {
+ return isset($_COOKIE[$name]) ? $_COOKIE[$name] : null;
+ }
+
}
diff --git a/lib/SimplePie/SimplePie/Parser.php b/lib/SimplePie/SimplePie/Parser.php
index 9300b4ba9..7fb7bd9be 100644
--- a/lib/SimplePie/SimplePie/Parser.php
+++ b/lib/SimplePie/SimplePie/Parser.php
@@ -142,7 +142,7 @@ class SimplePie_Parser
$dom = new DOMDocument();
$dom->recover = true;
$dom->strictErrorChecking = false;
- $dom->loadXML($data);
+ @$dom->loadXML($data);
$this->encoding = $encoding = $dom->encoding = 'UTF-8';
$data2 = $dom->saveXML();
if (function_exists('mb_convert_encoding'))
diff --git a/lib/lib_rss.php b/lib/lib_rss.php
index 86c0a4ae4..823f53716 100644
--- a/lib/lib_rss.php
+++ b/lib/lib_rss.php
@@ -230,7 +230,3 @@ function cryptAvailable() {
}
return false;
}
-
-function html_chars_utf8($str) {
- return htmlspecialchars($str, ENT_COMPAT, 'UTF-8');
-}
diff --git a/p/api/greader.php b/p/api/greader.php
index 7a961225f..5a6fdad7d 100644
--- a/p/api/greader.php
+++ b/p/api/greader.php
@@ -135,6 +135,7 @@ function checkCompatibility() {
}
if ((!array_key_exists('HTTP_AUTHORIZATION', $_SERVER)) && //Apache mod_rewrite trick should be fine
(empty($_SERVER['SERVER_SOFTWARE']) || (stripos($_SERVER['SERVER_SOFTWARE'], 'nginx') === false)) && //nginx should be fine
+ (empty($_SERVER['SERVER_SOFTWARE']) || (stripos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') === false)) && //lighttpd should be fine
((!function_exists('getallheaders')) || (stripos(php_sapi_name(), 'cgi') !== false))) { //Main problem is Apache/CGI mode
die('FAIL getallheaders! (probably)');
}
diff --git a/p/i/.gitignore b/p/i/.gitignore
new file mode 100644
index 000000000..03c88fd7a
--- /dev/null
+++ b/p/i/.gitignore
@@ -0,0 +1 @@
+.htaccess
diff --git a/p/scripts/main.js b/p/scripts/main.js
index ae7b69364..2108eece2 100644
--- a/p/scripts/main.js
+++ b/p/scripts/main.js
@@ -297,7 +297,7 @@ function next_entry() {
function prev_feed() {
var active_feed = $("#aside_flux .feeds li.active");
if (active_feed.length > 0) {
- active_feed.prev().find('a.feed').each(function(){this.click();});
+ active_feed.prevAll(':visible:first').find('a.feed').each(function(){this.click();});
} else {
last_feed();
}
@@ -306,21 +306,21 @@ function prev_feed() {
function next_feed() {
var active_feed = $("#aside_flux .feeds li.active");
if (active_feed.length > 0) {
- active_feed.next().find('a.feed').each(function(){this.click();});
+ active_feed.nextAll(':visible:first').find('a.feed').each(function(){this.click();});
} else {
first_feed();
}
}
function first_feed() {
- var feed = $("#aside_flux .feeds.active li:first");
+ var feed = $("#aside_flux .feeds.active li:visible:first");
if (feed.length > 0) {
feed.find('a')[1].click();
}
}
function last_feed() {
- var feed = $("#aside_flux .feeds.active li:last");
+ var feed = $("#aside_flux .feeds.active li:visible:last");
if (feed.length > 0) {
feed.find('a')[1].click();
}
@@ -330,7 +330,7 @@ function prev_category() {
var active_cat = $("#aside_flux .category.stick.active");
if (active_cat.length > 0) {
- var prev_cat = active_cat.parent('li').prev().find('.category.stick a.btn');
+ var prev_cat = active_cat.parent('li').prevAll(':visible:first').find('.category.stick a.btn');
if (prev_cat.length > 0) {
prev_cat[0].click();
}
@@ -344,7 +344,7 @@ function next_category() {
var active_cat = $("#aside_flux .category.stick.active");
if (active_cat.length > 0) {
- var next_cat = active_cat.parent('li').next().find('.category.stick a.btn');
+ var next_cat = active_cat.parent('li').nextAll(':visible:first').find('.category.stick a.btn');
if (next_cat.length > 0) {
next_cat[0].click();
}
@@ -355,14 +355,14 @@ function next_category() {
}
function first_category() {
- var cat = $("#aside_flux .category.stick:first");
+ var cat = $("#aside_flux .category.stick:visible:first");
if (cat.length > 0) {
cat.find('a.btn')[0].click();
}
}
function last_category() {
- var cat = $("#aside_flux .category.stick:last");
+ var cat = $("#aside_flux .category.stick:visible:last");
if (cat.length > 0) {
cat.find('a.btn')[0].click();
}
@@ -373,11 +373,41 @@ function collapse_entry() {
var flux_current = $(".flux.current");
flux_current.toggleClass("active");
- if (isCollapsed) {
+ if (isCollapsed && auto_mark_article) {
mark_read(flux_current, true);
}
}
+function user_filter(key) {
+ console.log('user filter');
+ console.warn(key);
+ var filter = $('#dropdown-query');
+ var filters = filter.siblings('.dropdown-menu').find('.item.query a');
+ if (typeof key === "undefined") {
+ if (!filter.length) {
+ return;
+ }
+ // Display the filter div
+ window.location.hash = filter.attr('id');
+ // Force scrolling to the filter div
+ var scroll = needsScroll($('.header'));
+ if (scroll !== 0) {
+ $('html,body').scrollTop(scroll);
+ }
+ // Force the key value if there is only one action, so we can trigger it automatically
+ if (filters.length === 1) {
+ key = 1;
+ } else {
+ return;
+ }
+ }
+ // Trigger selected share action
+ key = parseInt(key);
+ if (key <= filters.length) {
+ filters[key - 1].click();
+ }
+}
+
function auto_share(key) {
var share = $(".flux.current.active").find('.dropdown-target[id^="dropdown-share"]');
var shares = share.siblings('.dropdown-menu').find('.item a');
@@ -531,9 +561,19 @@ function init_shortcuts() {
}, {
'disable_in_input': true
});
+
+ shortcut.add(shortcuts.user_filter, function () {
+ user_filter();
+ }, {
+ 'disable_in_input': true
+ });
for(var i = 1; i < 10; i++){
shortcut.add(i.toString(), function (e) {
- auto_share(String.fromCharCode(e.keyCode));
+ if ($('#dropdown-query').siblings('.dropdown-menu').is(':visible')) {
+ user_filter(String.fromCharCode(e.keyCode));
+ } else {
+ auto_share(String.fromCharCode(e.keyCode));
+ }
}, {
'disable_in_input': true
});
@@ -618,6 +658,13 @@ function init_shortcuts() {
}, {
'disable_in_input': true
});
+
+ shortcut.add(shortcuts.help, function () {
+ redirect(help_url, true);
+ }, {
+ 'disable_in_input': true
+ });
+
}
function init_stream(divStream) {
@@ -663,7 +710,7 @@ function init_stream(divStream) {
if (auto_mark_site) {
divStream.on('click', '.flux .link > a', function () {
- mark_read($(this).parent().parent().parent(), true);
+ mark_read($(this).parents(".flux"), true);
});
}
}
@@ -1063,6 +1110,12 @@ function init_share_observers() {
});
}
+function init_stats_observers() {
+ $('#feed_select').on('change', function(e) {
+ redirect($(this).find(':selected').data('url'));
+ });
+}
+
function init_remove_observers() {
$('.post').on('click', 'a.remove', function(e) {
var remove_what = $(this).attr('data-remove');
@@ -1177,6 +1230,7 @@ function init_all() {
init_remove_observers();
init_feed_observers();
init_password_observers();
+ init_stats_observers();
}
if (window.console) {
diff --git a/p/themes/Dark/dark.css b/p/themes/Dark/dark.css
index 5edc09042..2ef48c406 100644
--- a/p/themes/Dark/dark.css
+++ b/p/themes/Dark/dark.css
@@ -515,15 +515,13 @@ a.btn {
.categories .feeds .item.empty.active {
background: #c95;
}
-.categories .feeds .item.empty.active .feed {
- color: #fff;
-}
.categories .feeds .item.error .feed {
color: #a44;
}
.categories .feeds .item.error.active {
background: #a44;
}
+.categories .feeds .item.empty.active .feed,
.categories .feeds .item.error.active .feed {
color: #fff;
}
@@ -570,7 +568,7 @@ a.btn {
}
.prompt form {
margin: 10px auto 20px auto;
- width: 180px;
+ width: 200px;
}
.prompt input {
margin: 5px auto;
diff --git a/p/themes/Flat/flat.css b/p/themes/Flat/flat.css
index 4ae3e98e9..fcfbb1424 100644
--- a/p/themes/Flat/flat.css
+++ b/p/themes/Flat/flat.css
@@ -492,10 +492,6 @@ a.btn {
.categories .feeds .item.active {
background: #2980b9;
}
-.categories .feeds .item.active .feed,
-.categories .feeds .item.empty.active .feed {
- color: #fff;
-}
.categories .feeds .item.empty.active {
background: #f39c12;
}
@@ -508,6 +504,11 @@ a.btn {
.categories .feeds .item.error .feed {
color: #bd362f;
}
+.categories .feeds .item.active .feed,
+.categories .feeds .item.empty.active .feed,
+.categories .feeds .item.error.active .feed {
+ color: #fff;
+}
.categories .feeds .item .feed {
margin: 0;
width: 165px;
@@ -551,7 +552,7 @@ a.btn {
}
.prompt form {
margin: 10px auto 20px auto;
- width: 180px;
+ width: 200px;
}
.prompt input {
margin: 5px auto;
diff --git a/p/themes/Origine/origine.css b/p/themes/Origine/origine.css
index 0b95e2d70..0d1d95bad 100644
--- a/p/themes/Origine/origine.css
+++ b/p/themes/Origine/origine.css
@@ -540,21 +540,23 @@ a.btn {
.categories .feeds .item.active {
background: #0062BE;
}
-.categories .feeds .item.active .feed {
- color: #fff;
-}
-.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.active {
+ background: #BD362F;
+}
+.categories .feeds .item.empty .feed {
+ color: #e67e22;
}
.categories .feeds .item.error .feed {
color: #BD362F;
}
+.categories .feeds .item.active .feed,
+.categories .feeds .item.empty.active .feed,
+.categories .feeds .item.error.active .feed {
+ color: #fff;
+}
.categories .feeds .item .feed {
margin: 0;
width: 165px;
@@ -598,7 +600,7 @@ a.btn {
}
.prompt form {
margin: 10px auto 20px auto;
- width: 180px;
+ width: 200px;
}
.prompt input {
margin: 5px auto;
diff --git a/p/themes/Screwdriver/metadata.json b/p/themes/Screwdriver/metadata.json
index e04c6e806..f45f1a98a 100644
--- a/p/themes/Screwdriver/metadata.json
+++ b/p/themes/Screwdriver/metadata.json
@@ -2,6 +2,6 @@
"name": "Screwdriver",
"author": "Mister aiR",
"description": "C'est un cocktail ! C'est chaud mais « fresh » à la fois. Ce thème tue du chaton.",
- "version": 1.0,
+ "version": 1.1,
"files": ["template.css","screwdriver.css"]
}
diff --git a/p/themes/Screwdriver/screwdriver.css b/p/themes/Screwdriver/screwdriver.css
index 683eece88..665f89c71 100644
--- a/p/themes/Screwdriver/screwdriver.css
+++ b/p/themes/Screwdriver/screwdriver.css
@@ -206,6 +206,10 @@ a.btn {
background: linear-gradient(180deg, #EDE7DE 0%, #FFF 100%) #EDE7DE;
background: -webkit-linear-gradient(top, #EDE7DE 0%, #FFF 100%);
}
+#loginButton.btn{
+ border:none;
+ box-shadow: 0px 1px rgba(255, 255, 255, 0.08) inset,0 -1px #171717,0px 1px rgba(255, 255, 255, 0.08);
+}
.nav_menu .btn.active, .nav_menu .btn:active, .nav_menu .dropdown-target:target ~ .btn.dropdown-toggle{
box-shadow: 0px 2px #E2972A;
border-radius: 0;
@@ -333,7 +337,7 @@ a.btn {
.nav-head {
margin: 0;
background: linear-gradient(0deg, #EDE7DE 0%, #FFF 100%) #EDE7DE;
- background: -webkit-linear-gradient(0deg, #EDE7DE 0%, #FFF 100%);
+ background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #FFF 100%);
text-align: right;
}
.nav-head .item {
@@ -674,6 +678,15 @@ ul.feeds.active{
.prompt p {
margin: 20px 0;
}
+.prompt input#username,.prompt input#passwordPlain{
+ border:none;
+ box-shadow: 0px 1px rgba(255, 255, 255, 0.08) inset,0 -1px #171717,0px 1px rgba(255, 255, 255, 0.08);
+ background:#EDE7DE;
+}
+.prompt input#username:focus,.prompt input#passwordPlain:focus{
+ border: solid 1px #E7AB34;
+ box-shadow: 0 0 3px #E7AB34;
+}
/*=== New article notification */
#new-article {
@@ -755,13 +768,13 @@ ul.feeds.active{
}
.flux .item.title {
-text-decoration: line-through;
+opacity: 0.35;
}
.flux.favorite .item.title {
-text-decoration: none;
+opacity: 1;
}
.flux.not_read .item.title {
-text-decoration: none;
+opacity: 1;
}
.flux.current .item.title a {
color: #0f0f0f;
@@ -1084,7 +1097,7 @@ text-decoration: none;
text-align: center;
background: #171717;
box-shadow: 0 1px rgba(255,255,255,0.08);
- border-radius: 0 0 0 5px;
+ border-radius: 0 8px 0 8px;
}
.aside .btn-important {
display: inline-block;
diff --git a/p/themes/Screwdriver/template.css b/p/themes/Screwdriver/template.css
index bf421e322..ddb3f376f 100644
--- a/p/themes/Screwdriver/template.css
+++ b/p/themes/Screwdriver/template.css
@@ -309,6 +309,9 @@ a.btn {
list-style: none;
margin: 0;
}
+.state_unread li:not(.active)[data-unread="0"] {
+ display: none;
+}
.category {
display: block;
overflow: hidden;
diff --git a/p/themes/base-theme/base.css b/p/themes/base-theme/base.css
index c45b1812e..76ac37933 100644
--- a/p/themes/base-theme/base.css
+++ b/p/themes/base-theme/base.css
@@ -390,16 +390,18 @@ a.btn {
/*=== Aside main page (feeds) */
.categories .feeds .item.active {
}
-.categories .feeds .item.active .feed {
-}
-.categories .feeds .item.empty .feed {
-}
.categories .feeds .item.empty.active {
}
-.categories .feeds .item.empty.active .feed {
+.categories .feeds .item.error.active {
+}
+.categories .feeds .item.empty .feed {
}
.categories .feeds .item.error .feed {
}
+.categories .feeds .item.active .feed,
+.categories .feeds .item.empty.active .feed,
+.categories .feeds .item.error.active .feed {
+}
.categories .feeds .item .feed {
margin: 0;
width: 165px;