Merge branch 'dev' into 411-update-system

Conflicts:
	constants.php
This commit is contained in:
Marien Fressinaud
2014-09-08 19:26:35 +02:00
44 changed files with 1220 additions and 376 deletions

View File

@@ -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.

101
README.fr.md Normal file
View File

@@ -0,0 +1,101 @@
* [English version](README.md)
# FreshRSS
FreshRSS est un agrégateur de flux RSS à auto-héberger à limage 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 dun 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 sadapter à des besoins personnels et non professionnels.
Je ne garantis en aucun cas la sécurité de celle-ci, ni son bon fonctionnement.
Je mengage 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 lapplication FreshRSS via la commande git ou [en téléchargeant larchive](https://github.com/marienfressinaud/FreshRSS/archive/master.zip)
2. Placez lapplication 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 dinstallation
5. Tout devrait fonctionner :) En cas de problème, nhésitez pas à me contacter.
# Contrôle daccès
Il est requis pour le mode multi-utilisateur, et recommandé dans tous les cas, de limiter laccès à votre FreshRSS. Au choix :
* En utilisant lidentification par formulaire (requiert JavaScript, et PHP 5.3.7+ recommandé fonctionne avec certaines versions de PHP 5.3.3+)
* En utilisant lidentification par [Mozilla Persona](https://login.persona.org/about) incluse dans FreshRSS
* En utilisant un contrôle daccès HTTP défini par votre serveur Web
* Voir par exemple la [documentation dApache sur lauthentification](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 dactualisation automatique des flux.
Consultez la documentation de Cron de votre système dexploitation ([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)…).
Cest une bonne idée dutiliser 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 daccès aux répertoires clés de lapplication. Si vous les bougez, tout se passe ici.
* En cas de problème, les logs peuvent être utile à lire, soit depuis linterface 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)

122
README.md
View File

@@ -1,88 +1,90 @@
* [Version française](README.fr.md)
# FreshRSS
FreshRSS est un agrégateur de flux RSS à auto-héberger à limage 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 dun 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 sadapter à des besoins personnels et non professionnels.
Je ne garantis en aucun cas la sécurité de celle-ci, ni son bon fonctionnement.
Je mengage 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)
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)
# 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
# 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
![Capture décran de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png)
![FreshRSS screenshot](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png)
# Installation
1. Récupérez lapplication FreshRSS via la commande git ou [en téléchargeant larchive](https://github.com/marienfressinaud/FreshRSS/archive/master.zip)
2. Placez lapplication 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 dinstallation
5. Tout devrait fonctionner :) En cas de problème, nhésitez pas à me contacter.
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.
# Contrôle daccès
Il est requis pour le mode multi-utilisateur, et recommandé dans tous les cas, de limiter laccès à votre FreshRSS. Au choix :
* En utilisant lidentification par formulaire (requiert JavaScript, et PHP 5.3.7+ recommandé fonctionne avec certaines versions de PHP 5.3.3+)
* En utilisant lidentification par [Mozilla Persona](https://login.persona.org/about) incluse dans FreshRSS
* En utilisant un contrôle daccès HTTP défini par votre serveur Web
* Voir par exemple la [documentation dApache sur lauthentification](http://httpd.apache.org/docs/trunk/howto/auth.html)
* Créer dans ce cas un fichier `./p/i/.htaccess` avec un fichier `.htpasswd` correspondant.
# 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.
# Rafraîchissement automatique des flux
* Vous pouvez ajouter une tâche Cron lançant régulièrement le script dactualisation automatique des flux.
Consultez la documentation de Cron de votre système dexploitation ([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)…).
Cest une bonne idée dutiliser le même utilisateur que votre serveur Web (souvent “www-data”).
Par exemple, pour exécuter le script toutes les heures :
# 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)…).
Its 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 daccès aux répertoires clés de lapplication. Si vous les bougez, tout se passe ici.
* En cas de problème, les logs peuvent être utile à lire, soit depuis linterface 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)

View File

@@ -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));

View File

@@ -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);
Minz_Request::forward(array(
'c' => 'index',
'a' => 'index'
), 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']);
}
// What are you doing? you have to call this controller
// with a POST request!
Minz_Request::forward(array(
'c' => 'importExport',
'a' => 'index'
));
// 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);
}
// 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());
$export_files = array();
if ($export_opml) {
$export_files['feeds.opml'] = $this->generateOpml();
}
if ($export_starred) {
$export_files['starred.json'] = $this->generateArticles('starred');
}
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');
}
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
);
}
}
$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";
}
$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);
}

View File

@@ -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);
}
}

View File

@@ -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() {

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -151,6 +151,68 @@ SQL;
return $this->convertToSerie($repartition);
}
/**
* 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
*

326
app/i18n/de.php Normal file
View File

@@ -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',
);

View File

@@ -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)',

View File

@@ -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' => 'Sil ny a quun 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' => 'Sil ny a quun 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 limport du fichier Zip.',
'no_zip_extension' => 'Lextension Zip nest 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)',

View File

@@ -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 ();

View File

@@ -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));

View File

@@ -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>

View File

@@ -61,6 +61,16 @@
</div>
</div>
<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">

View File

@@ -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>

View File

@@ -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') {

View File

@@ -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 } ?>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);

View File

@@ -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>

View File

@@ -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);

1
data/tokens/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.txt

13
data/tokens/index.html Normal file
View File

@@ -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>

View File

@@ -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');
}
}

View File

@@ -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';
}
}

View File

@@ -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;
}
}

View File

@@ -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'))

View File

@@ -230,7 +230,3 @@ function cryptAvailable() {
}
return false;
}
function html_chars_utf8($str) {
return htmlspecialchars($str, ENT_COMPAT, 'UTF-8');
}

View File

@@ -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)');
}

1
p/i/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.htaccess

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;

View File

@@ -540,21 +540,23 @@ a.btn {
.categories .feeds .item.active {
background: #0062BE;
}
.categories .feeds .item.active .feed {
color: #fff;
.categories .feeds .item.empty.active {
background: #e67e22;
}
.categories .feeds .item.error.active {
background: #BD362F;
}
.categories .feeds .item.empty .feed {
color: #e67e22;
}
.categories .feeds .item.empty.active {
background: #e67e22;
}
.categories .feeds .item.empty.active .feed {
color: #fff;
}
.categories .feeds .item.error .feed {
color: #BD362F;
}
.categories .feeds .item.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;

View File

@@ -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"]
}

View File

@@ -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;

View File

@@ -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;

View File

@@ -390,16 +390,18 @@ a.btn {
/*=== Aside main page (feeds) */
.categories .feeds .item.active {
}
.categories .feeds .item.active .feed {
.categories .feeds .item.empty.active {
}
.categories .feeds .item.error.active {
}
.categories .feeds .item.empty .feed {
}
.categories .feeds .item.empty.active {
}
.categories .feeds .item.empty.active .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;