Browse Source

Merge remote-tracking branch 'origin/dev' into beta

Alexandre Alapetite 12 years ago
parent
commit
d7c929e53b
81 changed files with 1873 additions and 1584 deletions
  1. 17 10
      CHANGELOG
  2. 30 12
      README.md
  3. 93 154
      app/Controllers/configureController.php
  4. 21 19
      app/Controllers/entryController.php
  5. 18 22
      app/Controllers/feedController.php
  6. 51 35
      app/Controllers/indexController.php
  7. 141 0
      app/Controllers/usersController.php
  8. 97 22
      app/FreshRSS.php
  9. 1 1
      app/Models/Category.php
  10. 167 257
      app/Models/Configuration.php
  11. 0 161
      app/Models/ConfigurationDAO.php
  12. 1 5
      app/Models/Entry.php
  13. 11 21
      app/Models/Feed.php
  14. 18 14
      app/Models/LogDAO.php
  15. 36 0
      app/Models/UserDAO.php
  16. 53 9
      app/actualize_script.php
  17. 20 10
      app/i18n/en.php
  18. 20 11
      app/i18n/fr.php
  19. 6 1
      app/i18n/install.en.php
  20. 10 5
      app/i18n/install.fr.php
  21. 9 4
      app/layout/aside_configure.phtml
  22. 3 3
      app/layout/aside_flux.phtml
  23. 11 14
      app/layout/header.phtml
  24. 4 2
      app/layout/layout.phtml
  25. 2 2
      app/layout/nav_menu.phtml
  26. 59 0
      app/sql.php
  27. 58 0
      app/views/configure/archiving.phtml
  28. 32 103
      app/views/configure/display.phtml
  29. 2 2
      app/views/configure/feed.phtml
  30. 5 2
      app/views/configure/importExport.phtml
  31. 1 1
      app/views/configure/sharing.phtml
  32. 8 1
      app/views/configure/shortcut.phtml
  33. 154 0
      app/views/configure/users.phtml
  34. 1 1
      app/views/feed/actualize.phtml
  35. 12 11
      app/views/helpers/javascript_vars.phtml
  36. 2 2
      app/views/helpers/view/global_view.phtml
  37. 41 29
      app/views/helpers/view/normal_view.phtml
  38. 1 1
      app/views/helpers/view/reader_view.phtml
  39. 27 18
      app/views/index/index.phtml
  40. 18 14
      app/views/javascript/actualize.phtml
  41. 1 1
      constants.php
  42. 3 1
      data/.gitignore
  43. 13 0
      data/cache/index.html
  44. 13 0
      data/favicons/index.html
  45. 13 0
      data/log/index.html
  46. 1 0
      data/persona/.gitignore
  47. 13 0
      data/persona/index.html
  48. 3 3
      index.html
  49. 1 1
      index.php
  50. 110 49
      lib/Minz/Configuration.php
  51. 18 7
      lib/Minz/Dispatcher.php
  52. 1 1
      lib/Minz/FileNotExistException.php
  53. 14 1
      lib/Minz/FrontController.php
  54. 13 24
      lib/Minz/Log.php
  55. 53 94
      lib/Minz/ModelArray.php
  56. 14 6
      lib/Minz/ModelPdo.php
  57. 0 84
      lib/Minz/ModelTxt.php
  58. 8 1
      lib/Minz/Request.php
  59. 4 14
      lib/Minz/Session.php
  60. 10 19
      lib/Minz/View.php
  61. 1 0
      lib/SimplePie/SimplePie/File.php
  62. 121 0
      lib/lib_opml.php
  63. 29 148
      lib/lib_rss.php
  64. 5 9
      p/.htaccess
  65. 36 0
      p/Web.config
  66. BIN
      p/favicon.ico
  67. BIN
      p/favicon.png
  68. 7 7
      p/i/index.php
  69. 65 130
      p/i/install.php
  70. 22 2
      p/index.html
  71. 0 3
      p/index.php
  72. 12 0
      p/scripts/main.js
  73. 4 0
      p/themes/default/global.css
  74. 4 0
      p/themes/flat-design/global.css
  75. BIN
      p/themes/icons/favicon-128.png
  76. BIN
      p/themes/icons/favicon-16.png
  77. BIN
      p/themes/icons/favicon-256.png
  78. BIN
      p/themes/icons/favicon-32.png
  79. BIN
      p/themes/icons/favicon-48.png
  80. BIN
      p/themes/icons/favicon-512.png
  81. BIN
      p/themes/icons/favicon-64.png

+ 17 - 10
CHANGELOG

@@ -2,13 +2,20 @@
 
 ## 2014-01-xx FreshRSS 0.7
 
