Просмотр исходного кода

Merge pull request #1786 from FreshRSS/dev

FreshRSS 1.10.0
Alexandre Alapetite 8 лет назад
Родитель
Сommit
5ebeb9e3e5
100 измененных файлов с 785 добавлено и 178 удалено
  1. 7 12
      .travis.yml
  2. 26 0
      CHANGELOG.md
  3. 4 0
      CREDITS.md
  4. 1 0
      README.fr.md
  5. 1 0
      README.md
  6. 4 6
      app/Controllers/configureController.php
  7. 1 3
      app/Controllers/entryController.php
  8. 1 1
      app/Controllers/extensionController.php
  9. 17 6
      app/Controllers/feedController.php
  10. 1 1
      app/Controllers/indexController.php
  11. 12 4
      app/Controllers/subscriptionController.php
  12. 41 17
      app/Controllers/userController.php
  13. 6 0
      app/Models/Auth.php
  14. 3 2
      app/Models/CategoryDAO.php
  15. 6 2
      app/Models/ConfigurationSetter.php
  16. 49 14
      app/Models/EntryDAO.php
  17. 21 9
      app/Models/Feed.php
  18. 25 11
      app/Models/FeedDAO.php
  19. 148 0
      app/Models/ReadingMode.php
  20. 1 1
      app/SQL/install.sql.mysql.php
  21. 1 1
      app/SQL/install.sql.pgsql.php
  22. 1 1
      app/SQL/install.sql.sqlite.php
  23. 3 0
      app/i18n/cz/admin.php
  24. 6 0
      app/i18n/cz/conf.php
  25. 4 0
      app/i18n/cz/feedback.php
  26. 1 0
      app/i18n/cz/gen.php
  27. 7 1
      app/i18n/cz/sub.php
  28. 3 0
      app/i18n/de/admin.php
  29. 6 0
      app/i18n/de/conf.php
  30. 4 0
      app/i18n/de/feedback.php
  31. 1 0
      app/i18n/de/gen.php
  32. 7 1
      app/i18n/de/sub.php
  33. 3 0
      app/i18n/en/admin.php
  34. 6 0
      app/i18n/en/conf.php
  35. 4 0
      app/i18n/en/feedback.php
  36. 1 0
      app/i18n/en/gen.php
  37. 4 4
      app/i18n/en/index.php
  38. 7 1
      app/i18n/en/sub.php
  39. 3 0
      app/i18n/es/admin.php
  40. 6 0
      app/i18n/es/conf.php
  41. 4 0
      app/i18n/es/feedback.php
  42. 1 0
      app/i18n/es/gen.php
  43. 7 1
      app/i18n/es/sub.php
  44. 3 0
      app/i18n/fr/admin.php
  45. 6 0
      app/i18n/fr/conf.php
  46. 4 0
      app/i18n/fr/feedback.php
  47. 1 0
      app/i18n/fr/gen.php
  48. 1 1
      app/i18n/fr/index.php
  49. 7 1
      app/i18n/fr/sub.php
  50. 3 0
      app/i18n/he/admin.php
  51. 6 0
      app/i18n/he/conf.php
  52. 4 0
      app/i18n/he/feedback.php
  53. 1 0
      app/i18n/he/gen.php
  54. 7 1
      app/i18n/he/sub.php
  55. 3 0
      app/i18n/it/admin.php
  56. 6 0
      app/i18n/it/conf.php
  57. 4 0
      app/i18n/it/feedback.php
  58. 1 0
      app/i18n/it/gen.php
  59. 7 1
      app/i18n/it/sub.php
  60. 10 7
      app/i18n/kr/admin.php
  61. 6 0
      app/i18n/kr/conf.php
  62. 5 1
      app/i18n/kr/feedback.php
  63. 1 0
      app/i18n/kr/gen.php
  64. 9 3
      app/i18n/kr/sub.php
  65. 3 0
      app/i18n/nl/admin.php
  66. 6 0
      app/i18n/nl/conf.php
  67. 4 0
      app/i18n/nl/feedback.php
  68. 1 0
      app/i18n/nl/gen.php
  69. 10 4
      app/i18n/nl/sub.php
  70. 3 0
      app/i18n/pt-br/admin.php
  71. 6 0
      app/i18n/pt-br/conf.php
  72. 4 0
      app/i18n/pt-br/feedback.php
  73. 1 0
      app/i18n/pt-br/gen.php
  74. 7 1
      app/i18n/pt-br/sub.php
  75. 3 0
      app/i18n/ru/admin.php
  76. 6 0
      app/i18n/ru/conf.php
  77. 4 0
      app/i18n/ru/feedback.php
  78. 1 0
      app/i18n/ru/gen.php
  79. 7 1
      app/i18n/ru/sub.php
  80. 3 0
      app/i18n/tr/admin.php
  81. 6 0
      app/i18n/tr/conf.php
  82. 4 0
      app/i18n/tr/feedback.php
  83. 1 0
      app/i18n/tr/gen.php
  84. 7 1
      app/i18n/tr/sub.php
  85. 10 7
      app/i18n/zh-cn/admin.php
  86. 6 0
      app/i18n/zh-cn/conf.php
  87. 4 0
      app/i18n/zh-cn/feedback.php
  88. 15 14
      app/i18n/zh-cn/gen.php
  89. 4 4
      app/i18n/zh-cn/index.php
  90. 7 1
      app/i18n/zh-cn/sub.php
  91. 1 1
      app/layout/aside_feed.phtml
  92. 14 15
      app/layout/nav_menu.phtml
  93. 2 2
      app/views/configure/archiving.phtml
  94. 10 1
      app/views/configure/display.phtml
  95. 30 0
      app/views/configure/shortcut.phtml
  96. 13 8
      app/views/helpers/feed/update.phtml
  97. 4 0
      app/views/helpers/javascript_vars.phtml
  98. 1 1
      app/views/index/global.phtml
  99. 1 1
      app/views/index/normal.phtml
  100. 16 2
      app/views/index/reader.phtml

+ 7 - 12
.travis.yml

@@ -5,18 +5,20 @@ php:
   - '5.6'
   - '7.0'
   - '7.1'
+  - '7.2'
   - hhvm
   - nightly
 
 install:
   # newest version without https://github.com/squizlabs/PHP_CodeSniffer/pull/1404
-  - pear install PHP_CodeSniffer-3.0.0RC4
+  - composer global require squizlabs/php_codesniffer "<=3.0.0RC4"
 
 script:
   - phpenv rehash
   - |
     if [[ $VALIDATE_STANDARD == yes ]]; then
-      phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p
+      COMPOSER_BIN=$(composer global config --absolute bin-dir)
+      $COMPOSER_BIN/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p
     fi
   - |
     if [[ $CHECK_TRANSLATION == yes ]]; then
@@ -29,18 +31,11 @@ env:
 matrix:
   fast_finish: true
   include:
+    # PHP 5.3 only runs on Ubuntu 12.04 (precise), not 14.04 (trusty)
     - php: "5.3"
       dist: precise
-    - php: "7.1"
+    - php: "7.2"
       env: CHECK_TRANSLATION=yes VALIDATE_STANDARD=no
   allow_failures:
-    # PHP 5.3 only runs on Ubuntu 12.04 (precise), not 14.04 (trusty)
-    - php: "5.3"
-      dist: precise
-    - php: "5.4"
-    - php: "5.5"
-    - php: "5.6"
-    - php: "7.0"
-    - php: hhvm
-    - php: nightly
     - env: CHECK_TRANSLATION=yes VALIDATE_STANDARD=no
+    - dist: precise

+ 26 - 0
CHANGELOG.md

@@ -1,5 +1,31 @@
 # FreshRSS changelog
 
