Browse Source

Merge pull request #1399 from FreshRSS/dev

Release 1.6.2
Alexandre Alapetite 9 years ago
parent
commit
5f637bd816
51 changed files with 460 additions and 148 deletions
  1. 22 1
      CHANGELOG.md
  2. 5 1
      CREDITS.md
  3. 11 2
      README.fr.md
  4. 10 1
      README.md
  5. 4 0
      app/Controllers/authController.php
  6. 4 0
      app/Controllers/feedController.php
  7. 4 2
      app/Controllers/importExportController.php
  8. 112 47
      app/Controllers/updateController.php
  9. 10 5
      app/Models/Auth.php
  10. 30 17
      app/Models/EntryDAO.php
  11. 1 0
      app/Models/Factory.php
  12. 4 1
      app/Models/UserDAO.php
  13. 4 0
      app/i18n/cz/admin.php
  14. 1 1
      app/i18n/cz/gen.php
  15. 4 0
      app/i18n/cz/install.php
  16. 4 0
      app/i18n/de/admin.php
  17. 1 1
      app/i18n/de/gen.php
  18. 4 0
      app/i18n/de/install.php
  19. 4 0
      app/i18n/en/admin.php
  20. 1 1
      app/i18n/en/gen.php
  21. 4 0
      app/i18n/en/install.php
  22. 4 0
      app/i18n/fr/admin.php
  23. 1 1
      app/i18n/fr/gen.php
  24. 4 0
      app/i18n/fr/install.php
  25. 4 0
      app/i18n/it/admin.php
  26. 1 1
      app/i18n/it/gen.php
  27. 4 0
      app/i18n/it/install.php
  28. 4 0
      app/i18n/nl/admin.php
  29. 1 1
      app/i18n/nl/gen.php
  30. 4 0
      app/i18n/nl/install.php
  31. 4 0
      app/i18n/ru/admin.php
  32. 1 1
      app/i18n/ru/gen.php
  33. 4 0
      app/i18n/ru/install.php
  34. 4 0
      app/i18n/tr/admin.php
  35. 1 1
      app/i18n/tr/gen.php
  36. 4 0
      app/i18n/tr/install.php
  37. 9 3
      app/install.php
  38. 1 1
      app/views/auth/formLogin.phtml
  39. 60 41
      app/views/helpers/export/articles.phtml
  40. 25 3
      cli/README.md
  41. 3 0
      cli/create-user.php
  42. 7 7
      cli/do-install.php
  43. 35 0
      cli/user-info.php
  44. 1 1
      constants.php
  45. 4 1
      data/config.default.php
  46. 3 1
      extensions/README.md
  47. 6 0
      lib/Minz/ModelPdo.php
  48. 5 2
      lib/favicons.php
  49. 8 2
      lib/lib_install.php
  50. 3 0
      lib/lib_rss.php
  51. 1 1
      p/scripts/main.js

+ 22 - 1
CHANGELOG.md

@@ -1,5 +1,26 @@
 # Changelog
 # Changelog
 
 