-* Installeur
-	* Nouvel utilisateur par défaut, en prévision du futur support multi-utilisateurs
-	* Supporte une mise à jour depuis la version 0.6
-		* Juste placer application.ini et Configuration.array.php dans le nouveau répertoire “./data/”
+* Nouveau mode multi-utilisateur
+	* L’utilisateur par défaut (administrateur) peut créer et supprimer d’autres utilisateurs
+	* Nécessite un contrôle d’accès, soit :
+		* par HTTP (par exemple sous Apache en créant un fichier ./p/i/.htaccess et .htpasswd)
+			* le nom d’utilisateur HTTP doit correspondre au nom d’utilisateur FreshRSS
+		* par Mozilla Persona, en renseignant l’adresse courriel des utilisateurs
+* Installateur supportant les mises à jour :
+	* Depuis une v0.6, placer application.ini et Configuration.array.php dans le nouveau répertoire “./data/” (voir réorganisation ci-dessous)
+	* Pour les versions suivantes, juste garder “./data/config.php” “./data/*_user.php”
 * Importation OPML instantanée et plus tolérante
-	* Nouvelle gestion des favicons avec téléchargement en parallèle et compatibilité multi-utilisateurs
+* Nouvelle gestion des favicons avec téléchargement en parallèle
 * Nouvelles options
+	* Réorganisation des options
+	* Gestion des utilisateurs
 	* Améliorations partage vers Shaarli, Poche, Diaspora*, Facebook, Twitter, Google+, courriel
 	* Permet la suppression de tous les articles d’un flux
 	* Option pour marquer les articles comme lus dès la réception
@@ -16,18 +23,17 @@
 	* Permet de modifier la description et l’adresse d’un flux RSS ainsi que le site Web associé
 	* Nouveau raccourci pour ouvrir/fermer un article (‘c’ par défaut)
 	* Boutons pour effacer les logs et pour purger les vieux articles
-	* Légère réorganisation des options
 * SQL :
 	* Nouveau moteur de recherche, aussi accessible depuis la vue mobile
 		* Mots clefs de recherche “intitle:”, “inurl:”, “author:”
 	* Les articles sont triés selon la date de leur ajout dans FreshRSS plutôt que la date déclarée (souvent erronée)
 		* Permet de marquer tout comme lu sans affecter les nouveaux articles arrivés en cours de lecture
 	* Refactorisation
-		* Les tables sont préfixées avec le nom d’utilisateur en prévision du futur support multi-utilisateurs
+		* Les tables sont préfixées avec le nom d’utilisateur afin de permettre le mode multi-utilisateurs
 		* Amélioration des performances
 		* Tolère un beaucoup plus grand nombre d’articles
 		* Compression des données côté MySQL plutôt que côté PHP
-		* Incompatible avec la version 0.6 (nécessite une mise à jour grâce à l’installeur) 
+		* Incompatible avec la version 0.6 (nécessite une mise à jour grâce à l’installateur) 
 	* Affichage de la taille de la base de données dans FreshRSS
 	* Correction problème de marquage de tous les favoris comme lus
 * HTML5 :
@@ -42,10 +48,11 @@
 		* FreshRSS fonctionne aussi en mode dégradé sans images (alternatives Unicode) et/ou sans CSS
 	* Diverses améliorations mineures
 * PHP :
-	* Meilleure gestion des caractères spéciaux dans différents cas
-	* Amélioration des performances
 	* Encore plus tolérant pour les flux comportant des erreurs
 	* Mise à jour automatique de l’URL du flux (en base de données) lorsque SimplePie découvre qu’elle a changé
+	* Meilleure gestion des caractères spéciaux dans différents cas
+	* Compatibilité PHP 5.5+ avec OPcache
+	* Amélioration des performances
 	* Chargement automatique des classes
 	* Alternative dans le cas d’absence de librairie JSON
 	* Pour le développement, le cache HTTP peut être désactivé en créant un fichier “./no-cache.txt”

+ 30 - 12
README.md

@@ -1,11 +1,14 @@
 # FreshRSS
 FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de [Leed](http://projet.idleman.fr/leed/) ou de [Kriss Feed](http://tontof.net/kriss/feed/).
+
 Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable.
 
+Il permet de gérer plusieurs utilisateurs, et dispose d’un mode de lecture anonyme.
+
 * Site officiel : http://freshrss.org
 * Démo : http://marienfressinaud.fr/projets/freshrss/
 * Développeur : Marien Fressinaud <dev@marienfressinaud.fr>
-* Version actuelle : 0.7-beta3
+* Version actuelle : 0.7-beta4
 * Date de publication 2014-01-xx
 * License [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html)
 
@@ -19,13 +22,15 @@ Privilégiez pour cela des demandes sur GitHub
 (https://github.com/marienfressinaud/FreshRSS/issues) ou par mail (dev@marienfressinaud.fr)
 
 # Pré-requis
-* Serveur Apache2 ou Nginx (non testé sur les autres)
-* PHP 5.2+ (PHP 5.3.3+ recommandé)
- * Requis : [LibXML](http://php.net/xml), [PCRE](http://php.net/pcre), [cURL](http://php.net/curl), [PDO_MySQL](http://php.net/pdo-mysql)
- * Recommandés : [JSON](http://php.net/json), [zlib](http://php.net/zlib), [mbstring](http://php.net/mbstring), [iconv](http://php.net/iconv)
+* Serveur modeste, par exemple sous Linux ou Windows
+	* Fonctionne même sur un Raspberry Pi avec des temps de réponse < 1s (testé sur 150 flux, 22k articles, soit 32Mo de données partiellement compressées)
+* Serveur Web Apache2 ou Nginx (non testé sur les autres)
+* PHP 5.2+ (PHP 5.3.4+ recommandé)
+	* Requis : [PDO_MySQL](http://php.net/pdo-mysql), [cURL](http://php.net/curl), [LibXML](http://php.net/xml), [PCRE](http://php.net/pcre), [ctype](http://php.net/ctype)
+	* Recommandés : [JSON](http://php.net/json), [zlib](http://php.net/zlib), [mbstring](http://php.net/mbstring), [iconv](http://php.net/iconv)
 * MySQL 5.0.3+ (ou SQLite 3.7.4+ à venir)
 * Un navigateur Web récent tel Firefox, Chrome, Opera, Safari, Internet Explorer 9+
- * Fonctionne aussi sur mobile
+	* Fonctionne aussi sur mobile
 
 ![Capture d’écran de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png)
 
@@ -37,20 +42,33 @@ Privilégiez pour cela des demandes sur GitHub
 5. Tout devrait fonctionner :) En cas de problème, n’hésitez pas à me contacter.
 
 # Contrôle d’accès
-Il est recommandé de limiter l’accès à votre FreshRSS, soit :
+Il est requis pour le mode multi-utilisateur, et recommandé dans tous les cas, de limiter l’accès à votre FreshRSS :
 * En utilisant l’identification par [Mozilla Persona](https://login.persona.org/about) incluse dans FreshRSS
-* En utilisant un contrôle d’accès défini par votre serveur Web
- * Voir par exemple la [documentation d’Apache sur l’authentification](http://httpd.apache.org/docs/trunk/howto/auth.html)
+* En utilisant un contrôle d’accès HTTP défini par votre serveur Web
+	* Voir par exemple la [documentation d’Apache sur l’authentification](http://httpd.apache.org/docs/trunk/howto/auth.html)
+		* Créer dans ce cas un fichier `./p/i/.htaccess` avec un fichier `.htpasswd` correspondant.
 
 # Rafraîchissement automatique des flux
-* Vous pouvez ajouter une tâche CRON sur le script d’actualisation des flux. Par exemple, pour exécuter le script toutes les heures :
+* Vous pouvez ajouter une tâche Cron lançant régulièrement le script d’actualisation automatique des flux.
+Consultez la documentation de Cron de votre système d’exploitation ([Debian/Ubuntu](http://doc.ubuntu-fr.org/cron), [Red Hat/Fedora](http://doc.fedora-fr.org/wiki/CRON_:_Configuration_de_t%C3%A2ches_automatis%C3%A9es), [Slackware](http://docs.slackware.com/fr:slackbook:process_control?#cron), [Gentoo](http://wiki.gentoo.org/wiki/Cron/fr), [Arch Linux](http://wiki.archlinux.fr/Cron)…).
+C’est une bonne idée d’utiliser le même utilisateur que votre serveur Web (souvent “www-data”).
+Par exemple, pour exécuter le script toutes les heures :
 
 ```
-7 * * * * php /chemin/vers/FreshRSS/app/actualize_script.php >/dev/null 2>&1
+7 * * * * php /chemin/vers/FreshRSS/app/actualize_script.php > /tmp/FreshRSS.log 2>&1
 ```
 
 # Conseils
 * Pour une meilleure sécurité, faites en sorte que seul le répertoire `./p/` soit accessible depuis le Web, par exemple en faisant pointer un sous-domaine sur le répertoire `./p/`.
-* Les données personnelles se trouvent dans le répertoire `./data/` (déjà protégé par un .htaccess pour Apache - vérifiez que cela fonctionne -, à protéger vous-même dans le cas d’autres serveurs Web).
+	* En particulier, les données personnelles se trouvent dans le répertoire `./data/`.
 * Le fichier `./constants.php` définit les chemins d’accès aux répertoires clés de l’application. Si vous les bougez, tout se passe ici.
 * En cas de problème, les logs peuvent être utile à lire, soit depuis l’interface de FreshRSS, soit manuellement depuis `./data/log/*.log`.
+
+# Sauvegarde
+* Il faut conserver vos fichiers `./data/config.php` ainsi que `./data/*_user.php` et éventuellement `./data/persona/`
+* Vous pouvez exporter votre liste de flux depuis FreshRSS au format OPML
+* Pour sauvegarder les articles eux-même, vous pouvez utiliser [phpMyAdmin](http://www.phpmyadmin.net) ou les outils de MySQL :
+
+```bash
+mysqldump -u utilisateur -p --databases freshrss > freshrss.sql
+```

+ 93 - 154
app/Controllers/configureController.php

@@ -2,7 +2,7 @@
 
 class FreshRSS_configure_Controller extends Minz_ActionController {
 	public function firstAction () {
-		if (login_is_conf ($this->view->conf) && !is_logged ()) {
+		if (!$this->view->loginOk) {
 			Minz_Error::error (
 				403,
 				array ('error' => array (Minz_Translate::t ('access_denied')))
@@ -16,7 +16,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 	public function categorizeAction () {
 		$feedDAO = new FreshRSS_FeedDAO ();
 		$catDAO = new FreshRSS_CategoryDAO ();
-		$catDAO->checkDefault ();
 		$defaultCategory = $catDAO->getDefault ();
 		$defaultId = $defaultCategory->id ();
 
@@ -51,8 +50,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 					$catDAO->addCategory ($values);
 				}
 			}
+			invalidateHttpCache();
 
-			// notif
 			$notif = array (
 				'type' => 'good',
 				'content' => Minz_Translate::t ('categories_updated')
@@ -93,14 +92,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 				);
 			} else {
 				if (Minz_Request::isPost () && $this->view->flux) {
-					$name = Minz_Request::param ('name', '');
-					$description = sanitizeHTML(Minz_Request::param('description', '', true));
-					$website = Minz_Request::param('website', '');
-					$url = Minz_Request::param('url', '');
-					$keep_history = intval(Minz_Request::param ('keep_history', -2));
-					$cat = Minz_Request::param ('category', 0);
-					$path = Minz_Request::param ('path_entries', '');
-					$priority = Minz_Request::param ('priority', 0);
 					$user = Minz_Request::param ('http_user', '');
 					$pass = Minz_Request::param ('http_pass', '');
 
@@ -109,16 +100,18 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 						$httpAuth = $user . ':' . $pass;
 					}
 
+					$cat = intval(Minz_Request::param('category', 0));
+
 					$values = array (
-						'name' => $name,
-						'description' => $description,
-						'website' => $website,
-						'url' => $url,
+						'name' => Minz_Request::param ('name', ''),
+						'description' => sanitizeHTML(Minz_Request::param('description', '', true)),
+						'website' => Minz_Request::param('website', ''),
+						'url' => Minz_Request::param('url', ''),
 						'category' => $cat,
-						'pathEntries' => $path,
-						'priority' => $priority,
+						'pathEntries' => Minz_Request::param ('path_entries', ''),
+						'priority' => intval(Minz_Request::param ('priority', 0)),
 						'httpAuth' => $httpAuth,
-						'keep_history' => $keep_history
+						'keep_history' => intval(Minz_Request::param ('keep_history', -2)),
 					);
 
 					if ($feedDAO->updateFeed ($id, $values)) {
@@ -134,6 +127,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 							'content' => Minz_Translate::t ('error_occurred_update')
 						);
 					}
+					invalidateHttpCache();
 
 					Minz_Session::_param ('notification', $notif);
 					Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => array ('id' => $id)), true);
@@ -147,109 +141,39 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 	}
 
 	public function displayAction () {
-		if (Minz_Request::isPost ()) {
-			$current_token = $this->view->conf->token ();
-
-			$language = Minz_Request::param ('language', 'en');
-			$nb = Minz_Request::param ('posts_per_page', 10);
-			$mode = Minz_Request::param ('view_mode', 'normal');
-			$view = Minz_Request::param ('default_view', 'a');
-			$auto_load_more = Minz_Request::param ('auto_load_more', 'no');
-			$display = Minz_Request::param ('display_posts', 'no');
-			$onread_jump_next = Minz_Request::param ('onread_jump_next', 'no');
-			$lazyload = Minz_Request::param ('lazyload', 'no');
-			$sort = Minz_Request::param ('sort_order', 'DESC');
-			$old = Minz_Request::param ('old_entries', 3);
-			$keepHistoryDefault = Minz_Request::param('keep_history_default', 0);
-			$mail = Minz_Request::param ('mail_login', false);
-			$anon = Minz_Request::param ('anon_access', 'no');
-			$token = Minz_Request::param ('token', $current_token);
-			$openArticle = Minz_Request::param ('mark_open_article', 'no');
-			$openSite = Minz_Request::param ('mark_open_site', 'no');
-			$scroll = Minz_Request::param ('mark_scroll', 'no');
-			$reception = Minz_Request::param ('mark_upon_reception', 'no');
-			$theme = Minz_Request::param ('theme', 'default');
-			$topline_read = Minz_Request::param ('topline_read', 'no');
-			$topline_favorite = Minz_Request::param ('topline_favorite', 'no');
-			$topline_date = Minz_Request::param ('topline_date', 'no');
-			$topline_link = Minz_Request::param ('topline_link', 'no');
-			$bottomline_read = Minz_Request::param ('bottomline_read', 'no');
-			$bottomline_favorite = Minz_Request::param ('bottomline_favorite', 'no');
-			$bottomline_sharing = Minz_Request::param ('bottomline_sharing', 'no');
-			$bottomline_tags = Minz_Request::param ('bottomline_tags', 'no');
-			$bottomline_date = Minz_Request::param ('bottomline_date', 'no');
-			$bottomline_link = Minz_Request::param ('bottomline_link', 'no');
-
-			$this->view->conf->_language ($language);
-			$this->view->conf->_postsPerPage (intval ($nb));
-			$this->view->conf->_viewMode ($mode);
-			$this->view->conf->_defaultView ($view);
-			$this->view->conf->_autoLoadMore ($auto_load_more);
-			$this->view->conf->_displayPosts ($display);
-			$this->view->conf->_onread_jump_next ($onread_jump_next);
-			$this->view->conf->_lazyload ($lazyload);
-			$this->view->conf->_sortOrder ($sort);
-			$this->view->conf->_oldEntries ($old);
-			$this->view->conf->_keepHistoryDefault($keepHistoryDefault);
-			$this->view->conf->_mailLogin ($mail);
-			$this->view->conf->_anonAccess ($anon);
-			$this->view->conf->_token ($token);
-			$this->view->conf->_markWhen (array (
-				'article' => $openArticle,
-				'site' => $openSite,
-				'scroll' => $scroll,
-				'reception' => $reception,
+		if (Minz_Request::isPost()) {
+			$this->view->conf->_language(Minz_Request::param('language', 'en'));
+			$this->view->conf->_posts_per_page(Minz_Request::param('posts_per_page', 10));
+			$this->view->conf->_view_mode(Minz_Request::param('view_mode', 'normal'));
+			$this->view->conf->_default_view (Minz_Request::param('default_view', 'a'));
+			$this->view->conf->_auto_load_more(Minz_Request::param('auto_load_more', false));
+			$this->view->conf->_display_posts(Minz_Request::param('display_posts', false));
+			$this->view->conf->_onread_jump_next(Minz_Request::param('onread_jump_next', false));
+			$this->view->conf->_lazyload (Minz_Request::param('lazyload', false));
+			$this->view->conf->_sort_order(Minz_Request::param('sort_order', 'DESC'));
+			$this->view->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),
 			));
-			$this->view->conf->_theme ($theme);
-			$this->view->conf->_topline_read ($topline_read);
-			$this->view->conf->_topline_favorite ($topline_favorite);
-			$this->view->conf->_topline_date ($topline_date);
-			$this->view->conf->_topline_link ($topline_link);
-			$this->view->conf->_bottomline_read ($bottomline_read);
-			$this->view->conf->_bottomline_favorite ($bottomline_favorite);
-			$this->view->conf->_bottomline_sharing ($bottomline_sharing);
-			$this->view->conf->_bottomline_tags ($bottomline_tags);
-			$this->view->conf->_bottomline_date ($bottomline_date);
-			$this->view->conf->_bottomline_link ($bottomline_link);
-
-			$values = array (
-				'language' => $this->view->conf->language (),
-				'posts_per_page' => $this->view->conf->postsPerPage (),
-				'view_mode' => $this->view->conf->viewMode (),
-				'default_view' => $this->view->conf->defaultView (),
-				'auto_load_more' => $this->view->conf->autoLoadMore (),
-				'display_posts' => $this->view->conf->displayPosts (),
-				'onread_jump_next' => $this->view->conf->onread_jump_next (), 
-				'lazyload' => $this->view->conf->lazyload (),
-				'sort_order' => $this->view->conf->sortOrder (),
-				'old_entries' => $this->view->conf->oldEntries (),
-				'keep_history_default' => $this->view->conf->keepHistoryDefault(),
-				'mail_login' => $this->view->conf->mailLogin (),
-				'anon_access' => $this->view->conf->anonAccess (),
-				'token' => $this->view->conf->token (),
-				'mark_when' => $this->view->conf->markWhen (),
-				'theme' => $this->view->conf->theme (),
-				'topline_read' => $this->view->conf->toplineRead () ? 'yes' : 'no',
-				'topline_favorite' => $this->view->conf->toplineFavorite () ? 'yes' : 'no',
-				'topline_date' => $this->view->conf->toplineDate () ? 'yes' : 'no',
-				'topline_link' => $this->view->conf->toplineLink () ? 'yes' : 'no',
-				'bottomline_read' => $this->view->conf->bottomlineRead () ? 'yes' : 'no',
-				'bottomline_favorite' => $this->view->conf->bottomlineFavorite () ? 'yes' : 'no',
-				'bottomline_sharing' => $this->view->conf->bottomlineSharing () ? 'yes' : 'no',
-				'bottomline_tags' => $this->view->conf->bottomlineTags () ? 'yes' : 'no',
-				'bottomline_date' => $this->view->conf->bottomlineDate () ? 'yes' : 'no',
-				'bottomline_link' => $this->view->conf->bottomlineLink () ? 'yes' : 'no',
-			);
-
-			$confDAO = new FreshRSS_ConfigurationDAO ();
-			$confDAO->update ($values);
-			Minz_Session::_param ('conf', $this->view->conf);
-			Minz_Session::_param ('mail', $this->view->conf->mailLogin ());
-
-			Minz_Session::_param ('language', $this->view->conf->language ());
+			$this->view->conf->_theme(Minz_Request::param('theme', 'default'));
+			$this->view->conf->_topline_read(Minz_Request::param('topline_read', false));
+			$this->view->conf->_topline_favorite(Minz_Request::param('topline_favorite', false));
+			$this->view->conf->_topline_date(Minz_Request::param('topline_date', false));
+			$this->view->conf->_topline_link(Minz_Request::param('topline_link', false));
+			$this->view->conf->_bottomline_read(Minz_Request::param('bottomline_read', false));
+			$this->view->conf->_bottomline_favorite(Minz_Request::param('bottomline_favorite', false));
+			$this->view->conf->_bottomline_sharing(Minz_Request::param('bottomline_sharing', false));
+			$this->view->conf->_bottomline_tags(Minz_Request::param('bottomline_tags', false));
+			$this->view->conf->_bottomline_date(Minz_Request::param('bottomline_date', false));
+			$this->view->conf->_bottomline_link(Minz_Request::param('bottomline_link', false));
+			$this->view->conf->save();
+
+			Minz_Session::_param ('language', $this->view->conf->language);
 			Minz_Translate::reset ();
+			invalidateHttpCache();
 
-			// notif
 			$notif = array (
 				'type' => 'good',
 				'content' => Minz_Translate::t ('configuration_updated')
@@ -261,31 +185,24 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 
 		$this->view->themes = FreshRSS_Themes::get();
 
-		Minz_View::prependTitle (Minz_Translate::t ('general_and_reading_management') . ' - ');
-
-		$entryDAO = new FreshRSS_EntryDAO ();
-		$this->view->nb_total = $entryDAO->count ();
-		$this->view->size_total = $entryDAO->size ();
+		Minz_View::prependTitle (Minz_Translate::t ('reading_configuration') . ' - ');
 	}
 
 	public function sharingAction () {
 		if (Minz_Request::isPost ()) {
-			$this->view->conf->_sharing (array (
-				'shaarli' => Minz_Request::param ('shaarli', ''),
-				'poche' => Minz_Request::param ('poche', ''),
-				'diaspora' => Minz_Request::param ('diaspora', ''),
-				'twitter' => Minz_Request::param ('twitter', 'no') === 'yes',
-				'g+' => Minz_Request::param ('g+', 'no') === 'yes',
-				'facebook' => Minz_Request::param ('facebook', 'no') === 'yes',
-				'email' => Minz_Request::param ('email', 'no') === 'yes',
-				'print' => Minz_Request::param ('print', 'no') === 'yes'
+			$this->view->conf->_sharing (array(
+				'shaarli' => Minz_Request::param ('shaarli', false),
+				'poche' => Minz_Request::param ('poche', false),
+				'diaspora' => Minz_Request::param ('diaspora', false),
+				'twitter' => Minz_Request::param ('twitter', false),
+				'g+' => Minz_Request::param ('g+', false),
+				'facebook' => Minz_Request::param ('facebook', false),
+				'email' => Minz_Request::param ('email', false),
+				'print' => Minz_Request::param ('print', false),
 			));
+			$this->view->conf->save();
+			invalidateHttpCache();
 
-			$confDAO = new FreshRSS_ConfigurationDAO ();
-			$confDAO->update ($this->view->conf->sharing ());
-			Minz_Session::_param ('conf', $this->view->conf);
-
-			// notif
 			$notif = array (
 				'type' => 'good',
 				'content' => Minz_Translate::t ('configuration_updated')
@@ -296,12 +213,10 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 		}
 
 		Minz_View::prependTitle (Minz_Translate::t ('sharing_management') . ' - ');
-
-		$entryDAO = new FreshRSS_EntryDAO ();
-		$this->view->nb_total = $entryDAO->count ();
 	}
 
 	public function importExportAction () {
+		require_once(LIB_PATH . '/lib_opml.php');
 		$catDAO = new FreshRSS_CategoryDAO ();
 		$this->view->categories = $catDAO->listCategories ();
 
@@ -326,6 +241,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 			$this->view->categories = $list;
 		} elseif ($this->view->req == 'import' && Minz_Request::isPost ()) {
 			if ($_FILES['file']['error'] == 0) {
+				invalidateHttpCache();
 				// on parse le fichier OPML pour récupérer les catégories et les flux associés
 				try {
 					list ($categories, $feeds) = opml_import (
@@ -373,32 +289,21 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 		                    '9', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9',
 		                    'f10', 'f11', 'f12');
 		$this->view->list_keys = $list_keys;
-		$list_names = array ('mark_read', 'mark_favorite', 'go_website', 'next_entry',
-		                     'prev_entry', 'next_page', 'prev_page', 'collapse_entry',
-		                     'load_more');
 
 		if (Minz_Request::isPost ()) {
 			$shortcuts = Minz_Request::param ('shortcuts');
 			$shortcuts_ok = array ();
 
 			foreach ($shortcuts as $key => $value) {
-				if (in_array ($key, $list_names)
-				 && in_array ($value, $list_keys)) {
+				if (in_array($value, $list_keys)) {
 					$shortcuts_ok[$key] = $value;
 				}
 			}
 
 			$this->view->conf->_shortcuts ($shortcuts_ok);
+			$this->view->conf->save();
+			invalidateHttpCache();
 
-			$values = array (
-				'shortcuts' => $this->view->conf->shortcuts ()
-			);
-
-			$confDAO = new FreshRSS_ConfigurationDAO ();
-			$confDAO->update ($values);
-			Minz_Session::_param ('conf', $this->view->conf);
-
-			// notif
 			$notif = array (
 				'type' => 'good',
 				'content' => Minz_Translate::t ('shortcuts_updated')
@@ -410,4 +315,38 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 
 		Minz_View::prependTitle (Minz_Translate::t ('shortcuts_management') . ' - ');
 	}
+
+	public function usersAction() {
+		Minz_View::prependTitle(Minz_Translate::t ('users') . ' - ');
+	}
+
+	public function archivingAction () {
+		if (Minz_Request::isPost()) {
+			$old = Minz_Request::param('old_entries', 3);
+			$keepHistoryDefault = Minz_Request::param('keep_history_default', 0);
+
+			$this->view->conf->_old_entries($old);
+			$this->view->conf->_keep_history_default($keepHistoryDefault);
+			$this->view->conf->save();
+			invalidateHttpCache();
+
+			$notif = array(
+				'type' => 'good',
+				'content' => Minz_Translate::t('configuration_updated')
+			);
+			Minz_Session::_param('notification', $notif);
+
+			Minz_Request::forward(array('c' => 'configure', 'a' => 'archiving'), true);
+		}
+
+		Minz_View::prependTitle(Minz_Translate::t('archiving_configuration') . ' - ');
+
+		$entryDAO = new FreshRSS_EntryDAO();
+		$this->view->nb_total = $entryDAO->count();
+		$this->view->size_user = $entryDAO->size();
+
+		if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
+			$this->view->size_total = $entryDAO->size(true);
+		}
+	}
 }

+ 21 - 19
app/Controllers/entryController.php

@@ -2,7 +2,7 @@
 
 class FreshRSS_entry_Controller extends Minz_ActionController {
 	public function firstAction () {
-		if (login_is_conf ($this->view->conf) && !is_logged ()) {
+		if (!$this->view->loginOk) {
 			Minz_Error::error (
 				403,
 				array ('error' => array (Minz_Translate::t ('access_denied')))
@@ -16,6 +16,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
 			$this->view->_useLayout (false);
 		}
 	}
+
 	public function lastAction () {
 		$ajax = Minz_Request::param ('ajax');
 		if (!$ajax && $this->redirect) {
@@ -38,7 +39,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
 		$nextGet = Minz_Request::param ('nextGet', $get); 
 		$idMax = Minz_Request::param ('idMax', 0);
 
-		$is_read = !!$is_read;
+		$is_read = (bool)$is_read;
 
 		$entryDAO = new FreshRSS_EntryDAO ();
 		if ($id == false) {
@@ -87,33 +88,34 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
 	}
 
 	public function optimizeAction() {
-		@set_time_limit(300);
-		invalidateHttpCache();
+		if (Minz_Request::isPost()) {
+			@set_time_limit(300);
 
-		// La table des entrées a tendance à grossir énormément
-		// Cette action permet d'optimiser cette table permettant de grapiller un peu de place
-		// Cette fonctionnalité n'est à appeler qu'occasionnellement
-		$entryDAO = new FreshRSS_EntryDAO();
-		$entryDAO->optimizeTable();
+			// La table des entrées a tendance à grossir énormément
+			// Cette action permet d'optimiser cette table permettant de grapiller un peu de place
+			// Cette fonctionnalité n'est à appeler qu'occasionnellement
+			$entryDAO = new FreshRSS_EntryDAO();
+			$entryDAO->optimizeTable();
 
-		invalidateHttpCache();
+			invalidateHttpCache();
 
-		$notif = array (
-			'type' => 'good',
-			'content' => Minz_Translate::t ('optimization_complete')
-		);
-		Minz_Session::_param ('notification', $notif);
+			$notif = array (
+				'type' => 'good',
+				'content' => Minz_Translate::t ('optimization_complete')
+			);
+			Minz_Session::_param ('notification', $notif);
+		}
 
 		Minz_Request::forward(array(
 			'c' => 'configure',
-			'a' => 'display'
+			'a' => 'archiving'
 		), true);
 	}
 
 	public function purgeAction() {
 		@set_time_limit(300);
 
-		$nb_month_old = max($this->view->conf->oldEntries(), 1);
+		$nb_month_old = max($this->view->conf->old_entries, 1);
 		$date_min = time() - (3600 * 24 * 30 * $nb_month_old);
 
 		$feedDAO = new FreshRSS_FeedDAO();
@@ -125,7 +127,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
 		foreach ($feeds as $feed) {
 			$feedHistory = $feed->keepHistory();
 			if ($feedHistory == -2) {	//default
-				$feedHistory = $this->view->conf->keepHistoryDefault();
+				$feedHistory = $this->view->conf->keep_history_default;
 			}
 			if ($feedHistory >= 0) {
 				$nb = $feedDAO->cleanOldEntries($feed->id(), $date_min, $feedHistory);
@@ -147,7 +149,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
 
 		Minz_Request::forward(array(
 			'c' => 'configure',
-			'a' => 'display'
+			'a' => 'archiving'
 		), true);
 	}
 }

+ 18 - 22
app/Controllers/feedController.php

@@ -2,18 +2,17 @@
 
 class FreshRSS_feed_Controller extends Minz_ActionController {
 	public function firstAction () {
-		$token = $this->view->conf->token();
-		$token_param = Minz_Request::param ('token', '');
-		$token_is_ok = ($token != '' && $token == $token_param);
-		$action = Minz_Request::actionName ();
-
-		if (login_is_conf ($this->view->conf) &&
-				!is_logged () &&
-				!($token_is_ok && $action == 'actualize')) {
-			Minz_Error::error (
-				403,
-				array ('error' => array (Minz_Translate::t ('access_denied')))
-			);
+		if (!$this->view->loginOk) {
+			$token = $this->view->conf->token;	//TODO: check the token logic again, and if it is still needed
+			$token_param = Minz_Request::param ('token', '');
+			$token_is_ok = ($token != '' && $token == $token_param);
+			$action = Minz_Request::actionName ();
+			if (!($token_is_ok && $action === 'actualize')) {
+				Minz_Error::error (
+					403,
+					array ('error' => array (Minz_Translate::t ('access_denied')))
+				);
+			}
 		}
 
 		$this->catDAO = new FreshRSS_CategoryDAO ();
@@ -79,13 +78,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 						$feed->_id ($id);
 						$feed->faviconPrepare();
 
-						$is_read = $this->view->conf->markUponReception() === 'yes' ? 1 : 0;
+						$is_read = $this->view->conf->mark_when['reception'] ? 1 : 0;
 
 						$entryDAO = new FreshRSS_EntryDAO ();
 						$entries = array_reverse($feed->entries());	//We want chronological order and SimplePie uses reverse order
 
 						// on calcule la date des articles les plus anciens qu'on accepte
-						$nb_month_old = $this->view->conf->oldEntries ();
+						$nb_month_old = $this->view->conf->old_entries;
 						$date_min = time () - (3600 * 24 * 30 * $nb_month_old);
 
 						$transactionStarted = true;
@@ -182,26 +181,25 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 		}
 
 		// on calcule la date des articles les plus anciens qu'on accepte
-		$nb_month_old = max($this->view->conf->oldEntries(), 1);
+		$nb_month_old = max($this->view->conf->old_entries, 1);
 		$date_min = time () - (3600 * 24 * 30 * $nb_month_old);
 
 		$i = 0;
 		$flux_update = 0;
+		$is_read = $this->view->conf->mark_when['reception'] ? 1 : 0;
 		foreach ($feeds as $feed) {
 			try {
 				$url = $feed->url();
 				$feed->load(false);
 				$entries = array_reverse($feed->entries());	//We want chronological order and SimplePie uses reverse order
 
-				$is_read = $this->view->conf->markUponReception() === 'yes' ? 1 : 0;
-
 				//For this feed, check last n entry GUIDs already in database
 				$existingGuids = array_fill_keys ($entryDAO->listLastGuidsByFeed ($feed->id (), count($entries) + 10), 1);
 				$useDeclaredDate = empty($existingGuids);
 
 				$feedHistory = $feed->keepHistory();
 				if ($feedHistory == -2) {	//default
-					$feedHistory = $this->view->conf->keepHistoryDefault();
+					$feedHistory = $this->view->conf->keep_history_default;
 				}
 
 				// On ne vérifie pas strictement que l'article n'est pas déjà en BDD
@@ -309,7 +307,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 		$this->addCategories ($categories);
 
 		// on calcule la date des articles les plus anciens qu'on accepte
-		$nb_month_old = $this->view->conf->oldEntries ();
+		$nb_month_old = $this->view->conf->old_entries;
 		$date_min = time () - (3600 * 24 * 30 * $nb_month_old);
 
 		// la variable $error permet de savoir si une erreur est survenue
@@ -412,10 +410,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 	}
 
 	private function addCategories ($categories) {
-		$catDAO = new FreshRSS_CategoryDAO ();
-
 		foreach ($categories as $cat) {
-			if (!$catDAO->searchByName ($cat->name ())) {
+			if (!$this->catDAO->searchByName ($cat->name ())) {
 				$values = array (
 					'id' => $cat->id (),
 					'name' => $cat->name (),

+ 51 - 35
app/Controllers/indexController.php

@@ -16,17 +16,18 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 
 	public function indexAction () {
 		$output = Minz_Request::param ('output');
-
-		$token = $this->view->conf->token();
-		$token_param = Minz_Request::param ('token', '');
-		$token_is_ok = ($token != '' && $token === $token_param);
-
-		// check if user is log in
-		if(login_is_conf ($this->view->conf) &&
-				!is_logged() &&
-				$this->view->conf->anonAccess() === 'no' &&
-				!($output === 'rss' && $token_is_ok)) {
-			return;
+		$token = '';
+
+		// check if user is logged in
+		if (!$this->view->loginOk && !Minz_Configuration::allowAnonymous())
+		{
+			$token = $this->view->conf->token;
+			$token_param = Minz_Request::param ('token', '');
+			$token_is_ok = ($token != '' && $token === $token_param);
+			if (!($output === 'rss' && $token_is_ok)) {
+				return;
+			}
+			$params['token'] = $token;
 		}
 
 		// construction of RSS url of this feed
@@ -35,11 +36,6 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 		if (isset ($params['search'])) {
 			$params['search'] = urlencode ($params['search']);
 		}
-		if (login_is_conf($this->view->conf) &&
-				$this->view->conf->anonAccess() === 'no' &&
-				$token != '') {
-			$params['token'] = $token;
-		}
 		$this->view->rss_url = array (
 			'c' => 'index',
 			'a' => 'index',
@@ -91,13 +87,13 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 		);
 
 		// On récupère les différents éléments de filtrage
-		$this->view->state = $state = Minz_Request::param ('state', $this->view->conf->defaultView ());
+		$this->view->state = $state = Minz_Request::param ('state', $this->view->conf->default_view);
 		$filter = Minz_Request::param ('search', '');
 		if (!empty($filter)) {
 			$state = 'all';	//Search always in read and unread articles
 		}
-		$this->view->order = $order = Minz_Request::param ('order', $this->view->conf->sortOrder ());
-		$nb = Minz_Request::param ('nb', $this->view->conf->postsPerPage ());
+		$this->view->order = $order = Minz_Request::param ('order', $this->view->conf->sort_order);
+		$nb = Minz_Request::param ('nb', $this->view->conf->posts_per_page);
 		$first = Minz_Request::param ('next', '');
 
 		if ($state === 'not_read') {	//Any unread article in this category at all?
@@ -128,16 +124,16 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 		$this->view->today = $today;
 
 		// on calcule la date des articles les plus anciens qu'on affiche
-		$nb_month_old = $this->view->conf->oldEntries ();
+		$nb_month_old = $this->view->conf->old_entries;
 		$date_min = $today - (3600 * 24 * 30 * $nb_month_old);	//Do not use a fast changing value such as time() to allow SQL caching
-		$keepHistoryDefault = $this->view->conf->keepHistoryDefault();
+		$keepHistoryDefault = $this->view->conf->keep_history_default;
 
 		try {
 			$entries = $this->entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter, $date_min, $keepHistoryDefault);
 
 			// Si on a récupéré aucun article "non lus"
 			// on essaye de récupérer tous les articles
-			if ($state === 'not_read' && empty($entries)) {	//TODO: Remove in v0.8
+			if ($state === 'not_read' && empty($entries)) {
 				Minz_Log::record ('Conflicting information about nbNotRead!', Minz_Log::DEBUG);
 				$this->view->state = 'all';
 				$entries = $this->entryDAO->listWhere($getType, $getId, 'all', $order, $nb, $first, $filter, $date_min, $keepHistoryDefault);
@@ -212,7 +208,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 	}
 
 	public function logsAction () {
-		if (login_is_conf ($this->view->conf) && !is_logged ()) {
+		if (!$this->view->loginOk) {
 			Minz_Error::error (
 				403,
 				array ('error' => array (Minz_Translate::t ('access_denied')))
@@ -222,17 +218,10 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 		Minz_View::prependTitle (Minz_Translate::t ('logs') . ' - ');
 
 		if (Minz_Request::isPost ()) {
-			file_put_contents(LOG_PATH . '/application.log', '');
+			FreshRSS_LogDAO::truncate();
 		}
 
-		$logs = array();
-		try {
-			$logDAO = new FreshRSS_LogDAO ();
-			$logs = $logDAO->lister ();
-			$logs = array_reverse ($logs);
-		} catch (Minz_FileNotExistException $e) {
-
-		}
+		$logs = FreshRSS_LogDAO::lines();	//TODO: ask only the necessary lines
 
 		//gestion pagination
 		$page = Minz_Request::param ('page', 1);
@@ -260,13 +249,40 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 		curl_close ($ch);
 
 		$res = json_decode ($result, true);
-		if ($res['status'] === 'okay' && $res['email'] === $this->view->conf->mailLogin ()) {
-			Minz_Session::_param ('mail', $res['email']);
+
+		$loginOk = false;
+		$reason = '';
+		if ($res['status'] === 'okay') {
+			$email = filter_var($res['email'], FILTER_VALIDATE_EMAIL);
+			if ($email != '') {
+				$personaFile = DATA_PATH . '/persona/' . $email . '.txt';
+				if (($currentUser = @file_get_contents($personaFile)) !== false) {
+					$currentUser = trim($currentUser);
+					if (ctype_alnum($currentUser)) {
+						try {
+							$this->conf = new FreshRSS_Configuration($currentUser);
+							$loginOk = strcasecmp($email, $this->conf->mail_login) === 0;
+						} catch (Minz_Exception $e) {
+							$reason = 'Invalid configuration for user [' . $currentUser . ']! ' . $e->getMessage();	//Permission denied or conf file does not exist
+						}
+					} else {
+						$reason = 'Invalid username format [' . $currentUser . ']!';
+					}
+				}
+			} else {
+				$reason = 'Invalid email format [' . $res['email'] . ']!';
+			}
+		}
+		if ($loginOk) {
+			Minz_Session::_param('currentUser', $currentUser);
+			Minz_Session::_param ('mail', $email);
+			$this->view->loginOk = true;
 			invalidateHttpCache();
 		} else {
 			$res = array ();
 			$res['status'] = 'failure';
-			$res['reason'] = Minz_Translate::t ('invalid_login');
+			$res['reason'] = $reason == '' ? Minz_Translate::t ('invalid_login') : $reason;
+			Minz_Log::record ('Persona: ' . $res['reason'], Minz_Log::WARNING);
 		}
 
 		header('Content-Type: application/json; charset=UTF-8');

+ 141 - 0
app/Controllers/usersController.php

@@ -0,0 +1,141 @@
+<?php
+
+class FreshRSS_users_Controller extends Minz_ActionController {
+	public function firstAction() {
+		if (!$this->view->loginOk) {
+			Minz_Error::error(
+				403,
+				array('error' => array(Minz_Translate::t('access_denied')))
+			);
+		}
+	}
+
+	public function authAction() {
+		if (Minz_Request::isPost()) {
+			$ok = true;
+
+			$mail = Minz_Request::param('mail_login', false);
+			$this->view->conf->_mail_login($mail);
+			$ok &= $this->view->conf->save();
+
+			$email = $this->view->conf->mail_login;
+			Minz_Session::_param('mail', $email);
+
+			if ($email != '') {
+				$personaFile = DATA_PATH . '/persona/' . $email . '.txt';
+				@unlink($personaFile);
+				$ok &= (file_put_contents($personaFile, Minz_Session::param('currentUser', '_')) !== false);
+			}
+
+			if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
+				$current_token = $this->view->conf->token;
+				$token = Minz_Request::param('token', $current_token);
+				$this->view->conf->_token($token);
+				$ok &= $this->view->conf->save();
+
+				$anon = Minz_Request::param('anon_access', false);
+				$anon = ((bool)$anon) && ($anon !== 'no');
+				$auth_type = Minz_Request::param('auth_type', 'none');
+				if ($anon != Minz_Configuration::allowAnonymous() ||
+					$auth_type != Minz_Configuration::authType()) {
+					Minz_Configuration::_allowAnonymous($anon);
+					Minz_Configuration::_authType($auth_type);
+					$ok &= Minz_Configuration::writeFile();
+				}
+			}
+
+			invalidateHttpCache();
+
+			$notif = array(
+				'type' => $ok ? 'good' : 'bad',
+				'content' => Minz_Translate::t($ok ? 'configuration_updated' : 'error_occurred')
+			);
+			Minz_Session::_param('notification', $notif);
+		}
+		Minz_Request::forward(array('c' => 'configure', 'a' => 'users'), true);
+	}
+
+	public function createAction() {
+		if (Minz_Request::isPost() && Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
+			require_once(APP_PATH . '/sql.php');
+
+			$new_user_language = Minz_Request::param('new_user_language', $this->view->conf->language);
+			if (!in_array($new_user_language, $this->view->conf->availableLanguages())) {
+				$new_user_language = $this->view->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
+
+				$ok &= !in_array(strtoupper($new_user_name), array_map('strtoupper', listUsers()));	//Not an existing user, case-insensitive
+
+				$configPath = DATA_PATH . '/' . $new_user_name . '_user.php';
+				$ok &= !file_exists($configPath);
+			}
+			if ($ok) {
+				$new_user_email = filter_var($_POST['new_user_email'], FILTER_VALIDATE_EMAIL);
+				if (empty($new_user_email)) {
+					$new_user_email = '';
+					$ok &= Minz_Configuration::authType() !== 'persona';
+				} else {
+					$personaFile = DATA_PATH . '/persona/' . $new_user_email . '.txt';
+					@unlink($personaFile);
+					$ok &= (file_put_contents($personaFile, $new_user_name) !== false);
+				}
+			}
+			if ($ok) {
+				$config_array = array(
+					'language' => $new_user_language,
+					'mail_login' => $new_user_email,
+				);
+				$ok &= (file_put_contents($configPath, "<?php\n return " . var_export($config_array, true) . ';') !== false);
+			}
+			if ($ok) {
+				$userDAO = new FreshRSS_UserDAO();
+				$ok &= $userDAO->createUser($new_user_name);
+			}
+			invalidateHttpCache();
+
+			$notif = array(
+				'type' => $ok ? 'good' : 'bad',
+				'content' => Minz_Translate::t($ok ? 'user_created' : 'error_occurred', $new_user_name)
+			);
+			Minz_Session::_param('notification', $notif);
+		}
+		Minz_Request::forward(array('c' => 'configure', 'a' => 'users'), true);
+	}
+
+	public function deleteAction() {
+		if (Minz_Request::isPost() && Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) {
+			require_once(APP_PATH . '/sql.php');
+
+			$username = Minz_Request::param('username');
+			$ok = ctype_alnum($username);
+
+			if ($ok) {
+				$ok &= (strcasecmp($username, Minz_Configuration::defaultUser()) !== 0);	//It is forbidden to delete the default user
+			}
+			if ($ok) {
+				$configPath = DATA_PATH . '/' . $username . '_user.php';
+				$ok &= file_exists($configPath);
+			}
+			if ($ok) {
+				$userDAO = new FreshRSS_UserDAO();
+				$ok &= $userDAO->deleteUser($username);
+				$ok &= unlink($configPath);
+				//TODO: delete Persona file
+			}
+			invalidateHttpCache();
+
+			$notif = array(
+				'type' => $ok ? 'good' : 'bad',
+				'content' => Minz_Translate::t($ok ? 'user_deleted' : 'error_occurred', $username)
+			);
+			Minz_Session::_param('notification', $notif);
+		}
+		Minz_Request::forward(array('c' => 'configure', 'a' => 'users'), true);
+	}
+}

+ 97 - 22
app/FreshRSS.php

@@ -1,46 +1,121 @@
 <?php
 class FreshRSS extends Minz_FrontController {
-	public function init () {
-		Minz_Session::init ('FreshRSS');
-		Minz_Translate::init ();
-
-		$this->loadParamsView ();
-		$this->loadStylesAndScripts ();
-		$this->loadNotifications ();
+	public function init() {
+		if (!isset($_SESSION)) {
+			Minz_Session::init('FreshRSS');
+		}
+		$this->accessControl(Minz_Session::param('currentUser', ''));
+		$this->loadParamsView();
+		$this->loadStylesAndScripts();	//TODO: Do not load that when not needed, e.g. some Ajax requests
+		$this->loadNotifications();
 	}
 
-	private function loadParamsView () {
+	private function accessControl($currentUser) {
+		if ($currentUser == '') {
+			switch (Minz_Configuration::authType()) {
+				case 'http_auth':
+					$currentUser = httpAuthUser();
+					$loginOk = $currentUser != '';
+					break;
+				case 'persona':
+					$loginOk = false;
+					$email = filter_var(Minz_Session::param('mail'), FILTER_VALIDATE_EMAIL);
+					if ($email != '') {	//TODO: Remove redundancy with indexController
+						$personaFile = DATA_PATH . '/persona/' . $email . '.txt';
+						if (($currentUser = @file_get_contents($personaFile)) !== false) {
+							$currentUser = trim($currentUser);
+							$loginOk = true;
+						}
+					}
+					if (!$loginOk) {
+						$currentUser = Minz_Configuration::defaultUser();
+					}
+					break;
+				case 'none':
+					$currentUser = Minz_Configuration::defaultUser();
+					$loginOk = true;
+					break;
+				default:
+					$currentUser = Minz_Configuration::defaultUser();
+					$loginOk = false;
+					break;
+			}
+		} else {
+			$loginOk = true;
+		}
+
+		if (!ctype_alnum($currentUser)) {
+			Minz_Session::_param('currentUser', '');
+			die('Invalid username [' . $currentUser . ']!');
+		}
+
 		try {
-			$this->conf = Minz_Session::param ('conf', new FreshRSS_Configuration ());
-		} catch (Minz_Exception $e) {
-			// Permission denied or conf file does not exist
-			// it's critical!
-			print $e->getMessage();
-			exit();
+			$this->conf = new FreshRSS_Configuration($currentUser);
+			Minz_View::_param ('conf', $this->conf);
+			Minz_Session::_param('currentUser', $currentUser);
+		} catch (Minz_Exception $me) {
+			$loginOk = false;
+			try {
+				$this->conf = new FreshRSS_Configuration(Minz_Configuration::defaultUser());
+				Minz_Session::_param('currentUser', Minz_Configuration::defaultUser());
+				Minz_View::_param('conf', $this->conf);
+				$notif = array(
+					'type' => 'bad',
+					'content' => 'Invalid configuration for user [' . $currentUser . ']!',
+				);
+				Minz_Session::_param ('notification', $notif);
+				Minz_Log::record ($notif['content'] . ' ' . $me->getMessage(), Minz_Log::WARNING);
+				Minz_Session::_param('currentUser', '');
+			} catch (Exception $e) {
+				die($e->getMessage());
+			}
 		}
 
-		Minz_View::_param ('conf', $this->conf);
-		Minz_Session::_param ('language', $this->conf->language ());
+		if ($loginOk) {
+			switch (Minz_Configuration::authType()) {
+				case 'http_auth':
+					$loginOk = strcasecmp($currentUser, httpAuthUser()) === 0;
+					break;
+				case 'persona':
+					$loginOk = strcasecmp(Minz_Session::param('mail'), $this->conf->mail_login) === 0;
+					break;
+				case 'none':
+					$loginOk = true;
+					break;
+				default:
+					$loginOk = false;
+					break;
+			}
+			if ((!$loginOk) && (PHP_SAPI === 'cli') && (Minz_Request::actionName() === 'actualize')) {	//Command line
+				Minz_Configuration::_authType('none');
+				$loginOk = true;
+			}
+		}
+		Minz_View::_param ('loginOk', $loginOk);
+	}
 
+	private function loadParamsView () {
+		Minz_Session::_param ('language', $this->conf->language);
+		Minz_Translate::init();
 		$output = Minz_Request::param ('output');
-		if(!$output) {
-			$output = $this->conf->viewMode();
+		if (!$output) {
+			$output = $this->conf->view_mode;
 			Minz_Request::_param ('output', $output);
 		}
 	}
 
 	private function loadStylesAndScripts () {
-		$theme = FreshRSS_Themes::get_infos($this->conf->theme());
+		$theme = FreshRSS_Themes::get_infos($this->conf->theme);
 		if ($theme) {
-			foreach($theme["files"] as $file) {
+			foreach($theme['files'] as $file) {
 				Minz_View::appendStyle (Minz_Url::display ('/themes/' . $theme['path'] . '/' . $file . '?' . @filemtime(PUBLIC_PATH . '/themes/' . $theme['path'] . '/' . $file)));
 			}
 		}
 
-		if (login_is_conf ($this->conf)) {
+		if (Minz_Configuration::authType() === 'persona') {
 			Minz_View::appendScript ('https://login.persona.org/include.js');
 		}
-		$includeLazyLoad = $this->conf->lazyload () === 'yes' && ($this->conf->displayPosts () === 'yes' || Minz_Request::param ('output') === 'reader');
+		$includeLazyLoad = $this->conf->lazyload && ($this->conf->display_posts || Minz_Request::param ('output') === 'reader');
 		Minz_View::appendScript (Minz_Url::display ('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js')), false, !$includeLazyLoad, !$includeLazyLoad);
 		if ($includeLazyLoad) {
 			Minz_View::appendScript (Minz_Url::display ('/scripts/jquery.lazyload.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.lazyload.min.js')));

+ 1 - 1
app/Models/Category.php

@@ -48,7 +48,7 @@ class FreshRSS_Category extends Minz_Model {
 		return $this->nbNotRead;
 	}
 	public function feeds () {
-		if (is_null ($this->feeds)) {
+		if ($this->feeds === null) {
 			$feedDAO = new FreshRSS_FeedDAO ();
 			$this->feeds = $feedDAO->listByCategory ($this->id ());
 			$this->nbFeed = 0;

+ 167 - 257
app/Models/Configuration.php

@@ -1,263 +1,184 @@
 <?php
 
-class FreshRSS_Configuration extends Minz_Model {
-	private $available_languages = array (
+class FreshRSS_Configuration {
+	private $filename;
+
+	private $data = array(
+		'language' => 'en',
+		'old_entries' => 3,
+		'keep_history_default' => 0,
+		'mail_login' => '',
+		'token' => '',
+		'posts_per_page' => 20,
+		'view_mode' => 'normal',
+		'default_view' => 'not_read',
+		'auto_load_more' => true,
+		'display_posts' => false,
+		'onread_jump_next' => true,
+		'lazyload' => true,
+		'sort_order' => 'DESC',
+		'anon_access' => false,
+		'mark_when' => array(
+			'article' => true,
+			'site' => true,
+			'scroll' => false,
+			'reception' => false,
+		),
+		'theme' => 'default',
+		'shortcuts' => array(
+			'mark_read' => 'r',
+			'mark_favorite' => 'f',
+			'go_website' => 'space',
+			'next_entry' => 'j',
+			'prev_entry' => 'k',
+			'collapse_entry' => 'c',
+			'load_more' => 'm',
+			'auto_share' => 's',
+		),
+		'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(
+			'shaarli' => '',
+			'poche' => '',
+			'diaspora' => '',
+			'twitter' => true,
+			'g+' => true,
+			'facebook' => true,
+			'email' => true,
+			'print' => true,
+		),
+	);
+
+	private $available_languages = array(
 		'en' => 'English',
 		'fr' => 'Français',
 	);
-	private $language;
-	private $posts_per_page;
-	private $view_mode;
-	private $default_view;
-	private $display_posts;
-	private $onread_jump_next;
-	private $lazyload;
-	private $sort_order;
-	private $old_entries;
-	private $keep_history_default;
-	private $shortcuts = array ();
-	private $mail_login = '';
-	private $mark_when = array ();
-	private $sharing = array ();
-	private $theme;
-	private $anon_access;
-	private $token;
-	private $auto_load_more;
-	private $topline_read;
-	private $topline_favorite;
-	private $topline_date;
-	private $topline_link;
-	private $bottomline_read;
-	private $bottomline_favorite;
-	private $bottomline_sharing;
-	private $bottomline_tags;
-	private $bottomline_date;
-	private $bottomline_link;
 
-	public function __construct () {
-		$confDAO = new FreshRSS_ConfigurationDAO ();
-		$this->_language ($confDAO->language);
-		$this->_postsPerPage ($confDAO->posts_per_page);
-		$this->_viewMode ($confDAO->view_mode);
-		$this->_defaultView ($confDAO->default_view);
-		$this->_displayPosts ($confDAO->display_posts);
-		$this->_onread_jump_next ($confDAO->onread_jump_next);
-		$this->_lazyload ($confDAO->lazyload);
-		$this->_sortOrder ($confDAO->sort_order);
-		$this->_oldEntries ($confDAO->old_entries);
-		$this->_keepHistoryDefault($confDAO->keep_history_default);
-		$this->_shortcuts ($confDAO->shortcuts);
-		$this->_mailLogin ($confDAO->mail_login);
-		$this->_markWhen ($confDAO->mark_when);
-		$this->_sharing ($confDAO->sharing);
-		$this->_theme ($confDAO->theme);
-		FreshRSS_Themes::setThemeId ($confDAO->theme);
-		$this->_anonAccess ($confDAO->anon_access);
-		$this->_token ($confDAO->token);
-		$this->_autoLoadMore ($confDAO->auto_load_more);
-		$this->_topline_read ($confDAO->topline_read);
-		$this->_topline_favorite ($confDAO->topline_favorite);
-		$this->_topline_date ($confDAO->topline_date);
-		$this->_topline_link ($confDAO->topline_link);
-		$this->_bottomline_read ($confDAO->bottomline_read);
-		$this->_bottomline_favorite ($confDAO->bottomline_favorite);
-		$this->_bottomline_sharing ($confDAO->bottomline_sharing);
-		$this->_bottomline_tags ($confDAO->bottomline_tags);
-		$this->_bottomline_date ($confDAO->bottomline_date);
-		$this->_bottomline_link ($confDAO->bottomline_link);
-	}
+	public function __construct ($user) {
+		$this->filename = DATA_PATH . '/' . $user . '_user.php';
 
-	public function availableLanguages () {
-		return $this->available_languages;
-	}
-	public function language () {
-		return $this->language;
-	}
-	public function postsPerPage () {
-		return $this->posts_per_page;
-	}
-	public function viewMode () {
-		return $this->view_mode;
-	}
-	public function defaultView () {
-		return $this->default_view;
-	}
-	public function displayPosts () {
-		return $this->display_posts;
-	}
-	public function onread_jump_next () {
-		return $this->onread_jump_next;
-	}
-	public function lazyload () {
-		return $this->lazyload;
-	}
-	public function sortOrder () {
-		return $this->sort_order;
-	}
-	public function oldEntries () {
-		return $this->old_entries;
-	}
-	public function keepHistoryDefault() {
-		return $this->keep_history_default;
-	}
-	public function shortcuts () {
-		return $this->shortcuts;
-	}
-	public function mailLogin () {
-		return $this->mail_login;
-	}
-	public function markWhen () {
-		return $this->mark_when;
-	}
-	public function markWhenArticle () {
-		return $this->mark_when['article'];
-	}
-	public function markWhenSite () {
-		return $this->mark_when['site'];
+		$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;
 	}
-	public function markWhenScroll () {
-		return $this->mark_when['scroll'];
+
+	public function save() {
+		@rename($this->filename, $this->filename . '.bak.php');
+		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 markUponReception () {
-		return $this->mark_when['reception'];
+
+	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 sharing ($key = false) {
+
+	public function sharing($key = false) {
 		if ($key === false) {
-			return $this->sharing;
-		} elseif (isset ($this->sharing[$key])) {
-			return $this->sharing[$key];
+			return $this->data['sharing'];
+		}
+		if (isset($this->data['sharing'][$key])) {
+			return $this->data['sharing'][$key];
 		}
 		return false;
 	}
-	public function theme () {
-		return $this->theme;
-	}
-	public function anonAccess () {
-		return $this->anon_access;
-	}
-	public function token () {
-		return $this->token;
-	}
-	public function autoLoadMore () {
-		return $this->auto_load_more;
-	}
-	public function toplineRead () {
-		return $this->topline_read;
-	}
-	public function toplineFavorite () {
-		return $this->topline_favorite;
-	}
-	public function toplineDate () {
-		return $this->topline_date;
-	}
-	public function toplineLink () {
-		return $this->topline_link;
-	}
-	public function bottomlineRead () {
-		return $this->bottomline_read;
-	}
-	public function bottomlineFavorite () {
-		return $this->bottomline_favorite;
-	}
-	public function bottomlineSharing () {
-		return $this->bottomline_sharing;
-	}
-	public function bottomlineTags () {
-		return $this->bottomline_tags;
-	}
-	public function bottomlineDate () {
-		return $this->bottomline_date;
-	}
-	public function bottomlineLink () {
-		return $this->bottomline_link;
+
+	public function availableLanguages() {
+		return $this->available_languages;
 	}
 
-	public function _language ($value) {
-		if (!isset ($this->available_languages[$value])) {
+	public function _language($value) {
+		if (!isset($this->available_languages[$value])) {
 			$value = 'en';
 		}
-		$this->language = $value;
+		$this->data['language'] = $value;
 	}
-	public function _postsPerPage ($value) {
+	public function _posts_per_page ($value) {
 		$value = intval($value);
-		$this->posts_per_page = $value > 0 ? $value : 10;
+		$this->data['posts_per_page'] = $value > 0 ? $value : 10;
 	}
-	public function _viewMode ($value) {
-		if ($value == 'global' || $value == 'reader') {
-			$this->view_mode = $value;
+	public function _view_mode ($value) {
+		if ($value === 'global' || $value === 'reader') {
+			$this->data['view_mode'] = $value;
 		} else {
-			$this->view_mode = 'normal';
+			$this->data['view_mode'] = 'normal';
 		}
 	}
-	public function _defaultView ($value) {
-		if ($value == 'not_read') {
-			$this->default_view = 'not_read';
-		} else {
-			$this->default_view = 'all';
-		}
+	public function _default_view ($value) {
+		$this->data['default_view'] = $value === 'all' ? 'all' : 'not_read';
 	}
-	public function _displayPosts ($value) {
-		if ($value == 'yes') {
-			$this->display_posts = 'yes';
-		} else {
-			$this->display_posts = 'no';
-		}
+	public function _display_posts ($value) {
+		$this->data['display_posts'] = ((bool)$value) && $value !== 'no';
 	}
 	public function _onread_jump_next ($value) {
-		if ($value == 'no') {
-			$this->onread_jump_next = 'no';
-		} else {
-			$this->onread_jump_next = 'yes';
-		}
+		$this->data['onread_jump_next'] = ((bool)$value) && $value !== 'no';
 	}
 	public function _lazyload ($value) {
-		if ($value == 'no') {
-			$this->lazyload = 'no';
-		} else {
-			$this->lazyload = 'yes';
-		}
+		$this->data['lazyload'] = ((bool)$value) && $value !== 'no';
 	}
-	public function _sortOrder ($value) {
-		$this->sort_order = $value === 'ASC' ? 'ASC' : 'DESC';
+	public function _sort_order ($value) {
+		$this->data['sort_order'] = $value === 'ASC' ? 'ASC' : 'DESC';
 	}
-	public function _oldEntries($value) {
+	public function _old_entries($value) {
 		$value = intval($value);
-		$this->old_entries = $value > 0 ? $value : 3;
+		$this->data['old_entries'] = $value > 0 ? $value : 3;
 	}
-	public function _keepHistoryDefault($value) {
+	public function _keep_history_default($value) {
 		$value = intval($value);
-		$this->keep_history_default = $value >= -1 ? $value : 0;
+		$this->data['keep_history_default'] = $value >= -1 ? $value : 0;
 	}
 	public function _shortcuts ($values) {
 		foreach ($values as $key => $value) {
-			$this->shortcuts[$key] = $value;
+			if (isset($this->data['shortcuts'][$key])) {
+				$this->data['shortcuts'][$key] = $value;
+			}
 		}
 	}
-	public function _mailLogin ($value) {
-		if (filter_var ($value, FILTER_VALIDATE_EMAIL)) {
-			$this->mail_login = $value;
-		} elseif ($value == false) {
-			$this->mail_login = false;
+	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 _markWhen ($values) {
-		if(!isset($values['article'])) {
-			$values['article'] = 'yes';
-		}
-		if(!isset($values['site'])) {
-			$values['site'] = 'yes';
-		}
-		if(!isset($values['scroll'])) {
-			$values['scroll'] = 'yes';
-		}
-		if(!isset($values['reception'])) {
-			$values['reception'] = 'no';
+	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';
+			}
 		}
-
-		$this->mark_when['article'] = $values['article'];
-		$this->mark_when['site'] = $values['site'];
-		$this->mark_when['scroll'] = $values['scroll'];
-		$this->mark_when['reception'] = $values['reception'];
 	}
 	public function _sharing ($values) {
 		$are_url = array ('shaarli', 'poche', 'diaspora');
@@ -273,61 +194,50 @@ class FreshRSS_Configuration extends Minz_Model {
 				if (!$is_url) {
 					$value = '';
 				}
-			} elseif(!is_bool ($value)) {
+			} elseif (!is_bool($value)) {
 				$value = true;
 			}
 
-			$this->sharing[$key] = $value;
+			$this->data['sharing'][$key] = $value;
 		}
 	}
-	public function _theme ($value) {
-		$this->theme = $value;
-	}
-	public function _anonAccess ($value) {
-		if ($value == 'yes') {
-			$this->anon_access = 'yes';
-		} else {
-			$this->anon_access = 'no';
-		}
+	public function _theme($value) {
+		$this->data['theme'] = $value;
 	}
-	public function _token ($value) {
-		$this->token = $value;
+	public function _token($value) {
+		$this->data['token'] = $value;
 	}
-	public function _autoLoadMore ($value) {
-		if ($value == 'yes') {
-			$this->auto_load_more = 'yes';
-		} else {
-			$this->auto_load_more = 'no';
-		}
+	public function _auto_load_more($value) {
+		$this->data['auto_load_more'] = ((bool)$value) && $value !== 'no';
 	}
-	public function _topline_read ($value) {
-		$this->topline_read = $value === 'yes';
+	public function _topline_read($value) {
+		$this->data['topline_read'] = ((bool)$value) && $value !== 'no';
 	}
-	public function _topline_favorite ($value) {
-		$this->topline_favorite = $value === 'yes';
+	public function _topline_favorite($value) {
+		$this->data['topline_favorite'] = ((bool)$value) && $value !== 'no';
 	}
-	public function _topline_date ($value) {
-		$this->topline_date = $value === 'yes';
+	public function _topline_date($value) {
+		$this->data['topline_date'] = ((bool)$value) && $value !== 'no';
 	}
-	public function _topline_link ($value) {
-		$this->topline_link = $value === 'yes';
+	public function _topline_link($value) {
+		$this->data['topline_link'] = ((bool)$value) && $value !== 'no';
 	}
-	public function _bottomline_read ($value) {
-		$this->bottomline_read = $value === 'yes';
+	public function _bottomline_read($value) {
+		$this->data['bottomline_read'] = ((bool)$value) && $value !== 'no';
 	}
-	public function _bottomline_favorite ($value) {
-		$this->bottomline_favorite = $value === 'yes';
+	public function _bottomline_favorite($value) {
+		$this->data['bottomline_favorite'] = ((bool)$value) && $value !== 'no';
 	}
-	public function _bottomline_sharing ($value) {
-		$this->bottomline_sharing = $value === 'yes';
+	public function _bottomline_sharing($value) {
+		$this->data['bottomline_sharing'] = ((bool)$value) && $value !== 'no';
 	}
-	public function _bottomline_tags ($value) {
-		$this->bottomline_tags = $value === 'yes';
+	public function _bottomline_tags($value) {
+		$this->data['bottomline_tags'] = ((bool)$value) && $value !== 'no';
 	}
-	public function _bottomline_date ($value) {
-		$this->bottomline_date = $value === 'yes';
+	public function _bottomline_date($value) {
+		$this->data['bottomline_date'] = ((bool)$value) && $value !== 'no';
 	}
-	public function _bottomline_link ($value) {
-		$this->bottomline_link = $value === 'yes';
+	public function _bottomline_link($value) {
+		$this->data['bottomline_link'] = ((bool)$value) && $value !== 'no';
 	}
 }

+ 0 - 161
app/Models/ConfigurationDAO.php

@@ -1,161 +0,0 @@
-<?php
-
-class FreshRSS_ConfigurationDAO extends Minz_ModelArray {
-	public $language = 'en';
-	public $posts_per_page = 20;
-	public $view_mode = 'normal';
-	public $default_view = 'not_read';
-	public $display_posts = 'no';
-	public $onread_jump_next = 'yes';
-	public $lazyload = 'yes';
-	public $sort_order = 'DESC';
-	public $old_entries = 3;
-	public $keep_history_default = 0;
-	public $shortcuts = array (
-		'mark_read' => 'r',
-		'mark_favorite' => 'f',
-		'go_website' => 'space',
-		'next_entry' => 'j',
-		'prev_entry' => 'k',
-		'collapse_entry' => 'c',
-		'load_more' => 'm'
-	);
-	public $mail_login = '';
-	public $mark_when = array (
-		'article' => 'yes',
-		'site' => 'yes',
-		'scroll' => 'no',
-		'reception' => 'no'
-	);
-	public $sharing = array (
-		'shaarli' => '',
-		'poche' => '',
-		'diaspora' => '',
-		'twitter' => true,
-		'g+' => true,
-		'facebook' => true,
-		'email' => true,
-		'print' => true
-	);
-	public $theme = 'default';
-	public $anon_access = 'no';
-	public $token = '';
-	public $auto_load_more = 'yes';
-	public $topline_read = 'yes';
-	public $topline_favorite = 'yes';
-	public $topline_date = 'yes';
-	public $topline_link = 'yes';
-	public $bottomline_read = 'yes';
-	public $bottomline_favorite = 'yes';
-	public $bottomline_sharing = 'yes';
-	public $bottomline_tags = 'yes';
-	public $bottomline_date = 'yes';
-	public $bottomline_link = 'yes';
-
-	public function __construct ($nameFile = '') {
-		if (empty($nameFile)) {
-			$nameFile = DATA_PATH . '/' . Minz_Configuration::currentUser () . '_user.php';
-		}
-		parent::__construct ($nameFile);
-
-		// TODO : simplifier ce code, une boucle for() devrait suffire !
-		if (isset ($this->array['language'])) {
-			$this->language = $this->array['language'];
-		}
-		if (isset ($this->array['posts_per_page'])) {
-			$this->posts_per_page = intval($this->array['posts_per_page']);
-		}
-		if (isset ($this->array['view_mode'])) {
-			$this->view_mode = $this->array['view_mode'];
-		}
-		if (isset ($this->array['default_view'])) {
-			$this->default_view = $this->array['default_view'];
-		}
-		if (isset ($this->array['display_posts'])) {
-			$this->display_posts = $this->array['display_posts'];
-		}
-		if (isset ($this->array['onread_jump_next'])) {
-			$this->onread_jump_next = $this->array['onread_jump_next'];
-		}
-		if (isset ($this->array['lazyload'])) {
-			$this->lazyload = $this->array['lazyload'];
-		}
-		if (isset ($this->array['sort_order'])) {
-			$this->sort_order = $this->array['sort_order'];
-		}
-		if (isset ($this->array['old_entries'])) {
-			$this->old_entries = intval($this->array['old_entries']);
-		}
-		if (isset ($this->array['keep_history_default'])) {
-			$this->keep_history_default = intval($this->array['keep_history_default']);
-		}
-		if (isset ($this->array['shortcuts'])) {
-			$this->shortcuts = array_merge (
-				$this->shortcuts, $this->array['shortcuts']
-			);
-		}
-		if (isset ($this->array['mail_login'])) {
-			$this->mail_login = $this->array['mail_login'];
-		}
-		if (isset ($this->array['mark_when'])) {
-			$this->mark_when = $this->array['mark_when'];
-		}
-		if (isset ($this->array['sharing'])) {
-			$this->sharing = array_merge (
-				$this->sharing, $this->array['sharing']
-			);
-		}
-		if (isset ($this->array['theme'])) {
-			$this->theme = $this->array['theme'];
-		}
-		if (isset ($this->array['anon_access'])) {
-			$this->anon_access = $this->array['anon_access'];
-		}
-		if (isset ($this->array['token'])) {
-			$this->token = $this->array['token'];
-		}
-		if (isset ($this->array['auto_load_more'])) {
-			$this->auto_load_more = $this->array['auto_load_more'];
-		}
-
-		if (isset ($this->array['topline_read'])) {
-			$this->topline_read = $this->array['topline_read'];
-		}
-		if (isset ($this->array['topline_favorite'])) {
-			$this->topline_favorite = $this->array['topline_favorite'];
-		}
-		if (isset ($this->array['topline_date'])) {
-			$this->topline_date = $this->array['topline_date'];
-		}
-		if (isset ($this->array['topline_link'])) {
-			$this->topline_link = $this->array['topline_link'];
-		}
-		if (isset ($this->array['bottomline_read'])) {
-			$this->bottomline_read = $this->array['bottomline_read'];
-		}
-		if (isset ($this->array['bottomline_favorite'])) {
-			$this->bottomline_favorite = $this->array['bottomline_favorite'];
-		}
-		if (isset ($this->array['bottomline_sharing'])) {
-			$this->bottomline_sharing = $this->array['bottomline_sharing'];
-		}
-		if (isset ($this->array['bottomline_tags'])) {
-			$this->bottomline_tags = $this->array['bottomline_tags'];
-		}
-		if (isset ($this->array['bottomline_date'])) {
-			$this->bottomline_date = $this->array['bottomline_date'];
-		}
-		if (isset ($this->array['bottomline_link'])) {
-			$this->bottomline_link = $this->array['bottomline_link'];
-		}
-	}
-
-	public function update ($values) {
-		foreach ($values as $key => $value) {
-			$this->array[$key] = $value;
-		}
-
-		$this->writeFile($this->array);
-		invalidateHttpCache();
-	}
-}

+ 1 - 5
app/Models/Entry.php

@@ -38,11 +38,7 @@ class FreshRSS_Entry extends Minz_Model {
 		return $this->title;
 	}
 	public function author () {
-		if (is_null ($this->author)) {
-			return '';
-		} else {
-			return $this->author;
-		}
+		return $this->author === null ? '' : $this->author;
 	}
 	public function content () {
 		return $this->content;

+ 11 - 21
app/Models/Feed.php

@@ -44,11 +44,7 @@ class FreshRSS_Feed extends Minz_Model {
 		return $this->category;
 	}
 	public function entries () {
-		if (!is_null ($this->entries)) {
-			return $this->entries;
-		} else {
-			return array ();
-		}
+		return $this->entries === null ? array() : $this->entries;
 	}
 	public function name () {
 		return $this->name;
@@ -140,10 +136,7 @@ class FreshRSS_Feed extends Minz_Model {
 		$this->category = $value >= 0 ? $value : 0;
 	}
 	public function _name ($value) {
-		if (is_null ($value)) {
-			$value = '';
-		}
-		$this->name = $value;
+		$this->name = $value === null ? '' : $value;
 	}
 	public function _website ($value, $validate=true) {
 		if ($validate) {
@@ -155,10 +148,7 @@ class FreshRSS_Feed extends Minz_Model {
 		$this->website = $value;
 	}
 	public function _description ($value) {
-		if (is_null ($value)) {
-			$value = '';
-		}
-		$this->description = $value;
+		$this->description = $value === null ? '' : $value;
 	}
 	public function _lastUpdate ($value) {
 		$this->lastUpdate = $value;
@@ -190,7 +180,7 @@ class FreshRSS_Feed extends Minz_Model {
 	}
 
 	public function load ($loadDetails = false) {
-		if (!is_null ($this->url)) {
+		if ($this->url !== null) {
 			if (CACHE_PATH === false) {
 				throw new Minz_FileNotExistException (
 					'CACHE_PATH',
@@ -253,7 +243,7 @@ class FreshRSS_Feed extends Minz_Model {
 
 				// si on a utilisé l'auto-discover, notre url va avoir changé
 				$subscribe_url = $feed->subscribe_url ();
-				if (!is_null ($subscribe_url) && $subscribe_url != $this->url) {
+				if ($subscribe_url !== null && $subscribe_url !== $this->url) {
 					if ($this->httpAuth != '') {
 						// on enlève les id si authentification HTTP
 						$subscribe_url = preg_replace ('#((.+)://)((.+)@)(.+)#', '${1}${5}', $subscribe_url);
@@ -263,7 +253,7 @@ class FreshRSS_Feed extends Minz_Model {
 
 				if ($loadDetails) {
 					$title = htmlspecialchars(html_only_entity_decode($feed->get_title()), ENT_COMPAT, 'UTF-8');
-					$this->_name (!is_null ($title) ? $title : $this->url);
+					$this->_name ($title === null ? $this->url : $title);
 
 					$this->_website(html_only_entity_decode($feed->get_link()));
 					$this->_description(html_only_entity_decode($feed->get_description()));
@@ -286,7 +276,7 @@ class FreshRSS_Feed extends Minz_Model {
 			// gestion des tags (catégorie == tag)
 			$tags_tmp = $item->get_categories ();
 			$tags = array ();
-			if (!is_null ($tags_tmp)) {
+			if ($tags_tmp !== null) {
 				foreach ($tags_tmp as $tag) {
 					$tags[] = html_only_entity_decode ($tag->get_label ());
 				}
@@ -308,10 +298,10 @@ class FreshRSS_Feed extends Minz_Model {
 			$entry = new FreshRSS_Entry (
 				$this->id (),
 				$item->get_id (),
-				!is_null ($title) ? $title : '',
-				!is_null ($author) ? html_only_entity_decode ($author->name) : '',
-				!is_null ($content) ? $content : '',
-				!is_null ($link) ? $link : '',
+				$title === null ? '' : $title,
+				$author === null ? '' : html_only_entity_decode ($author->name),
+				$content === null ? '' : $content,
+				$link === null ? '' : $link,
 				$date ? $date : time ()
 			);
 			$entry->_tags ($tags);

+ 18 - 14
app/Models/LogDAO.php

@@ -1,21 +1,25 @@
 <?php
 
-class FreshRSS_LogDAO extends Minz_ModelTxt {
-	public function __construct () {
-		parent::__construct (LOG_PATH . '/application.log', 'r+');
-	}
-
-	public function lister () {
+class FreshRSS_LogDAO {
+	public static function lines() {
 		$logs = array ();
-		while (($line = $this->readLine ()) !== false) {
-			if (preg_match ('/^\[([^\[]+)\] \[([^\[]+)\] --- (.*)$/', $line, $matches)) {
-				$myLog = new FreshRSS_Log ();
-				$myLog->_date ($matches[1]);
-				$myLog->_level ($matches[2]);
-				$myLog->_info ($matches[3]);
-				$logs[] = $myLog;
+		$handle = @fopen(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log', 'r');
+		if ($handle) {
+			while (($line = fgets($handle)) !== false) {
+				if (preg_match ('/^\[([^\[]+)\] \[([^\[]+)\] --- (.*)$/', $line, $matches)) {
+					$myLog = new FreshRSS_Log ();
+					$myLog->_date ($matches[1]);
+					$myLog->_level ($matches[2]);
+					$myLog->_info ($matches[3]);
+					$logs[] = $myLog;
+				}
 			}
+			fclose($handle);
 		}
-		return $logs;
+		return array_reverse($logs);
+	}
+
+	public static function truncate() {
+		file_put_contents(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log', '');
 	}
 }

+ 36 - 0
app/Models/UserDAO.php

@@ -0,0 +1,36 @@
+<?php
+
+class FreshRSS_UserDAO extends Minz_ModelPdo {
+	public function createUser($username) {
+		require_once(APP_PATH . '/sql.php');
+		$db = Minz_Configuration::dataBase();
+
+		$sql = sprintf(SQL_CREATE_TABLES, $db['prefix'] . $username . '_');
+		$stm = $this->bd->prepare($sql, array(PDO::ATTR_EMULATE_PREPARES => true));
+		$values = array(
+			'catName' => Minz_Translate::t('default_category'),
+		);
+		if ($stm && $stm->execute($values)) {
+			return true;
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+
+	public function deleteUser($username) {
+		require_once(APP_PATH . '/sql.php');
+		$db = Minz_Configuration::dataBase();
+
+		$sql = sprintf(SQL_DROP_TABLES, $db['prefix'] . $username . '_');
+		$stm = $this->bd->prepare($sql);
+		if ($stm && $stm->execute()) {
+			return true;
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+}

+ 53 - 9
app/actualize_script.php

@@ -1,15 +1,59 @@
 <?php
 require(dirname(__FILE__) . '/../constants.php');
 
-$_GET['c'] = 'feed';
-$_GET['a'] = 'actualize';
-$_GET['force'] = true;
-$_SERVER['HTTP_HOST'] = '';
+//<Mutex>
+$lock = DATA_PATH . '/actualize.lock.txt';
+if (file_exists($lock) && ((time() - @filemtime($lock)) > 3600)) {
+	@unlink($lock);
+}
+if (($handle = @fopen($lock, 'x')) === false) {
+	syslog(LOG_NOTICE, 'FreshRSS actualize already running?');
+	fwrite(STDERR, 'FreshRSS actualize already running?' . "\n");
+	return;
+}
+register_shutdown_function('unlink', $lock);
+//Could use http://php.net/function.pcntl-signal.php to catch interruptions
+@fclose($handle);
+//</Mutex>
 
 require(LIB_PATH . '/lib_rss.php');	//Includes class autoloader
 
-$front_controller = new FreshRSS ();
-$front_controller->init ();
-Minz_Session::_param('mail', true); // permet de se passer de la phase de connexion
-$front_controller->run ();
-invalidateHttpCache();
+session_cache_limiter('');
+ob_implicit_flush(false);
+ob_start();
+echo 'Results: ', "\n";	//Buffered
+
+Minz_Configuration::init();
+
+$users = listUsers();
+shuffle($users);	//Process users in random order
+array_unshift($users, Minz_Configuration::defaultUser());	//But always start with admin
+$users = array_unique($users);
+
+foreach ($users as $myUser) {
+	syslog(LOG_INFO, 'FreshRSS actualize ' . $myUser);
+	fwrite(STDOUT, 'Actualize ' . $myUser . "...\n");	//Unbuffered
+	echo $myUser, ' ';	//Buffered
+
+	$_GET['c'] = 'feed';
+	$_GET['a'] = 'actualize';
+	$_GET['ajax'] = 1;
+	$_GET['force'] = true;
+	$_SERVER['HTTP_HOST'] = '';
+
+	$freshRSS = new FreshRSS();
+	$freshRSS->_useOb(false);
+
+	Minz_Session::init('FreshRSS');
+	Minz_Session::_param('currentUser', $myUser);
+
+	$freshRSS->init();
+	$freshRSS->run();
+
+	invalidateHttpCache();
+	Minz_Session::unset_session(true);
+	Minz_ModelPdo::clean();
+}
+syslog(LOG_INFO, 'FreshRSS actualize done.');
+ob_end_flush();
+fwrite(STDOUT, 'Done.' . "\n");

+ 20 - 10
app/i18n/en.php

@@ -8,7 +8,7 @@ return array (
 	'search_short'			=> 'Search',
 
 	'configuration'			=> 'Configuration',
-	'general_and_reading'		=> 'General and reading',
+	'users'				=> 'Users',
 	'categories'			=> 'Categories',
 	'category'			=> 'Category',
 	'shortcuts'			=> 'Shortcuts',
@@ -68,7 +68,6 @@ return array (
 	'feed_updated'			=> 'Feed has been updated',
 	'rss_feed_management'		=> 'RSS feeds management',
 	'configuration_updated'		=> 'Configuration has been updated',
-	'general_and_reading_management'=> 'General and reading management',
 	'sharing_management'		=> 'Sharing options management',
 	'bad_opml_file'			=> 'Your OPML file is invalid',
 	'shortcuts_updated'		=> 'Shortcuts have been updated',
@@ -124,6 +123,7 @@ return array (
 	'next_page'			=> 'Skip to the next page',
 	'previous_page'			=> 'Skip to the previous page',
 	'collapse_article'		=> 'Collapse current article',
+	'auto_share'			=> 'Share current article',
 
 	'file_to_import'		=> 'File to import',
 	'import'			=> 'Import',
@@ -138,7 +138,7 @@ return array (
 	'articles'			=> 'articles',
 	'number_articles'		=> 'Number of articles',
 	'by_feed'			=> 'by feed',
-	'by_default'		=> 'By default',
+	'by_default'			=> 'By default',
 	'keep_history'			=> 'Minimum number of articles to keep',
 	'categorize'			=> 'Store in a category',
 	'truncate'			=> 'Delete all articles',
@@ -157,15 +157,25 @@ return array (
 	'no_selected_feed'		=> 'No feed selected.',
 	'think_to_add'			=> 'Think to add RSS feeds!',
 
-	'general_configuration'		=> 'General configuration',
-	'language'			=> 'Language',
-	'month'				=> 'months',
+	'current_user'			=> 'Current user',
 	'default_user'			=> 'Username of the default user (maximum 16 alphanumeric characters)',
-	'persona_connection_email'	=> 'Login mail address (use <a href="https://persona.org/">Mozilla Persona</a>)',
-	'allow_anonymous'		=> 'Allow anonymous reading',
+	'persona_connection_email'	=> 'Login mail address (for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)',
+	'allow_anonymous'		=> 'Allow anonymous reading for the default user (%s)',
 	'auth_token'			=> 'Authentication token',
-	'explain_token'			=> 'Allows to access RSS output without authentication.<br />%s?token=%s',
-	'login_configuration'	=> 'Login',
+	'explain_token'			=> 'Allows to access RSS output of the default user without authentication.<br /><kbd>%s?token=%s</kbd>',
+	'login_configuration'		=> 'Login',
+	'is_admin'			=> 'is administrator',
+	'auth_type'			=> 'Authentication method',
+	'auth_none'			=> 'None (dangerous)',
+	'users_list'			=> 'List of users',
+	'create_user'			=> 'Create new user',
+	'username'			=> 'Username',
+	'create'			=> 'Create',
+	'user_created'			=> 'User %s has been created',
+	'user_deleted'			=> 'User %s has been deleted',
+
+	'language'			=> 'Language',
+	'month'				=> 'months',
 	'archiving_configuration'	=> 'Archiving',
 	'delete_articles_every'	=> 'Remove articles after',
 	'purge_now'			=> 'Purge now',

+ 20 - 11
app/i18n/fr.php

@@ -8,7 +8,7 @@ return array (
 	'search_short'			=> 'Rechercher',
 
 	'configuration'			=> 'Configuration',
-	'general_and_reading'		=> 'Général et lecture',
+	'users'				=> 'Utilisateurs',
 	'categories'			=> 'Catégories',
 	'category'			=> 'Catégorie',
 	'shortcuts'			=> 'Raccourcis',
@@ -68,7 +68,6 @@ return array (
 	'feed_updated'			=> 'Le flux a été mis à jour',
 	'rss_feed_management'		=> 'Gestion des flux RSS',
 	'configuration_updated'		=> 'La configuration a été mise à jour',
-	'general_and_reading_management'=> 'Gestion générale et affichage',
 	'sharing_management'		=> 'Gestion des options de partage',
 	'bad_opml_file'			=> 'Votre fichier OPML n’est pas valide',
 	'shortcuts_updated'		=> 'Les raccourcis ont été mis à jour',
@@ -124,6 +123,7 @@ return array (
 	'next_page'			=> 'Passer à la page suivante',
 	'previous_page'			=> 'Passer à la page précédente',
 	'collapse_article'		=> 'Refermer l’article courant',
+	'auto_share'			=> 'Partager l’article courant',
 
 	'file_to_import'		=> 'Fichier à importer',
 	'import'			=> 'Importer',
@@ -138,7 +138,7 @@ return array (
 	'articles'			=> 'articles',
 	'number_articles'		=> 'Nombre d’articles',
 	'by_feed'			=> 'par flux',
-	'by_default'		=> 'Par défaut',
+	'by_default'			=> 'Par défaut',
 	'keep_history'			=> 'Nombre minimum d’articles à conserver',
 	'categorize'			=> 'Ranger dans une catégorie',
 	'truncate'			=> 'Supprimer tous les articles',
@@ -157,16 +157,25 @@ return array (
 	'no_selected_feed'		=> 'Aucun flux sélectionné.',
 	'think_to_add'			=> 'Pensez à en ajouter !',
 
-	'general_configuration'		=> 'Configuration générale',
-	'language'			=> 'Langue',
-	
-	'month'				=> 'mois',
+	'current_user'			=> 'Utilisateur actuel',
 	'default_user'			=> 'Nom de l’utilisateur par défaut (16 caractères alphanumériques maximum)',
-	'persona_connection_email'	=> 'Adresse courriel de connexion (utilise <a href="https://persona.org/">Mozilla Persona</a>)',
-	'allow_anonymous'		=> 'Autoriser la lecture anonyme',
+	'persona_connection_email'	=> 'Adresse courriel de connexion (pour <a href="https://persona.org/" rel="external">Mozilla Persona</a>)',
+	'allow_anonymous'		=> 'Autoriser la lecture anonyme pour l’utilisateur par défaut (%s)',
 	'auth_token'			=> 'Jeton d’identification',
-	'explain_token'			=> 'Permet d’accéder à la sortie RSS sans besoin de s’authentifier.<br />%s?output=rss&token=%s',
-	'login_configuration'	=> 'Identification',
+	'explain_token'			=> 'Permet d’accéder à la sortie RSS de l’utilisateur par défaut sans besoin de s’authentifier.<br /><kbd>%s?output=rss&token=%s</kbd>',
+	'login_configuration'		=> 'Identification',
+	'is_admin'			=> 'est administrateur',
+	'auth_type'			=> 'Méthode d’authentification',
+	'auth_none'			=> 'Aucune (dangereux)',
+	'users_list'			=> 'Liste des utilisateurs',
+	'create_user'			=> 'Créer un nouvel utilisateur',
+	'username'			=> 'Nom d’utilisateur',
+	'create'			=> 'Créer',
+	'user_created'			=> 'L’utilisateur %s a été créé',
+	'user_deleted'			=> 'L’utilisateur %s a été supprimé',
+
+	'language'			=> 'Langue',
+	'month'				=> 'mois',
 	'archiving_configuration'	=> 'Archivage',
 	'delete_articles_every'	=> 'Supprimer les articles après',
 	'purge_now'			=> 'Purger maintenant',

+ 6 - 1
app/i18n/install.en.php

@@ -5,6 +5,7 @@ return array (
 	'installation_step'		=> 'Installation - step %d',
 	'steps'				=> 'Steps',
 	'checks'			=> 'Checks',
+	'general_configuration'	=> 'General configuration',
 	'bdd_configuration'		=> 'Database configuration',
 	'bdd_type'		=> 'Type of database',
 	'version_update'		=> 'Update',
@@ -31,6 +32,10 @@ return array (
 	'pdomysql_is_nok'		=> 'You lack PDO or its driver for MySQL (php5-mysql package)',
 	'dom_is_ok'			=> 'You have the required library to browse the DOM',
 	'dom_is_nok'			=> 'You lack a required library to browse the DOM (php-xml package)',
+	'pcre_is_ok'			=> 'You have the required library for regular expressions (PCRE)',
+	'pcre_is_nok'			=> 'You lack a required library for regular expressions (php-pcre)',
+	'ctype_is_ok'			=> 'You have the required library for character type checking (ctype)',
+	'ctype_is_nok'			=> 'You lack a required library for character type checking (php-ctype)',
 	'cache_is_ok'			=> 'Permissions on cache directory are good',
 	'log_is_ok'			=> 'Permissions on logs directory are good',
 	'favicons_is_ok'		=> 'Permissions on favicons directory are good',
@@ -55,7 +60,7 @@ return array (
 	'update_start'			=> 'Start update process',
 	'update_long'			=> 'This can take a long time, depending on the size of your database. You may have to wait for this page to time out (~5 minutes) and then refresh this page.',
 
-	'installation_is_ok'		=> 'The installation process was successful.<br />The final step will now attempt to delete the <kbd>./public/install.php</kbd> file and any database backup created during the update process.<br />You may choose to skip this step and delete <kbd>./public/install.php</kbd> manually.',
+	'installation_is_ok'		=> 'The installation process was successful.<br />The final step will now attempt to delete the <kbd>./p/i/install.php</kbd> file and any database backup created during the update process.<br />You may choose to skip this step and delete <kbd>./p/i/install.php</kbd> manually.',
 	'finish_installation'		=> 'Complete installation',
 	'install_not_deleted'		=> 'Something went wrong; you must delete the file <em>%s</em> manually.',
 );

+ 10 - 5
app/i18n/install.fr.php

@@ -5,6 +5,7 @@ return array (
 	'installation_step'		=> 'Installation - étape %d',
 	'steps'				=> 'Étapes',
 	'checks'			=> 'Vérifications',
+	'general_configuration'	=> 'Configuration générale',
 	'bdd_configuration'		=> 'Base de données',
 	'bdd_type'		=> 'Type de base de données',
 	'version_update'		=> 'Mise à jour',
@@ -26,11 +27,15 @@ return array (
 	'minz_is_ok'			=> 'Vous disposez du framework Minz',
 	'minz_is_nok'			=> 'Vous ne disposez pas de la librairie Minz. Vous devriez exécuter le script <em>build.sh</em> ou bien <a href="https://github.com/marienfressinaud/MINZ">la télécharger sur Github</a> et installer dans le répertoire <em>%s</em> le contenu de son répertoire <em>/lib</em>.',
 	'curl_is_ok'			=> 'Vous disposez de cURL dans sa version %s',
-	'curl_is_nok'			=> 'Vous ne disposez pas de cURL (librairie php5-curl)',
-	'pdomysql_is_ok'		=> 'Vous disposez de PDO et de son driver pour MySQL (librairie php5-mysql)',
+	'curl_is_nok'			=> 'Vous ne disposez pas de cURL (paquet php5-curl)',
+	'pdomysql_is_ok'		=> 'Vous disposez de PDO et de son driver pour MySQL (paquet php5-mysql)',
 	'pdomysql_is_nok'		=> 'Vous ne disposez pas de PDO ou de son driver pour MySQL',
 	'dom_is_ok'			=> 'Vous disposez du nécessaire pour parcourir le DOM',
-	'dom_is_nok'			=> 'Vous ne disposez pas du nécessaire pour parcourir le DOM (librairie php-xml)',
+	'dom_is_nok'			=> 'Il manque une librairie pour parcourir le DOM (paquet php-xml)',
+	'pcre_is_ok'			=> 'Vous disposez du nécessaire pour les expressions régulières (PCRE)',
+	'pcre_is_nok'			=> 'Il manque une librairie pour les expressions régulières (php-pcre)',
+	'ctype_is_ok'			=> 'Vous disposez du nécessaire pour la vérification des types de caractères (ctype)',
+	'ctype_is_nok'			=> 'Il manque une librairie pour la vérification des types de caractères (php-ctype)',
 	'cache_is_ok'			=> 'Les droits sur le répertoire de cache sont bons',
 	'log_is_ok'			=> 'Les droits sur le répertoire des logs sont bons',
 	'favicons_is_ok'		=> 'Les droits sur le répertoire des favicons sont bons',
@@ -41,7 +46,7 @@ return array (
 	'general_conf_is_ok'		=> 'La configuration générale a été enregistrée.',
 	'random_string'			=> 'Chaîne aléatoire',
 	'change_value'			=> 'Vous devriez changer cette valeur par n’importe quelle autre',
-	'base_url'			=> 'Base de l’url',
+	'base_url'			=> 'Base de l’URL',
 	'do_not_change_if_doubt'	=> 'Laissez tel quel dans le doute',
 
 	'bdd_conf_is_ok'		=> 'La configuration de la base de données a été enregistrée.',
@@ -55,7 +60,7 @@ return array (
 	'update_start'			=> 'Lancer la mise à jour',
 	'update_long'			=> 'Ce processus peut prendre longtemps, selon la taille de votre base de données. Vous aurez peut-être à attendre que cette page dépasse son temps maximum d’exécution (~5 minutes) puis à la recharger.',
 
-	'installation_is_ok'		=> 'L’installation s’est bien passée.<br />La dernière étape va maintenant tenter de supprimer le fichier <kbd>/public/install.php</kbd>, ainsi que d’éventuelles copies de base de données créées durant le processus de mise à jour.<br />Vous pouvez choisir de sauter cette étape et de supprimer <kbd>/public/install.php</kbd> manuellement.',
+	'installation_is_ok'		=> 'L’installation s’est bien passée.<br />La dernière étape va maintenant tenter de supprimer le fichier <kbd>./p/i/install.php</kbd>, ainsi que d’éventuelles copies de base de données créées durant le processus de mise à jour.<br />Vous pouvez choisir de sauter cette étape et de supprimer <kbd>./p/i/install.php</kbd> manuellement.',
 	'finish_installation'		=> 'Terminer l’installation',
 	'install_not_deleted'		=> 'Quelque chose s’est mal passé, vous devez supprimer le fichier <em>%s</em> à la main.',
 );

+ 9 - 4
app/layout/aside_configure.phtml

@@ -1,8 +1,13 @@
-<div class="nav nav-list aside">
+<ul class="nav nav-list aside">
 	<li class="nav-header"><?php echo Minz_Translate::t ('configuration'); ?></li>
-
+	<li class="item<?php echo Minz_Request::actionName () == 'users' ? ' active' : ''; ?>">
+		<a href="<?php echo _url ('configure', 'users'); ?>"><?php echo Minz_Translate::t ('users'); ?></a>
+	</li>
 	<li class="item<?php echo Minz_Request::actionName () == 'display' ? ' active' : ''; ?>">
-		<a href="<?php echo _url ('configure', 'display'); ?>"><?php echo Minz_Translate::t ('general_and_reading'); ?></a>
+		<a href="<?php echo _url ('configure', 'display'); ?>"><?php echo Minz_Translate::t ('reading_configuration'); ?></a>
+	</li>
+	<li class="item<?php echo Minz_Request::actionName () == 'archiving' ? ' active' : ''; ?>">
+		<a href="<?php echo _url ('configure', 'archiving'); ?>"><?php echo Minz_Translate::t ('archiving_configuration'); ?></a>
 	</li>
 	<li class="item<?php echo Minz_Request::actionName () == 'sharing' ? ' active' : ''; ?>">
 		<a href="<?php echo _url ('configure', 'sharing'); ?>"><?php echo Minz_Translate::t ('sharing'); ?></a>
@@ -10,4 +15,4 @@
 	<li class="item<?php echo Minz_Request::actionName () == 'shortcut' ? ' active' : ''; ?>">
 		<a href="<?php echo _url ('configure', 'shortcut'); ?>"><?php echo Minz_Translate::t ('shortcuts'); ?></a>
 	</li>
-</div>
+</ul>

+ 3 - 3
app/layout/aside_flux.phtml

@@ -2,14 +2,14 @@
 	<a class="toggle_aside" href="#close"><?php echo FreshRSS_Themes::icon('close'); ?></a>
 
 	<ul class="categories">
-		<?php if (!login_is_conf ($this->conf) || is_logged ()) { ?>
+		<?php if ($this->loginOk) { ?>
 		<li>
 			<div class="stick">
 				<a class="btn btn-important" href="<?php echo _url ('configure', 'feed'); ?>"><?php echo Minz_Translate::t ('subscription_management'); ?></a>
 				<a class="btn btn-important" href="<?php echo _url ('configure', 'categorize'); ?>" title="<?php echo Minz_Translate::t ('categories_management'); ?>"><?php echo FreshRSS_Themes::icon('category-white'); ?></a>
 			</div>
 		</li>
-		<?php } elseif (login_is_conf ($this->conf)) { ?>
+		<?php } elseif (Minz_Configuration::needsLogin()) { ?>
 		<li><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Minz_Translate::t ('about_freshrss'); ?></a></li>
 		<?php } ?>
 
@@ -69,7 +69,7 @@
 		<li class="dropdown-close"><a href="#close">❌</a></li>
 		<li class="item"><a href="<?php echo _url ('index', 'index', 'get', 'f_!!!!!!'); ?>"><?php echo Minz_Translate::t ('filter'); ?></a></li>
 		<li class="item"><a target="_blank" href="http://example.net/"><?php echo Minz_Translate::t ('see_website'); ?></a></li>
-		<?php if (!login_is_conf ($this->conf) || is_logged ()) { ?>
+		<?php if ($this->loginOk) { ?>
 		<li class="separator"></li>
 		<li class="item"><a href="<?php echo _url ('configure', 'feed', 'id', '!!!!!!'); ?>"><?php echo Minz_Translate::t ('administration'); ?></a></li>
 		<li class="item"><a href="<?php echo _url ('feed', 'actualize', 'id', '!!!!!!'); ?>"><?php echo Minz_Translate::t ('actualize'); ?></a></li>

+ 11 - 14
app/layout/header.phtml

@@ -1,9 +1,9 @@
-<?php if (login_is_conf ($this->conf)) { ?>
+<?php if (Minz_Configuration::canLogIn()) { ?>
 <ul class="nav nav-head nav-login">
-	<?php if (!is_logged ()) { ?>
-	<li class="item"><?php echo FreshRSS_Themes::icon('login'); ?> <a class="signin" href="#"><?php echo Minz_Translate::t ('login'); ?></a></li>
-	<?php } else { ?>
+	<?php if ($this->loginOk) { ?>
 	<li class="item"><?php echo FreshRSS_Themes::icon('logout'); ?> <a class="signout" href="#"><?php echo Minz_Translate::t ('logout'); ?></a></li>
+	<?php } else { ?>
+	<li class="item"><?php echo FreshRSS_Themes::icon('login'); ?> <a class="signin" href="#"><?php echo Minz_Translate::t ('login'); ?></a></li>
 	<?php } ?>
 </ul>
 <?php } ?>
@@ -19,9 +19,7 @@
 	</div>
 
 	<div class="item search">
-		<?php if(!login_is_conf ($this->conf) ||
-		         is_logged() ||
-		         $this->conf->anonAccess() == 'yes') { ?>
+		<?php if ($this->loginOk || Minz_Configuration::allowAnonymous()) { ?>
 		<form action="<?php echo _url ('index', 'index'); ?>" method="get">
 			<div class="stick">
 				<?php $search = Minz_Request::param ('search', ''); ?>
@@ -48,31 +46,30 @@
 		<?php } ?>
 	</div>
 
-	<?php if (!login_is_conf ($this->conf) || is_logged ()) { ?>
+	<?php if ($this->loginOk) { ?>
 	<div class="item configure">
 		<div class="dropdown">
 			<div id="dropdown-configure" class="dropdown-target"></div>
-
 			<a class="btn dropdown-toggle" href="#dropdown-configure"><?php echo FreshRSS_Themes::icon('configure'); ?></a>
 			<ul class="dropdown-menu">
 				<li class="dropdown-close"><a href="#close">❌</a></li>
 				<li class="dropdown-header"><?php echo Minz_Translate::t ('configuration'); ?></li>
-				<li class="item"><a href="<?php echo _url ('configure', 'display'); ?>"><?php echo Minz_Translate::t ('general_and_reading'); ?></a></li>
+				<li class="item"><a href="<?php echo _url ('configure', 'users'); ?>"><?php echo Minz_Translate::t ('users'); ?></a></li>
+				<li class="item"><a href="<?php echo _url ('configure', 'display'); ?>"><?php echo Minz_Translate::t ('reading_configuration'); ?></a></li>
+				<li class="item"><a href="<?php echo _url ('configure', 'archiving'); ?>"><?php echo Minz_Translate::t ('archiving_configuration'); ?></a></li>
 				<li class="item"><a href="<?php echo _url ('configure', 'sharing'); ?>"><?php echo Minz_Translate::t ('sharing'); ?></a></li>
 				<li class="item"><a href="<?php echo _url ('configure', 'shortcut'); ?>"><?php echo Minz_Translate::t ('shortcuts'); ?></a></li>
 				<li class="separator"></li>
 				<li class="item"><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Minz_Translate::t ('about'); ?></a></li>
 				<li class="item"><a href="<?php echo _url ('index', 'logs'); ?>"><?php echo Minz_Translate::t ('logs'); ?></a></li>
-				<?php if (login_is_conf ($this->conf) && is_logged ()) { ?>
+				<?php if (Minz_Configuration::canLogIn()) { ?>
 				<li class="separator"></li>
 				<li class="item"><a class="signout" href="#"><?php echo FreshRSS_Themes::icon('logout'); ?> <?php echo Minz_Translate::t ('logout'); ?></a></li>
 				<?php } ?>
 			</ul>
 		</div>
 	</div>
-	<?php }
-
-	if (login_is_conf ($this->conf) && !is_logged ()) { ?>
+	<?php } elseif (Minz_Configuration::canLogIn()) { ?>
 	<div class="item configure">
 		<?php echo FreshRSS_Themes::icon('login'); ?> <a class="signin" href="#"><?php echo Minz_Translate::t ('login'); ?></a>
 	</div>

+ 4 - 2
app/layout/layout.phtml

@@ -1,5 +1,5 @@
 <!DOCTYPE html>
-<html lang="<?php echo $this->conf->language (); ?>" xml:lang="<?php echo $this->conf->language (); ?>">
+<html lang="<?php echo $this->conf->language; ?>" xml:lang="<?php echo $this->conf->language; ?>">
 	<head>
 		<meta charset="UTF-8" />
 		<meta name="viewport" content="initial-scale=1.0" />
@@ -16,7 +16,8 @@
 ?>
 		<link id="prefetch" rel="next prefetch" href="<?php echo Minz_Url::display (array ('c' => Minz_Request::controllerName (), 'a' => Minz_Request::actionName (), 'params' => $params)); ?>" />
 <?php } ?>
-		<link rel="icon" href="<?php echo Minz_Url::display ('/favicon.ico'); ?>" />
+		<link rel="shortcut icon" type="image/x-icon" sizes="16x16 64x64" href="<?php echo Minz_Url::display('/favicon.ico'); ?>" />
+		<link rel="icon msapplication-TileImage apple-touch-icon" type="image/png" sizes="256x256" href="<?php echo Minz_Url::display('/themes/icons/favicon-256.png'); ?>" />
 <?php if (isset ($this->rss_url)) { ?>
 		<link rel="alternate" type="application/rss+xml" title="<?php echo $this->rss_title; ?>" href="<?php echo Minz_Url::display ($this->rss_url); ?>" />
 <?php } ?>
@@ -24,6 +25,7 @@
 		<link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('non-starred', true); ?>">
 		<link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('read', true); ?>">
 		<link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('unread', true); ?>">
+		<meta name="msapplication-TileColor" content="#FFF" />
 		<meta name="robots" content="noindex,nofollow" />
 	</head>
 	<body>

+ 2 - 2
app/layout/nav_menu.phtml

@@ -1,7 +1,7 @@
 <div class="nav_menu">
 	<a class="btn toggle_aside" href="#aside_flux"><?php echo FreshRSS_Themes::icon('category'); ?></a>
 
-	<?php if (!login_is_conf ($this->conf) || is_logged ()) { ?>
+	<?php if ($this->loginOk) { ?>
 	<a id="actualize" class="btn" href="<?php echo _url ('feed', 'actualize'); ?>"><?php echo FreshRSS_Themes::icon('refresh'); ?></a>
 
 	<?php
@@ -19,7 +19,7 @@
 			$string_mark = Minz_Translate::t ('mark_cat_read');
 		}
 		$nextGet = $get;
-		if (($this->conf->onread_jump_next () === 'yes') && (strlen ($get) > 2)) {
+		if ($this->conf->onread_jump_next && (strlen ($get) > 2)) {
 			$anotherUnreadId = '';
 			$foundCurrent = false;
 			switch ($get[0]) {

+ 59 - 0
app/sql.php

@@ -0,0 +1,59 @@
+<?php
+define('SQL_CREATE_TABLES', '
+CREATE TABLE IF NOT EXISTS `%1$scategory` (
+	`id` SMALLINT NOT NULL AUTO_INCREMENT,	-- v0.7
+	`name` varchar(255) NOT NULL,
+	`color` char(7),
+	PRIMARY KEY (`id`),
+	UNIQUE KEY (`name`)	-- v0.7
+) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci
+ENGINE = INNODB;
+
+CREATE TABLE IF NOT EXISTS `%1$sfeed` (
+	`id` SMALLINT NOT NULL AUTO_INCREMENT,	-- v0.7
+	`url` varchar(511) CHARACTER SET latin1 NOT NULL,
+	`category` SMALLINT DEFAULT 0,	-- v0.7
+	`name` varchar(255) NOT NULL,
+	`website` varchar(255) CHARACTER SET latin1,
+	`description` text,
+	`lastUpdate` int(11) DEFAULT 0,
+	`priority` tinyint(2) NOT NULL DEFAULT 10,
+	`pathEntries` varchar(511) DEFAULT NULL,
+	`httpAuth` varchar(511) DEFAULT NULL,
+	`error` boolean DEFAULT 0,
+	`keep_history` MEDIUMINT NOT NULL DEFAULT -2,	-- v0.7
+	`cache_nbEntries` int DEFAULT 0,	-- v0.7
+	`cache_nbUnreads` int DEFAULT 0,	-- v0.7
+	PRIMARY KEY (`id`),
+	FOREIGN KEY (`category`) REFERENCES `%1$scategory`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
+	UNIQUE KEY (`url`),	-- v0.7
+	INDEX (`name`),	-- v0.7
+	INDEX (`priority`),	-- v0.7
+	INDEX (`keep_history`)	-- v0.7
+) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci
+ENGINE = INNODB;
+
+CREATE TABLE IF NOT EXISTS `%1$sentry` (
+	`id` bigint NOT NULL,	-- v0.7
+	`guid` varchar(760) CHARACTER SET latin1 NOT NULL,	-- Maximum for UNIQUE is 767B
+	`title` varchar(255) NOT NULL,
+	`author` varchar(255),
+	`content_bin` blob,	-- v0.7
+	`link` varchar(1023) CHARACTER SET latin1 NOT NULL,
+	`date` int(11),
+	`is_read` boolean NOT NULL DEFAULT 0,
+	`is_favorite` boolean NOT NULL DEFAULT 0,
+	`id_feed` SMALLINT,	-- v0.7
+	`tags` varchar(1023),
+	PRIMARY KEY (`id`),
+	FOREIGN KEY (`id_feed`) REFERENCES `%1$sfeed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
+	UNIQUE KEY (`id_feed`,`guid`),	-- v0.7
+	INDEX (`is_favorite`),	-- v0.7
+	INDEX (`is_read`)	-- v0.7
+) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci
+ENGINE = INNODB;
+
+INSERT INTO `%1$scategory` (name) VALUES(:catName);
+');
+
+define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory');

+ 58 - 0
app/views/configure/archiving.phtml

@@ -0,0 +1,58 @@
+<?php $this->partial('aside_configure'); ?>
+
+<div class="post">
+	<a href="<?php echo _url('index', 'index'); ?>"><?php echo Minz_Translate::t('back_to_rss_feeds'); ?></a>
+
+	<form method="post" action="<?php echo _url('configure', 'archiving'); ?>">
+		<legend><?php echo Minz_Translate::t('archiving_configuration'); ?></legend>
+		<p><?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('archiving_configuration_help'); ?></p>
+
+		<div class="form-group">
+			<label class="group-name" for="old_entries"><?php echo Minz_Translate::t('delete_articles_every'); ?></label>
+			<div class="group-controls">
+				<input type="number" id="old_entries" name="old_entries" min="1" max="1200" value="<?php echo $this->conf->old_entries; ?>" /> <?php echo Minz_Translate::t('month'); ?>
+				  <a class="btn confirm" href="<?php echo _url('entry', 'purge'); ?>"><?php echo Minz_Translate::t('purge_now'); ?></a>
+			</div>
+		</div>
+		<div class="form-group">
+			<label class="group-name" for="keep_history_default"><?php echo Minz_Translate::t('keep_history'), ' ', Minz_Translate::t('by_feed'); ?></label>
+			<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 . ($this->conf->keep_history_default == $v ? '" selected="selected' : '') . '">' . $t . ' </option>';
+					}
+				?></select> (<?php echo Minz_Translate::t('by_default'); ?>)
+			</div>
+		</div>
+
+		<div class="form-group form-actions">
+			<div class="group-controls">
+				<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button>
+				<button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button>
+			</div>
+		</div>
+	</form>
+
+	<form method="post" action="<?php echo _url('entry', 'optimize'); ?>">
+		<legend><?php echo Minz_Translate::t ('advanced'); ?></legend>
+
+		<div class="form-group">
+		<p class="group-name"><?php echo Minz_Translate::t('current_user'); ?></p>
+			<div class="group-controls">
+				<p><?php echo $this->nb_total, ' ', Minz_Translate::t('articles'), ', ', formatBytes($this->size_user); ?></p>
+				<input type="hidden" name="optimiseDatabase" value="1" />
+				<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('optimize_bdd'); ?></button>
+				<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('optimize_todo_sometimes'); ?>
+			</div>
+		</div>
+
+		<?php if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { ?>
+		<div class="form-group">
+			<p class="group-name"><?php echo Minz_Translate::t('users'); ?></p>
+			<div class="group-controls">
+				<p><?php echo formatBytes($this->size_total); ?></p>
+			</div>
+		</div>
+		<?php } ?>
+	</form>
+</div>

+ 32 - 103
app/views/configure/display.phtml

@@ -4,7 +4,7 @@
 	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
 
 	<form method="post" action="<?php echo _url ('configure', 'display'); ?>">
-		<legend><?php echo Minz_Translate::t ('general_configuration'); ?></legend>
+		<legend><?php echo Minz_Translate::t ('theme'); ?></legend>
 
 		<div class="form-group">
 			<label class="group-name" for="language"><?php echo Minz_Translate::t ('language'); ?></label>
@@ -12,7 +12,7 @@
 				<select name="language" id="language">
 				<?php $languages = $this->conf->availableLanguages (); ?>
 				<?php foreach ($languages as $short => $lib) { ?>
-				<option value="<?php echo $short; ?>"<?php echo $this->conf->language () == $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option>
+				<option value="<?php echo $short; ?>"<?php echo $this->conf->language === $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option>
 				<?php } ?>
 				</select>
 			</div>
@@ -23,7 +23,7 @@
 			<div class="group-controls">
 				<select name="theme" id="theme">
 				<?php foreach ($this->themes as $theme) { ?>
-				<option value="<?php echo $theme['path']; ?>"<?php echo $this->conf->theme () == $theme['path'] ? ' selected="selected"' : ''; ?>>
+				<option value="<?php echo $theme['path']; ?>"<?php echo $this->conf->theme === $theme['path'] ? ' selected="selected"' : ''; ?>>
 					<?php echo $theme['name'] . ' ' . Minz_Translate::t ('by') . ' ' . $theme['author']; ?> 
 				</option>
 				<?php } ?>
@@ -38,71 +38,12 @@
 			</div>
 		</div>
 
-		<legend><?php echo Minz_Translate::t ('login_configuration'); ?></legend>
-
-		<div class="form-group">
-			<label class="group-name" for="mail_login"><?php echo Minz_Translate::t ('persona_connection_email'); ?></label>
-			<?php $mail = $this->conf->mailLogin (); ?>
-			<div class="group-controls">
-				<input type="email" id="mail_login" name="mail_login" value="<?php echo $mail ? $mail : ''; ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" />
-				<noscript><b><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></b></noscript>
-				<label class="checkbox" for="anon_access">
-					<input type="checkbox" name="anon_access" id="anon_access" value="yes"<?php echo $this->conf->anonAccess () == 'yes' ? ' checked="checked"' : ''; ?> />
-					<?php echo Minz_Translate::t ('allow_anonymous'); ?>
-				</label>
-			</div>
-		</div>
-
-		<div class="form-group">
-			<label class="group-name" for="token"><?php echo Minz_Translate::t ('auth_token'); ?></label>
-			<?php $token = $this->conf->token (); ?>
-			<div class="group-controls">
-				<input type="text" id="token" name="token" value="<?php echo $token; ?>"  placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>"/>
-				<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('explain_token', Minz_Url::display(null, 'html', true), $token); ?>
-			</div>
-		</div>
-
-		<div class="form-group form-actions">
-			<div class="group-controls">
-				<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
-				<button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button>
-			</div>
-		</div>
-
-		<legend><?php echo Minz_Translate::t ('archiving_configuration'); ?></legend>
-		<p><?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('archiving_configuration_help'); ?></p>
-		
-		<div class="form-group">
-			<label class="group-name" for="old_entries"><?php echo Minz_Translate::t ('delete_articles_every'); ?></label>
-			<div class="group-controls">
-				<input type="number" id="old_entries" name="old_entries" min="1" max="1200" value="<?php echo $this->conf->oldEntries (); ?>" /> <?php echo Minz_Translate::t ('month'); ?>
-				  <a class="btn confirm" href="<?php echo _url('entry', 'purge'); ?>"><?php echo Minz_Translate::t('purge_now'); ?></a>
-			</div>
-		</div>
-		<div class="form-group">
-			<label class="group-name" for="keep_history_default"><?php echo Minz_Translate::t('keep_history'), ' ', Minz_Translate::t('by_feed'); ?> (<?php echo Minz_Translate::t('by_default'); ?>)</label>
-			<div class="group-controls">
-				<select class="number" name="keep_history_default" id="keep_history_default"><?php
-					foreach (array(-3 => '', 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 . ($this->conf->keepHistoryDefault() == $v ? '" selected="selected' : '') . '">' . $t . ' </option>';
-					}
-				?></select>
-			</div>
-		</div>
-
-		<div class="form-group form-actions">
-			<div class="group-controls">
-				<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
-				<button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button>
-			</div>
-		</div>
-
 		<legend><?php echo Minz_Translate::t ('reading_configuration'); ?></legend>
 
 		<div class="form-group">
 			<label class="group-name" for="posts_per_page"><?php echo Minz_Translate::t ('articles_per_page'); ?></label>
 			<div class="group-controls">
-				<input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo $this->conf->postsPerPage (); ?>" />
+				<input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo $this->conf->posts_per_page; ?>" />
 			</div>
 		</div>
 
@@ -110,8 +51,8 @@
 			<label class="group-name" for="sort_order"><?php echo Minz_Translate::t ('sort_order'); ?></label>
 			<div class="group-controls">
 				<select name="sort_order" id="sort_order">
-					<option value="DESC"<?php echo $this->conf->sortOrder () === 'DESC' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('newer_first'); ?></option>
-					<option value="ASC"<?php echo $this->conf->sortOrder () === 'ASC' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('older_first'); ?></option>
+					<option value="DESC"<?php echo $this->conf->sort_order === 'DESC' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('newer_first'); ?></option>
+					<option value="ASC"<?php echo $this->conf->sort_order === 'ASC' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('older_first'); ?></option>
 				</select>
 			</div>
 		</div>
@@ -120,16 +61,16 @@
 			<label class="group-name" for="view_mode"><?php echo Minz_Translate::t ('default_view'); ?></label>
 			<div class="group-controls">
 				<select name="view_mode" id="view_mode">
-					<option value="normal"<?php echo $this->conf->viewMode () == 'normal' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('normal_view'); ?></option>
-					<option value="reader"<?php echo $this->conf->viewMode () == 'reader' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('reader_view'); ?></option>
-					<option value="global"<?php echo $this->conf->viewMode () == 'global' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('global_view'); ?></option>
+					<option value="normal"<?php echo $this->conf->view_mode === 'normal' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('normal_view'); ?></option>
+					<option value="reader"<?php echo $this->conf->view_mode === 'reader' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('reader_view'); ?></option>
+					<option value="global"<?php echo $this->conf->view_mode === 'global' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('global_view'); ?></option>
 				</select>
 				<label class="radio" for="radio_all">
-					<input type="radio" name="default_view" id="radio_all" value="all"<?php echo $this->conf->defaultView () == 'all' ? ' checked="checked"' : ''; ?> />
+					<input type="radio" name="default_view" id="radio_all" value="all"<?php echo $this->conf->default_view === 'all' ? ' checked="checked"' : ''; ?> />
 					<?php echo Minz_Translate::t ('show_all_articles'); ?>
 				</label>
 				<label class="radio" for="radio_not_read">
-					<input type="radio" name="default_view" id="radio_not_read" value="not_read"<?php echo $this->conf->defaultView () == 'not_read' ? ' checked="checked"' : ''; ?> />
+					<input type="radio" name="default_view" id="radio_not_read" value="not_read"<?php echo $this->conf->default_view === 'not_read' ? ' checked="checked"' : ''; ?> />
 					<?php echo Minz_Translate::t ('show_not_reads'); ?>
 				</label>
 			</div>
@@ -138,9 +79,9 @@
 		<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="yes"<?php echo $this->conf->autoLoadMore () == 'yes' ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="auto_load_more" id="auto_load_more" value="1"<?php echo $this->conf->auto_load_more ? ' checked="checked"' : ''; ?> />
 					<?php echo Minz_Translate::t ('auto_load_more'); ?>
-					<?php echo $this->conf->displayPosts () == 'no' ? '<noscript> - <b>' . Minz_Translate::t ('javascript_should_be_activated') . '</b></noscript>' : ''; ?>
+					<noscript> - <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
 				</label>
 			</div>
 		</div>
@@ -148,9 +89,9 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<label class="checkbox" for="display_posts">
-					<input type="checkbox" name="display_posts" id="display_posts" value="yes"<?php echo $this->conf->displayPosts () == 'yes' ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="display_posts" id="display_posts" value="1"<?php echo $this->conf->display_posts ? ' checked="checked"' : ''; ?> />
 					<?php echo Minz_Translate::t ('display_articles_unfolded'); ?>
-					<?php echo $this->conf->displayPosts () == 'no' ? '<noscript> - <b>' . Minz_Translate::t ('javascript_should_be_activated') . '</b></noscript>' : ''; ?>
+					<noscript> - <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
 				</label>
 			</div>
 		</div>
@@ -158,9 +99,9 @@
 		<div class="form-group">
 			<div class="group-controls">
 				<label class="checkbox" for="lazyload">
-					<input type="checkbox" name="lazyload" id="lazyload" value="yes"<?php echo $this->conf->lazyload () == 'yes' ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="lazyload" id="lazyload" value="1"<?php echo $this->conf->lazyload ? ' checked="checked"' : ''; ?> />
 					<?php echo Minz_Translate::t ('img_with_lazyload'); ?>
-					<?php echo $this->conf->lazyload () == 'yes' ? '<noscript> - <b>' . Minz_Translate::t ('javascript_should_be_activated') . '</b></noscript>' : ''; ?>
+					<noscript> - <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript>
 				</label>
 			</div>
 		</div>
@@ -169,19 +110,19 @@
 			<label class="group-name"><?php echo Minz_Translate::t ('auto_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="yes"<?php echo $this->conf->markWhenArticle () == 'yes' ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="mark_open_article" id="check_open_article" value="1"<?php echo $this->conf->mark_when['article'] ? ' checked="checked"' : ''; ?> />
 					<?php echo Minz_Translate::t ('article_selected'); ?>
 				</label>
 				<label class="checkbox" for="check_open_site">
-					<input type="checkbox" name="mark_open_site" id="check_open_site" value="yes"<?php echo $this->conf->markWhenSite () == 'yes' ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="mark_open_site" id="check_open_site" value="1"<?php echo $this->conf->mark_when['site'] ? ' checked="checked"' : ''; ?> />
 					<?php echo Minz_Translate::t ('article_open_on_website'); ?>
 				</label>
 				<label class="checkbox" for="check_scroll">
-					<input type="checkbox" name="mark_scroll" id="check_scroll" value="yes"<?php echo $this->conf->markWhenScroll () == 'yes' ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="mark_scroll" id="check_scroll" value="1"<?php echo $this->conf->mark_when['scroll'] ? ' checked="checked"' : ''; ?> />
 					<?php echo Minz_Translate::t ('scroll'); ?>
 				</label>
 				<label class="checkbox" for="check_reception">
-					<input type="checkbox" name="mark_upon_reception" id="check_reception" value="yes"<?php echo $this->conf->markUponReception () == 'yes' ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="mark_upon_reception" id="check_reception" value="1"<?php echo $this->conf->mark_when['reception'] ? ' checked="checked"' : ''; ?> />
 					<?php echo Minz_Translate::t ('upon_reception'); ?>
 				</label>
 			</div>
@@ -191,7 +132,7 @@
 			<label class="group-name"><?php echo Minz_Translate::t ('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="yes"<?php echo $this->conf->onread_jump_next () == 'yes' ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="onread_jump_next" id="onread_jump_next" value="1"<?php echo $this->conf->onread_jump_next ? ' checked="checked"' : ''; ?> />
 					<?php echo Minz_Translate::t ('jump_next'); ?>
 				</label>
 			</div>
@@ -221,20 +162,20 @@
 				<tbody>
 					<tr>
 						<th><?php echo Minz_Translate::t ('top_line'); ?></th>
-						<td><input type="checkbox" name="topline_read" value="yes"<?php echo $this->conf->toplineRead () ? ' checked="checked"' : ''; ?> /></td>
-						<td><input type="checkbox" name="topline_favorite" value="yes"<?php echo $this->conf->toplineFavorite () ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="topline_read" value="1"<?php echo $this->conf->topline_read ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="topline_favorite" value="1"<?php echo $this->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="yes"<?php echo $this->conf->toplineDate () ? ' checked="checked"' : ''; ?> /></td>
-						<td><input type="checkbox" name="topline_link" value="yes"<?php echo $this->conf->toplineLink () ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="topline_date" value="1"<?php echo $this->conf->topline_date ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="topline_link" value="1"<?php echo $this->conf->topline_link ? ' checked="checked"' : ''; ?> /></td>
 					</tr><tr>
 						<th><?php echo Minz_Translate::t ('bottom_line'); ?></th>
-						<td><input type="checkbox" name="bottomline_read" value="yes"<?php echo $this->conf->bottomlineRead () ? ' checked="checked"' : ''; ?> /></td>
-						<td><input type="checkbox" name="bottomline_favorite" value="yes"<?php echo $this->conf->bottomlineFavorite () ? ' checked="checked"' : ''; ?> /></td>
-						<td><input type="checkbox" name="bottomline_sharing" value="yes"<?php echo $this->conf->bottomlineSharing () ? ' checked="checked"' : ''; ?> /></td>
-						<td><input type="checkbox" name="bottomline_tags" value="yes"<?php echo $this->conf->bottomlineTags () ? ' checked="checked"' : ''; ?> /></td>
-						<td><input type="checkbox" name="bottomline_date" value="yes"<?php echo $this->conf->bottomlineDate () ? ' checked="checked"' : ''; ?> /></td>
-						<td><input type="checkbox" name="bottomline_link" value="yes"<?php echo $this->conf->bottomlineLink () ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="bottomline_read" value="1"<?php echo $this->conf->bottomline_read ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="bottomline_favorite" value="1"<?php echo $this->conf->bottomline_favorite ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="bottomline_sharing" value="1"<?php echo $this->conf->bottomline_sharing ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="bottomline_tags" value="1"<?php echo $this->conf->bottomline_tags ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="bottomline_date" value="1"<?php echo $this->conf->bottomline_date ? ' checked="checked"' : ''; ?> /></td>
+						<td><input type="checkbox" name="bottomline_link" value="1"<?php echo $this->conf->bottomline_link ? ' checked="checked"' : ''; ?> /></td>
 					</tr>
 				</tbody>
 			</table><br />
@@ -246,17 +187,5 @@
 				<button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button>
 			</div>
 		</div>
-
-		<legend><?php echo Minz_Translate::t ('advanced'); ?></legend>
-		<div class="form-group">
-			<label class="group-name"></label>
-			<div class="group-controls">
-				<p><?php echo $this->nb_total; ?> <?php echo Minz_Translate::t('articles') ?>, <?php echo formatBytes($this->size_total); ?>.</p>
-				<p><a class="btn" href="<?php echo _url('entry', 'optimize'); ?>">
-					<?php echo Minz_Translate::t('optimize_bdd'); ?>
-				</a></p>
-				<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('optimize_todo_sometimes'); ?>
-			</div>
-		</div>
 	</form>
 </div>

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

@@ -87,8 +87,8 @@
 		<div class="form-group">
 			<label class="group-name" for="keep_history"><?php echo Minz_Translate::t ('keep_history'); ?></label>
 			<div class="group-controls">
-				<select class="number" name="keep_history" id="keep_history"><?php
-					foreach (array(-3 => '', -2 => Minz_Translate::t('by_default'), 0 => '0', 10 => '10', 50 => '50', 100 => '100', 500 => '500', 1000 => '1 000', 5000 => '5 000', 10000 => '10 000', -1 => '∞') as $v => $t) {
+				<select class="number" name="keep_history" id="keep_history" required="required"><?php
+					foreach (array('' => '', -2 => Minz_Translate::t('by_default'), 0 => '0', 10 => '10', 50 => '50', 100 => '100', 500 => '500', 1000 => '1 000', 5000 => '5 000', 10000 => '10 000', -1 => '∞') as $v => $t) {
 						echo '<option value="' . $v . ($this->flux->keepHistory() === $v ? '" selected="selected' : '') . '">' . $t . '</option>';
 					}
 				?></select>

+ 5 - 2
app/views/configure/importExport.phtml

@@ -1,5 +1,8 @@
-<?php if ($this->req == 'export') { ?>
-<?php echo '<?xml version="1.0" encoding="UTF-8" ?>'; // résout bug sur certain serveur ?>
+<?php
+require_once(LIB_PATH . '/lib_opml.php');
+if ($this->req == 'export') {
+	echo '<?xml version="1.0" encoding="UTF-8" ?>';
+?>
 <!-- Generated by <?php echo Minz_Configuration::title (); ?> -->
 <opml version="2.0">
 	<head>

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

@@ -47,7 +47,7 @@
 					foreach ($services as $service) {
 				?>
 				<label class="checkbox" for="<?php echo $service; ?>">
-					<input type="checkbox" name="<?php echo $service; ?>" id="<?php echo $service; ?>" value="yes"<?php echo $this->conf->sharing ($service) ? ' checked="checked"' : ''; ?> />
+					<input type="checkbox" name="<?php echo $service; ?>" id="<?php echo $service; ?>" value="1"<?php echo $this->conf->sharing($service) ? ' checked="checked"' : ''; ?> />
 					<?php echo Minz_Translate::t ($service); ?>
 				</label>
 				<?php } ?>

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

@@ -9,7 +9,7 @@
 		<?php } ?>
 	</datalist>
 
-	<?php $s = $this->conf->shortcuts (); ?>
+	<?php $s = $this->conf->shortcuts; ?>
 
 	<form method="post" action="<?php echo _url ('configure', 'shortcut'); ?>">
 		<legend><?php echo Minz_Translate::t ('shortcuts_management'); ?></legend>
@@ -68,6 +68,13 @@
 			</div>
 		</div>
 
+		<div class="form-group">
+			<label class="group-name" for="auto_share_shortcut"><?php echo Minz_Translate::t ('auto_share'); ?></label>
+			<div class="group-controls">
+				<input type="text" id="auto_share_shortcut" name="shortcuts[auto_share]" list="keys" value="<?php echo $s['auto_share']; ?>" />
+			</div>
+		</div>
+
 		<div class="form-group form-actions">
 			<div class="group-controls">
 				<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>

+ 154 - 0
app/views/configure/users.phtml

@@ -0,0 +1,154 @@
+<?php $this->partial('aside_configure'); ?>
+
+<div class="post">
+	<a href="<?php echo _url('index', 'index'); ?>"><?php echo Minz_Translate::t('back_to_rss_feeds'); ?></a>
+
+	<form method="post" action="<?php echo _url('users', 'auth'); ?>">
+		<legend><?php echo Minz_Translate::t('login_configuration'); ?></legend>
+
+		<div class="form-group">
+			<label class="group-name" for="current_user"><?php echo Minz_Translate::t('current_user'); ?></label>
+			<div class="group-controls">
+				<input id="current_user" type="text" disabled="disabled" value="<?php echo Minz_Session::param('currentUser', '_'); ?>" />
+				<label class="checkbox" for="is_admin">
+					<input type="checkbox" id="is_admin" disabled="disabled" <?php echo Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_')) ? 'checked="checked" ' : ''; ?>/>
+					<?php echo Minz_Translate::t('is_admin'); ?>
+				</label>
+			</div>
+		</div>
+
+		<div class="form-group">
+			<label class="group-name" for="mail_login"><?php echo Minz_Translate::t('persona_connection_email'); ?></label>
+			<?php $mail = $this->conf->mail_login; ?>
+			<div class="group-controls">
+				<input type="email" id="mail_login" name="mail_login" value="<?php echo $mail; ?>" <?php echo Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_')) ? '' : 'disabled="disabled"'; ?> placeholder="alice@example.net" />
+				<noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript>
+			</div>
+		</div>
+
+		<?php if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { ?>
+		<div class="form-group form-actions">
+			<div class="group-controls">
+				<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button>
+				<button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button>
+			</div>
+		</div>
+		<?php } ?>
+
+	<?php if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { ?>
+
+		<legend><?php echo Minz_Translate::t('auth_type'); ?></legend>
+
+		<div class="form-group">
+			<label class="group-name" for="auth_type"><?php echo Minz_Translate::t('auth_type'); ?></label>
+			<div class="group-controls">
+				<select id="auth_type" name="auth_type" required="required">
+					<option value=""></option>
+					<option value="none"<?php echo Minz_Configuration::authType() === 'none' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t('auth_none'); ?></option>
+					<option value="http_auth"<?php echo Minz_Configuration::authType() === 'http_auth' ? ' selected="selected"' : '', httpAuthUser() == '' ? ' disabled="disabled"' : ''; ?>>HTTP Auth</option>
+					<option value="persona"<?php echo Minz_Configuration::authType() === 'persona' ? ' selected="selected"' : '', $this->conf->mail_login == '' ? ' disabled="disabled"' : ''; ?>>Mozilla Persona</option>
+				</select>
+				<code>$_SERVER['REMOTE_USER'] = `<?php echo httpAuthUser(); ?>`</code>
+			</div>
+		</div>
+
+		<div class="form-group form-actions">
+			<div class="group-controls">
+				<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button>
+				<button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button>
+			</div>
+		</div>
+
+		<?php if (Minz_Configuration::authType() === 'persona') { ?>
+
+		<legend>Mozilla Persona</legend>
+		<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"' : ''; ?> />
+					<?php echo Minz_Translate::t('allow_anonymous', Minz_Configuration::defaultUser()); ?>
+				</label>
+			</div>
+		</div>
+
+		<div class="form-group">
+			<label class="group-name" for="token"><?php echo Minz_Translate::t('auth_token'); ?></label>
+			<?php $token = $this->conf->token; ?>
+			<div class="group-controls">
+				<input type="text" id="token" name="token" value="<?php echo $token; ?>"  placeholder="<?php echo Minz_Translate::t('blank_to_disable'); ?>"/>
+				<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('explain_token', Minz_Url::display(null, 'html', true), $token); ?>
+			</div>
+		</div>
+
+		<div class="form-group form-actions">
+			<div class="group-controls">
+				<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button>
+				<button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button>
+			</div>
+		</div>
+
+		<?php } ?>
+	</form>
+
+	<form method="post" action="<?php echo _url('users', 'delete'); ?>">
+		<legend><?php echo Minz_Translate::t('users'); ?></legend>
+
+		<div class="form-group">
+			<label class="group-name" for="users_list"><?php echo Minz_Translate::t('users_list'); ?></label>
+			<div class="group-controls">
+				<select id="users_list" name="username"><?php
+					foreach (listUsers() as $user) {
+						echo '<option>', $user, '</option>';
+					}
+				?></select>
+			</div>
+		</div>
+	
+		<div class="form-group form-actions">
+			<div class="group-controls">
+				<button type="submit" class="btn btn-attention confirm"><?php echo Minz_Translate::t('delete'); ?></button>
+			</div>
+		</div>
+	</form>
+
+	<form method="post" action="<?php echo _url('users', 'create'); ?>">
+		<legend><?php echo Minz_Translate::t('create_user'); ?></legend>
+
+		<div class="form-group">
+			<label class="group-name" for="new_user_language"><?php echo Minz_Translate::t ('language'); ?></label>
+			<div class="group-controls">
+				<select name="new_user_language" id="new_user_language">
+				<?php $languages = $this->conf->availableLanguages (); ?>
+				<?php foreach ($languages as $short => $lib) { ?>
+				<option value="<?php echo $short; ?>"<?php echo $this->conf->language === $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option>
+				<?php } ?>
+				</select>
+			</div>
+		</div>
+
+		<div class="form-group">
+			<label class="group-name" for="new_user_name"><?php echo Minz_Translate::t('username'); ?></label>
+			<div class="group-controls">
+				<input id="new_user_name" name="new_user_name" type="text" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" placeholder="demo" />
+			</div>
+		</div>
+
+		<div class="form-group">
+			<label class="group-name" for="new_user_email"><?php echo Minz_Translate::t('persona_connection_email'); ?></label>
+			<?php $mail = $this->conf->mail_login; ?>
+			<div class="group-controls">
+				<input type="email" id="new_user_email" name="new_user_email" placeholder="alice@example.net" />
+			</div>
+		</div>
+
+		<div class="form-group form-actions">
+			<div class="group-controls">
+				<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('create'); ?></button>
+				<button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button>
+			</div>
+		</div>
+
+	</form>
+
+	<?php } ?>
+</div>

+ 1 - 1
app/views/feed/actualize.phtml

@@ -1 +1 @@
-OK
+OK

+ 12 - 11
app/views/helpers/javascript_vars.phtml

@@ -1,16 +1,16 @@
 <?php
 	echo '"use strict";', "\n";
-	$mark = $this->conf->markWhen ();
+	$mark = $this->conf->mark_when;
 	echo 'var ',
-		'hide_posts=', ($this->conf->displayPosts () === 'yes' || Minz_Request::param ('output') === 'reader') ? 'false' : 'true',
-		',auto_mark_article=', $mark['article'] === 'yes' ? 'true' : 'false',
-		',auto_mark_site=', $mark['site'] === 'yes' ? 'true' : 'false',
-		',auto_mark_scroll=', $mark['scroll'] === 'yes' ? 'true' : 'false',
-		',auto_load_more=', $this->conf->autoLoadMore () === 'yes' ? 'true' : 'false',
-		',full_lazyload=', $this->conf->lazyload () === 'yes' && ($this->conf->displayPosts () === 'yes' || Minz_Request::param ('output') === 'reader') ? 'true' : 'false',
-		',does_lazyload=', $this->conf->lazyload() === 'yes' ? 'true' : 'false';
+		'hide_posts=', ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'false' : 'true',
+		',auto_mark_article=', $mark['article'] ? 'true' : 'false',
+		',auto_mark_site=', $mark['site'] ? 'true' : 'false',
+		',auto_mark_scroll=', $mark['scroll'] ? 'true' : 'false',
+		',auto_load_more=', $this->conf->auto_load_more ? 'true' : 'false',
+		',full_lazyload=', $this->conf->lazyload && ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'true' : 'false',
+		',does_lazyload=', $this->conf->lazyload ? 'true' : 'false';
 
-	$s = $this->conf->shortcuts ();
+	$s = $this->conf->shortcuts;
 	echo ',shortcuts={',
 			'mark_read:"', $s['mark_read'], '",',
 			'mark_favorite:"', $s['mark_favorite'], '",',
@@ -18,7 +18,8 @@
 			'prev_entry:"', $s['prev_entry'], '",',
 			'next_entry:"', $s['next_entry'], '",',
 			'collapse_entry:"', $s['collapse_entry'], '",',
-			'load_more:"', $s['load_more'], '"',
+			'load_more:"', $s['load_more'], '",',
+			'auto_share:"', $s['auto_share'], '"',
 		"},\n";
 
 	if (Minz_Request::param ('output') === 'global') {
@@ -29,7 +30,7 @@
 	if ($mail != 'null') {
 		$mail = '"' . $mail . '"';
 	}
-	echo 'use_persona=', login_is_conf ($this->conf) ? 'true' : 'false',
+	echo 'use_persona=', Minz_Configuration::authType() === 'persona' ? 'true' : 'false',
 		',url_freshrss="', _url ('index', 'index'), '",',
 		'url_login="', _url ('index', 'login'), '",',
 		'url_logout="', _url ('index', 'logout'), '",',

+ 2 - 2
app/views/helpers/view/global_view.phtml

@@ -31,6 +31,6 @@
 </div>
 
 <div id="overlay"></div>
-<div id="panel"<?php echo $this->conf->displayPosts () === 'no' ? ' class="hide_posts"' : ''; ?>>
+<div id="panel"<?php echo $this->conf->display_posts ? '' : ' class="hide_posts"'; ?>>
 	<a class="close" href="#"><?php echo FreshRSS_Themes::icon('close'); ?></a>
-</div>
+</div>

+ 41 - 29
app/views/helpers/view/normal_view.phtml

@@ -7,39 +7,54 @@ if (!empty($this->entries)) {
 	$display_today = true;
 	$display_yesterday = true;
 	$display_others = true;
-
-	$logged = !login_is_conf ($this->conf) || is_logged ();
-	$shaarli = $this->conf->sharing ('shaarli');
-	$poche = $this->conf->sharing ('poche');
-	$diaspora = $this->conf->sharing ('diaspora');
+	if ($this->loginOk) {
+		$shaarli = $this->conf->sharing ('shaarli');
+		$poche = $this->conf->sharing ('poche');
+		$diaspora = $this->conf->sharing ('diaspora');
+	} else {
+		$shaarli = '';
+		$poche = '';
+		$diaspora = '';
+	}
 	$twitter = $this->conf->sharing ('twitter');
 	$google_plus = $this->conf->sharing ('g+');
 	$facebook = $this->conf->sharing ('facebook');
 	$email = $this->conf->sharing ('email');
 	$print = $this->conf->sharing ('print');
-	$today = $this->today;
-	$hidePosts = $this->conf->displayPosts() === 'no';
-	$lazyload = $this->conf->lazyload() === 'yes';
+	$hidePosts = !$this->conf->display_posts;
+	$lazyload = $this->conf->lazyload;
+	$topline_read = $this->conf->topline_read;
+	$topline_favorite = $this->conf->topline_favorite;
+	$topline_date = $this->conf->topline_date;
+	$topline_link = $this->conf->topline_link;
+	$bottomline_read = $this->conf->bottomline_read;
+	$bottomline_favorite = $this->conf->bottomline_favorite;
+	$bottomline_sharing = $this->conf->bottomline_sharing && (
+		$shaarli || $poche || $diaspora || $twitter ||
+		$google_plus || $facebook || $email || $print);
+	$bottomline_tags = $this->conf->bottomline_tags;
+	$bottomline_date = $this->conf->bottomline_date;
+	$bottomline_link = $this->conf->bottomline_link;
 ?>
 
 <div id="stream" class="normal<?php echo $hidePosts ? ' hide_posts' : ''; ?>">
 	<?php foreach ($this->entries as $item) { ?>
 
-	<?php if ($display_today && $item->isDay (FreshRSS_Days::TODAY, $today)) { ?>
+	<?php if ($display_today && $item->isDay (FreshRSS_Days::TODAY, $this->today)) { ?>
 	<div class="day" id="day_today">
 		<?php echo Minz_Translate::t ('today'); ?>
 		<span class="date"> - <?php echo timestamptodate (time (), false); ?></span>
 		<span class="name"><?php echo $this->currentName; ?></span>
 	</div>
 	<?php $display_today = false; } ?>
-	<?php if ($display_yesterday && $item->isDay (FreshRSS_Days::YESTERDAY, $today)) { ?>
+	<?php if ($display_yesterday && $item->isDay (FreshRSS_Days::YESTERDAY, $this->today)) { ?>
 	<div class="day" id="day_yesterday">
 		<?php echo Minz_Translate::t ('yesterday'); ?>
 		<span class="date"> - <?php echo timestamptodate (time () - 86400, false); ?></span>
 		<span class="name"><?php echo $this->currentName; ?></span>
 	</div>
 	<?php $display_yesterday = false; } ?>
-	<?php if ($display_others && $item->isDay (FreshRSS_Days::BEFORE_YESTERDAY, $today)) { ?>
+	<?php if ($display_others && $item->isDay (FreshRSS_Days::BEFORE_YESTERDAY, $this->today)) { ?>
 	<div class="day" id="day_before_yesterday">
 		<?php echo Minz_Translate::t ('before_yesterday'); ?>
 		<span class="name"><?php echo $this->currentName; ?></span>
@@ -48,14 +63,14 @@ if (!empty($this->entries)) {
 
 	<div class="flux<?php echo !$item->isRead () ? ' not_read' : ''; ?><?php echo $item->isFavorite () ? ' favorite' : ''; ?>" id="flux_<?php echo $item->id (); ?>">
 		<ul class="horizontal-list flux_header"><?php
-			if ($logged) {
-				if ($this->conf->toplineRead ()) {
+			if ($this->loginOk) {
+				if ($topline_read) {
 					?><li class="item manage"><?php
 						?><a class="read" href="<?php echo _url ('entry', 'read', 'id', $item->id (), 'is_read', $item->isRead () ? 0 : 1); ?>"><?php
 							echo FreshRSS_Themes::icon($item->isRead () ? 'read' : 'unread'); ?></a><?php
 					?></li><?php
 				}
-				if ($this->conf->toplineFavorite ()) {
+				if ($topline_favorite) {
 					 ?><li class="item manage"><?php
 						?><a class="bookmark" href="<?php echo _url ('entry', 'bookmark', 'id', $item->id (), 'is_favorite', $item->isFavorite () ? 0 : 1); ?>"><?php
 							echo FreshRSS_Themes::icon($item->isFavorite () ? 'starred' : 'non-starred'); ?></a><?php
@@ -67,8 +82,8 @@ if (!empty($this->entries)) {
 			?>
 			<li class="item website"><a href="<?php echo _url ('index', 'index', 'get', 'f_' . $feed->id ()); ?>"><img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span></a></li>
 			<li class="item title"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo $item->title (); ?></a></li>
-			<?php if ($this->conf->toplineDate ()) { ?><li class="item date"><?php echo $item->date (); ?> </li><?php } ?>
-			<?php if ($this->conf->toplineLink ()) { ?><li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a></li><?php } ?>
+			<?php if ($topline_date) { ?><li class="item date"><?php echo $item->date (); ?> </li><?php } ?>
+			<?php if ($topline_link) { ?><li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a></li><?php } ?>
 		</ul>
 
 		<div class="flux_content">
@@ -85,14 +100,14 @@ if (!empty($this->entries)) {
 				?>
 			</div>
 			<ul class="horizontal-list bottom"><?php
-				if ($logged) {
-					if ($this->conf->bottomlineRead ()) {
+				if ($this->loginOk) {
+					if ($bottomline_read) {
 						?><li class="item manage"><?php
 							?><a class="read" href="<?php echo _url ('entry', 'read', 'id', $item->id (), 'is_read', $item->isRead () ? 0 : 1); ?>"><?php
 								echo FreshRSS_Themes::icon($item->isRead () ? 'read' : 'unread'); ?></a><?php
 						?></li><?php
 					}
-					if ($this->conf->bottomlineFavorite ()) {
+					if ($bottomline_favorite) {
 						?><li class="item manage"><?php
 							?><a class="bookmark" href="<?php echo _url ('entry', 'bookmark', 'id', $item->id (), 'is_favorite', $item->isFavorite () ? 0 : 1); ?>"><?php
 								echo FreshRSS_Themes::icon($item->isFavorite () ? 'starred' : 'non-starred'); ?></a><?php
@@ -101,10 +116,7 @@ if (!empty($this->entries)) {
 				} ?>
 				<li class="item">
 					<?php
-						if ($this->conf->bottomlineSharing () && (
-								$shaarli || $poche || $diaspora || $twitter ||
-								$google_plus || $facebook || $email
-							)) {
+						if ($bottomline_sharing) {
 							$link = urlencode ($item->link ());
 							$title = urlencode ($item->title () . ' - ' . $feed->name ());
 					?>
@@ -117,19 +129,19 @@ if (!empty($this->entries)) {
 
 						<ul class="dropdown-menu">
 							<li class="dropdown-close"><a href="#close">❌</a></li>
-							<?php if ($logged && $shaarli) { ?>
+							<?php if ($shaarli) { ?>
 							<li class="item">
 								<a target="_blank" href="<?php echo $shaarli . '?post=' . $link . '&amp;title=' . $title . '&amp;source=FreshRSS'; ?>">
 									<?php echo Minz_Translate::t ('shaarli'); ?>
 								</a>
 							</li>
-							<?php } if ($logged && $poche) { ?>
+							<?php } if ($poche) { ?>
 							<li class="item">
 								<a target="_blank" href="<?php echo $poche . '?action=add&amp;url=' . base64_encode (urldecode($link)); ?>">
 									<?php echo Minz_Translate::t ('poche'); ?>
 								</a>
 							</li>
-							<?php } if ($logged && $diaspora) { ?>
+							<?php } if ($diaspora) { ?>
 							<li class="item">
 								<a target="_blank" href="<?php echo $diaspora . '/bookmarklet?url=' . $link . '&amp;title=' . $title; ?>">
 									<?php echo Minz_Translate::t ('diaspora'); ?>
@@ -171,7 +183,7 @@ if (!empty($this->entries)) {
 					<?php } ?>
 				</li>
 				<?php
-					$tags = $this->conf->bottomlineTags () ? $item->tags() : null;
+					$tags = $bottomline_tags ? $item->tags() : null;
 					if (!empty($tags)) {
 				?>
 				<li class="item">
@@ -190,8 +202,8 @@ if (!empty($this->entries)) {
 					</div>
 				</li>
 				<?php } ?>
-				<?php if ($this->conf->bottomlineDate ()) { ?><li class="item date"><?php echo $item->date (); ?> </li><?php } ?>
-				<?php if ($this->conf->bottomlineLink ()) { ?><li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a></li><?php } ?>
+				<?php if ($bottomline_date) { ?><li class="item date"><?php echo $item->date (); ?> </li><?php } ?>
+				<?php if ($bottomline_link) { ?><li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a></li><?php } ?>
 			</ul>
 		</div>
 	</div>

+ 1 - 1
app/views/helpers/view/reader_view.phtml

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

+ 27 - 18
app/views/index/index.phtml

@@ -1,29 +1,38 @@
 <?php
 
+function showForbidden() {
+?><div class="post content">
+	<h1><?php echo Minz_Translate::t ('forbidden_access'); ?></h1>
+	<p><?php echo Minz_Configuration::canLogIn() ?
+		Minz_Translate::t ('forbidden_access_description') :
+		Minz_Translate::t ('forbidden_access') . ' (' . Minz_Configuration::authType() . ')'; ?></p>
+	<p><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Minz_Translate::t ('about_freshrss'); ?></a></p>
+</div><?php
+}
+
 $output = Minz_Request::param ('output', 'normal');
-$token = $this->conf->token();
-$token_param = Minz_Request::param ('token', '');
-$token_is_ok = ($token != '' && $token == $token_param);
 
-if(!login_is_conf ($this->conf) ||
-   is_logged() ||
-   $this->conf->anonAccess() == 'yes' ||
-   ($output == 'rss' && $token_is_ok)) {
-	if($output == 'rss') {
+if ($this->loginOk || Minz_Configuration::allowAnonymous()) {
+	if ($output === 'normal') {
+		$this->renderHelper ('view/normal_view');
+	} elseif ($output === 'rss') {
 		$this->renderHelper ('view/rss_view');
-	} elseif($output == 'reader') {
+	} elseif ($output === 'reader') {
 		$this->renderHelper ('view/reader_view');
-	} elseif($output == 'global') {
+	} elseif ($output === 'global') {
 		$this->renderHelper ('view/global_view');
 	} else {
 		$this->renderHelper ('view/normal_view');
 	}
+} elseif ($output === 'rss') {
+	$token = $this->conf->token;
+	$token_param = Minz_Request::param ('token', '');
+	$token_is_ok = ($token != '' && $token == $token_param);
+	if ($token_is_ok) {
+		$this->renderHelper ('view/rss_view');
+	} else {
+		showForbidden();
+	}
 } else {
-?>
-<div class="post content">
-	<h1><?php echo Minz_Translate::t ('forbidden_access'); ?></h1>
-	<p><?php echo Minz_Translate::t ('forbidden_access_description'); ?></p>
-	<p><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Minz_Translate::t ('about_freshrss'); ?></a></p>
-</div>
-<?php
-}
+	showForbidden();
+}

+ 18 - 14
app/views/javascript/actualize.phtml

@@ -1,37 +1,41 @@
-var feeds = new Array ();
+"use strict";
+var feeds = [];
 <?php foreach ($this->feeds as $feed) { ?>
-feeds.push ("<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'actualize', 'params' => array ('id' => $feed->id (), 'ajax' => '1')), 'php'); ?>");
+feeds.push("<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'actualize', 'params' => array ('id' => $feed->id (), 'ajax' => '1')), 'php'); ?>");
 <?php } ?>
 
-function initProgressBar (init) {
+function initProgressBar(init) {
 	if (init) {
-		$("body").after ("\<div id=\"actualizeProgress\" class=\"actualizeProgress\">\
+		$("body").after("\<div id=\"actualizeProgress\" class=\"actualizeProgress\">\
 			<?php echo Minz_Translate::t ('refresh'); ?> <span class=\"progress\">0 / " + feeds.length + "</span><br />\
 			<progress id=\"actualizeProgressBar\" value=\"0\" max=\"" + feeds.length + "\"></progress>\
 		</div>");
 	} else {
-		window.location.reload ();
+		window.location.reload();
 	}
 }
-function updateProgressBar (i) {
+function updateProgressBar(i) {
 	$("#actualizeProgressBar").val(i);
-	$("#actualizeProgress .progress").html (i + " / " + feeds.length);
+	$("#actualizeProgress .progress").html(i + " / " + feeds.length);
 }
 
-function updateFeeds () {
-	initProgressBar (true);
+function updateFeeds() {
+	if (feeds.length === 0) {
+		return;
+	}
+	initProgressBar(true);
 
 	var i = 0;
 	for (var f in feeds) {
-		$.ajax ({
+		$.ajax({
 			type: 'POST',
 			url: feeds[f],
-		}).done (function (data) {
+		}).done(function (data) {
 			i++;
-			updateProgressBar (i);
+			updateProgressBar(i);
 
-			if (i == feeds.length) {
-				initProgressBar (false);
+			if (i === feeds.length) {
+				initProgressBar(false);
 			}
 		});
 	}

+ 1 - 1
constants.php

@@ -1,5 +1,5 @@
 <?php
-define('FRESHRSS_VERSION', '0.7-beta3');
+define('FRESHRSS_VERSION', '0.8-dev');
 define('FRESHRSS_WEBSITE', 'http://freshrss.org');
 
 // Constantes de chemins

+ 3 - 1
data/.gitignore

@@ -3,4 +3,6 @@ config.php
 *_user.php
 *.sqlite
 touch.txt
-no-cache.txt
+no-cache.txt
+*.bak.php
+*.lock.txt

+ 13 - 0
data/cache/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">
+<head>
+<meta charset="UTF-8" />
+<meta http-equiv="Refresh" content="0; url=/" />
+<title>Redirection</title>
+<meta name="robots" content="noindex" />
+</head>
+
+<body>
+<p><a href="/">Redirection</a></p>
+</body>
+</html>

+ 13 - 0
data/favicons/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">
+<head>
+<meta charset="UTF-8" />
+<meta http-equiv="Refresh" content="0; url=/" />
+<title>Redirection</title>
+<meta name="robots" content="noindex" />
+</head>
+
+<body>
+<p><a href="/">Redirection</a></p>
+</body>
+</html>

+ 13 - 0
data/log/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">
+<head>
+<meta charset="UTF-8" />
+<meta http-equiv="Refresh" content="0; url=/" />
+<title>Redirection</title>
+<meta name="robots" content="noindex" />
+</head>
+
+<body>
+<p><a href="/">Redirection</a></p>
+</body>
+</html>

+ 1 - 0
data/persona/.gitignore

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

+ 13 - 0
data/persona/index.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">
+<head>
+<meta charset="UTF-8" />
+<meta http-equiv="Refresh" content="0; url=/" />
+<title>Redirection</title>
+<meta name="robots" content="noindex" />
+</head>
+
+<body>
+<p><a href="/">Redirection</a></p>
+</body>
+</html>

+ 3 - 3
index.html

@@ -2,12 +2,12 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">
 <head>
 <meta charset="UTF-8" />
-<meta http-equiv="Refresh" content="0; url=p/i/" />
+<meta http-equiv="Refresh" content="0; url=p/" />
 <title>Redirection</title>
-<meta name="robots" content="noindex" />
+<meta name="robots" content="noindex,nofollow" />
 </head>
 
 <body>
-<p><a href="p/i/">FreshRSS</a></p>
+<p><a href="p/">FreshRSS</a></p>
 </body>
 </html>

+ 1 - 1
index.php

@@ -1,3 +1,3 @@
 <?php
-header('Location: p/i/', true, 301);
+header('Location: p/', true, 301);
 include('index.html');

+ 110 - 49
lib/Minz/Configuration.php

@@ -28,7 +28,7 @@ class Minz_Configuration {
 
 	/**
 	 * définition des variables de configuration
-	 * $sel_application une chaîne de caractères aléatoires (obligatoire)
+	 * $salt une chaîne de caractères aléatoires (obligatoire)
 	 * $environment gère le niveau d'affichage pour log et erreurs
 	 * $use_url_rewriting indique si on utilise l'url_rewriting
 	 * $base_url le chemin de base pour accéder à l'application
@@ -42,7 +42,7 @@ class Minz_Configuration {
 	 *     - password mot de passe de l'utilisateur
 	 *     - base le nom de la base de données
 	 */
