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

Merge branch '730-improve_configuration' into dev

BREAKING FEATURE: please follow instructions from
https://github.com/FreshRSS/FreshRSS/issues/730 to
update your configuration file.

Fix https://github.com/FreshRSS/FreshRSS/issues/730
Marien Fressinaud 11 лет назад
Родитель
Сommit
46e98bad91
65 измененных файлов с 1429 добавлено и 1162 удалено
  1. 36 36
      app/Controllers/authController.php
  2. 7 5
      app/Controllers/categoryController.php
  3. 48 51
      app/Controllers/configureController.php
  4. 2 2
      app/Controllers/entryController.php
  5. 13 11
      app/Controllers/feedController.php
  6. 4 4
      app/Controllers/importExportController.php
  7. 12 9
      app/Controllers/indexController.php
  8. 4 3
      app/Controllers/javascriptController.php
  9. 15 13
      app/Controllers/userController.php
  10. 32 14
      app/FreshRSS.php
  11. 36 14
      app/Models/Auth.php
  12. 0 345
      app/Models/Configuration.php
  13. 368 0
      app/Models/ConfigurationSetter.php
  14. 8 12
      app/Models/Context.php
  15. 1 1
      app/Models/EntryDAO.php
  16. 8 8
      app/Models/Factory.php
  17. 2 1
      app/Models/Feed.php
  18. 214 18
      app/Models/Share.php
  19. 2 2
      app/Models/UserDAO.php
  20. 36 31
      app/actualize_script.php
  21. 11 0
      app/i18n/en/gen.php
  22. 1 12
      app/i18n/en/index.php
  23. 1 1
      app/i18n/fr/conf.php
  24. 11 0
      app/i18n/fr/gen.php
  25. 1 12
      app/i18n/fr/index.php
  26. 4 4
      app/layout/aside_feed.phtml
  27. 6 5
      app/layout/header.phtml
  28. 2 2
      app/layout/layout.phtml
  29. 6 6
      app/layout/nav_menu.phtml
  30. 17 17
      app/views/auth/index.phtml
  31. 5 5
      app/views/configure/archiving.phtml
  32. 15 15
      app/views/configure/display.phtml
  33. 2 2
      app/views/configure/queries.phtml
  34. 22 22
      app/views/configure/reading.phtml
  35. 16 11
      app/views/configure/sharing.phtml
  36. 1 1
      app/views/configure/shortcut.phtml
  37. 1 1
      app/views/helpers/export/opml.phtml
  38. 8 8
      app/views/helpers/javascript_vars.phtml
  39. 1 1
      app/views/helpers/pagination.phtml
  40. 2 2
      app/views/index/global.phtml
  41. 1 1
      app/views/index/index.phtml
  42. 27 29
      app/views/index/normal.phtml
  43. 2 2
      app/views/index/reader.phtml
  44. 3 3
      app/views/user/manage.phtml
  45. 2 2
      app/views/user/profile.phtml
  46. 30 0
      data/config.default.php
  47. 0 0
      data/users/_/.gitkeep
  48. 66 0
      data/users/_/config.default.php
  49. 0 9
      lib/Minz/BadConfigurationException.php
  50. 167 371
      lib/Minz/Configuration.php
  51. 8 0
      lib/Minz/ConfigurationException.php
  52. 4 0
      lib/Minz/ConfigurationNamespaceException.php
  53. 4 0
      lib/Minz/ConfigurationParamException.php
  54. 4 3
      lib/Minz/Error.php
  55. 24 2
      lib/Minz/FrontController.php
  56. 8 3
      lib/Minz/Log.php
  57. 2 1
      lib/Minz/ModelPdo.php
  58. 2 1
      lib/Minz/Request.php
  59. 1 1
      lib/Minz/Session.php
  60. 23 7
      lib/Minz/Translate.php
  61. 3 1
      lib/Minz/View.php
  62. 46 1
      lib/lib_rss.php
  63. 19 16
      p/api/greader.php
  64. 1 1
      p/i/index.php
  65. 1 1
      p/scripts/main.js

+ 36 - 36
app/Controllers/authController.php

@@ -27,10 +27,10 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
 		if (Minz_Request::isPost()) {
 			$ok = true;
 
-			$current_token = FreshRSS_Context::$conf->token;
+			$current_token = FreshRSS_Context::$user_conf->token;
 			$token = Minz_Request::param('token', $current_token);
-			FreshRSS_Context::$conf->_token($token);
-			$ok &= FreshRSS_Context::$conf->save();
+			FreshRSS_Context::$user_conf->token = $token;
+			$ok &= FreshRSS_Context::$user_conf->save();
 
 			$anon = Minz_Request::param('anon_access', false);
 			$anon = ((bool)$anon) && ($anon !== 'no');
@@ -39,18 +39,20 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
 			$auth_type = Minz_Request::param('auth_type', 'none');
 			$unsafe_autologin = Minz_Request::param('unsafe_autologin', false);
 			$api_enabled = Minz_Request::param('api_enabled', false);
-			if ($anon != Minz_Configuration::allowAnonymous() ||
-				$auth_type != Minz_Configuration::authType() ||
-				$anon_refresh != Minz_Configuration::allowAnonymousRefresh() ||
-				$unsafe_autologin != Minz_Configuration::unsafeAutologinEnabled() ||
-				$api_enabled != Minz_Configuration::apiEnabled()) {
-
-				Minz_Configuration::_authType($auth_type);
-				Minz_Configuration::_allowAnonymous($anon);
-				Minz_Configuration::_allowAnonymousRefresh($anon_refresh);
-				Minz_Configuration::_enableAutologin($unsafe_autologin);
-				Minz_Configuration::_enableApi($api_enabled);
-				$ok &= Minz_Configuration::writeFile();
+			if ($anon != FreshRSS_Context::$system_conf->allow_anonymous ||
+				$auth_type != FreshRSS_Context::$system_conf->auth_type ||
+				$anon_refresh != FreshRSS_Context::$system_conf->allow_anonymous_refresh ||
+				$unsafe_autologin != FreshRSS_Context::$system_conf->unsafe_autologin_enabled ||
+				$api_enabled != FreshRSS_Context::$system_conf->api_enabled) {
+
+				// TODO: test values from form
+				FreshRSS_Context::$system_conf->auth_type = $auth_type;
+				FreshRSS_Context::$system_conf->allow_anonymous = $anon;
+				FreshRSS_Context::$system_conf->allow_anonymous_refresh = $anon_refresh;
+				FreshRSS_Context::$system_conf->unsafe_autologin_enabled = $unsafe_autologin;
+				FreshRSS_Context::$system_conf->api_enabled = $api_enabled;
+
+				$ok &= FreshRSS_Context::$system_conf->save();
 			}
 
 			invalidateHttpCache();
@@ -76,7 +78,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
 			Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true);
 		}
 
-		$auth_type = Minz_Configuration::authType();
+		$auth_type = FreshRSS_Context::$system_conf->auth_type;
 		switch ($auth_type) {
 		case 'form':
 			Minz_Request::forward(array('c' => 'auth', 'a' => 'formLogin'));
@@ -118,11 +120,9 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
 			$nonce = Minz_Session::param('nonce');
 			$username = Minz_Request::param('username', '');
 			$challenge = Minz_Request::param('challenge', '');
-			try {
-				$conf = new FreshRSS_Configuration($username);
-			} catch(Minz_Exception $e) {
-				// $username is not a valid user, nor the configuration file!
-				Minz_Log::warning('Login failure: ' . $e->getMessage());
+
+			$conf = get_user_configuration($username);
+			if (is_null($conf)) {
 				Minz_Request::bad(_t('feedback.auth.login.invalid'),
 				                  array('c' => 'auth', 'a' => 'login'));
 			}
@@ -154,7 +154,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
 				Minz_Request::bad(_t('feedback.auth.login.invalid'),
 				                  array('c' => 'auth', 'a' => 'login'));
 			}
-		} elseif (Minz_Configuration::unsafeAutologinEnabled()) {
+		} elseif (FreshRSS_Context::$system_conf->unsafe_autologin_enabled) {
 			$username = Minz_Request::param('u', '');
 			$password = Minz_Request::param('p', '');
 			Minz_Request::_param('p');
@@ -163,11 +163,8 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
 				return;
 			}
 