+## 2016-12-26 FreshRSS 1.6.2
+
+* Features
+	* Add git compatibility in Web update system [#1357](https://github.com/FreshRSS/FreshRSS/issues/1357)
+		* Requires that the initial installation is done with git
+	* New option `limits.cookie_duration` in `data/config.php` to set the login cookie duration [#1384](https://github.com/FreshRSS/FreshRSS/issues/1384)
+* SQL
+	* More robust export function in the case of large datasets [#1372](https://github.com/FreshRSS/FreshRSS/issues/1372)
+* CLI
+	* New command `./cli/user-info.php` to get some user information [#1345](https://github.com/FreshRSS/FreshRSS/issues/1345)
+* Bug fixing
+	* Fix bug in estimating last user activity [#1358](https://github.com/FreshRSS/FreshRSS/issues/1358)
+	* PostgreSQL: fix bug when updating cached values [#1360](https://github.com/FreshRSS/FreshRSS/issues/1360)
+	* Fix bug in confirmation before marking as read [#1348](https://github.com/FreshRSS/FreshRSS/issues/1348)
+	* Fix small bugs in installer [#1363](https://github.com/FreshRSS/FreshRSS/pull/1363)
+	* Allow slash in database hostname, when using sockets [#1364](https://github.com/FreshRSS/FreshRSS/issues/1364)
+	* Add curl user-agent to retrieve favicons [#1380](https://github.com/FreshRSS/FreshRSS/issues/1380)
+	* Send login cookie only once [#1398](https://github.com/FreshRSS/FreshRSS/pull/1398)
+	* Add a check for PHP extension fileinfo [#1375](https://github.com/FreshRSS/FreshRSS/issues/1375)
+
+
 ## 2016-11-02 FreshRSS 1.6.1
 ## 2016-11-02 FreshRSS 1.6.1
 
 
 * Bug fixing
 * Bug fixing
@@ -53,7 +74,7 @@
 	* Download icon 💾 for podcasts [#1236](https://github.com/FreshRSS/FreshRSS/issues/1236)
 	* Download icon 💾 for podcasts [#1236](https://github.com/FreshRSS/FreshRSS/issues/1236)
 * SimplePie
 * SimplePie
 	* Fix auto-discovery of RSS feeds in Web pages served as `text/xml` [#1264](https://github.com/FreshRSS/FreshRSS/issues/1264)
 	* Fix auto-discovery of RSS feeds in Web pages served as `text/xml` [#1264](https://github.com/FreshRSS/FreshRSS/issues/1264)
-* Mics.
+* Misc.
 	* Removed *resource-priorities* attributes (`defer`, `lazyload`), deprecated by W3C [#1222](https://github.com/FreshRSS/FreshRSS/pull/1222)
 	* Removed *resource-priorities* attributes (`defer`, `lazyload`), deprecated by W3C [#1222](https://github.com/FreshRSS/FreshRSS/pull/1222)
 
 
 
 

+ 5 - 1
CREDITS.md

@@ -1,5 +1,5 @@
 This is a credit file of people who have [contributed to FreshRSS](https://github.com/FreshRSS/FreshRSS/graphs/contributors) with, at least,
 This is a credit file of people who have [contributed to FreshRSS](https://github.com/FreshRSS/FreshRSS/graphs/contributors) with, at least,
-one commit on the FreshRSS repository (at https://github.com/FreshRSS/FreshRSS).
+one commit on one of the FreshRSS repositories (at https://github.com/FreshRSS).
 Please note a commit on THIS specific file is not considered as a contribution
 Please note a commit on THIS specific file is not considered as a contribution
 (too easy!). Its purpose is to show that even the smallest contribution is important.
 (too easy!). Its purpose is to show that even the smallest contribution is important.
 People are sorted by name so please keep this order.
 People are sorted by name so please keep this order.
@@ -19,15 +19,19 @@ People are sorted by name so please keep this order.
 * [hckweb](https://github.com/hckweb): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=hckweb)
 * [hckweb](https://github.com/hckweb): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=hckweb)
 * [Jaussoin Timothée](https://github.com/edhelas): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=edhelas), [Web](http://edhelas.movim.eu/)
 * [Jaussoin Timothée](https://github.com/edhelas): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=edhelas), [Web](http://edhelas.movim.eu/)
 * [Julien Reichardt](https://github.com/j8r): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=j8r), [Web](https://blog.jrei.ch/)
 * [Julien Reichardt](https://github.com/j8r): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=j8r), [Web](https://blog.jrei.ch/)
+* [Kevin Papst](https://github.com/kevinpapst): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=kevinpapst), [Web](http://www.kevinpapst.de/)
 * [Luc Didry](https://github.com/ldidry): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=ldidry), [Web](https://www.fiat-tux.fr/)
 * [Luc Didry](https://github.com/ldidry): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=ldidry), [Web](https://www.fiat-tux.fr/)
 * [marcomrc](https://github.com/marcomrc): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=marcomrc)
 * [marcomrc](https://github.com/marcomrc): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=marcomrc)
 * [Marcus Rohrmoser](https://github.com/mro): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=mro), [Web](http://mro.name/~me)
 * [Marcus Rohrmoser](https://github.com/mro): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=mro), [Web](http://mro.name/~me)
 * [Marien Fressinaud](https://github.com/marienfressinaud): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=marienfressinaud), [Web](http://marienfressinaud.fr/)
 * [Marien Fressinaud](https://github.com/marienfressinaud): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=marienfressinaud), [Web](http://marienfressinaud.fr/)
 * [Melvyn Laïly](https://github.com/yaurthek): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=yaurthek), [Web](http://x2a.yt/)
 * [Melvyn Laïly](https://github.com/yaurthek): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=yaurthek), [Web](http://x2a.yt/)
 * [Nicolas Elie](https://github.com/nicolaselie): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=nicolaselie)
 * [Nicolas Elie](https://github.com/nicolaselie): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=nicolaselie)
+* [Nicolas Lœuillet](https://github.com/nicosomb): [contributions](https://github.com/FreshRSS/documentation/commits?author=nicosomb), [Web](http://www.loeuillet.org/)
 * [plopoyop](https://github.com/plopoyop): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=plopoyop)
 * [plopoyop](https://github.com/plopoyop): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=plopoyop)
 * [purexo](https://github.com/purexo): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:purexo), [Web](https://purexo.mom/)
 * [purexo](https://github.com/purexo): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:purexo), [Web](https://purexo.mom/)
+* [Quentin Dufour](https://github.com/superboum): [contributions](https://github.com/FreshRSS/documentation/commits?author=superboum), [Web](http://quentin.dufour.io/)
 * [romibi](https://github.com/romibi): [contributions](https://github.com/FreshRSS/FreshRSS/commits/dev?author=romibi)
 * [romibi](https://github.com/romibi): [contributions](https://github.com/FreshRSS/FreshRSS/commits/dev?author=romibi)
+* [subic](https://github.com/subic): [contributions](https://github.com/FreshRSS/documentation/commits?author=subic)
 * [Tets42](https://github.com/Tets42): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Tets42)
 * [Tets42](https://github.com/Tets42): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Tets42)
 * [tomgue](https://github.com/tomgue): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=tomgue)
 * [tomgue](https://github.com/tomgue): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=tomgue)
 * [Wanabo](https://github.com/Wanabo): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Wanabo)
 * [Wanabo](https://github.com/Wanabo): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Wanabo)

+ 11 - 2
README.fr.md

@@ -8,6 +8,7 @@ Il se veut léger et facile à prendre en main tout en étant un outil puissant
 Il permet de gérer plusieurs utilisateurs, et dispose d’un mode de lecture anonyme.
 Il permet de gérer plusieurs utilisateurs, et dispose d’un mode de lecture anonyme.
 Il supporte [PubSubHubbub](https://code.google.com/p/pubsubhubbub/) pour des notifications instantanées depuis les sites compatibles.
 Il supporte [PubSubHubbub](https://code.google.com/p/pubsubhubbub/) pour des notifications instantanées depuis les sites compatibles.
 Il y a une API pour les clients (mobiles), ainsi qu’une [interface en ligne de commande](./cli/README.md).
 Il y a une API pour les clients (mobiles), ainsi qu’une [interface en ligne de commande](./cli/README.md).
+Enfin, il permet l’ajout d’[extensions](#extensions) pour encore plus de personnalisation.
 
 
 * Site officiel : http://freshrss.org
 * Site officiel : http://freshrss.org
 * Démo : http://demo.freshrss.org/
 * Démo : http://demo.freshrss.org/
@@ -23,7 +24,7 @@ Voir la [liste des versions](../../releases).
 * Pour ceux qui veulent bien aider à tester ou déveloper les dernières fonctionnalités, [la branche dev](https://github.com/FreshRSS/FreshRSS/tree/dev) vous ouvre les bras !
 * Pour ceux qui veulent bien aider à tester ou déveloper les dernières fonctionnalités, [la branche dev](https://github.com/FreshRSS/FreshRSS/tree/dev) vous ouvre les bras !
 
 
 # Avertissements
 # Avertissements
-Cette application a été développée pour s’adapter principalement à des besoins personnels, et aucune garantie n'est fournie.
+Cette application a été développée pour s’adapter principalement à des besoins personnels, et aucune garantie nest fournie.
 Les demandes de fonctionnalités, rapports de bugs, et autres contributions sont les bienvenues. Privilégiez pour cela des [demandes sur GitHub](https://github.com/FreshRSS/FreshRSS/issues).
 Les demandes de fonctionnalités, rapports de bugs, et autres contributions sont les bienvenues. Privilégiez pour cela des [demandes sur GitHub](https://github.com/FreshRSS/FreshRSS/issues).
 Nous sommes une communauté amicale.
 Nous sommes une communauté amicale.
 
 
@@ -85,13 +86,16 @@ sudo git checkout -b dev origin/dev
 
 
 # Mettre les droits d’accès pour le serveur Web
 # Mettre les droits d’accès pour le serveur Web
 sudo chown -R :www-data . && sudo chmod -R g+r . && sudo chmod -R g+w ./data/
 sudo chown -R :www-data . && sudo chmod -R g+r . && sudo chmod -R g+w ./data/
+# Si vous souhaitez permettre les mises à jour par l’interface Web
+sudo chmod -R g+w .
+
 # Publier FreshRSS dans votre répertoire HTML public
 # Publier FreshRSS dans votre répertoire HTML public
 sudo ln -s /usr/share/FreshRSS/p /var/www/html/FreshRSS
 sudo ln -s /usr/share/FreshRSS/p /var/www/html/FreshRSS
 # Naviguez vers http://example.net/FreshRSS pour terminer l’installation
 # Naviguez vers http://example.net/FreshRSS pour terminer l’installation
 # (Si vous le faite depuis localhost, vous pourrez avoir à ajuster le réglage de votre adresse publique)
 # (Si vous le faite depuis localhost, vous pourrez avoir à ajuster le réglage de votre adresse publique)
 # ou utilisez l’interface en ligne de commande
 # ou utilisez l’interface en ligne de commande
 
 
-# Mettre à jour FreshRSS vers une nouvelle version
+# Mettre à jour FreshRSS vers une nouvelle version par git
 cd /usr/share/FreshRSS
 cd /usr/share/FreshRSS
 sudo git pull
 sudo git pull
 sudo chown -R :www-data . && sudo chmod -R g+r . && sudo chmod -R g+w ./data/
 sudo chown -R :www-data . && sudo chmod -R g+r . && sudo chmod -R g+w ./data/
@@ -140,6 +144,11 @@ mysqldump -u utilisateur -p --databases freshrss > freshrss.sql
 ```
 ```
 
 
 
 
+# Extensions 
+FreshRSS permet l’ajout d’extensions en plus des fonctionnalités natives.
+Voir le [dépôt dédié à ces extensions](https://github.com/FreshRSS/Extensions).
+
+
 # Bibliothèques incluses
 # Bibliothèques incluses
 * [SimplePie](http://simplepie.org/)
 * [SimplePie](http://simplepie.org/)
 * [MINZ](https://github.com/marienfressinaud/MINZ)
 * [MINZ](https://github.com/marienfressinaud/MINZ)

+ 10 - 1
README.md

@@ -8,6 +8,7 @@ It is at the same time lightweight, easy to work with, powerful and customizable
 It is a multi-user application with an anonymous reading mode.
 It is a multi-user application with an anonymous reading mode.
 It supports [PubSubHubbub](https://code.google.com/p/pubsubhubbub/) for instant notifications from compatible Web sites.
 It supports [PubSubHubbub](https://code.google.com/p/pubsubhubbub/) for instant notifications from compatible Web sites.
 There is an API for (mobile) clients, and a [Command-Line Interface](./cli/README.md).
 There is an API for (mobile) clients, and a [Command-Line Interface](./cli/README.md).
+Finally, it supports [extensions](#extensions) for further tuning.
 
 
 * Official website: http://freshrss.org
 * Official website: http://freshrss.org
 * Demo: http://demo.freshrss.org/
 * Demo: http://demo.freshrss.org/
@@ -85,13 +86,16 @@ sudo git checkout -b dev origin/dev
 
 
 # Set the rights so that your Web server can access the files
 # Set the rights so that your Web server can access the files
 sudo chown -R :www-data . && sudo chmod -R g+r . && sudo chmod -R g+w ./data/
 sudo chown -R :www-data . && sudo chmod -R g+r . && sudo chmod -R g+w ./data/
+# If you would like to allow updates from the Web interface
+sudo chmod -R g+w .
+
 # Publish FreshRSS in your public HTML directory
 # Publish FreshRSS in your public HTML directory
 sudo ln -s /usr/share/FreshRSS/p /var/www/html/FreshRSS
 sudo ln -s /usr/share/FreshRSS/p /var/www/html/FreshRSS
 # Navigate to http://example.net/FreshRSS to complete the installation
 # Navigate to http://example.net/FreshRSS to complete the installation
 # (If you do it from localhost, you may have to adjust the setting of your public address later)
 # (If you do it from localhost, you may have to adjust the setting of your public address later)
 # or use the Command-Line Interface
 # or use the Command-Line Interface
 
 
-# Update to a newer version of FreshRSS
+# Update to a newer version of FreshRSS with git
 cd /usr/share/FreshRSS
 cd /usr/share/FreshRSS
 sudo git pull
 sudo git pull
 sudo chown -R :www-data . && sudo chmod -R g+r . && sudo chmod -R g+w ./data/
 sudo chown -R :www-data . && sudo chmod -R g+r . && sudo chmod -R g+w ./data/
@@ -140,6 +144,11 @@ mysqldump -u user -p --databases freshrss > freshrss.sql
 ```
 ```
 
 
 
 
+# Extensions 
+FreshRSS supports further customizations by adding extensions on top of its core functionality.
+See the [repository dedicated to those extensions](https://github.com/FreshRSS/Extensions). 
+
+
 # Included libraries
 # Included libraries
 * [SimplePie](http://simplepie.org/)
 * [SimplePie](http://simplepie.org/)
 * [MINZ](https://github.com/marienfressinaud/MINZ)
 * [MINZ](https://github.com/marienfressinaud/MINZ)

+ 4 - 0
app/Controllers/authController.php

@@ -113,6 +113,10 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
 		$file_mtime = @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js');
 		$file_mtime = @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js');
 		Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . $file_mtime));
 		Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . $file_mtime));
 
 
+		$conf = Minz_Configuration::get('system');
+		$limits = $conf->limits;
+		$this->view->cookie_days = round($limits['cookie_duration'] / 86400, 1);
+
 		if (Minz_Request::isPost()) {
 		if (Minz_Request::isPost()) {
 			$nonce = Minz_Session::param('nonce');
 			$nonce = Minz_Session::param('nonce');
 			$username = Minz_Request::param('username', '');
 			$username = Minz_Request::param('username', '');

+ 4 - 0
app/Controllers/feedController.php

@@ -27,6 +27,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 	}
 	}
 
 
 	public static function addFeed($url, $title = '', $cat_id = 0, $new_cat_name = '', $http_auth = '') {
 	public static function addFeed($url, $title = '', $cat_id = 0, $new_cat_name = '', $http_auth = '') {
+		FreshRSS_UserDAO::touch();
 		@set_time_limit(300);
 		@set_time_limit(300);
 
 
 		$catDAO = new FreshRSS_CategoryDAO();
 		$catDAO = new FreshRSS_CategoryDAO();
@@ -484,6 +485,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 		if ($feed_id <= 0 || $feed_name == '') {
 		if ($feed_id <= 0 || $feed_name == '') {
 			return false;
 			return false;
 		}
 		}
+		FreshRSS_UserDAO::touch();
 		$feedDAO = FreshRSS_Factory::createFeedDao();
 		$feedDAO = FreshRSS_Factory::createFeedDao();
 		return $feedDAO->updateFeed($feed_id, array('name' => $feed_name));
 		return $feedDAO->updateFeed($feed_id, array('name' => $feed_name));
 	}
 	}
@@ -492,6 +494,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 		if ($feed_id <= 0 || ($cat_id <= 0 && $new_cat_name == '')) {
 		if ($feed_id <= 0 || ($cat_id <= 0 && $new_cat_name == '')) {
 			return false;
 			return false;
 		}
 		}
+		FreshRSS_UserDAO::touch();
 
 
 		$catDAO = new FreshRSS_CategoryDAO();
 		$catDAO = new FreshRSS_CategoryDAO();
 		if ($cat_id > 0) {
 		if ($cat_id > 0) {
@@ -540,6 +543,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 	}
 	}
 
 
 	public static function deleteFeed($feed_id) {
 	public static function deleteFeed($feed_id) {
+		FreshRSS_UserDAO::touch();
 		$feedDAO = FreshRSS_Factory::createFeedDao();
 		$feedDAO = FreshRSS_Factory::createFeedDao();
 		if ($feedDAO->deleteFeed($feed_id)) {
 		if ($feedDAO->deleteFeed($feed_id)) {
 			// TODO: Delete old favicon
 			// TODO: Delete old favicon

+ 4 - 2
app/Controllers/importExportController.php

@@ -531,6 +531,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
 		$this->entryDAO = FreshRSS_Factory::createEntryDao($username);
 		$this->entryDAO = FreshRSS_Factory::createEntryDao($username);
 		$this->feedDAO = FreshRSS_Factory::createFeedDao($username);
 		$this->feedDAO = FreshRSS_Factory::createFeedDao($username);
 
 
+		$this->entryDAO->disableBuffering();
+
 		if ($export_feeds === true) {
 		if ($export_feeds === true) {
 			//All feeds
 			//All feeds
 			$export_feeds = $this->feedDAO->listFeedsIds();
 			$export_feeds = $this->feedDAO->listFeedsIds();
@@ -641,13 +643,13 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
 			$this->view->list_title = _t('sub.import_export.starred_list');
 			$this->view->list_title = _t('sub.import_export.starred_list');
 			$this->view->type = 'starred';
 			$this->view->type = 'starred';
 			$unread_fav = $this->entryDAO->countUnreadReadFavorites();
 			$unread_fav = $this->entryDAO->countUnreadReadFavorites();
-			$this->view->entries = $this->entryDAO->listWhere(
+			$this->view->entriesRaw = $this->entryDAO->listWhereRaw(
 				's', '', FreshRSS_Entry::STATE_ALL, 'ASC', $unread_fav['all']
 				's', '', FreshRSS_Entry::STATE_ALL, 'ASC', $unread_fav['all']
 			);
 			);
 		} elseif ($type === 'feed' && $feed != null) {
 		} elseif ($type === 'feed' && $feed != null) {
 			$this->view->list_title = _t('sub.import_export.feed_list', $feed->name());
 			$this->view->list_title = _t('sub.import_export.feed_list', $feed->name());
 			$this->view->type = 'feed/' . $feed->id();
 			$this->view->type = 'feed/' . $feed->id();
-			$this->view->entries = $this->entryDAO->listWhere(
+			$this->view->entriesRaw = $this->entryDAO->listWhereRaw(
 				'f', $feed->id(), FreshRSS_Entry::STATE_ALL, 'ASC',
 				'f', $feed->id(), FreshRSS_Entry::STATE_ALL, 'ASC',
 				$maxFeedEntries
 				$maxFeedEntries
 			);
 			);

+ 112 - 47
app/Controllers/updateController.php

@@ -2,6 +2,45 @@
 
 
 class FreshRSS_update_Controller extends Minz_ActionController {
 class FreshRSS_update_Controller extends Minz_ActionController {
 
 
+	public static function isGit() {
+		return is_dir(FRESHRSS_PATH . '/.git/');
+	}
+
+	public static function hasGitUpdate() {
+		$cwd = getcwd();
+		chdir(FRESHRSS_PATH);
+		$output = array();
+		try {
+			exec('git fetch', $output, $return);
+			if ($return == 0) {
+				exec('git status -sb --porcelain remote', $output, $return);
+			} else {
+				$line = is_array($output) ? implode('; ', $output) : '' . $output;
+				Minz_Log::warning('git fetch warning:' . $line);
+			}
+		} catch (Exception $e) {
+			Minz_Log::warning('git fetch error:' . $e->getMessage());
+		}
+		chdir($cwd);
+		$line = is_array($output) ? implode('; ', $output) : '' . $output;
+		return strpos($line, '[behind') !== false;
+	}
+
+	public static function gitPull() {
+		$cwd = getcwd();
+		chdir(FRESHRSS_PATH);
+		$output = array();
+		$return = 1;
+		try {
+			exec('git pull --ff-only', $output, $return);
+		} catch (Exception $e) {
+			Minz_Log::warning('git pull error:' . $e->getMessage());
+		}
+		chdir($cwd);
+		$line = is_array($output) ? implode('; ', $output) : '' . $output;
+		return $return == 0 ? true : 'Git error: ' . $line;
+	}
+
 	public function firstAction() {
 	public function firstAction() {
 		if (!FreshRSS_Auth::hasAccess('admin')) {
 		if (!FreshRSS_Auth::hasAccess('admin')) {
 			Minz_Error::error(403);
 			Minz_Error::error(403);
@@ -20,7 +59,7 @@ class FreshRSS_update_Controller extends Minz_ActionController {
 	public function indexAction() {
 	public function indexAction() {
 		Minz_View::prependTitle(_t('admin.update.title') . ' · ');
 		Minz_View::prependTitle(_t('admin.update.title') . ' · ');
 
 
-		if (file_exists(UPDATE_FILENAME) && !is_writable(FRESHRSS_PATH)) {
+		if (!is_writable(FRESHRSS_PATH)) {
 			$this->view->message = array(
 			$this->view->message = array(
 				'status' => 'bad',
 				'status' => 'bad',
 				'title' => _t('gen.short.damn'),
 				'title' => _t('gen.short.damn'),
@@ -53,49 +92,65 @@ class FreshRSS_update_Controller extends Minz_ActionController {
 			return;
 			return;
 		}
 		}
 
 
-		$auto_update_url = FreshRSS_Context::$system_conf->auto_update_url . '?v=' . FRESHRSS_VERSION;
-		$c = curl_init($auto_update_url);
-		curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
-		curl_setopt($c, CURLOPT_SSL_VERIFYPEER, true);
-		curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 2);
-		$result = curl_exec($c);
-		$c_status = curl_getinfo($c, CURLINFO_HTTP_CODE);
-		$c_error = curl_error($c);
-		curl_close($c);
-
-		if ($c_status !== 200) {
-			Minz_Log::warning(
-				'Error during update (HTTP code ' . $c_status . '): ' . $c_error
-			);
+		$script = '';
+		$version = '';
 
 
-			$this->view->message = array(
-				'status' => 'bad',
-				'title' => _t('gen.short.damn'),
-				'body' => _t('feedback.update.server_not_found', $auto_update_url)
-			);
-			return;
-		}
-
-		$res_array = explode("\n", $result, 2);
-		$status = $res_array[0];
-		if (strpos($status, 'UPDATE') !== 0) {
-			$this->view->message = array(
-				'status' => 'bad',
-				'title' => _t('gen.short.damn'),
-				'body' => _t('feedback.update.none')
-			);
+		if (self::isGit()) {
+			if (self::hasGitUpdate()) {
+				$version = 'git';
+			} else {
+				$this->view->message = array(
+					'status' => 'bad',
+					'title' => _t('gen.short.damn'),
+					'body' => _t('feedback.update.none')
+				);
+				@touch(join_path(DATA_PATH, 'last_update.txt'));
+				return;
+			}
+		} else {
+			$auto_update_url = FreshRSS_Context::$system_conf->auto_update_url . '?v=' . FRESHRSS_VERSION;
+			Minz_Log::debug('HTTP GET ' . $auto_update_url);
+			$c = curl_init($auto_update_url);
+			curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
+			curl_setopt($c, CURLOPT_SSL_VERIFYPEER, true);
+			curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 2);
+			$result = curl_exec($c);
+			$c_status = curl_getinfo($c, CURLINFO_HTTP_CODE);
+			$c_error = curl_error($c);
+			curl_close($c);
+
+			if ($c_status !== 200) {
+				Minz_Log::warning(
+					'Error during update (HTTP code ' . $c_status . '): ' . $c_error
+				);
+
+				$this->view->message = array(
+					'status' => 'bad',
+					'title' => _t('gen.short.damn'),
+					'body' => _t('feedback.update.server_not_found', $auto_update_url)
+				);
+				return;
+			}
 
 
-			@touch(join_path(DATA_PATH, 'last_update.txt'));
+			$res_array = explode("\n", $result, 2);
+			$status = $res_array[0];
+			if (strpos($status, 'UPDATE') !== 0) {
+				$this->view->message = array(
+					'status' => 'bad',
+					'title' => _t('gen.short.damn'),
+					'body' => _t('feedback.update.none')
+				);
+				@touch(join_path(DATA_PATH, 'last_update.txt'));
+				return;
+			}
 
 
-			return;
+			$script = $res_array[1];
+			$version = explode(' ', $status, 2);
+			$version = $version[1];
 		}
 		}
 
 
-		$script = $res_array[1];
 		if (file_put_contents(UPDATE_FILENAME, $script) !== false) {
 		if (file_put_contents(UPDATE_FILENAME, $script) !== false) {
-			$version = explode(' ', $status, 2);
-			$version = $version[1];
 			@file_put_contents(join_path(DATA_PATH, 'last_update.txt'), $version);
 			@file_put_contents(join_path(DATA_PATH, 'last_update.txt'), $version);
-
 			Minz_Request::forward(array('c' => 'update'), true);
 			Minz_Request::forward(array('c' => 'update'), true);
 		} else {
 		} else {
 			$this->view->message = array(
 			$this->view->message = array(
@@ -111,10 +166,13 @@ class FreshRSS_update_Controller extends Minz_ActionController {
 			Minz_Request::forward(array('c' => 'update'), true);
 			Minz_Request::forward(array('c' => 'update'), true);
 		}
 		}
 
 
-		require(UPDATE_FILENAME);
-
 		if (Minz_Request::param('post_conf', false)) {
 		if (Minz_Request::param('post_conf', false)) {
-			$res = do_post_update();
+			if (self::isGit()) {
+				$res = !self::hasGitUpdate();
+			} else {
+				require(UPDATE_FILENAME);
+				$res = do_post_update();
+			}
 
 
 			Minz_ExtensionManager::callHook('post_update');
 			Minz_ExtensionManager::callHook('post_update');
 
 
@@ -126,14 +184,21 @@ class FreshRSS_update_Controller extends Minz_ActionController {
 				Minz_Request::bad(_t('feedback.update.error', $res),
 				Minz_Request::bad(_t('feedback.update.error', $res),
 				                  array('c' => 'update', 'a' => 'index'));
 				                  array('c' => 'update', 'a' => 'index'));
 			}
 			}
-		}
-
-		if (Minz_Request::isPost()) {
-			save_info_update();
-		}
+		} else {
+			$res = false;
 
 
-		if (!need_info_update()) {
-			$res = apply_update();
+			if (self::isGit()) {
+				$res = self::gitPull();
+			} else {
+				if (Minz_Request::isPost()) {
+					save_info_update();
+				}
+				if (!need_info_update()) {
+					$res = apply_update();
+				} else {
+					return;
+				}
+			}
 
 
 			if ($res === true) {
 			if ($res === true) {
 				Minz_Request::forward(array(
 				Minz_Request::forward(array(

+ 10 - 5
app/Models/Auth.php

@@ -25,7 +25,7 @@ class FreshRSS_Auth {
 			self::giveAccess();
 			self::giveAccess();
 		} elseif (self::accessControl()) {
 		} elseif (self::accessControl()) {
 			self::giveAccess();
 			self::giveAccess();
-			FreshRSS_UserDAO::touch($current_user);
+			FreshRSS_UserDAO::touch();
 		} else {
 		} else {
 			// Be sure all accesses are removed!
 			// Be sure all accesses are removed!
 			self::removeAccess();
 			self::removeAccess();
@@ -219,8 +219,8 @@ class FreshRSS_FormAuth {
 	}
 	}
 
 
 	public static function makeCookie($username, $password_hash) {
 	public static function makeCookie($username, $password_hash) {
+		$conf = Minz_Configuration::get('system');
 		do {
 		do {
-			$conf = Minz_Configuration::get('system');
 			$token = sha1($conf->salt . $username . uniqid(mt_rand(), true));
 			$token = sha1($conf->salt . $username . uniqid(mt_rand(), true));
 			$token_file = DATA_PATH . '/tokens/' . $token . '.txt';
 			$token_file = DATA_PATH . '/tokens/' . $token . '.txt';
 		} while (file_exists($token_file));
 		} while (file_exists($token_file));
@@ -229,15 +229,17 @@ class FreshRSS_FormAuth {
 			return false;
 			return false;
 		}
 		}
 
 
-		$expire = time() + 2629744;	//1 month	//TODO: Use a configuration instead
+		$limits = $conf->limits;
+		$cookie_duration = empty($limits['cookie_duration']) ? 2629744 : $limits['cookie_duration'];
+		$expire = time() + $cookie_duration;
 		Minz_Session::setLongTermCookie('FreshRSS_login', $token, $expire);
 		Minz_Session::setLongTermCookie('FreshRSS_login', $token, $expire);
 		return $token;
 		return $token;
 	}
 	}
 
 
 	public static function deleteCookie() {
 	public static function deleteCookie() {
 		$token = Minz_Session::getLongTermCookie('FreshRSS_login');
 		$token = Minz_Session::getLongTermCookie('FreshRSS_login');
-		Minz_Session::deleteLongTermCookie('FreshRSS_login');
 		if (ctype_alnum($token)) {
 		if (ctype_alnum($token)) {
+			Minz_Session::deleteLongTermCookie('FreshRSS_login');
 			@unlink(DATA_PATH . '/tokens/' . $token . '.txt');
 			@unlink(DATA_PATH . '/tokens/' . $token . '.txt');
 		}
 		}
 
 
@@ -247,7 +249,10 @@ class FreshRSS_FormAuth {
 	}
 	}
 
 
 	public static function purgeTokens() {
 	public static function purgeTokens() {
-		$oldest = time() - 2629744;	// 1 month	// TODO: Use a configuration instead
+		$conf = Minz_Configuration::get('system');
+		$limits = $conf->limits;
+		$cookie_duration = empty($limits['cookie_duration']) ? 2629744 : $limits['cookie_duration'];
+		$oldest = time() - $cookie_duration;
 		foreach (new DirectoryIterator(DATA_PATH . '/tokens/') as $file_info) {
 		foreach (new DirectoryIterator(DATA_PATH . '/tokens/') as $file_info) {
 			// $extension = $file_info->getExtension(); doesn't work in PHP < 5.3.7
 			// $extension = $file_info->getExtension(); doesn't work in PHP < 5.3.7
 			$extension = pathinfo($file_info->getFilename(), PATHINFO_EXTENSION);
 			$extension = pathinfo($file_info->getFilename(), PATHINFO_EXTENSION);

+ 30 - 17
app/Models/EntryDAO.php

@@ -241,6 +241,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 		if (count($ids) < 1) {
 		if (count($ids) < 1) {
 			return 0;
 			return 0;
 		}
 		}
+		FreshRSS_UserDAO::touch();
 		$sql = 'UPDATE `' . $this->prefix . 'entry` '
 		$sql = 'UPDATE `' . $this->prefix . 'entry` '
 		     . 'SET is_favorite=? '
 		     . 'SET is_favorite=? '
 		     . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?)';
 		     . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?)';
@@ -315,6 +316,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 	 * @return integer affected rows
 	 * @return integer affected rows
 	 */
 	 */
 	public function markRead($ids, $is_read = true) {
 	public function markRead($ids, $is_read = true) {
+		FreshRSS_UserDAO::touch();
 		if (is_array($ids)) {	//Many IDs at once (used by API)
 		if (is_array($ids)) {	//Many IDs at once (used by API)
 			if (count($ids) < 6) {	//Speed heuristics
 			if (count($ids) < 6) {	//Speed heuristics
 				$affected = 0;
 				$affected = 0;
@@ -379,6 +381,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 	 * @return integer affected rows
 	 * @return integer affected rows
 	 */
 	 */
 	public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0, $filter = null, $state = 0) {
 	public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0, $filter = null, $state = 0) {
+		FreshRSS_UserDAO::touch();
 		if ($idMax == 0) {
 		if ($idMax == 0) {
 			$idMax = time() . '000000';
 			$idMax = time() . '000000';
 			Minz_Log::debug('Calling markReadEntries(0) is deprecated!');
 			Minz_Log::debug('Calling markReadEntries(0) is deprecated!');
@@ -421,6 +424,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 	 * @return integer affected rows
 	 * @return integer affected rows
 	 */
 	 */
 	public function markReadCat($id, $idMax = 0, $filter = null, $state = 0) {
 	public function markReadCat($id, $idMax = 0, $filter = null, $state = 0) {
+		FreshRSS_UserDAO::touch();
 		if ($idMax == 0) {
 		if ($idMax == 0) {
 			$idMax = time() . '000000';
 			$idMax = time() . '000000';
 			Minz_Log::debug('Calling markReadCat(0) is deprecated!');
 			Minz_Log::debug('Calling markReadCat(0) is deprecated!');
@@ -458,6 +462,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 	 * @return integer affected rows
 	 * @return integer affected rows
 	 */
 	 */
 	public function markReadFeed($id_feed, $idMax = 0, $filter = null, $state = 0) {
 	public function markReadFeed($id_feed, $idMax = 0, $filter = null, $state = 0) {
+		FreshRSS_UserDAO::touch();
 		if ($idMax == 0) {
 		if ($idMax == 0) {
 			$idMax = time() . '000000';
 			$idMax = time() . '000000';
 			Minz_Log::debug('Calling markReadFeed(0) is deprecated!');
 			Minz_Log::debug('Calling markReadFeed(0) is deprecated!');
@@ -513,7 +518,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 
 
 		$stm->execute($values);
 		$stm->execute($values);
 		$res = $stm->fetchAll(PDO::FETCH_ASSOC);
 		$res = $stm->fetchAll(PDO::FETCH_ASSOC);
-		$entries = self::daoToEntry($res);
+		$entries = self::daoToEntries($res);
 		return isset($entries[0]) ? $entries[0] : null;
 		return isset($entries[0]) ? $entries[0] : null;
 	}
 	}
 
 
@@ -528,7 +533,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 
 
 		$stm->execute($values);
 		$stm->execute($values);
 		$res = $stm->fetchAll(PDO::FETCH_ASSOC);
 		$res = $stm->fetchAll(PDO::FETCH_ASSOC);
-		$entries = self::daoToEntry($res);
+		$entries = self::daoToEntries($res);
 		return isset($entries[0]) ? $entries[0] : null;
 		return isset($entries[0]) ? $entries[0] : null;
 	}
 	}
 
 
@@ -661,7 +666,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 			. ($limit > 0 ? ' LIMIT ' . $limit : ''));	//TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
 			. ($limit > 0 ? ' LIMIT ' . $limit : ''));	//TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
 	}
 	}
 
 
-	public function listWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) {
+	public function listWhereRaw($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) {
 		list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min);
 		list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min);
 
 
 		$sql = 'SELECT e0.id, e0.guid, e0.title, e0.author, '
 		$sql = 'SELECT e0.id, e0.guid, e0.title, e0.author, '
@@ -675,8 +680,12 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 
 
 		$stm = $this->bd->prepare($sql);
 		$stm = $this->bd->prepare($sql);
 		$stm->execute($values);
 		$stm->execute($values);
+		return $stm;
+	}
 
 
-		return self::daoToEntry($stm->fetchAll(PDO::FETCH_ASSOC));
+	public function listWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) {
+		$stm = $this->listWhereRaw($type, $id, $state, $order, $limit, $firstId, $filter, $date_min);
+		return self::daoToEntries($stm->fetchAll(PDO::FETCH_ASSOC));
 	}
 	}
 
 
 	public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) {	//For API
 	public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) {	//For API
@@ -805,15 +814,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 		return $res[0];
 		return $res[0];
 	}
 	}
 
 
-	public static function daoToEntry($listDAO) {
-		$list = array();
-
-		if (!is_array($listDAO)) {
-			$listDAO = array($listDAO);
-		}
-
-		foreach ($listDAO as $key => $dao) {
-			$entry = new FreshRSS_Entry(
+	public static function daoToEntry($dao) {
+		$entry = new FreshRSS_Entry(
 				$dao['id_feed'],
 				$dao['id_feed'],
 				$dao['guid'],
 				$dao['guid'],
 				$dao['title'],
 				$dao['title'],
@@ -825,10 +827,21 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 				$dao['is_favorite'],
 				$dao['is_favorite'],
 				$dao['tags']
 				$dao['tags']
 			);
 			);
-			if (isset($dao['id'])) {
-				$entry->_id($dao['id']);
-			}
-			$list[] = $entry;
+		if (isset($dao['id'])) {
+			$entry->_id($dao['id']);
+		}
+		return $entry;
+	}
+
+	private static function daoToEntries($listDAO) {
+		$list = array();
+
+		if (!is_array($listDAO)) {
+			$listDAO = array($listDAO);
+		}
+
+		foreach ($listDAO as $key => $dao) {
+			$list[] = self::daoToEntry($dao);
 		}
 		}
 
 
 		unset($listDAO);
 		unset($listDAO);

+ 1 - 0
app/Models/Factory.php

@@ -6,6 +6,7 @@ class FreshRSS_Factory {
 		$conf = Minz_Configuration::get('system');
 		$conf = Minz_Configuration::get('system');
 		switch ($conf->db['type']) {
 		switch ($conf->db['type']) {
 			case 'sqlite':
 			case 'sqlite':
+			case 'pgsql':
 				return new FreshRSS_FeedDAOSQLite($username);
 				return new FreshRSS_FeedDAOSQLite($username);
 			default:
 			default:
 				return new FreshRSS_FeedDAO($username);
 				return new FreshRSS_FeedDAO($username);

+ 4 - 1
app/Models/UserDAO.php

@@ -84,7 +84,10 @@ class FreshRSS_UserDAO extends Minz_ModelPdo {
 		return is_dir(join_path(DATA_PATH , 'users', $username));
 		return is_dir(join_path(DATA_PATH , 'users', $username));
 	}
 	}
 
 
-	public static function touch($username) {
+	public static function touch($username = '') {
+		if (($username == '') || (!ctype_alnum($username))) {
+			$username = Minz_Session::param('currentUser', '_');
+		}
 		return touch(join_path(DATA_PATH , 'users', $username, 'config.php'));
 		return touch(join_path(DATA_PATH , 'users', $username, 'config.php'));
 	}
 	}
 
 

+ 4 - 0
app/i18n/cz/admin.php

@@ -57,6 +57,10 @@ return array(
 			'nok' => 'Tabulka kanálů je nastavena špatně.',
 			'nok' => 'Tabulka kanálů je nastavena špatně.',
 			'ok' => 'Tabulka kanálů je v pořádku.',
 			'ok' => 'Tabulka kanálů je v pořádku.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'Nemáte PHP fileinfo (balíček fileinfo).',
+			'ok' => 'Máte rozšíření fileinfo.',
+		),
 		'files' => 'Instalace souborů',
 		'files' => 'Instalace souborů',
 		'json' => array(
 		'json' => array(
 			'nok' => 'Nemáte JSON (balíček php5-json).',
 			'nok' => 'Nemáte JSON (balíček php5-json).',

+ 1 - 1
app/i18n/cz/gen.php

@@ -22,7 +22,7 @@ return array(
 	),
 	),
 	'auth' => array(
 	'auth' => array(
 		'email' => 'Email',
 		'email' => 'Email',
-		'keep_logged_in' => 'Zapamatovat přihlášení <small>(1 měsíc)</small>',
+		'keep_logged_in' => 'Zapamatovat přihlášení <small>(%s dny)</small>',
 		'login' => 'Login',
 		'login' => 'Login',
 		'logout' => 'Odhlášení',
 		'logout' => 'Odhlášení',
 		'password' =>  array(
 		'password' =>  array(

+ 4 - 0
app/i18n/cz/install.php

@@ -56,6 +56,10 @@ return array(
 			'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/favicons</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
 			'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/favicons</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
 			'ok' => 'Oprávnění adresáře favicons jsou v pořádku.',
 			'ok' => 'Oprávnění adresáře favicons jsou v pořádku.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'Nemáte PHP fileinfo (balíček fileinfo).',
+			'ok' => 'Máte rozšíření fileinfo.',
+		),
 		'http_referer' => array(
 		'http_referer' => array(
 			'nok' => 'Zkontrolujte prosím že neměníte HTTP REFERER.',
 			'nok' => 'Zkontrolujte prosím že neměníte HTTP REFERER.',
 			'ok' => 'Váš HTTP REFERER je znám a odpovídá Vašemu serveru.',
 			'ok' => 'Váš HTTP REFERER je znám a odpovídá Vašemu serveru.',

+ 4 - 0
app/i18n/de/admin.php

@@ -57,6 +57,10 @@ return array(
 			'nok' => 'Die Tabelle <em>feed</em> ist schlecht konfiguriert.',
 			'nok' => 'Die Tabelle <em>feed</em> ist schlecht konfiguriert.',
 			'ok' => 'Die Tabelle <em>feed</em> ist korrekt konfiguriert.',
 			'ok' => 'Die Tabelle <em>feed</em> ist korrekt konfiguriert.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'Ihnen fehlt PHP fileinfo (Paket fileinfo).',
+			'ok' => 'Sie haben die fileinfo-Erweiterung.',
+		),
 		'files' => 'Datei-Installation',
 		'files' => 'Datei-Installation',
 		'json' => array(
 		'json' => array(
 			'nok' => 'Ihnen fehlt die JSON-Erweiterung (Paket php5-json).',
 			'nok' => 'Ihnen fehlt die JSON-Erweiterung (Paket php5-json).',

+ 1 - 1
app/i18n/de/gen.php

@@ -22,7 +22,7 @@ return array(
 	),
 	),
 	'auth' => array(
 	'auth' => array(
 		'email' => 'E-Mail-Adresse',
 		'email' => 'E-Mail-Adresse',
-		'keep_logged_in' => 'Eingeloggt bleiben <small>(1 Monat)</small>',
+		'keep_logged_in' => 'Eingeloggt bleiben <small>(%s Tage)</small>',
 		'login' => 'Anmelden',
 		'login' => 'Anmelden',
 		'logout' => 'Abmelden',
 		'logout' => 'Abmelden',
 		'password' => array(
 		'password' => array(

+ 4 - 0
app/i18n/de/install.php

@@ -56,6 +56,10 @@ return array(
 			'nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>./data/favicons</em>. Der HTTP-Server muss Schreibrechte besitzen.',
 			'nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>./data/favicons</em>. Der HTTP-Server muss Schreibrechte besitzen.',
 			'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data/favicons</em> sind in Ordnung.',
 			'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data/favicons</em> sind in Ordnung.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'Ihnen fehlt PHP fileinfo (Paket fileinfo).',
+			'ok' => 'Sie haben die fileinfo-Erweiterung.',
+		),
 		'http_referer' => array(
 		'http_referer' => array(
 			'nok' => 'Bitte stellen Sie sicher, dass Sie Ihren HTTP REFERER nicht abändern.',
 			'nok' => 'Bitte stellen Sie sicher, dass Sie Ihren HTTP REFERER nicht abändern.',
 			'ok' => 'Ihr HTTP REFERER ist bekannt und entspricht Ihrem Server.',
 			'ok' => 'Ihr HTTP REFERER ist bekannt und entspricht Ihrem Server.',

+ 4 - 0
app/i18n/en/admin.php

@@ -57,6 +57,10 @@ return array(
 			'nok' => 'Feed table is bad configured.',
 			'nok' => 'Feed table is bad configured.',
 			'ok' => 'Feed table is ok.',
 			'ok' => 'Feed table is ok.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'Cannot find the PHP fileinfo library (fileinfo package).',
+			'ok' => 'You have the fileinfo library.',
+		),
 		'files' => 'File installation',
 		'files' => 'File installation',
 		'json' => array(
 		'json' => array(
 			'nok' => 'Cannot find JSON (php5-json package).',
 			'nok' => 'Cannot find JSON (php5-json package).',

+ 1 - 1
app/i18n/en/gen.php

@@ -22,7 +22,7 @@ return array(
 	),
 	),
 	'auth' => array(
 	'auth' => array(
 		'email' => 'Email address',
 		'email' => 'Email address',
-		'keep_logged_in' => 'Keep me logged in <small>(1 month)</small>',
+		'keep_logged_in' => 'Keep me logged in <small>(%s days)</small>',
 		'login' => 'Login',
 		'login' => 'Login',
 		'logout' => 'Logout',
 		'logout' => 'Logout',
 		'password' => array(
 		'password' => array(

+ 4 - 0
app/i18n/en/install.php

@@ -56,6 +56,10 @@ return array(
 			'nok' => 'Check permissions on <em>./data/favicons</em> directory. HTTP server must have rights to write into',
 			'nok' => 'Check permissions on <em>./data/favicons</em> directory. HTTP server must have rights to write into',
 			'ok' => 'Permissions on favicons directory are good.',
 			'ok' => 'Permissions on favicons directory are good.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'Cannot find the PHP fileinfo library (fileinfo package).',
+			'ok' => 'You have the fileinfo library.',
+		),
 		'http_referer' => array(
 		'http_referer' => array(
 			'nok' => 'Please check that you are not altering your HTTP REFERER.',
 			'nok' => 'Please check that you are not altering your HTTP REFERER.',
 			'ok' => 'Your HTTP REFERER is known and corresponds to your server.',
 			'ok' => 'Your HTTP REFERER is known and corresponds to your server.',

+ 4 - 0
app/i18n/fr/admin.php

@@ -57,6 +57,10 @@ return array(
 			'nok' => 'La table feed est mal configurée.',
 			'nok' => 'La table feed est mal configurée.',
 			'ok' => 'La table feed est bien configurée.',
 			'ok' => 'La table feed est bien configurée.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'Impossible de trouver la librairie PHP fileinfo (paquet fileinfo).',
+			'ok' => 'Vous disposez de la librairie fileinfo.',
+		),
 		'files' => 'Installation des fichiers',
 		'files' => 'Installation des fichiers',
 		'json' => array(
 		'json' => array(
 			'nok' => 'Vous ne disposez pas de JSON (paquet php5-json).',
 			'nok' => 'Vous ne disposez pas de JSON (paquet php5-json).',

+ 1 - 1
app/i18n/fr/gen.php

@@ -22,7 +22,7 @@ return array(
 	),
 	),
 	'auth' => array(
 	'auth' => array(
 		'email' => 'Adresse courriel',
 		'email' => 'Adresse courriel',
-		'keep_logged_in' => 'Rester connecté <small>(1 mois)</small>',
+		'keep_logged_in' => 'Rester connecté <small>(%s jours)</small>',
 		'login' => 'Connexion',
 		'login' => 'Connexion',
 		'logout' => 'Déconnexion',
 		'logout' => 'Déconnexion',
 		'password' => array(
 		'password' => array(

+ 4 - 0
app/i18n/fr/install.php

@@ -56,6 +56,10 @@ return array(
 			'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/favicons</em>. Le serveur HTTP doit être capable d’écrire dedans',
 			'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/favicons</em>. Le serveur HTTP doit être capable d’écrire dedans',
 			'ok' => 'Les droits sur le répertoire des favicons sont bons.',
 			'ok' => 'Les droits sur le répertoire des favicons sont bons.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'Vous ne disposez pas de PHP fileinfo (paquet fileinfo).',
+			'ok' => 'Vous disposez de fileinfo.',
+		),
 		'http_referer' => array(
 		'http_referer' => array(
 			'nok' => 'Veuillez vérifier que vous ne modifiez pas votre HTTP REFERER.',
 			'nok' => 'Veuillez vérifier que vous ne modifiez pas votre HTTP REFERER.',
 			'ok' => 'Le HTTP REFERER est connu et semble correspondre à votre serveur.',
 			'ok' => 'Le HTTP REFERER est connu et semble correspondre à votre serveur.',

+ 4 - 0
app/i18n/it/admin.php

@@ -57,6 +57,10 @@ return array(
 			'nok' => 'La tabella Feed ha una configurazione errata.',
 			'nok' => 'La tabella Feed ha una configurazione errata.',
 			'ok' => 'Tabella Feed OK.',
 			'ok' => 'Tabella Feed OK.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'Manca il supporto per PHP fileinfo (pacchetto fileinfo).',
+			'ok' => 'Estensione fileinfo presente.',
+		),
 		'files' => 'Installazione files',
 		'files' => 'Installazione files',
 		'json' => array(
 		'json' => array(
 			'nok' => 'Manca il supoorto a JSON (pacchetto php5-json).',
 			'nok' => 'Manca il supoorto a JSON (pacchetto php5-json).',

+ 1 - 1
app/i18n/it/gen.php

@@ -22,7 +22,7 @@ return array(
 	),
 	),
 	'auth' => array(
 	'auth' => array(
 		'email' => 'Indirizzo email',
 		'email' => 'Indirizzo email',
-		'keep_logged_in' => 'Ricorda i dati <small>(1 mese)</small>',
+		'keep_logged_in' => 'Ricorda i dati <small>(%s giorni)</small>',
 		'login' => 'Accedi',
 		'login' => 'Accedi',
 		'logout' => 'Esci',
 		'logout' => 'Esci',
 		'password' => array(
 		'password' => array(

+ 4 - 0
app/i18n/it/install.php

@@ -56,6 +56,10 @@ return array(
 			'nok' => 'Verifica i permessi sulla cartella <em>./data/favicons</em>. Il server HTTP deve avere i permessi per scriverci dentro',
 			'nok' => 'Verifica i permessi sulla cartella <em>./data/favicons</em>. Il server HTTP deve avere i permessi per scriverci dentro',
 			'ok' => 'I permessi sulla cartella favicons sono corretti.',
 			'ok' => 'I permessi sulla cartella favicons sono corretti.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'Manca il supporto per PHP fileinfo (pacchetto fileinfo).',
+			'ok' => 'Estensione fileinfo presente.',
+		),
 		'http_referer' => array(
 		'http_referer' => array(
 			'nok' => 'Per favore verifica che non stai alterando il tuo HTTP REFERER.',
 			'nok' => 'Per favore verifica che non stai alterando il tuo HTTP REFERER.',
 			'ok' => 'Il tuo HTTP REFERER riconosciuto corrisponde al tuo server.',
 			'ok' => 'Il tuo HTTP REFERER riconosciuto corrisponde al tuo server.',

+ 4 - 0
app/i18n/nl/admin.php

@@ -57,6 +57,10 @@ return array(
 			'nok' => 'Feed tabel is slecht geconfigureerd.',
 			'nok' => 'Feed tabel is slecht geconfigureerd.',
 			'ok' => 'Feed tabel is ok.',
 			'ok' => 'Feed tabel is ok.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'U mist de PHP fileinfo (fileinfo package).',
+			'ok' => 'U hebt de fileinfo uitbreiding.',
+		),
 		'files' => 'Bestanden installatie',
 		'files' => 'Bestanden installatie',
 		'json' => array(
 		'json' => array(
 			'nok' => 'U mist JSON (php5-json package).',
 			'nok' => 'U mist JSON (php5-json package).',

+ 1 - 1
app/i18n/nl/gen.php

@@ -22,7 +22,7 @@ return array(
 	),
 	),
 	'auth' => array(
 	'auth' => array(
 		'email' => 'Email adres',
 		'email' => 'Email adres',
-		'keep_logged_in' => 'Ingelogd blijven voor <small>(1 maand)</small>',
+		'keep_logged_in' => 'Ingelogd blijven voor <small>(%s dagen)</small>',
 		'login' => 'Log in',
 		'login' => 'Log in',
 		'logout' => 'Log uit',
 		'logout' => 'Log uit',
 		'password' => array(
 		'password' => array(

+ 4 - 0
app/i18n/nl/install.php

@@ -56,6 +56,10 @@ return array(
 			'nok' => 'Controleer permissies van de <em>./data/favicons</em> map. HTTP server moet rechten hebben om er in te kunnen schrijven',
 			'nok' => 'Controleer permissies van de <em>./data/favicons</em> map. HTTP server moet rechten hebben om er in te kunnen schrijven',
 			'ok' => 'Permissies van de favicons map zijn goed.',
 			'ok' => 'Permissies van de favicons map zijn goed.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'U mist PHP fileinfo (fileinfo package).',
+			'ok' => 'U hebt de fileinfo uitbreiding.',
+		),
 		'http_referer' => array(
 		'http_referer' => array(
 			'nok' => 'Controleer a.u.b. dat u niet uw HTTP REFERER wijzigd.',
 			'nok' => 'Controleer a.u.b. dat u niet uw HTTP REFERER wijzigd.',
 			'ok' => 'Uw HTTP REFERER is bekend en komt overeen met uw server.',
 			'ok' => 'Uw HTTP REFERER is bekend en komt overeen met uw server.',

+ 4 - 0
app/i18n/ru/admin.php

@@ -57,6 +57,10 @@ return array(
 			'nok' => 'Таблица подписок (feed) неправильно настроена.',
 			'nok' => 'Таблица подписок (feed) неправильно настроена.',
 			'ok' => 'Таблица подписок (feed) настроена правильно.',
 			'ok' => 'Таблица подписок (feed) настроена правильно.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'У вас не установлено расширение PHP fileinfo (пакет fileinfo).',
+			'ok' => 'У вас установлено расширение fileinfo.',
+		),
 		'files' => 'Установка файлов',
 		'files' => 'Установка файлов',
 		'json' => array(
 		'json' => array(
 			'nok' => 'У вас не установлена библиотека для работы с JSON (пакет php5-json).',
 			'nok' => 'У вас не установлена библиотека для работы с JSON (пакет php5-json).',

+ 1 - 1
app/i18n/ru/gen.php

@@ -22,7 +22,7 @@ return array(
 	),
 	),
 	'auth' => array(
 	'auth' => array(
 		'email' => 'Email address',
 		'email' => 'Email address',
-		'keep_logged_in' => 'Keep me logged in <small>(1 month)</small>',
+		'keep_logged_in' => 'Keep me logged in <small>(%s дней)</small>',
 		'login' => 'Login',
 		'login' => 'Login',
 		'logout' => 'Logout',
 		'logout' => 'Logout',
 		'password' => array(
 		'password' => array(

+ 4 - 0
app/i18n/ru/install.php

@@ -56,6 +56,10 @@ return array(
 			'nok' => 'Проверьте права доступа к папке <em>./data/favicons</em> . Сервер HTTP должен иметь права на запись в эту папку.',
 			'nok' => 'Проверьте права доступа к папке <em>./data/favicons</em> . Сервер HTTP должен иметь права на запись в эту папку.',
 			'ok' => 'Права на папку значков в порядке.',
 			'ok' => 'Права на папку значков в порядке.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'У вас нет расширения PHP fileinfo (пакет fileinfo).',
+			'ok' => 'У вас установлено расширение fileinfo.',
+		),
 		'http_referer' => array(
 		'http_referer' => array(
 			'nok' => 'Убедитесь, что вы не изменяете ваш HTTP REFERER.',
 			'nok' => 'Убедитесь, что вы не изменяете ваш HTTP REFERER.',
 			'ok' => 'Ваш HTTP REFERER известен и соотвествует вашему серверу.',
 			'ok' => 'Ваш HTTP REFERER известен и соотвествует вашему серверу.',

+ 4 - 0
app/i18n/tr/admin.php

@@ -57,6 +57,10 @@ return array(
 			'nok' => 'Akış tablosu kötü yapılandırılmış.',
 			'nok' => 'Akış tablosu kötü yapılandırılmış.',
 			'ok' => 'Akış tablosu sorunsuz.',
 			'ok' => 'Akış tablosu sorunsuz.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'PHP fileinfo eksik (fileinfo package).',
+			'ok' => 'fileinfo eklentisi sorunsuz.',
+		),
 		'files' => 'Dosya kurulumu',
 		'files' => 'Dosya kurulumu',
 		'json' => array(
 		'json' => array(
 			'nok' => 'JSON eklentisi eksik (php5-json package).',
 			'nok' => 'JSON eklentisi eksik (php5-json package).',

+ 1 - 1
app/i18n/tr/gen.php

@@ -22,7 +22,7 @@ return array(
 	),
 	),
 	'auth' => array(
 	'auth' => array(
 		'email' => 'Email adresleri',
 		'email' => 'Email adresleri',
-		'keep_logged_in' => '<small>(1 ay)</small> oturumu açık tut',
+		'keep_logged_in' => '<small>(%s günler)</small> oturumu açık tut',
 		'login' => 'Giriş',
 		'login' => 'Giriş',
 		'logout' => 'Çıkış',
 		'logout' => 'Çıkış',
 		'password' => array(
 		'password' => array(

+ 4 - 0
app/i18n/tr/install.php

@@ -56,6 +56,10 @@ return array(
 			'nok' => '<em>./data/favicons</em> klasör yetkisini kontrol edin. HTTP yazma yetkisi olmalı',
 			'nok' => '<em>./data/favicons</em> klasör yetkisini kontrol edin. HTTP yazma yetkisi olmalı',
 			'ok' => 'Site ikonu klasörü yetkileri sorunsuz.',
 			'ok' => 'Site ikonu klasörü yetkileri sorunsuz.',
 		),
 		),
+		'fileinfo' => array(
+			'nok' => 'PHP fileinfo eksik (fileinfo package).',
+			'ok' => 'fileinfo eklentisi sorunsuz.',
+		),
 		'http_referer' => array(
 		'http_referer' => array(
 			'nok' => 'Lütfen HTTP REFERER değiştirmediğinize emin olun.',
 			'nok' => 'Lütfen HTTP REFERER değiştirmediğinize emin olun.',
 			'ok' => 'HTTP REFERER ve sunucunuz arası iletişim sorunsuz.',
 			'ok' => 'HTTP REFERER ve sunucunuz arası iletişim sorunsuz.',

+ 9 - 3
app/install.php

@@ -230,7 +230,7 @@ function saveStep3() {
 			$_SESSION['bd_error'] = '';
 			$_SESSION['bd_error'] = '';
 			header('Location: index.php?step=4');
 			header('Location: index.php?step=4');
 		} else {
 		} else {
-			$_SESSION['bd_error'] = empty($config_array['db']['bd_error']) ? 'Unknown error!' : $config_array['db']['bd_error'];
+			$_SESSION['bd_error'] = empty($config_array['db']['error']) ? 'Unknown error!' : $config_array['db']['error'];
 		}
 		}
 	}
 	}
 	invalidateHttpCache();
 	invalidateHttpCache();
@@ -375,7 +375,7 @@ function checkDbUser(&$dbOptions) {
 		}
 		}
 	} catch (PDOException $e) {
 	} catch (PDOException $e) {
 		$ok = false;
 		$ok = false;
-		$dbOptions['bd_error'] = $e->getMessage();
+		$dbOptions['error'] = $e->getMessage();
 	}
 	}
 	return $ok;
 	return $ok;
 }
 }
@@ -478,6 +478,12 @@ function printStep1() {
 	<p class="alert alert-error"><span class="alert-head"><?php echo _t('gen.short.damn'); ?></span> <?php echo _t('install.check.xml.nok'); ?></p>
 	<p class="alert alert-error"><span class="alert-head"><?php echo _t('gen.short.damn'); ?></span> <?php echo _t('install.check.xml.nok'); ?></p>
 	<?php } ?>
 	<?php } ?>
 
 
+	<?php if ($res['fileinfo'] == 'ok') { ?>
+	<p class="alert alert-success"><span class="alert-head"><?php echo _t('gen.short.ok'); ?></span> <?php echo _t('install.check.fileinfo.ok'); ?></p>
+	<?php } else { ?>
+	<p class="alert alert-error"><span class="alert-head"><?php echo _t('gen.short.damn'); ?></span> <?php echo _t('install.check.fileinfo.nok'); ?></p>
+	<?php } ?>
+
 	<?php if ($res['data'] == 'ok') { ?>
 	<?php if ($res['data'] == 'ok') { ?>
 	<p class="alert alert-success"><span class="alert-head"><?php echo _t('gen.short.ok'); ?></span> <?php echo _t('install.check.data.ok'); ?></p>
 	<p class="alert alert-success"><span class="alert-head"><?php echo _t('gen.short.ok'); ?></span> <?php echo _t('install.check.data.ok'); ?></p>
 	<?php } else { ?>
 	<?php } else { ?>
@@ -634,7 +640,7 @@ function printStep3() {
 		<div class="form-group">
 		<div class="form-group">
 			<label class="group-name" for="host"><?php echo _t('install.bdd.host'); ?></label>
 			<label class="group-name" for="host"><?php echo _t('install.bdd.host'); ?></label>
 			<div class="group-controls">
 			<div class="group-controls">
-				<input type="text" id="host" name="host" pattern="[0-9A-Za-z_.-]{1,64}(:[0-9]{2,5})?" value="<?php echo isset($_SESSION['bd_host']) ? $_SESSION['bd_host'] : $system_default_config->db['host']; ?>" tabindex="2" />
+				<input type="text" id="host" name="host" pattern="[0-9A-Z/a-z_.-]{1,64}(:[0-9]{2,5})?" value="<?php echo isset($_SESSION['bd_host']) ? $_SESSION['bd_host'] : $system_default_config->db['host']; ?>" tabindex="2" />
 			</div>
 			</div>
 		</div>
 		</div>
 
 

+ 1 - 1
app/views/auth/formLogin.phtml

@@ -20,7 +20,7 @@
 		<div>
 		<div>
 			<label class="checkbox" for="keep_logged_in">
 			<label class="checkbox" for="keep_logged_in">
 				<input type="checkbox" name="keep_logged_in" id="keep_logged_in" value="1" />
 				<input type="checkbox" name="keep_logged_in" id="keep_logged_in" value="1" />
-				<?php echo _t('gen.auth.keep_logged_in'); ?>
+				<?php echo _t('gen.auth.keep_logged_in', $this->cookie_days); ?>
 			</label>
 			</label>
 			<br />
 			<br />
 		</div>
 		</div>

+ 60 - 41
app/views/helpers/export/articles.phtml

@@ -1,47 +1,66 @@
 <?php
 <?php
-    $username = Minz_Session::param('currentUser', '_');
+$username = Minz_Session::param('currentUser', '_');
 
 
-    $articles = array(
-        'id' => 'user/' . str_replace('/', '', $username) . '/state/org.freshrss/' . $this->type,
-        'title' => $this->list_title,
-        'author' => $username,
-        'items' => array()
-    );
+$options = 0;
+if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
+	$options = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
+}
 
 
-    foreach ($this->entries as $entry) {
-        if (!isset($this->feed)) {
-            $feed = FreshRSS_CategoryDAO::findFeed($this->categories, $entry->feed ());
-        } else {
-            $feed = $this->feed;
-        }
+$articles = array(
+	'id' => 'user/' . str_replace('/', '', $username) . '/state/org.freshrss/' . $this->type,
+	'title' => $this->list_title,
+	'author' => $username,
+	'items' => array(),
+);
 
 
-        $articles['items'][] = array(
-            'id' => $entry->guid(),
-            'categories' => array_values($entry->tags()),
-            'title' => $entry->title(),
-            'author' => $entry->author(),
-            'published' => $entry->date(true),
-            'updated' => $entry->date(true),
-            'alternate' => array(array(
-                'href' => $entry->link(),
-                'type' => 'text/html'
-            )),
-            'content' => array(
-                'content' => $entry->content()
-            ),
-            'origin' => array(
-                'streamId' => $feed->id(),
-                'title' => $feed->name(),
-                'htmlUrl' => $feed->website(),
-                'feedUrl' => $feed->url()
-            )
-        );
-    }
+echo rtrim(json_encode($articles, $options), " ]}\n\r\t"), "\n";
+$first = true;
 
 
-    $options = 0;
-    if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
-        $options = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
-    }
+foreach ($this->entriesRaw as $entryRaw) {
+	if (empty($entryRaw)) {
+		continue;
+	}
+	$entry = FreshRSS_EntryDAO::daoToEntry($entryRaw);
+	if (!isset($this->feed)) {
+		$feed = FreshRSS_CategoryDAO::findFeed($this->categories, $entry->feed());
+		if ($feed == null) {
+			$feed = $entry->feed(true);
+		}
+	} else {
+		$feed = $this->feed;
+	}
 
 
-    echo json_encode($articles, $options);
-?>
+	$article = array(
+		'id' => $entry->guid(),
+		'categories' => array_values($entry->tags()),
+		'title' => $entry->title(),
+		'author' => $entry->author(),
+		'published' => $entry->date(true),
+		'updated' => $entry->date(true),
+		'alternate' => array(array(
+			'href' => $entry->link(),
+			'type' => 'text/html',
+		)),
+		'content' => array(
+			'content' => $entry->content(),
+		),
+		'origin' => array(
+			'streamId' => $feed == null ? '' : $feed->id(),
+			'title' => $feed == null ? '' : $feed->name(),
+			'htmlUrl' => $feed == null ? '' : $feed->website(),
+			'feedUrl' => $feed == null ? '' : $feed->url(),
+		)
+	);
+
+	$line = json_encode($article, $options);
+	if ($line != '') {
+		if ($first) {
+			$first = false;
+		} else {
+			echo ",\n";
+		}
+		echo $line;
+	}
+}
+
+echo "\n]}\n";

+ 25 - 3
cli/README.md

@@ -32,11 +32,11 @@ Options in parenthesis are optional.
 ```sh
 ```sh
 cd /usr/share/FreshRSS
 cd /usr/share/FreshRSS
 
 
-./cli/do-install.php --default_user admin --auth_type form  ( --environment production --base_url https://rss.example.net/ --title FreshRSS --allow_anonymous --api_enabled --db-type mysql --db-host localhost:3306 --db-user freshrss --db-password dbPassword123 --db-base freshrss --db-prefix freshrss )
-# --auth_type can be: 'form' (recommended), 'http_auth' (using the Web server access control), 'none' (dangerous)
+./cli/do-install.php --default_user admin ( --auth_type form --environment production --base_url https://rss.example.net/ --title FreshRSS --allow_anonymous --api_enabled --db-type mysql --db-host localhost:3306 --db-user freshrss --db-password dbPassword123 --db-base freshrss --db-prefix freshrss )
+# --auth_type can be: 'form' (default), 'http_auth' (using the Web server access control), 'none' (dangerous)
 # --db-type can be: 'sqlite' (default), 'mysql' (MySQL or MariaDB), 'pgsql' (PostgreSQL)
 # --db-type can be: 'sqlite' (default), 'mysql' (MySQL or MariaDB), 'pgsql' (PostgreSQL)
 # --environment can be: 'production' (default), 'development' (for additional log messages)
 # --environment can be: 'production' (default), 'development' (for additional log messages)
-# --db-prefix is an optional prefix in front of the names of the tables
+# --db-prefix is an optional prefix in front of the names of the tables. We suggest using 'freshrss_'
 # This command does not create the default user. Do that with ./cli/create-user.php
 # This command does not create the default user. Do that with ./cli/create-user.php
 
 
 ./cli/create-user.php --user username ( --password 'password' --api-password 'api_password' --language en --email user@example.net --token 'longRandomString' --no-default-feeds )
 ./cli/create-user.php --user username ( --password 'password' --api-password 'api_password' --language en --email user@example.net --token 'longRandomString' --no-default-feeds )
@@ -55,4 +55,26 @@ cd /usr/share/FreshRSS
 ./cli/export-opml-for-user.php --user username > /path/to/file.opml.xml
 ./cli/export-opml-for-user.php --user username > /path/to/file.opml.xml
 
 
 ./cli/export-zip-for-user.php --user username ( --max-feed-entries 100 ) > /path/to/file.zip
 ./cli/export-zip-for-user.php --user username ( --max-feed-entries 100 ) > /path/to/file.zip
+
+./cli/user-info.php -h --user username
+# -h is to use a human-readable format
+# --user can be a username, or '*' to loop on all users
+# Returns a * if the user is admin, the name of the user, the date/time of last action, and the size occupied
+```
+
+
+## Unix piping
+
+It is possible to invoke a command multiple times, e.g. with different usernames, thanks to the `xargs -n1` command.
+
+Example showing user information for all users which username starts with 'a':
+
+```sh
+./cli/list-users.php | grep '^a' | xargs -n1 ./cli/user-info.php -h --user
+```
+
+Example showing all users ranked by date of last activity:
+
+```sh
+./cli/user-info.php -h --user '*' | sort -k2 -r
 ```
 ```

+ 3 - 0
cli/create-user.php

@@ -43,6 +43,9 @@ if (!$ok) {
 
 
 invalidateHttpCache(FreshRSS_Context::$system_conf->default_user);
 invalidateHttpCache(FreshRSS_Context::$system_conf->default_user);
 
 
+echo '• Remember to refresh the feeds of the user: ', $username , "\n",
+	"\t", './cli/actualize-user.php --user ', $username, "\n";
+
 accessRights();
 accessRights();
 
 
 done($ok);
 done($ok);

+ 7 - 7
cli/do-install.php

@@ -26,12 +26,12 @@ $dBparams = array(
 
 
 $options = getopt('', array_merge($params, $dBparams));
 $options = getopt('', array_merge($params, $dBparams));
 
 
-if (empty($options['default_user']) || empty($options['auth_type'])) {
-	fail('Usage: ' . basename(__FILE__) . " --default_user admin --auth_type form" .
-		" ( --environment production --base_url https://rss.example.net/" .
+if (empty($options['default_user'])) {
+	fail('Usage: ' . basename(__FILE__) . " --default_user admin ( --auth_type form" .
+		" --environment production --base_url https://rss.example.net/" .
 		" --title FreshRSS --allow_anonymous --api_enabled" .
 		" --title FreshRSS --allow_anonymous --api_enabled" .
 		" --db-type mysql --db-host localhost:3306 --db-user freshrss --db-password dbPassword123" .
 		" --db-type mysql --db-host localhost:3306 --db-user freshrss --db-password dbPassword123" .
-		" --db-base freshrss --db-prefix freshrss )");
+		" --db-base freshrss --db-prefix freshrss_ )");
 }
 }
 
 
 fwrite(STDERR, 'FreshRSS install…' . "\n");
 fwrite(STDERR, 'FreshRSS install…' . "\n");
@@ -51,7 +51,7 @@ if (!ctype_alnum($options['default_user'])) {
 	fail('FreshRSS invalid default username (must be ASCII alphanumeric): ' . $options['default_user']);
 	fail('FreshRSS invalid default username (must be ASCII alphanumeric): ' . $options['default_user']);
 }
 }
 
 
-if (!in_array($options['auth_type'], array('form', 'http_auth', 'none'))) {
+if (isset($options['auth_type']) && !in_array($options['auth_type'], array('form', 'http_auth', 'none'))) {
 	fail('FreshRSS invalid authentication method (auth_type must be one of { form, http_auth, none }: ' . $options['auth_type']);
 	fail('FreshRSS invalid authentication method (auth_type must be one of { form, http_auth, none }: ' . $options['auth_type']);
 }
 }
 
 
@@ -86,11 +86,11 @@ if (file_put_contents(join_path(DATA_PATH, 'config.php'), "<?php\n return " . va
 $config['db']['default_user'] = $config['default_user'];
 $config['db']['default_user'] = $config['default_user'];
 if (!checkDb($config['db'])) {
 if (!checkDb($config['db'])) {
 	@unlink(join_path(DATA_PATH, 'config.php'));
 	@unlink(join_path(DATA_PATH, 'config.php'));
-	fail('FreshRSS database error: ' . (empty($config['db']['bd_error']) ? 'Unknown error' : $config['db']['bd_error']));
+	fail('FreshRSS database error: ' . (empty($config['db']['error']) ? 'Unknown error' : $config['db']['error']));
 }
 }
 
 
 echo '• Remember to create the default user: ', $config['default_user'] , "\n",
 echo '• Remember to create the default user: ', $config['default_user'] , "\n",
-	"\t", './cli/create-user.php --user ', $config['default_user'] , " --password 'password' --more-options\n";
+	"\t", './cli/create-user.php --user ', $config['default_user'], " --password 'password' --more-options\n";
 
 
 accessRights();
 accessRights();
 
 

+ 35 - 0
cli/user-info.php

@@ -0,0 +1,35 @@
+#!/usr/bin/php
+<?php
+require('_cli.php');
+
+$options = getopt('h', array(
+		'user:',
+	));
+
+if (empty($options['user'])) {
+	fail('Usage: ' . basename(__FILE__) . " -h --user username");
+}
+
+$users = $options['user'] === '*' ? listUsers() : array($options['user']);
+
+foreach ($users as $username) {
+	$username = cliInitUser($username);
+
+	$entryDAO = FreshRSS_Factory::createEntryDao($username);
+
+	echo $username === FreshRSS_Context::$system_conf->default_user ? '*' : ' ', "\t";
+
+	if (isset($options['h'])) {	//Human format
+		echo
+			$username, "\t",
+			date('c', FreshRSS_UserDAO::mtime($username)), "\t",
+			format_bytes($entryDAO->size()), "\t",
+			"\n";
+	} else {
+		echo
+			$username, "\t",
+			FreshRSS_UserDAO::mtime($username), "\t",
+			$entryDAO->size(), "\t",
+			"\n";
+	}
+}

+ 1 - 1
constants.php

@@ -1,5 +1,5 @@
 <?php
 <?php
-define('FRESHRSS_VERSION', '1.6.1');
+define('FRESHRSS_VERSION', '1.6.2');
 define('FRESHRSS_WEBSITE', 'http://freshrss.org');
 define('FRESHRSS_WEBSITE', 'http://freshrss.org');
 define('FRESHRSS_WIKI', 'http://doc.freshrss.org');
 define('FRESHRSS_WIKI', 'http://doc.freshrss.org');
 
 

+ 4 - 1
data/config.default.php

@@ -46,7 +46,7 @@ return array(
 	#	`http_auth` is an access controled by the HTTP Web server (e.g. `/FreshRSS/p/i/.htaccess` for Apache)
 	#	`http_auth` is an access controled by the HTTP Web server (e.g. `/FreshRSS/p/i/.htaccess` for Apache)
 	#		if you use `http_auth`, remember to protect only `/FreshRSS/p/i/`,
 	#		if you use `http_auth`, remember to protect only `/FreshRSS/p/i/`,
 	#		and in particular not protect `/FreshRSS/p/api/` if you would like to use the API (different login system).
 	#		and in particular not protect `/FreshRSS/p/api/` if you would like to use the API (different login system).
-	'auth_type' => 'none',
+	'auth_type' => 'form',
 
 
 	# Allow or not the use of the API, used for mobile apps.
 	# Allow or not the use of the API, used for mobile apps.
 	#	End-point is http://example.net/FreshRSS/p/api/greader.php
 	#	End-point is http://example.net/FreshRSS/p/api/greader.php
@@ -74,6 +74,9 @@ return array(
 
 
 	'limits' => array(
 	'limits' => array(
 
 
+		# Duration in seconds of the login cookie.
+		'cookie_duration' => 2592000,
+
 		# Duration in seconds of the SimplePie cache,
 		# Duration in seconds of the SimplePie cache,
 		#	during which a query to the RSS feed will return the local cached version.
 		#	during which a query to the RSS feed will return the local cached version.
 		# Especially important for multi-user setups.
 		# Especially important for multi-user setups.

+ 3 - 1
extensions/README.md

@@ -1,3 +1,5 @@
 # FreshRSS extensions
 # FreshRSS extensions
 
 
-You may place in this directory some custom extensions for FreshRSS.
+You may place custom extensions for FreshRSS in this directory.
+
+You can find some extensions in our [GitHub repository](https://github.com/FreshRSS/Extensions).

+ 6 - 0
lib/Minz/ModelPdo.php

@@ -116,6 +116,12 @@ class Minz_ModelPdo {
 		self::$sharedBd = null;
 		self::$sharedBd = null;
 		self::$sharedPrefix = '';
 		self::$sharedPrefix = '';
 	}
 	}
+
+	public function disableBuffering() {
+		if ((self::$sharedDbType === 'mysql') && defined('PDO::MYSQL_ATTR_USE_BUFFERED_QUERY')) {
+			$this->bd->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
+		}
+	}
 }
 }
 
 
 class MinzPDO extends PDO {
 class MinzPDO extends PDO {

+ 5 - 2
lib/favicons.php

@@ -9,7 +9,7 @@ $default_favicon = PUBLIC_PATH . '/themes/icons/default_favicon.ico';
 function download_favicon($website, $dest) {
 function download_favicon($website, $dest) {
 	global $favicons_dir, $default_favicon;
 	global $favicons_dir, $default_favicon;
 
 
-	syslog(LOG_DEBUG, 'FreshRSS Favicon discovery GET ' . $website);
+	syslog(LOG_INFO, 'FreshRSS Favicon discovery GET ' . $website);
 	$favicon_getter = new \Favicon\Favicon();
 	$favicon_getter = new \Favicon\Favicon();
 	$favicon_getter->setCacheDir($favicons_dir);
 	$favicon_getter->setCacheDir($favicons_dir);
 	$favicon_url = $favicon_getter->get($website);
 	$favicon_url = $favicon_getter->get($website);
@@ -18,11 +18,12 @@ function download_favicon($website, $dest) {
 		return @copy($default_favicon, $dest);
 		return @copy($default_favicon, $dest);
 	}
 	}
 
 
-	syslog(LOG_DEBUG, 'FreshRSS Favicon GET ' . $favicon_url);
+	syslog(LOG_INFO, 'FreshRSS Favicon GET ' . $favicon_url);
 	$c = curl_init($favicon_url);
 	$c = curl_init($favicon_url);
 	curl_setopt($c, CURLOPT_HEADER, false);
 	curl_setopt($c, CURLOPT_HEADER, false);
 	curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
 	curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
 	curl_setopt($c, CURLOPT_BINARYTRANSFER, true);
 	curl_setopt($c, CURLOPT_BINARYTRANSFER, true);
+	curl_setopt($c, CURLOPT_USERAGENT, 'FreshRSS/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ')');
 	$img_raw = curl_exec($c);
 	$img_raw = curl_exec($c);
 	$status_code = curl_getinfo($c, CURLINFO_HTTP_CODE);
 	$status_code = curl_getinfo($c, CURLINFO_HTTP_CODE);
 	curl_close($c);
 	curl_close($c);
@@ -34,6 +35,8 @@ function download_favicon($website, $dest) {
 			fclose($file);
 			fclose($file);
 			return true;
 			return true;
 		}
 		}
+	} else {
+		syslog(LOG_WARNING, 'FreshRSS Favicon GET ' . $favicon_url . ' error ' . $status_code);
 	}
 	}
 
 
 	return false;
 	return false;

+ 8 - 2
lib/lib_install.php

@@ -15,6 +15,7 @@ function checkRequirements() {
 	$pdo = $pdo_mysql || $pdo_sqlite || $pdo_pgsql;
 	$pdo = $pdo_mysql || $pdo_sqlite || $pdo_pgsql;
 	$pcre = extension_loaded('pcre');
 	$pcre = extension_loaded('pcre');
 	$ctype = extension_loaded('ctype');
 	$ctype = extension_loaded('ctype');
+	$fileinfo = extension_loaded('fileinfo');
 	$dom = class_exists('DOMDocument');
 	$dom = class_exists('DOMDocument');
 	$xml = function_exists('xml_parser_create');
 	$xml = function_exists('xml_parser_create');
 	$json = function_exists('json_encode');
 	$json = function_exists('json_encode');
@@ -34,6 +35,7 @@ function checkRequirements() {
 		'pdo' => $pdo ? 'ok' : 'ko',
 		'pdo' => $pdo ? 'ok' : 'ko',
 		'pcre' => $pcre ? 'ok' : 'ko',
 		'pcre' => $pcre ? 'ok' : 'ko',
 		'ctype' => $ctype ? 'ok' : 'ko',
 		'ctype' => $ctype ? 'ok' : 'ko',
+		'fileinfo' => $fileinfo ? 'ok' : 'ko',
 		'dom' => $dom ? 'ok' : 'ko',
 		'dom' => $dom ? 'ok' : 'ko',
 		'xml' => $xml ? 'ok' : 'ko',
 		'xml' => $xml ? 'ok' : 'ko',
 		'json' => $json ? 'ok' : 'ko',
 		'json' => $json ? 'ok' : 'ko',
@@ -42,7 +44,7 @@ function checkRequirements() {
 		'users' => $users ? 'ok' : 'ko',
 		'users' => $users ? 'ok' : 'ko',
 		'favicons' => $favicons ? 'ok' : 'ko',
 		'favicons' => $favicons ? 'ok' : 'ko',
 		'http_referer' => $http_referer ? 'ok' : 'ko',
 		'http_referer' => $http_referer ? 'ok' : 'ko',
-		'all' => $php && $minz && $curl && $pdo && $pcre && $ctype && $dom && $xml &&
+		'all' => $php && $minz && $curl && $pdo && $pcre && $ctype && $fileinfo && $dom && $xml &&
 		         $data && $cache && $users && $favicons && $http_referer ?
 		         $data && $cache && $users && $favicons && $http_referer ?
 		         'ok' : 'ko'
 		         'ok' : 'ko'
 	);
 	);
@@ -54,8 +56,8 @@ function generateSalt() {
 
 
 function checkDb(&$dbOptions) {
 function checkDb(&$dbOptions) {
 	$dsn = '';
 	$dsn = '';
+	$driver_options = null;
 	try {
 	try {
-		$driver_options = null;
 		switch ($dbOptions['type']) {
 		switch ($dbOptions['type']) {
 		case 'mysql':
 		case 'mysql':
 			include_once(APP_PATH . '/SQL/install.sql.mysql.php');
 			include_once(APP_PATH . '/SQL/install.sql.mysql.php');
@@ -99,8 +101,12 @@ function checkDb(&$dbOptions) {
 		default:
 		default:
 			return false;
 			return false;
 		}
 		}
+
+		$c = new PDO($dsn, $dbOptions['user'], $dbOptions['password'], $driver_options);
+		$res = $c->query('SELECT 1');
 	} catch (PDOException $e) {
 	} catch (PDOException $e) {
 		$dsn = '';
 		$dsn = '';
+		syslog(LOG_DEBUG, 'FreshRSS SQL warning: ' . $e->getMessage());
 		$dbOptions['error'] = $e->getMessage();
 		$dbOptions['error'] = $e->getMessage();
 	}
 	}
 	$dbOptions['dsn'] = $dsn;
 	$dbOptions['dsn'] = $dsn;

+ 3 - 0
lib/lib_rss.php

@@ -127,6 +127,8 @@ function format_bytes($bytes, $precision = 2, $system = 'IEC') {
 	} elseif ($system === 'SI') {
 	} elseif ($system === 'SI') {
 		$base = 1000;
 		$base = 1000;
 		$units = array('B', 'KB', 'MB', 'GB', 'TB');
 		$units = array('B', 'KB', 'MB', 'GB', 'TB');
+	} else {
+		return format_number($bytes, $precision);
 	}
 	}
 	$bytes = max(intval($bytes), 0);
 	$bytes = max(intval($bytes), 0);
 	$pow = $bytes === 0 ? 0 : floor(log($bytes) / log($base));
 	$pow = $bytes === 0 ? 0 : floor(log($bytes) / log($base));
@@ -396,6 +398,7 @@ function check_install_php() {
 		'pdo' => $pdo_mysql || $pdo_sqlite,
 		'pdo' => $pdo_mysql || $pdo_sqlite,
 		'pcre' => extension_loaded('pcre'),
 		'pcre' => extension_loaded('pcre'),
 		'ctype' => extension_loaded('ctype'),
 		'ctype' => extension_loaded('ctype'),
+		'fileinfo' => extension_loaded('fileinfo'),
 		'dom' => class_exists('DOMDocument'),
 		'dom' => class_exists('DOMDocument'),
 		'json' => extension_loaded('json'),
 		'json' => extension_loaded('json'),
 		'zip' => extension_loaded('zip'),
 		'zip' => extension_loaded('zip'),

+ 1 - 1
p/scripts/main.js

@@ -1354,7 +1354,6 @@ function init_beforeDOM() {
 		window.setTimeout(init_beforeDOM, 100);
 		window.setTimeout(init_beforeDOM, 100);
 		return;
 		return;
 	}
 	}
-	init_confirm_action();
 	if (['normal', 'reader', 'global'].indexOf(context.current_view) >= 0) {
 	if (['normal', 'reader', 'global'].indexOf(context.current_view) >= 0) {
 		inject_script('jquery.sticky-kit.min.js');
 		inject_script('jquery.sticky-kit.min.js');
 		init_normal();
 		init_normal();
@@ -1372,6 +1371,7 @@ function init_afterDOM() {
 	init_notifications();
 	init_notifications();
 	$stream = $('#stream');
 	$stream = $('#stream');
 	if ($stream.length > 0) {
 	if ($stream.length > 0) {
+		init_confirm_action();
 		init_load_more($stream);
 		init_load_more($stream);
 		init_posts();
 		init_posts();
 		init_nav_entries();
 		init_nav_entries();