-	private static $sel_application = '';
+	private static $salt = '';
 	private static $environment = Minz_Configuration::PRODUCTION;
 	private static $base_url = '';
 	private static $use_url_rewriting = false;
@@ -51,20 +51,23 @@ class Minz_Configuration {
 	private static $cache_enabled = false;
 	private static $delay_cache = 3600;
 	private static $default_user = '';
-	private static $current_user = '';
+	private static $allow_anonymous = false;
+	private static $auth_type = 'none';
 
 	private static $db = array (
-		'host' => false,
-		'user' => false,
-		'password' => false,
-		'base' => false
+		'type' => 'mysql',
+		'host' => '',
+		'user' => '',
+		'password' => '',
+		'base' => '',
+		'prefix' => '',
 	);
 
 	/*
 	 * Getteurs
 	 */
 	public static function salt () {
-		return self::$sel_application;
+		return self::$salt;
 	}
 	public static function environment () {
 		return self::$environment;
@@ -76,7 +79,7 @@ class Minz_Configuration {
 		return self::$use_url_rewriting;
 	}
 	public static function title () {
-		return stripslashes(self::$title);
+		return self::$title;
 	}
 	public static function language () {
 		return self::$language;
@@ -93,8 +96,34 @@ class Minz_Configuration {
 	public static function defaultUser () {
 		return self::$default_user;
 	}
-	public static function currentUser () {
-		return self::$current_user;
+	public static function isAdmin($currentUser) {
+		return $currentUser === self::$default_user;
+	}
+	public static function allowAnonymous() {
+		return self::$allow_anonymous;
+	}
+	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 === 'persona';
+	}
+
+	public static function _allowAnonymous($allow = false) {
+		self::$allow_anonymous = (bool)$allow;
+	}
+	public static function _authType($value) {
+		$value = strtolower($value);
+		switch ($value) {
+			case 'http_auth':
+			case 'persona':
+			case 'none':
+				self::$auth_type = $value;
+				break;
+		}
 	}
 
 	/**
@@ -113,22 +142,37 @@ class Minz_Configuration {
 		}
 	}
 
+	public static function writeFile() {
+		$ini_array = array(
+			'general' => array(
+				'environment' => self::$environment,
+				'use_url_rewriting' => self::$use_url_rewriting,
+				'salt' => self::$salt,
+				'base_url' => self::$base_url,
+				'title' => self::$title,
+				'default_user' => self::$default_user,
+				'allow_anonymous' => self::$allow_anonymous,
+				'auth_type' => self::$auth_type,
+			),
+			'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
+		}
+		return (bool)$result;
+	}
+
 	/**
-	 * Parse un fichier de configuration de type ".ini"
-	 * @exception Minz_FileNotExistException si le CONF_PATH_NAME n'existe pas
+	 * 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é
 	 */
 	private static function parseFile () {
-		if (!file_exists (DATA_PATH . self::CONF_PATH_NAME)) {
-			throw new Minz_FileNotExistException (
-				DATA_PATH . self::CONF_PATH_NAME,
-				Minz_Exception::ERROR
-			);
-		}
-
 		$ini_array = include(DATA_PATH . self::CONF_PATH_NAME);
 
-		if (!$ini_array) {
+		if (!is_array($ini_array)) {
 			throw new Minz_PermissionDeniedException (
 				DATA_PATH . self::CONF_PATH_NAME,
 				Minz_Exception::ERROR
@@ -144,23 +188,30 @@ class Minz_Configuration {
 		}
 		$general = $ini_array['general'];
 
-		// sel_application est obligatoire
-		if (!isset ($general['sel_application'])) {
-			throw new Minz_BadConfigurationException (
-				'sel_application',
-				Minz_Exception::ERROR
-			);
+		// 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
+				);
+			}
 		}
-		self::$sel_application = $general['sel_application'];
+		self::$salt = $general['salt'];
 
 		if (isset ($general['environment'])) {
 			switch ($general['environment']) {
+			case Minz_Configuration::SILENT:
 			case 'silent':
 				self::$environment = Minz_Configuration::SILENT;
 				break;
+			case Minz_Configuration::DEVELOPMENT:
 			case 'development':
 				self::$environment = Minz_Configuration::DEVELOPMENT;
 				break;
+			case Minz_Configuration::PRODUCTION:
 			case 'production':
 				self::$environment = Minz_Configuration::PRODUCTION;
 				break;
@@ -195,26 +246,28 @@ class Minz_Configuration {
 			}
 		}
 		if (isset ($general['delay_cache'])) {
-			self::$delay_cache = $general['delay_cache'];
+			self::$delay_cache = inval($general['delay_cache']);
 		}
 		if (isset ($general['default_user'])) {
 			self::$default_user = $general['default_user'];
-			self::$current_user = self::$default_user;
+		}
+		if (isset ($general['allow_anonymous'])) {
+			self::$allow_anonymous = ((bool)($general['allow_anonymous'])) && ($general['allow_anonymous'] !== 'no');
+		}
+		if (isset ($general['auth_type'])) {
+			self::_authType($general['auth_type']);
 		}
 
 		// Base de données
-		$db = false;
 		if (isset ($ini_array['db'])) {
 			$db = $ini_array['db'];
-		}
-		if ($db) {
-			if (!isset ($db['host'])) {
+			if (empty($db['host'])) {
 				throw new Minz_BadConfigurationException (
 					'host',
 					Minz_Exception::ERROR
 				);
 			}
-			if (!isset ($db['user'])) {
+			if (empty($db['user'])) {
 				throw new Minz_BadConfigurationException (
 					'user',
 					Minz_Exception::ERROR
@@ -226,33 +279,41 @@ class Minz_Configuration {
 					Minz_Exception::ERROR
 				);
 			}
-			if (!isset ($db['base'])) {
+			if (empty($db['base'])) {
 				throw new Minz_BadConfigurationException (
 					'base',
 					Minz_Exception::ERROR
 				);
 			}
 
-			self::$db['type'] = isset ($db['type']) ? $db['type'] : 'mysql';
+			if (!empty($db['type'])) {
+				self::$db['type'] = $db['type'];
+			}
 			self::$db['host'] = $db['host'];
 			self::$db['user'] = $db['user'];
 			self::$db['password'] = $db['password'];
 			self::$db['base'] = $db['base'];
-			self::$db['prefix'] = isset ($db['prefix']) ? $db['prefix'] : '';
+			if (isset($db['prefix'])) {
+				self::$db['prefix'] = $db['prefix'];
+			}
 		}
 	}
 
-	private static function setReporting () {
-		if (self::environment () == self::DEVELOPMENT) {
-			error_reporting (E_ALL);
-			ini_set ('display_errors','On');
-			ini_set('log_errors', 'On');
-		} elseif (self::environment () == self::PRODUCTION) {
-			error_reporting(E_ALL);
-			ini_set('display_errors','Off');
-			ini_set('log_errors', 'On');
-		} else {
-			error_reporting(0);
+	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;
 		}
 	}
 }

+ 18 - 7
lib/Minz/Dispatcher.php

@@ -22,7 +22,7 @@ class Minz_Dispatcher {
 	 * Récupère l'instance du Dispatcher
 	 */
 	public static function getInstance ($router) {
-		if (is_null (self::$instance)) {
+		if (self::$instance === null) {
 			self::$instance = new Minz_Dispatcher ($router);
 		}
 		return self::$instance;
@@ -40,19 +40,26 @@ class Minz_Dispatcher {
 	 * Remplit le body de Response à partir de la Vue
 	 * @exception Minz_Exception
 	 */
-	public function run () {
+	public function run ($ob = true) {
 		$cache = new Minz_Cache();
 		// Le ob_start est dupliqué : sans ça il y a un bug sous Firefox
 		// ici on l'appelle avec 'ob_gzhandler', après sans.
 		// Vraisemblablement la compression fonctionne mais c'est sale
 		// J'ignore les effets de bord :(
-		ob_start ('ob_gzhandler');
+		if ($ob) {
+			ob_start ('ob_gzhandler');
+		}
 
 		if (Minz_Cache::isEnabled () && !$cache->expired ()) {
-			ob_start ();
+			if ($ob) {
+				ob_start ();
+			}
 			$cache->render ();
-			$text = ob_get_clean();
+			if ($ob) {
+				$text = ob_get_clean();
+			}
 		} else {
+			$text = '';	//TODO: Clean this code
 			while (Minz_Request::$reseted) {
 				Minz_Request::$reseted = false;
 
@@ -67,9 +74,13 @@ class Minz_Dispatcher {
 					$this->controller->lastAction ();
 
 					if (!Minz_Request::$reseted) {
-						ob_start ();
+						if ($ob) {
+							ob_start ();
+						}
 						$this->controller->view ()->build ();
-						$text = ob_get_clean();
+						if ($ob) {
+							$text = ob_get_clean();
+						}
 					}
 				} catch (Minz_Exception $e) {
 					throw $e;

+ 1 - 1
lib/Minz/FileNotExistException.php

@@ -1,7 +1,7 @@
 <?php
 class Minz_FileNotExistException extends Minz_Exception {
 	public function __construct ($file_name, $code = self::ERROR) {
-		$message = 'File doesn\'t exist : `' . $file_name.'`';
+		$message = 'File not found: `' . $file_name.'`';
 		
 		parent::__construct ($message, $code);
 	}

+ 14 - 1
lib/Minz/FrontController.php

@@ -26,6 +26,8 @@ class Minz_FrontController {
 	protected $dispatcher;
 	protected $router;
 
+	private $useOb = true;
+
 	/**
 	 * Constructeur
 	 * Initialise le router et le dispatcher
@@ -61,7 +63,7 @@ class Minz_FrontController {
 	 */
 	public function run () {
 		try {
-			$this->dispatcher->run ();
+			$this->dispatcher->run ($this->useOb);
 			Minz_Response::send ();
 		} catch (Minz_Exception $e) {
 			try {
@@ -94,4 +96,15 @@ class Minz_FrontController {
 		}
 		exit ('### Application problem ###<br />'."\n".$txt);
 	}
+
+	public function useOb() {
+		return $this->useOb;
+	}
+
+	/**
+	 * Use ob_start('ob_gzhandler') or not.
+	 */
+	public function _useOb($ob) {
+		return $this->useOb = (bool)$ob;
+	}
 }

+ 13 - 24
lib/Minz/Log.php

@@ -1,5 +1,5 @@
 <?php
-/** 
+/**
  * MINZ - Copyright 2011 Marien Fressinaud
  * Sous licence AGPL3 <http://www.gnu.org/licenses/>
 */
@@ -19,7 +19,7 @@ class Minz_Log {
 	const WARNING = 4;
 	const NOTICE = 8;
 	const DEBUG = 16;
-	
+
 	/**
 	 * Enregistre un message dans un fichier de log spécifique
 	 * Message non loggué si
@@ -32,14 +32,14 @@ class Minz_Log {
 	 */
 	public static function record ($information, $level, $file_name = null) {
 		$env = Minz_Configuration::environment ();
-		
+
 		if (! ($env === Minz_Configuration::SILENT
 		       || ($env === Minz_Configuration::PRODUCTION
 		       && ($level >= Minz_Log::NOTICE)))) {
-			if (is_null ($file_name)) {
-				$file_name = LOG_PATH . '/application.log';
+			if ($file_name === null) {
+				$file_name = LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log';
 			}
-			
+
 			switch ($level) {
 			case Minz_Log::ERROR :
 				$level_label = 'error';
@@ -56,24 +56,13 @@ class Minz_Log {
 			default :
 				$level_label = 'unknown';
 			}
-			
-			if ($env == Minz_Configuration::PRODUCTION) {
-				$file = @fopen ($file_name, 'a');
-			} else {
-				$file = fopen ($file_name, 'a');
-			}
-			
-			if ($file !== false) {
-				$log = '[' . date('r') . ']';
-				$log .= ' [' . $level_label . ']';
-				$log .= ' --- ' . $information . "\n";
-				fwrite ($file, $log); 
-				fclose ($file);
-			} else {
-				throw new Minz_PermissionDeniedException (
-					$file_name,
-					Minz_Exception::ERROR
-				);
+
+			$log = '[' . date('r') . ']'
+			     . ' [' . $level_label . ']'
+			     . ' --- ' . $information . "\n";
+
+			if (file_put_contents($file_name, $log, FILE_APPEND | LOCK_EX) === false) {
+				throw new Minz_PermissionDeniedException($file_name, Minz_Exception::ERROR);
 			}
 		}
 	}

+ 53 - 94
lib/Minz/ModelArray.php

@@ -1,5 +1,5 @@
 <?php
-/** 
+/**
  * MINZ - Copyright 2011 Marien Fressinaud
  * Sous licence AGPL3 <http://www.gnu.org/licenses/>
 */
@@ -7,116 +7,75 @@
 /**
  * La classe Model_array représente le modèle interragissant avec les fichiers de type texte gérant des tableaux php
  */
-class Minz_ModelArray extends Minz_ModelTxt {
+class Minz_ModelArray {
 	/**
-	 * $array Le tableau php contenu dans le fichier $nameFile
+	 * $filename est le nom du fichier
 	 */
-	protected $array = array ();
-	
+	protected $filename;
+
 	/**
-	 * Ouvre le fichier indiqué, charge le tableau dans $array et le $nameFile
-	 * @param $nameFile le nom du fichier à ouvrir contenant un tableau
+	 * Ouvre le fichier indiqué, charge le tableau dans $array et le $filename
+	 * @param $filename le nom du fichier à ouvrir contenant un tableau
 	 * Remarque : $array sera obligatoirement un tableau
 	 */
-	public function __construct ($nameFile) {
-		parent::__construct ($nameFile);
-		
-		if (!$this->getLock ('read')) {
-			throw new Minz_PermissionDeniedException ($this->filename);
+	public function __construct ($filename) {
+		$this->filename = $filename;
+	}
+
+	protected function loadArray() {
+		if (!file_exists($this->filename)) {
+			throw new Minz_FileNotExistException($this->filename, Minz_Exception::WARNING);
+		}
+		elseif (($handle = $this->getLock()) === false) {
+			throw new Minz_PermissionDeniedException($this->filename);
 		} else {
-			$this->array = include ($this->filename);
-			$this->releaseLock ();
-		
-			if (!is_array ($this->array)) {
-				$this->array = array ();
+			$data = include($this->filename);
+			$this->releaseLock($handle);
+
+			if ($data === false) {
+				throw new Minz_PermissionDeniedException($this->filename);
+			} elseif (!is_array($data)) {
+				$data = array();
 			}
-		
-			$this->array = $this->decodeArray ($this->array);
+			return $data;
 		}
-	}	
-	
+	}
+
 	/**
-	 * Écrit un tableau dans le fichier $nameFile
-	 * @param $array le tableau php à enregistrer
+	 * Sauve le tableau $array dans le fichier $filename
 	 **/
-	public function writeFile ($array) {
-		if (!$this->getLock ('write')) {
-			throw new Minz_PermissionDeniedException ($this->namefile);
-		} else {
-			$this->erase ();
-		
-			$this->writeLine ('<?php');
-			$this->writeLine ('return ', false);
-			$this->writeArray ($array);
-			$this->writeLine (';');
-		
-			$this->releaseLock ();
-		}
-	}
-	
-	private function writeArray ($array, $profondeur = 0) {
-		$tab = '';
-		for ($i = 0; $i < $profondeur; $i++) {
-			$tab .= "\t";
-		}
-		$this->writeLine ('array (');
-		
-		foreach ($array as $key => $value) {
-			if (is_int ($key)) {
-				$this->writeLine ($tab . "\t" . $key . ' => ', false);
-			} else {
-				$this->writeLine ($tab . "\t" . '\'' . $key . '\'' . ' => ', false);
-			}
-			
-			if (is_array ($value)) {
-				$this->writeArray ($value, $profondeur + 1);
-				$this->writeLine (',');
-			} else {
-				if (is_numeric ($value)) {
-					$this->writeLine ($value . ',');
-				} else {
-					$this->writeLine ('\'' . addslashes ($value) . '\',');
-				}
-			}
+	protected function writeArray($array) {
+		if (file_put_contents($this->filename, "<?php\n return " . var_export($array, true) . ';', LOCK_EX) === false) {
+			throw new Minz_PermissionDeniedException($this->filename);
 		}
-		
-		$this->writeLine ($tab . ')', false);
-	}
-	
-	private function decodeArray ($array) {
-		$new_array = array ();
-		
-		foreach ($array as $key => $value) {
-			if (is_array ($value)) {
-				$new_array[$key] = $this->decodeArray ($value);
-			} else {
-				$new_array[$key] = stripslashes ($value);
-			}
+		if (function_exists('opcache_invalidate')) {
+			opcache_invalidate($this->filename);	//Clear PHP 5.5+ cache for include
 		}
-		
-		return $new_array;
+		return true;
 	}
-	
-	private function getLock ($type) {
-		if ($type == 'write') {
-			$lock = LOCK_EX;
-		} else {
-			$lock = LOCK_SH;
+
+	private function getLock() {
+		$handle = fopen($this->filename, 'r');
+		if ($handle === false) {
+			return false;
 		}
-	
-		$count = 1;
-		while (!flock ($this->file, $lock) && $count <= 50) {
-			$count++;
+
+		$count = 50;
+		while (!flock($handle, LOCK_SH) && $count > 0) {
+			$count--;
+			usleep(1000);
 		}
-		
-		if ($count >= 50) {
-			return false;
+
+		if ($count > 0) {
+			return $handle;
 		} else {
-			return true;
+			fclose($handle);
+			return false;
 		}
 	}
-	
-	private function releaseLock () {
-		flock ($this->file, LOCK_UN);
+
+	private function releaseLock($handle) {
+		flock($handle, LOCK_UN);
+		fclose($handle);
 	}
 }

+ 14 - 6
lib/Minz/ModelPdo.php

@@ -1,5 +1,5 @@
 <?php
-/** 
+/**
  * MINZ - Copyright 2011 Marien Fressinaud
  * Sous licence AGPL3 <http://www.gnu.org/licenses/>
 */
@@ -23,7 +23,7 @@ class Minz_ModelPdo {
 	protected $bd;
 
 	protected $prefix;
-	
+
 	/**
 	 * Créé la connexion à la base de données à l'aide des variables
 	 * HOST, BASE, USER et PASS définies dans le fichier de configuration
@@ -60,8 +60,7 @@ class Minz_ModelPdo {
 			);
 			self::$sharedBd = $this->bd;
 
-			$userPrefix = Minz_Configuration::currentUser ();
-			$this->prefix = $db['prefix'] . (empty($userPrefix) ? '' : ($userPrefix . '_'));
+			$this->prefix = $db['prefix'] . Minz_Session::param('currentUser', '_') . '_';
 			self::$sharedPrefix = $this->prefix;
 		} catch (Exception $e) {
 			throw new Minz_PDOConnectionException (
@@ -81,15 +80,24 @@ class Minz_ModelPdo {
 		$this->bd->rollBack();
 	}
 
-	public function size() {
+	public function size($all = false) {
 		$db = Minz_Configuration::dataBase ();
 		$sql = 'SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema = ?';
-		$stm = $this->bd->prepare ($sql);
 		$values = array ($db['base']);
+		if (!$all) {
+			$sql .= ' AND table_name LIKE ?';
+			$values[] = $this->prefix . '%';
+		}
+		$stm = $this->bd->prepare ($sql);
 		$stm->execute ($values);
 		$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
 		return $res[0];
 	}
+
+	public static function clean() {
+		self::$sharedBd = null;
+		self::$sharedPrefix = '';
+	}
 }
 
 class FreshPDO extends PDO {

+ 0 - 84
lib/Minz/ModelTxt.php

@@ -1,84 +0,0 @@
-<?php
-/** 
- * MINZ - Copyright 2011 Marien Fressinaud
- * Sous licence AGPL3 <http://www.gnu.org/licenses/>
-*/
-
-/**
- * La classe Model_txt représente le modèle interragissant avec les fichiers de type texte
- */
-class Minz_ModelTxt {
-	/**
-	 * $file représente le fichier à ouvrir
-	 */
-	protected $file;
-	
-	/**
-	 * $filename est le nom du fichier
-	 */
-	protected $filename;
-	
-	/**
-	 * Ouvre un fichier dans $file
-	 * @param $nameFile nom du fichier à ouvrir
-	 * @param $mode mode d'ouverture du fichier ('a+' par défaut)
-	 * @exception FileNotExistException si le fichier n'existe pas
-	 *          > ou ne peux pas être ouvert
-	 */
-	public function __construct ($nameFile, $mode = 'a+') {
-		$this->filename = $nameFile;
-		if (!file_exists($this->filename)) {
-			throw new Minz_FileNotExistException (
-				$this->filename,
-				Minz_Exception::WARNING
-			);
-		}
-
-		$this->file = @fopen ($this->filename, $mode);
-		
-		if (!$this->file) {
-			throw new Minz_PermissionDeniedException (
-				$this->filename,
-				Minz_Exception::WARNING
-			);
-		}
-	}
-	
-	/**
-	 * Lit une ligne de $file
-	 * @return une ligne du fichier
-	 */
-	public function readLine () {
-		return fgets ($this->file);
-	}
-	
-	/**
-	 * Écrit une ligne dans $file
-	 * @param $line la ligne à écrire
-	 */
-	public function writeLine ($line, $newLine = true) {
-		$char = '';
-		if ($newLine) {
-			$char = "\n";
-		}
-		
-		fwrite ($this->file, $line . $char);
-	}
-	
-	/**
-	 * Efface le fichier $file
-	 * @return true en cas de succès, false sinon
-	 */
-	public function erase () {
-		return ftruncate ($this->file, 0);
-	}
-	
-	/**
-	 * Ferme $file
-	 */
-	public function __destruct () {
-		if (isset ($this->file)) {
-			fclose ($this->file);
-		}
-	}
-}

+ 8 - 1
lib/Minz/Request.php

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

+ 4 - 14
lib/Minz/Session.php

@@ -8,7 +8,7 @@ class Minz_Session {
 	/**
 	 * $session stocke les variables de session
 	 */
-	private static $session = array ();
+	private static $session = array ();	//TODO: Try to avoid having another local copy
 
 	/**
 	 * Initialise la session, avec un nom
@@ -33,13 +33,7 @@ class Minz_Session {
 	 * @return la valeur de la variable de session, false si n'existe pas
 	 */
 	public static function param ($p, $default = false) {
-		if (isset (self::$session[$p])) {
-			$return = self::$session[$p];
-		} else {
-			$return = $default;
-		}
-
-		return $return;
+		return isset(self::$session[$p]) ? self::$session[$p] : $default;
 	}
 
 
@@ -55,11 +49,6 @@ class Minz_Session {
 		} else {
 			$_SESSION[$p] = $v;
 			self::$session[$p] = $v;
-
-			if($p == 'language') {
-				// reset pour remettre à jour le fichier de langue à utiliser
-				Minz_Translate::reset ();
-			}
 		}
 	}
 
@@ -71,11 +60,12 @@ class Minz_Session {
 	public static function unset_session ($force = false) {
 		$language = self::param ('language');
 
-		session_unset ();
+		session_destroy();
 		self::$session = array ();
 
 		if (!$force) {
 			self::_param ('language', $language);
+			Minz_Translate::reset ();
 		}
 	}
 }

+ 10 - 19
lib/Minz/View.php

@@ -13,7 +13,7 @@ class Minz_View {
 	const LAYOUT_FILENAME = '/layout.phtml';
 
 	private $view_filename = '';
-	private $use_layout = false;
+	private $use_layout = null;
 
 	private static $title = '';
 	private static $styles = array ();
@@ -31,12 +31,6 @@ class Minz_View {
 		                     . Minz_Request::controllerName () . '/'
 		                     . Minz_Request::actionName () . '.phtml';
 
-		if (file_exists (APP_PATH
-		               . self::LAYOUT_PATH_NAME
-		               . self::LAYOUT_FILENAME)) {
-			$this->use_layout = true;
-		}
-
 		self::$title = Minz_Configuration::title ();
 	}
 
@@ -44,6 +38,9 @@ class Minz_View {
 	 * Construit la vue
 	 */
 	public function build () {
+		if ($this->use_layout === null) {	//TODO: avoid file_exists and require views to be explicit
+			$this->use_layout = file_exists (APP_PATH . self::LAYOUT_PATH_NAME . self::LAYOUT_FILENAME);
+		}
 		if ($this->use_layout) {
 			$this->buildLayout ();
 		} else {
@@ -66,10 +63,8 @@ class Minz_View {
 	 * Affiche la Vue en elle-même
 	 */
 	public function render () {
-		if (file_exists ($this->view_filename)) {
-			include ($this->view_filename);
-		} else {
-			Minz_Log::record ('File doesn\'t exist : `'
+		if ((@include($this->view_filename)) === false) {
+			Minz_Log::record ('File not found: `'
 			            . $this->view_filename . '`',
 			            Minz_Log::NOTICE);
 		}
@@ -84,10 +79,8 @@ class Minz_View {
 		             . self::LAYOUT_PATH_NAME . '/'
 		             . $part . '.phtml';
 
-		if (file_exists ($fic_partial)) {
-			include ($fic_partial);
-		} else {
-			Minz_Log::record ('File doesn\'t exist : `'
+		if ((@include($fic_partial)) === false) {
+			Minz_Log::record ('File not found: `'
 			            . $fic_partial . '`',
 			            Minz_Log::WARNING);
 		}
@@ -102,10 +95,8 @@ class Minz_View {
 		            . '/views/helpers/'
 		            . $helper . '.phtml';
 
-		if (file_exists ($fic_helper)) {
-			include ($fic_helper);
-		} else {
-			Minz_Log::record ('File doesn\'t exist : `'
+		if ((@include($fic_helper)) === false) {;
+			Minz_Log::record ('File not found: `'
 			            . $fic_helper . '`',
 			            Minz_Log::WARNING);
 		}

+ 1 - 0
lib/SimplePie/SimplePie/File.php

@@ -77,6 +77,7 @@ class SimplePie_File
 		$this->useragent = $useragent;
 		if (preg_match('/^http(s)?:\/\//i', $url))
 		{
+			syslog(LOG_INFO, 'SimplePie GET ' . $url);	//FreshRSS
 			if ($useragent === null)
 			{
 				$useragent = ini_get('user_agent');

+ 121 - 0
lib/lib_opml.php

@@ -0,0 +1,121 @@
+<?php
+function opml_export ($cats) {
+	$txt = '';
+
+	foreach ($cats as $cat) {
+		$txt .= '<outline text="' . $cat['name'] . '">' . "\n";
+
+		foreach ($cat['feeds'] as $feed) {
+			$txt .= "\t" . '<outline text="' . $feed->name () . '" type="rss" xmlUrl="' . $feed->url () . '" htmlUrl="' . $feed->website () . '" description="' . htmlspecialchars($feed->description(), ENT_COMPAT, 'UTF-8') . '" />' . "\n";
+		}
+
+		$txt .= '</outline>' . "\n";
+	}
+
+	return $txt;
+}
+
+function opml_import ($xml) {
+	$xml = html_only_entity_decode($xml);	//!\ Assume UTF-8
+
+	$dom = new DOMDocument();
+	$dom->recover = true;
+	$dom->strictErrorChecking = false;
+	$dom->loadXML($xml);
+	$dom->encoding = 'UTF-8';
+
+	$opml = simplexml_import_dom($dom);
+
+	if (!$opml) {
+		throw new FreshRSS_Opml_Exception ();
+	}
+
+	$catDAO = new FreshRSS_CategoryDAO();
+	$catDAO->checkDefault();
+	$defCat = $catDAO->getDefault();
+
+	$categories = array ();
+	$feeds = array ();
+
+	foreach ($opml->body->outline as $outline) {
+		if (!isset ($outline['xmlUrl'])) {
+			// Catégorie
+			$title = '';
+
+			if (isset ($outline['text'])) {
+				$title = (string) $outline['text'];
+			} elseif (isset ($outline['title'])) {
+				$title = (string) $outline['title'];
+			}
+
+			if ($title) {
+				// Permet d'éviter les soucis au niveau des id :
+				// ceux-ci sont générés en fonction de la date,
+				// un flux pourrait être dans une catégorie X avec l'id Y
+				// alors qu'il existe déjà la catégorie X mais avec l'id Z
+				// Y ne sera pas ajouté et le flux non plus vu que l'id
+				// de sa catégorie n'exisera pas
+				$title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
+				$catDAO = new FreshRSS_CategoryDAO ();
+				$cat = $catDAO->searchByName ($title);
+				if ($cat === false) {
+					$cat = new FreshRSS_Category ($title);
+					$values = array (
+						'name' => $cat->name (),
+						'color' => $cat->color ()
+					);
+					$cat->_id ($catDAO->addCategory ($values));
+				}
+
+				$feeds = array_merge ($feeds, getFeedsOutline ($outline, $cat->id ()));
+			}
+		} else {
+			// Flux rss sans catégorie, on récupère l'ajoute dans la catégorie par défaut
+			$feeds[] = getFeed ($outline, $defCat->id());
+		}
+	}
+
+	return array ($categories, $feeds);
+}
+
+/**
+ * import all feeds of a given outline tag
+ */
+function getFeedsOutline ($outline, $cat_id) {
+	$feeds = array ();
+
+	foreach ($outline->children () as $child) {
+		if (isset ($child['xmlUrl'])) {
+			$feeds[] = getFeed ($child, $cat_id);
+		} else {
+			$feeds = array_merge(
+				$feeds,
+				getFeedsOutline ($child, $cat_id)
+			);
+		}
+	}
+
+	return $feeds;
+}
+
+function getFeed ($outline, $cat_id) {
+	$url = (string) $outline['xmlUrl'];
+	$url = htmlspecialchars($url, ENT_COMPAT, 'UTF-8');
+	$title = '';
+	if (isset ($outline['text'])) {
+		$title = (string) $outline['text'];
+	} elseif (isset ($outline['title'])) {
+		$title = (string) $outline['title'];
+	}
+	$title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
+	$feed = new FreshRSS_Feed ($url);
+	$feed->_category ($cat_id);
+	$feed->_name ($title);
+	if (isset($outline['htmlUrl'])) {
+		$feed->_website(htmlspecialchars((string)$outline['htmlUrl'], ENT_COMPAT, 'UTF-8'));
+	}
+	if (isset($outline['description'])) {
+		$feed->_description(sanitizeHTML((string)$outline['description']));
+	}
+	return $feed;
+}

+ 29 - 148
lib/lib_rss.php

@@ -56,16 +56,6 @@ function checkUrl($url) {
 	}
 }
 
-// vérifie qu'on est connecté
-function is_logged () {
-	return Minz_Session::param ('mail') != false;
-}
-
-// vérifie que le système d'authentification est configuré
-function login_is_conf ($conf) {
-	return $conf->mailLogin () != false;
-}
-
 // tiré de Shaarli de Seb Sauvage	//Format RFC 4648 base64url
 function small_hash ($txt) {
 	$t = rtrim (base64_encode (hash ('crc32', $txt, true)), '=');
@@ -98,40 +88,20 @@ function timestamptodate ($t, $hour = true) {
 	return @date ($date, $t);
 }
 
-function sortEntriesByDate ($entry1, $entry2) {
-	return $entry2->date (true) - $entry1->date (true);
-}
-function sortReverseEntriesByDate ($entry1, $entry2) {
-	return $entry1->date (true) - $entry2->date (true);
-}
-
-function get_domain ($url) {
-	return parse_url($url, PHP_URL_HOST);
-}
-
-function opml_export ($cats) {
-	$txt = '';
-
-	foreach ($cats as $cat) {
-		$txt .= '<outline text="' . $cat['name'] . '">' . "\n";
-
-		foreach ($cat['feeds'] as $feed) {
-			$txt .= "\t" . '<outline text="' . $feed->name () . '" type="rss" xmlUrl="' . $feed->url () . '" htmlUrl="' . $feed->website () . '" description="' . htmlspecialchars($feed->description(), ENT_COMPAT, 'UTF-8') . '" />' . "\n";
-		}
-
-		$txt .= '</outline>' . "\n";
-	}
-
-	return $txt;
-}
-
 function html_only_entity_decode($text) {
 	static $htmlEntitiesOnly = null;
 	if ($htmlEntitiesOnly === null) {
-		$htmlEntitiesOnly = array_flip(array_diff(
-			get_html_translation_table(HTML_ENTITIES, ENT_NOQUOTES, 'UTF-8'),	//Decode HTML entities
-			get_html_translation_table(HTML_SPECIALCHARS, ENT_NOQUOTES, 'UTF-8')	//Preserve XML entities
-		));
+		if (version_compare(PHP_VERSION, '5.3.4') >= 0) {
+			$htmlEntitiesOnly = array_flip(array_diff(
+				get_html_translation_table(HTML_ENTITIES, ENT_NOQUOTES, 'UTF-8'),	//Decode HTML entities
+				get_html_translation_table(HTML_SPECIALCHARS, ENT_NOQUOTES, 'UTF-8')	//Preserve XML entities
+			));
+		} else {
+			$htmlEntitiesOnly = array_map('utf8_encode', array_flip(array_diff(
+				get_html_translation_table(HTML_ENTITIES, ENT_NOQUOTES),	//Decode HTML entities
+				get_html_translation_table(HTML_SPECIALCHARS, ENT_NOQUOTES)	//Preserve XML entities
+			)));
+		}
 	}
 	return strtr($text, $htmlEntitiesOnly);
 }
@@ -144,112 +114,6 @@ function sanitizeHTML($data) {
 	return html_only_entity_decode($simplePie->sanitize->sanitize($data, SIMPLEPIE_CONSTRUCT_MAYBE_HTML));
 }
 
-function opml_import ($xml) {
-	$xml = html_only_entity_decode($xml);	//!\ Assume UTF-8
-
-	$dom = new DOMDocument();
-	$dom->recover = true;
-	$dom->strictErrorChecking = false;
-	$dom->loadXML($xml);
-	$dom->encoding = 'UTF-8';
-
-	$opml = simplexml_import_dom($dom);
-
-	if (!$opml) {
-		throw new FreshRSS_Opml_Exception ();
-	}
-
-	$catDAO = new FreshRSS_CategoryDAO();
-	$catDAO->checkDefault();
-	$defCat = $catDAO->getDefault();
-
-	$categories = array ();
-	$feeds = array ();
-
-	foreach ($opml->body->outline as $outline) {
-		if (!isset ($outline['xmlUrl'])) {
-			// Catégorie
-			$title = '';
-
-			if (isset ($outline['text'])) {
-				$title = (string) $outline['text'];
-			} elseif (isset ($outline['title'])) {
-				$title = (string) $outline['title'];
-			}
-
-			if ($title) {
-				// Permet d'éviter les soucis au niveau des id :
-				// ceux-ci sont générés en fonction de la date,
-				// un flux pourrait être dans une catégorie X avec l'id Y
-				// alors qu'il existe déjà la catégorie X mais avec l'id Z
-				// Y ne sera pas ajouté et le flux non plus vu que l'id
-				// de sa catégorie n'exisera pas
-				$title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
-				$catDAO = new FreshRSS_CategoryDAO ();
-				$cat = $catDAO->searchByName ($title);
-				if ($cat === false) {
-					$cat = new FreshRSS_Category ($title);
-					$values = array (
-						'name' => $cat->name (),
-						'color' => $cat->color ()
-					);
-					$cat->_id ($catDAO->addCategory ($values));
-				}
-
-				$feeds = array_merge ($feeds, getFeedsOutline ($outline, $cat->id ()));
-			}
-		} else {
-			// Flux rss sans catégorie, on récupère l'ajoute dans la catégorie par défaut
-			$feeds[] = getFeed ($outline, $defCat->id());
-		}
-	}
-
-	return array ($categories, $feeds);
-}
-
-/**
- * import all feeds of a given outline tag
- */
-function getFeedsOutline ($outline, $cat_id) {
-	$feeds = array ();
-
-	foreach ($outline->children () as $child) {
-		if (isset ($child['xmlUrl'])) {
-			$feeds[] = getFeed ($child, $cat_id);
-		} else {
-			$feeds = array_merge(
-				$feeds,
-				getFeedsOutline ($child, $cat_id)
-			);
-		}
-	}
-
-	return $feeds;
-}
-
-function getFeed ($outline, $cat_id) {
-	$url = (string) $outline['xmlUrl'];
-	$url = htmlspecialchars($url, ENT_COMPAT, 'UTF-8');
-	$title = '';
-	if (isset ($outline['text'])) {
-		$title = (string) $outline['text'];
-	} elseif (isset ($outline['title'])) {
-		$title = (string) $outline['title'];
-	}
-	$title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8');
-	$feed = new FreshRSS_Feed ($url);
-	$feed->_category ($cat_id);
-	$feed->_name ($title);
-	if (isset($outline['htmlUrl'])) {
-		$feed->_website(htmlspecialchars((string)$outline['htmlUrl'], ENT_COMPAT, 'UTF-8'));
-	}
-	if (isset($outline['description'])) {
-		$feed->_description(sanitizeHTML((string)$outline['description']));
-	}
-	return $feed;
-}
-
-
 /* permet de récupérer le contenu d'un article pour un flux qui n'est pas complet */
 function get_content_by_parsing ($url, $path) {
 	require_once (LIB_PATH . '/lib_phpQuery.php');
@@ -307,5 +171,22 @@ function uSecString() {
 }
 
 function invalidateHttpCache() {
-	file_put_contents(DATA_PATH . '/touch.txt', uTimeString());
+	touch(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log');
+	Minz_Session::_param('touch', uTimeString());
+}
+
+function usernameFromPath($userPath) {
+	if (preg_match('%/([a-z0-9]{1,16})_user\.php$%', $userPath, $matches)) {
+		return $matches[1];
+	} else {
+		return '';
+	}
+}
+
+function listUsers() {
+	return array_map('usernameFromPath', glob(DATA_PATH . '/*_user.php'));
+}
+
+function httpAuthUser() {
+	return isset($_SERVER['REMOTE_USER']) ? $_SERVER['REMOTE_USER'] : '';
 }

+ 5 - 9
p/.htaccess

@@ -10,13 +10,13 @@ AddDefaultCharset	UTF-8
 	AddType application/font-woff .woff
 
 	AddCharset	UTF-8	.css
+	AddCharset	UTF-8	.html
 	AddCharset	UTF-8	.js
-	AddCharset	UTF-8	.map
 	AddCharset	UTF-8	.svg
 </IfModule>
 
 <IfModule mod_deflate.c>
-	AddOutputFilterByType DEFLATE application/javascript application/json image/svg+xml text/css text/javascript
+	AddOutputFilterByType DEFLATE application/javascript application/json application/xhtml+xml image/svg+xml text/css text/html text/javascript
 </IfModule>
 
 <IfModule mod_expires.c>
@@ -24,11 +24,13 @@ AddDefaultCharset	UTF-8
 	ExpiresByType	application/font-woff	"access plus 1 month"
 	ExpiresByType	application/javascript	"access plus 1 month"
 	ExpiresByType	application/json	"access plus 1 month"
+	ExpiresByType	application/xhtml+xml	"access plus 1 month"
 	ExpiresByType	image/gif	"access plus 1 month"
 	ExpiresByType	image/png	"access plus 1 month"
 	ExpiresByType	image/svg+xml	"access plus 1 month"
 	ExpiresByType	image/x-icon	"access plus 1 month"
 	ExpiresByType	text/css	"access plus 1 month"
+	ExpiresByType	text/html	"access plus 1 month"
 	ExpiresByType	text/javascript	"access plus 1 month"
 	<FilesMatch "\.php$">
 		ExpiresActive	Off
@@ -36,13 +38,7 @@ AddDefaultCharset	UTF-8
 </IfModule>
 
 <IfModule mod_headers.c>
-	<FilesMatch "\.(css|js|ico|gif|png|woff)$">
+	<FilesMatch "\.(css|html|js|ico|gif|png|woff)$">
 		Header	merge Cache-Control "public"
 	</FilesMatch>
 </IfModule>
-
-<Files "favicon.ico">
-	Order	Deny,Allow
-	Allow	from all
-	Satisfy	Any
-</Files>

+ 36 - 0
p/Web.config

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+	<system.web>
+		<httpRuntime executionTimeout="300" maxRequestLength="8192"/>
+	</system.web>
+	<system.webServer>
+		<defaultDocument>
+			<files>
+				<clear />
+				<add value="index.php" />
+				<add value="index.html" />
+			</files>
+		</defaultDocument>
+		<urlCompression doStaticCompression="true" doDynamicCompression="true"/>
+		<staticContent>
+			<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="31.00:00:00" />
+		</staticContent>
+		<caching>
+			<profiles>
+				<add extension=".css" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" />
+				<add extension=".gif" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" />
+				<add extension=".html" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" />
+				<add extension=".jpg" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" />
+				<add extension=".js" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" />
+				<add extension=".png" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" />
+				<add extension=".svg" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" />
+				<add extension=".woff" policy="CacheForTimePeriod" duration="31.00:00:00" location="Any" kernelCachePolicy="DontCache" />
+			</profiles>
+		</caching>
+		<!--<httpProtocol>
+			<customHeaders>
+				<add name="Cache-Control" value="public" />
+			</customHeaders>
+		</httpProtocol>-->
+	</system.webServer>
+</configuration>

BIN
p/favicon.ico


BIN
p/favicon.png


+ 7 - 7
p/i/index.php

@@ -22,23 +22,23 @@ if (file_exists ('install.php')) {
 	require('install.php');
 } else {
 	require('../../constants.php');
+	require(LIB_PATH . '/lib_rss.php');	//Includes class autoloader
 
 	session_cache_limiter('');
+	Minz_Session::init('FreshRSS');
+
 	if (!file_exists(DATA_PATH . '/no-cache.txt')) {
-		require (LIB_PATH . '/http-conditional.php');
-		$dateLastModification = max(
-			@filemtime(DATA_PATH . '/touch.txt'),
-			@filemtime(LOG_PATH . '/application.log'),
+		require(LIB_PATH . '/http-conditional.php');
+		$currentUser = Minz_Session::param('currentUser', '');
+		$dateLastModification = $currentUser === '' ? time() : max(
+			@filemtime(LOG_PATH . '/' . $currentUser . '.log'),
 			@filemtime(DATA_PATH . '/config.php')
 		);
-		$_SERVER['QUERY_STRING'] .= '&utime=' . file_get_contents(DATA_PATH . '/touch.txt');	//For ETag
 		if (httpConditional($dateLastModification, 0, 0, false, false, true)) {
 			exit();	//No need to send anything
 		}
 	}
 
-	require(LIB_PATH . '/lib_rss.php');	//Includes class autoloader
-
 	try {
 		$front_controller = new FreshRSS();
 		$front_controller->init ();

+ 65 - 130
p/i/install.php

@@ -12,60 +12,9 @@ if (isset ($_GET['step'])) {
 	define ('STEP', 1);
 }
 
-define ('SQL_CREATE_DB', 'CREATE DATABASE %1$s DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
-
-define ('SQL_CAT', 'CREATE TABLE IF NOT EXISTS `%1$scategory` (
-	`id` SMALLINT NOT NULL AUTO_INCREMENT,	-- v0.7
-	`name` varchar(255) NOT NULL,
-	`color` char(7),
-	PRIMARY KEY (`id`),
-	UNIQUE KEY (`name`)	-- v0.7
-) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci
-ENGINE = INNODB;');
-
-define ('SQL_FEED', 'CREATE TABLE IF NOT EXISTS `%1$sfeed` (
-	`id` SMALLINT NOT NULL AUTO_INCREMENT,	-- v0.7
-	`url` varchar(511) CHARACTER SET latin1 NOT NULL,
-	`category` SMALLINT DEFAULT 0,	-- v0.7
-	`name` varchar(255) NOT NULL,
-	`website` varchar(255) CHARACTER SET latin1,
-	`description` text,
-	`lastUpdate` int(11) DEFAULT 0,
-	`priority` tinyint(2) NOT NULL DEFAULT 10,
-	`pathEntries` varchar(511) DEFAULT NULL,
-	`httpAuth` varchar(511) DEFAULT NULL,
-	`error` boolean DEFAULT 0,
-	`keep_history` MEDIUMINT NOT NULL DEFAULT -2,	-- v0.7, -2 = default
-	`cache_nbEntries` int DEFAULT 0,	-- v0.7
-	`cache_nbUnreads` int DEFAULT 0,	-- v0.7
-	PRIMARY KEY (`id`),
-	FOREIGN KEY (`category`) REFERENCES `%1$scategory`(`id`) ON DELETE SET NULL ON UPDATE CASCADE,
-	UNIQUE KEY (`url`),	-- v0.7
-	INDEX (`name`),	-- v0.7
-	INDEX (`priority`),	-- v0.7
-	INDEX (`keep_history`)	-- v0.7
-) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci
-ENGINE = INNODB;');
-
-define ('SQL_ENTRY', 'CREATE TABLE IF NOT EXISTS `%1$sentry` (
-	`id` bigint NOT NULL,	-- v0.7
-	`guid` varchar(760) CHARACTER SET latin1 NOT NULL,	-- Maximum for UNIQUE is 767B
-	`title` varchar(255) NOT NULL,
-	`author` varchar(255),
-	`content_bin` blob,	-- v0.7
-	`link` varchar(1023) CHARACTER SET latin1 NOT NULL,
-	`date` int(11),
-	`is_read` boolean NOT NULL DEFAULT 0,
-	`is_favorite` boolean NOT NULL DEFAULT 0,
-	`id_feed` SMALLINT,	-- v0.7
-	`tags` varchar(1023),
-	PRIMARY KEY (`id`),
-	FOREIGN KEY (`id_feed`) REFERENCES `%1$sfeed`(`id`) ON DELETE CASCADE ON UPDATE CASCADE,
-	UNIQUE KEY (`id_feed`,`guid`),	-- v0.7
-	INDEX (`is_favorite`),	-- v0.7
-	INDEX (`is_read`)	-- v0.7
-) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci
-ENGINE = INNODB;');
+define('SQL_CREATE_DB', 'CREATE DATABASE %1$s DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;');
+
+include(APP_PATH . '/sql.php');
 
 //<updates>
 define('SQL_SHOW_TABLES', 'SHOW tables;');
@@ -134,21 +83,6 @@ SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads
 define('SQL_UPDATE_HISTORYv007b', 'UPDATE `%1$sfeed` SET keep_history = CASE WHEN keep_history = 0 THEN -2 WHEN keep_history = 1 THEN -1 ELSE keep_history END;');
 //</updates>
 
-function writeLine ($f, $line) {
-	fwrite ($f, $line . "\n");
-}
-function writeArray ($f, $array) {
-	foreach ($array as $key => $val) {
-		if (is_array ($val)) {
-			writeLine ($f, '\'' . $key . '\' => array (');
-			writeArray ($f, $val);
-			writeLine ($f, '),');
-		} else {
-			writeLine ($f, '\'' . $key . '\' => \'' . $val . '\',');
-		}
-	}
-}
-
 // gestion internationalisation
 $translates = array ();
 $actual = 'en';
@@ -219,34 +153,36 @@ function saveStep2 () {
 			return false;
 		}
 
-		$_SESSION['sel_application'] = sha1(uniqid(mt_rand(), true).implode('', stat(__FILE__)));
-		$_SESSION['title'] = addslashes(substr(trim($_POST['title']), 0, 25));
+		$_SESSION['salt'] = sha1(uniqid(mt_rand(), true).implode('', stat(__FILE__)));
+		$_SESSION['title'] = substr(trim($_POST['title']), 0, 25);
 		$_SESSION['old_entries'] = $_POST['old_entries'];
 		if ((!ctype_digit($_SESSION['old_entries'])) || ($_SESSION['old_entries'] < 1)) {
 			$_SESSION['old_entries'] = 3;
 		}
-		$_SESSION['mail_login'] = addslashes ($_POST['mail_login']);
-		$_SESSION['default_user'] = substr(preg_replace ('/[^a-zA-Z0-9]/', '', $_POST['default_user']), 0, 16);
+		$_SESSION['mail_login'] = filter_var($_POST['mail_login'], FILTER_VALIDATE_EMAIL);
+		$_SESSION['default_user'] = substr(preg_replace('/[^a-zA-Z0-9]/', '', $_POST['default_user']), 0, 16);
 
 		$token = '';
 		if ($_SESSION['mail_login']) {
-			$token = sha1($_SESSION['sel_application'] . $_SESSION['mail_login']);
+			$token = sha1($_SESSION['salt'] . $_SESSION['mail_login']);
 		}
 
-		$file_data = DATA_PATH . '/' . $_SESSION['default_user'] . '_user.php';
-
-		@unlink($file_data);	//To avoid access-rights problems
-		$f = fopen ($file_data, 'w');
-		writeLine ($f, '<?php');
-		writeLine ($f, 'return array (');
-		writeArray ($f, array (
+		$config_array = array (
 			'language' => $_SESSION['language'],
 			'old_entries' => $_SESSION['old_entries'],
 			'mail_login' => $_SESSION['mail_login'],
-			'token' => $token
-		));
-		writeLine ($f, ');');
-		fclose ($f);
+			'token' => $token,
+		);
+
+		$configPath = DATA_PATH . '/' . $_SESSION['default_user'] . '_user.php';
+		@unlink($configPath);	//To avoid access-rights problems
+		file_put_contents($configPath, "<?php\n return " . var_export($config_array, true) . ';');
+
+		if ($_SESSION['mail_login'] != '') {
+			$personaFile = DATA_PATH . '/persona/' . $_SESSION['mail_login'] . '.txt';
+			@unlink($personaFile);
+			file_put_contents($personaFile, $_SESSION['default_user']);
+		}
 
 		header ('Location: index.php?step=3');
 	}
@@ -262,18 +198,18 @@ function saveStep3 () {
 		}
 
 		$_SESSION['bd_type'] = isset ($_POST['type']) ? $_POST['type'] : 'mysql';
-		$_SESSION['bd_host'] = addslashes ($_POST['host']);
-		$_SESSION['bd_user'] = addslashes ($_POST['user']);
-		$_SESSION['bd_password'] = addslashes ($_POST['pass']);
-		$_SESSION['bd_base'] = addslashes ($_POST['base']);
-		$_SESSION['bd_prefix'] = addslashes ($_POST['prefix']);
+		$_SESSION['bd_host'] = $_POST['host'];
+		$_SESSION['bd_user'] = $_POST['user'];
+		$_SESSION['bd_password'] = $_POST['pass'];
+		$_SESSION['bd_base'] = substr($_POST['base'], 0, 64);
+		$_SESSION['bd_prefix'] = substr($_POST['prefix'], 0, 16);
 		$_SESSION['bd_prefix_user'] = $_SESSION['bd_prefix'] . (empty($_SESSION['default_user']) ? '' : ($_SESSION['default_user'] . '_'));
 
 		$ini_array = array(
 			'general' => array(
 				'environment' => empty($_SESSION['environment']) ? 'production' : $_SESSION['environment'],
 				'use_url_rewriting' => false,
-				'sel_application' => $_SESSION['sel_application'],
+				'salt' => $_SESSION['salt'],
 				'base_url' => '',
 				'title' => $_SESSION['title'],
 				'default_user' => $_SESSION['default_user'],
@@ -496,7 +432,7 @@ function checkStep0 () {
 	if ($ini_array) {
 		$ini_general = isset($ini_array['general']) ? $ini_array['general'] : null;
 		if ($ini_general) {
-			$keys = array('environment', 'sel_application', 'title', 'default_user');
+			$keys = array('environment', 'salt', 'title', 'default_user');
 			foreach ($keys as $key) {
 				if ((empty($_SESSION[$key])) && isset($ini_general[$key])) {
 					$_SESSION[$key] = $ini_general[$key];
@@ -543,6 +479,8 @@ function checkStep1 () {
 	$minz = file_exists (LIB_PATH . '/Minz');
 	$curl = extension_loaded ('curl');
 	$pdo = extension_loaded ('pdo_mysql');
+	$pcre = extension_loaded ('pcre');
+	$ctype = extension_loaded ('ctype');
 	$dom = class_exists('DOMDocument');
 	$data = DATA_PATH && is_writable (DATA_PATH);
 	$cache = CACHE_PATH && is_writable (CACHE_PATH);
@@ -554,17 +492,19 @@ function checkStep1 () {
 		'minz' => $minz ? 'ok' : 'ko',
 		'curl' => $curl ? 'ok' : 'ko',
 		'pdo-mysql' => $pdo ? 'ok' : 'ko',
+		'pcre' => $pcre ? 'ok' : 'ko',
+		'ctype' => $ctype ? 'ok' : 'ko',
 		'dom' => $dom ? 'ok' : 'ko',
 		'data' => $data ? 'ok' : 'ko',
 		'cache' => $cache ? 'ok' : 'ko',
 		'log' => $log ? 'ok' : 'ko',
 		'favicons' => $favicons ? 'ok' : 'ko',
-		'all' => $php && $minz && $curl && $pdo && $dom && $data && $cache && $log && $favicons ? 'ok' : 'ko'
+		'all' => $php && $minz && $curl && $pdo && $pcre && $ctype && $dom && $data && $cache && $log && $favicons ? 'ok' : 'ko'
 	);
 }
 
 function checkStep2 () {
-	$conf = !empty($_SESSION['sel_application']) &&
+	$conf = !empty($_SESSION['salt']) &&
 	        !empty($_SESSION['title']) &&
 	        !empty($_SESSION['old_entries']) &&
 	        isset($_SESSION['mail_login']) &&
@@ -605,7 +545,7 @@ function checkStep3 () {
 }
 
 function checkBD () {
-	$error = false;
+	$ok = false;
 
 	try {
 		$str = '';
@@ -643,35 +583,21 @@ function checkBD () {
 			$res = $c->query($sql);	//Backup tables
 		}
 
-		$sql = sprintf (SQL_CAT, $_SESSION['bd_prefix_user']);
-		$res = $c->query ($sql);
-
-		if (!$res) {
-			$error = true;
-		}
-
-		$sql = sprintf (SQL_FEED, $_SESSION['bd_prefix_user']);
-		$res = $c->query ($sql);
-
-		if (!$res) {
-			$error = true;
-		}
-
-		$sql = sprintf (SQL_ENTRY, $_SESSION['bd_prefix_user']);
-		$res = $c->query ($sql);
-
-		if (!$res) {
-			$error = true;
-		}
+		$sql = sprintf(SQL_CREATE_TABLES, $_SESSION['bd_prefix_user']);
+		$stm = $c->prepare($sql, array(PDO::ATTR_EMULATE_PREPARES => true));
+		$values = array(
+			'catName' => _t('default_category'),
+		);
+		$ok = $stm->execute($values);
 	} catch (PDOException $e) {
 		$error = true;
 	}
 
-	if ($error && file_exists (DATA_PATH . '/config.php')) {
-		unlink (DATA_PATH . '/config.php');
+	if (!$ok) {
+		@unlink(DATA_PATH . '/config.php');
 	}
 
-	return !$error;
+	return $ok;
 }
 
 /*** AFFICHAGE ***/
@@ -726,6 +652,12 @@ function printStep1 () {
 	<p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('minz_is_nok', LIB_PATH . '/Minz'); ?></p>
 	<?php } ?>
 
+	<?php if ($res['pdo-mysql'] == 'ok') { ?>
+	<p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('pdomysql_is_ok'); ?></p>
+	<?php } else { ?>
+	<p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('pdomysql_is_nok'); ?></p>
+	<?php } ?>
+
 	<?php if ($res['curl'] == 'ok') { ?>
 	<?php $version = curl_version(); ?>
 	<p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('curl_is_ok', $version['version']); ?></p>
@@ -733,10 +665,16 @@ function printStep1 () {
 	<p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('curl_is_nok'); ?></p>
 	<?php } ?>
 
-	<?php if ($res['pdo-mysql'] == 'ok') { ?>
-	<p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('pdomysql_is_ok'); ?></p>
+	<?php if ($res['pcre'] == 'ok') { ?>
+	<p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('pcre_is_ok'); ?></p>
 	<?php } else { ?>
-	<p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('pdomysql_is_nok'); ?></p>
+	<p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('pcre_is_nok'); ?></p>
+	<?php } ?>
+
+	<?php if ($res['ctype'] == 'ok') { ?>
+	<p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('ctype_is_ok'); ?></p>
+	<?php } else { ?>
+	<p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('ctype_is_nok'); ?></p>
 	<?php } ?>
 
 	<?php if ($res['dom'] == 'ok') { ?>
@@ -785,9 +723,6 @@ function printStep2 () {
 
 	<form action="index.php?step=2" method="post">
 		<legend><?php echo _t ('general_configuration'); ?></legend>
-		<?php
-			$url = substr ($_SERVER['PHP_SELF'], 0, -10);
-		?>
 
 		<div class="form-group">
 			<label class="group-name" for="title"><?php echo _t ('title'); ?></label>
@@ -799,14 +734,14 @@ function printStep2 () {
 		<div class="form-group">
 			<label class="group-name" for="old_entries"><?php echo _t ('delete_articles_every'); ?></label>
 			<div class="group-controls">
-				<input type="number" id="old_entries" name="old_entries" value="<?php echo isset ($_SESSION['old_entries']) ? $_SESSION['old_entries'] : '3'; ?>" /> <?php echo _t ('month'); ?>
+				<input type="number" id="old_entries" name="old_entries" required="required" min="1" max="1200" value="<?php echo isset ($_SESSION['old_entries']) ? $_SESSION['old_entries'] : '3'; ?>" /> <?php echo _t ('month'); ?>
 			</div>
 		</div>
 
 		<div class="form-group">
 			<label class="group-name" for="default_user"><?php echo _t ('default_user'); ?></label>
 			<div class="group-controls">
-				<input type="text" id="default_user" name="default_user" maxlength="16" value="<?php echo isset ($_SESSION['default_user']) ? $_SESSION['default_user'] : ''; ?>" placeholder="user1" />
+				<input type="text" id="default_user" name="default_user" required="required" size="16" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" value="<?php echo isset ($_SESSION['default_user']) ? $_SESSION['default_user'] : ''; ?>" placeholder="<?php echo httpAuthUser() == '' ? 'user1' : httpAuthUser(); ?>" />
 			</div>
 		</div>
 
@@ -861,14 +796,14 @@ function printStep3 () {
 		<div class="form-group">
 			<label class="group-name" for="host"><?php echo _t ('host'); ?></label>
 			<div class="group-controls">
-				<input type="text" id="host" name="host" value="<?php echo isset ($_SESSION['bd_host']) ? $_SESSION['bd_host'] : 'localhost'; ?>" />
+				<input type="text" id="host" name="host" pattern="[0-9A-Za-z_.-]{1,64}" value="<?php echo isset ($_SESSION['bd_host']) ? $_SESSION['bd_host'] : 'localhost'; ?>" />
 			</div>
 		</div>
 
 		<div class="form-group">
 			<label class="group-name" for="user"><?php echo _t ('username'); ?></label>
 			<div class="group-controls">
-				<input type="text" id="user" name="user" value="<?php echo isset ($_SESSION['bd_user']) ? $_SESSION['bd_user'] : ''; ?>" />
+				<input type="text" id="user" name="user" maxlength="16" pattern="[0-9A-Za-z_]{1,16}" value="<?php echo isset ($_SESSION['bd_user']) ? $_SESSION['bd_user'] : ''; ?>" />
 			</div>
 		</div>
 
@@ -882,14 +817,14 @@ function printStep3 () {
 		<div class="form-group">
 			<label class="group-name" for="base"><?php echo _t ('bdd'); ?></label>
 			<div class="group-controls">
-				<input type="text" id="base" name="base" maxlength="64" value="<?php echo isset ($_SESSION['bd_base']) ? $_SESSION['bd_base'] : ''; ?>" placeholder="freshrss" />
+				<input type="text" id="base" name="base" maxlength="64" pattern="[0-9A-Za-z_]{1,64}" value="<?php echo isset ($_SESSION['bd_base']) ? $_SESSION['bd_base'] : ''; ?>" placeholder="freshrss" />
 			</div>
 		</div>
 
 		<div class="form-group">
 			<label class="group-name" for="prefix"><?php echo _t ('prefix'); ?></label>
 			<div class="group-controls">
-				<input type="text" id="prefix" name="prefix" maxlength="16" value="<?php echo isset ($_SESSION['bd_prefix']) ? $_SESSION['bd_prefix'] : 'freshrss_'; ?>" />
+				<input type="text" id="prefix" name="prefix" maxlength="16" pattern="[0-9A-Za-z_]{1,16}" value="<?php echo isset ($_SESSION['bd_prefix']) ? $_SESSION['bd_prefix'] : 'freshrss_'; ?>" />
 			</div>
 		</div>
 

+ 22 - 2
p/index.html

@@ -2,11 +2,31 @@
 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-GB" lang="en-GB">
 <head>
 <meta charset="UTF-8" />
+<meta name="viewport" content="initial-scale=1.0" />
 <meta http-equiv="Refresh" content="0; url=i/" />
-<title>Redirection</title>
+<title>FreshRSS</title>
+<link rel="shortcut icon" type="image/x-icon" sizes="16x16 64x64" href="favicon.ico" />
+<link rel="icon msapplication-TileImage apple-touch-icon" type="image/png" sizes="256x256" href="themes/icons/favicon-256.png" />
+<meta name="msapplication-TileColor" content="#FFF" />
+<meta name="robots" content="noindex,nofollow" />
+<style>
+body {
+	font-family: sans-serif;
+	text-align: center;
+}
+h1 {
+	font-size: xx-large;
+	text-shadow: 1px -1px 0 #CCCCCC;
+}
+h1 a {
+	color: #0062BE;
+	text-decoration: none;
+}
+</style>
 </head>
 
 <body>
-<p><a href="i/">FreshRSS</a></p>
+<h1><a href="i/">FreshRSS</a></h1>
+<p><a href="i/"><img class="logo" width="25%" src="themes/icons/icon.svg" alt="⊚" /></a></p>
 </body>
 </html>

+ 0 - 3
p/index.php

@@ -1,3 +0,0 @@
-<?php
-header('Location: i/', true, 301);
-include('index.html');

+ 12 - 0
p/scripts/main.js

@@ -220,6 +220,13 @@ function collapse_entry() {
 	$(".flux.current").toggleClass("active");
 }
 
+function auto_share() {
+	var share = $(".flux.current.active").find('.dropdown-target[id^="dropdown-share"]');
+	if (share.length) {
+		window.location.hash = share.attr('id');
+	}
+}
+
 function inMarkViewport(flux, box_to_follow, relative_follow) {
 	var top = flux.position().top;
 	if (relative_follow) {
@@ -338,6 +345,11 @@ function init_shortcuts() {
 	}, {
 		'disable_in_input': true
 	});
+	shortcut.add(shortcuts.auto_share, function () {
+		auto_share();
+	}, {
+		'disable_in_input': true
+	});
 
 	// Touches de navigation
 	shortcut.add(shortcuts.prev_entry, prev_entry, {

+ 4 - 0
p/themes/default/global.css

@@ -112,6 +112,10 @@ input, select, textarea {
 		border-color: #33BBFF;
 		box-shadow: 0 2px 2px #DDDDFF inset;
 	}
+	input:invalid, select:invalid {
+		border-color: red;
+		box-shadow: 0 0 2px 1px red;
+	}
 
 .form-group {
 	margin: 0;

+ 4 - 0
p/themes/flat-design/global.css

@@ -113,6 +113,10 @@ input, select, textarea {
 		color: #333;
 		border-color: #2980b9;
 	}
+	input:invalid, select:invalid {
+		border-color: red;
+		box-shadow: 0 0 2px 1px red;
+	}
 
 .form-group {
 	margin: 5px 0;

BIN
p/themes/icons/favicon-128.png


BIN
p/themes/icons/favicon-16.png


BIN
p/themes/icons/favicon-256.png


BIN
p/themes/icons/favicon-32.png


BIN
p/themes/icons/favicon-48.png


BIN
p/themes/icons/favicon-512.png


BIN
p/themes/icons/favicon-64.png