-			try {
-				$conf = new FreshRSS_Configuration($username);
-			} catch(Minz_Exception $e) {
-				// $username is not a valid user, nor the configuration file!
-				Minz_Log::warning('Login failure: ' . $e->getMessage());
+			$conf = get_user_configuration($username);
+			if (is_null($conf)) {
 				return;
 			}
 
@@ -235,13 +232,12 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
 					$persona_file = DATA_PATH . '/persona/' . $email . '.txt';
 					if (($current_user = @file_get_contents($persona_file)) !== false) {
 						$current_user = trim($current_user);
-						try {
-							$conf = new FreshRSS_Configuration($current_user);
+						$conf = get_user_configuration($current_user);
+						if (!is_null($conf)) {
 							$login_ok = strcasecmp($email, $conf->mail_login) === 0;
-						} catch (Minz_Exception $e) {
-							//Permission denied or conf file does not exist
+						} else {
 							$reason = 'Invalid configuration for user ' .
-							          '[' . $current_user . '] ' . $e->getMessage();
+							          '[' . $current_user . ']';
 						}
 					}
 				} else {
@@ -293,7 +289,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
 
 		$this->view->no_form = false;
 		// Enable changement of auth only if Persona!
-		if (Minz_Configuration::authType() != 'persona') {
+		if (FreshRSS_Context::$system_conf->auth_type != 'persona') {
 			$this->view->message = array(
 				'status' => 'bad',
 				'title' => _t('gen.short.damn'),
@@ -303,7 +299,11 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
 			return;
 		}
 
-		$conf = new FreshRSS_Configuration(Minz_Configuration::defaultUser());
+		$conf = get_user_configuration(FreshRSS_Context::$system_conf->default_user);
+		if (is_null($conf)) {
+			return;
+		}
+
 		// Admin user must have set its master password.
 		if (!$conf->passwordHash) {
 			$this->view->message = array(
@@ -327,8 +327,8 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
 			);
 
 			if ($ok) {
-				Minz_Configuration::_authType('form');
-				$ok = Minz_Configuration::writeFile();
+				FreshRSS_Context::$system_conf->auth_type = 'form';
+				$ok = FreshRSS_Context::$system_conf->save();
 
 				if ($ok) {
 					Minz_Request::good(_t('feedback.auth.form.set'));

+ 7 - 5
app/Controllers/categoryController.php

@@ -30,7 +30,7 @@ class FreshRSS_category_Controller extends Minz_ActionController {
 		$catDAO = new FreshRSS_CategoryDAO();
 		$url_redirect = array('c' => 'subscription', 'a' => 'index');
 
-		$limits = Minz_Configuration::limits();
+		$limits = FreshRSS_Context::$system_conf->limits;
 		$this->view->categories = $catDAO->listCategories(false);
 
 		if (count($this->view->categories) >= $limits['max_categories']) {
@@ -141,8 +141,9 @@ class FreshRSS_category_Controller extends Minz_ActionController {
 			}
 
 			// Remove related queries.
-			FreshRSS_Context::$conf->remove_query_by_get('c_' . $id);
-			FreshRSS_Context::$conf->save();
+			FreshRSS_Context::$user_conf->queries = remove_query_by_get(
+				'c_' . $id, FreshRSS_Context::$user_conf->queries);
+			FreshRSS_Context::$user_conf->save();
 
 			Minz_Request::good(_t('feedback.sub.category.deleted'), $url_redirect);
 		}
@@ -177,9 +178,10 @@ class FreshRSS_category_Controller extends Minz_ActionController {
 
 				// Remove related queries
 				foreach ($feeds as $feed) {
-					FreshRSS_Context::$conf->remove_query_by_get('f_' . $feed->id());
+					FreshRSS_Context::$user_conf->queries = remove_query_by_get(
+						'f_' . $feed->id(), FreshRSS_Context::$user_conf->queries);
 				}
-				FreshRSS_Context::$conf->save();
+				FreshRSS_Context::$user_conf->save();
 
 				Minz_Request::good(_t('feedback.sub.category.emptied'), $url_redirect);
 			} else {

+ 48 - 51
app/Controllers/configureController.php

@@ -41,24 +41,24 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 	 */
 	public function displayAction() {
 		if (Minz_Request::isPost()) {
-			FreshRSS_Context::$conf->_language(Minz_Request::param('language', 'en'));
-			FreshRSS_Context::$conf->_theme(Minz_Request::param('theme', FreshRSS_Themes::$defaultTheme));
-			FreshRSS_Context::$conf->_content_width(Minz_Request::param('content_width', 'thin'));
-			FreshRSS_Context::$conf->_topline_read(Minz_Request::param('topline_read', false));
-			FreshRSS_Context::$conf->_topline_favorite(Minz_Request::param('topline_favorite', false));
-			FreshRSS_Context::$conf->_topline_date(Minz_Request::param('topline_date', false));
-			FreshRSS_Context::$conf->_topline_link(Minz_Request::param('topline_link', false));
-			FreshRSS_Context::$conf->_bottomline_read(Minz_Request::param('bottomline_read', false));
-			FreshRSS_Context::$conf->_bottomline_favorite(Minz_Request::param('bottomline_favorite', false));
-			FreshRSS_Context::$conf->_bottomline_sharing(Minz_Request::param('bottomline_sharing', false));
-			FreshRSS_Context::$conf->_bottomline_tags(Minz_Request::param('bottomline_tags', false));
-			FreshRSS_Context::$conf->_bottomline_date(Minz_Request::param('bottomline_date', false));
-			FreshRSS_Context::$conf->_bottomline_link(Minz_Request::param('bottomline_link', false));
-			FreshRSS_Context::$conf->_html5_notif_timeout(Minz_Request::param('html5_notif_timeout', 0));
-			FreshRSS_Context::$conf->save();
-
-			Minz_Session::_param('language', FreshRSS_Context::$conf->language);
-			Minz_Translate::reset();
+			FreshRSS_Context::$user_conf->language = Minz_Request::param('language', 'en');
+			FreshRSS_Context::$user_conf->theme = Minz_Request::param('theme', FreshRSS_Themes::$defaultTheme);
+			FreshRSS_Context::$user_conf->content_width = Minz_Request::param('content_width', 'thin');
+			FreshRSS_Context::$user_conf->topline_read = Minz_Request::param('topline_read', false);
+			FreshRSS_Context::$user_conf->topline_favorite = Minz_Request::param('topline_favorite', false);
+			FreshRSS_Context::$user_conf->topline_date = Minz_Request::param('topline_date', false);
+			FreshRSS_Context::$user_conf->topline_link = Minz_Request::param('topline_link', false);
+			FreshRSS_Context::$user_conf->bottomline_read = Minz_Request::param('bottomline_read', false);
+			FreshRSS_Context::$user_conf->bottomline_favorite = Minz_Request::param('bottomline_favorite', false);
+			FreshRSS_Context::$user_conf->bottomline_sharing = Minz_Request::param('bottomline_sharing', false);
+			FreshRSS_Context::$user_conf->bottomline_tags = Minz_Request::param('bottomline_tags', false);
+			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->save();
+
+			Minz_Session::_param('language', FreshRSS_Context::$user_conf->language);
+			Minz_Translate::reset(FreshRSS_Context::$user_conf->language);
 			invalidateHttpCache();
 
 			Minz_Request::good(_t('feedback.conf.updated'),
@@ -100,29 +100,26 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 	 */
 	public function readingAction() {
 		if (Minz_Request::isPost()) {
-			FreshRSS_Context::$conf->_posts_per_page(Minz_Request::param('posts_per_page', 10));
-			FreshRSS_Context::$conf->_view_mode(Minz_Request::param('view_mode', 'normal'));
-			FreshRSS_Context::$conf->_default_view(Minz_Request::param('default_view', 'adaptive'));
-			FreshRSS_Context::$conf->_auto_load_more(Minz_Request::param('auto_load_more', false));
-			FreshRSS_Context::$conf->_display_posts(Minz_Request::param('display_posts', false));
-			FreshRSS_Context::$conf->_display_categories(Minz_Request::param('display_categories', false));
-			FreshRSS_Context::$conf->_hide_read_feeds(Minz_Request::param('hide_read_feeds', false));
-			FreshRSS_Context::$conf->_onread_jump_next(Minz_Request::param('onread_jump_next', false));
-			FreshRSS_Context::$conf->_lazyload(Minz_Request::param('lazyload', false));
-			FreshRSS_Context::$conf->_sticky_post(Minz_Request::param('sticky_post', false));
-			FreshRSS_Context::$conf->_reading_confirm(Minz_Request::param('reading_confirm', false));
-			FreshRSS_Context::$conf->_auto_remove_article(Minz_Request::param('auto_remove_article', false));
-			FreshRSS_Context::$conf->_sort_order(Minz_Request::param('sort_order', 'DESC'));
-			FreshRSS_Context::$conf->_mark_when(array(
+			FreshRSS_Context::$user_conf->posts_per_page = Minz_Request::param('posts_per_page', 10);
+			FreshRSS_Context::$user_conf->view_mode = Minz_Request::param('view_mode', 'normal');
+			FreshRSS_Context::$user_conf->default_view = Minz_Request::param('default_view', 'adaptive');
+			FreshRSS_Context::$user_conf->auto_load_more = Minz_Request::param('auto_load_more', false);
+			FreshRSS_Context::$user_conf->display_posts = Minz_Request::param('display_posts', false);
+			FreshRSS_Context::$user_conf->display_categories = Minz_Request::param('display_categories', false);
+			FreshRSS_Context::$user_conf->hide_read_feeds = Minz_Request::param('hide_read_feeds', false);
+			FreshRSS_Context::$user_conf->onread_jump_next = Minz_Request::param('onread_jump_next', false);
+			FreshRSS_Context::$user_conf->lazyload = Minz_Request::param('lazyload', false);
+			FreshRSS_Context::$user_conf->sticky_post = Minz_Request::param('sticky_post', false);
+			FreshRSS_Context::$user_conf->reading_confirm = Minz_Request::param('reading_confirm', false);
+			FreshRSS_Context::$user_conf->auto_remove_article = Minz_Request::param('auto_remove_article', false);
+			FreshRSS_Context::$user_conf->sort_order = Minz_Request::param('sort_order', 'DESC');
+			FreshRSS_Context::$user_conf->mark_when = array(
 				'article' => Minz_Request::param('mark_open_article', false),
 				'site' => Minz_Request::param('mark_open_site', false),
 				'scroll' => Minz_Request::param('mark_scroll', false),
 				'reception' => Minz_Request::param('mark_upon_reception', false),
-			));
-			FreshRSS_Context::$conf->save();
-
-			Minz_Session::_param('language', FreshRSS_Context::$conf->language);
-			Minz_Translate::reset();
+			);
+			FreshRSS_Context::$user_conf->save();
 			invalidateHttpCache();
 
 			Minz_Request::good(_t('feedback.conf.updated'),
@@ -142,8 +139,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 	public function sharingAction() {
 		if (Minz_Request::isPost()) {
 			$params = Minz_Request::params();
-			FreshRSS_Context::$conf->_sharing($params['share']);
-			FreshRSS_Context::$conf->save();
+			FreshRSS_Context::$user_conf->sharing = $params['share'];
+			FreshRSS_Context::$user_conf->save();
 			invalidateHttpCache();
 
 			Minz_Request::good(_t('feedback.conf.updated'),
@@ -184,8 +181,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 				}
 			}
 
-			FreshRSS_Context::$conf->_shortcuts($shortcuts_ok);
-			FreshRSS_Context::$conf->save();
+			FreshRSS_Context::$user_conf->shortcuts = $shortcuts_ok;
+			FreshRSS_Context::$user_conf->save();
 			invalidateHttpCache();
 
 			Minz_Request::good(_t('feedback.conf.shortcuts_updated'),
@@ -212,10 +209,10 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 	 */
 	public function archivingAction() {
 		if (Minz_Request::isPost()) {
-			FreshRSS_Context::$conf->_old_entries(Minz_Request::param('old_entries', 3));
-			FreshRSS_Context::$conf->_keep_history_default(Minz_Request::param('keep_history_default', 0));
-			FreshRSS_Context::$conf->_ttl_default(Minz_Request::param('ttl_default', -2));
-			FreshRSS_Context::$conf->save();
+			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->save();
 			invalidateHttpCache();
 
 			Minz_Request::good(_t('feedback.conf.updated'),
@@ -252,8 +249,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 					$query['name'] = _t('conf.query.number', $key + 1);
 				}
 			}
-			FreshRSS_Context::$conf->_queries($queries);
-			FreshRSS_Context::$conf->save();
+			FreshRSS_Context::$user_conf->queries = $queries;
+			FreshRSS_Context::$user_conf->save();
 
 			Minz_Request::good(_t('feedback.conf.updated'),
 			                   array('c' => 'configure', 'a' => 'queries'));
@@ -261,7 +258,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 			$this->view->query_get = array();
 			$cat_dao = new FreshRSS_CategoryDAO();
 			$feed_dao = FreshRSS_Factory::createFeedDao();
-			foreach (FreshRSS_Context::$conf->queries as $key => $query) {
+			foreach (FreshRSS_Context::$user_conf->queries as $key => $query) {
 				if (!isset($query['get'])) {
 					continue;
 				}
@@ -329,7 +326,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 	 */
 	public function addQueryAction() {
 		$whitelist = array('get', 'order', 'name', 'search', 'state');
-		$queries = FreshRSS_Context::$conf->queries;
+		$queries = FreshRSS_Context::$user_conf->queries;
 		$query = Minz_Request::params();
 		$query['name'] = _t('conf.query.number', count($queries) + 1);
 		foreach ($query as $key => $value) {
@@ -338,8 +335,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 			}
 		}
 		$queries[] = $query;
-		FreshRSS_Context::$conf->_queries($queries);
-		FreshRSS_Context::$conf->save();
+		FreshRSS_Context::$user_conf->queries = $queries;
+		FreshRSS_Context::$user_conf->save();
 
 		Minz_Request::good(_t('feedback.conf.query_created', $query['name']),
 		                   array('c' => 'configure', 'a' => 'queries'));

+ 2 - 2
app/Controllers/entryController.php

@@ -154,7 +154,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
 	public function purgeAction() {
 		@set_time_limit(300);
 
-		$nb_month_old = max(FreshRSS_Context::$conf->old_entries, 1);
+		$nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1);
 		$date_min = time() - (3600 * 24 * 30 * $nb_month_old);
 
 		$feedDAO = FreshRSS_Factory::createFeedDao();
@@ -168,7 +168,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
 			if ($feed_history == -2) {
 				// TODO: -2 must be a constant!
 				// -2 means we take the default value from configuration
-				$feed_history = FreshRSS_Context::$conf->keep_history_default;
+				$feed_history = FreshRSS_Context::$user_conf->keep_history_default;
 			}
 
 			if ($feed_history >= 0) {

+ 13 - 11
app/Controllers/feedController.php

@@ -14,12 +14,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 			// Token is useful in the case that anonymous refresh is forbidden
 			// and CRON task cannot be used with php command so the user can
 			// set a CRON task to refresh his feeds by using token inside url
-			$token = FreshRSS_Context::$conf->token;
+			$token = FreshRSS_Context::$user_conf->token;
 			$token_param = Minz_Request::param('token', '');
 			$token_is_ok = ($token != '' && $token == $token_param);
 			$action = Minz_Request::actionName();
+			$allow_anonymous_refresh = FreshRSS_Context::$system_conf->allow_anonymous_refresh;
 			if ($action !== 'actualize' ||
-					!(Minz_Configuration::allowAnonymousRefresh() || $token_is_ok)) {
+					!($allow_anonymous_refresh || $token_is_ok)) {
 				Minz_Error::error(403);
 			}
 		}
@@ -65,7 +66,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 			'params' => array(),
 		);
 
-		$limits = Minz_Configuration::limits();
+		$limits = FreshRSS_Context::$system_conf->limits;
 		$this->view->feeds = $feedDAO->listFeeds();
 		if (count($this->view->feeds) >= $limits['max_feeds']) {
 			Minz_Request::bad(_t('feedback.sub.feed.over_max', $limits['max_feeds']),
@@ -161,14 +162,14 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 			$feed->_id($id);
 			$feed->faviconPrepare();
 
-			$is_read = FreshRSS_Context::$conf->mark_when['reception'] ? 1 : 0;
+			$is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0;
 
 			$entryDAO = FreshRSS_Factory::createEntryDao();
 			// We want chronological order and SimplePie uses reverse order.
 			$entries = array_reverse($feed->entries());
 
 			// Calculate date of oldest entries we accept in DB.
-			$nb_month_old = FreshRSS_Context::$conf->old_entries;
+			$nb_month_old = FreshRSS_Context::$user_conf->old_entries;
 			$date_min = time() - (3600 * 24 * 30 * $nb_month_old);
 
 			// Use a shared statement and a transaction to improve a LOT the
@@ -272,15 +273,15 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 				$feeds[] = $feed;
 			}
 		} else {
-			$feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$conf->ttl_default);
+			$feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$user_conf->ttl_default);
 		}
 
 		// Calculate date of oldest entries we accept in DB.
-		$nb_month_old = max(FreshRSS_Context::$conf->old_entries, 1);
+		$nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1);
 		$date_min = time() - (3600 * 24 * 30 * $nb_month_old);
 
 		$updated_feeds = 0;
-		$is_read = FreshRSS_Context::$conf->mark_when['reception'] ? 1 : 0;
+		$is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0;
 		foreach ($feeds as $feed) {
 			if (!$feed->lock()) {
 				Minz_Log::notice('Feed already being actualized: ' . $feed->url());
@@ -302,7 +303,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 			if ($feed_history == -2) {
 				// TODO: -2 must be a constant!
 				// -2 means we take the default value from configuration
-				$feed_history = FreshRSS_Context::$conf->keep_history_default;
+				$feed_history = FreshRSS_Context::$user_conf->keep_history_default;
 			}
 
 			// We want chronological order and SimplePie uses reverse order.
@@ -476,8 +477,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 			// TODO: Delete old favicon
 
 			// Remove related queries
-			FreshRSS_Context::$conf->remove_query_by_get('f_' . $id);
-			FreshRSS_Context::$conf->save();
+			FreshRSS_Context::$user_conf->queries = remove_query_by_get(
+				'f_' . $id, FreshRSS_Context::$user_conf->queries);
+			FreshRSS_Context::$user_conf->save();
 
 			Minz_Request::good(_t('feedback.sub.feed.deleted'), $redirect_url);
 		} else {

+ 4 - 4
app/Controllers/importExportController.php

@@ -174,7 +174,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
 
 		$nb_feeds = count($this->feedDAO->listFeeds());
 		$nb_cats = count($this->catDAO->listCategories(false));
-		$limits = Minz_Configuration::limits();
+		$limits = FreshRSS_Context::$system_conf->limits;
 
 		foreach ($opml_elements as $elt) {
 			$is_error = false;
@@ -315,7 +315,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
 			return true;
 		}
 
-		$is_read = FreshRSS_Context::$conf->mark_when['reception'] ? 1 : 0;
+		$is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0;
 
 		$google_compliant = strpos($article_object['id'], 'com.google') !== false;
 
@@ -323,7 +323,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
 		$article_to_feed = array();
 
 		$nb_feeds = count($this->feedDAO->listFeeds());
-		$limits = Minz_Configuration::limits();
+		$limits = FreshRSS_Context::$system_conf->limits;
 
 		// First, we check feeds of articles are in DB (and add them if needed).
 		foreach ($article_object['items'] as $item) {
@@ -532,7 +532,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
 			$this->view->type = 'feed/' . $feed->id();
 			$this->view->entries = $this->entryDAO->listWhere(
 				'f', $feed->id(), FreshRSS_Entry::STATE_ALL, 'ASC',
-				FreshRSS_Context::$conf->posts_per_page
+				FreshRSS_Context::$user_conf->posts_per_page
 			);
 			$this->view->feed = $feed;
 		}

+ 12 - 9
app/Controllers/indexController.php

@@ -9,7 +9,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 	 * This action only redirect on the default view mode (normal or global)
 	 */
 	public function indexAction() {
-		$prefered_output = FreshRSS_Context::$conf->view_mode;
+		$prefered_output = FreshRSS_Context::$user_conf->view_mode;
 		Minz_Request::forward(array(
 			'c' => 'index',
 			'a' => $prefered_output
@@ -20,7 +20,8 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 	 * This action displays the normal view of FreshRSS.
 	 */
 	public function normalAction() {
-		if (!FreshRSS_Auth::hasAccess() && !Minz_Configuration::allowAnonymous()) {
+		$allow_anonymous = FreshRSS_Context::$system_conf->allow_anonymous;
+		if (!FreshRSS_Auth::hasAccess() && !$allow_anonymous) {
 			Minz_Request::forward(array('c' => 'auth', 'a' => 'login'));
 			return;
 		}
@@ -82,7 +83,8 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 	 * This action displays the global view of FreshRSS.
 	 */
 	public function globalAction() {
-		if (!FreshRSS_Auth::hasAccess() && !Minz_Configuration::allowAnonymous()) {
+		$allow_anonymous = FreshRSS_Context::$system_conf->allow_anonymous;
+		if (!FreshRSS_Auth::hasAccess() && !$allow_anonymous) {
 			Minz_Request::forward(array('c' => 'auth', 'a' => 'login'));
 			return;
 		}
@@ -109,13 +111,14 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 	 * This action displays the RSS feed of FreshRSS.
 	 */
 	public function rssAction() {
-		$token = FreshRSS_Context::$conf->token;
+		$allow_anonymous = FreshRSS_Context::$system_conf->allow_anonymous;
+		$token = FreshRSS_Context::$user_conf->token;
 		$token_param = Minz_Request::param('token', '');
 		$token_is_ok = ($token != '' && $token === $token_param);
 
 		// Check if user has access.
 		if (!FreshRSS_Auth::hasAccess() &&
-				!Minz_Configuration::allowAnonymous() &&
+				!$allow_anonymous &&
 				!$token_is_ok) {
 			Minz_Error::error(403);
 		}
@@ -160,10 +163,10 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 		FreshRSS_Context::_get(Minz_Request::param('get', 'a'));
 
 		FreshRSS_Context::$state = Minz_Request::param(
-			'state', FreshRSS_Context::$conf->default_state
+			'state', FreshRSS_Context::$user_conf->default_state
 		);
 		$state_forced_by_user = Minz_Request::param('state', false) !== false;
-		if (FreshRSS_Context::$conf->default_view === 'adaptive' &&
+		if (FreshRSS_Context::$user_conf->default_view === 'adaptive' &&
 				FreshRSS_Context::$get_unread <= 0 &&
 				!FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_READ) &&
 				!$state_forced_by_user) {
@@ -172,10 +175,10 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 
 		FreshRSS_Context::$search = Minz_Request::param('search', '');
 		FreshRSS_Context::$order = Minz_Request::param(
-			'order', FreshRSS_Context::$conf->sort_order
+			'order', FreshRSS_Context::$user_conf->sort_order
 		);
 		FreshRSS_Context::$number = Minz_Request::param(
-			'nb', FreshRSS_Context::$conf->posts_per_page
+			'nb', FreshRSS_Context::$user_conf->posts_per_page
 		);
 		FreshRSS_Context::$first_id = Minz_Request::param('next', '');
 	}

+ 4 - 3
app/Controllers/javascriptController.php

@@ -8,7 +8,7 @@ class FreshRSS_javascript_Controller extends Minz_ActionController {
 	public function actualizeAction() {
 		header('Content-Type: text/javascript; charset=UTF-8');
 		$feedDAO = FreshRSS_Factory::createFeedDao();
-		$this->view->feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$conf->ttl_default);
+		$this->view->feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$user_conf->ttl_default);
 	}
 
 	public function nbUnreadsPerFeedAction() {
@@ -28,11 +28,12 @@ class FreshRSS_javascript_Controller extends Minz_ActionController {
 		$user = isset($_GET['user']) ? $_GET['user'] : '';
 		if (ctype_alnum($user)) {
 			try {
-				$conf = new FreshRSS_Configuration($user);
+				$salt = FreshRSS_Context::$system_conf->salt;
+				$conf = get_user_configuration($user);
 				$s = $conf->passwordHash;
 				if (strlen($s) >= 60) {
 					$this->view->salt1 = substr($s, 0, 29);	//CRYPT_BLOWFISH Salt: "$2a$", a two digit cost parameter, "$", and 22 characters from the alphabet "./0-9A-Za-z".
-					$this->view->nonce = sha1(Minz_Configuration::salt() . uniqid(mt_rand(), true));
+					$this->view->nonce = sha1($salt . uniqid(mt_rand(), true));
 					Minz_Session::_param('nonce', $this->view->nonce);
 					return;	//Success
 				}

+ 15 - 13
app/Controllers/userController.php

@@ -39,9 +39,9 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 				$passwordPlain = '';
 				$passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash);	//Compatibility with bcrypt.js
 				$ok &= ($passwordHash != '');
-				FreshRSS_Context::$conf->_passwordHash($passwordHash);
+				FreshRSS_Context::$user_conf->passwordHash = $passwordHash;
 			}
-			Minz_Session::_param('passwordHash', FreshRSS_Context::$conf->passwordHash);
+			Minz_Session::_param('passwordHash', FreshRSS_Context::$user_conf->passwordHash);
 
 			$passwordPlain = Minz_Request::param('apiPasswordPlain', '', true);
 			if ($passwordPlain != '') {
@@ -52,17 +52,17 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 				$passwordPlain = '';
 				$passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash);	//Compatibility with bcrypt.js
 				$ok &= ($passwordHash != '');
-				FreshRSS_Context::$conf->_apiPasswordHash($passwordHash);
+				FreshRSS_Context::$user_conf->apiPasswordHash = $passwordHash;
 			}
 
 			// TODO: why do we need of hasAccess here?
 			if (FreshRSS_Auth::hasAccess('admin')) {
-				FreshRSS_Context::$conf->_mail_login(Minz_Request::param('mail_login', '', true));
+				FreshRSS_Context::$user_conf->mail_login = Minz_Request::param('mail_login', '', true);
 			}
-			$email = FreshRSS_Context::$conf->mail_login;
+			$email = FreshRSS_Context::$user_conf->mail_login;
 			Minz_Session::_param('mail', $email);
 
-			$ok &= FreshRSS_Context::$conf->save();
+			$ok &= FreshRSS_Context::$user_conf->save();
 
 			if ($email != '') {
 				$personaFile = DATA_PATH . '/persona/' . $email . '.txt';
@@ -105,20 +105,21 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 
 	public function createAction() {
 		if (Minz_Request::isPost() && FreshRSS_Auth::hasAccess('admin')) {
-			$db = Minz_Configuration::dataBase();
+			$db = FreshRSS_Context::$system_conf->db;
 			require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
 
-			$new_user_language = Minz_Request::param('new_user_language', FreshRSS_Context::$conf->language);
-			$languages = FreshRSS_Context::$conf->availableLanguages();
+			$new_user_language = Minz_Request::param('new_user_language', FreshRSS_Context::$user_conf->language);
+			$languages = Minz_Translate::availableLanguages();
 			if (!isset($languages[$new_user_language])) {
-				$new_user_language = FreshRSS_Context::$conf->language;
+				$new_user_language = FreshRSS_Context::$user_conf->language;
 			}
 
 			$new_user_name = Minz_Request::param('new_user_name');
 			$ok = ($new_user_name != '') && ctype_alnum($new_user_name);
 
 			if ($ok) {
-				$ok &= (strcasecmp($new_user_name, Minz_Configuration::defaultUser()) !== 0);	//It is forbidden to alter the default user
+				$default_user = FreshRSS_Context::$system_conf->default_user;
+				$ok &= (strcasecmp($new_user_name, $default_user) !== 0);	//It is forbidden to alter the default user
 
 				$ok &= !in_array(strtoupper($new_user_name), array_map('strtoupper', listUsers()));	//Not an existing user, case-insensitive
 
@@ -179,7 +180,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 
 	public function deleteAction() {
 		if (Minz_Request::isPost() && FreshRSS_Auth::hasAccess('admin')) {
-			$db = Minz_Configuration::dataBase();
+			$db = FreshRSS_Context::$system_conf->db;
 			require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
 
 			$username = Minz_Request::param('username');
@@ -187,7 +188,8 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 			$user_data = join_path(DATA_PATH, 'users', $username);
 
 			if ($ok) {
-				$ok &= (strcasecmp($username, Minz_Configuration::defaultUser()) !== 0);	//It is forbidden to delete the default user
+				$default_user = FreshRSS_Context::$system_conf->default_user;
+				$ok &= (strcasecmp($username, $default_user) !== 0);	//It is forbidden to delete the default user
 			}
 			if ($ok) {
 				$ok &= is_dir($user_data);

+ 32 - 14
app/FreshRSS.php

@@ -6,10 +6,30 @@ class FreshRSS extends Minz_FrontController {
 			Minz_Session::init('FreshRSS');
 		}
 
-		// Need to be called just after session init because it initializes
-		// current user.
-		FreshRSS_Auth::init();
+		$this->initConfiguration();
+		$this->initAuth();
+		FreshRSS_Context::init();
+		$this->initI18n();
+		FreshRSS_Share::load(join_path(DATA_PATH, 'shares.php'));
+		$this->loadStylesAndScripts();
+		$this->loadNotifications();
+		$this->loadExtensions();
+	}
+
+	private function initConfiguration() {
+		$configuration_setter = new FreshRSS_ConfigurationSetter();
+		$current_user = Minz_Session::param('currentUser', '_');
+
+		Minz_Configuration::register('user',
+		                             join_path(USERS_PATH, $current_user, 'config.php'),
+		                             join_path(USERS_PATH, '_', 'config.default.php'),
+		                             $configuration_setter);
+		$system_conf = Minz_Configuration::get('system');
+		$system_conf->_configurationSetter($configuration_setter);
+	}
 
+	private function initAuth() {
+		FreshRSS_Auth::init();
 		if (Minz_Request::isPost() && !is_referer_from_same_domain()) {
 			// Basic protection against XSRF attacks
 			FreshRSS_Auth::removeAccess();
@@ -22,21 +42,19 @@ class FreshRSS extends Minz_FrontController {
 				))
 			);
 		}
+	}
 
-		// Load context and configuration.
-		FreshRSS_Context::init();
-
-		// Init i18n.
-		Minz_Session::_param('language', FreshRSS_Context::$conf->language);
-		Minz_Translate::init();
+	private function initI18n() {
+		Minz_Session::_param('language', FreshRSS_Context::$user_conf->language);
 
-		$this->loadStylesAndScripts();
-		$this->loadNotifications();
-		$this->loadExtensions();
+		Minz_Translate::init(array(
+			'en' => 'English',
+			'fr' => 'Français',
+		), FreshRSS_Context::$user_conf->language);
 	}
 
 	private function loadStylesAndScripts() {
-		$theme = FreshRSS_Themes::load(FreshRSS_Context::$conf->theme);
+		$theme = FreshRSS_Themes::load(FreshRSS_Context::$user_conf->theme);
 		if ($theme) {
 			foreach($theme['files'] as $file) {
 				if ($file[0] === '_') {
@@ -57,7 +75,7 @@ class FreshRSS extends Minz_FrontController {
 		Minz_View::appendScript(Minz_Url::display('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js')));
 		Minz_View::appendScript(Minz_Url::display('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js')));
 
-		if (Minz_Configuration::authType() === 'persona') {
+		if (FreshRSS_Context::$system_conf->auth_type === 'persona') {
 			// TODO move it in a plugin
 			// Needed for login AND logout with Persona.
 			Minz_View::appendScript('https://login.persona.org/include.js');

+ 36 - 14
app/Models/Auth.php

@@ -16,7 +16,8 @@ class FreshRSS_Auth {
 		self::$login_ok = Minz_Session::param('loginOk', false);
 		$current_user = Minz_Session::param('currentUser', '');
 		if ($current_user === '') {
-			$current_user = Minz_Configuration::defaultUser();
+			$conf = Minz_Configuration::get('system');
+			$current_user = $conf->default_user;
 			Minz_Session::_param('currentUser', $current_user);
 		}
 
@@ -40,7 +41,9 @@ class FreshRSS_Auth {
 	 * @return boolean true if user can be connected, false else.
 	 */
 	private static function accessControl() {
-		switch (Minz_Configuration::authType()) {
+		$conf = Minz_Configuration::get('system');
+		$auth_type = $conf->auth_type;
+		switch ($auth_type) {
 		case 'form':
 			$credentials = FreshRSS_FormAuth::getCredentialsFromCookie();
 			$current_user = '';
@@ -80,21 +83,18 @@ class FreshRSS_Auth {
 	 */
 	public static function giveAccess() {
 		$current_user = Minz_Session::param('currentUser');
-		try {
-			$conf = new FreshRSS_Configuration($current_user);
-		} catch(Minz_Exception $e) {
-			die($e->getMessage());
-		}
+		$user_conf = get_user_configuration($current_user);
+		$system_conf = Minz_Configuration::get('system');
 
-		switch (Minz_Configuration::authType()) {
+		switch ($system_conf->auth_type) {
 		case 'form':
-			self::$login_ok = Minz_Session::param('passwordHash') === $conf->passwordHash;
+			self::$login_ok = Minz_Session::param('passwordHash') === $user_conf->passwordHash;
 			break;
 		case 'http_auth':
 			self::$login_ok = strcasecmp($current_user, httpAuthUser()) === 0;
 			break;
 		case 'persona':
-			self::$login_ok = strcasecmp(Minz_Session::param('mail'), $conf->mail_login) === 0;
+			self::$login_ok = strcasecmp(Minz_Session::param('mail'), $user_conf->mail_login) === 0;
 			break;
 		case 'none':
 			self::$login_ok = true;
@@ -114,12 +114,14 @@ class FreshRSS_Auth {
 	 * @return boolean true if user has corresponding access, false else.
 	 */
 	public static function hasAccess($scope = 'general') {
+		$conf = Minz_Configuration::get('system');
+		$default_user = $conf->default_user;
 		$ok = self::$login_ok;
 		switch ($scope) {
 		case 'general':
 			break;
 		case 'admin':
-			$ok &= Minz_Session::param('currentUser') === Minz_Configuration::defaultUser();
+			$ok &= Minz_Session::param('currentUser') === $default_user;
 			break;
 		default:
 			$ok = false;
@@ -133,9 +135,10 @@ class FreshRSS_Auth {
 	public static function removeAccess() {
 		Minz_Session::_param('loginOk');
 		self::$login_ok = false;
-		Minz_Session::_param('currentUser', Minz_Configuration::defaultUser());
+		$conf = Minz_Configuration::get('system');
+		Minz_Session::_param('currentUser', $conf->default_user);
 
-		switch (Minz_Configuration::authType()) {
+		switch ($conf->auth_type) {
 		case 'form':
 			Minz_Session::_param('passwordHash');
 			FreshRSS_FormAuth::deleteCookie();
@@ -151,6 +154,24 @@ class FreshRSS_Auth {
 			// TODO: extensions
 		}
 	}
+
+	/**
+	 * Return if authentication is enabled on this instance of FRSS.
+	 */
+	public static function accessNeedsLogin() {
+		$conf = Minz_Configuration::get('system');
+		$auth_type = $conf->auth_type;
+		return $auth_type !== 'none';
+	}
+
+	/**
+	 * Return if authentication requires a PHP action.
+	 */
+	public static function accessNeedsAction() {
+		$conf = Minz_Configuration::get('system');
+		$auth_type = $conf->auth_type;
+		return $auth_type === 'form' || $auth_type === 'persona';
+	}
 }
 
 
@@ -194,7 +215,8 @@ class FreshRSS_FormAuth {
 
 	public static function makeCookie($username, $password_hash) {
 		do {
-			$token = sha1(Minz_Configuration::salt() . $username . uniqid(mt_rand(), true));
+			$conf = Minz_Configuration::get('system');
+			$token = sha1($conf->salt . $username . uniqid(mt_rand(), true));
 			$token_file = DATA_PATH . '/tokens/' . $token . '.txt';
 		} while (file_exists($token_file));
 

+ 0 - 345
app/Models/Configuration.php

@@ -1,345 +0,0 @@
-<?php
-
-class FreshRSS_Configuration {
-	private $filename;
-
-	private $data = array(
-		'language' => 'en',
-		'old_entries' => 3,
-		'keep_history_default' => 0,
-		'ttl_default' => 3600,
-		'mail_login' => '',
-		'token' => '',
-		'passwordHash' => '',	//CRYPT_BLOWFISH
-		'apiPasswordHash' => '',	//CRYPT_BLOWFISH
-		'posts_per_page' => 20,
-		'view_mode' => 'normal',
-		'default_view' => 'adaptive',
-		'default_state' => FreshRSS_Entry::STATE_NOT_READ,
-		'auto_load_more' => true,
-		'display_posts' => false,
-		'display_categories' => false,
-		'hide_read_feeds' => true,
-		'onread_jump_next' => true,
-		'lazyload' => true,
-		'sticky_post' => true,
-		'reading_confirm' => false,
-		'auto_remove_article' => false,
-		'sort_order' => 'DESC',
-		'anon_access' => false,
-		'mark_when' => array(
-			'article' => true,
-			'site' => true,
-			'scroll' => false,
-			'reception' => false,
-		),
-		'theme' => 'Origine',
-		'content_width' => 'thin',
-		'shortcuts' => array(
-			'mark_read' => 'r',
-			'mark_favorite' => 'f',
-			'go_website' => 'space',
-			'next_entry' => 'j',
-			'prev_entry' => 'k',
-			'first_entry' => 'home',
-			'last_entry' => 'end',
-			'collapse_entry' => 'c',
-			'load_more' => 'm',
-			'auto_share' => 's',
-			'focus_search' => 'a',
-			'user_filter' => 'u',
-			'help' => 'f1',
-			'close_dropdown' => 'escape',
-		),
-		'topline_read' => true,
-		'topline_favorite' => true,
-		'topline_date' => true,
-		'topline_link' => true,
-		'bottomline_read' => true,
-		'bottomline_favorite' => true,
-		'bottomline_sharing' => true,
-		'bottomline_tags' => true,
-		'bottomline_date' => true,
-		'bottomline_link' => true,
-		'sharing' => array(),
-		'queries' => array(),
-		'html5_notif_timeout' => 0,
-	);
-
-	private $available_languages = array(
-		'en' => 'English',
-		'fr' => 'Français',
-	);
-
-	private $shares;
-
-	public function __construct($user) {
-		$this->filename = join_path(DATA_PATH, 'users', $user, 'config.php');
-
-		$data = @include($this->filename);
-		if (!is_array($data)) {
-			throw new Minz_PermissionDeniedException($this->filename);
-		}
-
-		foreach ($data as $key => $value) {
-			if (isset($this->data[$key])) {
-				$function = '_' . $key;
-				$this->$function($value);
-			}
-		}
-		$this->data['user'] = $user;
-
-		$this->shares = join_path(DATA_PATH, 'shares.php');
-
-		$shares = @include($this->shares);
-		if (!is_array($shares)) {
-			throw new Minz_PermissionDeniedException($this->shares);
-		}
-
-		$this->data['shares'] = $shares;
-	}
-
-	public function save() {
-		@rename($this->filename, $this->filename . '.bak.php');
-		unset($this->data['shares']); // Remove shares because it is not intended to be stored in user configuration
-		if (file_put_contents($this->filename, "<?php\n return " . var_export($this->data, true) . ';', LOCK_EX) === false) {
-			throw new Minz_PermissionDeniedException($this->filename);
-		}
-		if (function_exists('opcache_invalidate')) {
-			opcache_invalidate($this->filename);	//Clear PHP 5.5+ cache for include
-		}
-		invalidateHttpCache();
-		return true;
-	}
-
-	public function __get($name) {
-		if (array_key_exists($name, $this->data)) {
-			return $this->data[$name];
-		} else {
-			$trace = debug_backtrace();
-			trigger_error('Undefined FreshRSS_Configuration->' . $name . 'in ' . $trace[0]['file'] . ' line ' . $trace[0]['line'], E_USER_NOTICE);	//TODO: Use Minz exceptions
-			return null;
-		}
-	}
-
-	public function availableLanguages() {
-		return $this->available_languages;
-	}
-
-	public function remove_query_by_get($get) {
-		$final_queries = array();
-		foreach ($this->queries as $key => $query) {
-			if (empty($query['get']) || $query['get'] !== $get) {
-				$final_queries[$key] = $query;
-			}
-		}
-		$this->_queries($final_queries);
-	}
-
-	public function _language($value) {
-		if (!isset($this->available_languages[$value])) {
-			$value = 'en';
-		}
-		$this->data['language'] = $value;
-	}
-	public function _posts_per_page($value) {
-		$value = intval($value);
-		$this->data['posts_per_page'] = $value > 0 ? $value : 10;
-	}
-	public function _view_mode($value) {
-		if ($value === 'global' || $value === 'reader') {
-			$this->data['view_mode'] = $value;
-		} else {
-			$this->data['view_mode'] = 'normal';
-		}
-	}
-	public function _default_view($value) {
-		switch ($value) {
-		case 'all':
-			$this->data['default_view'] = $value;
-			$this->data['default_state'] = (FreshRSS_Entry::STATE_READ +
-			                                FreshRSS_Entry::STATE_NOT_READ);
-			break;
-		case 'adaptive':
-		case 'unread':
-		default:
-			$this->data['default_view'] = $value;
-			$this->data['default_state'] = FreshRSS_Entry::STATE_NOT_READ;
-		}
-	}
-	public function _default_state($value) {
-		$this->data['default_state'] = (int)$value;
-	}
-
-	public function _display_posts($value) {
-		$this->data['display_posts'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _display_categories($value) {
-		$this->data['display_categories'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _hide_read_feeds($value) {
-		$this->data['hide_read_feeds'] = (bool)$value;
-	}
-	public function _onread_jump_next($value) {
-		$this->data['onread_jump_next'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _lazyload($value) {
-		$this->data['lazyload'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _sticky_post($value) {
-		$this->data['sticky_post'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _reading_confirm($value) {
-		$this->data['reading_confirm'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _auto_remove_article($value) {
-		$this->data['auto_remove_article'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _sort_order($value) {
-		$this->data['sort_order'] = $value === 'ASC' ? 'ASC' : 'DESC';
-	}
-	public function _old_entries($value) {
-		$value = intval($value);
-		$this->data['old_entries'] = $value > 0 ? $value : 3;
-	}
-	public function _keep_history_default($value) {
-		$value = intval($value);
-		$this->data['keep_history_default'] = $value >= -1 ? $value : 0;
-	}
-	public function _ttl_default($value) {
-		$value = intval($value);
-		$this->data['ttl_default'] = $value >= -1 ? $value : 3600;
-	}
-	public function _shortcuts($values) {
-		foreach ($values as $key => $value) {
-			if (isset($this->data['shortcuts'][$key])) {
-				$this->data['shortcuts'][$key] = $value;
-			}
-		}
-	}
-	public function _passwordHash($value) {
-		$this->data['passwordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : '';
-	}
-	public function _apiPasswordHash($value) {
-		$this->data['apiPasswordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : '';
-	}
-	public function _mail_login($value) {
-		$value = filter_var($value, FILTER_VALIDATE_EMAIL);
-		if ($value) {
-			$this->data['mail_login'] = $value;
-		} else {
-			$this->data['mail_login'] = '';
-		}
-	}
-	public function _anon_access($value) {
-		$this->data['anon_access'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _mark_when($values) {
-		foreach ($values as $key => $value) {
-			if (isset($this->data['mark_when'][$key])) {
-				$this->data['mark_when'][$key] = ((bool)$value) && $value !== 'no';
-			}
-		}
-	}
-	public function _sharing($values) {
-		$this->data['sharing'] = array();
-		$unique = array();
-		foreach ($values as $value) {
-			if (!is_array($value)) {
-				continue;
-			}
-
-			// Verify URL and add default value when needed
-			if (isset($value['url'])) {
-				$is_url = (
-					filter_var($value['url'], FILTER_VALIDATE_URL) ||
-					(version_compare(PHP_VERSION, '5.3.3', '<') &&
-						(strpos($value, '-') > 0) &&
-						($value === filter_var($value, FILTER_SANITIZE_URL)))
-				);  //PHP bug #51192
-				if (!$is_url) {
-					continue;
-				}
-			} else {
-				$value['url'] = null;
-			}
-
-			// Add a default name
-			if (empty($value['name'])) {
-				$value['name'] = $value['type'];
-			}
-
-			$json_value = json_encode($value);
-			if (!in_array($json_value, $unique)) {
-				$unique[] = $json_value;
-				$this->data['sharing'][] = $value;
-			}
-		}
-	}
-	public function _queries($values) {
-		$this->data['queries'] = array();
-		foreach ($values as $value) {
-			$value = array_filter($value);
-			$params = $value;
-			unset($params['name']);
-			unset($params['url']);
-			$value['url'] = Minz_Url::display(array('params' => $params));
-
-			$this->data['queries'][] = $value;
-		}
-	}
-	public function _theme($value) {
-		$this->data['theme'] = $value;
-	}
-	public function _content_width($value) {
-		if ($value === 'medium' ||
-				$value === 'large' ||
-				$value === 'no_limit') {
-			$this->data['content_width'] = $value;
-		} else {
-			$this->data['content_width'] = 'thin';
-		}
-	}
-	
-	public function _html5_notif_timeout($value) {
-		$value = intval($value);
-		$this->data['html5_notif_timeout'] = $value >= 0 ? $value : 0;
-	}
-	
-	public function _token($value) {
-		$this->data['token'] = $value;
-	}
-	public function _auto_load_more($value) {
-		$this->data['auto_load_more'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _topline_read($value) {
-		$this->data['topline_read'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _topline_favorite($value) {
-		$this->data['topline_favorite'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _topline_date($value) {
-		$this->data['topline_date'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _topline_link($value) {
-		$this->data['topline_link'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _bottomline_read($value) {
-		$this->data['bottomline_read'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _bottomline_favorite($value) {
-		$this->data['bottomline_favorite'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _bottomline_sharing($value) {
-		$this->data['bottomline_sharing'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _bottomline_tags($value) {
-		$this->data['bottomline_tags'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _bottomline_date($value) {
-		$this->data['bottomline_date'] = ((bool)$value) && $value !== 'no';
-	}
-	public function _bottomline_link($value) {
-		$this->data['bottomline_link'] = ((bool)$value) && $value !== 'no';
-	}
-}

+ 368 - 0
app/Models/ConfigurationSetter.php

@@ -0,0 +1,368 @@
+<?php
+
+class FreshRSS_ConfigurationSetter {
+	/**
+	 * Return if the given key is supported by this setter.
+	 * @param $key the key to test.
+	 * @return true if the key is supported, false else.
+	 */
+	public function support($key) {
+		$name_setter = '_' . $key;
+		return is_callable(array($this, $name_setter));
+	}
+
+	/**
+	 * Set the given key in data with the current value.
+	 * @param $data an array containing the list of all configuration data.
+	 * @param $key the key to update.
+	 * @param $value the value to set.
+	 */
+	public function handle(&$data, $key, $value) {
+		$name_setter = '_' . $key;
+		call_user_func_array(array($this, $name_setter), array(&$data, $value));
+	}
+
+	/**
+	 * A helper to set boolean values.
+	 *
+	 * @param $value the tested value.
+	 * @return true if value is true and different from no, false else.
+	 */
+	private function handleBool($value) {
+		return ((bool)$value) && $value !== 'no';
+	}
+
+	/**
+	 * The (long) list of setters for user configuration.
+	 */
+	private function _apiPasswordHash(&$data, $value) {
+		$data['apiPasswordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : '';
+	}
+
+	private function _content_width(&$data, $value) {
+		$value = strtolower($value);
+		if (!in_array($value, array('thin', 'medium', 'large', 'no_limit'))) {
+			$value = 'thin';
+		}
+
+		$data['content_width'] = $value;
+	}
+
+	private function _default_state(&$data, $value) {
+		$data['default_state'] = (int)$value;
+	}
+
+	private function _default_view(&$data, $value) {
+		switch ($value) {
+		case 'all':
+			$data['default_view'] = $value;
+			$data['default_state'] = (FreshRSS_Entry::STATE_READ +
+			                          FreshRSS_Entry::STATE_NOT_READ);
+			break;
+		case 'adaptive':
+		case 'unread':
+		default:
+			$data['default_view'] = $value;
+			$data['default_state'] = FreshRSS_Entry::STATE_NOT_READ;
+		}
+	}
+
+	private function _html5_notif_timeout(&$data, $value) {
+		$value = intval($value);
+		$data['html5_notif_timeout'] = $value >= 0 ? $value : 0;
+	}
+
+	private function _keep_history_default(&$data, $value) {
+		$value = intval($value);
+		$data['keep_history_default'] = $value >= -1 ? $value : 0;
+	}
+
+	// It works for system config too!
+	private function _language(&$data, $value) {
+		$value = strtolower($value);
+		$languages = Minz_Translate::availableLanguages();
+		if (!isset($languages[$value])) {
+			$value = 'en';
+		}
+		$data['language'] = $value;
+	}
+
+	private function _mail_login(&$data, $value) {
+		$value = filter_var($value, FILTER_VALIDATE_EMAIL);
+		$data['mail_login'] = $value ? $value : '';
+	}
+
+	private function _old_entries(&$data, $value) {
+		$value = intval($value);
+		$data['old_entries'] = $value > 0 ? $value : 3;
+	}
+
+	private function _passwordHash(&$data, $value) {
+		$data['passwordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : '';
+	}
+
+	private function _posts_per_page(&$data, $value) {
+		$value = intval($value);
+		$data['posts_per_page'] = $value > 0 ? $value : 10;
+	}
+
+	private function _queries(&$data, $values) {
+		$data['queries'] = array();
+		foreach ($values as $value) {
+			$value = array_filter($value);
+			$params = $value;
+			unset($params['name']);
+			unset($params['url']);
+			$value['url'] = Minz_Url::display(array('params' => $params));
+			$data['queries'][] = $value;
+		}
+	}
+
+	private function _sharing(&$data, $values) {
+		$data['sharing'] = array();
+		foreach ($values as $value) {
+			if (!is_array($value)) {
+				continue;
+			}
+
+			// Verify URL and add default value when needed
+			if (isset($value['url'])) {
+				$is_url = (
+					filter_var($value['url'], FILTER_VALIDATE_URL) ||
+					(version_compare(PHP_VERSION, '5.3.3', '<') &&
+						(strpos($value, '-') > 0) &&
+						($value === filter_var($value, FILTER_SANITIZE_URL)))
+				); //PHP bug #51192
+				if (!$is_url) {
+					continue;
+				}
+			} else {
+				$value['url'] = null;
+			}
+
+			$data['sharing'][] = $value;
+		}
+	}
+
+	private function _shortcuts(&$data, $values) {
+		foreach ($values as $key => $value) {
+			if (isset($data['shortcuts'][$key])) {
+				$data['shortcuts'][$key] = $value;
+			}
+		}
+	}
+
+	private function _sort_order(&$data, $value) {
+		$data['sort_order'] = $value === 'ASC' ? 'ASC' : 'DESC';
+	}
+
+	private function _ttl_default(&$data, $value) {
+		$value = intval($value);
+		$data['ttl_default'] = $value >= -1 ? $value : 3600;
+	}
+
+	private function _view_mode(&$data, $value) {
+		$value = strtolower($value);
+		if (!in_array($value, array('global', 'normal', 'reader'))) {
+			$value = 'normal';
+		}
+		$data['view_mode'] =  $value;
+	}
+
+	/**
+	 * A list of boolean setters.
+	 */
+	private function _anon_access(&$data, $value) {
+		$data['anon_access'] = $this->handleBool($value);
+	}
+
+	private function _auto_load_more(&$data, $value) {
+		$data['auto_load_more'] = $this->handleBool($value);
+	}
+
+	private function _auto_remove_article(&$data, $value) {
+		$data['auto_remove_article'] = $this->handleBool($value);
+	}
+
+	private function _display_categories(&$data, $value) {
+		$data['display_categories'] = $this->handleBool($value);
+	}
+
+	private function _display_posts(&$data, $value) {
+		$data['display_posts'] = $this->handleBool($value);
+	}
+
+	private function _hide_read_feeds(&$data, $value) {
+		$data['hide_read_feeds'] = $this->handleBool($value);
+	}
+
+	private function _lazyload(&$data, $value) {
+		$data['lazyload'] = $this->handleBool($value);
+	}
+
+	private function _mark_when(&$data, $values) {
+		foreach ($values as $key => $value) {
+			if (isset($data['mark_when'][$key])) {
+				$data['mark_when'][$key] = $this->handleBool($value);
+			}
+		}
+	}
+
+	private function _onread_jump_next(&$data, $value) {
+		$data['onread_jump_next'] = $this->handleBool($value);
+	}
+
+	private function _reading_confirm(&$data, $value) {
+		$data['reading_confirm'] = $this->handleBool($value);
+	}
+
+	private function _sticky_post(&$data, $value) {
+		$data['sticky_post'] = $this->handleBool($value);
+	}
+
+	private function _bottomline_date(&$data, $value) {
+		$data['bottomline_date'] = $this->handleBool($value);
+	}
+	private function _bottomline_favorite(&$data, $value) {
+		$data['bottomline_favorite'] = $this->handleBool($value);
+	}
+	private function _bottomline_link(&$data, $value) {
+		$data['bottomline_link'] = $this->handleBool($value);
+	}
+	private function _bottomline_read(&$data, $value) {
+		$data['bottomline_read'] = $this->handleBool($value);
+	}
+	private function _bottomline_sharing(&$data, $value) {
+		$data['bottomline_sharing'] = $this->handleBool($value);
+	}
+	private function _bottomline_tags(&$data, $value) {
+		$data['bottomline_tags'] = $this->handleBool($value);
+	}
+
+	private function _topline_date(&$data, $value) {
+		$data['topline_date'] = $this->handleBool($value);
+	}
+	private function _topline_favorite(&$data, $value) {
+		$data['topline_favorite'] = $this->handleBool($value);
+	}
+	private function _topline_link(&$data, $value) {
+		$data['topline_link'] = $this->handleBool($value);
+	}
+	private function _topline_read(&$data, $value) {
+		$data['topline_read'] = $this->handleBool($value);
+	}
+
+	/**
+	 * The (not so long) list of setters for system configuration.
+	 */
+	private function _allow_anonymous(&$data, $value) {
+		$data['allow_anonymous'] = $this->handleBool($value) && FreshRSS_Auth::accessNeedsAction();
+	}
+
+	private function _allow_anonymous_refresh(&$data, $value) {
+		$data['allow_anonymous_refresh'] = $this->handleBool($value) && $data['allow_anonymous'];
+	}
+
+	private function _api_enabled(&$data, $value) {
+		$data['api_enabled'] = $this->handleBool($value);
+	}
+
+	private function _auth_type(&$data, $value) {
+		$value = strtolower($value);
+		if (!in_array($value, array('form', 'http_auth', 'persona', 'none'))) {
+			$value = 'none';
+		}
+		$data['auth_type'] = $value;
+		$this->_allow_anonymous($data, $data['allow_anonymous']);
+	}
+
+	private function _db(&$data, $value) {
+		if (!isset($value['type'])) {
+			return;
+		}
+
+		switch ($value['type']) {
+		case 'mysql':
+			if (empty($value['host']) ||
+					empty($value['user']) ||
+					empty($value['base']) ||
+					!isset($value['password'])) {
+				return;
+			}
+
+			$data['db']['type'] = $value['type'];
+			$data['db']['host'] = $value['host'];
+			$data['db']['user'] = $value['user'];
+			$data['db']['base'] = $value['base'];
+			$data['db']['password'] = $value['password'];
+			$data['db']['prefix'] = isset($value['prefix']) ? $value['prefix'] : '';
+			break;
+		case 'sqlite':
+			$data['db']['type'] = $value['type'];
+			$data['db']['host'] = '';
+			$data['db']['user'] = '';
+			$data['db']['base'] = '';
+			$data['db']['password'] = '';
+			$data['db']['prefix'] = '';
+			break;
+		default:
+			return;
+		}
+	}
+
+	private function _default_user(&$data, $value) {
+		$user_list = listUsers();
+		if (in_array($value, $user_list)) {
+			$data['default_user'] = $value;
+		}
+	}
+
+	private function _environment(&$data, $value) {
+		$value = strtolower($value);
+		if (!in_array($value, array('silent', 'development', 'production'))) {
+			$value = 'production';
+		}
+		$data['environment'] =  $value;
+	}
+
+	private function _limits(&$data, $values) {
+		$max_small_int = 16384;
+		$limits_keys = array(
+			'cache_duration' => array(
+				'min' => 0,
+			),
+			'timeout' => array(
+				'min' => 0,
+			),
+			'max_inactivity' => array(
+				'min' => 0,
+			),
+			'max_feeds' => array(
+				'min' => 0,
+				'max' => $max_small_int,
+			),
+			'max_categories' => array(
+				'min' => 0,
+				'max' => $max_small_int,
+			),
+		);
+
+		foreach ($values as $key => $value) {
+			if (!isset($limits_keys[$key])) {
+				continue;
+			}
+
+			$limits = $limits_keys[$key];
+			if (
+				(!isset($limits['min']) || $value > $limits['min']) &&
+				(!isset($limits['max']) || $value < $limits['max'])
+			) {
+				$data['limits'][$key] = $value;
+			}
+		}
+	}
+
+	private function _unsafe_autologin_enabled(&$data, $value) {
+		$data['unsafe_autologin_enabled'] = $this->handleBool($value);
+	}
+}

+ 8 - 12
app/Models/Context.php

@@ -5,7 +5,8 @@
  * useful functions associated to the current view state.
  */
 class FreshRSS_Context {
-	public static $conf = null;
+	public static $user_conf = null;
+	public static $system_conf = null;
 	public static $categories = array();
 
 	public static $name = '';
@@ -37,17 +38,12 @@ class FreshRSS_Context {
 	/**
 	 * Initialize the context.
 	 *
-	 * Set the correct $conf and $categories variables.
+	 * Set the correct configurations and $categories variables.
 	 */
 	public static function init() {
 		// Init configuration.
-		$current_user = Minz_Session::param('currentUser');
-		try {
-			self::$conf = new FreshRSS_Configuration($current_user);
-		} catch(Minz_Exception $e) {
-			Minz_Log::error('Cannot load configuration file of user `' . $current_user . '`');
-			die($e->getMessage());
-		}
+		self::$system_conf = Minz_Configuration::get('system');
+		self::$user_conf = Minz_Configuration::get('user');
 
 		$catDAO = new FreshRSS_CategoryDAO();
 		self::$categories = $catDAO->listCategories();
@@ -198,7 +194,7 @@ class FreshRSS_Context {
 		// By default, $next_get == $get
 		self::$next_get = $get;
 
-		if (self::$conf->onread_jump_next && strlen($get) > 2) {
+		if (self::$user_conf->onread_jump_next && strlen($get) > 2) {
 			$another_unread_id = '';
 			$found_current_get = false;
 			switch ($get[0]) {
@@ -276,7 +272,7 @@ class FreshRSS_Context {
 	 * @return boolean
 	 */
 	public static function isAutoRemoveAvailable() {
-		if (!self::$conf->auto_remove_article) {
+		if (!self::$user_conf->auto_remove_article) {
 			return false;
 		}
 		if (self::isStateEnabled(FreshRSS_Entry::STATE_READ)) {
@@ -297,7 +293,7 @@ class FreshRSS_Context {
 	 * @return boolean
 	 */
 	public static function isStickyPostEnabled() {
-		if (self::$conf->sticky_post) {
+		if (self::$user_conf->sticky_post) {
 			return true;
 		}
 		if (self::isAutoRemoveAvailable()) {

+ 1 - 1
app/Models/EntryDAO.php

@@ -586,7 +586,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo {
 	}
 
 	public function size($all = false) {
-		$db = Minz_Configuration::dataBase();
+		$db = FreshRSS_Context::$system_conf->db;
 		$sql = 'SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema=?';	//MySQL
 		$values = array($db['base']);
 		if (!$all) {

+ 8 - 8
app/Models/Factory.php

@@ -3,8 +3,8 @@
 class FreshRSS_Factory {
 
 	public static function createFeedDao($username = null) {
-		$db = Minz_Configuration::dataBase();
-		if ($db['type'] === 'sqlite') {
+		$conf = Minz_Configuration::get('system');
+		if ($conf->db['type'] === 'sqlite') {
 			return new FreshRSS_FeedDAOSQLite($username);
 		} else {
 			return new FreshRSS_FeedDAO($username);
@@ -12,8 +12,8 @@ class FreshRSS_Factory {
 	}
 
 	public static function createEntryDao($username = null) {
-		$db = Minz_Configuration::dataBase();
-		if ($db['type'] === 'sqlite') {
+		$conf = Minz_Configuration::get('system');
+		if ($conf->db['type'] === 'sqlite') {
 			return new FreshRSS_EntryDAOSQLite($username);
 		} else {
 			return new FreshRSS_EntryDAO($username);
@@ -21,8 +21,8 @@ class FreshRSS_Factory {
 	}
 
 	public static function createStatsDAO($username = null) {
-		$db = Minz_Configuration::dataBase();
-		if ($db['type'] === 'sqlite') {
+		$conf = Minz_Configuration::get('system');
+		if ($conf->db['type'] === 'sqlite') {
 			return new FreshRSS_StatsDAOSQLite($username);
 		} else {
 			return new FreshRSS_StatsDAO($username);
@@ -30,8 +30,8 @@ class FreshRSS_Factory {
 	}
 
 	public static function createDatabaseDAO($username = null) {
-		$db = Minz_Configuration::dataBase();
-		if ($db['type'] === 'sqlite') {
+		$conf = Minz_Configuration::get('system');
+		if ($conf->db['type'] === 'sqlite') {
 			return new FreshRSS_DatabaseDAOSQLite($username);
 		} else {
 			return new FreshRSS_DatabaseDAO($username);

+ 2 - 1
app/Models/Feed.php

@@ -40,7 +40,8 @@ class FreshRSS_Feed extends Minz_Model {
 
 	public function hash() {
 		if ($this->hash === null) {
-			$this->hash = hash('crc32b', Minz_Configuration::salt() . $this->url);
+			$salt = FreshRSS_Context::$system_conf->salt;
+			$this->hash = hash('crc32b', $salt . $this->url);
 		}
 		return $this->hash;
 	}

+ 214 - 18
app/Models/Share.php

@@ -1,44 +1,240 @@
 <?php
 
+/**
+ * Manage the sharing options in FreshRSS.
+ */
 class FreshRSS_Share {
+	/**
+	 * The list of available sharing options.
+	 */
+	private static $list_sharing = array();
 
-	static public function generateUrl($options, $selected, $link, $title) {
-		$share = $options[$selected['type']];
+	/**
+	 * Register a new sharing option.
+	 * @param $share_options is an array defining the share option.
+	 */
+	public static function register($share_options) {
+		$type = $share_options['type'];
+
+		if (isset(self::$list_sharing[$type])) {
+			return;
+		}
+
+		$help_url = isset($share_options['help']) ? $share_options['help'] : '';
+		self::$list_sharing[$type] = new FreshRSS_Share(
+			$type, $share_options['url'], $share_options['transform'],
+			$share_options['form'], $help_url
+		);
+	}
+
+	/**
+	 * Register sharing options in a file.
+	 * @param $filename the name of the file to load.
+	 */
+	public static function load($filename) {
+		$shares_from_file = @include($filename);
+		if (!is_array($shares_from_file)) {
+			$shares_from_file = array();
+		}
+
+		foreach ($shares_from_file as $share_type => $share_options) {
+			$share_options['type'] = $share_type;
+			self::register($share_options);
+		}
+	}
+
+	/**
+	 * Return the list of sharing options.
+	 * @return an array of FreshRSS_Share objects.
+	 */
+	public static function enum() {
+		return self::$list_sharing;
+	}
+
+	/**
+	 * Return FreshRSS_Share object related to the given type.
+	 * @param $type the share type, null if $type is not registered.
+	 */
+	public static function get($type) {
+		if (!isset(self::$list_sharing[$type])) {
+			return null;
+		}
+
+		return self::$list_sharing[$type];
+	}
+
+	/**
+	 *
+	 */
+	private $type = '';
+	private $name = '';
+	private $url_transform = '';
+	private $transform = array();
+	private $form_type = 'simple';
+	private $help_url = '';
+	private $custom_name = null;
+	private $base_url = null;
+	private $title = null;
+	private $link = null;
+
+	/**
+	 * Create a FreshRSS_Share object.
+	 * @param $type is a unique string defining the kind of share option.
+	 * @param $url_transform defines the url format to use in order to share.
+	 * @param $transform is an array of transformations to apply on link and title.
+	 * @param $form_type defines which form we have to use to complete. "simple"
+	 *        is typically for a centralized service while "advanced" is for
+	 *        decentralized ones.
+	 * @param $help_url is an optional url to give help on this option.
+	 */
+	private function __construct($type, $url_transform, $transform = array(),
+	                             $form_type, $help_url = '') {
+		$this->type = $type;
+		$this->name = _t('gen.share.' . $type);
+		$this->url_transform = $url_transform;
+		$this->help_url = $help_url;
+
+		if (!is_array($transform)) {
+			$transform = array();
+		}
+		$this->transform = $transform;
+
+		if (!in_array($form_type, array('simple', 'advanced'))) {
+			$form_type = 'simple';
+		}
+		$this->form_type = $form_type;
+	}
+
+	/**
+	 * Update a FreshRSS_Share object with information from an array.
+	 * @param $options is a list of informations to update where keys should be
+	 *        in this list: name, url, title, link.
+	 */
+	public function update($options) {
+		$available_options = array(
+			'name' => 'custom_name',
+			'url' => 'base_url',
+			'title' => 'title',
+			'link' => 'link',
+		);
+
+		foreach ($options as $key => $value) {
+			if (!isset($available_options[$key])) {
+				continue;
+			}
+
+			$this->$available_options[$key] = $value;
+		}
+	}
+
+	/**
+	 * Return the current type of the share option.
+	 */
+	public function type() {
+		return $this->type;
+	}
+
+	/**
+	 * Return the current form type of the share option.
+	 */
+	public function formType() {
+		return $this->form_type;
+	}
+
+	/**
+	 * Return the current help url of the share option.
+	 */
+	public function help() {
+		return $this->help_url;
+	}
+
+	/**
+	 * Return the current name of the share option.
+	 */
+	public function name($real = false) {
+		if ($real || is_null($this->custom_name)) {
+			return $this->name;
+		} else {
+			return $this->custom_name;
+		}
+	}
+
+	/**
+	 * Return the current base url of the share option.
+	 */
+	public function baseUrl() {
+		return $this->base_url;
+	}
+
+	/**
+	 * Return the current url by merging url_transform and base_url.
+	 */
+	public function url() {
 		$matches = array(
 			'~URL~',
 			'~TITLE~',
 			'~LINK~',
 		);
 		$replaces = array(
-			$selected['url'],
-			self::transformData($title, self::getTransform($share, 'title')),
-			self::transformData($link, self::getTransform($share, 'link')),
+			$this->base_url,
+			$this->title(),
+			$this->link(),
 		);
-		$url = str_replace($matches, $replaces, $share['url']);
-		return $url;
+		return str_replace($matches, $replaces, $this->url_transform);
 	}
 
-	static private function transformData($data, $transform) {
-		if (!is_array($transform)) {
-			return $data;
+	/**
+	 * Return the title.
+	 * @param $raw true if we should get the title without transformations.
+	 */
+	public function title($raw = false) {
+		if ($raw) {
+			return $this->title;
 		}
-		if (count($transform) === 0) {
+
+		return $this->transform($this->title, $this->getTransform('title'));
+	}
+
+	/**
+	 * Return the link.
+	 * @param $raw true if we should get the link without transformations.
+	 */
+	public function link($raw = false) {
+		if ($raw) {
+			return $this->link;
+		}
+
+		return $this->transform($this->link, $this->getTransform('link'));
+	}
+
+	/**
+	 * Transform a data with the given functions.
+	 * @param $data the data to transform.
+	 * @param $tranform an array containing a list of functions to apply.
+	 * @return the transformed data.
+	 */
+	private static function transform($data, $transform) {
+		if (!is_array($transform) || empty($transform)) {
 			return $data;
 		}
+
 		foreach ($transform as $action) {
 			$data = call_user_func($action, $data);
 		}
+
 		return $data;
 	}
 
-	static private function getTransform($options, $type) {
-		$transform = $options['transform'];
-
-		if (array_key_exists($type, $transform)) {
-			return $transform[$type];
+	/**
+	 * Get the list of transformations for the given attribute.
+	 * @param $attr the attribute of which we want the transformations.
+	 * @return an array containing a list of transformations to apply.
+	 */
+	private function getTransform($attr) {
+		if (array_key_exists($attr, $this->transform)) {
+			return $this->transform[$attr];
 		}
 
-		return $transform;
+		return $this->transform;
 	}
-
 }

+ 2 - 2
app/Models/UserDAO.php

@@ -2,7 +2,7 @@
 
 class FreshRSS_UserDAO extends Minz_ModelPdo {
 	public function createUser($username) {
-		$db = Minz_Configuration::dataBase();
+		$db = FreshRSS_Context::$system_conf->db;
 		require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
 
 		$userPDO = new Minz_ModelPdo($username);
@@ -34,7 +34,7 @@ class FreshRSS_UserDAO extends Minz_ModelPdo {
 	}
 
 	public function deleteUser($username) {
-		$db = Minz_Configuration::dataBase();
+		$db = FreshRSS_Context::$system_conf->db;
 		require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
 
 		if ($db['type'] === 'sqlite') {

+ 36 - 31
app/actualize_script.php

@@ -12,58 +12,63 @@ if (defined('STDOUT')) {
 	fwrite(STDOUT, 'Starting feed actualization at ' . $begin_date->format('c') . "\n");	//Unbuffered
 }
 
-Minz_Configuration::init();
 
-$users = listUsers();
-shuffle($users);	//Process users in random order
+// Set the header params ($_GET) to call the FRSS application.
+$_GET['c'] = 'feed';
+$_GET['a'] = 'actualize';
+$_GET['ajax'] = 1;
+$_GET['force'] = true;
+$_SERVER['HTTP_HOST'] = '';
+
+
+$app = new FreshRSS();
+$app->init();
 
-if (Minz_Configuration::defaultUser() !== ''){
-	array_unshift($users, Minz_Configuration::defaultUser());	//But always start with admin
+$system_conf = Minz_Configuration::get('system');
+$system_conf->auth_type = 'none';  // avoid necessity to be logged in (not saved!)
+
+// Create the list of users to actualize.
+// Users are processed in a random order but always start with admin
+$users = listUsers();
+shuffle($users);
+if ($system_conf->default_user !== ''){
+	array_unshift($users, $system_conf->default_user);
 	$users = array_unique($users);
 }
 
-$limits = Minz_Configuration::limits();
-$minLastActivity = time() - $limits['max_inactivity'];
 
-foreach ($users as $myUser) {
-	if (($myUser !== Minz_Configuration::defaultUser()) && (FreshRSS_UserDAO::mtime($myUser) < $minLastActivity)) {
-		syslog(LOG_INFO, 'FreshRSS skip inactive user ' . $myUser);
+$limits = $system_conf->limits;
+$min_last_activity = time() - $limits['max_inactivity'];
+foreach ($users as $user) {
+	if (($user !== $system_conf->default_user) &&
+			(FreshRSS_UserDAO::mtime($user) < $min_last_activity)) {
+		syslog(LOG_INFO, 'FreshRSS skip inactive user ' . $user);
 		if (defined('STDOUT')) {
-			fwrite(STDOUT, 'FreshRSS skip inactive user ' . $myUser . "\n");	//Unbuffered
+			fwrite(STDOUT, 'FreshRSS skip inactive user ' . $user . "\n");	//Unbuffered
 		}
 		continue;
 	}
-	syslog(LOG_INFO, 'FreshRSS actualize ' . $myUser);
+	syslog(LOG_INFO, 'FreshRSS actualize ' . $user);
 	if (defined('STDOUT')) {
-		fwrite(STDOUT, 'Actualize ' . $myUser . "...\n");	//Unbuffered
+		fwrite(STDOUT, 'Actualize ' . $user . "...\n");	//Unbuffered
 	}
-	echo $myUser, ' ';	//Buffered
+	echo $user, ' ';	//Buffered
 
-	$_GET['c'] = 'feed';
-	$_GET['a'] = 'actualize';
-	$_GET['ajax'] = 1;
-	$_GET['force'] = true;
-	$_SERVER['HTTP_HOST'] = '';
 
-	$freshRSS = new FreshRSS();
+	Minz_Session::_param('currentUser', $user);
+	FreshRSS_Auth::giveAccess();
+	$app->run();
 
-	Minz_Configuration::_authType('none');
-
-	Minz_Session::init('FreshRSS');
-	Minz_Session::_param('currentUser', $myUser);
-
-	$freshRSS->init();
-	$freshRSS->run();
 
 	if (!invalidateHttpCache()) {
-		syslog(LOG_NOTICE, 'FreshRSS write access problem in ' . USERS_PATH . '/*/log.txt!');
+		syslog(LOG_NOTICE, 'FreshRSS write access problem in ' . join_path(USERS_PATH, $user, 'log.txt'));
 		if (defined('STDERR')) {
-			fwrite(STDERR, 'Write access problem in ' . USERS_PATH . '/*/log.txt!' . "\n");
+			fwrite(STDERR, 'Write access problem in ' . join_path(USERS_PATH, $user, 'log.txt') . "\n");
 		}
 	}
-	Minz_Session::unset_session(true);
-	Minz_ModelPdo::clean();
 }
+
+
 syslog(LOG_INFO, 'FreshRSS actualize done.');
 if (defined('STDOUT')) {
 	fwrite(STDOUT, 'Done.' . "\n");

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

@@ -128,6 +128,17 @@ return array(
 		'nothing_to_load' => 'There are no more articles',
 		'previous' => 'Previous',
 	),
+	'share' => array(
+		'blogotext' => 'Blogotext',
+		'diaspora' => 'Diaspora*',
+		'email' => 'Email',
+		'facebook' => 'Facebook',
+		'g+' => 'Google+',
+		'print' => 'Print',
+		'shaarli' => 'Shaarli',
+		'twitter' => 'Twitter',
+		'wallabag' => 'wallabag',
+	),
 	'short' => array(
 		'attention' => 'Attention!',
 		'blank_to_disable' => 'Leave blank to disable',

+ 1 - 12
app/i18n/en/index.php

@@ -55,18 +55,7 @@ return array(
 		'subscription' => 'Subscriptions management',
 		'unread' => 'Show only unread',
 	),
-	'share' => array(
-		'_' => 'Share',
-		'blogotext' => 'Blogotext',
-		'diaspora' => 'Diaspora*',
-		'email' => 'Email',
-		'facebook' => 'Facebook',
-		'g+' => 'Google+',
-		'print' => 'Print',
-		'shaarli' => 'Shaarli',
-		'twitter' => 'Twitter',
-		'wallabag' => 'wallabag',
-	),
+	'share' => 'Share',
 	'tag' => array(
 		'related' => 'Related tags',
 	),

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

@@ -121,7 +121,7 @@ return array(
 		'_' => 'Partage',
 		'blogotext' => 'Blogotext',
 		'diaspora' => 'Diaspora*',
-		'email' => 'Email',
+		'email' => 'Courriel',
 		'facebook' => 'Facebook',
 		'g+' => 'Google+',
 		'more_information' => 'Plus d’informations',

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

@@ -128,6 +128,17 @@ return array(
 		'nothing_to_load' => 'Fin des articles',
 		'previous' => 'Précédent',
 	),
+	'share' => array(
+		'blogotext' => 'Blogotext',
+		'diaspora' => 'Diaspora*',
+		'email' => 'Courriel',
+		'facebook' => 'Facebook',
+		'g+' => 'Google+',
+		'print' => 'Imprimer',
+		'shaarli' => 'Shaarli',
+		'twitter' => 'Twitter',
+		'wallabag' => 'wallabag',
+	),
 	'short' => array(
 		'attention' => 'Attention !',
 		'blank_to_disable' => 'Laissez vide pour désactiver',

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

@@ -55,18 +55,7 @@ return array(
 		'subscription' => 'Gestion des abonnements',
 		'unread' => 'Afficher les non lus',
 	),
-	'share' => array(
-		'_' => 'Partager',
-		'blogotext' => 'Blogotext',
-		'diaspora' => 'Diaspora*',
-		'email' => 'Courriel',
-		'facebook' => 'Facebook',
-		'g+' => 'Google+',
-		'print' => 'Imprimer',
-		'shaarli' => 'Shaarli',
-		'twitter' => 'Twitter',
-		'wallabag' => 'wallabag',
-	),
+	'share' => 'Partager',
 	'tag' => array(
 		'related' => 'Tags associés',
 	),

+ 4 - 4
app/layout/aside_feed.phtml

@@ -1,6 +1,6 @@
 <?php
 	$class = '';
-	if (FreshRSS_Context::$conf->hide_read_feeds &&
+	if (FreshRSS_Context::$user_conf->hide_read_feeds &&
 			FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_NOT_READ) &&
 			!FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_READ)) {
 		$class = ' state_unread';
@@ -15,7 +15,7 @@
 		<a class="btn btn-important" href="<?php echo _url('subscription', 'index'); ?>"><?php echo _t('index.menu.subscription'); ?></a>
 		<a class="btn btn-important" href="<?php echo _url('importExport', 'index'); ?>"><?php echo _i('import'); ?></a>
 	</div>
-	<?php } elseif (Minz_Configuration::needsLogin()) { ?>
+	<?php } elseif (FreshRSS_Auth::accessNeedsLogin()) { ?>
 	<a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('index.menu.about'); ?></a>
 	<?php } ?>
 
@@ -39,7 +39,7 @@
 				$feeds = $cat->feeds();
 				if (!empty($feeds)) {
 					$c_active = FreshRSS_Context::isCurrentGet('c_' . $cat->id());
-					$c_show = $c_active && (!FreshRSS_Context::$conf->display_categories ||
+					$c_show = $c_active && (!FreshRSS_Context::$user_conf->display_categories ||
 					                        FreshRSS_Context::$current_get['feed']);
 		?>
 		<li class="tree-folder category<?php echo $c_active ? ' active' : ''; ?>" data-unread="<?php echo $cat->nbNotRead(); ?>">
@@ -84,7 +84,7 @@
 		<li class="item"><a href="<?php echo _url('subscription', 'index', 'id', '!!!!!!'); ?>"><?php echo _t('gen.action.manage'); ?></a></li>
 		<li class="item"><a href="<?php echo _url('feed', 'actualize', 'id', '!!!!!!'); ?>"><?php echo _t('gen.action.actualize'); ?></a></li>
 		<li class="item">
-			<?php $confirm = FreshRSS_Context::$conf->reading_confirm ? 'confirm' : ''; ?>
+			<?php $confirm = FreshRSS_Context::$user_conf->reading_confirm ? 'confirm' : ''; ?>
 			<button class="read_all as-link <?php echo $confirm; ?>"
 			        form="mark-read-aside"
 			        formaction="<?php echo _url('entry', 'read', 'get', 'f_!!!!!!'); ?>"

+ 6 - 5
app/layout/header.phtml

@@ -1,5 +1,6 @@
 <?php
-if (Minz_Configuration::canLogIn()) {
+
+if (FreshRSS_Auth::accessNeedsAction()) {
 	?><ul class="nav nav-head nav-login"><?php
 		if (FreshRSS_Auth::hasAccess()) {
 			?><li class="item"><?php echo _i('logout'); ?> <a class="signout" href="<?php echo _url('auth', 'logout'); ?>"><?php echo _t('gen.auth.logout'); ?></a></li><?php
@@ -15,13 +16,13 @@ if (Minz_Configuration::canLogIn()) {
 		<h1>
 			<a href="<?php echo _url('index', 'index'); ?>">
 				<img class="logo" src="<?php echo _i('icon', true); ?>" alt="⊚" />
-				<?php echo Minz_Configuration::title(); ?>
+				<?php echo FreshRSS_Context::$system_conf->title; ?>
 			</a>
 		</h1>
 	</div>
 
 	<div class="item search">
-		<?php if (FreshRSS_Auth::hasAccess() || Minz_Configuration::allowAnonymous()) { ?>
+		<?php if (FreshRSS_Auth::hasAccess() || FreshRSS_Context::$system_conf->allow_anonymous) { ?>
 		<form action="<?php echo _url('index', 'index'); ?>" method="get">
 			<div class="stick">
 				<?php $search = Minz_Request::param('search', ''); ?>
@@ -76,14 +77,14 @@ if (Minz_Configuration::canLogIn()) {
 				<li class="item"><a href="<?php echo _url('index', 'logs'); ?>"><?php echo _t('gen.menu.logs'); ?></a></li>
 				<li class="item"><a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('gen.menu.about'); ?></a></li>
 				<?php
-				if (Minz_Configuration::canLogIn()) {
+				if (FreshRSS_Auth::accessNeedsAction()) {
 					?><li class="separator"></li>
 				<li class="item"><a class="signout" href="<?php echo _url('auth', 'logout'); ?>"><?php echo _i('logout'), ' ', _t('gen.auth.logout'); ?></a></li><?php
 				} ?>
 			</ul>
 		</div>
 	</div>
-	<?php } elseif (Minz_Configuration::canLogIn()) { ?>
+	<?php } elseif (FreshRSS_Auth::accessNeedsAction()) { ?>
 	<div class="item configure">
 		<?php echo _i('login'); ?><a class="signin" href="<?php echo _url('auth', 'login'); ?>"><?php echo _t('gen.auth.login'); ?></a>
 	</div>

+ 2 - 2
app/layout/layout.phtml

@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html lang="<?php echo FreshRSS_Context::$conf->language; ?>" xml:lang="<?php echo FreshRSS_Context::$conf->language; ?>">
+<html lang="<?php echo FreshRSS_Context::$user_conf->language; ?>" xml:lang="<?php echo FreshRSS_Context::$user_conf->language; ?>">
 	<head>
 		<meta charset="UTF-8" />
 		<meta name="viewport" content="initial-scale=1.0" />
@@ -34,7 +34,7 @@
 		<link rel="apple-touch-icon" href="<?php echo Minz_Url::display('/themes/icons/apple-touch-icon.png'); ?>">
 		<meta name="apple-mobile-web-app-capable" content="yes" />
 		<meta name="apple-mobile-web-app-status-bar-style" content="black" />
-		<meta name="apple-mobile-web-app-title" content="<?php echo Minz_Configuration::title(); ?>">
+		<meta name="apple-mobile-web-app-title" content="<?php echo FreshRSS_Context::$system_conf->title; ?>">
 		<meta name="msapplication-TileColor" content="#FFF" />
 		<meta name="robots" content="noindex,nofollow" />
 	</head>

+ 6 - 6
app/layout/nav_menu.phtml

@@ -39,13 +39,13 @@
 					<a class="no-mobile" href="<?php echo _url('configure', 'queries'); ?>"><?php echo _i('configure'); ?></a>
 				</li>
 
-				<?php foreach (FreshRSS_Context::$conf->queries as $query) { ?>
+				<?php foreach (FreshRSS_Context::$user_conf->queries as $query) { ?>
 				<li class="item query">
 					<a href="<?php echo $query['url']; ?>"><?php echo $query['name']; ?></a>
 				</li>
 				<?php } ?>
 
-				<?php if (count(FreshRSS_Context::$conf->queries) > 0) { ?>
+				<?php if (count(FreshRSS_Context::$user_conf->queries) > 0) { ?>
 				<li class="separator no-mobile"></li>
 				<?php } ?>
 
@@ -82,7 +82,7 @@
 	<form id="mark-read-menu" method="post" style="display: none"></form>
 
 	<div class="stick" id="nav_menu_read_all">
-		<?php $confirm = FreshRSS_Context::$conf->reading_confirm ? 'confirm' : ''; ?>
+		<?php $confirm = FreshRSS_Context::$user_conf->reading_confirm ? 'confirm' : ''; ?>
 		<button class="read_all btn <?php echo $confirm; ?>"
 		        form="mark-read-menu"
 		        formaction="<?php echo Minz_Url::display($mark_read_url); ?>"
@@ -145,8 +145,8 @@
 
 		<?php
 			$url_output['a'] = 'rss';
-			if (FreshRSS_Context::$conf->token) {
-				$url_output['params']['token'] = FreshRSS_Context::$conf->token;
+			if (FreshRSS_Context::$user_conf->token) {
+				$url_output['params']['token'] = FreshRSS_Context::$user_conf->token;
 			}
 		?>
 		<a class="view_rss btn" target="_blank" title="<?php echo _t('index.menu.rss_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>">
@@ -193,7 +193,7 @@
 		<?php echo _i($icon); ?>
 	</a>
 	
-	<?php if (FreshRSS_Auth::hasAccess() || Minz_Configuration::allowAnonymousRefresh()) { ?>
+	<?php if (FreshRSS_Auth::hasAccess() || FreshRSS_Context::$system_conf->allow_anonymous_refresh) { ?>
 	<a id="actualize" class="btn" href="<?php echo _url('feed', 'actualize'); ?>" title="<?php echo _t('gen.action.actualize'); ?>"><?php echo _i('refresh'); ?></a>
 	<?php } ?>
 </div>

+ 17 - 17
app/views/auth/index.phtml

@@ -10,13 +10,13 @@
 			<label class="group-name" for="auth_type"><?php echo _t('admin.auth.type'); ?></label>
 			<div class="group-controls">
 				<select id="auth_type" name="auth_type" required="required">
-					<?php if (!in_array(Minz_Configuration::authType(), array('form', 'persona', 'http_auth', 'none'))) { ?>
+					<?php if (!in_array(FreshRSS_Context::$system_conf->auth_type, array('form', 'persona', 'http_auth', 'none'))) { ?>
 						<option selected="selected"></option>
 					<?php } ?>
-					<option value="form"<?php echo Minz_Configuration::authType() === 'form' ? ' selected="selected"' : '', cryptAvailable() ? '' : ' disabled="disabled"'; ?>><?php echo _t('admin.auth.form'); ?></option>
-					<option value="persona"<?php echo Minz_Configuration::authType() === 'persona' ? ' selected="selected"' : '', FreshRSS_Context::$conf->mail_login == '' ? ' disabled="disabled"' : ''; ?>><?php echo _t('admin.auth.persona'); ?></option>
-					<option value="http_auth"<?php echo Minz_Configuration::authType() === 'http_auth' ? ' selected="selected"' : '', httpAuthUser() == '' ? ' disabled="disabled"' : ''; ?>><?php echo _t('admin.auth.http'); ?> (REMOTE_USER = '<?php echo httpAuthUser(); ?>')</option>
-					<option value="none"<?php echo Minz_Configuration::authType() === 'none' ? ' selected="selected"' : ''; ?>><?php echo _t('admin.auth.none'); ?></option>
+					<option value="form"<?php echo FreshRSS_Context::$system_conf->auth_type === 'form' ? ' selected="selected"' : '', cryptAvailable() ? '' : ' disabled="disabled"'; ?>><?php echo _t('admin.auth.form'); ?></option>
+					<option value="persona"<?php echo FreshRSS_Context::$system_conf->auth_type === 'persona' ? ' selected="selected"' : '', FreshRSS_Context::$user_conf->mail_login == '' ? ' disabled="disabled"' : ''; ?>><?php echo _t('admin.auth.persona'); ?></option>
+					<option value="http_auth"<?php echo FreshRSS_Context::$system_conf->auth_type === 'http_auth' ? ' selected="selected"' : '', httpAuthUser() == '' ? ' disabled="disabled"' : ''; ?>><?php echo _t('admin.auth.http'); ?> (REMOTE_USER = '<?php echo httpAuthUser(); ?>')</option>
+					<option value="none"<?php echo FreshRSS_Context::$system_conf->auth_type === 'none' ? ' selected="selected"' : ''; ?>><?php echo _t('admin.auth.none'); ?></option>
 				</select>
 			</div>
 		</div>
@@ -24,9 +24,9 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<label class="checkbox" for="anon_access">
-					<input type="checkbox" name="anon_access" id="anon_access" value="1"<?php echo Minz_Configuration::allowAnonymous() ? ' checked="checked"' : '',
-						Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> />
-					<?php echo _t('admin.auth.allow_anonymous', Minz_Configuration::defaultUser()); ?>
+					<input type="checkbox" name="anon_access" id="anon_access" value="1"<?php echo FreshRSS_Context::$system_conf->allow_anonymous ? ' checked="checked"' : '',
+						FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> />
+					<?php echo _t('admin.auth.allow_anonymous', FreshRSS_Context::$system_conf->default_user); ?>
 				</label>
 			</div>
 		</div>
@@ -34,8 +34,8 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<label class="checkbox" for="anon_refresh">
-					<input type="checkbox" name="anon_refresh" id="anon_refresh" value="1"<?php echo Minz_Configuration::allowAnonymousRefresh() ? ' checked="checked"' : '',
-						Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> />
+					<input type="checkbox" name="anon_refresh" id="anon_refresh" value="1"<?php echo FreshRSS_Context::$system_conf->allow_anonymous_refresh ? ' checked="checked"' : '',
+						FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> />
 					<?php echo _t('admin.auth.allow_anonymous_refresh'); ?>
 				</label>
 			</div>
@@ -44,21 +44,21 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<label class="checkbox" for="unsafe_autologin">
-					<input type="checkbox" name="unsafe_autologin" id="unsafe_autologin" value="1"<?php echo Minz_Configuration::unsafeAutologinEnabled() ? ' checked="checked"' : '',
-						Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> />
+					<input type="checkbox" name="unsafe_autologin" id="unsafe_autologin" value="1"<?php echo FreshRSS_Context::$system_conf->unsafe_autologin_enabled ? ' checked="checked"' : '',
+						FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> />
 					<?php echo _t('admin.auth.unsafe_autologin'); ?>
 					<kbd><?php echo Minz_Url::display(array('c' => 'auth', 'a' => 'login', 'params' => array('u' => 'alice', 'p' => '1234')), 'html', true); ?></kbd>
 				</label>
 			</div>
 		</div>
 
-		<?php if (Minz_Configuration::canLogIn()) { ?>
+		<?php if (FreshRSS_Auth::accessNeedsAction()) { ?>
 		<div class="form-group">
 			<label class="group-name" for="token"><?php echo _t('admin.auth.token'); ?></label>
-			<?php $token = FreshRSS_Context::$conf->token; ?>
+			<?php $token = FreshRSS_Context::$user_conf->token; ?>
 			<div class="group-controls">
 				<input type="text" id="token" name="token" value="<?php echo $token; ?>" placeholder="<?php echo _t('gen.short.blank_to_disable'); ?>"<?php
-					echo Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> />
+					echo FreshRSS_Auth::accessNeedsAction() ? '' : ' disabled="disabled"'; ?> />
 				<?php echo _i('help'); ?> <?php echo _t('admin.auth.token_help'); ?>
 				<kbd><?php echo Minz_Url::display(array('params' => array('output' => 'rss', 'token' => $token)), 'html', true); ?></kbd>
 			</div>
@@ -68,8 +68,8 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<label class="checkbox" for="api_enabled">
-					<input type="checkbox" name="api_enabled" id="api_enabled" value="1"<?php echo Minz_Configuration::apiEnabled() ? ' checked="checked"' : '',
-						Minz_Configuration::needsLogin() ? '' : ' disabled="disabled"'; ?> />
+					<input type="checkbox" name="api_enabled" id="api_enabled" value="1"<?php echo FreshRSS_Context::$system_conf->api_enabled ? ' checked="checked"' : '',
+						FreshRSS_Auth::accessNeedsLogin() ? '' : ' disabled="disabled"'; ?> />
 					<?php echo _t('admin.auth.api_enabled'); ?>
 				</label>
 			</div>

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

@@ -10,7 +10,7 @@
 		<div class="form-group">
 			<label class="group-name" for="old_entries"><?php echo _t('conf.archiving.delete_after'); ?></label>
 			<div class="group-controls">
-				<input type="number" id="old_entries" name="old_entries" min="1" max="1200" value="<?php echo FreshRSS_Context::$conf->old_entries; ?>" /> <?php echo _t('gen.date.month'); ?>
+				<input type="number" id="old_entries" name="old_entries" min="1" max="1200" value="<?php echo FreshRSS_Context::$user_conf->old_entries; ?>" /> <?php echo _t('gen.date.month'); ?>
 				  <a class="btn confirm" href="<?php echo _url('entry', 'purge'); ?>"><?php echo _t('conf.archiving.purge_now'); ?></a>
 			</div>
 		</div>
@@ -19,7 +19,7 @@
 			<div class="group-controls">
 				<select class="number" name="keep_history_default" id="keep_history_default" required="required"><?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) {
-						echo '<option value="' . $v . (FreshRSS_Context::$conf->keep_history_default == $v ? '" selected="selected' : '') . '">' . $t . ' </option>';
+						echo '<option value="' . $v . (FreshRSS_Context::$user_conf->keep_history_default == $v ? '" selected="selected' : '') . '">' . $t . ' </option>';
 					}
 				?></select> (<?php echo _t('gen.short.by_default'); ?>)
 			</div>
@@ -34,13 +34,13 @@
 					                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) {
-						echo '<option value="' . $v . (FreshRSS_Context::$conf->ttl_default == $v ? '" selected="selected' : '') . '">' . $t . '</option>';
-						if (FreshRSS_Context::$conf->ttl_default == $v) {
+						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;
 						}
 					}
 					if (!$found) {
-						echo '<option value="' . intval(FreshRSS_Context::$conf->ttl_default) . '" selected="selected">' . intval(FreshRSS_Context::$conf->ttl_default) . 's</option>';
+						echo '<option value="' . intval(FreshRSS_Context::$user_conf->ttl_default) . '" selected="selected">' . intval(FreshRSS_Context::$user_conf->ttl_default) . 's</option>';
 					}
 				?></select> (<?php echo _t('gen.short.by_default'); ?>)
 			</div>

+ 15 - 15
app/views/configure/display.phtml

@@ -10,9 +10,9 @@
 			<label class="group-name" for="language"><?php echo _t('conf.display.language'); ?></label>
 			<div class="group-controls">
 				<select name="language" id="language">
-				<?php $languages = FreshRSS_Context::$conf->availableLanguages(); ?>
+				<?php $languages = Minz_Translate::availableLanguages(); ?>
 				<?php foreach ($languages as $short => $lib) { ?>
-				<option value="<?php echo $short; ?>"<?php echo FreshRSS_Context::$conf->language === $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option>
+				<option value="<?php echo $short; ?>"<?php echo FreshRSS_Context::$user_conf->language === $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option>
 				<?php } ?>
 				</select>
 			</div>
@@ -24,7 +24,7 @@
 				<ul class="slides">
 					<?php $slides = count($this->themes); $i = 1; ?>
 					<?php foreach($this->themes as $theme) { ?>
-						<input type="radio" name="theme" id="img-<?php echo $i ?>" <?php if (FreshRSS_Context::$conf->theme === $theme['id']) {echo "checked";}?> value="<?php echo $theme['id'] ?>"/>
+						<input type="radio" name="theme" id="img-<?php echo $i ?>" <?php if (FreshRSS_Context::$user_conf->theme === $theme['id']) {echo "checked";}?> value="<?php echo $theme['id'] ?>"/>
 						<li class="slide-container">
 							<div class="slide">
 								<img src="<?php echo Minz_Url::display('/themes/' . $theme['id'] . '/thumbs/original.png')?>"/>
@@ -49,7 +49,7 @@
 			</div>
 		</div>
 
-		<?php $width = FreshRSS_Context::$conf->content_width; ?>
+		<?php $width = FreshRSS_Context::$user_conf->content_width; ?>
 		<div class="form-group">
 			<label class="group-name" for="content_width"><?php echo _t('conf.display.width.content'); ?></label>
 			<div class="group-controls">
@@ -87,20 +87,20 @@
 				<tbody>
 					<tr>
 						<th><?php echo _t('conf.display.icon.top_line'); ?></th>
-						<td><input type="checkbox" name="topline_read" value="1"<?php echo FreshRSS_Context::$conf->topline_read ? ' checked="checked"' : ''; ?> /></td>
-						<td><input type="checkbox" name="topline_favorite" value="1"<?php echo FreshRSS_Context::$conf->topline_favorite ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="topline_read" value="1"<?php echo FreshRSS_Context::$user_conf->topline_read ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="topline_favorite" value="1"<?php echo FreshRSS_Context::$user_conf->topline_favorite ? ' checked="checked"' : ''; ?> /></td>
 						<td><input type="checkbox" disabled="disabled" /></td>
 						<td><input type="checkbox" disabled="disabled" /></td>
-						<td><input type="checkbox" name="topline_date" value="1"<?php echo FreshRSS_Context::$conf->topline_date ? ' checked="checked"' : ''; ?> /></td>
-						<td><input type="checkbox" name="topline_link" value="1"<?php echo FreshRSS_Context::$conf->topline_link ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="topline_date" value="1"<?php echo FreshRSS_Context::$user_conf->topline_date ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="topline_link" value="1"<?php echo FreshRSS_Context::$user_conf->topline_link ? ' checked="checked"' : ''; ?> /></td>
 					</tr><tr>
 						<th><?php echo _t('conf.display.icon.bottom_line'); ?></th>
-						<td><input type="checkbox" name="bottomline_read" value="1"<?php echo FreshRSS_Context::$conf->bottomline_read ? ' checked="checked"' : ''; ?> /></td>
-						<td><input type="checkbox" name="bottomline_favorite" value="1"<?php echo FreshRSS_Context::$conf->bottomline_favorite ? ' checked="checked"' : ''; ?> /></td>
-						<td><input type="checkbox" name="bottomline_sharing" value="1"<?php echo FreshRSS_Context::$conf->bottomline_sharing ? ' checked="checked"' : ''; ?> /></td>
-						<td><input type="checkbox" name="bottomline_tags" value="1"<?php echo FreshRSS_Context::$conf->bottomline_tags ? ' checked="checked"' : ''; ?> /></td>
-						<td><input type="checkbox" name="bottomline_date" value="1"<?php echo FreshRSS_Context::$conf->bottomline_date ? ' checked="checked"' : ''; ?> /></td>
-						<td><input type="checkbox" name="bottomline_link" value="1"<?php echo FreshRSS_Context::$conf->bottomline_link ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="bottomline_read" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_read ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="bottomline_favorite" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_favorite ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="bottomline_sharing" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_sharing ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="bottomline_tags" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_tags ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="bottomline_date" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_date ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="bottomline_link" value="1"<?php echo FreshRSS_Context::$user_conf->bottomline_link ? ' checked="checked"' : ''; ?> /></td>
 					</tr>
 				</tbody>
 			</table><br />
@@ -109,7 +109,7 @@
 		<div class="form-group">
 			<label class="group-name" for="posts_per_page"><?php echo _t('conf.display.notif_html5.timeout'); ?></label>
 			<div class="group-controls">
-				<input type="number" id="html5_notif_timeout" name="html5_notif_timeout" value="<?php echo FreshRSS_Context::$conf->html5_notif_timeout; ?>" /> <?php echo _t('conf.display.notif_html5.seconds'); ?>
+				<input type="number" id="html5_notif_timeout" name="html5_notif_timeout" value="<?php echo FreshRSS_Context::$user_conf->html5_notif_timeout; ?>" /> <?php echo _t('conf.display.notif_html5.seconds'); ?>
 			</div>
 		</div>
 

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

@@ -6,7 +6,7 @@
 	<form method="post" action="<?php echo _url('configure', 'queries'); ?>">
 		<legend><?php echo _t('conf.query'); ?></legend>
 
-		<?php foreach (FreshRSS_Context::$conf->queries as $key => $query) { ?>
+		<?php foreach (FreshRSS_Context::$user_conf->queries as $key => $query) { ?>
 		<div class="form-group" id="query-group-<?php echo $key; ?>">
 			<label class="group-name" for="queries_<?php echo $key; ?>_name">
 				<?php echo _t('conf.query.number', $key + 1); ?>
@@ -82,7 +82,7 @@
 		</div>
 		<?php } ?>
 
-		<?php if (count(FreshRSS_Context::$conf->queries) > 0) { ?>
+		<?php if (count(FreshRSS_Context::$user_conf->queries) > 0) { ?>
 		<div class="form-group form-actions">
 			<div class="group-controls">
 				<button type="submit" class="btn btn-important"><?php echo _t('gen.action.submit'); ?></button>

+ 22 - 22
app/views/configure/reading.phtml

@@ -9,7 +9,7 @@
 		<div class="form-group">
 			<label class="group-name" for="posts_per_page"><?php echo _t('conf.reading.articles_per_page'); ?></label>
 			<div class="group-controls">
-				<input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo FreshRSS_Context::$conf->posts_per_page; ?>" min="5" max="50" />
+				<input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo FreshRSS_Context::$user_conf->posts_per_page; ?>" min="5" max="50" />
 				<?php echo _i('help'); ?> <?php echo _t('conf.reading.number_divided_when_reader'); ?>
 			</div>
 		</div>
@@ -18,8 +18,8 @@
 			<label class="group-name" for="sort_order"><?php echo _t('conf.reading.sort'); ?></label>
 			<div class="group-controls">
 				<select name="sort_order" id="sort_order">
-					<option value="DESC"<?php echo FreshRSS_Context::$conf->sort_order === 'DESC' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.sort.newer_first'); ?></option>
-					<option value="ASC"<?php echo FreshRSS_Context::$conf->sort_order === 'ASC' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.sort.older_first'); ?></option>
+					<option value="DESC"<?php echo FreshRSS_Context::$user_conf->sort_order === 'DESC' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.sort.newer_first'); ?></option>
+					<option value="ASC"<?php echo FreshRSS_Context::$user_conf->sort_order === 'ASC' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.sort.older_first'); ?></option>
 				</select>
 			</div>
 		</div>
@@ -28,9 +28,9 @@
 			<label class="group-name" for="view_mode"><?php echo _t('conf.reading.view.default'); ?></label>
 			<div class="group-controls">
 				<select name="view_mode" id="view_mode">
-					<option value="normal"<?php echo FreshRSS_Context::$conf->view_mode === 'normal' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.view.normal'); ?></option>
-					<option value="reader"<?php echo FreshRSS_Context::$conf->view_mode === 'reader' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.view.reader'); ?></option>
-					<option value="global"<?php echo FreshRSS_Context::$conf->view_mode === 'global' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.view.global'); ?></option>
+					<option value="normal"<?php echo FreshRSS_Context::$user_conf->view_mode === 'normal' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.view.normal'); ?></option>
+					<option value="reader"<?php echo FreshRSS_Context::$user_conf->view_mode === 'reader' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.view.reader'); ?></option>
+					<option value="global"<?php echo FreshRSS_Context::$user_conf->view_mode === 'global' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.view.global'); ?></option>
 				</select>
 			</div>
 		</div>
@@ -39,9 +39,9 @@
 			<label class="group-name" for="view_mode"><?php echo _t('conf.reading.show'); ?></label>
 			<div class="group-controls">
 				<select name="default_view" id="default_view">
-					<option value="adaptive"<?php echo FreshRSS_Context::$conf->default_view === 'adaptive' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.show.adaptive'); ?></option>
-					<option value="all"<?php echo FreshRSS_Context::$conf->default_view === 'all' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.show.all_articles'); ?></option>
-					<option value="unread"<?php echo FreshRSS_Context::$conf->default_view === 'unread' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.show.unread'); ?></option>
+					<option value="adaptive"<?php echo FreshRSS_Context::$user_conf->default_view === 'adaptive' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.show.adaptive'); ?></option>
+					<option value="all"<?php echo FreshRSS_Context::$user_conf->default_view === 'all' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.show.all_articles'); ?></option>
+					<option value="unread"<?php echo FreshRSS_Context::$user_conf->default_view === 'unread' ? ' selected="selected"' : ''; ?>><?php echo _t('conf.reading.show.unread'); ?></option>
 				</select>
 			</div>
 		</div>
@@ -49,7 +49,7 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<label class="checkbox" for="hide_read_feeds">
-					<input type="checkbox" name="hide_read_feeds" id="hide_read_feeds" value="1"<?php echo FreshRSS_Context::$conf->hide_read_feeds ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="hide_read_feeds" id="hide_read_feeds" value="1"<?php echo FreshRSS_Context::$user_conf->hide_read_feeds ? ' checked="checked"' : ''; ?> />
 					<?php echo _t('conf.reading.hide_read_feeds'); ?>
 				</label>
 			</div>
@@ -58,7 +58,7 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<label class="checkbox" for="display_posts">
-					<input type="checkbox" name="display_posts" id="display_posts" value="1"<?php echo FreshRSS_Context::$conf->display_posts ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="display_posts" id="display_posts" value="1"<?php echo FreshRSS_Context::$user_conf->display_posts ? ' checked="checked"' : ''; ?> />
 					<?php echo _t('conf.reading.display_articles_unfolded'); ?>
 					<noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
 				</label>
@@ -68,7 +68,7 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<label class="checkbox" for="display_categories">
-					<input type="checkbox" name="display_categories" id="display_categories" value="1"<?php echo FreshRSS_Context::$conf->display_categories ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="display_categories" id="display_categories" value="1"<?php echo FreshRSS_Context::$user_conf->display_categories ? ' checked="checked"' : ''; ?> />
 					<?php echo _t('conf.reading.display_categories_unfolded'); ?>
 					<noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
 				</label>
@@ -78,7 +78,7 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<label class="checkbox" for="sticky_post">
-					<input type="checkbox" name="sticky_post" id="sticky_post" value="1"<?php echo FreshRSS_Context::$conf->sticky_post ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="sticky_post" id="sticky_post" value="1"<?php echo FreshRSS_Context::$user_conf->sticky_post ? ' checked="checked"' : ''; ?> />
 					<?php echo _t('conf.reading.sticky_post'); ?>
 					<noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
 				</label>
@@ -88,7 +88,7 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<label class="checkbox" for="auto_load_more">
-					<input type="checkbox" name="auto_load_more" id="auto_load_more" value="1"<?php echo FreshRSS_Context::$conf->auto_load_more ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="auto_load_more" id="auto_load_more" value="1"<?php echo FreshRSS_Context::$user_conf->auto_load_more ? ' checked="checked"' : ''; ?> />
 					<?php echo _t('conf.reading.auto_load_more'); ?>
 					<noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
 				</label>
@@ -98,7 +98,7 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<label class="checkbox" for="lazyload">
-					<input type="checkbox" name="lazyload" id="lazyload" value="1"<?php echo FreshRSS_Context::$conf->lazyload ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="lazyload" id="lazyload" value="1"<?php echo FreshRSS_Context::$user_conf->lazyload ? ' checked="checked"' : ''; ?> />
 					<?php echo _t('conf.reading.img_with_lazyload'); ?>
 					<noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
 				</label>
@@ -108,7 +108,7 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<label class="checkbox" for="reading_confirm">
-					<input type="checkbox" name="reading_confirm" id="reading_confirm" value="1"<?php echo FreshRSS_Context::$conf->reading_confirm ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="reading_confirm" id="reading_confirm" value="1"<?php echo FreshRSS_Context::$user_conf->reading_confirm ? ' checked="checked"' : ''; ?> />
 					<?php echo _t('conf.reading.confirm_enabled'); ?>
 					<noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
 				</label>
@@ -118,7 +118,7 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<label class="checkbox" for="auto_remove_article">
-					<input type="checkbox" name="auto_remove_article" id="auto_remove_article" value="1"<?php echo FreshRSS_Context::$conf->auto_remove_article ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="auto_remove_article" id="auto_remove_article" value="1"<?php echo FreshRSS_Context::$user_conf->auto_remove_article ? ' checked="checked"' : ''; ?> />
 					<?php echo _t('conf.reading.auto_remove_article'); ?>
 					<noscript> — <strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
 				</label>
@@ -129,19 +129,19 @@
 			<label class="group-name"><?php echo _t('conf.reading.read.when'); ?></label>
 			<div class="group-controls">
 				<label class="checkbox" for="check_open_article">
-					<input type="checkbox" name="mark_open_article" id="check_open_article" value="1"<?php echo FreshRSS_Context::$conf->mark_when['article'] ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="mark_open_article" id="check_open_article" value="1"<?php echo FreshRSS_Context::$user_conf->mark_when['article'] ? ' checked="checked"' : ''; ?> />
 					<?php echo _t('conf.reading.read.article_viewed'); ?>
 				</label>
 				<label class="checkbox" for="check_open_site">
-					<input type="checkbox" name="mark_open_site" id="check_open_site" value="1"<?php echo FreshRSS_Context::$conf->mark_when['site'] ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="mark_open_site" id="check_open_site" value="1"<?php echo FreshRSS_Context::$user_conf->mark_when['site'] ? ' checked="checked"' : ''; ?> />
 					<?php echo _t('conf.reading.read.article_open_on_website'); ?>
 				</label>
 				<label class="checkbox" for="check_scroll">
-					<input type="checkbox" name="mark_scroll" id="check_scroll" value="1"<?php echo FreshRSS_Context::$conf->mark_when['scroll'] ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="mark_scroll" id="check_scroll" value="1"<?php echo FreshRSS_Context::$user_conf->mark_when['scroll'] ? ' checked="checked"' : ''; ?> />
 					<?php echo _t('conf.reading.read.scroll'); ?>
 				</label>
 				<label class="checkbox" for="check_reception">
-					<input type="checkbox" name="mark_upon_reception" id="check_reception" value="1"<?php echo FreshRSS_Context::$conf->mark_when['reception'] ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="mark_upon_reception" id="check_reception" value="1"<?php echo FreshRSS_Context::$user_conf->mark_when['reception'] ? ' checked="checked"' : ''; ?> />
 					<?php echo _t('conf.reading.read.upon_reception'); ?>
 				</label>
 			</div>
@@ -151,7 +151,7 @@
 			<label class="group-name"><?php echo _t('conf.reading.after_onread'); ?></label>
 			<div class="group-controls">
 				<label class="checkbox" for="onread_jump_next">
-					<input type="checkbox" name="onread_jump_next" id="onread_jump_next" value="1"<?php echo FreshRSS_Context::$conf->onread_jump_next ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="onread_jump_next" id="onread_jump_next" value="1"<?php echo FreshRSS_Context::$user_conf->onread_jump_next ? ' checked="checked"' : ''; ?> />
 					<?php echo _t('conf.reading.jump_next'); ?>
 				</label>
 			</div>

+ 16 - 11
app/views/configure/sharing.phtml

@@ -15,22 +15,25 @@
 			<a target="_blank" class="btn" title="<?php echo _t('conf.sharing.more_information'); ?>" href="##help##"><?php echo _i('help'); ?></a>
 			</div></div>'>
 		<legend><?php echo _t('conf.sharing'); ?></legend>
-		<?php foreach (FreshRSS_Context::$conf->sharing as $key => $sharing) { ?>
-			<?php $share = FreshRSS_Context::$conf->shares[$sharing['type']]; ?>
-			<div class="form-group" id="group-share-<?php echo $key; ?>">
+		<?php
+			foreach (FreshRSS_Context::$user_conf->sharing as $key => $share_options) {
+				$share = FreshRSS_Share::get($share_options['type']);
+				$share->update($share_options);
+		?>
+			<div class="form-group group-share" id="group-share-<?php echo $key; ?>">
 				<label class="group-name">
-					<?php echo _t('conf.sharing.' . $sharing['type']); ?>
+					<?php echo $share->name(true); ?>
 				</label>
 				<div class="group-controls">
-					<input type='hidden' id='share_<?php echo $key;?>_type' name="share[<?php echo $key;?>][type]" value='<?php echo $sharing['type']?>' />
-					<?php if ($share['form'] === 'advanced') { ?>
+					<input type='hidden' id='share_<?php echo $key; ?>_type' name="share[<?php echo $key; ?>][type]" value='<?php echo $share->type(); ?>' />
+					<?php if ($share->formType() === 'advanced') { ?>
 						<div class="stick">
-							<input type="text" id="share_<?php echo $key;?>_name" name="share[<?php echo $key;?>][name]" class="extend" value="<?php echo $sharing['name']?>" placeholder="<?php echo _t('conf.sharing.share_name'); ?>" size="64" />
-							<input type="url" id="share_<?php echo $key;?>_url" name="share[<?php echo $key;?>][url]" class="extend" value="<?php echo $sharing['url']?>" placeholder="<?php echo _t('conf.sharing.share_url'); ?>" size="64" />
+							<input type="text" id="share_<?php echo $key; ?>_name" name="share[<?php echo $key; ?>][name]" class="extend" value="<?php echo $share->name(); ?>" placeholder="<?php echo _t('conf.sharing.share_name'); ?>" size="64" />
+							<input type="url" id="share_<?php echo $key; ?>_url" name="share[<?php echo $key; ?>][url]" class="extend" value="<?php echo $share->baseUrl(); ?>" placeholder="<?php echo _t('conf.sharing.share_url'); ?>" size="64" />
 							<a href='#' class='remove btn btn-attention' data-remove="group-share-<?php echo $key; ?>"><?php echo _i('close'); ?></a>
 						</div>
 
-						<a target="_blank" class="btn" title="<?php echo _t('conf.sharing.more_information'); ?>" href="<?php echo $share['help']?>"><?php echo _i('help'); ?></a>
+						<a target="_blank" class="btn" title="<?php echo _t('conf.sharing.more_information'); ?>" href="<?php echo $share->help(); ?>"><?php echo _i('help'); ?></a>
 					<?php } else { ?>
 					<a href='#' class='remove btn btn-attention' data-remove="group-share-<?php echo $key; ?>"><?php echo _i('close'); ?></a>
 					<?php } ?>
@@ -41,8 +44,10 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<select>
-					<?php foreach(FreshRSS_Context::$conf->shares as $key => $params) { ?>
-						<option value='<?php echo $key?>' data-form='<?php echo $params['form']?>' data-help='<?php if (!empty($params['help'])) {echo $params['help'];}?>'><?php echo _t('conf.sharing.' . $key) ?></option>
+					<?php foreach (FreshRSS_Share::enum() as $share) { ?>
+					<option value='<?php echo $share->type(); ?>' data-form='<?php echo $share->formType(); ?>' data-help='<?php echo $share->help(); ?>'>
+						<?php echo $share->name(true); ?>
+					</option>
 					<?php } ?>
 				</select>
 				<a href='#' class='share add btn'><?php echo _i('add'); ?></a>

+ 1 - 1
app/views/configure/shortcut.phtml

@@ -9,7 +9,7 @@
 		<?php } ?>
 	</datalist>
 
-	<?php $s = FreshRSS_Context::$conf->shortcuts; ?>
+	<?php $s = FreshRSS_Context::$user_conf->shortcuts; ?>
 
 	<form method="post" action="<?php echo _url('configure', 'shortcut'); ?>">
 		<legend><?php echo _t('conf.shortcut'); ?></legend>

+ 1 - 1
app/views/helpers/export/opml.phtml

@@ -2,7 +2,7 @@
 
 $opml_array = array(
 	'head' => array(
-		'title' => Minz_Configuration::title(),
+		'title' => FreshRSS_Context::$system_conf->title,
 		'dateCreated' => date('D, d M Y H:i:s')
 	),
 	'body' => array()

+ 8 - 8
app/views/helpers/javascript_vars.phtml

@@ -1,12 +1,12 @@
 "use strict";
 <?php
 
-$mark = FreshRSS_Context::$conf->mark_when;
+$mark = FreshRSS_Context::$user_conf->mark_when;
 $mail = Minz_Session::param('mail', false);
 $auto_actualize = Minz_Session::param('actualize_feeds', false);
-$hide_posts = (FreshRSS_Context::$conf->display_posts ||
+$hide_posts = (FreshRSS_Context::$user_conf->display_posts ||
                Minz_Request::param('output') === 'reader');
-$s = FreshRSS_Context::$conf->shortcuts;
+$s = FreshRSS_Context::$user_conf->shortcuts;
 
 $url_login = Minz_Url::display(array(
 	'c' => 'auth',
@@ -20,16 +20,16 @@ $url_logout = Minz_Url::display(array(
 echo 'var context={',
 	'auto_remove_article:', FreshRSS_Context::isAutoRemoveAvailable() ? 'true' : 'false', ',',
 	'hide_posts:', $hide_posts ? 'false' : 'true', ',',
-	'display_order:"', Minz_Request::param('order', FreshRSS_Context::$conf->sort_order), '",',
+	'display_order:"', Minz_Request::param('order', FreshRSS_Context::$user_conf->sort_order), '",',
 	'auto_mark_article:', $mark['article'] ? 'true' : 'false', ',',
 	'auto_mark_site:', $mark['site'] ? 'true' : 'false', ',',
 	'auto_mark_scroll:', $mark['scroll'] ? 'true' : 'false', ',',
-	'auto_load_more:', FreshRSS_Context::$conf->auto_load_more ? 'true' : 'false', ',',
+	'auto_load_more:', FreshRSS_Context::$user_conf->auto_load_more ? 'true' : 'false', ',',
 	'auto_actualize_feeds:', $auto_actualize ? 'true' : 'false', ',',
-	'does_lazyload:', FreshRSS_Context::$conf->lazyload ? 'true' : 'false', ',',
+	'does_lazyload:', FreshRSS_Context::$user_conf->lazyload ? 'true' : 'false', ',',
 	'sticky_post:', FreshRSS_Context::isStickyPostEnabled() ? 'true' : 'false', ',',
-	'html5_notif_timeout:', FreshRSS_Context::$conf->html5_notif_timeout, ',',
-	'auth_type:"', Minz_Configuration::authType(), '",',
+	'html5_notif_timeout:', FreshRSS_Context::$user_conf->html5_notif_timeout, ',',
+	'auth_type:"', FreshRSS_Context::$system_conf->auth_type, '",',
 	'current_user_mail:', $mail ? ('"' . $mail . '"') : 'null', ',',
 	'current_view:"', Minz_Request::param('output', 'normal'), '"',
 "},\n";

+ 1 - 1
app/views/helpers/pagination.phtml

@@ -24,7 +24,7 @@
 		</a>
 	<?php } elseif ($url_mark_read) { ?>
 		<button id="bigMarkAsRead"
-		        class="as-link <?php echo FreshRSS_Context::$conf->reading_confirm ? 'confirm' : ''; ?>"
+		        class="as-link <?php echo FreshRSS_Context::$user_conf->reading_confirm ? 'confirm' : ''; ?>"
 		        form="mark-read-pagination"
 		        formaction="<?php echo Minz_Url::display($url_mark_read); ?>"
 		        type="submit">

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

@@ -2,7 +2,7 @@
 	$this->partial('nav_menu');
 
 	$class = '';
-	if (FreshRSS_Context::$conf->hide_read_feeds &&
+	if (FreshRSS_Context::$user_conf->hide_read_feeds &&
 			FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_NOT_READ) &&
 			!FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_READ)) {
 		$class = ' state_unread';
@@ -50,5 +50,5 @@
 <div id="overlay">
 	<a class="close" href="#"><?php echo _i('close'); ?></a>
 </div>
-<div id="panel"<?php echo FreshRSS_Context::$conf->display_posts ? '' : ' class="hide_posts"'; ?>>
+<div id="panel"<?php echo FreshRSS_Context::$user_conf->display_posts ? '' : ' class="hide_posts"'; ?>>
 </div>

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

@@ -2,7 +2,7 @@
 
 $output = Minz_Request::param('output', 'normal');
 
-if (FreshRSS_Auth::hasAccess() || Minz_Configuration::allowAnonymous()) {
+if (FreshRSS_Auth::hasAccess() || FreshRSS_Context::$system_conf->allow_anonymous) {
 	if ($output === 'normal') {
 		$this->renderHelper('view/normal_view');
 	} elseif ($output === 'reader') {

+ 27 - 29
app/views/index/normal.phtml

@@ -8,24 +8,24 @@ if (!empty($this->entries)) {
 	$display_yesterday = true;
 	$display_others = true;
 	if (FreshRSS_Auth::hasAccess()) {
-		$sharing = FreshRSS_Context::$conf->sharing;
+		$sharing = FreshRSS_Context::$user_conf->sharing;
 	} else {
 		$sharing = array();
 	}
-	$hidePosts = !FreshRSS_Context::$conf->display_posts;
-	$lazyload = FreshRSS_Context::$conf->lazyload;
-	$topline_read = FreshRSS_Context::$conf->topline_read;
-	$topline_favorite = FreshRSS_Context::$conf->topline_favorite;
-	$topline_date = FreshRSS_Context::$conf->topline_date;
-	$topline_link = FreshRSS_Context::$conf->topline_link;
-	$bottomline_read = FreshRSS_Context::$conf->bottomline_read;
-	$bottomline_favorite = FreshRSS_Context::$conf->bottomline_favorite;
-	$bottomline_sharing = FreshRSS_Context::$conf->bottomline_sharing && (count($sharing));
-	$bottomline_tags = FreshRSS_Context::$conf->bottomline_tags;
-	$bottomline_date = FreshRSS_Context::$conf->bottomline_date;
-	$bottomline_link = FreshRSS_Context::$conf->bottomline_link;
+	$hidePosts = !FreshRSS_Context::$user_conf->display_posts;
+	$lazyload = FreshRSS_Context::$user_conf->lazyload;
+	$topline_read = FreshRSS_Context::$user_conf->topline_read;
+	$topline_favorite = FreshRSS_Context::$user_conf->topline_favorite;
+	$topline_date = FreshRSS_Context::$user_conf->topline_date;
+	$topline_link = FreshRSS_Context::$user_conf->topline_link;
+	$bottomline_read = FreshRSS_Context::$user_conf->bottomline_read;
+	$bottomline_favorite = FreshRSS_Context::$user_conf->bottomline_favorite;
+	$bottomline_sharing = FreshRSS_Context::$user_conf->bottomline_sharing && (count($sharing));
+	$bottomline_tags = FreshRSS_Context::$user_conf->bottomline_tags;
+	$bottomline_date = FreshRSS_Context::$user_conf->bottomline_date;
+	$bottomline_link = FreshRSS_Context::$user_conf->bottomline_link;
 
-	$content_width = FreshRSS_Context::$conf->content_width;
+	$content_width = FreshRSS_Context::$user_conf->content_width;
 
 	$today = @strtotime('today');
 ?>
@@ -129,8 +129,6 @@ if (!empty($this->entries)) {
 				} ?>
 				<li class="item"><?php
 						if ($bottomline_sharing) {
-							$link = urlencode($item->link());
-							$title = urlencode($item->title() . ' · ' . $feed->name());
 					?><div class="dropdown">
 						<div id="dropdown-share-<?php echo $item->id();?>" class="dropdown-target"></div>
 						<a class="dropdown-toggle" href="#dropdown-share-<?php echo $item->id();?>">
@@ -139,19 +137,19 @@ if (!empty($this->entries)) {
 						</a>
 
 						<ul class="dropdown-menu">
-							<li class="dropdown-close"><a href="#close">❌</a></li>
-							<?php
-								foreach ($sharing as $share) {
-									$type_share = FreshRSS_Context::$conf->shares[$share['type']];
-									$has_specific_title = ($type_share['form'] === 'advanced');
-							?>
-								<li class="item share">
-									<a target="_blank" href="<?php echo FreshRSS_Share::generateUrl(FreshRSS_Context::$conf->shares, $share, $item->link(), $item->title() . ' . ' . $feed->name())?>">
-										<?php echo $has_specific_title ? $share['name'] : _t('index.share.' . $share['name']); ?>
-									</a>
-								</li>
-							<?php } ?>
-						</ul>
+							<li class="dropdown-close"><a href="#close">❌</a></li><?php
+								$link = $item->link();
+								$title = $item->title() . ' · ' . $feed->name();
+								foreach (FreshRSS_Context::$user_conf->sharing as $share_options) {
+									$share = FreshRSS_Share::get($share_options['type']);
+									$share_options['link'] = $link;
+									$share_options['title'] = $title;
+									$share->update($share_options);
+							?><li class="item share">
+								<a target="_blank" href="<?php echo $share->url(); ?>"><?php echo $share->name(); ?></a>
+							</li><?php
+								}
+						?></ul>
 					</div>
 					<?php } ?>
 				</li><?php

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

@@ -2,8 +2,8 @@
 $this->partial('nav_menu');
 
 if (!empty($this->entries)) {
-	$lazyload = FreshRSS_Context::$conf->lazyload;
-	$content_width = FreshRSS_Context::$conf->content_width;
+	$lazyload = FreshRSS_Context::$user_conf->lazyload;
+	$content_width = FreshRSS_Context::$user_conf->content_width;
 ?>
 
 <div id="stream" class="reader">

+ 3 - 3
app/views/user/manage.phtml

@@ -10,9 +10,9 @@
 			<label class="group-name" for="new_user_language"><?php echo _t('admin.user.language'); ?></label>
 			<div class="group-controls">
 				<select name="new_user_language" id="new_user_language">
-				<?php $languages = FreshRSS_Context::$conf->availableLanguages(); ?>
+				<?php $languages = Minz_Translate::availableLanguages(); ?>
 				<?php foreach ($languages as $short => $lib) { ?>
-				<option value="<?php echo $short; ?>"<?php echo FreshRSS_Context::$conf->language === $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option>
+				<option value="<?php echo $short; ?>"<?php echo FreshRSS_Context::$user_conf->language === $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option>
 				<?php } ?>
 				</select>
 			</div>
@@ -38,7 +38,7 @@
 
 		<div class="form-group">
 			<label class="group-name" for="new_user_email"><?php echo _t('admin.user.email_persona'); ?></label>
-			<?php $mail = FreshRSS_Context::$conf->mail_login; ?>
+			<?php $mail = FreshRSS_Context::$user_conf->mail_login; ?>
 			<div class="group-controls">
 				<input type="email" id="new_user_email" name="new_user_email" class="extend" autocomplete="off" placeholder="alice@example.net" />
 			</div>

+ 2 - 2
app/views/user/profile.phtml

@@ -28,7 +28,7 @@
 			</div>
 		</div>
 
-		<?php if (Minz_Configuration::apiEnabled()) { ?>
+		<?php if (FreshRSS_Context::$system_conf->api_enabled) { ?>
 		<div class="form-group">
 			<label class="group-name" for="apiPasswordPlain"><?php echo _t('conf.profile.password_api'); ?></label>
 			<div class="group-controls">
@@ -42,7 +42,7 @@
 
 		<div class="form-group">
 			<label class="group-name" for="mail_login"><?php echo _t('conf.profile.email_persona'); ?></label>
-			<?php $mail = FreshRSS_Context::$conf->mail_login; ?>
+			<?php $mail = FreshRSS_Context::$user_conf->mail_login; ?>
 			<div class="group-controls">
 				<input type="email" id="mail_login" name="mail_login" class="extend" autocomplete="off" value="<?php echo $mail; ?>" <?php echo FreshRSS_Auth::hasAccess('admin') ? '' : 'disabled="disabled"'; ?> placeholder="alice@example.net" />
 				<noscript><b><?php echo _t('gen.js.should_be_activated'); ?></b></noscript>

+ 30 - 0
data/config.default.php

@@ -0,0 +1,30 @@
+<?php
+
+return array(
+	'environment' => 'production',
+	'salt' => '',
+	'base_url' => '',
+	'language' => 'en',
+	'title' => 'FreshRSS',
+	'default_user' => '_',
+	'allow_anonymous' => false,
+	'allow_anonymous_refresh' => false,
+	'auth_type' => 'none',
+	'api_enabled' => false,
+	'unsafe_autologin_enabled' => false,
+	'limits' => array(
+		'cache_duration' => 800,
+		'timeout' => 10,
+		'max_inactivity' => PHP_INT_MAX,
+		'max_feeds' => 16384,
+		'max_categories' => 16384,
+	),
+	'db' => array(
+		'type' => 'sqlite',
+		'host' => '',
+		'user' => '',
+		'password' => '',
+		'base' => '',
+		'prefix' => '',
+	),
+);

+ 0 - 0
data/users/_/.gitkeep


+ 66 - 0
data/users/_/config.default.php

@@ -0,0 +1,66 @@
+<?php
+
+return array (
+	'language' => 'en',
+	'old_entries' => 3,
+	'keep_history_default' => 0,
+	'ttl_default' => 3600,
+	'mail_login' => '',
+	'token' => '',
+	'passwordHash' => '',
+	'apiPasswordHash' => '',
+	'posts_per_page' => 20,
+	'view_mode' => 'normal',
+	'default_view' => 'adaptive',
+	'default_state' => FreshRSS_Entry::STATE_NOT_READ,
+	'auto_load_more' => true,
+	'display_posts' => false,
+	'display_categories' => false,
+	'hide_read_feeds' => true,
+	'onread_jump_next' => true,
+	'lazyload' => true,
+	'sticky_post' => true,
+	'reading_confirm' => false,
+	'auto_remove_article' => false,
+	'sort_order' => 'DESC',
+	'anon_access' => false,
+	'mark_when' => array (
+		'article' => true,
+		'site' => true,
+		'scroll' => false,
+		'reception' => false,
+	),
+	'theme' => 'Origine',
+	'content_width' => 'thin',
+	'shortcuts' => array (
+		'mark_read' => 'r',
+		'mark_favorite' => 'f',
+		'go_website' => 'space',
+		'next_entry' => 'j',
+		'prev_entry' => 'k',
+		'first_entry' => 'home',
+		'last_entry' => 'end',
+		'collapse_entry' => 'c',
+		'load_more' => 'm',
+		'auto_share' => 's',
+		'focus_search' => 'a',
+		'user_filter' => 'u',
+		'help' => 'f1',
+		'close_dropdown' => 'escape',
+	),
+	'topline_read' => true,
+	'topline_favorite' => true,
+	'topline_date' => true,
+	'topline_link' => true,
+	'bottomline_read' => true,
+	'bottomline_favorite' => true,
+	'bottomline_sharing' => true,
+	'bottomline_tags' => true,
+	'bottomline_date' => true,
+	'bottomline_link' => true,
+	'sharing' => array (
+	),
+	'queries' => array (
+	),
+	'html5_notif_timeout' => 0,
+);

+ 0 - 9
lib/Minz/BadConfigurationException.php

@@ -1,9 +0,0 @@
-<?php
-class Minz_BadConfigurationException extends Minz_Exception {
-	public function __construct ($part_missing, $code = self::ERROR) {
-		$message = '`' . $part_missing
-		         . '` in the configuration file is missing or is misconfigured';
-		
-		parent::__construct ($message, $code);
-	}
-}

+ 167 - 371
lib/Minz/Configuration.php

@@ -1,419 +1,215 @@
 <?php
-/**
- * MINZ - Copyright 2011 Marien Fressinaud
- * Sous licence AGPL3 <http://www.gnu.org/licenses/>
-*/
 
 /**
- * La classe Configuration permet de gérer la configuration de l'application
+ * Manage configuration for the application.
  */
 class Minz_Configuration {
-	const CONF_PATH_NAME = '/config.php';
+	/**
+	 * The list of configurations.
+	 */
+	private static $config_list = array();
 
 	/**
-	 * VERSION est la version actuelle de MINZ
+	 * Add a new configuration to the list of configuration.
+	 *
+	 * @param $namespace the name of the current configuration
+	 * @param $config_filename the filename of the configuration
+	 * @param $default_filename a filename containing default values for the configuration
+	 * @param $configuration_setter an optional helper to set values in configuration
+	 * @throws Minz_ConfigurationNamespaceException if the namespace already exists.
 	 */
-	const VERSION = '1.3.1.freshrss';  // version spéciale FreshRSS
+	public static function register($namespace, $config_filename, $default_filename = null,
+	                                $configuration_setter = null) {
+		if (isset(self::$config_list[$namespace])) {
+			throw new Minz_ConfigurationNamespaceException(
+				$namespace . ' namespace already exists'
+			);
+		}
+
+		self::$config_list[$namespace] = new Minz_Configuration(
+			$namespace, $config_filename, $default_filename, $configuration_setter
+		);
+	}
 
 	/**
-	 * valeurs possibles pour l'"environment"
-	 * SILENT rend l'application muette (pas de log)
-	 * PRODUCTION est recommandée pour une appli en production
-	 *			(log les erreurs critiques)
-	 * DEVELOPMENT log toutes les erreurs
+	 * Parse a file and return its data.
+	 *
+	 * If the file does not contain a valid PHP code returning an array, an
+	 * empty array is returned anyway.
+	 *
+	 * @param $filename the name of the file to parse.
+	 * @return an array of values
+	 * @throws Minz_FileNotExistException if the file does not exist.
 	 */
-	const SILENT = 0;
-	const PRODUCTION = 1;
-	const DEVELOPMENT = 2;
+	public static function load($filename) {
+		if (!file_exists($filename)) {
+			throw new Minz_FileNotExistException($filename);
+		}
+
+		$data = @include($filename);
+		if (is_array($data)) {
+			return $data;
+		} else {
+			return array();
+		}
+	}
 
 	/**
-	 * définition des variables de configuration
-	 * $salt une chaîne de caractères aléatoires (obligatoire)
-	 * $environment gère le niveau d'affichage pour log et erreurs
-	 * $base_url le chemin de base pour accéder à l'application
-	 * $title le nom de l'application
-	 * $language la langue par défaut de l'application
-	 * $db paramètres pour la base de données (tableau)
-	 *     - host le serveur de la base
-	 *     - user nom d'utilisateur
-	 *     - password mot de passe de l'utilisateur
-	 *     - base le nom de la base de données
+	 * Return the configuration related to a given namespace.
+	 *
+	 * @param $namespace the name of the configuration to get.
+	 * @return a Minz_Configuration object
+	 * @throws Minz_ConfigurationNamespaceException if the namespace does not exist.
 	 */
-	private static $salt = '';
-	private static $environment = Minz_Configuration::PRODUCTION;
-	private static $base_url = '';
-	private static $title = '';
-	private static $language = 'en';
-	private static $default_user = '';
-	private static $allow_anonymous = false;
-	private static $allow_anonymous_refresh = false;
-	private static $auth_type = 'none';
-	private static $api_enabled = false;
-	private static $unsafe_autologin_enabled = false;
+	public static function get($namespace) {
+		if (!isset(self::$config_list[$namespace])) {
+			throw new Minz_ConfigurationNamespaceException(
+				$namespace . ' namespace does not exist'
+			);
+		}
 
-	private static $db = array (
-		'type' => 'mysql',
-		'host' => '',
-		'user' => '',
-		'password' => '',
-		'base' => '',
-		'prefix' => '',
-	);
+		return self::$config_list[$namespace];
+	}
 
-	const MAX_SMALL_INT = 16384;
-	private static $limits = array(
-		'cache_duration' => 800,	//SimplePie cache duration in seconds
-		'timeout' => 10,	//SimplePie timeout in seconds
-		'max_inactivity' => PHP_INT_MAX,	//Time in seconds after which a user who has not used the account is considered inactive (no auto-refresh of feeds).
-		'max_feeds' => Minz_Configuration::MAX_SMALL_INT,
-		'max_categories' => Minz_Configuration::MAX_SMALL_INT,
-	);
+	/**
+	 * The namespace of the current configuration.
+	 */
+	private $namespace = '';
 
-	/*
-	 * Getteurs
+	/**
+	 * The filename for the current configuration.
 	 */
-	public static function salt () {
-		return self::$salt;
-	}
-	public static function environment ($str = false) {
-		$env = self::$environment;
+	private $config_filename = '';
 
-		if ($str) {
-			switch (self::$environment) {
-			case self::SILENT:
-				$env = 'silent';
-				break;
-			case self::DEVELOPMENT:
-				$env = 'development';
-				break;
-			case self::PRODUCTION:
-			default:
-				$env = 'production';
-			}
-		}
+	/**
+	 * The filename for the current default values, null by default.
+	 */
+	private $default_filename = null;
 
-		return $env;
-	}
-	public static function baseUrl () {
-		return self::$base_url;
-	}
-	public static function title () {
-		return self::$title;
-	}
-	public static function language () {
-		return self::$language;
-	}
-	public static function dataBase () {
-		return self::$db;
-	}
-	public static function limits() {
-		return self::$limits;
-	}
-	public static function defaultUser () {
-		return self::$default_user;
-	}
-	public static function allowAnonymous() {
-		return self::$allow_anonymous;
-	}
-	public static function allowAnonymousRefresh() {
-		return self::$allow_anonymous_refresh;
-	}
-	public static function authType() {
-		return self::$auth_type;
-	}
-	public static function needsLogin() {
-		return self::$auth_type !== 'none';
-	}
-	public static function canLogIn() {
-		return self::$auth_type === 'form' || self::$auth_type === 'persona';
-	}
-	public static function apiEnabled() {
-		return self::$api_enabled;
-	}
-	public static function unsafeAutologinEnabled() {
-		return self::$unsafe_autologin_enabled;
-	}
+	/**
+	 * The configuration values, an empty array by default.
+	 */
+	private $data = array();
 
-	public static function _allowAnonymous($allow = false) {
-		self::$allow_anonymous = ((bool)$allow) && self::canLogIn();
-	}
-	public static function _allowAnonymousRefresh($allow = false) {
-		self::$allow_anonymous_refresh = ((bool)$allow) && self::allowAnonymous();
-	}
-	public static function _authType($value) {
-		$value = strtolower($value);
-		switch ($value) {
-			case 'form':
-			case 'http_auth':
-			case 'persona':
-			case 'none':
-				self::$auth_type = $value;
-				break;
-		}
-		self::_allowAnonymous(self::$allow_anonymous);
-	}
+	/**
+	 * The default values, an empty array by default.
+	 */
+	private $data_default = array();
 
-	public static function _enableApi($value = false) {
-		self::$api_enabled = (bool)$value;
-	}
-	public static function _enableAutologin($value = false) {
-		self::$unsafe_autologin_enabled = (bool)$value;
-	}
+	/**
+	 * An object which help to set good values in configuration.
+	 */
+	private $configuration_setter = null;
 
 	/**
-	 * Initialise les variables de configuration
-	 * @exception Minz_FileNotExistException si le CONF_PATH_NAME n'existe pas
-	 * @exception Minz_BadConfigurationException si CONF_PATH_NAME mal formaté
+	 * Create a new Minz_Configuration object.
+	 * 
+	 * @param $namespace the name of the current configuration.
+	 * @param $config_filename the file containing configuration values.
+	 * @param $default_filename the file containing default values, null by default.
+	 * @param $configuration_setter an optional helper to set values in configuration
 	 */
-	public static function init () {
+	private function __construct($namespace, $config_filename, $default_filename = null,
+	                             $configuration_setter = null) {
+		$this->namespace = $namespace;
+		$this->config_filename = $config_filename;
+
 		try {
-			self::parseFile ();
-			self::setReporting ();
+			$this->data = self::load($this->config_filename);
 		} catch (Minz_FileNotExistException $e) {
-			throw $e;
-		} catch (Minz_BadConfigurationException $e) {
-			throw $e;
+			if (is_null($default_filename)) {
+				throw $e;
+			}
 		}
-	}
 
-	public static function writeFile() {
-		$ini_array = array(
-			'general' => array(
-				'environment' => self::environment(true),
-				'salt' => self::$salt,
-				'base_url' => self::$base_url,
-				'title' => self::$title,
-				'default_user' => self::$default_user,
-				'allow_anonymous' => self::$allow_anonymous,
-				'allow_anonymous_refresh' => self::$allow_anonymous_refresh,
-				'auth_type' => self::$auth_type,
-				'api_enabled' => self::$api_enabled,
-				'unsafe_autologin_enabled' => self::$unsafe_autologin_enabled,
-			),
-			'limits' => self::$limits,
-			'db' => self::$db,
-		);
-		@rename(DATA_PATH . self::CONF_PATH_NAME, DATA_PATH . self::CONF_PATH_NAME . '.bak.php');
-		$result = file_put_contents(DATA_PATH . self::CONF_PATH_NAME, "<?php\n return " . var_export($ini_array, true) . ';');
-		if (function_exists('opcache_invalidate')) {
-			opcache_invalidate(DATA_PATH . self::CONF_PATH_NAME);	//Clear PHP 5.5+ cache for include
+		$this->default_filename = $default_filename;
+		if (!is_null($this->default_filename)) {
+			$this->data_default = self::load($this->default_filename);
 		}
-		return (bool)$result;
+
+		$this->_configurationSetter($configuration_setter);
 	}
 
 	/**
-	 * Parse un fichier de configuration
-	 * @exception Minz_PermissionDeniedException si le CONF_PATH_NAME n'est pas accessible
-	 * @exception Minz_BadConfigurationException si CONF_PATH_NAME mal formaté
+	 * Set a configuration setter for the current configuration.
+	 * @param $configuration_setter the setter to call when modifying data. It
+	 *        must implement an handle($key, $value) method.
 	 */
-	private static function parseFile () {
-		$ini_array = include(DATA_PATH . self::CONF_PATH_NAME);
-
-		if (!is_array($ini_array)) {
-			throw new Minz_PermissionDeniedException (
-				DATA_PATH . self::CONF_PATH_NAME,
-				Minz_Exception::ERROR
-			);
+	public function _configurationSetter($configuration_setter) {
+		if (is_callable(array($configuration_setter, 'handle'))) {
+			$this->configuration_setter = $configuration_setter;
 		}
+	}
 
-		// [general] est obligatoire
-		if (!isset ($ini_array['general'])) {
-			throw new Minz_BadConfigurationException (
-				'[general]',
-				Minz_Exception::ERROR
-			);
-		}
-		$general = $ini_array['general'];
-
-		// salt est obligatoire
-		if (!isset ($general['salt'])) {
-			if (isset($general['sel_application'])) {	//v0.6
-				$general['salt'] = $general['sel_application'];
-			} else {
-				throw new Minz_BadConfigurationException (
-					'salt',
-					Minz_Exception::ERROR
-				);
-			}
+	/**
+	 * Return the value of the given param.
+	 * 
+	 * @param $key the name of the param.
+	 * @param $default default value to return if key does not exist.
+	 * @return the value corresponding to the key.
+	 * @throws Minz_ConfigurationParamException if the param does not exist
+	 */
+	public function param($key, $default = null) {
+		if (isset($this->data[$key])) {
+			return $this->data[$key];
+		} elseif (!is_null($default)) {
+			return $default;
+		} elseif (isset($this->data_default[$key])) {
+			return $this->data_default[$key];
+		} else {
+			Minz_Log::warning($key . ' does not exist in configuration');
+			return null;
 		}
-		self::$salt = $general['salt'];
+	}
 
-		if (isset ($general['environment'])) {
-			switch ($general['environment']) {
-			case 'silent':
-				self::$environment = Minz_Configuration::SILENT;
-				break;
-			case 'development':
-				self::$environment = Minz_Configuration::DEVELOPMENT;
-				break;
-			case 'production':
-				self::$environment = Minz_Configuration::PRODUCTION;
-				break;
-			default:
-				if ($general['environment'] >= 0 &&
-					$general['environment'] <= 2) {
-					// fallback 0.7-beta
-					self::$environment = $general['environment'];
-				} else {
-					throw new Minz_BadConfigurationException (
-						'environment',
-						Minz_Exception::ERROR
-					);
-				}
-			}
+	/**
+	 * A wrapper for param().
+	 */
+	public function __get($key) {
+		return $this->param($key);
+	}
 
+	/**
+	 * Set or remove a param.
+	 *
+	 * @param $key the param name to set.
+	 * @param $value the value to set. If null, the key is removed from the configuration.
+	 */
+	public function _param($key, $value = null) {
+		if (!is_null($this->configuration_setter) && $this->configuration_setter->support($key)) {
+			$this->configuration_setter->handle($this->data, $key, $value);
+		} elseif (isset($this->data[$key]) && is_null($value)) {
+			unset($this->data[$key]);
+		} elseif (!is_null($value)) {
+			$this->data[$key] = $value;
 		}
-		if (isset ($general['base_url'])) {
-			self::$base_url = $general['base_url'];
-		}
+	}
 
-		if (isset ($general['title'])) {
-			self::$title = $general['title'];
-		}
-		if (isset ($general['language'])) {
-			self::$language = $general['language'];
-		}
-		if (isset ($general['default_user'])) {
-			self::$default_user = $general['default_user'];
-		}
-		if (isset ($general['auth_type'])) {
-			self::_authType($general['auth_type']);
-		}
-		if (isset ($general['allow_anonymous'])) {
-			self::$allow_anonymous = (
-				((bool)($general['allow_anonymous'])) &&
-				($general['allow_anonymous'] !== 'no')
-			);
-		}
-		if (isset ($general['allow_anonymous_refresh'])) {
-			self::$allow_anonymous_refresh = (
-				((bool)($general['allow_anonymous_refresh'])) &&
-				($general['allow_anonymous_refresh'] !== 'no')
-			);
-		}
-		if (isset ($general['api_enabled'])) {
-			self::$api_enabled = (
-				((bool)($general['api_enabled'])) &&
-				($general['api_enabled'] !== 'no')
-			);
-		}
-		if (isset ($general['unsafe_autologin_enabled'])) {
-			self::$unsafe_autologin_enabled = (
-				((bool)($general['unsafe_autologin_enabled'])) &&
-				($general['unsafe_autologin_enabled'] !== 'no')
-			);
-		}
+	/**
+	 * A wrapper for _param().
+	 */
+	public function __set($key, $value) {
+		$this->_param($key, $value);
+	}
 
-		if (isset($ini_array['limits'])) {
-			$limits = $ini_array['limits'];
-			if (isset($limits['cache_duration'])) {
-				$v = intval($limits['cache_duration']);
-				if ($v > 0) {
-					self::$limits['cache_duration'] = $v;
-				}
-			}
-			if (isset($limits['timeout'])) {
-				$v = intval($limits['timeout']);
-				if ($v > 0) {
-					self::$limits['timeout'] = $v;
-				}
-			}
-			if (isset($limits['max_inactivity'])) {
-				$v = intval($limits['max_inactivity']);
-				if ($v > 0) {
-					self::$limits['max_inactivity'] = $v;
-				}
-			}
-			if (isset($limits['max_feeds'])) {
-				$v = intval($limits['max_feeds']);
-				if ($v > 0 && $v < Minz_Configuration::MAX_SMALL_INT) {
-					self::$limits['max_feeds'] = $v;
-				}
-			}
-			if (isset($limits['max_categories'])) {
-				$v = intval($limits['max_categories']);
-				if ($v > 0 && $v < Minz_Configuration::MAX_SMALL_INT) {
-					self::$limits['max_categories'] = $v;
-				}
-			}
-		}
+	/**
+	 * Save the current configuration in the configuration file.
+	 */
+	public function save() {
+		$back_filename = $this->config_filename . '.bak.php';
+		@rename($this->config_filename, $back_filename);
 
-		// Base de données
-		if (isset ($ini_array['db'])) {
-			$db = $ini_array['db'];
-			if (empty($db['type'])) {
-				throw new Minz_BadConfigurationException (
-					'type',
-					Minz_Exception::ERROR
-				);
-			}
-			switch ($db['type']) {
-				case 'mysql':
-					if (empty($db['host'])) {
-						throw new Minz_BadConfigurationException (
-							'host',
-							Minz_Exception::ERROR
-						);
-					}
-					if (empty($db['user'])) {
-						throw new Minz_BadConfigurationException (
-							'user',
-							Minz_Exception::ERROR
-						);
-					}
-					if (!isset($db['password'])) {
-						throw new Minz_BadConfigurationException (
-							'password',
-							Minz_Exception::ERROR
-						);
-					}
-					if (empty($db['base'])) {
-						throw new Minz_BadConfigurationException (
-							'base',
-							Minz_Exception::ERROR
-						);
-					}
-					self::$db['host'] = $db['host'];
-					self::$db['user'] = $db['user'];
-					self::$db['password'] = $db['password'];
-					self::$db['base'] = $db['base'];
-					if (isset($db['prefix'])) {
-						self::$db['prefix'] = $db['prefix'];
-					}
-					break;
-				case 'sqlite':
-					self::$db['host'] = '';
-					self::$db['user'] = '';
-					self::$db['password'] = '';
-					self::$db['base'] = '';
-					self::$db['prefix'] = '';
-					break;
-				default:
-					throw new Minz_BadConfigurationException (
-						'type',
-						Minz_Exception::ERROR
-					);
-					break;
-			}
-			self::$db['type'] = $db['type'];
+		if (file_put_contents($this->config_filename,
+		                      "<?php\nreturn " . var_export($this->data, true) . ';',
+		                      LOCK_EX) === false) {
+			return false;
 		}
-	}
 
-	private static function setReporting() {
-		switch (self::$environment) {
-			case self::PRODUCTION:
-				error_reporting(E_ALL);
-				ini_set('display_errors','Off');
-				ini_set('log_errors', 'On');
-				break;
-			case self::DEVELOPMENT:
-				error_reporting(E_ALL);
-				ini_set('display_errors','On');
-				ini_set('log_errors', 'On');
-				break;
-			case self::SILENT:
-				error_reporting(0);
-				break;
+		// Clear PHP 5.5+ cache for include
+		if (function_exists('opcache_invalidate')) {
+			opcache_invalidate($this->config_filename);
 		}
+
+		return true;
 	}
 }

+ 8 - 0
lib/Minz/ConfigurationException.php

@@ -0,0 +1,8 @@
+<?php
+
+class Minz_ConfigurationException extends Minz_Exception {
+	public function __construct($error, $code = self::ERROR) {
+		$message = 'Configuration error: ' . $error;
+		parent::__construct($message, $code);
+	}
+}

+ 4 - 0
lib/Minz/ConfigurationNamespaceException.php

@@ -0,0 +1,4 @@
+<?php
+
+class Minz_ConfigurationNamespaceException extends Minz_ConfigurationException {
+}

+ 4 - 0
lib/Minz/ConfigurationParamException.php

@@ -0,0 +1,4 @@
+<?php
+
+class Minz_ConfigurationParamException extends Minz_ConfigurationException {
+}

+ 4 - 3
lib/Minz/Error.php

@@ -82,7 +82,8 @@ class Minz_Error {
 	 *       > en fonction de l'environment
 	 */
 	private static function processLogs ($logs) {
-		$env = Minz_Configuration::environment ();
+		$conf = Minz_Configuration::get('system');
+		$env = $conf->environment;
 		$logs_ok = array ();
 		$error = array ();
 		$warning = array ();
@@ -98,10 +99,10 @@ class Minz_Error {
 			$notice = $logs['notice'];
 		}
 
-		if ($env == Minz_Configuration::PRODUCTION) {
+		if ($env == 'production') {
 			$logs_ok = $error;
 		}
-		if ($env == Minz_Configuration::DEVELOPMENT) {
+		if ($env == 'development') {
 			$logs_ok = array_merge ($error, $warning, $notice);
 		}
 

+ 24 - 2
lib/Minz/FrontController.php

@@ -31,9 +31,12 @@ class Minz_FrontController {
 	 */
 	public function __construct () {
 		try {
-			Minz_Configuration::init ();
+			Minz_Configuration::register('system',
+			                             DATA_PATH . '/config.php',
+			                             DATA_PATH . '/config.default.php');
+			$this->setReporting();
 
-			Minz_Request::init ();
+			Minz_Request::init();
 
 			$url = $this->buildUrl();
 			$url['params'] = array_merge (
@@ -110,4 +113,23 @@ class Minz_FrontController {
 		}
 		exit ('### Application problem ###<br />'."\n".$txt);
 	}
+
+	private function setReporting() {
+		$conf = Minz_Configuration::get('system');
+		switch($conf->environment) {
+		case 'production':
+			error_reporting(E_ALL);
+			ini_set('display_errors','Off');
+			ini_set('log_errors', 'On');
+			break;
+		case 'development':
+			error_reporting(E_ALL);
+			ini_set('display_errors','On');
+			ini_set('log_errors', 'On');
+			break;
+		case 'silent':
+			error_reporting(0);
+			break;
+		}
+	}
 }

+ 8 - 3
lib/Minz/Log.php

@@ -31,10 +31,15 @@ class Minz_Log {
 	 * @param $file_name fichier de log
 	 */
 	public static function record ($information, $level, $file_name = null) {
-		$env = Minz_Configuration::environment ();
+		try {
+			$conf = Minz_Configuration::get('system');
+			$env = $conf->environment;
+		} catch (Minz_ConfigurationException $e) {
+			$env = 'production';
+		}
 
-		if (! ($env === Minz_Configuration::SILENT
-		       || ($env === Minz_Configuration::PRODUCTION
+		if (! ($env === 'silent'
+		       || ($env === 'production'
 		       && ($level >= Minz_Log::NOTICE)))) {
 			if ($file_name === null) {
 				$file_name = join_path(USERS_PATH, Minz_Session::param('currentUser', '_'), 'log.txt');

+ 2 - 1
lib/Minz/ModelPdo.php

@@ -44,7 +44,8 @@ class Minz_ModelPdo {
 			return;
 		}
 
-		$db = Minz_Configuration::dataBase();
+		$conf = Minz_Configuration::get('system');
+		$db = $conf->db;
 
 		if ($currentUser === null) {
 			$currentUser = Minz_Session::param('currentUser', '_');

+ 2 - 1
lib/Minz/Request.php

@@ -96,7 +96,8 @@ class Minz_Request {
 	 * @return la base de l'url
 	 */
 	public static function getBaseUrl() {
-		$defaultBaseUrl = Minz_Configuration::baseUrl();
+		$conf = Minz_Configuration::get('system');
+		$defaultBaseUrl = $conf->base_url;
 		if (!empty($defaultBaseUrl)) {
 			return $defaultBaseUrl;
 		} elseif (isset($_SERVER['REQUEST_URI'])) {

+ 1 - 1
lib/Minz/Session.php

@@ -55,7 +55,7 @@ class Minz_Session {
 
 		if (!$force) {
 			self::_param('language', $language);
-			Minz_Translate::reset();
+			Minz_Translate::reset($language);
 		}
 	}
 

+ 23 - 7
lib/Minz/Translate.php

@@ -9,6 +9,11 @@
  * It uses files in `./app/i18n/`
  */
 class Minz_Translate {
+	/**
+	 * $lang_list is the list of available languages.
+	 */
+	private static $lang_list = array();
+
 	/**
 	 * $lang_name is the name of the current language to use.
 	 */
@@ -25,19 +30,30 @@ class Minz_Translate {
 	private static $translates = array();
 
 	/**
-	 * Load $lang_name and $lang_path based on configuration and selected language.
+	 * Init the translation object.
+	 * @param $lang_list the list of available languages.
+	 * @param $lang_name the lang to show.
 	 */
-	public static function init() {
-		$l = Minz_Configuration::language();
-		self::$lang_name = Minz_Session::param('language', $l);
+	public static function init($lang_list, $lang_name) {
+		self::$lang_list = $lang_list;
+		self::$lang_name = $lang_name;
 		self::$lang_path = APP_PATH . '/i18n/' . self::$lang_name . '/';
 	}
 
 	/**
-	 * Alias for init().
+	 * Reset the translation object with a new language.
+	 * @param $lang_name the new language to use
+	 */
+	public static function reset($lang_name) {
+		self::init(self::$lang_list, $lang_name);
+	}
+
+	/**
+	 * Return the list of available languages.
+	 * @return an array.
 	 */
-	public static function reset() {
-		self::init();
+	public static function availableLanguages() {
+		return self::$lang_list;
 	}
 
 	/**

+ 3 - 1
lib/Minz/View.php

@@ -28,7 +28,9 @@ class Minz_View {
 	public function __construct () {
 		$this->change_view(Minz_Request::controllerName(),
 		                   Minz_Request::actionName());
-		self::$title = Minz_Configuration::title ();
+
+		$conf = Minz_Configuration::get('system');
+		self::$title = $conf->title;
 	}
 
 	/**

+ 46 - 1
lib/lib_rss.php

@@ -119,7 +119,8 @@ function html_only_entity_decode($text) {
 }
 
 function customSimplePie() {
-	$limits = Minz_Configuration::limits();
+	$system_conf = Minz_Configuration::get('system');
+	$limits = $system_conf->limits;
 	$simplePie = new SimplePie();
 	$simplePie->set_useragent(_t('gen.freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ') ' . SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION);
 	$simplePie->set_cache_location(CACHE_PATH);
@@ -236,6 +237,33 @@ function listUsers() {
 	return $final_list;
 }
 
+
+/**
+ * Register and return the configuration for a given user.
+ *
+ * Note this function has been created to generate temporary configuration
+ * objects. If you need a long-time configuration, please don't use this function.
+ *
+ * @param $username the name of the user of which we want the configuration.
+ * @return a Minz_Configuration object, null if the configuration cannot be loaded.
+ */
+function get_user_configuration($username) {
+	$namespace = time() . '_user_' . $username;
+	try {
+		Minz_Configuration::register($namespace,
+		                             join_path(USERS_PATH, $username, 'config.php'),
+		                             join_path(USERS_PATH, '_', 'config.default.php'));
+	} catch (Minz_ConfigurationNamespaceException $e) {
+		// namespace already exists, do nothing.
+	} catch (Minz_FileNotExistException $e) {
+		Minz_Log::warning($e->getMessage());
+		return null;
+	}
+
+	return Minz_Configuration::get($namespace);
+}
+
+
 function httpAuthUser() {
 	return isset($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'] : '';
 }
@@ -359,3 +387,20 @@ function recursive_unlink($dir) {
 	}
 	return rmdir($dir);
 }
+
+
+/**
+ * Remove queries where $get is appearing.
+ * @param $get the get attribute which should be removed.
+ * @param $queries an array of queries.
+ * @return the same array whithout those where $get is appearing.
+ */
+function remove_query_by_get($get, $queries) {
+	$final_queries = array();
+	foreach ($queries as $key => $query) {
+		if (empty($query['get']) || $query['get'] !== $get) {
+			$final_queries[$key] = $query;
+		}
+	}
+	return $final_queries;
+}

+ 19 - 16
p/api/greader.php

@@ -150,13 +150,12 @@ function authorizationToUserConf() {
 		if (count($headerAuthX) === 2) {
 			$user = $headerAuthX[0];
 			if (ctype_alnum($user)) {
-				try {
-					$conf = new FreshRSS_Configuration($user);
-				} catch (Exception $e) {
-					logMe($e->getMessage() . "\n");
+				$conf = get_user_configuration($user);
+				if (is_null($conf)) {
 					unauthorized();
 				}
-				if ($headerAuthX[1] === sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash)) {
+				$system_conf = Minz_Configuration::get('system');
+				if ($headerAuthX[1] === sha1($system_conf->salt . $conf->user . $conf->apiPasswordHash)) {
 					return $conf;
 				} else {
 					logMe('Invalid API authorisation for user ' . $user . ': ' . $headerAuthX[1] . "\n");
@@ -177,16 +176,16 @@ function clientLogin($email, $pass) {	//http://web.archive.org/web/2013060409104
 		if (!function_exists('password_verify')) {
 			include_once(LIB_PATH . '/password_compat.php');
 		}
-		try {
-			$conf = new FreshRSS_Configuration($email);
-		} catch (Exception $e) {
-			logMe($e->getMessage() . "\n");
-			Minz_Log::warning('Invalid API user ' . $email);
+
+		$conf = get_user_configuration($email);
+		if (is_null($conf)) {
 			unauthorized();
 		}
+
 		if ($conf->apiPasswordHash != '' && password_verify($pass, $conf->apiPasswordHash)) {
 			header('Content-Type: text/plain; charset=UTF-8');
-			$auth = $email . '/' . sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash);
+			$system_conf = Minz_Configuration::get('system');
+			$auth = $email . '/' . sha1($system_conf->salt . $conf->user . $conf->apiPasswordHash);
 			echo 'SID=', $auth, "\n",
 				'Auth=', $auth, "\n";
 			exit();
@@ -204,7 +203,8 @@ function token($conf) {
 //http://blog.martindoms.com/2009/08/15/using-the-google-reader-api-part-1/
 //https://github.com/ericmann/gReader-Library/blob/master/greader.class.php
 	logMe('token('. $conf->user . ")\n");	//TODO: Implement real token that expires
-	$token = str_pad(sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash), 57, 'Z');	//Must have 57 characters
+	$system_conf = Minz_Configuration::get('system');
+	$token = str_pad(sha1($system_conf->salt . $conf->user . $conf->apiPasswordHash), 57, 'Z');	//Must have 57 characters
 	echo $token, "\n";
 	exit();
 }
@@ -212,7 +212,8 @@ function token($conf) {
 function checkToken($conf, $token) {
 //http://code.google.com/p/google-reader-api/wiki/ActionToken
 	logMe('checkToken(' . $token . ")\n");
-	if ($token === str_pad(sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash), 57, 'Z')) {
+	$system_conf = Minz_Configuration::get('system');
+	if ($token === str_pad(sha1($system_conf->salt . $conf->user . $conf->apiPasswordHash), 57, 'Z')) {
 		return true;
 	}
 	unauthorized();
@@ -536,9 +537,11 @@ logMe('----------------------------------------------------------------'."\n");
 $pathInfo = empty($_SERVER['PATH_INFO']) ? '/Error' : urldecode($_SERVER['PATH_INFO']);
 $pathInfos = explode('/', $pathInfo);
 
-Minz_Configuration::init();
-
-if (!Minz_Configuration::apiEnabled()) {
+Minz_Configuration::register('system',
+                             DATA_PATH . '/config.php',
+                             DATA_PATH . '/config.default.php');
+$system_conf = Minz_Configuration::get('system');
+if (!$system_conf->api_enabled) {
 	serviceUnavailable();
 }
 

+ 1 - 1
p/i/index.php

@@ -33,7 +33,7 @@ if (file_exists(DATA_PATH . '/do-install.txt')) {
 		$currentUser = Minz_Session::param('currentUser', '');
 		$dateLastModification = $currentUser === '' ? time() : max(
 			@filemtime(join_path(USERS_PATH, $currentUser, 'log.txt')),
-			@filemtime(join_path(DATA_PATH . 'config.php'))
+			@filemtime(join_path(DATA_PATH, 'config.php'))
 		);
 		if (httpConditional($dateLastModification, 0, 0, false, PHP_COMPRESSION, true)) {
 			exit();	//No need to send anything

+ 1 - 1
p/scripts/main.js

@@ -1086,7 +1086,7 @@ function init_print_action() {
 }
 
 function init_share_observers() {
-	shares = $('.form-group:not(".form-actions")').length;
+	shares = $('.group-share').length;
 
 	$('.share.add').on('click', function(e) {
 		var opt = $(this).siblings('select').find(':selected');