+## 2018-02-24 FreshRSS 1.10.0
+
+* API
+	* Add compatibility with FeedMe 3.5.3+ on Android [#1774](https://github.com/FreshRSS/FreshRSS/pull/1774)
+* Features
+	* Ability to pause feeds, and to hide them from categories [#1750](https://github.com/FreshRSS/FreshRSS/pull/1750)
+	* Ability for the admin to reset a user’s password [#960](https://github.com/FreshRSS/FreshRSS/issues/960)
+* Security
+	* Allow HTTP Auth login with `REDIRECT_REMOTE_USER` when using Apache internal redirect [#1772](https://github.com/FreshRSS/FreshRSS/pull/1772)
+* UI
+	* New icons for marking as favourite and marking as read in the Reading View [#603](https://github.com/FreshRSS/FreshRSS/issues/603)
+	* Add shortcuts to switch views [#1755](https://github.com/FreshRSS/FreshRSS/pull/1755)
+* Bug fixing
+	* Fix login bug when HTTP `REMOTE_USER` changes (used by YunoHost) [#1756](https://github.com/FreshRSS/FreshRSS/pull/1756)
+	* Fix warning in PHP 7.2 [#1739](https://github.com/FreshRSS/FreshRSS/pull/1739)
+* Extensions
+	* Allow extensions to define their own reading view [#1714](https://github.com/FreshRSS/FreshRSS/pull/1714)
+* I18n
+	* Updated Chinese [#1769](https://github.com/FreshRSS/FreshRSS/pull/1769)
+	* Updated Dutch [#1792](https://github.com/FreshRSS/FreshRSS/pull/1792)
+	* Updated Korean [#1776](https://github.com/FreshRSS/FreshRSS/pull/1776)
+* Misc.
+	* More sites in `force-https.default.txt` [#1745](https://github.com/FreshRSS/FreshRSS/pull/1745)
+	* Trim URLs when adding new feeds [#1778](https://github.com/FreshRSS/FreshRSS/pull/1778)
+
+
 ## 2017-12-17 FreshRSS 1.9.0
 
 * Features

+ 4 - 0
CREDITS.md

@@ -19,6 +19,7 @@ People are sorted by name so please keep this order.
 * [danc](https://github.com/danc): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=danc), [Web](http://tintouli.free.fr/)
 * [David Souza](https://github.com/araujo0205): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:araujo0205), [Web](http://davidsouza.tech/)
 * [dswd](https://github.com/dswd): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:dswd)
+* [Django Janny](https://github.com/keltroth): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:keltroth)
 * [ealdraed](https://github.com/ealdraed): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=ealdraed)
 * [Frans de Jonge](https://github.com/Frenzie): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Frenzie), [Web](http://fransdejonge.com/)
 * [gsongsong](https://github.com/gsongsong): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:gsongsong)
@@ -26,6 +27,7 @@ People are sorted by name so please keep this order.
 * [Guillaume Hayot](https://github.com/postblue): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:postblue), [Web](https://postblue.info/)
 * [hckweb](https://github.com/hckweb): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=hckweb)
 * [hoilc](https://github.com/hoilc): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:hoilc)
+* [Jan van den Berg](https://github.com/jan-vandenberg): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:jan-vandenberg), [Web](https://j11g.com/)
 * [Jaussoin Timothée](https://github.com/edhelas): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=edhelas), [Web](http://edhelas.movim.eu/)
 * [jlefler](https://github.com/jlefler): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:jlefler)
 * [Jonas Östanbäck](https://github.com/cez81): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=cez81)
@@ -37,8 +39,10 @@ People are sorted by name so please keep this order.
 * [Marien Fressinaud](https://github.com/marienfressinaud): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=marienfressinaud), [Web](https://marienfressinaud.fr/)
 * [Melvyn Laïly](https://github.com/yaurthek): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=yaurthek), [Web](http://x2a.yt/)
 * [MSZ](https://github.com/mszkb): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=mszkb)
+* [Nico B](https://github.com/youknow0): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:youknow0)
 * [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/)
+* [Nicola Spanti](https://github.com/RyDroid): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:RyDroid), [Web](http://www.nicola-spanti.info/)
 * [Olivier Dossmann](https://github.com/blankoworld): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=blankoworld), [Web](https://olivier.dossmann.net)
 * [plopoyop](https://github.com/plopoyop): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=plopoyop)
 * [Paulius Šukys](https://github.com/psukys): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:psukys), [Web](http://sukys.eu)

+ 1 - 0
README.fr.md

@@ -177,6 +177,7 @@ Tout client supportant une API de type Google Reader. Sélection :
 
 * Android
 	* [News+](https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus) avec [News+ Google Reader extension](https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus.extension.google_reader) (Propriétaire)
+	* [FeedMe 3.5.3+](https://play.google.com/store/apps/details?id=com.seazon.feedme) (Propriétaire)
 	* [EasyRSS](https://github.com/Alkarex/EasyRSS) (Libre, [F-Droid](https://f-droid.org/fr/packages/org.freshrss.easyrss/))
 * GNU/Linux
 	* [FeedReader 2.0+](https://jangernert.github.io/FeedReader/) (Libre)

+ 1 - 0
README.md

@@ -183,6 +183,7 @@ Any client supporting a Google Reader-like API. Selection:
 
 * Android
 	* [News+](https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus) with [News+ Google Reader extension](https://play.google.com/store/apps/details?id=com.noinnion.android.newsplus.extension.google_reader) (Closed source)
+	* [FeedMe 3.5.3+](https://play.google.com/store/apps/details?id=com.seazon.feedme) (Closed source)
 	* [EasyRSS](https://github.com/Alkarex/EasyRSS) (Open source, [F-Droid](https://f-droid.org/packages/org.freshrss.easyrss/))
 * GNU/Linux
 	* [FeedReader 2.0+](https://jangernert.github.io/FeedReader/) (Open source)

+ 4 - 6
app/Controllers/configureController.php

@@ -55,6 +55,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 			FreshRSS_Context::$user_conf->bottomline_date = Minz_Request::param('bottomline_date', false);
 			FreshRSS_Context::$user_conf->bottomline_link = Minz_Request::param('bottomline_link', false);
 			FreshRSS_Context::$user_conf->html5_notif_timeout = Minz_Request::param('html5_notif_timeout', 0);
+			FreshRSS_Context::$user_conf->show_nav_buttons = Minz_Request::param('show_nav_buttons', false);
 			FreshRSS_Context::$user_conf->save();
 
 			Minz_Session::_param('language', FreshRSS_Context::$user_conf->language);
@@ -170,7 +171,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 		                   'm', 'n', 'o', 'p', 'page_down', 'page_up', 'q', 'r', 'return', 'right',
 		                   's', 'space', 't', 'tab', 'u', 'up', 'v', 'w', 'x', 'y',
 		                   'z', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9',
-		                   'f10', 'f11', 'f12');
+		                   'f10', 'f11', 'f12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0');
 		$this->view->list_keys = $list_keys;
 
 		if (Minz_Request::isPost()) {
@@ -204,16 +205,13 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 	 * The options available on that page are:
 	 *   - duration to retain old article (default: 3)
 	 *   - number of article to retain per feed (default: 0)
-	 *   - refresh frequency (default: -2)
-	 *
-	 * @todo explain why the default value is -2 but this value does not
-	 *       exist in the drop-down list
+	 *   - refresh frequency (default: 0)
 	 */
 	public function archivingAction() {
 		if (Minz_Request::isPost()) {
 			FreshRSS_Context::$user_conf->old_entries = Minz_Request::param('old_entries', 3);
 			FreshRSS_Context::$user_conf->keep_history_default = Minz_Request::param('keep_history_default', 0);
-			FreshRSS_Context::$user_conf->ttl_default = Minz_Request::param('ttl_default', -2);
+			FreshRSS_Context::$user_conf->ttl_default = Minz_Request::param('ttl_default', FreshRSS_Feed::TTL_DEFAULT);
 			FreshRSS_Context::$user_conf->save();
 			invalidateHttpCache();
 

+ 1 - 3
app/Controllers/entryController.php

@@ -177,9 +177,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
 
 		foreach ($feeds as $feed) {
 			$feed_history = $feed->keepHistory();
-			if ($feed_history == -2) {
-				// TODO: -2 must be a constant!
-				// -2 means we take the default value from configuration
+			if (FreshRSS_Feed::KEEP_HISTORY_DEFAULT === $feed_history) {
 				$feed_history = FreshRSS_Context::$user_conf->keep_history_default;
 			}
 

+ 1 - 1
app/Controllers/extensionController.php

@@ -240,7 +240,7 @@ class FreshRSS_extension_Controller extends Minz_ActionController {
 			$res = recursive_unlink($ext->getPath());
 			if ($res) {
 				Minz_Request::good(_t('feedback.extensions.removed', $ext_name),
-				                  $url_redirect);
+				                   $url_redirect);
 			} else {
 				Minz_Request::bad(_t('feedback.extensions.cannot_delete', $ext_name),
 				                  $url_redirect);

+ 17 - 6
app/Controllers/feedController.php

@@ -24,6 +24,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 				Minz_Error::error(403);
 			}
 		}
+		$this->updateTTL();
 	}
 
 	/**
@@ -44,6 +45,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 
 		$catDAO = new FreshRSS_CategoryDAO();
 
+		$url = trim($url);
+
 		$cat = null;
 		if ($new_cat_name != '') {
 			$new_cat_id = $catDAO->addCategory(array('name' => $new_cat_name));
@@ -282,11 +285,11 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 
 			$mtime = 0;
 			$ttl = $feed->ttl();
-			if ($ttl == -1) {
+			if ($ttl < FreshRSS_Feed::TTL_DEFAULT) {
 				continue;	//Feed refresh is disabled
 			}
 			if ((!$simplePiePush) && (!$feed_id) &&
-				($feed->lastUpdate() + 10 >= time() - ($ttl == -2 ? FreshRSS_Context::$user_conf->ttl_default : $ttl))) {
+				($feed->lastUpdate() + 10 >= time() - ($ttl == FreshRSS_Feed::TTL_DEFAULT ? FreshRSS_Context::$user_conf->ttl_default : $ttl))) {
 				//Too early to refresh from source, but check whether the feed was updated by another user
 				$mtime = $feed->cacheModifiedTime();
 				if ($feed->lastUpdate() + 10 >= $mtime) {
@@ -316,10 +319,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 
 			$feed_history = $feed->keepHistory();
 			if ($isNewFeed) {
-				$feed_history = -1; //∞
-			} elseif ($feed_history == -2) {
-				// TODO: -2 must be a constant!
-				// -2 means we take the default value from configuration
+				$feed_history = FreshRSS_Feed::KEEP_HISTORY_INFINITE;
+			} elseif (FreshRSS_Feed::KEEP_HISTORY_DEFAULT === $feed_history) {
 				$feed_history = FreshRSS_Context::$user_conf->keep_history_default;
 			}
 			$needFeedCacheRefresh = false;
@@ -639,4 +640,14 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 			Minz_Request::bad(_t('feedback.sub.feed.error'), $redirect_url);
 		}
 	}
+
+	/**
+	 * This method update TTL values for feeds if needed.
+	 * It changes the old default value (-2) to the new default value (0).
+	 * It changes the old disabled value (-1) to the default disabled value.
+	 */
+	private function updateTTL() {
+		$feedDAO = FreshRSS_Factory::createFeedDao();
+		$feedDAO->updateTTL();
+	}
 }

+ 1 - 1
app/Controllers/indexController.php

@@ -203,7 +203,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 		$entryDAO = FreshRSS_Factory::createEntryDao();
 
 		$get = FreshRSS_Context::currentGet(true);
-		if (count($get) > 1) {
+		if (is_array($get)) {
 			$type = $get[0];
 			$id = $get[1];
 		} else {

+ 12 - 4
app/Controllers/subscriptionController.php

@@ -15,8 +15,10 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
 		}
 
 		$catDAO = new FreshRSS_CategoryDAO();
+		$feedDAO = new FreshRSS_FeedDAO();
 
 		$catDAO->checkDefault();
+		$feedDAO->updateTTL();
 		$this->view->categories = $catDAO->listCategories(false);
 		$this->view->default_category = $catDAO->getDefault();
 	}
@@ -55,7 +57,7 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
 	 *   - display in main stream (default: 0)
 	 *   - HTTP authentication
 	 *   - number of article to retain (default: -2)
-	 *   - refresh frequency (default: -2)
+	 *   - refresh frequency (default: 0)
 	 * Default values are empty strings unless specified.
 	 */
 	public function feedAction() {
@@ -87,6 +89,12 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
 
 			$cat = intval(Minz_Request::param('category', 0));
 
+			$mute = Minz_Request::param('mute', false);
+			$ttl = intval(Minz_Request::param('ttl', FreshRSS_Feed::TTL_DEFAULT));
+			if ($mute && FreshRSS_Feed::TTL_DEFAULT === $ttl) {
+				$ttl = FreshRSS_Context::$user_conf->ttl_default;
+			}
+
 			$values = array(
 				'name' => Minz_Request::param('name', ''),
 				'description' => sanitizeHTML(Minz_Request::param('description', '', true)),
@@ -94,10 +102,10 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
 				'url' => checkUrl(Minz_Request::param('url', '')),
 				'category' => $cat,
 				'pathEntries' => Minz_Request::param('path_entries', ''),
-				'priority' => intval(Minz_Request::param('priority', 0)),
+				'priority' => intval(Minz_Request::param('priority', FreshRSS_Feed::PRIORITY_MAIN_STREAM)),
 				'httpAuth' => $httpAuth,
-				'keep_history' => intval(Minz_Request::param('keep_history', -2)),
-				'ttl' => intval(Minz_Request::param('ttl', -2)),
+				'keep_history' => intval(Minz_Request::param('keep_history', FreshRSS_Feed::KEEP_HISTORY_DEFAULT)),
+				'ttl' => $ttl * ($mute ? -1 : 1),
 			);
 
 			invalidateHttpCache();

+ 41 - 17
app/Controllers/userController.php

@@ -44,29 +44,54 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 		return preg_match('/^' . self::USERNAME_PATTERN . '$/', $username) === 1;
 	}
 
-	public static function updateContextUser($passwordPlain, $apiPasswordPlain, $userConfigUpdated = array()) {
+	public static function updateUser($user, $passwordPlain, $apiPasswordPlain, $userConfigUpdated = array()) {
+		$userConfig = get_user_configuration($user);
 		if ($passwordPlain != '') {
 			$passwordHash = self::hashPassword($passwordPlain);
-			FreshRSS_Context::$user_conf->passwordHash = $passwordHash;
+			$userConfig->passwordHash = $passwordHash;
 		}
 
 		if ($apiPasswordPlain != '') {
 			$apiPasswordHash = self::hashPassword($apiPasswordPlain);
-			FreshRSS_Context::$user_conf->apiPasswordHash = $apiPasswordHash;
+			$userConfig->apiPasswordHash = $apiPasswordHash;
 		}
 
 		if (is_array($userConfigUpdated)) {
 			foreach ($userConfigUpdated as $configName => $configValue) {
 				if ($configValue !== null) {
-					FreshRSS_Context::$user_conf->_param($configName, $configValue);
+					$userConfig->_param($configName, $configValue);
 				}
 			}
 		}
 
-		$ok = FreshRSS_Context::$user_conf->save();
+		$ok = $userConfig->save();
 		return $ok;
 	}
 
+	public function updateAction() {
+		if (Minz_Request::isPost()) {
+			$passwordPlain = Minz_Request::param('newPasswordPlain', '', true);
+			Minz_Request::_param('newPasswordPlain');	//Discard plain-text password ASAP
+			$_POST['newPasswordPlain'] = '';
+
+			$apiPasswordPlain = Minz_Request::param('apiPasswordPlain', '', true);
+
+			$username = Minz_Request::param('username');
+			$ok = self::updateUser($username, $passwordPlain, $apiPasswordPlain, array(
+					'token' => Minz_Request::param('token', null),
+				));
+
+			if ($ok) {
+				Minz_Request::good(_t('feedback.user.updated', $username),
+				                   array('c' => 'user', 'a' => 'manage'));
+			} else {
+				Minz_Request::bad(_t('feedback.user.updated.error', $username),
+				                  array('c' => 'user', 'a' => 'manage'));
+			}
+
+		}
+	}
+
 	/**
 	 * This action displays the user profile page.
 	 */
@@ -84,7 +109,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 
 			$apiPasswordPlain = Minz_Request::param('apiPasswordPlain', '', true);
 
-			$ok = self::updateContextUser($passwordPlain, $apiPasswordPlain, array(
+			$ok = self::updateUser(Minz_Session::param('currentUser'), $passwordPlain, $apiPasswordPlain, array(
 					'token' => Minz_Request::param('token', null),
 				));
 
@@ -110,19 +135,18 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 
 		Minz_View::prependTitle(_t('admin.user.title') . ' · ');
 
-		// Get the correct current user.
-		$username = Minz_Request::param('u', Minz_Session::param('currentUser'));
-		if (!FreshRSS_UserDAO::exist($username)) {
-			$username = Minz_Session::param('currentUser');
-		}
-		$this->view->current_user = $username;
+		$this->view->current_user = Minz_Request::param('u');
 
-		// Get information about the current user.
-		$entryDAO = FreshRSS_Factory::createEntryDao($this->view->current_user);
-		$this->view->nb_articles = $entryDAO->count();
+		$this->view->nb_articles = 0;
+		$this->view->size_user = 0;
+		if ($this->view->current_user) {
+			// Get information about the current user.
+			$entryDAO = FreshRSS_Factory::createEntryDao($this->view->current_user);
+			$this->view->nb_articles = $entryDAO->count();
 
-		$databaseDAO = FreshRSS_Factory::createDatabaseDAO();
-		$this->view->size_user = $databaseDAO->size();
+			$databaseDAO = FreshRSS_Factory::createDatabaseDAO();
+			$this->view->size_user = $databaseDAO->size();
+		}
 	}
 
 	public static function createUser($new_user_name, $passwordPlain, $apiPasswordPlain, $userConfig = array(), $insertDefaultFeeds = true) {

+ 6 - 0
app/Models/Auth.php

@@ -13,6 +13,11 @@ class FreshRSS_Auth {
 	 * This method initializes authentication system.
 	 */
 	public static function init() {
+		if (Minz_Session::param('REMOTE_USER', '') !== httpAuthUser()) {
+			//HTTP REMOTE_USER has changed
+			self::removeAccess();
+		}
+
 		self::$login_ok = Minz_Session::param('loginOk', false);
 		$current_user = Minz_Session::param('currentUser', '');
 		if ($current_user === '') {
@@ -58,6 +63,7 @@ class FreshRSS_Auth {
 			$login_ok = $current_user != '';
 			if ($login_ok) {
 				Minz_Session::_param('currentUser', $current_user);
+				Minz_Session::_param('REMOTE_USER', $current_user);
 			}
 			return $login_ok;
 		case 'none':

+ 3 - 2
app/Models/CategoryDAO.php

@@ -106,13 +106,14 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable
 	public function listCategories($prePopulateFeeds = true, $details = false) {
 		if ($prePopulateFeeds) {
 			$sql = 'SELECT c.id AS c_id, c.name AS c_name, '
-			     . ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.`cache_nbEntries`, f.`cache_nbUnreads` ')
+			     . ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.`cache_nbEntries`, f.`cache_nbUnreads`, f.ttl ')
 			     . 'FROM `' . $this->prefix . 'category` c '
 			     . 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category=c.id '
+			     . 'WHERE f.priority >= :priority_normal '
 			     . 'GROUP BY f.id, c_id '
 			     . 'ORDER BY c.name, f.name';
 			$stm = $this->bd->prepare($sql);
-			$stm->execute();
+			$stm->execute(array(':priority_normal' => FreshRSS_Feed::PRIORITY_NORMAL));
 			return self::daoToCategoryPrepopulated($stm->fetchAll(PDO::FETCH_ASSOC));
 		} else {
 			$sql = 'SELECT * FROM `' . $this->prefix . 'category` ORDER BY name';

+ 6 - 2
app/Models/ConfigurationSetter.php

@@ -81,7 +81,7 @@ class FreshRSS_ConfigurationSetter {
 
 	private function _keep_history_default(&$data, $value) {
 		$value = intval($value);
-		$data['keep_history_default'] = $value >= -1 ? $value : 0;
+		$data['keep_history_default'] = $value >= FreshRSS_Feed::KEEP_HISTORY_INFINITE ? $value : 0;
 	}
 
 	// It works for system config too!
@@ -154,7 +154,7 @@ class FreshRSS_ConfigurationSetter {
 
 	private function _ttl_default(&$data, $value) {
 		$value = intval($value);
-		$data['ttl_default'] = $value >= -1 ? $value : 3600;
+		$data['ttl_default'] = $value > FreshRSS_Feed::TTL_DEFAULT ? $value : 3600;
 	}
 
 	private function _view_mode(&$data, $value) {
@@ -184,6 +184,10 @@ class FreshRSS_ConfigurationSetter {
 		$data['mark_updated_article_unread'] = $this->handleBool($value);
 	}
 
+	private function _show_nav_buttons(&$data, $value) {
+		$data['show_nav_buttons'] = $this->handleBool($value);
+	}
+
 	private function _display_categories(&$data, $value) {
 		$data['display_categories'] = $this->handleBool($value);
 	}

+ 49 - 14
app/Models/EntryDAO.php

@@ -628,10 +628,12 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 			$firstId = $order === 'DESC' ? '9000000000'. '000000' : '0';
 		}*/
 		if ($firstId !== '') {
-			$search .= 'AND ' . $alias . 'id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' ';
+			$search .= 'AND ' . $alias . 'id ' . ($order === 'DESC' ? '<=' : '>=') . ' ? ';
+			$values[] = $firstId;
 		}
 		if ($date_min > 0) {
-			$search .= 'AND ' . $alias . 'id >= ' . $date_min . '000000 ';
+			$search .= 'AND ' . $alias . 'id >= ? ';
+			$values[] = $date_min . '000000';
 		}
 		if ($filter) {
 			if ($filter->getMinDate()) {
@@ -726,23 +728,23 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 		$values = array();
 		switch ($type) {
 		case 'a':
-			$where .= 'f.priority > 0 ';
-			$joinFeed = true;
+			$where .= 'f.priority > ' . FreshRSS_Feed::PRIORITY_NORMAL . ' ';
 			break;
 		case 's':	//Deprecated: use $state instead
-			$where .= 'e.is_favorite=1 ';
+			$where .= 'f.priority >= ' . FreshRSS_Feed::PRIORITY_NORMAL . ' ';
+			$where .= 'AND e.is_favorite=1 ';
 			break;
 		case 'c':
-			$where .= 'f.category=? ';
+			$where .= 'f.priority >= ' . FreshRSS_Feed::PRIORITY_NORMAL . ' ';
+			$where .= 'AND f.category=? ';
 			$values[] = intval($id);
-			$joinFeed = true;
 			break;
 		case 'f':
 			$where .= 'e.id_feed=? ';
 			$values[] = intval($id);
 			break;
 		case 'A':
-			$where .= '1=1 ';
+			$where .= 'f.priority >= ' . FreshRSS_Feed::PRIORITY_NORMAL . ' ';
 			break;
 		default:
 			throw new FreshRSS_EntriesGetter_Exception('Bad type in Entry->listByType: [' . $type . ']!');
@@ -752,7 +754,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 
 		return array(array_merge($values, $searchValues),
 			'SELECT e.id FROM `' . $this->prefix . 'entry` e '
-			. ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' : '')
+			. 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
 			. 'WHERE ' . $where
 			. $search
 			. 'ORDER BY e.id ' . $order
@@ -781,6 +783,23 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 		return self::daoToEntries($stm->fetchAll(PDO::FETCH_ASSOC));
 	}
 
+	public function listByIds($ids, $order = 'DESC') {
+		if (count($ids) < 1) {
+			return array();
+		}
+
+		$sql = 'SELECT id, guid, title, author, '
+			. ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
+			. ', link, date, is_read, is_favorite, id_feed, tags '
+			. 'FROM `' . $this->prefix . 'entry` '
+			. 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?) '
+			. 'ORDER BY id ' . $order;
+
+		$stm = $this->bd->prepare($sql);
+		$stm->execute($ids);
+		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
 		list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min);
 
@@ -873,12 +892,28 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 	}
 
 	public function countUnreadReadFavorites() {
-		$sql = 'SELECT c FROM ('
-			.	'SELECT COUNT(id) AS c, 1 as o FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 '
-			.	'UNION SELECT COUNT(id) AS c, 2 AS o FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 AND is_read=0'
-			.	') u ORDER BY o';
+		$sql = <<<SQL
+  SELECT c
+    FROM (
+         SELECT COUNT(e1.id) AS c
+              , 1 AS o
+           FROM `{$this->prefix}entry` AS e1
+           JOIN `{$this->prefix}feed` AS f1 ON e1.id_feed = f1.id
+          WHERE e1.is_favorite = 1
+            AND f1.priority >= :priority_normal
+         UNION
+         SELECT COUNT(e2.id) AS c
+              , 2 AS o
+           FROM `{$this->prefix}entry` AS e2
+           JOIN `{$this->prefix}feed` AS f2 ON e2.id_feed = f2.id
+          WHERE e2.is_favorite = 1
+            AND e2.is_read = 0
+            AND f2.priority >= :priority_normal
+         ) u
+ORDER BY o
+SQL;
 		$stm = $this->bd->prepare($sql);
-		$stm->execute();
+		$stm->execute(array(':priority_normal' => FreshRSS_Feed::PRIORITY_NORMAL));
 		$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
 		$all = empty($res[0]) ? 0 : $res[0];
 		$unread = empty($res[1]) ? 0 : $res[1];

+ 21 - 9
app/Models/Feed.php

@@ -1,6 +1,15 @@
 <?php
 
 class FreshRSS_Feed extends Minz_Model {
+	const PRIORITY_MAIN_STREAM = 10;
+	const PRIORITY_NORMAL = 0;
+	const PRIORITY_ARCHIVED = -10;
+
+	const TTL_DEFAULT = 0;
+
+	const KEEP_HISTORY_DEFAULT = -2;
+	const KEEP_HISTORY_INFINITE = -1;
+
 	private $id = 0;
 	private $url;
 	private $category = 1;
@@ -11,12 +20,13 @@ class FreshRSS_Feed extends Minz_Model {
 	private $website = '';
 	private $description = '';
 	private $lastUpdate = 0;
-	private $priority = 10;
+	private $priority = self::PRIORITY_MAIN_STREAM;
 	private $pathEntries = '';
 	private $httpAuth = '';
 	private $error = false;
-	private $keep_history = -2;
-	private $ttl = -2;
+	private $keep_history = self::KEEP_HISTORY_DEFAULT;
+	private $ttl = self::TTL_DEFAULT;
+	private $mute = false;
 	private $hash = null;
 	private $lockPath = '';
 	private $hubUrl = '';
@@ -104,9 +114,12 @@ class FreshRSS_Feed extends Minz_Model {
 	public function ttl() {
 		return $this->ttl;
 	}
+	public function mute() {
+		return $this->mute;
+	}
 	// public function ttlExpire() {
 		// $ttl = $this->ttl;
-		// if ($ttl == -2) {	//Default
+		// if ($ttl == self::TTL_DEFAULT) {	//Default
 			// $ttl = FreshRSS_Context::$user_conf->ttl_default;
 		// }
 		// if ($ttl == -1) {	//Never
@@ -198,8 +211,7 @@ class FreshRSS_Feed extends Minz_Model {
 		$this->lastUpdate = $value;
 	}
 	public function _priority($value) {
-		$value = intval($value);
-		$this->priority = $value >= 0 ? $value : 10;
+		$this->priority = intval($value);
 	}
 	public function _pathEntries($value) {
 		$this->pathEntries = $value;
@@ -213,14 +225,14 @@ class FreshRSS_Feed extends Minz_Model {
 	public function _keepHistory($value) {
 		$value = intval($value);
 		$value = min($value, 1000000);
-		$value = max($value, -2);
+		$value = max($value, self::KEEP_HISTORY_DEFAULT);
 		$this->keep_history = $value;
 	}
 	public function _ttl($value) {
 		$value = intval($value);
 		$value = min($value, 100000000);
-		$value = max($value, -2);
-		$this->ttl = $value;
+		$this->ttl = abs($value);
+		$this->mute = $value < self::TTL_DEFAULT;
 	}
 	public function _nbNotRead($value) {
 		$this->nbNotRead = intval($value);

+ 25 - 11
app/Models/FeedDAO.php

@@ -18,7 +18,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 					ttl
 				)
 				VALUES
-				(?, ?, ?, ?, ?, ?, 10, ?, 0, -2, -2)';
+				(?, ?, ?, ?, ?, ?, 10, ?, 0, ?, ?)';
 		$stm = $this->bd->prepare($sql);
 
 		$valuesTmp['url'] = safe_ascii($valuesTmp['url']);
@@ -32,6 +32,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 			substr($valuesTmp['description'], 0, 1023),
 			$valuesTmp['lastUpdate'],
 			base64_encode($valuesTmp['httpAuth']),
+			FreshRSS_Feed::KEEP_HISTORY_DEFAULT,
+			FreshRSS_Feed::TTL_DEFAULT,
 		);
 
 		if ($stm && $stm->execute($values)) {
@@ -249,18 +251,14 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 	 * Use $defaultCacheDuration == -1 to return all feeds, without filtering them by TTL.
 	 */
 	public function listFeedsOrderUpdate($defaultCacheDuration = 3600) {
+		$this->updateTTL();
 		$sql = 'SELECT id, url, name, website, `lastUpdate`, `pathEntries`, `httpAuth`, keep_history, ttl '
 		     . 'FROM `' . $this->prefix . 'feed` '
-		     . ($defaultCacheDuration < 0 ? '' : 'WHERE ttl <> -1 AND `lastUpdate` < (' . (time() + 60) . '-(CASE WHEN ttl=-2 THEN ' . intval($defaultCacheDuration) . ' ELSE ttl END)) ')
+		     . ($defaultCacheDuration < 0 ? '' : 'WHERE ttl >= ' . FreshRSS_Feed::TTL_DEFAULT
+		     . ' AND `lastUpdate` < (' . (time() + 60) . '-(CASE WHEN ttl=' . FreshRSS_Feed::TTL_DEFAULT . ' THEN ' . intval($defaultCacheDuration) . ' ELSE ttl END)) ')
 		     . 'ORDER BY `lastUpdate`';
 		$stm = $this->bd->prepare($sql);
-		if (!($stm && $stm->execute())) {
-			$sql2 = 'ALTER TABLE `' . $this->prefix . 'feed` ADD COLUMN ttl INT NOT NULL DEFAULT -2';	//v0.7.3
-			$stm = $this->bd->prepare($sql2);
-			$stm->execute();
-			$stm = $this->bd->prepare($sql);
-			$stm->execute();
-		}
+		$stm->execute();
 
 		return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC));
 	}
@@ -409,8 +407,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 			$myFeed->_pathEntries(isset($dao['pathEntries']) ? $dao['pathEntries'] : '');
 			$myFeed->_httpAuth(isset($dao['httpAuth']) ? base64_decode($dao['httpAuth']) : '');
 			$myFeed->_error(isset($dao['error']) ? $dao['error'] : 0);
-			$myFeed->_keepHistory(isset($dao['keep_history']) ? $dao['keep_history'] : -2);
-			$myFeed->_ttl(isset($dao['ttl']) ? $dao['ttl'] : -2);
+			$myFeed->_keepHistory(isset($dao['keep_history']) ? $dao['keep_history'] : FreshRSS_Feed::KEEP_HISTORY_DEFAULT);
+			$myFeed->_ttl(isset($dao['ttl']) ? $dao['ttl'] : FreshRSS_Feed::TTL_DEFAULT);
 			$myFeed->_nbNotRead(isset($dao['cache_nbUnreads']) ? $dao['cache_nbUnreads'] : 0);
 			$myFeed->_nbEntries(isset($dao['cache_nbEntries']) ? $dao['cache_nbEntries'] : 0);
 			if (isset($dao['id'])) {
@@ -421,4 +419,20 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 
 		return $list;
 	}
+
+	public function updateTTL() {
+		$sql = <<<SQL
+UPDATE `{$this->prefix}feed`
+   SET ttl = :new_value
+ WHERE ttl = :old_value
+SQL;
+		$stm = $this->bd->prepare($sql);
+		if (!($stm && $stm->execute(array(':new_value' => FreshRSS_Feed::TTL_DEFAULT, ':old_value' => -2)))) {
+			$sql2 = 'ALTER TABLE `' . $this->prefix . 'feed` ADD COLUMN ttl INT NOT NULL DEFAULT ' . FreshRSS_Feed::TTL_DEFAULT;	//v0.7.3
+			$stm = $this->bd->prepare($sql2);
+			$stm->execute();
+		} else {
+			$stm->execute(array(':new_value' => -3600, ':old_value' => -1));
+		}
+	}
 }

+ 148 - 0
app/Models/ReadingMode.php

@@ -0,0 +1,148 @@
+<?php
+
+/**
+ * Manage the reading modes in FreshRSS.
+ */
+class FreshRSS_ReadingMode {
+
+	/**
+	 * @var string
+	 */
+	protected $id;
+	/**
+	 * @var string
+	 */
+	protected $name;
+	/**
+	 * @var string
+	 */
+	protected $title;
+	/**
+	 * @var string[]
+	 */
+	protected $urlParams;
+	/**
+	 * @var bool
+	 */
+	protected $isActive = false;
+
+	/**
+	 * ReadingMode constructor.
+	 * @param string $id
+	 * @param string $title
+	 * @param string[] $urlParams
+	 * @param bool $active
+	 */
+	public function __construct($id, $title, $urlParams, $active) {
+		$this->id = $id;
+		$this->name = _i($id);
+		$this->title = $title;
+		$this->urlParams = $urlParams;
+		$this->isActive = $active;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getId() {
+		return $this->id;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getName() {
+		return $this->name;
+	}
+
+	/**
+	 * @param string $name
+	 * @return FreshRSS_ReadingMode
+	 */
+	public function setName($name) {
+		$this->name = $name;
+		return $this;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getTitle() {
+		return $this->title;
+	}
+
+	/**
+	 * @param string $title
+	 * @return FreshRSS_ReadingMode
+	 */
+	public function setTitle($title) {
+		$this->title = $title;
+		return $this;
+	}
+
+	/**
+	 * @return string
+	 */
+	public function getUrlParams() {
+		return $this->urlParams;
+	}
+
+	/**
+	 * @param string $urlParams
+	 * @return FreshRSS_ReadingMode
+	 */
+	public function setUrlParams($urlParams) {
+		$this->urlParams = $urlParams;
+		return $this;
+	}
+
+	/**
+	 * @return bool
+	 */
+	public function isActive() {
+		return $this->isActive;
+	}
+
+	/**
+	 * @param bool $isActive
+	 * @return FreshRSS_ReadingMode
+	 */
+	public function setIsActive($isActive) {
+		$this->isActive = $isActive;
+		return $this;
+	}
+
+	/**
+	 * Returns the built-in reading modes.
+	 * return ReadingMode[]
+	 */
+	public static function getReadingModes() {
+		$actualView = Minz_Request::actionName();
+		$defaultCtrl = Minz_Request::defaultControllerName();
+		$isDefaultCtrl = Minz_Request::controllerName() === $defaultCtrl;
+		$urlOutput = Minz_Request::currentRequest();
+
+		$readingModes = array(
+			new FreshRSS_ReadingMode(
+				"view-normal",
+				_t('index.menu.normal_view'),
+				array_merge($urlOutput, array('c' => $defaultCtrl, 'a' => 'normal')),
+				($isDefaultCtrl && $actualView === 'normal')
+			),
+			new FreshRSS_ReadingMode(
+				"view-global",
+				_t('index.menu.global_view'),
+				array_merge($urlOutput, array('c' => $defaultCtrl, 'a' => 'global')),
+				($isDefaultCtrl && $actualView === 'global')
+			),
+			new FreshRSS_ReadingMode(
+				"view-reader",
+				_t('index.menu.reader_view'),
+				array_merge($urlOutput, array('c' => $defaultCtrl, 'a' => 'reader')),
+				($isDefaultCtrl && $actualView === 'reader')
+			),
+		);
+
+		return $readingModes;
+	}
+}

+ 1 - 1
app/SQL/install.sql.mysql.php

@@ -23,7 +23,7 @@ CREATE TABLE IF NOT EXISTS `%1$sfeed` (
 	`httpAuth` varchar(511) DEFAULT NULL,
 	`error` boolean DEFAULT 0,
 	`keep_history` MEDIUMINT NOT NULL DEFAULT -2,	-- v0.7
-	`ttl` INT NOT NULL DEFAULT -2,	-- v0.7.3
+	`ttl` INT NOT NULL DEFAULT 0,	-- v0.7.3
 	`cache_nbEntries` int DEFAULT 0,	-- v0.7
 	`cache_nbUnreads` int DEFAULT 0,	-- v0.7
 	PRIMARY KEY (`id`),

+ 1 - 1
app/SQL/install.sql.pgsql.php

@@ -21,7 +21,7 @@ $SQL_CREATE_TABLES = array(
 	"httpAuth" VARCHAR(511) DEFAULT NULL,
 	"error" smallint DEFAULT 0,
 	"keep_history" INT NOT NULL DEFAULT -2,
-	"ttl" INT NOT NULL DEFAULT -2,
+	"ttl" INT NOT NULL DEFAULT 0,
 	"cache_nbEntries" INT DEFAULT 0,
 	"cache_nbUnreads" INT DEFAULT 0,
 	FOREIGN KEY ("category") REFERENCES "%1$scategory" ("id") ON DELETE SET NULL ON UPDATE CASCADE

+ 1 - 1
app/SQL/install.sql.sqlite.php

@@ -20,7 +20,7 @@ $SQL_CREATE_TABLES = array(
 	`httpAuth` varchar(511) DEFAULT NULL,
 	`error` boolean DEFAULT 0,
 	`keep_history` MEDIUMINT NOT NULL DEFAULT -2,
-	`ttl` INT NOT NULL DEFAULT -2,
+	`ttl` INT NOT NULL DEFAULT 0,
 	`cache_nbEntries` int DEFAULT 0,
 	`cache_nbUnreads` int DEFAULT 0,
 	FOREIGN KEY (`category`) REFERENCES `category`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,

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

@@ -175,12 +175,15 @@ return array(
 	'user' => array(
 		'articles_and_size' => '%s článků (%s)',
 		'create' => 'Vytvořit nového uživatele',
+		'delete_users' => 'Delete user', // TODO
 		'language' => 'Jazyk',
 		'number' => 'Zatím je vytvořen %d účet',
 		'numbers' => 'Zatím je vytvořeno %d účtů',
 		'password_form' => 'Heslo<br /><small>(pro přihlášení webovým formulářem)</small>',
 		'password_format' => 'Alespoň 7 znaků',
+		'selected' => 'Selected user', // TODO
 		'title' => 'Správa uživatelů',
+		'update_users' => 'Update user', // TODO
 		'user_list' => 'Seznam uživatelů',
 		'username' => 'Přihlašovací jméno',
 		'users' => 'Uživatelé',

+ 6 - 0
app/i18n/cz/conf.php

@@ -37,6 +37,7 @@ return array(
 			'no_limit' => 'Bez limitu',
 			'thin' => 'Tenká',
 		),
+		'show_nav_buttons' => 'Show the navigation buttons',	//TODO
 	),
 	'query' => array(
 		'_' => 'Uživatelské dotazy',
@@ -148,6 +149,7 @@ return array(
 		'collapse_article' => 'Srolovat',
 		'first_article' => 'Skočit na první článek',
 		'focus_search' => 'Hledání',
+		'global_view' => 'Switch to global view', // TODO
 		'help' => 'Zobrazit documentaci',
 		'javascript' => 'Pro použití zkratek musí být povolen JavaScript',
 		'last_article' => 'Skočit na poslední článek',
@@ -157,13 +159,17 @@ return array(
 		'navigation' => 'Navigace',
 		'navigation_help' => 'Pomocí přepínače "Shift" fungují navigační zkratky v rámci kanálů.<br/>Pomocí přepínače "Alt" fungují v rámci kategorií.',
 		'next_article' => 'Skočit na další článek',
+		'normal_view' => 'Switch to normal view', // TODO
 		'other_action' => 'Ostatní akce',
 		'previous_article' => 'Skočit na předchozí článek',
+		'reading_view' => 'Switch to reading view', // TODO
+		'rss_view' => 'Open RSS view in a new tab', // TODO
 		'see_on_website' => 'Navštívit původní webovou stránku',
 		'shift_for_all_read' => '+ <code>shift</code> označí vše jako přečtené',
 		'title' => 'Zkratky',
 		'user_filter' => 'Aplikovat uživatelské filtry',
 		'user_filter_help' => 'Je-li nastaven pouze jeden filtr, bude použit. Další filtry jsou dostupné pomocí jejich čísla.',
+		'views' => 'Views', // TODO
 	),
 	'user' => array(
 		'articles_and_size' => '%s článků (%s)',

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

@@ -101,6 +101,10 @@ return array(
 			'_' => 'Uživatel %s byl smazán',
 			'error' => 'Uživatele %s nelze smazat',
 		),
+		'updated' => array(
+			'_' => 'User %s has been updated', // TODO
+			'error' => 'User %s has not been updated', // TODO
+		),
 	),
 	'profile' => array(
 		'error' => 'Váš profil nelze změnit',

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

@@ -19,6 +19,7 @@ return array(
 		'see_website' => 'Navštívit WWW stránku',
 		'submit' => 'Odeslat',
 		'truncate' => 'Smazat všechny články',
+		'update' => 'Update', // TODO
 	),
 	'auth' => array(
 		'email' => 'Email',

+ 7 - 1
app/i18n/cz/sub.php

@@ -32,12 +32,18 @@ return array(
 		'description' => 'Popis',
 		'empty' => 'Kanál je prázdný. Ověřte prosím zda je ještě autorem udržován.',
 		'error' => 'Vyskytl se problém s kanálem. Ověřte že je vždy dostupný, prosím, a poté jej aktualizujte.',
-		'in_main_stream' => 'Zobrazit ve “Všechny kanály”',
 		'informations' => 'Informace',
 		'keep_history' => 'Zachovat tento minimální počet článků',
 		'moved_category_deleted' => 'Po smazání kategorie budou v ní obsažené kanály automaticky přesunuty do <em>%s</em>.',
+		'mute' => 'mute', // TODO
 		'no_selected' => 'Nejsou označeny žádné kanály.',
 		'number_entries' => '%d článků',
+		'priority' => array(
+			'_' => 'Visibility', // TODO
+			'archived' => 'Do not show (archived)', // TODO
+			'main_stream' => 'Zobrazit ve “Všechny kanály”',
+			'normal' => 'Show in its category', // TODO
+		),
 		'stats' => 'Statistika',
 		'think_to_add' => 'Můžete přidat kanály.',
 		'title' => 'Název',

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

@@ -175,12 +175,15 @@ return array(
 	'user' => array(
 		'articles_and_size' => '%s Artikel (%s)',
 		'create' => 'Neuen Benutzer erstellen',
+		'delete_users' => 'Delete user', // TODO
 		'language' => 'Sprache',
 		'number' => 'Es wurde bis jetzt %d Account erstellt',
 		'numbers' => 'Es wurden bis jetzt %d Accounts erstellt',
 		'password_form' => 'Passwort<br /><small>(für die Anmeldemethode per Webformular)</small>',
 		'password_format' => 'mindestens 7 Zeichen',
+		'selected' => 'Selected user', // TODO
 		'title' => 'Benutzer verwalten',
+		'update_users' => 'Update user', // TODO
 		'user_list' => 'Liste der Benutzer',
 		'username' => 'Nutzername',
 		'users' => 'Benutzer',

+ 6 - 0
app/i18n/de/conf.php

@@ -37,6 +37,7 @@ return array(
 			'no_limit' => 'Keine Begrenzung',
 			'thin' => 'Klein',
 		),
+		'show_nav_buttons' => 'Show the navigation buttons',	//TODO
 	),
 	'query' => array(
 		'_' => 'Benutzerabfragen',
@@ -148,6 +149,7 @@ return array(
 		'collapse_article' => 'Einklappen',
 		'first_article' => 'Zum ersten Artikel springen',
 		'focus_search' => 'Auf das Suchfeld zugreifen',
+		'global_view' => 'Switch to global view', // TODO
 		'help' => 'Dokumentation anzeigen',
 		'javascript' => 'JavaScript muss aktiviert sein, um Tastaturkürzel benutzen zu können',
 		'last_article' => 'Zum letzten Artikel springen',
@@ -157,13 +159,17 @@ return array(
 		'navigation' => 'Navigation',
 		'navigation_help' => 'Mit der "Umschalttaste" finden die Tastenkombination auf Feeds Anwendung.<br/>Mit der "Alt-Taste" finden die Tastenkombination auf Kategorien Anwendung.',
 		'next_article' => 'Zum nächsten Artikel springen',
+		'normal_view' => 'Switch to normal view', // TODO
 		'other_action' => 'Andere Aktionen',
 		'previous_article' => 'Zum vorherigen Artikel springen',
+		'reading_view' => 'Switch to reading view', // TODO
+		'rss_view' => 'Open RSS view in a new tab', // TODO
 		'see_on_website' => 'Auf der Original-Webseite ansehen',
 		'shift_for_all_read' => '+ <code>Umschalttaste</code>, um alle Artikel als gelesen zu markieren.',
 		'title' => 'Tastenkombination',
 		'user_filter' => 'Auf Benutzerfilter zugreifen',
 		'user_filter_help' => 'Wenn es nur einen Benutzerfilter gibt, wird dieser verwendet. Ansonsten sind die Filter über ihre Nummer erreichbar.',
+		'views' => 'Views', // TODO
 	),
 	'user' => array(
 		'articles_and_size' => '%s Artikel (%s)',

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

@@ -101,6 +101,10 @@ return array(
 			'_' => 'Der Benutzer %s ist gelöscht worden',
 			'error' => 'Der Benutzer %s kann nicht gelöscht werden',
 		),
+		'updated' => array(
+			'_' => 'User %s has been updated', // TODO
+			'error' => 'User %s has not been updated', // TODO
+		),
 	),
 	'profile' => array(
 		'error' => 'Ihr Profil kann nicht geändert werden',

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

@@ -19,6 +19,7 @@ return array(
 		'see_website' => 'Webseite ansehen',
 		'submit' => 'Abschicken',
 		'truncate' => 'Alle Artikel löschen',
+		'update' => 'Update', // TODO
 	),
 	'auth' => array(
 		'email' => 'E-Mail-Adresse',

+ 7 - 1
app/i18n/de/sub.php

@@ -32,12 +32,18 @@ return array(
 		'description' => 'Beschreibung',
 		'empty' => 'Dieser Feed ist leer. Bitte stellen Sie sicher, dass er noch gepflegt wird.',
 		'error' => 'Dieser Feed ist auf ein Problem gestoßen. Bitte stellen Sie sicher, dass er immer lesbar ist und aktualisieren Sie ihn dann.',
-		'in_main_stream' => 'In Haupt-Feeds zeigen',
 		'informations' => 'Information',
 		'keep_history' => 'Minimale Anzahl an Artikeln, die behalten wird',
 		'moved_category_deleted' => 'Wenn Sie eine Kategorie entfernen, werden deren Feeds automatisch in die Kategorie <em>%s</em> eingefügt.',
+		'mute' => 'mute', // TODO
 		'no_selected' => 'Kein Feed ausgewählt.',
 		'number_entries' => '%d Artikel',
+		'priority' => array(
+			'_' => 'Visibility', // TODO
+			'archived' => 'Do not show (archived)', // TODO
+			'main_stream' => 'In Haupt-Feeds zeigen',
+			'normal' => 'Show in its category', // TODO
+		),
 		'stats' => 'Statistiken',
 		'think_to_add' => 'Sie können Feeds hinzufügen.',
 		'title' => 'Titel',

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

@@ -175,12 +175,15 @@ return array(
 	'user' => array(
 		'articles_and_size' => '%s articles (%s)',
 		'create' => 'Create new user',
+		'delete_users' => 'Delete user',
 		'language' => 'Language',
 		'number' => 'There is %d account created',
 		'numbers' => 'There are %d accounts created',
 		'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',
 		'password_format' => 'At least 7 characters',
+		'selected' => 'Selected user',
 		'title' => 'Manage users',
+		'update_users' => 'Update user',
 		'user_list' => 'List of users',
 		'username' => 'Username',
 		'users' => 'Users',

+ 6 - 0
app/i18n/en/conf.php

@@ -37,6 +37,7 @@ return array(
 			'no_limit' => 'No limit',
 			'thin' => 'Thin',
 		),
+		'show_nav_buttons' => 'Show the navigation buttons',
 	),
 	'query' => array(
 		'_' => 'User queries',
@@ -148,6 +149,7 @@ return array(
 		'collapse_article' => 'Collapse',
 		'first_article' => 'Skip to the first article',
 		'focus_search' => 'Access search box',
+		'global_view' => 'Switch to global view',
 		'help' => 'Display documentation',
 		'javascript' => 'JavaScript must be enabled in order to use shortcuts',
 		'last_article' => 'Skip to the last article',
@@ -157,13 +159,17 @@ return array(
 		'navigation' => 'Navigation',
 		'navigation_help' => 'With the "Shift" modifier, navigation shortcuts apply on feeds.<br/>With the "Alt" modifier, navigation shortcuts apply on categories.',
 		'next_article' => 'Skip to the next article',
+		'normal_view' => 'Switch to normal view',
 		'other_action' => 'Other actions',
 		'previous_article' => 'Skip to the previous article',
+		'reading_view' => 'Switch to reading view',
+		'rss_view' => 'Open RSS view in a new tab',
 		'see_on_website' => 'See on original website',
 		'shift_for_all_read' => '+ <code>shift</code> to mark all articles as read',
 		'title' => 'Shortcuts',
 		'user_filter' => 'Access user filters',
 		'user_filter_help' => 'If there is only one user filter, it is used. Otherwise, filters are accessible by their number.',
+		'views' => 'Views',
 	),
 	'user' => array(
 		'articles_and_size' => '%s articles (%s)',

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

@@ -101,6 +101,10 @@ return array(
 			'_' => 'User %s has been deleted',
 			'error' => 'User %s cannot be deleted',
 		),
+		'updated' => array(
+			'_' => 'User %s has been updated',
+			'error' => 'User %s has not been updated',
+		),
 	),
 	'profile' => array(
 		'error' => 'Your profile cannot be modified',

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

@@ -19,6 +19,7 @@ return array(
 		'see_website' => 'See website',
 		'submit' => 'Submit',
 		'truncate' => 'Delete all articles',
+		'update' => 'Update',
 	),
 	'auth' => array(
 		'email' => 'Email address',

+ 4 - 4
app/i18n/en/index.php

@@ -41,18 +41,18 @@ return array(
 		'mark_cat_read' => 'Mark category as read',
 		'mark_feed_read' => 'Mark feed as read',
 		'newer_first' => 'Newer first',
-		'non-starred' => 'Show all but favourites',
+		'non-starred' => 'Show non-favourites',
 		'normal_view' => 'Normal view',
 		'older_first' => 'Oldest first',
 		'queries' => 'User queries',
-		'read' => 'Show only read',
+		'read' => 'Show read',
 		'reader_view' => 'Reading view',
 		'rss_view' => 'RSS feed',
 		'search_short' => 'Search',
-		'starred' => 'Show only favourites',
+		'starred' => 'Show favourites',
 		'stats' => 'Statistics',
 		'subscription' => 'Subscriptions management',
-		'unread' => 'Show only unread',
+		'unread' => 'Show unread',
 	),
 	'share' => 'Share',
 	'tag' => array(

+ 7 - 1
app/i18n/en/sub.php

@@ -32,12 +32,18 @@ return array(
 		'description' => 'Description',
 		'empty' => 'This feed is empty. Please verify that it is still maintained.',
 		'error' => 'This feed has encountered a problem. Please verify that it is always reachable then update it.',
-		'in_main_stream' => 'Show in main stream',
 		'informations' => 'Information',
 		'keep_history' => 'Minimum number of articles to keep',
 		'moved_category_deleted' => 'When you delete a category, its feeds are automatically classified under <em>%s</em>.',
+		'mute' => 'mute',
 		'no_selected' => 'No feed selected.',
 		'number_entries' => '%d articles',
+		'priority' => array(
+			'_' => 'Visibility',
+			'archived' => 'Do not show (archived)',
+			'main_stream' => 'Show in main stream',
+			'normal' => 'Show in its category',
+		),
 		'stats' => 'Statistics',
 		'think_to_add' => 'You may add some feeds.',
 		'title' => 'Title',

+ 3 - 0
app/i18n/es/admin.php

@@ -175,12 +175,15 @@ return array(
 	'user' => array(
 		'articles_and_size' => '%s articles (%s)',
 		'create' => 'Crear nuevo usuario',
+		'delete_users' => 'Delete user', // TODO
 		'language' => 'Idioma',
 		'number' => 'Hay %d cuenta creada',
 		'numbers' => 'Hay %d cuentas creadas',
 		'password_form' => 'Contraseña<br /><small>(para el método de identificación por formulario web)</small>',
 		'password_format' => 'Mínimo de 7 caracteres',
+		'selected' => 'Selected user', // TODO
 		'title' => 'Administrar usuarios',
+		'update_users' => 'Update user', // TODO
 		'user_list' => 'Lista de usuarios',
 		'username' => 'Nombre de usuario',
 		'users' => 'Usuarios',

+ 6 - 0
app/i18n/es/conf.php

@@ -37,6 +37,7 @@ return array(
 			'no_limit' => 'Sin límite',
 			'thin' => 'Estrecho',
 		),
+		'show_nav_buttons' => 'Show the navigation buttons',	//TODO
 	),
 	'query' => array(
 		'_' => 'Consultas de usuario',
@@ -148,6 +149,7 @@ return array(
 		'collapse_article' => 'Contraer',
 		'first_article' => 'Saltar al primer artículo',
 		'focus_search' => 'Acceso a la casilla de búsqueda',
+		'global_view' => 'Switch to global view', // TODO
 		'help' => 'Mostrar documentación',
 		'javascript' => 'JavaScript debe estar activado para poder usar atajos de teclado',
 		'last_article' => 'Saltar al último artículo',
@@ -157,13 +159,17 @@ return array(
 		'navigation' => 'Navegación',
 		'navigation_help' => 'Con el modificador "Mayúsculas" es posible usar los atajos de teclado en las fuentes.<br/>Con el modificador "Alt" es posible aplicar los atajos de teclado en las categorías.',
 		'next_article' => 'Saltar al siguiente artículo',
+		'normal_view' => 'Switch to normal view', // TODO
 		'other_action' => 'Otras acciones',
 		'previous_article' => 'Saltar al artículo anterior',
+		'reading_view' => 'Switch to reading view', // TODO
+		'rss_view' => 'Open RSS view in a new tab', // TODO
 		'see_on_website' => 'Ver en la web original',
 		'shift_for_all_read' => '+ <code>mayúsculas</code> para marcar todos los artículos como leídos',
 		'title' => 'Atajos de teclado',
 		'user_filter' => 'Acceso a filtros de usuario',
 		'user_filter_help' => 'Si solo hay un filtro de usuario, ese será el que se use. En caso contrario, los filtros están accesibles por su númeración.',
+		'views' => 'Views', // TODO
 	),
 	'user' => array(
 		'articles_and_size' => '%s artículos (%s)',

+ 4 - 0
app/i18n/es/feedback.php

@@ -101,6 +101,10 @@ return array(
 			'_' => 'El usuario %s ha sido eliminado',
 			'error' => 'El usuario %s no ha podido ser eliminado',
 		),
+		'updated' => array(
+			'_' => 'User %s has been updated', // TODO
+			'error' => 'User %s has not been updated', // TODO
+		),
 	),
 	'profile' => array(
 		'error' => 'Tu perfil no puede ser modificado',

+ 1 - 0
app/i18n/es/gen.php

@@ -19,6 +19,7 @@ return array(
 		'see_website' => 'Ver web',
 		'submit' => 'Enviar',
 		'truncate' => 'Borrar todos los artículos',
+		'update' => 'Update', // TODO
 	),
 	'auth' => array(
 		'email' => 'Correo electrónico',

+ 7 - 1
app/i18n/es/sub.php

@@ -27,12 +27,18 @@ return array(
 		'description' => 'Descripción',
 		'empty' => 'La fuente está vacía. Por favor, verifica que siga activa.',
 		'error' => 'Hay un problema con esta fuente. Por favor, veritica que esté disponible y prueba de nuevo.',
-		'in_main_stream' => 'Mostrar en salida principal',
 		'informations' => 'Información',
 		'keep_history' => 'Número mínimo de artículos a conservar',
 		'moved_category_deleted' => 'Al borrar una categoría todas sus fuentes pasan automáticamente a la categoría <em>%s</em>.',
+		'mute' => 'mute', // TODO
 		'no_selected' => 'No hay funentes seleccionadas.',
 		'number_entries' => '%d artículos',
+		'priority' => array(
+			'_' => 'Visibility', // TODO
+			'archived' => 'Do not show (archived)', // TODO
+			'main_stream' => 'Mostrar en salida principal',
+			'normal' => 'Show in its category', // TODO
+		),
 		'stats' => 'Estadísticas',
 		'think_to_add' => 'Puedes añadir fuentes.',
 		'title' => 'Título',

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

@@ -175,12 +175,15 @@ return array(
 	'user' => array(
 		'articles_and_size' => '%s articles (%s)',
 		'create' => 'Créer un nouvel utilisateur',
+		'delete_users' => 'Supprimer un utilisateur',
 		'language' => 'Langue',
 		'number' => '%d compte a déjà été créé',
 		'numbers' => '%d comptes ont déjà été créés',
 		'password_form' => 'Mot de passe<br /><small>(pour connexion par formulaire)</small>',
 		'password_format' => '7 caractères minimum',
+		'selected' => 'Utilisateur sélectionné',
 		'title' => 'Gestion des utilisateurs',
+		'update_users' => 'Mettre à jour un utilisateur',
 		'user_list' => 'Liste des utilisateurs',
 		'username' => 'Nom d’utilisateur',
 		'users' => 'Utilisateurs',

+ 6 - 0
app/i18n/fr/conf.php

@@ -37,6 +37,7 @@ return array(
 			'no_limit' => 'Pas de limite',
 			'thin' => 'Fine',
 		),
+		'show_nav_buttons' => 'Afficher les boutons de navigation',
 	),
 	'query' => array(
 		'_' => 'Filtres utilisateurs',
@@ -148,6 +149,7 @@ return array(
 		'collapse_article' => 'Refermer',
 		'first_article' => 'Passer au premier article',
 		'focus_search' => 'Accéder à la recherche',
+		'global_view' => 'Basculer vers la vue globale',
 		'help' => 'Afficher la documentation',
 		'javascript' => 'Le JavaScript doit être activé pour pouvoir profiter des raccourcis.',
 		'last_article' => 'Passer au dernier article',
@@ -157,13 +159,17 @@ return array(
 		'navigation' => 'Navigation',
 		'navigation_help' => 'Avec le modificateur "Shift", les raccourcis de navigation s’appliquent aux flux.<br/>Avec le modificateur "Alt", les raccourcis de navigation s’appliquent aux catégories.',
 		'next_article' => 'Passer à l’article suivant',
+		'normal_view' => 'Basculer vers la vue normale',
 		'other_action' => 'Autres actions',
 		'previous_article' => 'Passer à l’article précédent',
+		'reading_view' => 'Basculer vers la vue lecture',
+		'rss_view' => 'Ouvrir le flux RSS dans un nouvel onglet',
 		'see_on_website' => 'Voir sur le site d’origine',
 		'shift_for_all_read' => '+ <code>shift</code> pour marquer tous les articles comme lus',
 		'title' => 'Raccourcis',
 		'user_filter' => 'Accéder aux filtres utilisateur',
 		'user_filter_help' => 'S’il n’y a qu’un filtre utilisateur, celui-ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.',
+		'views' => 'Vues',
 	),
 	'user' => array(
 		'articles_and_size' => '%s articles (%s)',

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

@@ -101,6 +101,10 @@ return array(
 			'_' => 'L’utilisateur %s a été supprimé.',
 			'error' => 'L’utilisateur %s ne peut pas être supprimé.',
 		),
+		'updated' => array(
+			'_' => 'L’utilisateur %s a été mis à jour',
+			'error' => 'L’utilisateur %s n’a pas été mis à jour',
+		),
 	),
 	'profile' => array(
 		'error' => 'Votre profil n’a pas pu être mis à jour',

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

@@ -19,6 +19,7 @@ return array(
 		'see_website' => 'Voir le site',
 		'submit' => 'Valider',
 		'truncate' => 'Supprimer tous les articles',
+		'update' => 'Mettre à jour',
 	),
 	'auth' => array(
 		'email' => 'Adresse courriel',

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

@@ -41,7 +41,7 @@ return array(
 		'mark_cat_read' => 'Marquer la catégorie comme lue',
 		'mark_feed_read' => 'Marquer le flux comme lu',
 		'newer_first' => 'Plus récents en premier',
-		'non-starred' => 'Afficher tout sauf les favoris',
+		'non-starred' => 'Afficher les non-favoris',
 		'normal_view' => 'Vue normale',
 		'older_first' => 'Plus anciens en premier',
 		'queries' => 'Filtres utilisateurs',

+ 7 - 1
app/i18n/fr/sub.php

@@ -32,12 +32,18 @@ return array(
 		'description' => 'Description',
 		'empty' => 'Ce flux est vide. Veuillez vérifier qu’il est toujours maintenu.',
 		'error' => 'Ce flux a rencontré un problème. Veuillez vérifier qu’il est toujours accessible puis actualisez-le.',
-		'in_main_stream' => 'Afficher dans le flux principal',
 		'informations' => 'Informations',
 		'keep_history' => 'Nombre minimum d’articles à conserver',
 		'moved_category_deleted' => 'Lors de la suppression d’une catégorie, ses flux seront automatiquement classés dans <em>%s</em>.',
+		'mute' => 'muet',
 		'no_selected' => 'Aucun flux sélectionné.',
 		'number_entries' => '%d articles',
+		'priority' => array(
+			'_' => 'Visibilité',
+			'archived' => 'Ne pas afficher (archivé)',
+			'main_stream' => 'Afficher dans le flux principal',
+			'normal' => 'Afficher dans sa catégorie',
+		),
 		'stats' => 'Statistiques',
 		'think_to_add' => 'Vous pouvez ajouter des flux.',
 		'title' => 'Titre',

+ 3 - 0
app/i18n/he/admin.php

@@ -175,12 +175,15 @@ return array(
 	'user' => array(
 		'articles_and_size' => '%s articles (%s)', // @todo
 		'create' => 'יצירת משתמש חדש',
+		'delete_users' => 'Delete user', // TODO
 		'language' => 'שפה',
 		'number' => 'There is %d account created', // @todo
 		'numbers' => 'There are %d accounts created', // @todo
 		'password_form' => 'סיסמה<br /><small>(לשימוש בטפוס ההרשמה)</small>',
 		'password_format' => 'At least 7 characters', // @todo
+		'selected' => 'Selected user', // TODO
 		'title' => 'Manage users', // @todo
+		'update_users' => 'Update user', // TODO
 		'user_list' => 'רשימת משתמשים',
 		'username' => 'שם משתמש',
 		'users' => 'משתמשים',

+ 6 - 0
app/i18n/he/conf.php

@@ -37,6 +37,7 @@ return array(
 			'no_limit' => 'ללא הגבלה',
 			'thin' => 'צר',
 		),
+		'show_nav_buttons' => 'Show the navigation buttons',	//TODO
 	),
 	'query' => array(
 		'_' => 'שאילתות',
@@ -145,6 +146,7 @@ return array(
 		'collapse_article' => 'כיווץ',
 		'first_article' => 'דילוג למאמר הראשון',
 		'focus_search' => 'גישה לתיבת החיפוש',
+		'global_view' => 'Switch to global view', // TODO
 		'help' => 'הצגת התיעוד',
 		'javascript' => 'חובה להפעיל JavaScript על מנת לעשות שימוש בקיצורי דרך',
 		'last_article' => 'דילוג למאמר האחרון',
@@ -154,13 +156,17 @@ return array(
 		'navigation' => 'ניווט',
 		'navigation_help' => 'בעזרת מקש השיפט קיצורי דרך חלים על הזנות .<br/>עם מקש האלט הם חלים על קטגוריות.',
 		'next_article' => 'דילוג למאמר הבא',
+		'normal_view' => 'Switch to normal view', // TODO
 		'other_action' => 'פעולות אחרות',
 		'previous_article' => 'דילוג למאמר הקודם',
+		'reading_view' => 'Switch to reading view', // TODO
+		'rss_view' => 'Open RSS view in a new tab', // TODO
 		'see_on_website' => 'ראו את המקור באתר',
 		'shift_for_all_read' => '+ <code>shift</code> על מנת לסמן את כל המאמרים כנקראו',
 		'title' => 'קיצורי דרך',
 		'user_filter' => 'גישה למססנים',
 		'user_filter_help' => 'אם יש רק מזנן אחד הוא יהיה בשימוש. אחרת המסננים ישמשו על בסיס המספר שלהם.',
+		'views' => 'Views', // TODO
 	),
 	'user' => array(
 		'articles_and_size' => '%s articles (%s)', // @todo

+ 4 - 0
app/i18n/he/feedback.php

@@ -102,6 +102,10 @@ return array(
 			'_' => 'המשתמש %s נמחק',
 			'error' => 'User %s cannot be deleted', // @todo
 		),
+		'updated' => array(
+			'_' => 'User %s has been updated', // TODO
+			'error' => 'User %s has not been updated', // TODO
+		),
 	),
 	'profile' => array(
 		'error' => 'Your profile cannot be modified', // @todo

+ 1 - 0
app/i18n/he/gen.php

@@ -19,6 +19,7 @@ return array(
 		'see_website' => 'ראו אתר',
 		'submit' => 'אישור',
 		'truncate' => 'מחיקת כל המאמרים',
+		'update' => 'Update', // TODO
 	),
 	'auth' => array(
 		'email' => 'Email address', // @todo

+ 7 - 1
app/i18n/he/sub.php

@@ -32,12 +32,18 @@ return array(
 		'description' => 'תיאור',
 		'empty' => 'הזנה זו ריקה. אנא ודאו שהיא עדיין מתוחזקת.',
 		'error' => 'הזנה זו נתקלה בשגיאה, אנא ודאו שהיא תקינה ואז נסו שנית.',
-		'in_main_stream' => 'הצגה בזרם המרכזי',
 		'informations' => 'מידע',
 		'keep_history' => 'מסםר מינימלי של מאמרים לשמור',
 		'moved_category_deleted' => 'כאשר הקטגוריה נמחקת ההזנות שבתוכה אוטומטית מקוטלגות תחת  <em>%s</em>.',
+		'mute' => 'mute', // TODO
 		'no_selected' => 'אף הזנה לא נבחרה.',
 		'number_entries' => '%d מאמרים',
+		'priority' => array(
+			'_' => 'Visibility', // TODO
+			'archived' => 'Do not show (archived)', // TODO
+			'main_stream' => 'הצגה בזרם המרכזי',
+			'normal' => 'Show in its category', // TODO
+		),
 		'stats' => 'סטטיסטיקות',
 		'think_to_add' => 'ניתן להוסיף הזנות חדשות.',
 		'title' => 'כותרת',

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

@@ -175,12 +175,15 @@ return array(
 	'user' => array(
 		'articles_and_size' => '%s articoli (%s)',
 		'create' => 'Crea nuovo utente',
+		'delete_users' => 'Delete user', // TODO
 		'language' => 'Lingua',
 		'number' => ' %d profilo utente creato',
 		'numbers' => 'Sono presenti %d profili utente',
 		'password_form' => 'Password<br /><small>(per il login classico)</small>',
 		'password_format' => 'Almeno 7 caratteri',
+		'selected' => 'Selected user', // TODO
 		'title' => 'Gestione utenti',
+		'update_users' => 'Update user', // TODO
 		'user_list' => 'Lista utenti',
 		'username' => 'Nome utente',
 		'users' => 'Utenti',

+ 6 - 0
app/i18n/it/conf.php

@@ -37,6 +37,7 @@ return array(
 			'no_limit' => 'Nessun limite',
 			'thin' => 'Stretto',
 		),
+		'show_nav_buttons' => 'Show the navigation buttons',	//TODO
 	),
 	'query' => array(
 		'_' => 'Ricerche personali',
@@ -148,6 +149,7 @@ return array(
 		'collapse_article' => 'Collassa articoli',
 		'first_article' => 'Salta al primo articolo',
 		'focus_search' => 'Modulo di ricerca',
+		'global_view' => 'Switch to global view', // TODO
 		'help' => 'Mostra documentazione',
 		'javascript' => 'JavaScript deve essere abilitato per poter usare i comandi da tastiera',
 		'last_article' => 'Salta all ultimo articolo',
@@ -157,13 +159,17 @@ return array(
 		'navigation' => 'Navigazione',
 		'navigation_help' => 'Con il tasto "Shift" i comandi di navigazione verranno applicati ai feeds.<br/>Con il tasto "Alt" i comandi di navigazione verranno applicati alle categorie.',
 		'next_article' => 'Salta al contenuto successivo',
+		'normal_view' => 'Switch to normal view', // TODO
 		'other_action' => 'Altre azioni',
 		'previous_article' => 'Salta al contenuto precedente',
+		'reading_view' => 'Switch to reading view', // TODO
+		'rss_view' => 'Open RSS view in a new tab', // TODO
 		'see_on_website' => 'Vai al sito fonte',
 		'shift_for_all_read' => '+ <code>shift</code> per segnare tutti gli articoli come letti',
 		'title' => 'Comandi da tastiera',
 		'user_filter' => 'Accedi alle ricerche personali',
 		'user_filter_help' => 'Se è presente una sola ricerca personale verrà usata quella, altrimenti usare anche il numero associato.',
+		'views' => 'Views', // TODO
 	),
 	'user' => array(
 		'articles_and_size' => '%s articoli (%s)',

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

@@ -101,6 +101,10 @@ return array(
 			'_' => 'Utente %s cancellato',
 			'error' => 'Utente %s non cancellato',
 		),
+		'updated' => array(
+			'_' => 'User %s has been updated', // TODO
+			'error' => 'User %s has not been updated', // TODO
+		),
 	),
 	'profile' => array(
 		'error' => 'Il tuo profilo non può essere modificato',

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

@@ -19,6 +19,7 @@ return array(
 		'see_website' => 'Vai al sito',
 		'submit' => 'Conferma',
 		'truncate' => 'Cancella tutti gli articoli',
+		'update' => 'Update', // TODO
 	),
 	'auth' => array(
 		'email' => 'Indirizzo email',

+ 7 - 1
app/i18n/it/sub.php

@@ -32,12 +32,18 @@ return array(
 		'description' => 'Descrizione',
 		'empty' => 'Questo feed non contiene articoli. Per favore verifica il sito direttamente.',
 		'error' => 'Questo feed ha generato un errore. Per favore verifica se ancora disponibile.',
-		'in_main_stream' => 'Mostra in homepage',
 		'informations' => 'Informazioni',
 		'keep_history' => 'Numero minimo di articoli da mantenere',
 		'moved_category_deleted' => 'Cancellando una categoria i feed al suo interno verranno classificati automaticamente come <em>%s</em>.',
+		'mute' => 'mute', // TODO
 		'no_selected' => 'Nessun feed selezionato.',
 		'number_entries' => '%d articoli',
+		'priority' => array(
+			'_' => 'Visibility', // TODO
+			'archived' => 'Do not show (archived)', // TODO
+			'main_stream' => 'Mostra in homepage', // TODO
+			'normal' => 'Show in its category', // TODO
+		),
 		'stats' => 'Statistiche',
 		'think_to_add' => 'Aggiungi feed.',
 		'title' => 'Titolo',

+ 10 - 7
app/i18n/kr/admin.php

@@ -112,13 +112,13 @@ return array(
 		),
 		'title' => '확장 기능',
 		'user' => '사용자 확장 기능',
-		'community' => 'Available community extensions', // @todo translate
-		'name' => 'Name', // @todo translate
-		'version' => 'Version', // @todo translate
-		'description' => 'Description', // @todo translate
-		'author' => 'Author', // @todo translate
-		'latest' => 'Installed', // @todo translate
-		'update' => 'Update available', // @todo translate
+		'community' => '사용 가능한 커뮤니티 확장 기능들',
+		'name' => '이름',
+		'version' => '버전',
+		'description' => '설명',
+		'author' => '제작자',
+		'latest' => '설치됨',
+		'update' => '업데이트 있음',
 	),
 	'stats' => array(
 		'_' => '통계',
@@ -175,12 +175,15 @@ return array(
 	'user' => array(
 		'articles_and_size' => '%s 개의 글 (%s)',
 		'create' => '새 사용자 생성',
+		'delete_users' => 'Delete user', // TODO
 		'language' => '언어',
 		'number' => '%d 개의 계정이 생성되었습니다',
 		'numbers' => '%d 개의 계정이 생성되었습니다',
 		'password_form' => '암호<br /><small>(웹폼 로그인 방식 사용시)</small>',
 		'password_format' => '7 글자 이상이어야 합니다',
+		'selected' => 'Selected user', // TODO
 		'title' => '사용자 관리',
+		'update_users' => 'Update user', // TODO
 		'user_list' => '사용자 목록',
 		'username' => '사용자 이름',
 		'users' => '전체 사용자',

+ 6 - 0
app/i18n/kr/conf.php

@@ -37,6 +37,7 @@ return array(
 			'no_limit' => '제한 없음',
 			'thin' => '얇음',
 		),
+		'show_nav_buttons' => 'Show the navigation buttons',	//TODO
 	),
 	'query' => array(
 		'_' => '사용자 쿼리',
@@ -148,6 +149,7 @@ return array(
 		'collapse_article' => '접기',
 		'first_article' => '첫 글 보기',
 		'focus_search' => '검색창 사용하기',
+		'global_view' => '전체 모드로 전환',
 		'help' => '도움말 보기',
 		'javascript' => '단축키를 사용하기 위해선 자바스크립트를 사용하도록 설정하여야 합니다',
 		'last_article' => '마지막 글 보기',
@@ -157,13 +159,17 @@ return array(
 		'navigation' => '탐색',
 		'navigation_help' => '"Shift" 키를 누른 상태에선 탐색 단축키가 피드에 적용됩니다.<br/>"Alt" 키를 누른 상태에선 탐색 단축키가 카테고리에 적용됩니다.',
 		'next_article' => '다음 글 보기',
+		'normal_view' => '일반 모드로 전환',
 		'other_action' => '다른 동작',
 		'previous_article' => '이전 글 보기',
+		'reading_view' => '읽기 모드로 전환',
+		'rss_view' => '새 탭에서 RSS 피드 열기',
 		'see_on_website' => '글이 게재된 웹사이트에서 보기',
 		'shift_for_all_read' => '+ <code>shift</code>를 누른 상태에선 모두 읽음으로 표시',
 		'title' => '단축키',
 		'user_filter' => '사용자 필터 사용하기',
 		'user_filter_help' => '사용자 필터가 하나만 설정되어 있다면 해당 필터를 사용하고, 그렇지 않다면 필터를 번호로 선택할 수 있습니다.',
+		'views' => '표시',
 	),
 	'user' => array(
 		'articles_and_size' => '%s 개의 글 (%s)',

+ 5 - 1
app/i18n/kr/feedback.php

@@ -51,7 +51,7 @@ return array(
 		'zip_error' => 'ZIP 파일을 불러오는 동안 문제가 발생했습니다.',
 	),
 	'sub' => array(
-		'actualize' => 'Updating',
+		'actualize' => '피드를 가져오는 중입니다',
 		'category' => array(
 			'created' => '%s 카테고리가 생성되었습니다.',
 			'deleted' => '카테고리가 삭제되었습니다.',
@@ -101,6 +101,10 @@ return array(
 			'_' => '%s 사용자를 삭제했습니다',
 			'error' => '%s 사용자를 삭제할 수 없습니다',
 		),
+		'updated' => array(
+			'_' => 'User %s has been updated', // TODO
+			'error' => 'User %s has not been updated', // TODO
+		),
 	),
 	'profile' => array(
 		'error' => '프로필을 변경할 수 없습니다',

+ 1 - 0
app/i18n/kr/gen.php

@@ -19,6 +19,7 @@ return array(
 		'see_website' => '웹사이트 열기',
 		'submit' => '설정 저장',
 		'truncate' => '모든 글 삭제',
+		'update' => 'Update', // TODO
 	),
 	'auth' => array(
 		'email' => '메일 주소',

+ 9 - 3
app/i18n/kr/sub.php

@@ -2,8 +2,8 @@
 
 return array(
 	'api' => array(
-		'documentation' => 'Copy the following URL to use it within an external tool.',// TODO
-		'title' => 'API',// TODO
+		'documentation' => '외부 도구에서 API를 사용하기 위해서 아래 URL을 사용하세요.',
+		'title' => 'API',
 	),
 	'bookmarklet' => array(
 		'documentation' => '이 버튼을 즐겨찾기 막대로 끌어다 놓거나 마우스 오른쪽 클릭으로 나타나는 메뉴에서 "이 링크를 즐겨찾기에 추가"를 선택하세요. 그리고 피드를 구독하길 원하는 페이지에서 "구독하기" 버튼을 클릭하세요.',
@@ -32,12 +32,18 @@ return array(
 		'description' => '설명',
 		'empty' => '이 피드는 비어있습니다. 피드가 계속 운영되고 있는지 확인하세요.',
 		'error' => '이 피드에 문제가 발생했습니다. 이 피드에 접근 권한이 있는지 확인하세요.',
-		'in_main_stream' => '메인 스트림에 표시하기',
 		'informations' => '정보',
 		'keep_history' => '최소 유지 글 개수',
 		'moved_category_deleted' => '카테고리를 삭제하면, 해당 카테고리 아래에 있던 피드들은 자동적으로 <em>%s</em> 아래로 분류됩니다.',
+		'mute' => '무기한 새로고침 금지',
 		'no_selected' => '선택된 피드가 없습니다.',
 		'number_entries' => '%d 개의 글',
+		'priority' => array(
+			'_' => '표시',
+			'archived' => '표시하지 않음 (보관됨)',
+			'main_stream' => '메인 스트림에 표시하기',
+			'normal' => '피드가 속한 카테고리에만 표시하기',
+		),
 		'stats' => '통계',
 		'think_to_add' => '피드를 추가할 수 있습니다.',
 		'title' => '제목',

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

@@ -175,6 +175,7 @@ return array(
 	'user' => array(
 		'articles_and_size' => '%s artikelen (%s)',
 		'create' => 'Creëer nieuwe gebruiker',
+		'delete_users' => 'Delete user', // TODO
 		'language' => 'Taal',
 		'number' => 'Er is %d accounts gemaakt',
 		'numbers' => 'Er zijn %d accounts gemaakt',
@@ -185,7 +186,9 @@ return array(
 			'help' => '0 betekent dat er geen accountlimiet is',
 			'number' => 'Max aantal accounts',
 		),
+		'selected' => 'Selected user', // TODO
 		'title' => 'Beheer gebruikers',
+		'update_users' => 'Update user', // TODO
 		'user_list' => 'Lijst van gebruikers ',
 		'username' => 'Gebruikersnaam',
 		'users' => 'Gebruikers',

+ 6 - 0
app/i18n/nl/conf.php

@@ -37,6 +37,7 @@ return array(
 			'no_limit' => 'Geen limiet',
 			'thin' => 'Smal',
 		),
+		'show_nav_buttons' => 'Show the navigation buttons',	//TODO
 	),
 	'query' => array(
 		'_' => 'Gebruikers queries (informatie aanvragen)',
@@ -148,6 +149,7 @@ return array(
 		'collapse_article' => 'Inklappen',
 		'first_article' => 'Spring naar eerste artikel',
 		'focus_search' => 'Toegang zoek venster',
+		'global_view' => 'Switch to global view', // TODO
 		'help' => 'Toon documentatie',
 		'javascript' => 'JavaScript moet geactiveerd zijn om verwijzingen te gebruiken',
 		'last_article' => 'Spring naar laatste artikel',
@@ -157,13 +159,17 @@ return array(
 		'navigation' => 'Navigatie',
 		'navigation_help' => 'Met de "Shift" toets, kunt u navigatie verwijzingen voor feeds gebruiken.<br/>Met de "Alt" toets, kunt u navigatie verwijzingen voor categoriën gebruiken.',
 		'next_article' => 'Spring naar volgende artikel',
+		'normal_view' => 'Switch to normal view', // TODO
 		'other_action' => 'Andere acties',
 		'previous_article' => 'Spring naar vorige artikel',
+		'reading_view' => 'Switch to reading view', // TODO
+		'rss_view' => 'Open RSS view in a new tab', // TODO
 		'see_on_website' => 'Bekijk op originale website',
 		'shift_for_all_read' => '+ <code>shift</code> om alle artikelen als gelezen te markeren',
 		'title' => 'Verwijzingen',
 		'user_filter' => 'Toegang gebruikers filters',
 		'user_filter_help' => 'Als er slechts één gebruikers filter s, dan wordt deze gebruikt. Anders zijn ze toegankelijk met hun nummer.',
+		'views' => 'Views', // TODO
 	),
 	'user' => array(
 		'articles_and_size' => '%s artikelen (%s)',

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

@@ -101,6 +101,10 @@ return array(
 			'_' => 'Gebruiker %s is verwijderd',
 			'error' => 'Gebruiker %s kan niet worden verwijderd',
 		),
+		'updated' => array(
+			'_' => 'User %s has been updated', // TODO
+			'error' => 'User %s has not been updated', // TODO
+		),
 		'set_registration' => 'Het maximale aantal accounts is vernieuwd.',
 	),
 	'profile' => array(

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

@@ -19,6 +19,7 @@ return array(
 		'see_website' => 'Bekijk website',
 		'submit' => 'Opslaan',
 		'truncate' => 'Verwijder alle artikelen',
+		'update' => 'Update', // TODO
 	),
 	'auth' => array(
 		'email' => 'Email adres',

+ 10 - 4
app/i18n/nl/sub.php

@@ -2,7 +2,7 @@
 /* Dutch translation by Wanabo. http://www.nieuwskop.be */
 return array(
 	'api' => array(
-		'documentation' => 'Kopieer de volgende URL om hem in een externe toepassing te gebruiken.',
+		'documentation' => 'Kopieer de volgende URL om deze in een externe toepassing te gebruiken.',
 		'title' => 'API',
 	),
 	'bookmarklet' => array(
@@ -32,12 +32,18 @@ return array(
 		'description' => 'Omschrijving',
 		'empty' => 'Deze feed is leeg. Controleer of deze nog actueel is.',
 		'error' => 'Deze feed heeft problemen. Verifieer a.u.b het doeladres en actualiseer het.',
-		'in_main_stream' => 'Zichtbaar in het overzicht',
 		'informations' => 'Informatie',
 		'keep_history' => 'Minimum aantal artikelen om te houden',
 		'moved_category_deleted' => 'Als u een categorie verwijderd, worden de feeds automatisch geclassificeerd onder <em>%s</em>.',
+		'mute' => 'mute', // TODO
 		'no_selected' => 'Geen feed geselecteerd.',
 		'number_entries' => '%d artikelen',
+		'priority' => array(
+			'_' => 'Visibility', // TODO
+			'archived' => 'Do not show (archived)', // TODO
+			'main_stream' => 'Zichtbaar in het overzicht',
+			'normal' => 'Show in its category', // TODO
+		),
 		'pubsubhubbub' => 'Directe notificaties met PubSubHubbub',
 		'stats' => 'Statistieken',
 		'think_to_add' => 'Voeg wat feeds toe.',
@@ -49,13 +55,13 @@ return array(
 		'website' => 'Website URL',
 	),
 	'firefox' => array(
-		'documentation' => 'Volg de stappen die <a href="https://developer.mozilla.org/en-US/Firefox/Releases/2/Adding_feed_readers_to_Firefox#Adding_a_new_feed_reader_manually">hier</a> beschreven wordem om FreshRSS aan de Firefox-nieuwslezerlijst toe te voegen.',
+		'documentation' => 'Volg de stappen die <a href="https://developer.mozilla.org/en-US/Firefox/Releases/2/Adding_feed_readers_to_Firefox#Adding_a_new_feed_reader_manually">hier</a> beschreven worden om FreshRSS aan de Firefox-nieuwslezerlijst toe te voegen.',
 		'title' => 'Firefox-nieuwslezer',
 	),
 	'import_export' => array(
 		'export' => 'Exporteer',
 		'export_opml' => 'Exporteer lijst van feeds (OPML)',
-		'export_starred' => 'Exporteer je fovorieten',
+		'export_starred' => 'Exporteer je favorieten',
 		'feed_list' => 'Lijst van %s artikelen',
 		'file_to_import' => 'Bestand om te importeren<br />(OPML, JSON of ZIP)',
 		'file_to_import_no_zip' => 'Bestand om te importeren<br />(OPML of JSON)',

+ 3 - 0
app/i18n/pt-br/admin.php

@@ -175,12 +175,15 @@ return array(
 	'user' => array(
 		'articles_and_size' => '%s artigos (%s)',
 		'create' => 'Criar novo usuário',
+		'delete_users' => 'Delete user', // TODO
 		'language' => 'Idioma',
 		'number' => 'Há %d conta criada',
 		'numbers' => 'Há %d contas criadas',
 		'password_form' => 'Senha<br /><small>(para o login pelo método do formulário)</small>',
 		'password_format' => 'Ao menos 7 caracteres',
+		'selected' => 'Selected user', // TODO
 		'title' => 'Gerenciar usuários',
+		'update_users' => 'Update user', // TODO
 		'user_list' => 'Lista de usuários',
 		'username' => 'Usuário',
 		'users' => 'Usuários',

+ 6 - 0
app/i18n/pt-br/conf.php

@@ -37,6 +37,7 @@ return array(
 			'no_limit' => 'Sem lmite',
 			'thin' => 'Fino',
 		),
+		'show_nav_buttons' => 'Show the navigation buttons',	//TODO
 	),
 	'query' => array(
 		'_' => 'Queries do usuário',
@@ -148,6 +149,7 @@ return array(
 		'collapse_article' => 'Fechar',
 		'first_article' => 'Ir para o primeiro artigo',
 		'focus_search' => 'Acessar a caixa de busca',
+		'global_view' => 'Switch to global view', // TODO
 		'help' => 'Mostrar documentação',
 		'javascript' => 'JavaScript deve ser habilitado para utilizar atalhos',
 		'last_article' => 'Ir para o último artigo',
@@ -157,13 +159,17 @@ return array(
 		'navigation' => 'Navegação',
 		'navigation_help' => 'Com o modificador "Shift", atalhos de navegação aplicam aos feeds.<br/>Com o "Alt" modificador, atalhos de navegação aplicam as categorias.',
 		'next_article' => 'Pule para o próximo artigo',
+		'normal_view' => 'Switch to normal view', // TODO
 		'other_action' => 'Outras ações',
 		'previous_article' => 'Pule para o artigo anterior',
+		'reading_view' => 'Switch to reading view', // TODO
+		'rss_view' => 'Open RSS view in a new tab', // TODO
 		'see_on_website' => 'Visualize o site original',
 		'shift_for_all_read' => '+ <code>shift</code> para marcar todos os artigos como lido',
 		'title' => 'Atalhos',
 		'user_filter' => 'Acesse filtros de usuário',
 		'user_filter_help' => 'Se há apenas um filtro, ele é utilizado. Caso contrário, os filtros serão acessíveis pelos seus números.',
+		'views' => 'Views', // TODO
 	),
 	'user' => array(
 		'articles_and_size' => '%s artigos (%s)',

+ 4 - 0
app/i18n/pt-br/feedback.php

@@ -101,6 +101,10 @@ return array(
 			'_' => 'Usuário %s foi deletado',
 			'error' => 'Usuário %s não pode ser deletado',
 		),
+		'updated' => array(
+			'_' => 'User %s has been updated', // TODO
+			'error' => 'User %s has not been updated', // TODO
+		),
 	),
 	'profile' => array(
 		'error' => 'Your profile cannot be modified',

+ 1 - 0
app/i18n/pt-br/gen.php

@@ -19,6 +19,7 @@ return array(
 		'see_website' => 'Ver o site',
 		'submit' => 'Enviar',
 		'truncate' => 'Deletar todos os artigos',
+		'update' => 'Update', // TODO
 	),
 	'auth' => array(
 		'email' => 'Endereço de e-mail',

+ 7 - 1
app/i18n/pt-br/sub.php

@@ -32,12 +32,18 @@ return array(
 		'description' => 'Descrição',
 		'empty' => 'Este feed está vazio. Por favor verifique ele ainda é mantido.',
 		'error' => 'Este feed encontra-se com problema. Por favor verifique se ele ainda está disponível e atualize-o.',
-		'in_main_stream' => 'Mostrar na tela principal',
 		'informations' => 'Informações',
 		'keep_history' => 'Número mínimo de artigos para manter',
 		'moved_category_deleted' => 'Quando você deleta uma categoria, seus feeds são automaticamente classificados como <em>%s</em>.',
+		'mute' => 'mute', // TODO
 		'no_selected' => 'Nenhum feed selecionado.',
 		'number_entries' => '%d artigos',
+		'priority' => array(
+			'_' => 'Visibility', // TODO
+			'archived' => 'Do not show (archived)', // TODO
+			'main_stream' => 'Mostrar na tela principal',
+			'normal' => 'Show in its category', // TODO
+		),
 		'stats' => 'Estatísticas',
 		'think_to_add' => 'Você deve adicionar alguns feeds.',
 		'title' => 'Título',

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

@@ -175,12 +175,15 @@ return array(
 	'user' => array(
 		'articles_and_size' => '%s статей (%s)',
 		'create' => 'Создать нового пользователя',
+		'delete_users' => 'Delete user', // TODO
 		'language' => 'Язык',
 		'number' => 'На данный момент создан %d аккаунт',
 		'numbers' => 'На данный момент аккаунтов создано:  %d',
 		'password_form' => 'Пароль<br /><small>(для входа через Веб-форму)</small>',
 		'password_format' => 'Минимум 7 символов',
+		'selected' => 'Selected user', // TODO
 		'title' => 'Управление пользователями',
+		'update_users' => 'Update user', // TODO
 		'user_list' => 'Список пользователей',
 		'username' => 'Имя пользователя',
 		'users' => 'Пользователи',

+ 6 - 0
app/i18n/ru/conf.php

@@ -37,6 +37,7 @@ return array(
 			'no_limit' => 'No limit',
 			'thin' => 'Thin',
 		),
+		'show_nav_buttons' => 'Show the navigation buttons',	//TODO
 	),
 	'query' => array(
 		'_' => 'User queries',
@@ -148,6 +149,7 @@ return array(
 		'collapse_article' => 'Collapse',
 		'first_article' => 'Skip to the first article',
 		'focus_search' => 'Access search box',
+		'global_view' => 'Switch to global view',  // TODO
 		'help' => 'Display documentation',
 		'javascript' => 'JavaScript must be enabled in order to use shortcuts',
 		'last_article' => 'Skip to the last article',
@@ -157,13 +159,17 @@ return array(
 		'navigation' => 'Navigation',
 		'navigation_help' => 'With the "Shift" modifier, navigation shortcuts apply on feeds.<br/>With the "Alt" modifier, navigation shortcuts apply on categories.',
 		'next_article' => 'Skip to the next article',
+		'normal_view' => 'Switch to normal view', // TODO
 		'other_action' => 'Other actions',
 		'previous_article' => 'Skip to the previous article',
+		'reading_view' => 'Switch to reading view', // TODO
+		'rss_view' => 'Open RSS view in a new tab', // TODO
 		'see_on_website' => 'See on original website',
 		'shift_for_all_read' => '+ <code>shift</code> to mark all articles as read',
 		'title' => 'Shortcuts',
 		'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.',
+		'views' => 'Views', // TODO
 	),
 	'user' => array(
 		'articles_and_size' => '%s articles (%s)',

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

@@ -101,6 +101,10 @@ return array(
 			'_' => 'User %s has been deleted',	//TODO
 			'error' => 'User %s cannot be deleted',	//TODO
 		),
+		'updated' => array(
+			'_' => 'User %s has been updated', // TODO
+			'error' => 'User %s has not been updated', // TODO
+		),
 	),
 	'profile' => array(
 		'error' => 'Your profile cannot be modified',	//TODO

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

@@ -19,6 +19,7 @@ return array(
 		'see_website' => 'See website',
 		'submit' => 'Submit',
 		'truncate' => 'Delete all articles',
+		'update' => 'Update', // TODO
 	),
 	'auth' => array(
 		'email' => 'Email address',

+ 7 - 1
app/i18n/ru/sub.php

@@ -32,12 +32,18 @@ return array(
 		'description' => 'Description',// TODO
 		'empty' => 'This feed is empty. Please verify that it is still maintained.',// TODO
 		'error' => 'This feed has encountered a problem. Please verify that it is always reachable then actualize it.',// TODO
-		'in_main_stream' => 'Show in main stream',// TODO
 		'informations' => 'Information',// TODO
 		'keep_history' => 'Minimum number of articles to keep',// TODO
 		'moved_category_deleted' => 'When you delete a category, its feeds are automatically classified under <em>%s</em>.',// TODO
+		'mute' => 'mute', // TODO
 		'no_selected' => 'No feed selected.',// TODO
 		'number_entries' => '%d articles',// TODO
+		'priority' => array(
+			'_' => 'Visibility', // TODO
+			'archived' => 'Do not show (archived)', // TODO
+			'main_stream' => 'Show in main stream', // TODO
+			'normal' => 'Show in its category', // TODO
+		),
 		'stats' => 'Statistics',// TODO
 		'think_to_add' => 'You may add some feeds.',// TODO
 		'title' => 'Title',// TODO

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

@@ -175,12 +175,15 @@ return array(
 	'user' => array(
 		'articles_and_size' => '%s makale (%s)',
 		'create' => 'Yeni kullanıcı oluştur',
+		'delete_users' => 'Delete user', // TODO
 		'language' => 'Dil',
 		'number' => 'Oluşturulmuş %d hesap var',
 		'numbers' => 'Oluşturulmuş %d hesap var',
 		'password_form' => 'Şifre<br /><small>(Tarayıcı girişi için)</small>',
 		'password_format' => 'En az 7 karakter',
+		'selected' => 'Selected user', // TODO
 		'title' => 'Kullanıcıları yönet',
+		'update_users' => 'Update user', // TODO
 		'user_list' => 'Kullanıcı listesi',
 		'username' => 'Kullanıcı adı',
 		'users' => 'Kullanıcılar',

+ 6 - 0
app/i18n/tr/conf.php

@@ -37,6 +37,7 @@ return array(
 			'no_limit' => 'Sınırsız',
 			'thin' => 'Zayıf',
 		),
+		'show_nav_buttons' => 'Show the navigation buttons',	//TODO
 	),
 	'query' => array(
 		'_' => 'Kullanıcı sorguları',
@@ -148,6 +149,7 @@ return array(
 		'collapse_article' => 'Kapat',
 		'first_article' => 'İlk makaleyi atla',
 		'focus_search' => 'Arama kutusuna eriş',
+		'global_view' => 'Switch to global view', // TODO
 		'help' => 'Dokümantasyonu göster',
 		'javascript' => 'Kısayolları kullanabilmek için JavaScript aktif olmalıdır',
 		'last_article' => 'Son makaleyi atla',
@@ -157,13 +159,17 @@ return array(
 		'navigation' => 'Genel eylemler',
 		'navigation_help' => '"Shift" tuşu ile kısayollar akışlar için geçerli olur.<br/>"Alt" tuşu ile kısayollar kategoriler için geçerli olur.',
 		'next_article' => 'Sonraki makaleye geç',
+		'normal_view' => 'Switch to normal view', // TODO
 		'other_action' => 'Diğer eylemler',
 		'previous_article' => 'Önceki makaleye geç',
+		'reading_view' => 'Switch to reading view', // TODO
+		'rss_view' => 'Open RSS view in a new tab', // TODO
 		'see_on_website' => 'Orijinal sitede göster',
 		'shift_for_all_read' => '+ <code>shift</code> tuşu ile tüm makaleler okundu olarak işaretlenir',
 		'title' => 'Kısayollar',
 		'user_filter' => 'Kullanıcı filtrelerine eriş',
 		'user_filter_help' => 'Eğer tek filtre varsa o kullanılır. Yoksa filtrelerin kendi numaralarıyla kullanılır.',
+		'views' => 'Views', // TODO
 	),
 	'user' => array(
 		'articles_and_size' => '%s makale (%s)',

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

@@ -101,6 +101,10 @@ return array(
 			'_' => '%s kullanıcısı silindi',
 			'error' => '%s kullanıcısı silinemedi',
 		),
+		'updated' => array(
+			'_' => 'User %s has been updated', // TODO
+			'error' => 'User %s has not been updated', // TODO
+		),
 	),
 	'profile' => array(
 		'error' => 'Profiliniz düzenlenemedi',

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

@@ -19,6 +19,7 @@ return array(
 		'see_website' => 'Siteyi gör',
 		'submit' => 'Onayla',
 		'truncate' => 'Tüm makaleleri sil',
+		'update' => 'Update', // TODO
 	),
 	'auth' => array(
 		'email' => 'Email adresleri',

+ 7 - 1
app/i18n/tr/sub.php

@@ -32,12 +32,18 @@ return array(
 		'description' => 'Tanım',
 		'empty' => 'Bu akış boş. Lütfen akışın aktif olduğuna emin olun.',
 		'error' => 'Bu akışda bir hatayla karşılaşıldı. Lütfen akışın sürekli ulaşılabilir olduğuna emin olun.',
-		'in_main_stream' => 'Ana akışda göster',
 		'informations' => 'Bilgi',
 		'keep_history' => 'En az tutulacak makale sayısı',
 		'moved_category_deleted' => 'Bir kategoriyi silerseniz, içerisindeki akışlar <em>%s</em> içerisine yerleşir.',
+		'mute' => 'mute', // TODO
 		'no_selected' => 'Hiçbir akış seçilmedi.',
 		'number_entries' => '%d makale',
+		'priority' => array(
+			'_' => 'Visibility', // TODO
+			'archived' => 'Do not show (archived)', // TODO
+			'main_stream' => 'Ana akışda göster',
+			'normal' => 'Show in its category', // TODO
+		),
 		'stats' => 'İstatistikler',
 		'think_to_add' => 'Akış ekleyebilirsiniz.',
 		'title' => 'Başlık',

+ 10 - 7
app/i18n/zh-cn/admin.php

@@ -112,13 +112,13 @@ return array(
 		),
 		'title' => '扩展',
 		'user' => '用户扩展',
-		'community' => 'Available community extensions', // @todo translate
-		'name' => 'Name', // @todo translate
-		'version' => 'Version', // @todo translate
-		'description' => 'Description', // @todo translate
-		'author' => 'Author', // @todo translate
-		'latest' => 'Installed', // @todo translate
-		'update' => 'Update available', // @todo translate
+		'community' => '可用的社区扩展',
+		'name' => '名称',
+		'version' => '版本',
+		'description' => '描述',
+		'author' => '作者',
+		'latest' => '已安装',
+		'update' => '更新可用',
 	),
 	'stats' => array(
 		'_' => '统计',
@@ -175,12 +175,15 @@ return array(
 	'user' => array(
 		'articles_and_size' => '%s 篇文章 (%s)',
 		'create' => '创建新用户',
+		'delete_users' => 'Delete user', // TODO
 		'language' => '语言',
 		'number' => '已有 %d 个帐户',
 		'numbers' => '已有 %d 个帐户',
 		'password_form' => '密码<br /><small>(用于 Web-form 登录方式)</small>',
 		'password_format' => '至少 7 个字符',
+		'selected' => 'Selected user', // TODO
 		'title' => '用户管理',
+		'update_users' => 'Update user', // TODO
 		'user_list' => '用户列表',
 		'username' => '用户名',
 		'users' => '用户',

+ 6 - 0
app/i18n/zh-cn/conf.php

@@ -37,6 +37,7 @@ return array(
 			'no_limit' => '无限制',
 			'thin' => '小',
 		),
+		'show_nav_buttons' => 'Show the navigation buttons',	//TODO
 	),
 	'query' => array(
 		'_' => '自定义查询',
@@ -148,6 +149,7 @@ return array(
 		'collapse_article' => '收起文章',
 		'first_article' => '跳转到第一篇文章',
 		'focus_search' => '聚焦到搜索框',
+		'global_view' => '切换到全屏视图',
 		'help' => '显示帮助文档',
 		'javascript' => '若要使用快捷键,必须启用 JavaScript',
 		'last_article' => '跳转到最后一篇文章',
@@ -157,13 +159,17 @@ return array(
 		'navigation' => '浏览',
 		'navigation_help' => '搭配 "Shift" 键,浏览快捷键将生效于 RSS 源。<br/>搭配 "Alt" 键,浏览快捷键将生效于分类。',
 		'next_article' => '跳转到下一篇文章',
+		'normal_view' => '切换到普通视图',
 		'other_action' => '其他操作',
 		'previous_article' => '跳转到上一篇文章',
+		'reading_view' => '切换到阅读视图',
+		'rss_view' => '在新标签中打开 RSS 视图',
 		'see_on_website' => '在原网站上查看',
 		'shift_for_all_read' => '+ <code>shift</code> 可以将全部文章设为已读',
 		'title' => '快捷键',
 		'user_filter' => '显示自定义查询',
 		'user_filter_help' => '如果有多个自定义过滤器,则会按照它们的编号依次访问。',
+		'views' => '视图',
 	),
 	'user' => array(
 		'articles_and_size' => '%s 篇文章 (%s)',

+ 4 - 0
app/i18n/zh-cn/feedback.php

@@ -101,6 +101,10 @@ return array(
 			'_' => '用户 %s 已删除',
 			'error' => '用户 %s 删除失败',
 		),
+		'updated' => array(
+			'_' => 'User %s has been updated', // TODO
+			'error' => 'User %s has not been updated', // TODO
+		),
 	),
 	'profile' => array(
 		'error' => '你的帐户修改失败',

+ 15 - 14
app/i18n/zh-cn/gen.php

@@ -19,6 +19,7 @@ return array(
 		'see_website' => '查看网站',
 		'submit' => '提交',
 		'truncate' => '删除所有文章',
+		'update' => 'Update', // TODO
 	),
 	'auth' => array(
 		'email' => 'Email 地址',
@@ -42,18 +43,18 @@ return array(
 		),
 	),
 	'date' => array(
-		'Apr' => '\\A\\p\\r\\i\\l',
-		'Aug' => '\\A\\u\\g\\u\\s\\t',
-		'Dec' => '\\D\\e\\c\\e\\m\\b\\e\\r',
-		'Feb' => '\\F\\e\\b\\r\\u\\a\\r\\y',
-		'Jan' => '\\J\\a\\n\\u\\a\\r\\y',
-		'Jul' => '\\J\\u\\l\\y',
-		'Jun' => '\\J\\u\\n\\e',
-		'Mar' => '\\M\\a\\r\\c\\h',
-		'May' => '\\M\\a\\y',
-		'Nov' => '\\N\\o\\v\\e\\m\\b\\e\\r',
-		'Oct' => '\\O\\c\\t\\o\\b\\e\\r',
-		'Sep' => '\\S\\e\\p\\t\\e\\m\\b\\e\\r',
+		'Apr' => '\\四\\月',
+		'Aug' => '\\八\\月',
+		'Dec' => '\\十\\二\\月',
+		'Feb' => '\\二\\月',
+		'Jan' => '\\一\\月',
+		'Jul' => '\\七\\月',
+		'Jun' => '\\六\\月',
+		'Mar' => '\\三\\月',
+		'May' => '\\五\\月',
+		'Nov' => '\\十\\一\\月',
+		'Oct' => '\\十\\月',
+		'Sep' => '\\九\\月',
 		'apr' => '四月',
 		'april' => '四月',
 		'aug' => '八月',
@@ -159,7 +160,7 @@ return array(
 		'previous' => '上一页',
 	),
 	'share' => array(
-		'Known' => 'Known based sites',
+		'Known' => '基于 Known 的站点',
 		'blogotext' => 'Blogotext',
 		'diaspora' => 'Diaspora*',
 		'email' => 'Email',
@@ -169,7 +170,7 @@ return array(
 		'jdh' => 'Journal du hacker',
 		'mastodon' => 'Mastodon',
 		'movim' => 'Movim',
-		'print' => 'Print',
+		'print' => '打印',
 		'shaarli' => 'Shaarli',
 		'twitter' => 'Twitter',
 		'wallabag' => 'wallabag v1',

+ 4 - 4
app/i18n/zh-cn/index.php

@@ -41,18 +41,18 @@ return array(
 		'mark_cat_read' => '此分类设为已读',
 		'mark_feed_read' => '此源设为已读',
 		'newer_first' => '由新到旧',
-		'non-starred' => '显示收藏',
+		'non-starred' => '显示收藏',
 		'normal_view' => '普通视图',
 		'older_first' => '由旧到新',
 		'queries' => '自定义查询',
-		'read' => '显示已读',
+		'read' => '显示已读',
 		'reader_view' => '阅读视图',
 		'rss_view' => 'RSS 源',
 		'search_short' => '搜索',
-		'starred' => '显示收藏',
+		'starred' => '显示收藏',
 		'stats' => '统计',
 		'subscription' => '订阅管理',
-		'unread' => '显示未读',
+		'unread' => '显示未读',
 	),
 	'share' => '分享',
 	'tag' => array(

+ 7 - 1
app/i18n/zh-cn/sub.php

@@ -32,12 +32,18 @@ return array(
 		'description' => '描述',
 		'empty' => '此源为空。请确认它是否正常更新。',
 		'error' => '此源遇到一些问题。请在确认是否能正常访问后重试。',
-		'in_main_stream' => '在首页中显示',
 		'informations' => '信息',
 		'keep_history' => '至少保存的文章数',
 		'moved_category_deleted' => '删除分类时,其中的 RSS 源会自动归类到 <em>%s</em>',
+		'mute' => '暂停',
 		'no_selected' => '未选择 RSS 源。',
 		'number_entries' => '%d 篇文章',
+		'priority' => array(
+			'_' => '可见性',
+			'archived' => '不显示 (存档)',
+			'main_stream' => '在首页中显示',
+			'normal' => '在分类中显示',
+		),
 		'stats' => '统计',
 		'think_to_add' => '你可以添加一些 RSS 源。',
 		'title' => '标题',

+ 1 - 1
app/layout/aside_feed.phtml

@@ -53,7 +53,7 @@
 					foreach ($feeds as $feed) {
 						$f_active = FreshRSS_Context::isCurrentGet('f_' . $feed->id());
 				?>
-				<li id="f_<?php echo $feed->id(); ?>" class="item feed<?php echo $f_active ? ' active' : ''; ?><?php echo $feed->inError() ? ' error' : ''; ?><?php echo $feed->nbEntries() <= 0 ? ' empty' : ''; ?>" data-unread="<?php echo $feed->nbNotRead(); ?>" data-priority="<?php echo $feed->priority(); ?>">
+				<li id="f_<?php echo $feed->id(); ?>" class="item feed<?php echo $f_active ? ' active' : '', $feed->mute() ? ' mute' : ''; ?><?php echo $feed->inError() ? ' error' : ''; ?><?php echo $feed->nbEntries() <= 0 ? ' empty' : ''; ?>" data-unread="<?php echo $feed->nbNotRead(); ?>" data-priority="<?php echo $feed->priority(); ?>">
 					<div class="dropdown no-mobile">
 						<div class="dropdown-target"></div>
 						<a class="dropdown-toggle" data-fweb="<?php echo $feed->website(); ?>"><?php echo _i('configure'); ?></a>

+ 14 - 15
app/layout/nav_menu.phtml

@@ -131,20 +131,19 @@
 
 	<?php $url_output = Minz_Request::currentRequest(); ?>
 	<div class="stick" id="nav_menu_views">
-		<?php $url_output['a'] = 'normal'; ?>
-		<a class="view_normal btn <?php echo $actual_view == 'normal'? 'active' : ''; ?>" title="<?php echo _t('index.menu.normal_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
-			<?php echo _i("view-normal"); ?>
-		</a>
-
-		<?php $url_output['a'] = 'global'; ?>
-		<a class="view_global btn <?php echo $actual_view == 'global'? 'active' : ''; ?>" title="<?php echo _t('index.menu.global_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
-			<?php echo _i("view-global"); ?>
-		</a>
-
-		<?php $url_output['a'] = 'reader'; ?>
-		<a class="view_reader btn <?php echo $actual_view == 'reader'? 'active' : ''; ?>" title="<?php echo _t('index.menu.reader_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
-			<?php echo _i("view-reader"); ?>
-		</a>
+		<?php
+		$readingModes = FreshRSS_ReadingMode::getReadingModes();
+		$readingModes = Minz_ExtensionManager::callHook('nav_reading_modes', $readingModes);
+
+		/** @var FreshRSS_ReadingMode $mode */
+		foreach ($readingModes as $mode) {
+			?>
+			<a class="<?php echo $mode->getId(); ?> btn <?php if ($mode->isActive()) { echo 'active'; } ?>" title="<?php echo $mode->getTitle(); ?>" href="<?php echo Minz_Url::display($mode->getUrlParams()); ?>">
+				<?php echo $mode->getName(); ?>
+			</a>
+			<?php
+		}
+		?>
 
 		<?php
 			$url_output['a'] = 'rss';
@@ -156,7 +155,7 @@
 				$url_output['params']['hours'] = FreshRSS_Context::$user_conf->since_hours_posts_per_rss;
 			}
 		?>
-		<a class="view_rss btn" target="_blank" rel="noreferrer" title="<?php echo _t('index.menu.rss_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
+		<a class="view-rss btn" target="_blank" rel="noreferrer" title="<?php echo _t('index.menu.rss_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
 			<?php echo _i('rss'); ?>
 		</a>
 	</div>

+ 2 - 2
app/views/configure/archiving.phtml

@@ -19,7 +19,7 @@
 			<label class="group-name" for="keep_history_default"><?php echo _t('conf.archiving.keep_history_by_feed'); ?></label>
 			<div class="group-controls">
 				<select class="number" name="keep_history_default" id="keep_history_default" required="required" data-leave-validation="<?php echo FreshRSS_Context::$user_conf->keep_history_default; ?>"><?php
-					foreach (array('' => '', 0 => '0', 10 => '10', 50 => '50', 100 => '100', 500 => '500', 1000 => '1 000', 5000 => '5 000', 10000 => '10 000', -1 => '∞') as $v => $t) {
+					foreach (array('' => '', 0 => '0', 10 => '10', 50 => '50', 100 => '100', 500 => '500', 1000 => '1 000', 5000 => '5 000', 10000 => '10 000', FreshRSS_Feed::KEEP_HISTORY_INFINITE => '∞') as $v => $t) {
 						echo '<option value="' . $v . (FreshRSS_Context::$user_conf->keep_history_default == $v ? '" selected="selected' : '') . '">' . $t . ' </option>';
 					}
 				?></select> (<?php echo _t('gen.short.by_default'); ?>)
@@ -34,7 +34,7 @@
 					                3600 => '1h', 5400 => '1.5h', 7200 => '2h', 10800 => '3h', 14400 => '4h', 18800 => '5h', 21600 => '6h', 25200 => '7h', 28800 => '8h',
 					                36000 => '10h', 43200 => '12h', 64800 => '18h',
 					                86400 => '1d', 129600 => '1.5d', 172800 => '2d', 259200 => '3d', 345600 => '4d', 432000 => '5d', 518400 => '6d',
-					                604800 => '1wk', -1 => '∞') as $v => $t) {
+					                604800 => '1wk') as $v => $t) {
 						echo '<option value="' . $v . (FreshRSS_Context::$user_conf->ttl_default == $v ? '" selected="selected' : '') . '">' . $t . '</option>';
 						if (FreshRSS_Context::$user_conf->ttl_default == $v) {
 							$found = true;

+ 10 - 1
app/views/configure/display.phtml

@@ -106,7 +106,7 @@
 				</tbody>
 			</table><br />
 		</div>
-		
+
 		<div class="form-group">
 			<label class="group-name" for="html5_notif_timeout"><?php echo _t('conf.display.notif_html5.timeout'); ?></label>
 			<div class="group-controls">
@@ -114,6 +114,15 @@
 			</div>
 		</div>
 
+		<div class="form-group">
+			<div class="group-controls">
+				<label class="checkbox" for="show_nav_buttons">
+					<input type="checkbox" name="show_nav_buttons" id="show_nav_buttons" value="1"<?php echo FreshRSS_Context::$user_conf->show_nav_buttons ? ' checked="checked"' : ''; ?> data-leave-validation="<?php echo FreshRSS_Context::$user_conf->show_nav_buttons; ?>"/>
+					<?php echo _t('conf.display.show_nav_buttons'); ?>
+				</label>
+			</div>
+		</div>
+
 		<div class="form-group form-actions">
 			<div class="group-controls">
 				<button type="submit" class="btn btn-important"><?php echo _t('gen.action.submit'); ?></button>

+ 30 - 0
app/views/configure/shortcut.phtml

@@ -17,6 +17,36 @@
 
 		<noscript><p class="alert alert-error"><?php echo _t('conf.shortcut.javascript'); ?></p></noscript>
 
+		<legend><?php echo _t('conf.shortcut.views'); ?></legend>
+
+		<div class="form-group">
+			<label class="group-name" for="normal_view_shortcut"><?php echo _t('conf.shortcut.normal_view'); ?></label>
+			<div class="group-controls">
+			    <input type="text" id="normal_view_shortcut" name="shortcuts[normal_view]" list="keys" value="<?php echo $s['normal_view']; ?>" data-leave-validation="<?php echo $s['normal_view']; ?>"/>
+			</div>
+		</div>
+
+		<div class="form-group">
+			<label class="group-name" for="global_view_shortcut"><?php echo _t('conf.shortcut.global_view'); ?></label>
+			<div class="group-controls">
+			    <input type="text" id="global_view_shortcut" name="shortcuts[global_view]" list="keys" value="<?php echo $s['global_view']; ?>" data-leave-validation="<?php echo $s['global_view']; ?>"/>
+			</div>
+		</div>
+
+		<div class="form-group">
+			<label class="group-name" for="reading_view_shortcut"><?php echo _t('conf.shortcut.reading_view'); ?></label>
+			<div class="group-controls">
+			    <input type="text" id="reading_view_shortcut" name="shortcuts[reading_view]" list="keys" value="<?php echo $s['reading_view']; ?>" data-leave-validation="<?php echo $s['reading_view']; ?>"/>
+			</div>
+		</div>
+
+		<div class="form-group">
+			<label class="group-name" for="rss_view_shortcut"><?php echo _t('conf.shortcut.rss_view'); ?></label>
+			<div class="group-controls">
+			    <input type="text" id="rss_view_shortcut" name="shortcuts[rss_view]" list="keys" value="<?php echo $s['rss_view']; ?>" data-leave-validation="<?php echo $s['rss_view']; ?>"/>
+			</div>
+		</div>
+
 		<legend><?php echo _t('conf.shortcut.navigation'); ?></legend>
 
 		<p class="alert alert-warn"><?php echo _t('conf.shortcut.navigation_help');?></p>

+ 13 - 8
app/views/helpers/feed/update.phtml

@@ -65,12 +65,13 @@
 			</div>
 		</div>
 		<div class="form-group">
-			<label class="group-name" for="priority"><?php echo _t('sub.feed.in_main_stream'); ?></label>
+			<label class="group-name" for="priority"><?php echo _t('sub.feed.priority'); ?></label>
 			<div class="group-controls">
-				<label class="checkbox" for="priority">
-					<input type="checkbox" name="priority" id="priority" value="10"<?php echo $this->feed->priority() > 0 ? ' checked="checked"' : ''; ?> />
-					<?php echo _t('gen.short.yes'); ?>
-				</label>
+				<select name="priority" id="priority">
+				    <option value='<?php echo FreshRSS_Feed::PRIORITY_MAIN_STREAM;?>' <?php if (FreshRSS_Feed::PRIORITY_MAIN_STREAM === $this->feed->priority()) {echo 'selected="selected"';}?>><?php echo _t('sub.feed.priority.main_stream'); ?></option>
+				    <option value='<?php echo FreshRSS_Feed::PRIORITY_NORMAL;?>' <?php if (FreshRSS_Feed::PRIORITY_NORMAL === $this->feed->priority()) {echo 'selected="selected"';}?>><?php echo _t('sub.feed.priority.normal'); ?></option>
+				    <option value='<?php echo FreshRSS_Feed::PRIORITY_ARCHIVED;?>' <?php if (FreshRSS_Feed::PRIORITY_ARCHIVED === $this->feed->priority()) {echo 'selected="selected"';}?>><?php echo _t('sub.feed.priority.archived'); ?></option>
+				</select>
 			</div>
 		</div>
 
@@ -100,7 +101,7 @@
 			<label class="group-name" for="keep_history"><?php echo _t('sub.feed.keep_history'); ?></label>
 			<div class="group-controls">
 				<select class="number" name="keep_history" id="keep_history" required="required"><?php
-					foreach (array('' => '', -2 => _t('gen.short.by_default'), 0 => '0', 10 => '10', 50 => '50', 100 => '100', 500 => '500', 1000 => '1 000', 5000 => '5 000', 10000 => '10 000', -1 => '∞') as $v => $t) {
+					foreach (array('' => '', FreshRSS_Feed::KEEP_HISTORY_DEFAULT => _t('gen.short.by_default'), 0 => '0', 10 => '10', 50 => '50', 100 => '100', 500 => '500', 1000 => '1 000', 5000 => '5 000', 10000 => '10 000', FreshRSS_Feed::KEEP_HISTORY_INFINITE => '∞') as $v => $t) {
 						echo '<option value="' . $v . ($this->feed->keepHistory() === $v ? '" selected="selected' : '') . '">' . $t . '</option>';
 					}
 				?></select>
@@ -111,11 +112,11 @@
 			<div class="group-controls">
 				<select class="number" name="ttl" id="ttl" required="required"><?php
 					$found = false;
-					foreach (array(-2 => _t('gen.short.by_default'), 900 => '15min', 1200 => '20min', 1500 => '25min', 1800 => '30min', 2700 => '45min',
+					foreach (array(FreshRSS_Feed::TTL_DEFAULT => _t('gen.short.by_default'), 900 => '15min', 1200 => '20min', 1500 => '25min', 1800 => '30min', 2700 => '45min',
 					                3600 => '1h', 5400 => '1.5h', 7200 => '2h', 10800 => '3h', 14400 => '4h', 18800 => '5h', 21600 => '6h', 25200 => '7h', 28800 => '8h',
 					                36000 => '10h', 43200 => '12h', 64800 => '18h',
 					                86400 => '1d', 129600 => '1.5d', 172800 => '2d', 259200 => '3d', 345600 => '4d', 432000 => '5d', 518400 => '6d',
-					                604800 => '1wk', 1209600 => '2wk', 1814400 => '3wk', 2419200 => '4wk', 2629744 => '1mo', -1 => '∞') as $v => $t) {
+					                604800 => '1wk', 1209600 => '2wk', 1814400 => '3wk', 2419200 => '4wk', 2629744 => '1mo') as $v => $t) {
 						echo '<option value="' . $v . ($this->feed->ttl() === $v ? '" selected="selected' : '') . '">' . $t . '</option>';
 						if ($this->feed->ttl() == $v) {
 							$found = true;
@@ -125,6 +126,10 @@
 						echo '<option value="' . intval($this->feed->ttl()) . '" selected="selected">' . intval($this->feed->ttl()) . 's</option>';
 					}
 				?></select>
+				<label for="mute">
+					<input type="checkbox" name="mute" id="mute" value="1"<?php echo $this->feed->mute() ? ' checked="checked"' : ''; ?> />
+					<?php echo _t('sub.feed.mute'); ?>
+				</label>
 			</div>
 		</div>
 		<div class="form-group">

+ 4 - 0
app/views/helpers/javascript_vars.phtml

@@ -35,6 +35,10 @@ echo htmlspecialchars(json_encode(array(
 		'user_filter' => @$s['user_filter'],
 		'help' => @$s['help'],
 		'close_dropdown' => @$s['close_dropdown'],
+		'normal_view' => @$s['normal_view'],
+		'global_view' => @$s['global_view'],
+		'reading_view' => @$s['reading_view'],
+		'rss_view' => @$s['rss_view'],
 	),
 	'url' => array(
 		'index' => _url('index', 'index'),

+ 1 - 1
app/views/index/global.phtml

@@ -37,7 +37,7 @@
 					$empty = $feed->nbEntries() === 0 ? ' empty' : '';
 					$url_base['params']['get'] = 'f_' . $feed->id();
 			?>
-			<li id="f_<?php echo $feed->id(); ?>" class="item feed<?php echo $error, $empty; ?>" data-unread="<?php echo $feed->nbNotRead(); ?>" data-priority="<?php echo $feed->priority(); ?>">
+			<li id="f_<?php echo $feed->id(); ?>" class="item feed<?php echo $error, $empty, $feed->mute() ? ' mute' : ''; ?>" data-unread="<?php echo $feed->nbNotRead(); ?>" data-priority="<?php echo $feed->priority(); ?>">
 				<img class="favicon" src="<?php echo $feed->favicon(); ?>" alt="✇" />
 				<a class="item-title" data-unread="<?php echo format_number($feed->nbNotRead()); ?>" href="<?php echo Minz_Url::display($url_base); ?>"><?php echo $feed->name(); ?></a>
 			</li>

+ 1 - 1
app/views/index/normal.phtml

@@ -83,7 +83,7 @@ if (!empty($this->entries)) {
 	$this->renderHelper('pagination');
 ?></div>
 
-<?php $this->partial('nav_entries'); ?>
+<?php if (FreshRSS_Context::$user_conf->show_nav_buttons) $this->partial('nav_entries'); ?>
 
 <?php } else { ?>
 <div id="stream" class="prompt alert alert-warn normal">

+ 16 - 2
app/views/index/reader.phtml

@@ -18,11 +18,25 @@ if (!empty($this->entries)) {
 				<?php
 					$feed = FreshRSS_CategoryDAO::findFeed($this->categories, $item->feed());	//We most likely already have the feed object in cache
 					if (empty($feed)) $feed = $item->feed(true);
+					$favoriteUrl = array('c' => 'entry', 'a' => 'bookmark', 'params' => array('id' => $item->id()));
+					if ($item->isFavorite()) {
+						$favoriteUrl['params']['is_favorite'] = 0;
+					}
+					$readUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('id' => $item->id()));
+					if ($item->isRead()) {
+						$readUrl['params']['is_read'] = 0;
+					}
 				?>
-				<a target="_blank" rel="noreferrer" class="go_website" href="<?php echo $item->link(); ?>">
+				<a class="read" href="<?php echo Minz_Url::display($readUrl); ?>">
+					<?php echo _i($item->isRead() ? 'read' : 'unread'); ?>
+				</a>
+				<a class="bookmark" href="<?php echo Minz_Url::display($favoriteUrl); ?>">
+					<?php echo _i($item->isFavorite() ? 'starred' : 'non-starred'); ?>
+				</a>
+				<a href="<?php echo _url('index', 'reader', 'get', 'f_' . $feed->id()); ?>">
 					<img class="favicon" src="<?php echo $feed->favicon(); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span>
 				</a>
-				<h1 class="title"><?php echo $item->title(); ?></h1>
+				<h1 class="title"><a target="_blank" rel="noreferrer" class="go_website" href="<?php echo $item->link(); ?>"><?php echo $item->title(); ?></a></h1>
 
 				<div class="author"><?php
 					$author = $item->author();

Некоторые файлы не были показаны из-за большого количества измененных файлов