Browse Source

Fusion 0.7-dev

Alkarex 12 years ago
parent
commit
24d9d1628d
100 changed files with 5094 additions and 3670 deletions
  1. 31 21
      README.md
  2. 5 19
      actualize_script.php
  3. 0 93
      app/App_FrontController.php
  4. 146 106
      app/Controllers/configureController.php
  5. 112 0
      app/Controllers/entryController.php
  6. 4 4
      app/Controllers/errorController.php
  7. 430 0
      app/Controllers/feedController.php
  8. 280 0
      app/Controllers/indexController.php
  9. 2 2
      app/Controllers/javascriptController.php
  10. 6 0
      app/Exceptions/BadUrlException.php
  11. 7 0
      app/Exceptions/EntriesGetterException.php
  12. 1 2
      app/Exceptions/FeedException.php
  13. 6 0
      app/Exceptions/OpmlException.php
  14. 58 0
      app/FreshRSS.php
  15. 85 0
      app/Models/Category.php
  16. 250 0
      app/Models/CategoryDAO.php
  17. 42 161
      app/Models/Configuration.php
  18. 156 0
      app/Models/ConfigurationDAO.php
  19. 1 1
      app/Models/Days.php
  20. 192 0
      app/Models/Entry.php
  21. 464 0
      app/Models/EntryDAO.php
  22. 319 0
      app/Models/Feed.php
  23. 339 0
      app/Models/FeedDAO.php
  24. 26 0
      app/Models/Log.php
  25. 20 0
      app/Models/LogDAO.php
  26. 88 0
      app/Models/Themes.php
  27. 0 1
      app/configuration/.gitignore
  28. 0 117
      app/controllers/entryController.php
  29. 0 363
      app/controllers/feedController.php
  30. 0 257
      app/controllers/indexController.php
  31. 40 73
      app/i18n/en.php
  32. 38 71
      app/i18n/fr.php
  33. 61 0
      app/i18n/install.en.php
  34. 61 0
      app/i18n/install.fr.php
  35. 8 5
      app/layout/aside_configure.phtml
  36. 17 17
      app/layout/aside_feed.phtml
  37. 20 22
      app/layout/aside_flux.phtml
  38. 20 19
      app/layout/header.phtml
  39. 12 9
      app/layout/layout.phtml
  40. 3 3
      app/layout/nav_entries.phtml
  41. 69 43
      app/layout/nav_menu.phtml
  42. 0 332
      app/models/Category.php
  43. 0 156
      app/models/EntriesGetter.php
  44. 0 590
      app/models/Entry.php
  45. 0 19
      app/models/Exception/FeedException.php
  46. 0 564
      app/models/Feed.php
  47. 0 47
      app/models/Log_Model.php
  48. 0 33
      app/models/RSSPaginator.php
  49. 0 47
      app/models/RSSThemes.php
  50. 11 11
      app/views/configure/categorize.phtml
  51. 59 62
      app/views/configure/display.phtml
  52. 46 38
      app/views/configure/feed.phtml
  53. 9 9
      app/views/configure/importExport.phtml
  54. 64 0
      app/views/configure/sharing.phtml
  55. 27 13
      app/views/configure/shortcut.phtml
  56. 9 8
      app/views/entry/bookmark.phtml
  57. 9 8
      app/views/entry/read.phtml
  58. 2 2
      app/views/error/index.phtml
  59. 15 9
      app/views/helpers/javascript_vars.phtml
  60. 8 8
      app/views/helpers/logs_pagination.phtml
  61. 17 11
      app/views/helpers/pagination.phtml
  62. 3 5
      app/views/helpers/view/global_view.phtml
  63. 115 58
      app/views/helpers/view/normal_view.phtml
  64. 9 9
      app/views/helpers/view/reader_view.phtml
  65. 5 6
      app/views/helpers/view/rss_view.phtml
  66. 13 13
      app/views/index/about.phtml
  67. 5 5
      app/views/index/index.phtml
  68. 10 6
      app/views/index/logs.phtml
  69. 2 2
      app/views/javascript/actualize.phtml
  70. 0 1
      cache/.gitignore
  71. 13 0
      constants.php
  72. 6 0
      data/.gitignore
  73. 3 0
      data/.htaccess
  74. 1 0
      data/cache/.gitignore
  75. 2 0
      data/favicons/.gitignore
  76. 13 0
      data/index.html
  77. 1 0
      data/log/.gitignore
  78. 13 0
      index.html
  79. 0 2
      index.php
  80. 933 0
      lib/JSON.php
  81. 2 2
      lib/Minz/ActionController.php
  82. 9 0
      lib/Minz/ActionException.php
  83. 9 0
      lib/Minz/BadConfigurationException.php
  84. 3 3
      lib/Minz/Cache.php
  85. 48 40
      lib/Minz/Configuration.php
  86. 9 0
      lib/Minz/ControllerNotActionControllerException.php
  87. 9 0
      lib/Minz/ControllerNotExistException.php
  88. 8 0
      lib/Minz/CurrentPagePaginationException.php
  89. 20 34
      lib/Minz/Dispatcher.php
  90. 20 20
      lib/Minz/Error.php
  91. 16 0
      lib/Minz/Exception.php
  92. 8 0
      lib/Minz/FileNotExistException.php
  93. 26 56
      lib/Minz/FrontController.php
  94. 1 1
      lib/Minz/Helper.php
  95. 18 13
      lib/Minz/Log.php
  96. 1 1
      lib/Minz/Model.php
  97. 3 3
      lib/Minz/ModelArray.php
  98. 28 9
      lib/Minz/ModelPdo.php
  99. 5 5
      lib/Minz/ModelTxt.php
  100. 9 0
      lib/Minz/PDOConnectionException.php

+ 31 - 21
README.md

@@ -1,45 +1,55 @@
 # 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.
+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.
 
 * Site officiel : http://marienfressinaud.github.io/FreshRSS/
 * Démo : http://marienfressinaud.fr/projets/freshrss/
 * Développeur : Marien Fressinaud <dev@marienfressinaud.fr>
-* Version actuelle : 0.6.1
-* Date de publication 2013-11-21
-* License AGPL3
+* Version actuelle : 0.7-dev
+* Date de publication 2013-12-xx
+* License [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html)
 
 ![Logo de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_title.png)
 
 # Disclaimer
-Cette application a été développée pour s'adapter à des besoins personnels et non professionels.
+Cette application a été développée pour sadapter à des besoins personnels et non professionnels.
 Je ne garantis en aucun cas la sécurité de celle-ci, ni son bon fonctionnement.
-Je m'engage néanmoins à répondre dans la mesure du possible aux demandes d'évolution si celles-ci me semblent justifiées.
+Je m’engage néanmoins à répondre dans la mesure du possible aux demandes d’évolution si celles-ci me semblent justifiées.
 Privilégiez pour cela des demandes sur GitHub
 (https://github.com/marienfressinaud/FreshRSS/issues) ou par mail (dev@marienfressinaud.fr)
 
 # Pré-requis
 * Serveur Apache2 ou Nginx (non testé sur les autres)
 * PHP 5.2+ (PHP 5.3.3+ recommandé)
- * Requis : [libxml](http://php.net/xml), [cURL](http://php.net/curl), [PDO_MySQL](http://php.net/pdo-mysql)
- * Recommandés : [Zlib](http://php.net/zlib), [mbstring](http://php.net/mbstring), [iconv](http://php.net/iconv)
-* MySQL 5.0.3+ (SQLite à venir)
+ * 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)
+* 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
 
-![Capture d'écran de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png)
+![Capture décran de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png)
 
 # Installation
-1. Récupérez l'application FreshRSS via la commande git ou [en téléchargeant l'archive](https://github.com/marienfressinaud/FreshRSS/archive/master.zip)
-2. Déplacez l'application où vous voulez sur votre serveur (attention, la partie accessible se trouve dans le répertoire `./public`)
-3. Accédez à FreshRSS à travers votre navigateur web et suivez les instructions d'installation
-4. Tout devrait fonctionner :) En cas de problème, n'hésitez pas à me contacter.
-
-# Sécurité et conseils
-1. Pour une meilleure sécurité, faites en sorte que seul le répertoire `./public` soit accessible par le navigateur. Faites pointer un sous-domaine sur le répertoire `./public` par exemple
-2. Dans tous les cas, assurez-vous que `./app/configuration/application.ini` ne puisse pas être téléchargé !
-3. Le fichier de log peut être utile à lire si vous avez des soucis
-4. Le fichier `./public/index.php` défini les chemins d'accès aux répertoires clés de l'application. Si vous les bougez, tout se passe ici.
-5. Vous pouvez ajouter une tâche CRON sur le script d'actualisation des flux. Il s'agit d'un script PHP à exécuter avec la commande `php`. Par exemple, pour exécuter le script toutes les heures :
+1. Récupérez l’application FreshRSS via la commande git ou [en téléchargeant l’archive](https://github.com/marienfressinaud/FreshRSS/archive/master.zip)
+2. Placez l’application sur votre serveur (la partie à exposer au Web est le répertoire `./public/`)
+3. Le serveur Web doit avoir les droits d’écriture dans le répertoire `./data/`
+4. Accédez à FreshRSS à travers votre navigateur Web et suivez les instructions d’installation
+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 :
+* 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)
+
+# 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 :
 ```
 7 * * * * php /chemin/vers/freshrss/actualize_script.php >/dev/null 2>&1
 ```
+
+# Conseils
+* Pour une meilleure sécurité, faites en sorte que seul le répertoire `./public` soit accessible depuis le Web, par exemple en faisant pointer un sous-domaine sur le répertoire `./public`.
+* 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).
+* 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`.

+ 5 - 19
actualize_script.php

@@ -1,29 +1,15 @@
 <?php
-
-// Constantes de chemins
-define ('PUBLIC_PATH', realpath (dirname (__FILE__) . '/public'));
-define ('LIB_PATH', realpath (PUBLIC_PATH . '/../lib'));
-define ('APP_PATH', realpath (PUBLIC_PATH . '/../app'));
-define ('LOG_PATH', realpath (PUBLIC_PATH . '/../log'));
-define ('CACHE_PATH', realpath (PUBLIC_PATH . '/../cache'));
+require('constants.php');
 
 $_GET['c'] = 'feed';
 $_GET['a'] = 'actualize';
 $_GET['force'] = true;
 $_SERVER['HTTP_HOST'] = '';
 
-set_include_path (get_include_path ()
-		 . PATH_SEPARATOR
-		 . LIB_PATH
-		 . PATH_SEPARATOR
-		 . LIB_PATH . '/minz'
-		 . PATH_SEPARATOR
-		 . APP_PATH);
-
-require (APP_PATH . '/App_FrontController.php');
+require(LIB_PATH . '/lib_rss.php');	//Includes class autoloader
 
-$front_controller = new App_FrontController ();
+$front_controller = new FreshRSS ();
 $front_controller->init ();
-Session::_param('mail', true); // permet de se passer de la phase de connexion
+Minz_Session::_param('mail', true); // permet de se passer de la phase de connexion
 $front_controller->run ();
-touch(PUBLIC_PATH . '/data/touch.txt');
+invalidateHttpCache();

+ 0 - 93
app/App_FrontController.php

@@ -1,93 +0,0 @@
-<?php
-/** 
- * MINZ - Copyright 2011 Marien Fressinaud
- * Sous licence AGPL3 <http://www.gnu.org/licenses/>
-*/
-require ('FrontController.php');
-
-class App_FrontController extends FrontController {
-	public function init () {
-		$this->loadLibs ();
-		$this->loadModels ();
-
-		Session::init ();
-		RSSThemes::init ();
-		Translate::init ();
-
-		$this->loadParamsView ();
-		$this->loadStylesAndScripts ();
-		$this->loadNotifications ();
-	}
-
-	private function loadLibs () {
-		require (LIB_PATH . '/lib_phpQuery.php');
-		require (LIB_PATH . '/lib_rss.php');
-		require (LIB_PATH . '/SimplePie_autoloader.php');
-	}
-
-	private function loadModels () {
-		include (APP_PATH . '/models/Exception/FeedException.php');
-		include (APP_PATH . '/models/Exception/EntriesGetterException.php');
-		include (APP_PATH . '/models/RSSConfiguration.php');
-		include (APP_PATH . '/models/RSSThemes.php');
-		include (APP_PATH . '/models/Days.php');
-		include (APP_PATH . '/models/Category.php');
-		include (APP_PATH . '/models/Feed.php');
-		include (APP_PATH . '/models/Entry.php');
-		include (APP_PATH . '/models/EntriesGetter.php');
-		include (APP_PATH . '/models/RSSPaginator.php');
-		include (APP_PATH . '/models/Log_Model.php');
-	}
-
-	private function loadParamsView () {
-		try {
-			$this->conf = Session::param ('conf', new RSSConfiguration ());
-		} catch(MinzException $e) {
-			// Permission denied or conf file does not exist
-			// it's critical!
-			print $e->getMessage();
-			exit();
-		}
-
-		View::_param ('conf', $this->conf);
-
-		$entryDAO = new EntryDAO ();
-		View::_param ('nb_not_read', $entryDAO->countNotRead ());
-
-		Session::_param ('language', $this->conf->language ());
-
-		$output = Request::param ('output');
-		if(!$output) {
-			$output = $this->conf->viewMode();
-			Request::_param ('output', $output);
-		}
-	}
-
-	private function loadStylesAndScripts () {
-		$theme = RSSThemes::get_infos($this->conf->theme());
-		if ($theme) {
-			foreach($theme["files"] as $file) {
-				View::appendStyle (Url::display ('/themes/' . $theme['path'] . '/' . $file . '?' . @filemtime(PUBLIC_PATH . '/themes/' . $theme['path'] . '/' . $file)));
-			}
-		}
-		View::appendStyle (Url::display ('/themes/printer/style.css?' . @filemtime(PUBLIC_PATH . '/themes/printer/style.css')), 'print');
-
-		if (login_is_conf ($this->conf)) {
-			View::appendScript ('https://login.persona.org/include.js');
-		}
-		$includeLazyLoad = $this->conf->lazyload () === 'yes' && ($this->conf->displayPosts () === 'yes' || Request::param ('output') === 'reader');
-		View::appendScript (Url::display ('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js')), false, !$includeLazyLoad, !$includeLazyLoad);
-		if ($includeLazyLoad) {
-			View::appendScript (Url::display ('/scripts/jquery.lazyload.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.lazyload.min.js')));
-		}
-		View::appendScript (Url::display ('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js')));
-	}
-
-	private function loadNotifications () {
-		$notif = Session::param ('notification');
-		if ($notif) {
-			View::_param ('notification', $notif);
-			Session::_param ('notification');
-		}
-	}
-}

+ 146 - 106
app/controllers/configureController.php → app/Controllers/configureController.php

@@ -1,33 +1,33 @@
 <?php
 
-class configureController extends ActionController {
+class FreshRSS_configure_Controller extends Minz_ActionController {
 	public function firstAction () {
 		if (login_is_conf ($this->view->conf) && !is_logged ()) {
-			Error::error (
+			Minz_Error::error (
 				403,
-				array ('error' => array (Translate::t ('access_denied')))
+				array ('error' => array (Minz_Translate::t ('access_denied')))
 			);
 		}
 
-		$catDAO = new CategoryDAO ();
+		$catDAO = new FreshRSS_CategoryDAO ();
 		$catDAO->checkDefault ();
 	}
 
 	public function categorizeAction () {
-		$feedDAO = new FeedDAO ();
-		$catDAO = new CategoryDAO ();
+		$feedDAO = new FreshRSS_FeedDAO ();
+		$catDAO = new FreshRSS_CategoryDAO ();
 		$catDAO->checkDefault ();
 		$defaultCategory = $catDAO->getDefault ();
 		$defaultId = $defaultCategory->id ();
 
-		if (Request::isPost ()) {
-			$cats = Request::param ('categories', array ());
-			$ids = Request::param ('ids', array ());
-			$newCat = trim (Request::param ('new_category', ''));
+		if (Minz_Request::isPost ()) {
+			$cats = Minz_Request::param ('categories', array ());
+			$ids = Minz_Request::param ('ids', array ());
+			$newCat = trim (Minz_Request::param ('new_category', ''));
 
 			foreach ($cats as $key => $name) {
 				if (strlen ($name) > 0) {
-					$cat = new Category ($name);
+					$cat = new FreshRSS_Category ($name);
 					$values = array (
 						'name' => $cat->name (),
 						'color' => $cat->color ()
@@ -40,7 +40,7 @@ class configureController extends ActionController {
 			}
 
 			if ($newCat != '') {
-				$cat = new Category ($newCat);
+				$cat = new FreshRSS_Category ($newCat);
 				$values = array (
 					'id' => $cat->id (),
 					'name' => $cat->name (),
@@ -55,11 +55,11 @@ class configureController extends ActionController {
 			// notif
 			$notif = array (
 				'type' => 'good',
-				'content' => Translate::t ('categories_updated')
+				'content' => Minz_Translate::t ('categories_updated')
 			);
-			Session::_param ('notification', $notif);
+			Minz_Session::_param ('notification', $notif);
 
-			Request::forward (array ('c' => 'configure', 'a' => 'categorize'), true);
+			Minz_Request::forward (array ('c' => 'configure', 'a' => 'categorize'), true);
 		}
 
 		$this->view->categories = $catDAO->listCategories (false);
@@ -67,42 +67,42 @@ class configureController extends ActionController {
 		$this->view->feeds = $feedDAO->listFeeds ();
 		$this->view->flux = false;
 
-		View::prependTitle (Translate::t ('categories_management') . ' - ');
+		Minz_View::prependTitle (Minz_Translate::t ('categories_management') . ' - ');
 	}
 
 	public function feedAction () {
-		$catDAO = new CategoryDAO ();
+		$catDAO = new FreshRSS_CategoryDAO ();
 		$this->view->categories = $catDAO->listCategories (false);
 
-		$feedDAO = new FeedDAO ();
+		$feedDAO = new FreshRSS_FeedDAO ();
 		$this->view->feeds = $feedDAO->listFeeds ();
 
-		$id = Request::param ('id');
+		$id = Minz_Request::param ('id');
 		if ($id == false && !empty ($this->view->feeds)) {
 			$id = current ($this->view->feeds)->id ();
 		}
 
 		$this->view->flux = false;
 		if ($id != false) {
-			$this->view->flux = $feedDAO->searchById ($id);
+			$this->view->flux = $this->view->feeds[$id];
 
 			if (!$this->view->flux) {
-				Error::error (
+				Minz_Error::error (
 					404,
-					array ('error' => array (Translate::t ('page_not_found')))
+					array ('error' => array (Minz_Translate::t ('page_not_found')))
 				);
 			} else {
-				$catDAO = new CategoryDAO ();
-				$this->view->categories = $catDAO->listCategories (false);
-
-				if (Request::isPost () && $this->view->flux) {
-					$name = Request::param ('name', '');
-					$hist = Request::param ('keep_history', 'no');
-					$cat = Request::param ('category', 0);
-					$path = Request::param ('path_entries', '');
-					$priority = Request::param ('priority', 0);
-					$user = Request::param ('http_user', '');
-					$pass = Request::param ('http_pass', '');
+				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', '');
+					$hist = Minz_Request::param ('keep_history', 'no');
+					$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', '');
 
 					$keep_history = false;
 					if ($hist == 'yes') {
@@ -116,6 +116,9 @@ class configureController extends ActionController {
 
 					$values = array (
 						'name' => $name,
+						'description' => $description,
+						'website' => $website,
+						'url' => $url,
 						'category' => $cat,
 						'pathEntries' => $path,
 						'priority' => $priority,
@@ -128,58 +131,58 @@ class configureController extends ActionController {
 
 						$notif = array (
 							'type' => 'good',
-							'content' => Translate::t ('feed_updated')
+							'content' => Minz_Translate::t ('feed_updated')
 						);
 					} else {
 						$notif = array (
 							'type' => 'bad',
-							'content' => Translate::t ('error_occurred_update')
+							'content' => Minz_Translate::t ('error_occurred_update')
 						);
 					}
 
-					Session::_param ('notification', $notif);
-					Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => array ('id' => $id)), true);
+					Minz_Session::_param ('notification', $notif);
+					Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => array ('id' => $id)), true);
 				}
 
-				View::prependTitle (Translate::t ('rss_feed_management') . ' - ' . $this->view->flux->name () . ' - ');
+				Minz_View::prependTitle (Minz_Translate::t ('rss_feed_management') . ' - ' . $this->view->flux->name () . ' - ');
 			}
 		} else {
-			View::prependTitle (Translate::t ('rss_feed_management') . ' - ');
+			Minz_View::prependTitle (Minz_Translate::t ('rss_feed_management') . ' - ');
 		}
 	}
 
 	public function displayAction () {
-		if (Request::isPost ()) {
+		if (Minz_Request::isPost ()) {
 			$current_token = $this->view->conf->token ();
 
-			$language = Request::param ('language', 'en');
-			$nb = Request::param ('posts_per_page', 10);
-			$mode = Request::param ('view_mode', 'normal');
-			$view = Request::param ('default_view', 'all');
-			$auto_load_more = Request::param ('auto_load_more', 'no');
-			$display = Request::param ('display_posts', 'no');
-			$onread_jump_next = Request::param ('onread_jump_next', 'no');
-			$lazyload = Request::param ('lazyload', 'no');
-			$sort = Request::param ('sort_order', 'low_to_high');
-			$old = Request::param ('old_entries', 3);
-			$mail = Request::param ('mail_login', false);
-			$anon = Request::param ('anon_access', 'no');
-			$token = Request::param ('token', $current_token);
-			$openArticle = Request::param ('mark_open_article', 'no');
-			$openSite = Request::param ('mark_open_site', 'no');
-			$scroll = Request::param ('mark_scroll', 'no');
-			$urlShaarli = Request::param ('shaarli', '');
-			$theme = Request::param ('theme', 'default');
-			$topline_read = Request::param ('topline_read', 'no');
-			$topline_favorite = Request::param ('topline_favorite', 'no');
-			$topline_date = Request::param ('topline_date', 'no');
-			$topline_link = Request::param ('topline_link', 'no');
-			$bottomline_read = Request::param ('bottomline_read', 'no');
-			$bottomline_favorite = Request::param ('bottomline_favorite', 'no');
-			$bottomline_sharing = Request::param ('bottomline_sharing', 'no');
-			$bottomline_tags = Request::param ('bottomline_tags', 'no');
-			$bottomline_date = Request::param ('bottomline_date', 'no');
-			$bottomline_link = Request::param ('bottomline_link', 'no');
+			$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);
+			$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));
@@ -198,8 +201,8 @@ class configureController extends ActionController {
 				'article' => $openArticle,
 				'site' => $openSite,
 				'scroll' => $scroll,
+				'reception' => $reception,
 			));
-			$this->view->conf->_urlShaarli ($urlShaarli);
 			$this->view->conf->_theme ($theme);
 			$this->view->conf->_topline_read ($topline_read);
 			$this->view->conf->_topline_favorite ($topline_favorite);
@@ -227,7 +230,6 @@ class configureController extends ActionController {
 				'anon_access' => $this->view->conf->anonAccess (),
 				'token' => $this->view->conf->token (),
 				'mark_when' => $this->view->conf->markWhen (),
-				'url_shaarli' => $this->view->conf->urlShaarli (),
 				'theme' => $this->view->conf->theme (),
 				'topline_read' => $this->view->conf->toplineRead () ? 'yes' : 'no',
 				'topline_favorite' => $this->view->conf->toplineFavorite () ? 'yes' : 'no',
@@ -241,44 +243,81 @@ class configureController extends ActionController {
 				'bottomline_link' => $this->view->conf->bottomlineLink () ? 'yes' : 'no',
 			);
 
-			$confDAO = new RSSConfigurationDAO ();
+			$confDAO = new FreshRSS_ConfigurationDAO ();
 			$confDAO->update ($values);
-			Session::_param ('conf', $this->view->conf);
-			Session::_param ('mail', $this->view->conf->mailLogin ());
+			Minz_Session::_param ('conf', $this->view->conf);
+			Minz_Session::_param ('mail', $this->view->conf->mailLogin ());
 
-			Session::_param ('language', $this->view->conf->language ());
-			Translate::reset ();
+			Minz_Session::_param ('language', $this->view->conf->language ());
+			Minz_Translate::reset ();
 
 			// notif
 			$notif = array (
 				'type' => 'good',
-				'content' => Translate::t ('configuration_updated')
+				'content' => Minz_Translate::t ('configuration_updated')
 			);
-			Session::_param ('notification', $notif);
+			Minz_Session::_param ('notification', $notif);
 
-			Request::forward (array ('c' => 'configure', 'a' => 'display'), true);
+			Minz_Request::forward (array ('c' => 'configure', 'a' => 'display'), true);
 		}
 
-		$this->view->themes = RSSThemes::get();
+		$this->view->themes = FreshRSS_Themes::get();
 
-		View::prependTitle (Translate::t ('general_and_reading_management') . ' - ');
+		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 ();
+	}
+
+	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'
+			));
+
+			$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')
+			);
+			Minz_Session::_param ('notification', $notif);
+
+			Minz_Request::forward (array ('c' => 'configure', 'a' => 'sharing'), true);
+		}
+
+		Minz_View::prependTitle (Minz_Translate::t ('sharing_management') . ' - ');
+
+		$entryDAO = new FreshRSS_EntryDAO ();
+		$this->view->nb_total = $entryDAO->count ();
 	}
 
 	public function importExportAction () {
-		$catDAO = new CategoryDAO ();
+		$catDAO = new FreshRSS_CategoryDAO ();
 		$this->view->categories = $catDAO->listCategories ();
 
-		$this->view->req = Request::param ('q');
+		$this->view->req = Minz_Request::param ('q');
 
 		if ($this->view->req == 'export') {
-			View::_title ('freshrss_feeds.opml');
+			Minz_View::_title ('freshrss_feeds.opml');
 
 			$this->view->_useLayout (false);
 			header('Content-Type: application/xml; charset=utf-8');
 			header('Content-disposition: attachment; filename=freshrss_feeds.opml');
 
-			$feedDAO = new FeedDAO ();
-			$catDAO = new CategoryDAO ();
+			$feedDAO = new FreshRSS_FeedDAO ();
+			$catDAO = new FreshRSS_CategoryDAO ();
 
 			$list = array ();
 			foreach ($catDAO->listCategories () as $key => $cat) {
@@ -287,7 +326,7 @@ class configureController extends ActionController {
 			}
 
 			$this->view->categories = $list;
-		} elseif ($this->view->req == 'import' && Request::isPost ()) {
+		} elseif ($this->view->req == 'import' && Minz_Request::isPost ()) {
 			if ($_FILES['file']['error'] == 0) {
 				// on parse le fichier OPML pour récupérer les catégories et les flux associés
 				try {
@@ -297,20 +336,20 @@ class configureController extends ActionController {
 
 					// On redirige vers le controller feed qui va se charger d'insérer les flux en BDD
 					// les flux sont mis au préalable dans des variables de Request
-					Request::_param ('q', 'null');
-					Request::_param ('categories', $categories);
-					Request::_param ('feeds', $feeds);
-					Request::forward (array ('c' => 'feed', 'a' => 'massiveImport'));
-				} catch (OpmlException $e) {
-					Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
+					Minz_Request::_param ('q', 'null');
+					Minz_Request::_param ('categories', $categories);
+					Minz_Request::_param ('feeds', $feeds);
+					Minz_Request::forward (array ('c' => 'feed', 'a' => 'massiveImport'));
+				} catch (FreshRSS_Opml_Exception $e) {
+					Minz_Log::record ($e->getMessage (), Minz_Log::WARNING);
 
 					$notif = array (
 						'type' => 'bad',
-						'content' => Translate::t ('bad_opml_file')
+						'content' => Minz_Translate::t ('bad_opml_file')
 					);
-					Session::_param ('notification', $notif);
+					Minz_Session::_param ('notification', $notif);
 
-					Request::forward (array (
+					Minz_Request::forward (array (
 						'c' => 'configure',
 						'a' => 'importExport'
 					), true);
@@ -318,13 +357,13 @@ class configureController extends ActionController {
 			}
 		}
 
-		$feedDAO = new FeedDAO ();
+		$feedDAO = new FreshRSS_FeedDAO ();
 		$this->view->feeds = $feedDAO->listFeeds ();
 
 		// au niveau de la vue, permet de ne pas voir un flux sélectionné dans la liste
 		$this->view->flux = false;
 
-		View::prependTitle (Translate::t ('import_export_opml') . ' - ');
+		Minz_View::prependTitle (Minz_Translate::t ('import_export_opml') . ' - ');
 	}
 
 	public function shortcutAction () {
@@ -337,10 +376,11 @@ class configureController extends ActionController {
 		                    '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');
+		                     'prev_entry', 'next_page', 'prev_page', 'collapse_entry',
+		                     'load_more');
 
-		if (Request::isPost ()) {
-			$shortcuts = Request::param ('shortcuts');
+		if (Minz_Request::isPost ()) {
+			$shortcuts = Minz_Request::param ('shortcuts');
 			$shortcuts_ok = array ();
 
 			foreach ($shortcuts as $key => $value) {
@@ -356,20 +396,20 @@ class configureController extends ActionController {
 				'shortcuts' => $this->view->conf->shortcuts ()
 			);
 
-			$confDAO = new RSSConfigurationDAO ();
+			$confDAO = new FreshRSS_ConfigurationDAO ();
 			$confDAO->update ($values);
-			Session::_param ('conf', $this->view->conf);
+			Minz_Session::_param ('conf', $this->view->conf);
 
 			// notif
 			$notif = array (
 				'type' => 'good',
-				'content' => Translate::t ('shortcuts_updated')
+				'content' => Minz_Translate::t ('shortcuts_updated')
 			);
-			Session::_param ('notification', $notif);
+			Minz_Session::_param ('notification', $notif);
 
-			Request::forward (array ('c' => 'configure', 'a' => 'shortcut'), true);
+			Minz_Request::forward (array ('c' => 'configure', 'a' => 'shortcut'), true);
 		}
 
-		View::prependTitle (Translate::t ('shortcuts_management') . ' - ');
+		Minz_View::prependTitle (Minz_Translate::t ('shortcuts_management') . ' - ');
 	}
 }

+ 112 - 0
app/Controllers/entryController.php

@@ -0,0 +1,112 @@
+<?php
+
+class FreshRSS_entry_Controller extends Minz_ActionController {
+	public function firstAction () {
+		if (login_is_conf ($this->view->conf) && !is_logged ()) {
+			Minz_Error::error (
+				403,
+				array ('error' => array (Minz_Translate::t ('access_denied')))
+			);
+		}
+
+		$this->params = array ();
+		$this->redirect = false;
+		$ajax = Minz_Request::param ('ajax');
+		if ($ajax) {
+			$this->view->_useLayout (false);
+		}
+	}
+	public function lastAction () {
+		$ajax = Minz_Request::param ('ajax');
+		if (!$ajax && $this->redirect) {
+			Minz_Request::forward (array (
+				'c' => 'index',
+				'a' => 'index',
+				'params' => $this->params
+			), true);
+		} else {
+			Minz_Request::_param ('ajax');
+		}
+	}
+
+	public function readAction () {
+		$this->redirect = true;
+
+		$id = Minz_Request::param ('id');
+		$is_read = Minz_Request::param ('is_read');
+		$get = Minz_Request::param ('get');
+		$nextGet = Minz_Request::param ('nextGet', $get); 
+		$idMax = Minz_Request::param ('idMax', 0);
+
+		$is_read = !!$is_read;
+
+		$entryDAO = new FreshRSS_EntryDAO ();
+		if ($id == false) {
+			if (!$get) {
+				$entryDAO->markReadEntries ($idMax);
+			} else {
+				$typeGet = $get[0];
+				$get = substr ($get, 2);
+				switch ($typeGet) {
+					case 'c':
+						$entryDAO->markReadCat ($get, $idMax);
+						break;
+					case 'f':
+						$entryDAO->markReadFeed ($get, $idMax);
+						break;
+					case 's':
+						$entryDAO->markReadEntries ($idMax, true);
+						break;
+					case 'a':
+						$entryDAO->markReadEntries ($idMax);
+						break;
+				}
+				if ($nextGet !== 'a') {
+					$this->params = array ('get' => $nextGet);
+				}
+			}
+
+			$notif = array (
+				'type' => 'good',
+				'content' => Minz_Translate::t ('feeds_marked_read')
+			);
+			Minz_Session::_param ('notification', $notif);
+		} else {
+			$entryDAO->markRead ($id, $is_read);
+		}
+	}
+
+	public function bookmarkAction () {
+		$this->redirect = true;
+
+		$id = Minz_Request::param ('id');
+		if ($id) {
+			$entryDAO = new FreshRSS_EntryDAO ();
+			$entryDAO->markFavorite ($id, Minz_Request::param ('is_favorite'));
+		}
+	}
+
+	public function optimizeAction() {
+		@set_time_limit(300);
+		invalidateHttpCache();
+
+		// 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();
+
+		$notif = array (
+			'type' => 'good',
+			'content' => Minz_Translate::t ('optimization_complete')
+		);
+		Minz_Session::_param ('notification', $notif);
+
+		Minz_Request::forward(array(
+			'c' => 'configure',
+			'a' => 'display'
+		), true);
+	}
+}

+ 4 - 4
app/controllers/errorController.php → app/Controllers/errorController.php

@@ -1,8 +1,8 @@
 <?php
 
-class ErrorController extends ActionController {
+class FreshRSS_error_Controller extends Minz_ActionController {
 	public function indexAction () {
-		switch (Request::param ('code')) {
+		switch (Minz_Request::param ('code')) {
 		case 403:
 			$this->view->code = 'Error 403 - Forbidden';
 			break;
@@ -19,8 +19,8 @@ class ErrorController extends ActionController {
 			$this->view->code = 'Error 404 - Not found';
 		}
 		
-		$this->view->logs = Request::param ('logs');
+		$this->view->logs = Minz_Request::param ('logs');
 		
-		View::prependTitle ($this->view->code . ' - ');
+		Minz_View::prependTitle ($this->view->code . ' - ');
 	}
 }

+ 430 - 0
app/Controllers/feedController.php

@@ -0,0 +1,430 @@
+<?php
+
+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')))
+			);
+		}
+
+		$this->catDAO = new FreshRSS_CategoryDAO ();
+		$this->catDAO->checkDefault ();
+	}
+
+	private static function entryDateComparer($e1, $e2) {
+		$d1 = $e1->date(true);
+		$d2 = $e2->date(true);
+		if ($d1 === $d2) {
+			return 0;
+		}
+		return ($d1 < $d2) ? -1 : 1;
+	}
+
+	public function addAction () {
+		@set_time_limit(300);
+
+		if (Minz_Request::isPost ()) {
+			$url = Minz_Request::param ('url_rss');
+			$cat = Minz_Request::param ('category', false);
+			if ($cat === false) {
+				$def_cat = $this->catDAO->getDefault ();
+				$cat = $def_cat->id ();
+			}
+
+			$user = Minz_Request::param ('username');
+			$pass = Minz_Request::param ('password');
+			$params = array ();
+
+			$transactionStarted = false;
+			try {
+				$feed = new FreshRSS_Feed ($url);
+				$feed->_category ($cat);
+
+				$httpAuth = '';
+				if ($user != '' || $pass != '') {
+					$httpAuth = $user . ':' . $pass;
+				}
+				$feed->_httpAuth ($httpAuth);
+
+				$feed->load ();
+
+				$feedDAO = new FreshRSS_FeedDAO ();
+				$values = array (
+					'url' => $feed->url (),
+					'category' => $feed->category (),
+					'name' => $feed->name (),
+					'website' => $feed->website (),
+					'description' => $feed->description (),
+					'lastUpdate' => time (),
+					'httpAuth' => $feed->httpAuth (),
+				);
+
+				if ($feedDAO->searchByUrl ($values['url'])) {
+					// on est déjà abonné à ce flux
+					$notif = array (
+						'type' => 'bad',
+						'content' => Minz_Translate::t ('already_subscribed', $feed->name ())
+					);
+					Minz_Session::_param ('notification', $notif);
+				} else {
+					$id = $feedDAO->addFeed ($values);
+					if (!$id) {
+						// problème au niveau de la base de données
+						$notif = array (
+							'type' => 'bad',
+							'content' => Minz_Translate::t ('feed_not_added', $feed->name ())
+						);
+						Minz_Session::_param ('notification', $notif);
+					} else {
+						$feed->_id ($id);
+						$feed->faviconPrepare();
+
+						$is_read = $this->view->conf->markUponReception() === 'yes' ? 1 : 0;
+
+						$entryDAO = new FreshRSS_EntryDAO ();
+						$entries = $feed->entries ();
+						usort($entries, 'self::entryDateComparer');
+
+						// on calcule la date des articles les plus anciens qu'on accepte
+						$nb_month_old = $this->view->conf->oldEntries ();
+						$date_min = time () - (3600 * 24 * 30 * $nb_month_old);
+
+						$transactionStarted = true;
+						$feedDAO->beginTransaction ();
+						// on ajoute les articles en masse sans vérification
+						foreach ($entries as $entry) {
+							if ($entry->date (true) >= $date_min ||
+							    $feed->keepHistory ()) {
+								$values = $entry->toArray ();
+								$values['id_feed'] = $feed->id ();
+								$values['id'] = min(time(), $entry->date (true)) . uSecString();
+								$values['is_read'] = $is_read;
+								$entryDAO->addEntry ($values);
+							}
+						}
+						$feedDAO->updateLastUpdate ($feed->id ());
+						$feedDAO->commit ();
+						$transactionStarted = false;
+
+						// ok, ajout terminé
+						$notif = array (
+							'type' => 'good',
+							'content' => Minz_Translate::t ('feed_added', $feed->name ())
+						);
+						Minz_Session::_param ('notification', $notif);
+
+						// permet de rediriger vers la page de conf du flux
+						$params['id'] = $feed->id ();
+					}
+				}
+			} catch (FreshRSS_BadUrl_Exception $e) {
+				Minz_Log::record ($e->getMessage (), Minz_Log::WARNING);
+				$notif = array (
+					'type' => 'bad',
+					'content' => Minz_Translate::t ('invalid_url', $url)
+				);
+				Minz_Session::_param ('notification', $notif);
+			} catch (FreshRSS_Feed_Exception $e) {
+				Minz_Log::record ($e->getMessage (), Minz_Log::WARNING);
+				$notif = array (
+					'type' => 'bad',
+					'content' => Minz_Translate::t ('internal_problem_feed')
+				);
+				Minz_Session::_param ('notification', $notif);
+			} catch (Minz_FileNotExistException $e) {
+				// Répertoire de cache n'existe pas
+				Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
+				$notif = array (
+					'type' => 'bad',
+					'content' => Minz_Translate::t ('internal_problem_feed')
+				);
+				Minz_Session::_param ('notification', $notif);
+			}
+			if ($transactionStarted) {
+				$feedDAO->rollBack ();
+			}
+
+			Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => $params), true);
+		}
+	}
+
+	public function truncateAction () {
+		if (Minz_Request::isPost ()) {
+			$id = Minz_Request::param ('id');
+			$feedDAO = new FreshRSS_FeedDAO ();
+			$n = $feedDAO->truncate($id);
+			$notif = array(
+				'type' => $n === false ? 'bad' : 'good',
+				'content' => Minz_Translate::t ('n_entries_deleted', $n)
+			);
+			Minz_Session::_param ('notification', $notif);
+			invalidateHttpCache();
+			Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => array('id' => $id)), true);
+		}
+	}
+
+	public function actualizeAction () {
+		@set_time_limit(300);
+
+		$feedDAO = new FreshRSS_FeedDAO ();
+		$entryDAO = new FreshRSS_EntryDAO ();
+
+		$id = Minz_Request::param ('id');
+		$force = Minz_Request::param ('force', false);
+
+		// on créé la liste des flux à mettre à actualiser
+		// si on veut mettre un flux à jour spécifiquement, on le met
+		// dans la liste, mais seul (permet d'automatiser le traitement)
+		$feeds = array ();
+		if ($id) {
+			$feed = $feedDAO->searchById ($id);
+			if ($feed) {
+				$feeds = array ($feed);
+			}
+		} else {
+			$feeds = $feedDAO->listFeedsOrderUpdate ();
+		}
+
+		// on calcule la date des articles les plus anciens qu'on accepte
+		$nb_month_old = $this->view->conf->oldEntries ();
+		$date_min = time () - (3600 * 24 * 30 * $nb_month_old);
+
+		$i = 0;
+		$flux_update = 0;
+		foreach ($feeds as $feed) {
+			try {
+				$feed->load ();
+				$feed->faviconPrepare();
+				$entries = $feed->entries ();
+				usort($entries, 'self::entryDateComparer');
+
+				$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);
+
+				// On ne vérifie pas strictement que l'article n'est pas déjà en BDD
+				// La BDD refusera l'ajout car (id_feed, guid) doit être unique
+				$feedDAO->beginTransaction ();
+				foreach ($entries as $entry) {
+					if ((!isset ($existingGuids[$entry->guid ()])) &&
+						($entry->date (true) >= $date_min ||
+						$feed->keepHistory ())) {
+						$values = $entry->toArray ();
+						//Use declared date at first import, otherwise use discovery date
+						$values['id'] = empty($existingGuids) ? min(time(), $entry->date (true)) . uSecString() : uTimeString();
+						$values['is_read'] = $is_read;
+						$entryDAO->addEntry ($values);
+					}
+				}
+
+				if ((!$feed->keepHistory()) && (rand(0, 30) === 1)) {
+					$nb = $feedDAO->cleanOldEntries ($feed->id (), $date_min, count($entries) + 10);
+					if ($nb > 0) {
+						Minz_Log::record ($nb . ' old entries cleaned in feed ' . $feed->id (), Minz_Log::DEBUG);
+					}
+				}
+
+				// on indique que le flux vient d'être mis à jour en BDD
+				$feedDAO->updateLastUpdate ($feed->id ());
+				$feedDAO->commit ();
+				$flux_update++;
+			} catch (FreshRSS_Feed_Exception $e) {
+				Minz_Log::record ($e->getMessage (), Minz_Log::NOTICE);
+				$feedDAO->updateLastUpdate ($feed->id (), 1);
+			}
+
+			// On arrête à 10 flux pour ne pas surcharger le serveur
+			// sauf si le paramètre $force est à vrai
+			$i++;
+			if ($i >= 10 && !$force) {
+				break;
+			}
+		}
+
+		$url = array ();
+		if ($flux_update === 1) {
+			// on a mis un seul flux à jour
+			$notif = array (
+				'type' => 'good',
+				'content' => Minz_Translate::t ('feed_actualized', $feed->name ())
+			);
+		} elseif ($flux_update > 1) {
+			// plusieurs flux on été mis à jour
+			$notif = array (
+				'type' => 'good',
+				'content' => Minz_Translate::t ('n_feeds_actualized', $flux_update)
+			);
+		} else {
+			// aucun flux n'a été mis à jour, oups
+			$notif = array (
+				'type' => 'bad',
+				'content' => Minz_Translate::t ('no_feed_actualized')
+			);
+		}
+
+		if ($i === 1) {
+			// Si on a voulu mettre à jour qu'un flux
+			// on filtre l'affichage par ce flux
+			$feed = reset ($feeds);
+			$url['params'] = array ('get' => 'f_' . $feed->id ());
+		}
+
+		if (Minz_Request::param ('ajax', 0) === 0) {
+			Minz_Session::_param ('notification', $notif);
+			Minz_Request::forward ($url, true);
+		} else {
+			// Une requête Ajax met un seul flux à jour.
+			// Comme en principe plusieurs requêtes ont lieu,
+			// on indique que "plusieurs flux ont été mis à jour".
+			// Cela permet d'avoir une notification plus proche du
+			// ressenti utilisateur
+			$notif = array (
+				'type' => 'good',
+				'content' => Minz_Translate::t ('feeds_actualized')
+			);
+			Minz_Session::_param ('notification', $notif);
+			// et on désactive le layout car ne sert à rien
+			$this->view->_useLayout (false);
+		}
+	}
+
+	public function massiveImportAction () {
+		@set_time_limit(300);
+
+		$entryDAO = new FreshRSS_EntryDAO ();
+		$feedDAO = new FreshRSS_FeedDAO ();
+
+		$categories = Minz_Request::param ('categories', array (), true);
+		$feeds = Minz_Request::param ('feeds', array (), true);
+
+		// on ajoute les catégories en masse dans une fonction à part
+		$this->addCategories ($categories);
+
+		// on calcule la date des articles les plus anciens qu'on accepte
+		$nb_month_old = $this->view->conf->oldEntries ();
+		$date_min = time () - (3600 * 24 * 30 * $nb_month_old);
+
+		// la variable $error permet de savoir si une erreur est survenue
+		// Le but est de ne pas arrêter l'import même en cas d'erreur
+		// L'utilisateur sera mis au courant s'il y a eu des erreurs, mais
+		// ne connaîtra pas les détails. Ceux-ci seront toutefois logguées
+		$error = false;
+		$i = 0;
+		foreach ($feeds as $feed) {
+			try {
+				$values = array (
+					'id' => $feed->id (),
+					'url' => $feed->url (),
+					'category' => $feed->category (),
+					'name' => $feed->name (),
+					'website' => $feed->website (),
+					'description' => $feed->description (),
+					'lastUpdate' => 0,
+					'httpAuth' => $feed->httpAuth ()
+				);
+
+				// ajout du flux que s'il n'est pas déjà en BDD
+				if (!$feedDAO->searchByUrl ($values['url'])) {
+					$id = $feedDAO->addFeed ($values);
+					if ($id) {
+						$feed->_id ($id);
+						$feed->faviconPrepare();
+					} else {
+						$error = true;
+					}
+				}
+			} catch (FreshRSS_Feed_Exception $e) {
+				$error = true;
+				Minz_Log::record ($e->getMessage (), Minz_Log::WARNING);
+			}
+		}
+
+		if ($error) {
+			$res = Minz_Translate::t ('feeds_imported_with_errors');
+		} else {
+			$res = Minz_Translate::t ('feeds_imported');
+		}
+
+		$notif = array (
+			'type' => 'good',
+			'content' => $res
+		);
+		Minz_Session::_param ('notification', $notif);
+		Minz_Session::_param ('actualize_feeds', true);
+
+		// et on redirige vers la page d'accueil
+		Minz_Request::forward (array (
+			'c' => 'index',
+			'a' => 'index'
+		), true);
+	}
+
+	public function deleteAction () {
+		if (Minz_Request::isPost ()) {
+			$type = Minz_Request::param ('type', 'feed');
+			$id = Minz_Request::param ('id');
+
+			$feedDAO = new FreshRSS_FeedDAO ();
+			if ($type == 'category') {
+				if ($feedDAO->deleteFeedByCategory ($id)) {
+					$notif = array (
+						'type' => 'good',
+						'content' => Minz_Translate::t ('category_emptied')
+					);
+					//TODO: Delete old favicons
+				} else {
+					$notif = array (
+						'type' => 'bad',
+						'content' => Minz_Translate::t ('error_occured')
+					);
+				}
+			} else {
+				if ($feedDAO->deleteFeed ($id)) {
+					$notif = array (
+						'type' => 'good',
+						'content' => Minz_Translate::t ('feed_deleted')
+					);
+					FreshRSS_Feed::faviconDelete($id);
+				} else {
+					$notif = array (
+						'type' => 'bad',
+						'content' => Minz_Translate::t ('error_occured')
+					);
+				}
+			}
+
+			Minz_Session::_param ('notification', $notif);
+
+			if ($type == 'category') {
+				Minz_Request::forward (array ('c' => 'configure', 'a' => 'categorize'), true);
+			} else {
+				Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed'), true);
+			}
+		}
+	}
+
+	private function addCategories ($categories) {
+		$catDAO = new FreshRSS_CategoryDAO ();
+
+		foreach ($categories as $cat) {
+			if (!$catDAO->searchByName ($cat->name ())) {
+				$values = array (
+					'id' => $cat->id (),
+					'name' => $cat->name (),
+					'color' => $cat->color ()
+				);
+				$catDAO->addCategory ($values);
+			}
+		}
+	}
+}

+ 280 - 0
app/Controllers/indexController.php

@@ -0,0 +1,280 @@
+<?php
+
+class FreshRSS_index_Controller extends Minz_ActionController {
+	private $get = false;
+	private $nb_not_read_cat = 0;
+	private $entryDAO;
+	private $feedDAO;
+	private $catDAO;
+
+	function __construct($router) {
+		parent::__construct($router);
+		$this->entryDAO = new FreshRSS_EntryDAO ();
+		$this->feedDAO = new FreshRSS_FeedDAO ();
+		$this->catDAO = new FreshRSS_CategoryDAO ();
+	}
+
+	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;
+		}
+
+		// construction of RSS url of this feed
+		$params = Minz_Request::params ();
+		$params['output'] = 'rss';
+		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',
+			'params' => $params
+		);
+
+		if ($output === 'rss') {
+			// no layout for RSS output
+			$this->view->_useLayout (false);
+			header('Content-Type: application/rss+xml; charset=utf-8');
+		} else {
+			Minz_View::appendScript (Minz_Url::display ('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js')));
+
+			if ($output === 'global') {
+				Minz_View::appendScript (Minz_Url::display ('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js')));
+			}
+		}
+
+		$this->view->cat_aside = $this->catDAO->listCategories ();
+		$this->view->nb_favorites = $this->entryDAO->countUnreadReadFavorites ();
+		$this->view->currentName = '';
+
+		$this->view->get_c = '';
+		$this->view->get_f = '';
+
+		$get = Minz_Request::param ('get', 'a');
+		$getType = $get[0];
+		$getId = substr ($get, 2);
+		if (!$this->checkAndProcessType ($getType, $getId)) {
+			Minz_Log::record ('Not found [' . $getType . '][' . $getId . ']', Minz_Log::DEBUG);
+			Minz_Error::error (
+				404,
+				array ('error' => array (Minz_Translate::t ('page_not_found')))
+			);
+			return;
+		}
+
+		$this->view->nb_not_read = FreshRSS_CategoryDAO::CountUnreads($this->view->cat_aside, 1);
+
+		// mise à jour des titres
+		$this->view->rss_title = $this->view->currentName . ' | ' . Minz_View::title();
+		if ($this->view->nb_not_read > 0) {
+			Minz_View::appendTitle (' (' . $this->view->nb_not_read . ')');
+		}
+		Minz_View::prependTitle (
+			$this->view->currentName .
+			($this->nb_not_read_cat > 0 ? ' (' . $this->nb_not_read_cat . ')' : '') .
+			' - '
+		);
+
+		// On récupère les différents éléments de filtrage
+		$this->view->state = $state = Minz_Request::param ('state', $this->view->conf->defaultView ());
+		$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 ());
+		$first = Minz_Request::param ('next', '');
+
+		if ($state === 'not_read') {	//Any unread article in this category at all?
+			switch ($getType) {
+				case 'a':
+					$hasUnread = $this->view->nb_not_read > 0;
+					break;
+				case 's':
+					$hasUnread = $this->view->nb_favorites['unread'] > 0;
+					break;
+				case 'c':
+					$hasUnread = (!isset($this->view->cat_aside[$getId])) || ($this->view->cat_aside[$getId]->nbNotRead() > 0);
+					break;
+				case 'f':
+					$myFeed = FreshRSS_CategoryDAO::findFeed($this->view->cat_aside, $getId);
+					$hasUnread = ($myFeed === null) || ($myFeed->nbNotRead() > 0);
+					break;
+				default:
+					$hasUnread = true;
+					break;
+			}
+			if (!$hasUnread) {
+				$this->view->state = $state = 'all';
+			}
+		}
+
+		$today = @strtotime('today');
+		$this->view->today = $today;
+
+		// on calcule la date des articles les plus anciens qu'on affiche
+		$nb_month_old = $this->view->conf->oldEntries ();
+		$date_min = $today - (3600 * 24 * 30 * $nb_month_old);	//Do not use a fast changing value such as time() to allow SQL caching
+
+		try {
+			$entries = $this->entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter, $date_min);
+
+			// 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
+				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);
+			}
+
+			if (count($entries) <= $nb) {
+				$this->view->nextId  = '';
+			} else {	//We have more elements for pagination
+				$lastEntry = array_pop($entries);
+				$this->view->nextId  = $lastEntry->id();
+			}
+
+			$this->view->entries = $entries;
+		} catch (FreshRSS_EntriesGetter_Exception $e) {
+			Minz_Log::record ($e->getMessage (), Minz_Log::NOTICE);
+			Minz_Error::error (
+				404,
+				array ('error' => array (Minz_Translate::t ('page_not_found')))
+			);
+		}
+	}
+
+	/*
+	 * Vérifie que la catégorie / flux sélectionné existe
+	 * + Initialise correctement les variables de vue get_c et get_f
+	 * + Met à jour la variable $this->nb_not_read_cat
+	 */
+	private function checkAndProcessType ($getType, $getId) {
+		switch ($getType) {
+			case 'a':
+				$this->view->currentName = Minz_Translate::t ('your_rss_feeds');
+				$this->view->get_c = $getType;
+				return true;
+			case 's':
+				$this->view->currentName = Minz_Translate::t ('your_favorites');
+				$this->view->get_c = $getType;
+				return true;
+			case 'c':
+				$cat = isset($this->view->cat_aside[$getId]) ? $this->view->cat_aside[$getId] : null;
+				if ($cat === null) {
+					$cat = $this->catDAO->searchById ($getId);
+				}
+				if ($cat) {
+					$this->view->currentName = $cat->name ();
+					$this->nb_not_read_cat = $cat->nbNotRead ();
+					$this->view->get_c = $getId;
+					return true;
+				} else {
+					return false;
+				}
+			case 'f':
+				$feed = FreshRSS_CategoryDAO::findFeed($this->view->cat_aside, $getId);
+				if (empty($feed)) {
+					$feed = $this->feedDAO->searchById ($getId);
+				}
+				if ($feed) {
+					$this->view->currentName = $feed->name ();
+					$this->nb_not_read_cat = $feed->nbNotRead ();
+					$this->view->get_f = $getId;
+					$this->view->get_c = $feed->category ();
+					return true;
+				} else {
+					return false;
+				}
+			default:
+				return false;
+		}
+	}
+
+	public function aboutAction () {
+		Minz_View::prependTitle (Minz_Translate::t ('about') . ' - ');
+	}
+
+	public function logsAction () {
+		if (login_is_conf ($this->view->conf) && !is_logged ()) {
+			Minz_Error::error (
+				403,
+				array ('error' => array (Minz_Translate::t ('access_denied')))
+			);
+		}
+
+		Minz_View::prependTitle (Minz_Translate::t ('logs') . ' - ');
+
+		if (Minz_Request::isPost ()) {
+			file_put_contents(LOG_PATH . '/application.log', '');
+		}
+
+		$logs = array();
+		try {
+			$logDAO = new FreshRSS_LogDAO ();
+			$logs = $logDAO->lister ();
+			$logs = array_reverse ($logs);
+		} catch (Minz_FileNotExistException $e) {
+
+		}
+
+		//gestion pagination
+		$page = Minz_Request::param ('page', 1);
+		$this->view->logsPaginator = new Minz_Paginator ($logs);
+		$this->view->logsPaginator->_nbItemsPerPage (50);
+		$this->view->logsPaginator->_currentPage ($page);
+	}
+
+	public function loginAction () {
+		$this->view->_useLayout (false);
+
+		$url = 'https://verifier.login.persona.org/verify';
+		$assert = Minz_Request::param ('assertion');
+		$params = 'assertion=' . $assert . '&audience=' .
+			  urlencode (Minz_Url::display (null, 'php', true));
+		$ch = curl_init ();
+		$options = array (
+			CURLOPT_URL => $url,
+			CURLOPT_RETURNTRANSFER => TRUE,
+			CURLOPT_POST => 2,
+			CURLOPT_POSTFIELDS => $params
+		);
+		curl_setopt_array ($ch, $options);
+		$result = curl_exec ($ch);
+		curl_close ($ch);
+
+		$res = json_decode ($result, true);
+		if ($res['status'] === 'okay' && $res['email'] === $this->view->conf->mailLogin ()) {
+			Minz_Session::_param ('mail', $res['email']);
+			invalidateHttpCache();
+		} else {
+			$res = array ();
+			$res['status'] = 'failure';
+			$res['reason'] = Minz_Translate::t ('invalid_login');
+		}
+
+		header('Content-Type: application/json; charset=UTF-8');
+		$this->view->res = json_encode ($res);
+	}
+
+	public function logoutAction () {
+		$this->view->_useLayout (false);
+		Minz_Session::_param ('mail');
+		invalidateHttpCache();
+	}
+}

+ 2 - 2
app/controllers/javascriptController.php → app/Controllers/javascriptController.php

@@ -1,13 +1,13 @@
 <?php
 
-class javascriptController extends ActionController {
+class FreshRSS_javascript_Controller extends Minz_ActionController {
 	public function firstAction () {
 		$this->view->_useLayout (false);
 		header('Content-type: text/javascript');
 	}
 
 	public function actualizeAction () {
-		$feedDAO = new FeedDAO ();
+		$feedDAO = new FreshRSS_FeedDAO ();
 		$this->view->feeds = $feedDAO->listFeeds ();
 	}
 }

+ 6 - 0
app/Exceptions/BadUrlException.php

@@ -0,0 +1,6 @@
+<?php
+class FreshRSS_BadUrl_Exception extends FreshRSS_Feed_Exception {
+	public function __construct ($url) {
+		parent::__construct ('`' . $url . '` is not a valid URL');
+	}
+}

+ 7 - 0
app/Exceptions/EntriesGetterException.php

@@ -0,0 +1,7 @@
+<?php
+
+class FreshRSS_EntriesGetter_Exception extends Exception {
+	public function __construct ($message) {
+		parent::__construct ($message);
+	}
+}

+ 1 - 2
app/models/Exception/EntriesGetterException.php → app/Exceptions/FeedException.php

@@ -1,6 +1,5 @@
 <?php
-
-class EntriesGetterException extends Exception {
+class FreshRSS_Feed_Exception extends Exception {
 	public function __construct ($message) {
 		parent::__construct ($message);
 	}

+ 6 - 0
app/Exceptions/OpmlException.php

@@ -0,0 +1,6 @@
+<?php
+class FreshRSS_Opml_Exception extends FreshRSS_Feed_Exception {
+	public function __construct ($name_file) {
+		parent::__construct ('OPML file is invalid');
+	}
+}

+ 58 - 0
app/FreshRSS.php

@@ -0,0 +1,58 @@
+<?php
+class FreshRSS extends Minz_FrontController {
+	public function init () {
+		Minz_Session::init ();
+		Minz_Translate::init ();
+
+		$this->loadParamsView ();
+		$this->loadStylesAndScripts ();
+		$this->loadNotifications ();
+	}
+
+	private function loadParamsView () {
+		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();
+		}
+
+		Minz_View::_param ('conf', $this->conf);
+		Minz_Session::_param ('language', $this->conf->language ());
+
+		$output = Minz_Request::param ('output');
+		if(!$output) {
+			$output = $this->conf->viewMode();
+			Minz_Request::_param ('output', $output);
+		}
+	}
+
+	private function loadStylesAndScripts () {
+		$theme = FreshRSS_Themes::get_infos($this->conf->theme());
+		if ($theme) {
+			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)) {
+			Minz_View::appendScript ('https://login.persona.org/include.js');
+		}
+		$includeLazyLoad = $this->conf->lazyload () === 'yes' && ($this->conf->displayPosts () === 'yes' || 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')));
+		}
+		Minz_View::appendScript (Minz_Url::display ('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js')));
+	}
+
+	private function loadNotifications () {
+		$notif = Minz_Session::param ('notification');
+		if ($notif) {
+			Minz_View::_param ('notification', $notif);
+			Minz_Session::_param ('notification');
+		}
+	}
+}

+ 85 - 0
app/Models/Category.php

@@ -0,0 +1,85 @@
+<?php
+
+class FreshRSS_Category extends Minz_Model {
+	private $id = 0;
+	private $name;
+	private $color;
+	private $nbFeed = -1;
+	private $nbNotRead = -1;
+	private $feeds = null;
+
+	public function __construct ($name = '', $color = '#0062BE', $feeds = null) {
+		$this->_name ($name);
+		$this->_color ($color);
+		if (isset ($feeds)) {
+			$this->_feeds ($feeds);
+			$this->nbFeed = 0;
+			$this->nbNotRead = 0;
+			foreach ($feeds as $feed) {
+				$this->nbFeed++;
+				$this->nbNotRead += $feed->nbNotRead ();
+			}
+		}
+	}
+
+	public function id () {
+		return $this->id;
+	}
+	public function name () {
+		return $this->name;
+	}
+	public function color () {
+		return $this->color;
+	}
+	public function nbFeed () {
+		if ($this->nbFeed < 0) {
+			$catDAO = new FreshRSS_CategoryDAO ();
+			$this->nbFeed = $catDAO->countFeed ($this->id ());
+		}
+
+		return $this->nbFeed;
+	}
+	public function nbNotRead () {
+		if ($this->nbNotRead < 0) {
+			$catDAO = new FreshRSS_CategoryDAO ();
+			$this->nbNotRead = $catDAO->countNotRead ($this->id ());
+		}
+
+		return $this->nbNotRead;
+	}
+	public function feeds () {
+		if (is_null ($this->feeds)) {
+			$feedDAO = new FreshRSS_FeedDAO ();
+			$this->feeds = $feedDAO->listByCategory ($this->id ());
+			$this->nbFeed = 0;
+			$this->nbNotRead = 0;
+			foreach ($this->feeds as $feed) {
+				$this->nbFeed++;
+				$this->nbNotRead += $feed->nbNotRead ();
+			}
+		}
+
+		return $this->feeds;
+	}
+
+	public function _id ($value) {
+		$this->id = $value;
+	}
+	public function _name ($value) {
+		$this->name = $value;
+	}
+	public function _color ($value) {
+		if (preg_match ('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) {
+			$this->color = $value;
+		} else {
+			$this->color = '#0062BE';
+		}
+	}
+	public function _feeds ($values) {
+		if (!is_array ($values)) {
+			$values = array ($values);
+		}
+
+		$this->feeds = $values;
+	}
+}

+ 250 - 0
app/Models/CategoryDAO.php

@@ -0,0 +1,250 @@
+<?php
+class FreshRSS_CategoryDAO extends Minz_ModelPdo {
+	public function addCategory ($valuesTmp) {
+		$sql = 'INSERT INTO `' . $this->prefix . 'category` (name, color) VALUES(?, ?)';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array (
+			substr($valuesTmp['name'], 0, 255),
+			substr($valuesTmp['color'], 0, 7),
+		);
+
+		if ($stm && $stm->execute ($values)) {
+			return $this->bd->lastInsertId();
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+
+	public function updateCategory ($id, $valuesTmp) {
+		$sql = 'UPDATE `' . $this->prefix . 'category` SET name=?, color=? WHERE id=?';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array (
+			$valuesTmp['name'],
+			$valuesTmp['color'],
+			$id
+		);
+
+		if ($stm && $stm->execute ($values)) {
+			return $stm->rowCount();
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+
+	public function deleteCategory ($id) {
+		$sql = 'DELETE FROM `' . $this->prefix . 'category` WHERE id=?';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array ($id);
+
+		if ($stm && $stm->execute ($values)) {
+			return $stm->rowCount();
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+
+	public function searchById ($id) {
+		$sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=?';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array ($id);
+
+		$stm->execute ($values);
+		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
+		$cat = self::daoToCategory ($res);
+
+		if (isset ($cat[0])) {
+			return $cat[0];
+		} else {
+			return false;
+		}
+	}
+	public function searchByName ($name) {
+		$sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE name=?';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array ($name);
+
+		$stm->execute ($values);
+		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
+		$cat = self::daoToCategory ($res);
+
+		if (isset ($cat[0])) {
+			return $cat[0];
+		} else {
+			return false;
+		}
+	}
+
+	public function listCategories ($prePopulateFeeds = true, $details = false) {
+		if ($prePopulateFeeds) {
+			$sql = 'SELECT c.id AS c_id, c.name AS c_name, '
+			     . ($details ? 'c.color AS c_color, ' : '')
+			     . ($details ? 'f.* ' : 'f.id, f.name, f.website, f.priority, f.error, f.cache_nbEntries, f.cache_nbUnreads ')
+			     . 'FROM `' . $this->prefix . 'category` c '
+			     . 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category = c.id '
+			     . 'GROUP BY f.id '
+			     . 'ORDER BY c.name, f.name';
+			$stm = $this->bd->prepare ($sql);
+			$stm->execute ();
+			return self::daoToCategoryPrepopulated ($stm->fetchAll (PDO::FETCH_ASSOC));
+		} else {
+			$sql = 'SELECT * FROM `' . $this->prefix . 'category` ORDER BY name';
+			$stm = $this->bd->prepare ($sql);
+			$stm->execute ();
+			return self::daoToCategory ($stm->fetchAll (PDO::FETCH_ASSOC));
+		}
+	}
+
+	public function getDefault () {
+		$sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=1';
+		$stm = $this->bd->prepare ($sql);
+
+		$stm->execute ();
+		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
+		$cat = self::daoToCategory ($res);
+
+		if (isset ($cat[0])) {
+			return $cat[0];
+		} else {
+			return false;
+		}
+	}
+	public function checkDefault () {
+		$def_cat = $this->searchById (1);
+
+		if ($def_cat === false) {
+			$cat = new FreshRSS_Category (Minz_Translate::t ('default_category'));
+			$cat->_id (1);
+
+			$values = array (
+				'id' => $cat->id (),
+				'name' => $cat->name (),
+				'color' => $cat->color ()
+			);
+
+			$this->addCategory ($values);
+		}
+	}
+
+	public function count () {
+		$sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'category`';
+		$stm = $this->bd->prepare ($sql);
+		$stm->execute ();
+		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
+
+		return $res[0]['count'];
+	}
+
+	public function countFeed ($id) {
+		$sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'feed` WHERE category=?';
+		$stm = $this->bd->prepare ($sql);
+		$values = array ($id);
+		$stm->execute ($values);
+		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
+
+		return $res[0]['count'];
+	}
+
+	public function countNotRead ($id) {
+		$sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE category=? AND e.is_read=0';
+		$stm = $this->bd->prepare ($sql);
+		$values = array ($id);
+		$stm->execute ($values);
+		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
+
+		return $res[0]['count'];
+	}
+
+	public static function findFeed($categories, $feed_id) {
+		foreach ($categories as $category) {
+			foreach ($category->feeds () as $feed) {
+				if ($feed->id () === $feed_id) {
+					return $feed;
+				}
+			}
+		}
+		return null;
+	}
+
+	public static function CountUnreads($categories, $minPriority = 0) {
+		$n = 0;
+		foreach ($categories as $category) {
+			foreach ($category->feeds () as $feed) {
+				if ($feed->priority () >= $minPriority) {
+					$n += $feed->nbNotRead();
+				}
+			}
+		}
+		return $n;
+	}
+
+	public static function daoToCategoryPrepopulated ($listDAO) {
+		$list = array ();
+
+		if (!is_array ($listDAO)) {
+			$listDAO = array ($listDAO);
+		}
+
+		$previousLine = null;
+		$feedsDao = array();
+		foreach ($listDAO as $line) {
+			if ($previousLine['c_id'] != null && $line['c_id'] !== $previousLine['c_id']) {
+				// End of the current category, we add it to the $list
+				$cat = new FreshRSS_Category (
+					$previousLine['c_name'],
+					isset($previousLine['c_color']) ? $previousLine['c_color'] : '',
+					FreshRSS_FeedDAO::daoToFeed ($feedsDao, $previousLine['c_id'])
+				);
+				$cat->_id ($previousLine['c_id']);
+				$list[$previousLine['c_id']] = $cat;
+
+				$feedsDao = array();	//Prepare for next category
+			}
+
+			$previousLine = $line;
+			$feedsDao[] = $line;
+		}
+
+		// add the last category
+		if ($previousLine != null) {
+			$cat = new FreshRSS_Category (
+				$previousLine['c_name'],
+				isset($previousLine['c_color']) ? $previousLine['c_color'] : '',
+				FreshRSS_FeedDAO::daoToFeed ($feedsDao, $previousLine['c_id'])
+			);
+			$cat->_id ($previousLine['c_id']);
+			$list[$previousLine['c_id']] = $cat;
+		}
+
+		return $list;
+	}
+
+	public static function daoToCategory ($listDAO) {
+		$list = array ();
+
+		if (!is_array ($listDAO)) {
+			$listDAO = array ($listDAO);
+		}
+
+		foreach ($listDAO as $key => $dao) {
+			$cat = new FreshRSS_Category (
+				$dao['name'],
+				$dao['color']
+			);
+			$cat->_id ($dao['id']);
+			$list[$key] = $cat;
+		}
+
+		return $list;
+	}
+}

+ 42 - 161
app/models/RSSConfiguration.php → app/Models/Configuration.php

@@ -1,6 +1,5 @@
 <?php
-
-class RSSConfiguration extends Model {
+class FreshRSS_Configuration extends Minz_Model {
 	private $available_languages = array (
 		'en' => 'English',
 		'fr' => 'Français',
@@ -17,7 +16,7 @@ class RSSConfiguration extends Model {
 	private $shortcuts = array ();
 	private $mail_login = '';
 	private $mark_when = array ();
-	private $url_shaarli = '';
+	private $sharing = array ();
 	private $theme;
 	private $anon_access;
 	private $token;
@@ -34,7 +33,7 @@ class RSSConfiguration extends Model {
 	private $bottomline_link;
 
 	public function __construct () {
-		$confDAO = new RSSConfigurationDAO ();
+		$confDAO = new FreshRSS_ConfigurationDAO ();
 		$this->_language ($confDAO->language);
 		$this->_postsPerPage ($confDAO->posts_per_page);
 		$this->_viewMode ($confDAO->view_mode);
@@ -47,8 +46,9 @@ class RSSConfiguration extends Model {
 		$this->_shortcuts ($confDAO->shortcuts);
 		$this->_mailLogin ($confDAO->mail_login);
 		$this->_markWhen ($confDAO->mark_when);
-		$this->_urlShaarli ($confDAO->url_shaarli);
+		$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);
@@ -112,8 +112,16 @@ class RSSConfiguration extends Model {
 	public function markWhenScroll () {
 		return $this->mark_when['scroll'];
 	}
-	public function urlShaarli () {
-		return $this->url_shaarli;
+	public function markUponReception () {
+		return $this->mark_when['reception'];
+	}
+	public function sharing ($key = false) {
+		if ($key === false) {
+			return $this->sharing;
+		} elseif (isset ($this->sharing[$key])) {
+			return $this->sharing[$key];
+		}
+		return false;
 	}
 	public function theme () {
 		return $this->theme;
@@ -165,11 +173,8 @@ class RSSConfiguration extends Model {
 		$this->language = $value;
 	}
 	public function _postsPerPage ($value) {
-		if (is_int (intval ($value)) && $value > 0) {
-			$this->posts_per_page = $value;
-		} else {
-			$this->posts_per_page = 10;
-		}
+		$value = intval($value);
+		$this->posts_per_page = $value > 0 ? $value : 10;
 	}
 	public function _viewMode ($value) {
 		if ($value == 'global' || $value == 'reader') {
@@ -207,14 +212,10 @@ class RSSConfiguration extends Model {
 		}
 	}
 	public function _sortOrder ($value) {
-		if ($value == 'high_to_low') {
-			$this->sort_order = 'high_to_low';
-		} else {
-			$this->sort_order = 'low_to_high';
-		}
+		$this->sort_order = $value === 'ASC' ? 'ASC' : 'DESC';
 	}
 	public function _oldEntries ($value) {
-		if (is_int (intval ($value)) && $value > 0) {
+		if (ctype_digit ($value) && $value > 0) {
 			$this->old_entries = $value;
 		} else {
 			$this->old_entries = 3;
@@ -242,18 +243,34 @@ class RSSConfiguration extends Model {
 		if(!isset($values['scroll'])) {
 			$values['scroll'] = 'yes';
 		}
+		if(!isset($values['reception'])) {
+			$values['reception'] = '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 _urlShaarli ($value) {
-		if (filter_var ($value, FILTER_VALIDATE_URL)) {
-			$this->url_shaarli = $value;
-		} elseif (version_compare(PHP_VERSION, '5.3.3', '<') && (strpos($value, '-') > 0) && ($value === filter_var($value, FILTER_SANITIZE_URL))) {	//PHP bug #51192
-			$this->url_shaarli = $value;
-		} else {
-			$this->url_shaarli = '';
+	public function _sharing ($values) {
+		$are_url = array ('shaarli', 'poche', 'diaspora');
+		foreach ($values as $key => $value) {
+			if (in_array($key, $are_url)) {
+				$is_url = (
+					filter_var ($value, FILTER_VALIDATE_URL) ||
+					(version_compare(PHP_VERSION, '5.3.3', '<') &&
+						(strpos($value, '-') > 0) &&
+						($value === filter_var($value, FILTER_SANITIZE_URL)))
+				);  //PHP bug #51192
+
+				if (!$is_url) {
+					$value = '';
+				}
+			} elseif(!is_bool ($value)) {
+				$value = true;
+			}
+
+			$this->sharing[$key] = $value;
 		}
 	}
 	public function _theme ($value) {
@@ -307,139 +324,3 @@ class RSSConfiguration extends Model {
 		$this->bottomline_link = $value === 'yes';
 	}
 }
-
-class RSSConfigurationDAO extends Model_array {
-	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 = 'low_to_high';
-	public $old_entries = 3;
-	public $shortcuts = array (
-		'mark_read' => 'r',
-		'mark_favorite' => 'f',
-		'go_website' => 'space',
-		'next_entry' => 'j',
-		'prev_entry' => 'k'
-	);
-	public $mail_login = '';
-	public $mark_when = array (
-		'article' => 'yes',
-		'site' => 'yes',
-		'scroll' => 'no'
-	);
-	public $url_shaarli = '';
-	public $theme = 'default';
-	public $anon_access = 'no';
-	public $token = '';
-	public $auto_load_more = 'no';
-	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 () {
-		parent::__construct (PUBLIC_PATH . '/data/Configuration.array.php');
-
-		// TODO : simplifier ce code, une boucle for() devrait suffir !
-		if (isset ($this->array['language'])) {
-			$this->language = $this->array['language'];
-		}
-		if (isset ($this->array['posts_per_page'])) {
-			$this->posts_per_page = $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 = $this->array['old_entries'];
-		}
-		if (isset ($this->array['shortcuts'])) {
-			$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['url_shaarli'])) {
-			$this->url_shaarli = $this->array['url_shaarli'];
-		}
-		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);
-	}
-}

+ 156 - 0
app/Models/ConfigurationDAO.php

@@ -0,0 +1,156 @@
+<?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 $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 = $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 = $this->array['old_entries'];
+		}
+		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 - 1
app/models/Days.php → app/Models/Days.php

@@ -1,6 +1,6 @@
 <?php
 
-class Days {
+class FreshRSS_Days {
 	const TODAY = 0;
 	const YESTERDAY = 1;
 	const BEFORE_YESTERDAY = 2;

+ 192 - 0
app/Models/Entry.php

@@ -0,0 +1,192 @@
+<?php
+class FreshRSS_Entry extends Minz_Model {
+
+	private $id = 0;
+	private $guid;
+	private $title;
+	private $author;
+	private $content;
+	private $link;
+	private $date;
+	private $is_read;
+	private $is_favorite;
+	private $feed;
+	private $tags;
+
+	public function __construct ($feed = '', $guid = '', $title = '', $author = '', $content = '',
+	                             $link = '', $pubdate = 0, $is_read = false, $is_favorite = false, $tags = '') {
+		$this->_guid ($guid);
+		$this->_title ($title);
+		$this->_author ($author);
+		$this->_content ($content);
+		$this->_link ($link);
+		$this->_date ($pubdate);
+		$this->_isRead ($is_read);
+		$this->_isFavorite ($is_favorite);
+		$this->_feed ($feed);
+		$this->_tags (preg_split('/[\s#]/', $tags));
+	}
+
+	public function id () {
+		return $this->id;
+	}
+	public function guid () {
+		return $this->guid;
+	}
+	public function title () {
+		return $this->title;
+	}
+	public function author () {
+		if (is_null ($this->author)) {
+			return '';
+		} else {
+			return $this->author;
+		}
+	}
+	public function content () {
+		return $this->content;
+	}
+	public function link () {
+		return $this->link;
+	}
+	public function date ($raw = false) {
+		if ($raw) {
+			return $this->date;
+		} else {
+			return timestamptodate ($this->date);
+		}
+	}
+	public function dateAdded ($raw = false) {
+		$date = intval(substr($this->id, 0, -6));
+		if ($raw) {
+			return $date;
+		} else {
+			return timestamptodate ($date);
+		}
+	}
+	public function isRead () {
+		return $this->is_read;
+	}
+	public function isFavorite () {
+		return $this->is_favorite;
+	}
+	public function feed ($object = false) {
+		if ($object) {
+			$feedDAO = new FreshRSS_FeedDAO ();
+			return $feedDAO->searchById ($this->feed);
+		} else {
+			return $this->feed;
+		}
+	}
+	public function tags ($inString = false) {
+		if ($inString) {
+			return empty ($this->tags) ? '' : '#' . implode(' #', $this->tags);
+		} else {
+			return $this->tags;
+		}
+	}
+
+	public function _id ($value) {
+		$this->id = $value;
+	}
+	public function _guid ($value) {
+		$this->guid = $value;
+	}
+	public function _title ($value) {
+		$this->title = $value;
+	}
+	public function _author ($value) {
+		$this->author = $value;
+	}
+	public function _content ($value) {
+		$this->content = $value;
+	}
+	public function _link ($value) {
+		$this->link = $value;
+	}
+	public function _date ($value) {
+		if (ctype_digit ($value)) {
+			$this->date = intval ($value);
+		} else {
+			$this->date = time ();
+		}
+	}
+	public function _isRead ($value) {
+		$this->is_read = $value;
+	}
+	public function _isFavorite ($value) {
+		$this->is_favorite = $value;
+	}
+	public function _feed ($value) {
+		$this->feed = $value;
+	}
+	public function _tags ($value) {
+		if (!is_array ($value)) {
+			$value = array ($value);
+		}
+
+		foreach ($value as $key => $t) {
+			if (!$t) {
+				unset ($value[$key]);
+			}
+		}
+
+		$this->tags = $value;
+	}
+
+	public function isDay ($day, $today) {
+		$date = $this->dateAdded(true);
+		switch ($day) {
+			case FreshRSS_Days::TODAY:
+				$tomorrow = $today + 86400;
+				return $date >= $today && $date < $tomorrow;
+			case FreshRSS_Days::YESTERDAY:
+				$yesterday = $today - 86400;
+				return $date >= $yesterday && $date < $today;
+			case FreshRSS_Days::BEFORE_YESTERDAY:
+				$yesterday = $today - 86400;
+				return $date < $yesterday;
+			default:
+				return false;
+		}
+	}
+
+	public function loadCompleteContent($pathEntries) {
+		// Gestion du contenu
+		// On cherche à récupérer les articles en entier... même si le flux ne le propose pas
+		if ($pathEntries) {
+			$entryDAO = new FreshRSS_EntryDAO();
+			$entry = $entryDAO->searchByGuid($this->feed, $this->guid);
+
+			if($entry) {
+				// l'article existe déjà en BDD, en se contente de recharger ce contenu
+				$this->content = $entry->content();
+			} else {
+				try {
+					// l'article n'est pas en BDD, on va le chercher sur le site
+					$this->content = get_content_by_parsing(
+						$this->link(), $pathEntries
+					);
+				} catch (Exception $e) {
+					// rien à faire, on garde l'ancien contenu (requête a échoué)
+				}
+			}
+		}
+	}
+
+	public function toArray () {
+		return array (
+			'id' => $this->id (),
+			'guid' => $this->guid (),
+			'title' => $this->title (),
+			'author' => $this->author (),
+			'content' => $this->content (),
+			'link' => $this->link (),
+			'date' => $this->date (true),
+			'is_read' => $this->isRead (),
+			'is_favorite' => $this->isFavorite (),
+			'id_feed' => $this->feed (),
+			'tags' => $this->tags (true),
+		);
+	}
+}

+ 464 - 0
app/Models/EntryDAO.php

@@ -0,0 +1,464 @@
+<?php
+class FreshRSS_EntryDAO extends Minz_ModelPdo {
+	public function addEntry ($valuesTmp) {
+		$sql = 'INSERT INTO `' . $this->prefix . 'entry`(id, guid, title, author, content_bin, link, date, is_read, is_favorite, id_feed, tags) '
+		     . 'VALUES(?, ?, ?, ?, COMPRESS(?), ?, ?, ?, ?, ?, ?)';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array (
+			$valuesTmp['id'],
+			substr($valuesTmp['guid'], 0, 760),
+			substr($valuesTmp['title'], 0, 255),
+			substr($valuesTmp['author'], 0, 255),
+			$valuesTmp['content'],
+			substr($valuesTmp['link'], 0, 1023),
+			$valuesTmp['date'],
+			$valuesTmp['is_read'],
+			$valuesTmp['is_favorite'],
+			$valuesTmp['id_feed'],
+			substr($valuesTmp['tags'], 0, 1023),
+		);
+
+		if ($stm && $stm->execute ($values)) {
+			return $this->bd->lastInsertId();
+		} else {
+			$info = $stm->errorInfo();
+			if ((int)($info[0] / 1000) !== 23) {	//Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries
+				Minz_Log::record ('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
+				. ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title'], Minz_Log::ERROR);
+			} /*else {
+				Minz_Log::record ('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2]
+				. ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title'], Minz_Log::DEBUG);
+			}*/
+			return false;
+		}
+	}
+
+	public function markFavorite ($id, $is_favorite = true) {
+		$sql = 'UPDATE `' . $this->prefix . 'entry` e '
+		     . 'SET e.is_favorite = ? '
+		     . 'WHERE e.id=?';
+		$values = array ($is_favorite ? 1 : 0, $id);
+		$stm = $this->bd->prepare ($sql);
+		if ($stm && $stm->execute ($values)) {
+			return $stm->rowCount();
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+	public function markRead ($id, $is_read = true) {
+		$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+		     . 'SET e.is_read = ?,'
+		     . 'f.cache_nbUnreads=f.cache_nbUnreads' . ($is_read ? '-' : '+') . '1 '
+		     . 'WHERE e.id=?';
+		$values = array ($is_read ? 1 : 0, $id);
+		$stm = $this->bd->prepare ($sql);
+		if ($stm && $stm->execute ($values)) {
+			return $stm->rowCount();
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+	public function markReadEntries ($idMax = 0, $favorites = false) {
+		if ($idMax === 0) {
+			$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+			     . 'SET e.is_read = 1, f.cache_nbUnreads=0 '
+			     . 'WHERE e.is_read = 0 AND ';
+			if ($favorites) {
+				$sql .= 'e.is_favorite = 1';
+			} else {
+				$sql .= 'f.priority > 0';
+			}
+			$stm = $this->bd->prepare ($sql);
+			if ($stm && $stm->execute ()) {
+				return $stm->rowCount();
+			} else {
+				$info = $stm->errorInfo();
+				Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+				return false;
+			}
+		} else {
+			$this->bd->beginTransaction ();
+
+			$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+			     . 'SET e.is_read = 1 '
+			     . 'WHERE e.is_read = 0 AND e.id <= ? AND ';
+			if ($favorites) {
+				$sql .= 'e.is_favorite = 1';
+			} else {
+				$sql .= 'f.priority > 0';
+			}
+			$values = array ($idMax);
+			$stm = $this->bd->prepare ($sql);
+			if (!($stm && $stm->execute ($values))) {
+				$info = $stm->errorInfo();
+				Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+				$this->bd->rollBack ();
+				return false;
+			}
+			$affected = $stm->rowCount();
+
+			if ($affected > 0) {
+				$sql = 'UPDATE `' . $this->prefix . 'feed` f '
+			     . 'LEFT OUTER JOIN ('
+			     .	'SELECT e.id_feed, '
+			     .	'COUNT(*) AS nbUnreads '
+			     .	'FROM `' . $this->prefix . 'entry` e '
+			     .	'WHERE e.is_read = 0 '
+			     .	'GROUP BY e.id_feed'
+			     . ') x ON x.id_feed=f.id '
+			     . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0)';
+				$stm = $this->bd->prepare ($sql);
+				if (!($stm && $stm->execute ())) {
+					$info = $stm->errorInfo();
+					Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+					$this->bd->rollBack ();
+					return false;
+				}
+			}
+
+			$this->bd->commit ();
+			return $affected;
+		}
+	}
+	public function markReadCat ($id, $idMax = 0) {
+		if ($idMax === 0) {
+			$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+			     . 'SET e.is_read = 1, f.cache_nbUnreads=0 '
+			     . 'WHERE f.category = ? AND e.is_read = 0';
+			$values = array ($id);
+			$stm = $this->bd->prepare ($sql);
+			if ($stm && $stm->execute ($values)) {
+				return $stm->rowCount();
+			} else {
+				$info = $stm->errorInfo();
+				Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+				return false;
+			}
+		} else {
+			$this->bd->beginTransaction ();
+
+			$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+			     . 'SET e.is_read = 1 '
+			     . 'WHERE f.category = ? AND e.is_read = 0 AND e.id <= ?';
+			$values = array ($id, $idMax);
+			$stm = $this->bd->prepare ($sql);
+			if (!($stm && $stm->execute ($values))) {
+				$info = $stm->errorInfo();
+				Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+				$this->bd->rollBack ();
+				return false;
+			}
+			$affected = $stm->rowCount();
+
+			if ($affected > 0) {
+				$sql = 'UPDATE `' . $this->prefix . 'feed` f '
+			     . 'LEFT OUTER JOIN ('
+			     .	'SELECT e.id_feed, '
+			     .	'COUNT(*) AS nbUnreads '
+			     .	'FROM `' . $this->prefix . 'entry` e '
+			     .	'WHERE e.is_read = 0 '
+			     .	'GROUP BY e.id_feed'
+			     . ') x ON x.id_feed=f.id '
+			     . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) '
+			     . 'WHERE f.category = ?';
+				$values = array ($id);
+				$stm = $this->bd->prepare ($sql);
+				if (!($stm && $stm->execute ($values))) {
+					$info = $stm->errorInfo();
+					Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+					$this->bd->rollBack ();
+					return false;
+				}
+			}
+
+			$this->bd->commit ();
+			return $affected;
+		}
+	}
+	public function markReadFeed ($id, $idMax = 0) {
+		if ($idMax === 0) {
+			$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+			     . 'SET e.is_read = 1, f.cache_nbUnreads=0 '
+			     . 'WHERE f.id=? AND e.is_read = 0';
+			$values = array ($id);
+			$stm = $this->bd->prepare ($sql);
+			if ($stm && $stm->execute ($values)) {
+				return $stm->rowCount();
+			} else {
+				$info = $stm->errorInfo();
+				Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+				return false;
+			}
+		} else {
+			$this->bd->beginTransaction ();
+
+			$sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+			     . 'SET e.is_read = 1 '
+			     . 'WHERE f.id=? AND e.is_read = 0 AND e.id <= ?';
+			$values = array ($id, $idMax);
+			$stm = $this->bd->prepare ($sql);
+			if (!($stm && $stm->execute ($values))) {
+				$info = $stm->errorInfo();
+				Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+				$this->bd->rollBack ();
+				return false;
+			}
+			$affected = $stm->rowCount();
+
+			if ($affected > 0) {
+				$sql = 'UPDATE `' . $this->prefix . 'feed` f '
+				     . 'SET f.cache_nbUnreads=f.cache_nbUnreads-' . $affected
+				     . ' WHERE f.id=?';
+				$values = array ($id);
+				$stm = $this->bd->prepare ($sql);
+				if (!($stm && $stm->execute ($values))) {
+					$info = $stm->errorInfo();
+					Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+					$this->bd->rollBack ();
+					return false;
+				}
+			}
+
+			$this->bd->commit ();
+			return $affected;
+		}
+	}
+
+	public function searchByGuid ($feed_id, $id) {
+		// un guid est unique pour un flux donné
+		$sql = 'SELECT id, guid, title, author, UNCOMPRESS(content_bin) AS content, link, date, is_read, is_favorite, id_feed, tags '
+		     . 'FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid=?';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array (
+			$feed_id,
+			$id
+		);
+
+		$stm->execute ($values);
+		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
+		$entries = self::daoToEntry ($res);
+		return isset ($entries[0]) ? $entries[0] : false;
+	}
+
+	public function searchById ($id) {
+		$sql = 'SELECT id, guid, title, author, UNCOMPRESS(content_bin) AS content, link, date, is_read, is_favorite, id_feed, tags '
+		     . 'FROM `' . $this->prefix . 'entry` WHERE id=?';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array ($id);
+
+		$stm->execute ($values);
+		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
+		$entries = self::daoToEntry ($res);
+		return isset ($entries[0]) ? $entries[0] : false;
+	}
+
+	public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) {
+		$where = '';
+		$joinFeed = false;
+		$values = array();
+		switch ($type) {
+			case 'a':
+				$where .= 'f.priority > 0 ';
+				$joinFeed = true;
+				break;
+			case 's':
+				$where .= 'e1.is_favorite = 1 ';
+				break;
+			case 'c':
+				$where .= 'f.category = ? ';
+				$values[] = intval($id);
+				$joinFeed = true;
+				break;
+			case 'f':
+				$where .= 'e1.id_feed = ? ';
+				$values[] = intval($id);
+				break;
+			default:
+				throw new FreshRSS_EntriesGetter_Exception ('Bad type in Entry->listByType: [' . $type . ']!');
+		}
+		switch ($state) {
+			case 'all':
+				break;
+			case 'not_read':
+				$where .= 'AND e1.is_read = 0 ';
+				break;
+			case 'read':
+				$where .= 'AND e1.is_read = 1 ';
+				break;
+			default:
+				throw new FreshRSS_EntriesGetter_Exception ('Bad state in Entry->listByType: [' . $state . ']!');
+		}
+		switch ($order) {
+			case 'DESC':
+			case 'ASC':
+				break;
+			default:
+				throw new FreshRSS_EntriesGetter_Exception ('Bad order in Entry->listByType: [' . $order . ']!');
+		}
+		if ($firstId !== '') {
+			$where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' ';
+		}
+		if (($date_min > 0) && ($type !== 's')) {
+			$where .= 'AND (e1.id >= ' . $date_min . '000000 OR e1.is_favorite = 1 OR f.keep_history = 1) ';
+			$joinFeed = true;
+		}
+		$search = '';
+		if ($filter !== '') {
+			$filter = trim($filter);
+			$filter = addcslashes($filter, '\\%_');
+			if (stripos($filter, 'intitle:') === 0) {
+				$filter = substr($filter, strlen('intitle:'));
+				$intitle = true;
+			} else {
+				$intitle = false;
+			}
+			if (stripos($filter, 'inurl:') === 0) {
+				$filter = substr($filter, strlen('inurl:'));
+				$inurl = true;
+			} else {
+				$inurl = false;
+			}
+			if (stripos($filter, 'author:') === 0) {
+				$filter = substr($filter, strlen('author:'));
+				$author = true;
+			} else {
+				$author = false;
+			}
+			$terms = array_unique(explode(' ', $filter));
+			sort($terms);	//Put #tags first
+			foreach ($terms as $word) {
+				$word = trim($word);
+				if (strlen($word) > 0) {
+					if ($intitle) {
+						$search .= 'AND e1.title LIKE ? ';
+						$values[] = '%' . $word .'%';
+					} elseif ($inurl) {
+						$search .= 'AND CONCAT(e1.link, e1.guid) LIKE ? ';
+						$values[] = '%' . $word .'%';
+					} elseif ($author) {
+						$search .= 'AND e1.author LIKE ? ';
+						$values[] = '%' . $word .'%';
+					} else {
+						if ($word[0] === '#' && isset($word[1])) {
+							$search .= 'AND e1.tags LIKE ? ';
+							$values[] = '%' . $word .'%';
+						} else {
+							$search .= 'AND CONCAT(e1.title, UNCOMPRESS(e1.content_bin)) LIKE ? ';
+							$values[] = '%' . $word .'%';
+						}
+					}
+				}
+			}
+		}
+
+		$sql = 'SELECT e.id, e.guid, e.title, e.author, UNCOMPRESS(e.content_bin) AS content, e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags '
+		     . 'FROM `' . $this->prefix . 'entry` e '
+		     . 'INNER JOIN (SELECT e1.id FROM `' . $this->prefix . 'entry` e1 '
+			     . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed = f.id ' : '')
+			     . 'WHERE ' . $where
+			     . $search
+			     . 'ORDER BY e1.id ' . $order
+			     . ($limit > 0 ? ' LIMIT ' . $limit : '')	//TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
+		     . ') e2 ON e2.id = e.id '
+		     . 'ORDER BY e.id ' . $order;
+
+		$stm = $this->bd->prepare ($sql);
+		$stm->execute ($values);
+
+		return self::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC));
+	}
+
+	public function listLastGuidsByFeed($id, $n) {
+		$sql = 'SELECT guid FROM `' . $this->prefix . 'entry` WHERE id_feed=? ORDER BY id DESC LIMIT ' . intval($n);
+		$stm = $this->bd->prepare ($sql);
+		$values = array ($id);
+		$stm->execute ($values);
+		return $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+	}
+
+	public function countUnreadRead () {
+		$sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE priority > 0'
+		     . ' UNION SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE priority > 0 AND is_read = 0';
+		$stm = $this->bd->prepare ($sql);
+		$stm->execute ();
+		$res = $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+		$all = empty($res[0]) ? 0 : $res[0];
+		$unread = empty($res[1]) ? 0 : $res[1];
+		return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread);
+	}
+	public function count ($minPriority = null) {
+		$sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id';
+		if ($minPriority !== null) {
+			$sql = ' WHERE priority > ' . intval($minPriority);
+		}
+		$stm = $this->bd->prepare ($sql);
+		$stm->execute ();
+		$res = $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+		return $res[0];
+	}
+	public function countNotRead ($minPriority = null) {
+		$sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE is_read = 0';
+		if ($minPriority !== null) {
+			$sql = ' AND priority > ' . intval($minPriority);
+		}
+		$stm = $this->bd->prepare ($sql);
+		$stm->execute ();
+		$res = $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+		return $res[0];
+	}
+
+	public function countUnreadReadFavorites () {
+		$sql = 'SELECT COUNT(id) FROM `' . $this->prefix . 'entry` WHERE is_favorite=1'
+		     . ' UNION SELECT COUNT(id) FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 AND is_read = 0';
+		$stm = $this->bd->prepare ($sql);
+		$stm->execute ();
+		$res = $stm->fetchAll (PDO::FETCH_COLUMN, 0);
+		$all = empty($res[0]) ? 0 : $res[0];
+		$unread = empty($res[1]) ? 0 : $res[1];
+		return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread);
+	}
+
+	public function optimizeTable() {
+		$sql = 'OPTIMIZE TABLE `' . $this->prefix . 'entry`';
+		$stm = $this->bd->prepare ($sql);
+		$stm->execute ();
+	}
+
+	public static function daoToEntry ($listDAO) {
+		$list = array ();
+
+		if (!is_array ($listDAO)) {
+			$listDAO = array ($listDAO);
+		}
+
+		foreach ($listDAO as $key => $dao) {
+			$entry = new FreshRSS_Entry (
+				$dao['id_feed'],
+				$dao['guid'],
+				$dao['title'],
+				$dao['author'],
+				$dao['content'],
+				$dao['link'],
+				$dao['date'],
+				$dao['is_read'],
+				$dao['is_favorite'],
+				$dao['tags']
+			);
+			if (isset ($dao['id'])) {
+				$entry->_id ($dao['id']);
+			}
+			$list[] = $entry;
+		}
+
+		unset ($listDAO);
+
+		return $list;
+	}
+}

+ 319 - 0
app/Models/Feed.php

@@ -0,0 +1,319 @@
+<?php
+class FreshRSS_Feed extends Minz_Model {
+	private $id = 0;
+	private $url;
+	private $category = 1;
+	private $nbEntries = -1;
+	private $nbNotRead = -1;
+	private $entries = null;
+	private $name = '';
+	private $website = '';
+	private $description = '';
+	private $lastUpdate = 0;
+	private $priority = 10;
+	private $pathEntries = '';
+	private $httpAuth = '';
+	private $error = false;
+	private $keep_history = false;
+
+	public function __construct ($url, $validate=true) {
+		if ($validate) {
+			$this->_url ($url);
+		} else {
+			$this->url = $url;
+		}
+	}
+
+	public function id () {
+		return $this->id;
+	}
+	public function url () {
+		return $this->url;
+	}
+	public function category () {
+		return $this->category;
+	}
+	public function entries () {
+		if (!is_null ($this->entries)) {
+			return $this->entries;
+		} else {
+			return array ();
+		}
+	}
+	public function name () {
+		return $this->name;
+	}
+	public function website () {
+		return $this->website;
+	}
+	public function description () {
+		return $this->description;
+	}
+	public function lastUpdate () {
+		return $this->lastUpdate;
+	}
+	public function priority () {
+		return $this->priority;
+	}
+	public function pathEntries () {
+		return $this->pathEntries;
+	}
+	public function httpAuth ($raw = true) {
+		if ($raw) {
+			return $this->httpAuth;
+		} else {
+			$pos_colon = strpos ($this->httpAuth, ':');
+			$user = substr ($this->httpAuth, 0, $pos_colon);
+			$pass = substr ($this->httpAuth, $pos_colon + 1);
+
+			return array (
+				'username' => $user,
+				'password' => $pass
+			);
+		}
+	}
+	public function inError () {
+		return $this->error;
+	}
+	public function keepHistory () {
+		return $this->keep_history;
+	}
+	public function nbEntries () {
+		if ($this->nbEntries < 0) {
+			$feedDAO = new FreshRSS_FeedDAO ();
+			$this->nbEntries = $feedDAO->countEntries ($this->id ());
+		}
+
+		return $this->nbEntries;
+	}
+	public function nbNotRead () {
+		if ($this->nbNotRead < 0) {
+			$feedDAO = new FreshRSS_FeedDAO ();
+			$this->nbNotRead = $feedDAO->countNotRead ($this->id ());
+		}
+
+		return $this->nbNotRead;
+	}
+	public function faviconPrepare() {
+		$file = DATA_PATH . '/favicons/' . $this->id () . '.txt';
+		if (!file_exists ($file)) {
+			$t = $this->website;
+			if (empty($t)) {
+				$t = $this->url;
+			}
+			file_put_contents($file, $t);
+		}
+	}
+	public static function faviconDelete($id) {
+		$path = DATA_PATH . '/favicons/' . $id;
+		@unlink($path . '.ico');
+		@unlink($path . '.txt');
+	}
+	public function favicon () {
+		return Minz_Url::display ('/f.php?' . $this->id ());
+	}
+
+	public function _id ($value) {
+		$this->id = $value;
+	}
+	public function _url ($value, $validate=true) {
+		if ($validate) {
+			$value = checkUrl($value);
+		}
+		if (empty ($value)) {
+			throw new FreshRSS_BadUrl_Exception ($value);
+		}
+		$this->url = $value;
+	}
+	public function _category ($value) {
+		$this->category = $value;
+	}
+	public function _name ($value) {
+		if (is_null ($value)) {
+			$value = '';
+		}
+		$this->name = $value;
+	}
+	public function _website ($value, $validate=true) {
+		if ($validate) {
+			$value = checkUrl($value);
+		}
+		if (empty ($value)) {
+			$value = '';
+		}
+		$this->website = $value;
+	}
+	public function _description ($value) {
+		if (is_null ($value)) {
+			$value = '';
+		}
+		$this->description = $value;
+	}
+	public function _lastUpdate ($value) {
+		$this->lastUpdate = $value;
+	}
+	public function _priority ($value) {
+		$this->priority = ctype_digit ($value) ? intval ($value) : 10;
+	}
+	public function _pathEntries ($value) {
+		$this->pathEntries = $value;
+	}
+	public function _httpAuth ($value) {
+		$this->httpAuth = $value;
+	}
+	public function _error ($value) {
+		if ($value) {
+			$value = true;
+		} else {
+			$value = false;
+		}
+		$this->error = $value;
+	}
+	public function _keepHistory ($value) {
+		if ($value) {
+			$value = true;
+		} else {
+			$value = false;
+		}
+		$this->keep_history = $value;
+	}
+	public function _nbNotRead ($value) {
+		$this->nbNotRead = ctype_digit ($value) ? intval ($value) : -1;
+	}
+	public function _nbEntries ($value) {
+		$this->nbEntries = ctype_digit ($value) ? intval ($value) : -1;
+	}
+
+	public function load () {
+		if (!is_null ($this->url)) {
+			if (CACHE_PATH === false) {
+				throw new Minz_FileNotExistException (
+					'CACHE_PATH',
+					Minz_Exception::ERROR
+				);
+			} else {
+				$feed = new SimplePie ();
+				$feed->set_useragent(Minz_Translate::t ('freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ') ' . SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION);
+				$url = htmlspecialchars_decode ($this->url, ENT_QUOTES);
+				if ($this->httpAuth != '') {
+					$url = preg_replace ('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $url);
+				}
+
+				$feed->set_feed_url ($url);
+				$feed->set_cache_location (CACHE_PATH);
+				$feed->set_cache_duration(1500);
+				$feed->strip_htmltags (array (
+					'base', 'blink', 'body', 'doctype', 'embed',
+					'font', 'form', 'frame', 'frameset', 'html',
+					'input', 'marquee', 'meta', 'noscript',
+					'object', 'param', 'plaintext', 'script', 'style',
+				));
+				$feed->strip_attributes(array_merge($feed->strip_attributes, array(
+					'autoplay', 'onload', 'onunload', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup',
+					'onmouseover', 'onmousemove', 'onmouseout', 'onfocus', 'onblur',
+					'onkeypress', 'onkeydown', 'onkeyup', 'onselect', 'onchange', 'seamless')));
+				$feed->add_attributes(array(
+					'img' => array('lazyload' => ''),	//http://www.w3.org/TR/resource-priorities/
+					'audio' => array('preload' => 'none'),
+					'iframe' => array('postpone' => '', 'sandbox' => 'allow-scripts allow-same-origin'),
+					'video' => array('postpone' => '', 'preload' => 'none'),
+				));
+				$feed->set_url_replacements(array(
+					'a' => 'href',
+					'area' => 'href',
+					'audio' => 'src',
+					'blockquote' => 'cite',
+					'del' => 'cite',
+					'form' => 'action',
+					'iframe' => 'src',
+					'img' => array(
+						'longdesc',
+						'src'
+					),
+					'input' => 'src',
+					'ins' => 'cite',
+					'q' => 'cite',
+					'source' => 'src',
+					'track' => 'src',
+					'video' => array(
+						'poster',
+						'src',
+					),
+				));
+				$feed->init ();
+
+				if ($feed->error ()) {
+					throw new FreshRSS_Feed_Exception ($feed->error . ' [' . $url . ']');
+				}
+
+				// 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 ($this->httpAuth != '') {
+						// on enlève les id si authentification HTTP
+						$subscribe_url = preg_replace ('#((.+)://)((.+)@)(.+)#', '${1}${5}', $subscribe_url);
+					}
+					$this->_url ($subscribe_url);
+				}
+
+				$title = $feed->get_title ();
+				$this->_name (!is_null ($title) ? $title : $this->url);
+
+				$this->_website ($feed->get_link ());
+				$this->_description ($feed->get_description ());
+
+				// et on charge les articles du flux
+				$this->loadEntries ($feed);
+			}
+		}
+	}
+	private function loadEntries ($feed) {
+		$entries = array ();
+
+		foreach ($feed->get_items () as $item) {
+			$title = html_only_entity_decode (strip_tags ($item->get_title ()));
+			$author = $item->get_author ();
+			$link = $item->get_permalink ();
+			$date = @strtotime ($item->get_date ());
+
+			// gestion des tags (catégorie == tag)
+			$tags_tmp = $item->get_categories ();
+			$tags = array ();
+			if (!is_null ($tags_tmp)) {
+				foreach ($tags_tmp as $tag) {
+					$tags[] = html_only_entity_decode ($tag->get_label ());
+				}
+			}
+
+			$content = html_only_entity_decode ($item->get_content ());
+
+			$elinks = array();
+			foreach ($item->get_enclosures() as $enclosure) {
+				$elink = $enclosure->get_link();
+				if (array_key_exists($elink, $elinks)) continue;
+				$elinks[$elink] = '1';
+				$mime = strtolower($enclosure->get_type());
+				if (strpos($mime, 'image/') === 0) {
+					$content .= '<br /><img src="' . $elink . '" alt="" />';
+				}
+			}
+
+			$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 : '',
+				$date ? $date : time ()
+			);
+			$entry->_tags ($tags);
+			// permet de récupérer le contenu des flux tronqués
+			$entry->loadCompleteContent($this->pathEntries());
+
+			$entries[] = $entry;
+		}
+
+		$this->entries = $entries;
+	}
+}

+ 339 - 0
app/Models/FeedDAO.php

@@ -0,0 +1,339 @@
+<?php
+class FreshRSS_FeedDAO extends Minz_ModelPdo {
+	public function addFeed ($valuesTmp) {
+		$sql = 'INSERT INTO `' . $this->prefix . 'feed` (url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history) VALUES(?, ?, ?, ?, ?, ?, 10, ?, 0, 0)';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array (
+			substr($valuesTmp['url'], 0, 511),
+			$valuesTmp['category'],
+			substr($valuesTmp['name'], 0, 255),
+			substr($valuesTmp['website'], 0, 255),
+			substr($valuesTmp['description'], 0, 1023),
+			$valuesTmp['lastUpdate'],
+			base64_encode ($valuesTmp['httpAuth']),
+		);
+
+		if ($stm && $stm->execute ($values)) {
+			return $this->bd->lastInsertId();
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+
+	public function updateFeed ($id, $valuesTmp) {
+		$set = '';
+		foreach ($valuesTmp as $key => $v) {
+			$set .= $key . '=?, ';
+
+			if ($key == 'httpAuth') {
+				$valuesTmp[$key] = base64_encode ($v);
+			}
+		}
+		$set = substr ($set, 0, -2);
+
+		$sql = 'UPDATE `' . $this->prefix . 'feed` SET ' . $set . ' WHERE id=?';
+		$stm = $this->bd->prepare ($sql);
+
+		foreach ($valuesTmp as $v) {
+			$values[] = $v;
+		}
+		$values[] = $id;
+
+		if ($stm && $stm->execute ($values)) {
+			return $stm->rowCount();
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+
+	public function updateLastUpdate ($id, $inError = 0) {
+		$sql = 'UPDATE `' . $this->prefix . 'feed` f '	//2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE
+		     . 'SET f.cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=f.id),'
+		     . 'f.cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=f.id AND e2.is_read=0),'
+		     . 'lastUpdate=?, error=? '
+		     . 'WHERE f.id=?';
+
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array (
+			time (),
+			$inError,
+			$id,
+		);
+
+		if ($stm && $stm->execute ($values)) {
+			return $stm->rowCount();
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+
+	public function changeCategory ($idOldCat, $idNewCat) {
+		$catDAO = new FreshRSS_CategoryDAO ();
+		$newCat = $catDAO->searchById ($idNewCat);
+		if (!$newCat) {
+			$newCat = $catDAO->getDefault ();
+		}
+
+		$sql = 'UPDATE `' . $this->prefix . 'feed` SET category=? WHERE category=?';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array (
+			$newCat->id (),
+			$idOldCat
+		);
+
+		if ($stm && $stm->execute ($values)) {
+			return $stm->rowCount();
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+
+	public function deleteFeed ($id) {
+		/*//For MYISAM (MySQL 5.5-) without FOREIGN KEY
+		$sql = 'DELETE FROM `' . $this->prefix . 'entry` WHERE id_feed=?';
+		$stm = $this->bd->prepare ($sql);
+		$values = array ($id);
+		if (!($stm && $stm->execute ($values))) {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}*/
+
+		$sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE id=?';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array ($id);
+
+		if ($stm && $stm->execute ($values)) {
+			return $stm->rowCount();
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+	public function deleteFeedByCategory ($id) {
+		/*//For MYISAM (MySQL 5.5-) without FOREIGN KEY
+		$sql = 'DELETE FROM `' . $this->prefix . 'entry` e '
+		     . 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id '
+		     . 'WHERE f.category=?';
+		$stm = $this->bd->prepare ($sql);
+		$values = array ($id);
+		if (!($stm && $stm->execute ($values))) {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}*/
+
+		$sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE category=?';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array ($id);
+
+		if ($stm && $stm->execute ($values)) {
+			return $stm->rowCount();
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+
+	public function searchById ($id) {
+		$sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE id=?';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array ($id);
+
+		$stm->execute ($values);
+		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
+		$feed = self::daoToFeed ($res);
+
+		if (isset ($feed[$id])) {
+			return $feed[$id];
+		} else {
+			return false;
+		}
+	}
+	public function searchByUrl ($url) {
+		$sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE url=?';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array ($url);
+
+		$stm->execute ($values);
+		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
+		$feed = current (self::daoToFeed ($res));
+
+		if (isset ($feed)) {
+			return $feed;
+		} else {
+			return false;
+		}
+	}
+
+	public function listFeeds () {
+		$sql = 'SELECT * FROM `' . $this->prefix . 'feed` ORDER BY name';
+		$stm = $this->bd->prepare ($sql);
+		$stm->execute ();
+
+		return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
+	}
+
+	public function listFeedsOrderUpdate () {
+		$sql = 'SELECT * FROM `' . $this->prefix . 'feed` ORDER BY lastUpdate';
+		$stm = $this->bd->prepare ($sql);
+		$stm->execute ();
+
+		return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
+	}
+
+	public function listByCategory ($cat) {
+		$sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE category=? ORDER BY name';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array ($cat);
+
+		$stm->execute ($values);
+
+		return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
+	}
+
+	public function countEntries ($id) {
+		$sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=?';
+		$stm = $this->bd->prepare ($sql);
+		$values = array ($id);
+		$stm->execute ($values);
+		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
+
+		return $res[0]['count'];
+	}
+	public function countNotRead ($id) {
+		$sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND is_read=0';
+		$stm = $this->bd->prepare ($sql);
+		$values = array ($id);
+		$stm->execute ($values);
+		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
+
+		return $res[0]['count'];
+	}
+	public function updateCachedValues () {	//For one single feed, call updateLastUpdate($id)
+		$sql = 'UPDATE `' . $this->prefix . 'feed` f '
+		     . 'INNER JOIN ('
+		     .	'SELECT e.id_feed, '
+		     .	'COUNT(CASE WHEN e.is_read = 0 THEN 1 END) AS nbUnreads, '
+		     .	'COUNT(e.id) AS nbEntries '
+		     .	'FROM `' . $this->prefix . 'entry` e '
+		     .	'GROUP BY e.id_feed'
+		     . ') x ON x.id_feed=f.id '
+		     . 'SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads';
+		$stm = $this->bd->prepare ($sql);
+
+		$values = array ($feed_id);
+
+		if ($stm && $stm->execute ($values)) {
+			return $stm->rowCount();
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+
+	public function truncate ($id) {
+		$sql = 'DELETE e.* FROM `' . $this->prefix . 'entry` e WHERE e.id_feed=?';
+		$stm = $this->bd->prepare($sql);
+		$values = array($id);
+		$this->bd->beginTransaction ();
+		if (!($stm && $stm->execute ($values))) {
+				$info = $stm->errorInfo();
+				Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+				$this->bd->rollBack ();
+				return false;
+			}
+		$affected = $stm->rowCount();
+
+		$sql = 'UPDATE `' . $this->prefix . 'feed` f '
+			 . 'SET f.cache_nbEntries=0, f.cache_nbUnreads=0 WHERE f.id=?';
+		$values = array ($id);
+		$stm = $this->bd->prepare ($sql);
+		if (!($stm && $stm->execute ($values))) {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			$this->bd->rollBack ();
+			return false;
+		}
+
+		$this->bd->commit ();
+		return $affected;
+	}
+
+	public function cleanOldEntries ($id, $date_min, $keep = 15) {	//Remember to call updateLastUpdate($id) just after
+		$sql = 'DELETE e.* FROM `' . $this->prefix . 'entry` e '
+		     . 'WHERE e.id_feed = :id_feed AND e.id <= :id_max AND e.is_favorite = 0 AND e.id NOT IN '
+		     . '(SELECT id FROM (SELECT e2.id FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed = :id_feed ORDER BY id DESC LIMIT :keep) keep)';	//Double select because of: MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
+		$stm = $this->bd->prepare ($sql);
+
+		$id_max = intval($date_min) . '000000';
+
+		$stm->bindParam(':id_feed', $id, PDO::PARAM_INT);
+		$stm->bindParam(':id_max', $id_max, PDO::PARAM_INT);
+		$stm->bindParam(':keep', $keep, PDO::PARAM_INT);
+
+		if ($stm && $stm->execute ()) {
+			return $stm->rowCount();
+		} else {
+			$info = $stm->errorInfo();
+			Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR);
+			return false;
+		}
+	}
+
+	public static function daoToFeed ($listDAO, $catID = null) {
+		$list = array ();
+
+		if (!is_array ($listDAO)) {
+			$listDAO = array ($listDAO);
+		}
+
+		foreach ($listDAO as $key => $dao) {
+			if (!isset ($dao['name'])) {
+				continue;
+			}
+			if (isset ($dao['id'])) {
+				$key = $dao['id'];
+			}
+
+			$myFeed = new FreshRSS_Feed (isset($dao['url']) ? $dao['url'] : '', false);
+			$myFeed->_category ($catID === null ? $dao['category'] : $catID);
+			$myFeed->_name ($dao['name']);
+			$myFeed->_website ($dao['website'], false);
+			$myFeed->_description (isset($dao['description']) ? $dao['description'] : '');
+			$myFeed->_lastUpdate (isset($dao['lastUpdate']) ? $dao['lastUpdate'] : 0);
+			$myFeed->_priority ($dao['priority']);
+			$myFeed->_pathEntries (isset($dao['pathEntries']) ? $dao['pathEntries'] : '');
+			$myFeed->_httpAuth (isset($dao['httpAuth']) ? base64_decode ($dao['httpAuth']) : '');
+			$myFeed->_error ($dao['error']);
+			$myFeed->_keepHistory (isset($dao['keep_history']) ? $dao['keep_history'] : '');
+			$myFeed->_nbNotRead ($dao['cache_nbUnreads']);
+			$myFeed->_nbEntries ($dao['cache_nbEntries']);
+			if (isset ($dao['id'])) {
+				$myFeed->_id ($dao['id']);
+			}
+			$list[$key] = $myFeed;
+		}
+
+		return $list;
+	}
+}

+ 26 - 0
app/Models/Log.php

@@ -0,0 +1,26 @@
+<?php
+
+class FreshRSS_Log extends Minz_Model {
+	private $date;
+	private $level;
+	private $information;
+
+	public function date () {
+		return $this->date;
+	}
+	public function level () {
+		return $this->level;
+	}
+	public function info () {
+		return $this->information;
+	}
+	public function _date ($date) {
+		$this->date = $date;
+	}
+	public function _level ($level) {
+		$this->level = $level;
+	}
+	public function _info ($information) {
+		$this->information = $information;
+	}
+}

+ 20 - 0
app/Models/LogDAO.php

@@ -0,0 +1,20 @@
+<?php
+class FreshRSS_LogDAO extends Minz_ModelTxt {
+	public function __construct () {
+		parent::__construct (LOG_PATH . '/application.log', 'r+');
+	}
+
+	public function lister () {
+		$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;
+			}
+		}
+		return $logs;
+	}
+}

+ 88 - 0
app/Models/Themes.php

@@ -0,0 +1,88 @@
+<?php
+
+class FreshRSS_Themes extends Minz_Model {
+	private static $themesUrl = '/themes/';
+	private static $defaultIconsUrl = '/themes/icons/';
+
+	public static function get() {
+		$themes_list = array_diff(
+			scandir(PUBLIC_PATH . self::$themesUrl),
+			array('..', '.')
+		);
+
+		$list = array();
+		foreach ($themes_list as $theme_dir) {
+			$theme = self::get_infos($theme_dir);
+			if ($theme) {
+				$list[$theme_dir] = $theme;
+			}
+		}
+		return $list;
+	}
+
+	public static function get_infos($theme_id) {
+		$theme_dir = PUBLIC_PATH . self::$themesUrl . $theme_id ;
+		if (is_dir($theme_dir)) {
+			$json_filename = $theme_dir . '/metadata.json';
+			if (file_exists($json_filename)) {
+				$content = file_get_contents($json_filename);
+				$res = json_decode($content, true);
+				if ($res && isset($res['files']) && is_array($res['files'])) {
+					$res['path'] = $theme_id;
+					return $res;
+				}
+			}
+		}
+		return false;
+	}
+
+	private static $themeIconsUrl;
+	private static $themeIcons;
+
+	public static function setThemeId($theme_id) {
+		self::$themeIconsUrl = self::$themesUrl . $theme_id . '/icons/';
+		self::$themeIcons = is_dir(PUBLIC_PATH . self::$themeIconsUrl) ? array_fill_keys(array_diff(
+			scandir(PUBLIC_PATH . self::$themeIconsUrl),
+			array('..', '.')
+		), 1) : array();
+	}
+
+	public static function icon($name, $urlOnly = false) {
+		static $alts = array(
+			'add' => '✚',
+			'all' => '☰',
+			'bookmark' => '★',
+			'category' => '☷',
+			'category-white' => '☷',
+			'close' => '❌',
+			'configure' => '⚙',
+			'down' => '▽',
+			'favorite' => '★',
+			'help' => 'ⓘ',
+			'link' => '↗',
+			'login' => '🔒',
+			'logout' => '🔓',
+			'next' => '⏩',
+			'non-starred' => '☆',
+			'prev' => '⏪',
+			'read' => '☑',
+			'unread' => '☐',
+			'refresh' => '🔃',	//↻
+			'search' => '🔍',
+			'share' => '♺',
+			'starred' => '★',
+			'tag' => '⚐',
+			'up' => '△',
+		);
+		if (!isset($alts[$name])) {
+			return '';
+		}
+
+		$url = $name . '.svg';
+		$url = isset(self::$themeIcons[$url]) ? (self::$themeIconsUrl . $url) :
+			(self::$defaultIconsUrl . $url);
+
+		return $urlOnly ? Minz_Url::display($url) :
+			'<img class="icon" src="' . Minz_Url::display($url) . '" alt="' . $alts[$name] . '" />';
+	}
+}

+ 0 - 1
app/configuration/.gitignore

@@ -1 +0,0 @@
-*

+ 0 - 117
app/controllers/entryController.php

@@ -1,117 +0,0 @@
-<?php
-
-class entryController extends ActionController {
-	public function firstAction () {
-		if (login_is_conf ($this->view->conf) && !is_logged ()) {
-			Error::error (
-				403,
-				array ('error' => array (Translate::t ('access_denied')))
-			);
-		}
-
-		$this->params = array ();
-		$this->redirect = false;
-		$ajax = Request::param ('ajax');
-		if ($ajax) {
-			$this->view->_useLayout (false);
-		}
-	}
-	public function lastAction () {
-		$ajax = Request::param ('ajax');
-		if (!$ajax && $this->redirect) {
-			Request::forward (array (
-				'c' => 'index',
-				'a' => 'index',
-				'params' => $this->params
-			), true);
-		} else {
-			Request::_param ('ajax');
-		}
-	}
-
-	public function readAction () {
-		$this->redirect = true;
-
-		$id = Request::param ('id');
-		$is_read = Request::param ('is_read');
-		$get = Request::param ('get');
-		$nextGet = Request::param ('nextGet', $get); 
-		$dateMax = Request::param ('dateMax', 0);
-
-		$is_read = !!$is_read;
-
-		$entryDAO = new EntryDAO ();
-		if ($id == false) {
-			if (!$get) {
-				$entryDAO->markReadEntries ($is_read, $dateMax);
-			} else {
-				$typeGet = $get[0];
-				$get = substr ($get, 2);
-
-				if ($typeGet == 'c') {
-					$entryDAO->markReadCat ($get, $is_read, $dateMax);
-					$this->params = array ('get' => $nextGet); 
-				} elseif ($typeGet == 'f') {
-					$entryDAO->markReadFeed ($get, $is_read, $dateMax);
-					$this->params = array ('get' => $nextGet);
-				}
-			}
-
-			// notif
-			$notif = array (
-				'type' => 'good',
-				'content' => Translate::t ('feeds_marked_read')
-			);
-			Session::_param ('notification', $notif);
-		} else {
-			$entryDAO->updateEntry ($id, array ('is_read' => $is_read));
-		}
-	}
-
-	public function bookmarkAction () {
-		$this->redirect = true;
-
-		$id = Request::param ('id');
-		$is_fav = Request::param ('is_favorite');
-
-		if ($is_fav) {
-			$is_fav = true;
-		} else {
-			$is_fav = false;
-		}
-
-		$entryDAO = new EntryDAO ();
-		if ($id != false) {
-			$entry = $entryDAO->searchById ($id);
-
-			if ($entry != false) {
-				$values = array (
-					'is_favorite' => $is_fav,
-				);
-
-				$entryDAO->updateEntry ($entry->id (), $values);
-			}
-		}
-	}
-
-	public function optimizeAction() {
-		// 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 EntryDAO();
-		$entryDAO->optimizeTable();
-
-		touch(PUBLIC_PATH . '/data/touch.txt');
-
-		$notif = array (
-			'type' => 'good',
-			'content' => Translate::t ('optimization_complete')
-		);
-		Session::_param ('notification', $notif);
-
-		Request::forward(array(
-			'c' => 'configure',
-			'a' => 'display'
-		), true);
-	}
-}

+ 0 - 363
app/controllers/feedController.php

@@ -1,363 +0,0 @@
-<?php
-
-class feedController extends ActionController {
-	public function firstAction () {
-		$token = $this->view->conf->token();
-		$token_param = Request::param ('token', '');
-		$token_is_ok = ($token != '' && $token == $token_param);
-		$action = Request::actionName ();
-
-		if (login_is_conf ($this->view->conf) &&
-				!is_logged () &&
-				!($token_is_ok && $action == 'actualize')) {
-			Error::error (
-				403,
-				array ('error' => array (Translate::t ('access_denied')))
-			);
-		}
-
-		$this->catDAO = new CategoryDAO ();
-		$this->catDAO->checkDefault ();
-	}
-
-	public function addAction () {
-		if (Request::isPost ()) {
-			$url = Request::param ('url_rss');
-			$cat = Request::param ('category', false);
-			if ($cat === false) {
-				$def_cat = $this->catDAO->getDefault ();
-				$cat = $def_cat->id ();
-			}
-
-			$user = Request::param ('username');
-			$pass = Request::param ('password');
-			$params = array ();
-
-			try {
-				$feed = new Feed ($url);
-				$feed->_category ($cat);
-
-				$httpAuth = '';
-				if ($user != '' || $pass != '') {
-					$httpAuth = $user . ':' . $pass;
-				}
-				$feed->_httpAuth ($httpAuth);
-
-				$feed->load ();
-
-				$feedDAO = new FeedDAO ();
-				$values = array (
-					'id' => $feed->id (),
-					'url' => $feed->url (),
-					'category' => $feed->category (),
-					'name' => $feed->name (),
-					'website' => $feed->website (),
-					'description' => $feed->description (),
-					'lastUpdate' => time (),
-					'httpAuth' => $feed->httpAuth (),
-				);
-
-				if ($feedDAO->searchByUrl ($values['url'])) {
-					// on est déjà abonné à ce flux
-					$notif = array (
-						'type' => 'bad',
-						'content' => Translate::t ('already_subscribed', $feed->name ())
-					);
-					Session::_param ('notification', $notif);
-				} elseif (!$feedDAO->addFeed ($values)) {
-					// problème au niveau de la base de données
-					$notif = array (
-						'type' => 'bad',
-						'content' => Translate::t ('feed_not_added', $feed->name ())
-					);
-					Session::_param ('notification', $notif);
-				} else {
-					$entryDAO = new EntryDAO ();
-					$entries = $feed->entries ();
-
-					// on calcule la date des articles les plus anciens qu'on accepte
-					$nb_month_old = $this->view->conf->oldEntries ();
-					$date_min = time () - (60 * 60 * 24 * 30 * $nb_month_old);
-
-					// on ajoute les articles en masse sans vérification
-					foreach ($entries as $entry) {
-						if ($entry->date (true) >= $date_min ||
-						    $feed->keepHistory ()) {
-							$values = $entry->toArray ();
-							$entryDAO->addEntry ($values);
-						}
-					}
-
-					// ok, ajout terminé
-					$notif = array (
-						'type' => 'good',
-						'content' => Translate::t ('feed_added', $feed->name ())
-					);
-					Session::_param ('notification', $notif);
-
-					// permet de rediriger vers la page de conf du flux
-					$params['id'] = $feed->id ();
-				}
-			} catch (BadUrlException $e) {
-				Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
-				$notif = array (
-					'type' => 'bad',
-					'content' => Translate::t ('invalid_url', $url)
-				);
-				Session::_param ('notification', $notif);
-			} catch (FeedException $e) {
-				Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
-				$notif = array (
-					'type' => 'bad',
-					'content' => Translate::t ('internal_problem_feed')
-				);
-				Session::_param ('notification', $notif);
-			} catch (FileNotExistException $e) {
-				// Répertoire de cache n'existe pas
-				Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
-				$notif = array (
-					'type' => 'bad',
-					'content' => Translate::t ('internal_problem_feed')
-				);
-				Session::_param ('notification', $notif);
-			}
-
-			Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => $params), true);
-		}
-	}
-
-	public function actualizeAction () {
-		$feedDAO = new FeedDAO ();
-		$entryDAO = new EntryDAO ();
-
-		$id = Request::param ('id');
-		$force = Request::param ('force', false);
-
-		// on créé la liste des flux à mettre à actualiser
-		// si on veut mettre un flux à jour spécifiquement, on le met
-		// dans la liste, mais seul (permet d'automatiser le traitement)
-		$feeds = array ();
-		if ($id) {
-			$feed = $feedDAO->searchById ($id);
-			if ($feed) {
-				$feeds = array ($feed);
-			}
-		} else {
-			$feeds = $feedDAO->listFeedsOrderUpdate ();
-		}
-
-		// on calcule la date des articles les plus anciens qu'on accepte
-		$nb_month_old = $this->view->conf->oldEntries ();
-		$date_min = time () - (60 * 60 * 24 * 30 * $nb_month_old);
-
-		$i = 0;
-		$flux_update = 0;
-		foreach ($feeds as $feed) {
-			try {
-				$feed->load ();
-				$entries = $feed->entries ();
-
-				//For this feed, check last n entry IDs already in database
-				$existingIds = array_fill_keys ($entryDAO->listLastIdsByFeed ($feed->id (), count($entries) + 2), 1);
-
-				// ajout des articles en masse sans se soucier des erreurs
-				// On ne vérifie pas que l'article n'est pas déjà en BDD
-				// car demanderait plus de ressources
-				// La BDD refusera l'ajout de son côté car l'id doit être
-				// unique
-				foreach ($entries as $entry) {
-					if ((!isset ($existingIds[$entry->id ()])) &&
-						($entry->date (true) >= $date_min ||
-						$feed->keepHistory ())) {
-						$values = $entry->toArray ();
-						$entryDAO->addEntry ($values);
-					}
-				}
-
-				// on indique que le flux vient d'être mis à jour en BDD
-				$feedDAO->updateLastUpdate ($feed->id ());
-				$flux_update++;
-			} catch (FeedException $e) {
-				Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
-				$feedDAO->isInError ($feed->id ());
-			}
-
-			// On arrête à 10 flux pour ne pas surcharger le serveur
-			// sauf si le paramètre $force est à vrai
-			$i++;
-			if ($i >= 10 && !$force) {
-				break;
-			}
-		}
-
-		$entryDAO->cleanOldEntries ($nb_month_old);
-
-		$url = array ();
-		if ($flux_update === 1) {
-			// on a mis un seul flux à jour
-			$notif = array (
-				'type' => 'good',
-				'content' => Translate::t ('feed_actualized', $feed->name ())
-			);
-		} elseif ($flux_update > 1) {
-			// plusieurs flux on été mis à jour
-			$notif = array (
-				'type' => 'good',
-				'content' => Translate::t ('n_feeds_actualized', $flux_update)
-			);
-		} else {
-			// aucun flux n'a été mis à jour, oups
-			$notif = array (
-				'type' => 'bad',
-				'content' => Translate::t ('no_feed_actualized')
-			);
-		}
-
-		if ($i === 1) {
-			// Si on a voulu mettre à jour qu'un flux
-			// on filtre l'affichage par ce flux
-			$feed = reset ($feeds);
-			$url['params'] = array ('get' => 'f_' . $feed->id ());
-		}
-
-		if (Request::param ('ajax', 0) === 0) {
-			Session::_param ('notification', $notif);
-			Request::forward ($url, true);
-		} else {
-			// Une requête Ajax met un seul flux à jour.
-			// Comme en principe plusieurs requêtes ont lieu,
-			// on indique que "plusieurs flux ont été mis à jour".
-			// Cela permet d'avoir une notification plus proche du
-			// ressenti utilisateur
-			$notif = array (
-				'type' => 'good',
-				'content' => Translate::t ('feeds_actualized')
-			);
-			Session::_param ('notification', $notif);
-			// et on désactive le layout car ne sert à rien
-			$this->view->_useLayout (false);
-		}
-	}
-
-	public function massiveImportAction () {
-		$entryDAO = new EntryDAO ();
-		$feedDAO = new FeedDAO ();
-
-		$categories = Request::param ('categories', array (), true);
-		$feeds = Request::param ('feeds', array (), true);
-
-		// on ajoute les catégories en masse dans une fonction à part
-		$this->addCategories ($categories);
-
-		// on calcule la date des articles les plus anciens qu'on accepte
-		$nb_month_old = $this->view->conf->oldEntries ();
-		$date_min = time () - (60 * 60 * 24 * 30 * $nb_month_old);
-
-		// la variable $error permet de savoir si une erreur est survenue
-		// Le but est de ne pas arrêter l'import même en cas d'erreur
-		// L'utilisateur sera mis au courant s'il y a eu des erreurs, mais
-		// ne connaîtra pas les détails. Ceux-ci seront toutefois logguées
-		$error = false;
-		$i = 0;
-		foreach ($feeds as $feed) {
-			try {
-				$feed->load ();
-
-				$values = array (
-					'id' => $feed->id (),
-					'url' => $feed->url (),
-					'category' => $feed->category (),
-					'name' => $feed->name (),
-					'website' => $feed->website (),
-					'description' => $feed->description (),
-					'lastUpdate' => 0,
-					'httpAuth' => $feed->httpAuth ()
-				);
-
-				// ajout du flux que s'il n'est pas déjà en BDD
-				if (!$feedDAO->searchByUrl ($values['url'])) {
-					if (!$feedDAO->addFeed ($values)) {
-						$error = true;
-					}
-				}
-			} catch (FeedException $e) {
-				$error = true;
-				Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
-			}
-		}
-
-		if ($error) {
-			$res = Translate::t ('feeds_imported_with_errors');
-		} else {
-			$res = Translate::t ('feeds_imported');
-		}
-
-		$notif = array (
-			'type' => 'good',
-			'content' => $res
-		);
-		Session::_param ('notification', $notif);
-		Session::_param ('actualize_feeds', true);
-
-		// et on redirige vers la page import/export
-		Request::forward (array (
-			'c' => 'configure',
-			'a' => 'importExport'
-		), true);
-	}
-
-	public function deleteAction () {
-		$type = Request::param ('type', 'feed');
-		$id = Request::param ('id');
-
-		$feedDAO = new FeedDAO ();
-		if ($type == 'category') {
-			if ($feedDAO->deleteFeedByCategory ($id)) {
-				$notif = array (
-					'type' => 'good',
-					'content' => Translate::t ('category_emptied')
-				);
-			} else {
-				$notif = array (
-					'type' => 'bad',
-					'content' => Translate::t ('error_occured')
-				);
-			}
-		} else {
-			if ($feedDAO->deleteFeed ($id)) {
-				$notif = array (
-					'type' => 'good',
-					'content' => Translate::t ('feed_deleted')
-				);
-			} else {
-				$notif = array (
-					'type' => 'bad',
-					'content' => Translate::t ('error_occured')
-				);
-			}
-		}
-
-		Session::_param ('notification', $notif);
-
-		if ($type == 'category') {
-			Request::forward (array ('c' => 'configure', 'a' => 'categorize'), true);
-		} else {
-			Request::forward (array ('c' => 'configure', 'a' => 'feed'), true);
-		}
-	}
-
-	private function addCategories ($categories) {
-		$catDAO = new CategoryDAO ();
-
-		foreach ($categories as $cat) {
-			if (!$catDAO->searchByName ($cat->name ())) {
-				$values = array (
-					'id' => $cat->id (),
-					'name' => $cat->name (),
-					'color' => $cat->color ()
-				);
-				$catDAO->addCategory ($values);
-			}
-		}
-	}
-}

+ 0 - 257
app/controllers/indexController.php

@@ -1,257 +0,0 @@
-<?php
-
-class indexController extends ActionController {
-	private $get = false;
-	private $nb_not_read = 0;
-	private $nb_not_read_cat = 0;
-
-	public function indexAction () {
-		$output = Request::param ('output');
-
-		$token = $this->view->conf->token();
-		$token_param = 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;
-		}
-
-		// construction of RSS url of this feed
-		$params = Request::params ();
-		$params['output'] = 'rss';
-		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',
-			'params' => $params
-		);
-
-		$this->view->rss_title = View::title();
-
-		if ($output == 'rss') {
-			// no layout for RSS output
-			$this->view->_useLayout (false);
-			header('Content-Type: application/rss+xml; charset=utf-8');
-		} else {
-			View::appendScript (Url::display ('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js')));
-
-			if ($output == 'global') {
-				View::appendScript (Url::display ('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js')));
-			}
-		}
-
-		$nb_not_read = $this->view->nb_not_read;
-		if($nb_not_read > 0) {
-			View::appendTitle (' (' . $nb_not_read . ')');
-		}
-		View::prependTitle (' - ');
-
-		$entryDAO = new EntryDAO ();
-		$feedDAO = new FeedDAO ();
-		$catDAO = new CategoryDAO ();
-
-		$this->view->cat_aside = $catDAO->listCategories ();
-		$this->view->nb_favorites = $entryDAO->countUnreadReadFavorites ();
-		$this->view->nb_total = $entryDAO->count ();
-		$this->view->currentName = '';
-
-		$this->view->get_c = '';
-		$this->view->get_f = '';
-
-		$type = $this->getType ();
-		$error = $this->checkAndProcessType ($type);
-
-		// mise à jour des titres
-		$this->view->rss_title = $this->view->currentName . ' - ' . $this->view->rss_title;
-		View::prependTitle (
-			$this->view->currentName .
-			($this->nb_not_read_cat > 0 ? ' (' . $this->nb_not_read_cat . ')' : '')
-		);
-
-		if (!$error) {
-			// On récupère les différents éléments de filtrage
-			$this->view->state = $state = Request::param ('state', $this->view->conf->defaultView ());
-			$filter = Request::param ('search', '');
-			$this->view->order = $order = Request::param ('order', $this->view->conf->sortOrder ());
-			$nb = Request::param ('nb', $this->view->conf->postsPerPage ());
-			$first = Request::param ('next', '');
-
-			try {
-				// EntriesGetter permet de déporter la complexité du filtrage
-				$getter = new EntriesGetter ($type, $state, $filter, $order, $nb, $first);
-				$getter->execute ();
-				$entries = $getter->getPaginator ();
-
-				// Si on a récupéré aucun article "non lus"
-				// on essaye de récupérer tous les articles
-				if ($state == 'not_read' && $entries->isEmpty ()) {
-					$this->view->state = 'all';
-					$getter->_state ('all');
-					$getter->execute ();
-					$entries = $getter->getPaginator ();
-				}
-
-				$this->view->entryPaginator = $entries;
-			} catch(EntriesGetterException $e) {
-				Minz_Log::record ($e->getMessage (), Minz_Log::NOTICE);
-				Error::error (
-					404,
-					array ('error' => array (Translate::t ('page_not_found')))
-				);
-			}
-		} else {
-			Error::error (
-				404,
-				array ('error' => array (Translate::t ('page_not_found')))
-			);
-		}
-	}
-
-	/*
-	 * Détermine le type d'article à récupérer :
-	 * "tous", "favoris", "public", "catégorie" ou "flux"
-	 */
-	private function getType () {
-		$get = Request::param ('get', 'all');
-		$typeGet = $get[0];
-		$id = substr ($get, 2);
-
-		$type = null;
-		if ($get == 'all' || $get == 'favoris' || $get == 'public') {
-			$type = array (
-				'type' => $get,
-				'id' => $get
-			);
-		} elseif ($typeGet == 'f' || $typeGet == 'c') {
-			$type = array (
-				'type' => $typeGet,
-				'id' => $id
-			);
-		}
-
-		return $type;
-	}
-	/*
-	 * Vérifie que la catégorie / flux sélectionné existe
-	 * + Initialise correctement les variables de vue get_c et get_f
-	 * + Met à jour la variable $this->nb_not_read_cat
-	 */
-	private function checkAndProcessType ($type) {
-		if ($type['type'] == 'all') {
-			$this->view->currentName = Translate::t ('your_rss_feeds');
-			$this->view->get_c = $type['type'];
-			return false;
-		} elseif ($type['type'] == 'favoris') {
-			$this->view->currentName = Translate::t ('your_favorites');
-			$this->view->get_c = $type['type'];
-			return false;
-		} elseif ($type['type'] == 'public') {
-			$this->view->currentName = Translate::t ('public');
-			$this->view->get_c = $type['type'];
-			return false;
-		} elseif ($type['type'] == 'c') {
-			$catDAO = new CategoryDAO ();
-			$cat = $catDAO->searchById ($type['id']);
-			if ($cat) {
-				$this->view->currentName = $cat->name ();
-				$this->nb_not_read_cat = $cat->nbNotRead ();
-				$this->view->get_c = $type['id'];
-				return false;
-			} else {
-				return true;
-			}
-		} elseif ($type['type'] == 'f') {
-			$feedDAO = new FeedDAO ();
-			$feed = $feedDAO->searchById ($type['id']);
-			if ($feed) {
-				$this->view->currentName = $feed->name ();
-				$this->nb_not_read_cat = $feed->nbNotRead ();
-				$this->view->get_f = $type['id'];
-				$this->view->get_c = $feed->category ();
-				return false;
-			} else {
-				return true;
-			}
-		} else {
-			return true;
-		}
-	}
-
-	public function aboutAction () {
-		View::prependTitle (Translate::t ('about') . ' - ');
-	}
-
-	public function logsAction () {
-		if (login_is_conf ($this->view->conf) && !is_logged ()) {
-			Error::error (
-				403,
-				array ('error' => array (Translate::t ('access_denied')))
-			);
-		}
-
-		View::prependTitle (Translate::t ('logs') . ' - ');
-
-		$logs = array();
-		try {
-			$logDAO = new LogDAO ();
-			$logs = $logDAO->lister ();
-			$logs = array_reverse ($logs);
-		} catch(FileNotExistException $e) {
-
-		}
-
-		//gestion pagination
-		$page = Request::param ('page', 1);
-		$this->view->logsPaginator = new Paginator ($logs);
-		$this->view->logsPaginator->_nbItemsPerPage (50);
-		$this->view->logsPaginator->_currentPage ($page);
-	}
-
-	public function loginAction () {
-		$this->view->_useLayout (false);
-
-		$url = 'https://verifier.login.persona.org/verify';
-		$assert = Request::param ('assertion');
-		$params = 'assertion=' . $assert . '&audience=' .
-			  urlencode (Url::display (null, 'php', true));
-		$ch = curl_init ();
-		$options = array (
-			CURLOPT_URL => $url,
-			CURLOPT_RETURNTRANSFER => TRUE,
-			CURLOPT_POST => 2,
-			CURLOPT_POSTFIELDS => $params
-		);
-		curl_setopt_array ($ch, $options);
-		$result = curl_exec ($ch);
-		curl_close ($ch);
-
-		$res = json_decode ($result, true);
-		if ($res['status'] == 'okay' && $res['email'] == $this->view->conf->mailLogin ()) {
-			Session::_param ('mail', $res['email']);
-			touch(PUBLIC_PATH . '/data/touch.txt');
-		} else {
-			$res = array ();
-			$res['status'] = 'failure';
-			$res['reason'] = Translate::t ('invalid_login');
-		}
-
-		$this->view->res = json_encode ($res);
-	}
-
-	public function logoutAction () {
-		$this->view->_useLayout (false);
-		Session::_param ('mail');
-		touch(PUBLIC_PATH . '/data/touch.txt');
-	}
-}

+ 40 - 73
app/i18n/en.php

@@ -5,6 +5,7 @@ return array (
 	'login'				=> 'Login',
 	'logout'			=> 'Logout',
 	'search'			=> 'Search words or #tags',
+	'search_short'			=> 'Search',
 
 	'configuration'			=> 'Configuration',
 	'general_and_reading'		=> 'General and reading',
@@ -19,7 +20,7 @@ return array (
 	'import_export_opml'		=> 'Import / export (OPML)',
 
 	'subscription_management'	=> 'Subscriptions management',
-	'all_feeds'			=> 'Main stream (%d)',
+	'all_feeds'			=> 'Main stream',
 	'favorite_feeds'		=> 'Favourites (%d)',
 	'not_read'			=> '%d unread',
 	'not_reads'			=> '%d unread',
@@ -68,6 +69,7 @@ return array (
 	'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',
 	'shortcuts_management'		=> 'Shortcuts management',
@@ -83,10 +85,12 @@ return array (
 	'n_feeds_actualized'		=> '%d feeds have been updated',
 	'feeds_actualized'		=> 'RSS feeds have been updated',
 	'no_feed_actualized'		=> 'No RSS feed has been updated',
-	'feeds_imported_with_errors'	=> 'Feeds have been imported but errors occurred',
-	'feeds_imported'		=> 'Feeds have been imported',
+	'n_entries_deleted'		=> '%d articles have been deleted',
+	'feeds_imported_with_errors'	=> 'Your feeds have been imported but some errors occurred',
+	'feeds_imported'		=> 'Your feeds have been imported and will now be updated',
 	'category_emptied'		=> 'Category has been emptied',
 	'feed_deleted'			=> 'Feed has been deleted',
+	'feed_validator'		=> 'Check the validity of the feed',
 
 	'optimization_complete'		=> 'Optimization complete',
 
@@ -119,6 +123,7 @@ return array (
 	'shift_for_first'		=> '+ <code>shift</code> to skip to the first article of page',
 	'next_page'			=> 'Skip to the next page',
 	'previous_page'			=> 'Skip to the previous page',
+	'collapse_article'		=> 'Collapse current article',
 
 	'file_to_import'		=> 'File to import',
 	'import'			=> 'Import',
@@ -127,11 +132,14 @@ return array (
 
 	'informations'			=> 'Information',
 	'feed_in_error'			=> 'This feed has encountered a problem. Please verify that it is always reachable then actualize it.',
+	'feed_description'		=> 'Description',
 	'website_url'			=> 'Website URL',
 	'feed_url'			=> 'Feed URL',
+	'articles'			=> 'articles',
 	'number_articles'		=> 'Number of articles',
-	'keep_history'			=> 'Keep history?',
+	'keep_history'			=> 'Keep old articles?',
 	'categorize'			=> 'Store in a category',
+	'truncate'			=> 'Delete all articles',
 	'advanced'			=> 'Advanced',
 	'show_in_all_flux'		=> 'Show in main stream',
 	'yes'				=> 'Yes',
@@ -149,9 +157,10 @@ return array (
 
 	'general_configuration'		=> 'General configuration',
 	'language'			=> 'Language',
-	'delete_articles_every'		=> 'Remove articles every',
+	'delete_articles_every'		=> 'Remove articles after',
 	'month'				=> 'months',
-	'persona_connection_email'	=> 'Login mail address (use <a href="https://persona.org/">Persona</a>)',
+	'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',
 	'auth_token'			=> 'Authentication token',
 	'explain_token'			=> 'Allows to access RSS output without authentication.<br />%s?token=%s',
@@ -161,24 +170,36 @@ return array (
 	'sort_order'			=> 'Sort order',
 	'auto_load_more'		=> 'Load next articles at the page bottom',
 	'display_articles_unfolded'	=> 'Show articles unfolded by default',
-	'after_onread'			=> 'After marked as read,',
-	'jump_next'			=> 'jump to next unread sibling',
-	'reading_icons'		=> 'Reading icons',
-	'top_line'		=> 'Top line',
-	'bottom_line'		=> 'Bottom line',
+	'after_onread'			=> 'After “mark all as read”,',
+	'jump_next'			=> 'jump to next unread sibling (feed or category)',
+	'reading_icons'			=> 'Reading icons',
+	'top_line'			=> 'Top line',
+	'bottom_line'			=> 'Bottom line',
 	'img_with_lazyload'		=> 'Use "lazy load" mode to load pictures',
-	'auto_read_when'		=> 'Mark as read when',
-	'article_selected'		=> 'article is selected',
-	'article_open_on_website'	=> 'article is opened on its original website',
-	'scroll'			=> 'page scrolls',
+	'auto_read_when'		=> 'Mark article as read…',
+	'article_selected'		=> 'when article is selected',
+	'article_open_on_website'	=> 'when article is opened on its original website',
+	'scroll'			=> 'during page scrolls',
+	'upon_reception'		=> 'upon reception of the article',
 	'your_shaarli'			=> 'Your Shaarli',
+	'your_poche'			=> 'Your Poche',
+	'your_diaspora_pod'		=> 'Your Diaspora* pod',
 	'sharing'			=> 'Sharing',
 	'share'				=> 'Share',
-	'by_email'			=> 'By mail',
-	'on_shaarli'			=> 'On your Shaarli',
+	'by_email'			=> 'By email',
 	'optimize_bdd'			=> 'Optimize database',
-	'optimize_todo_sometimes'	=> 'To do occasionally to reduce size of database',
+	'optimize_todo_sometimes'	=> 'To do occasionally to reduce the size of the database',
 	'theme'				=> 'Theme',
+	'more_information'		=> 'More information',
+	'activate_sharing'		=> 'Activate sharing',
+	'shaarli'			=> 'Shaarli',
+	'poche'				=> 'Poche',
+	'diaspora'			=> 'Diaspora*',
+	'twitter'			=> 'Twitter',
+	'g+'				=> 'Google+',
+	'facebook'			=> 'Facebook',
+	'email'				=> 'Email',
+	'print'				=> 'Print',
 
 	'article'			=> 'Article',
 	'title'				=> 'Title',
@@ -214,6 +235,7 @@ return array (
 
 	'logs'				=> 'Logs',
 	'logs_empty'			=> 'Log file is empty',
+	'clear_logs'			=> 'Clear the logs',
 
 	'forbidden_access'		=> 'Forbidden access',
 	'forbidden_access_description'	=> 'Access is password protected, please <a class="signin" href="#">sign in</a> to read your feeds.',
@@ -249,59 +271,4 @@ return array (
 	// format for date() function, %s allows to indicate month in letter
 	'format_date'			=> '%s dS Y',
 	'format_date_hour'		=> '%s dS Y \a\t H\.i',
-
-	// INSTALLATION
-	'freshrss_installation'		=> 'Installation - FreshRSS',
-	'freshrss'			=> 'FreshRSS',
-	'installation_step'		=> 'Installation - step %d',
-	'steps'				=> 'Steps',
-	'checks'			=> 'Checks',
-	'bdd_configuration'		=> 'Database configuration',
-	'this_is_the_end'		=> 'This is the end',
-
-	'ok'				=> 'Ok!',
-	'congratulations'		=> 'Congratulations!',
-	'attention'			=> 'Attention!',
-	'damn'				=> 'Damn!',
-	'oops'				=> 'Oops!',
-	'next_step'			=> 'Go to the next step',
-
-	'language_defined'		=> 'Language has been defined.',
-	'choose_language'		=> 'Choose a language for FreshRSS',
-
-	'javascript_is_better'		=> 'FreshRSS is more pleasant with JavaScript enabled',
-	'php_is_ok'			=> 'Your PHP version is %s and it’s compatible with FreshRSS',
-	'php_is_nok'			=> 'Your PHP version is %s. You must have at least version %s',
-	'minz_is_ok'			=> 'You have Minz framework',
-	'minz_is_nok'			=> 'You haven’t Minz framework. You should execute <em>build.sh</em> script or <a href="https://github.com/marienfressinaud/MINZ">download it on Github</a> and install in <em>%s</em> directory the content of its <em>/lib</em> directory.',
-	'curl_is_ok'			=> 'You have version %s of cURL',
-	'curl_is_nok'			=> 'You haven’t cURL',
-	'pdomysql_is_ok'		=> 'You have PDO and its driver for MySQL',
-	'pdomysql_is_nok'		=> 'You haven’t PDO or its driver for MySQL',
-	'dom_is_ok'			=> 'You have the necessary to browse the DOM',
-	'dom_is_nok'			=> 'You haven’t the necessary to browse the DOM (php-xml package can be useful)',
-	'cache_is_ok'			=> 'Permissions on cache directory are good',
-	'log_is_ok'			=> 'Permissions on logs directory are good',
-	'conf_is_ok'			=> 'Permissions on configuration directory are good',
-	'data_is_ok'			=> 'Permissions on data directory are good',
-	'file_is_nok'			=> 'Check permissions on <em>%s</em> directory. HTTP server must have rights to write into',
-	'fix_errors_before'		=> 'Fix errors before skip to the next step.',
-
-	'general_conf_is_ok'		=> 'General configuration has been saved.',
-	'random_string'			=> 'Random string',
-	'change_value'			=> 'You should change this value by any other',
-	'base_url'			=> 'Base URL',
-	'do_not_change_if_doubt'	=> 'Don’t change if you doubt about it',
-
-	'bdd_conf_is_ok'		=> 'Database configuration has been saved.',
-	'bdd_conf_is_ko'		=> 'Verify your database information.',
-	'host'				=> 'Host',
-	'username'			=> 'Username',
-	'password'			=> 'Password',
-	'bdd'				=> 'Database',
-	'prefix'			=> 'Table prefix',
-
-	'installation_is_ok'		=> 'Installation process is finished. You must delete <em>install.php</em> file to access FreshRSS… or simply click on following button :)',
-	'finish_installation'		=> 'Finish installation',
-	'install_not_deleted'		=> 'Something was going wrong, you must delete the file <em>%s</em> manually.',
 );

+ 38 - 71
app/i18n/fr.php

@@ -5,6 +5,7 @@ return array (
 	'login'				=> 'Connexion',
 	'logout'			=> 'Déconnexion',
 	'search'			=> 'Rechercher des mots ou des #tags',
+	'search_short'			=> 'Rechercher',
 
 	'configuration'			=> 'Configuration',
 	'general_and_reading'		=> 'Général et lecture',
@@ -19,7 +20,7 @@ return array (
 	'import_export_opml'		=> 'Importer / exporter (OPML)',
 
 	'subscription_management'	=> 'Gestion des abonnements',
-	'all_feeds'			=> 'Flux principal (%d)',
+	'all_feeds'			=> 'Flux principal',
 	'favorite_feeds'		=> 'Favoris (%d)',
 	'not_read'			=> '%d non lu',
 	'not_reads'			=> '%d non lus',
@@ -68,6 +69,7 @@ return array (
 	'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',
 	'shortcuts_management'		=> 'Gestion des raccourcis',
@@ -83,10 +85,12 @@ return array (
 	'n_feeds_actualized'		=> '%d flux ont été mis à jour',
 	'feeds_actualized'		=> 'Les flux ont été mis à jour',
 	'no_feed_actualized'		=> 'Aucun flux n’a pu être mis à jour',
-	'feeds_imported_with_errors'	=> 'Les flux ont été importés mais des erreurs sont survenues',
-	'feeds_imported'		=> 'Les flux ont été importés',
+	'n_entries_deleted'		=> '%d articles ont été supprimés',
+	'feeds_imported_with_errors'	=> 'Vos flux ont été importés mais des erreurs sont survenues',
+	'feeds_imported'		=> 'Vos flux ont été importés et vont maintenant être actualisés',
 	'category_emptied'		=> 'La catégorie a été vidée',
 	'feed_deleted'			=> 'Le flux a été supprimé',
+	'feed_validator'		=> 'Vérifier la valididé du flux',
 
 	'optimization_complete'		=> 'Optimisation terminée',
 
@@ -119,6 +123,7 @@ return array (
 	'shift_for_first'		=> '+ <code>shift</code> pour passer au premier article de la page',
 	'next_page'			=> 'Passer à la page suivante',
 	'previous_page'			=> 'Passer à la page précédente',
+	'collapse_article'		=> 'Refermer l’article courant',
 
 	'file_to_import'		=> 'Fichier à importer',
 	'import'			=> 'Importer',
@@ -127,11 +132,14 @@ return array (
 
 	'informations'			=> 'Informations',
 	'feed_in_error'			=> 'Ce flux a rencontré un problème. Veuillez vérifier qu’il est toujours accessible puis actualisez-le.',
+	'feed_description'		=> 'Description',
 	'website_url'			=> 'URL du site',
 	'feed_url'			=> 'URL du flux',
+	'articles'			=> 'articles',
 	'number_articles'		=> 'Nombre d’articles',
-	'keep_history'			=> 'Garder l’historique ?',
+	'keep_history'			=> 'Garder les vieux articles ?',
 	'categorize'			=> 'Ranger dans une catégorie',
+	'truncate'			=> 'Supprimer tous les articles',
 	'advanced'			=> 'Avancé',
 	'show_in_all_flux'		=> 'Afficher dans le flux principal',
 	'yes'				=> 'Oui',
@@ -149,9 +157,10 @@ return array (
 
 	'general_configuration'		=> 'Configuration générale',
 	'language'			=> 'Langue',
-	'delete_articles_every'		=> 'Supprimer les articles tous les',
+	'delete_articles_every'		=> 'Supprimer les articles après',
 	'month'				=> 'mois',
-	'persona_connection_email'	=> 'Adresse courriel de connexion (utilise <a href="https://persona.org/">Persona</a>)',
+	'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',
 	'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',
@@ -161,24 +170,36 @@ return array (
 	'sort_order'			=> 'Ordre de tri',
 	'auto_load_more'		=> 'Charger les articles suivants en bas de page',
 	'display_articles_unfolded'	=> 'Afficher les articles dépliés par défaut',
-	'after_onread'			=> 'Après marqué comme lu,',
-	'jump_next'			=> 'sauter au prochain voisin non lu',
-	'reading_icons'		=> 'Icônes de lecture',
-	'top_line'		=> 'Ligne du haut',
-	'bottom_line'		=> 'Ligne du bas',
+	'after_onread'			=> 'Après “marquer tout comme lu”,',
+	'jump_next'			=> 'sauter au prochain voisin non lu (flux ou catégorie)',
+	'reading_icons'			=> 'Icônes de lecture',
+	'top_line'			=> 'Ligne du haut',
+	'bottom_line'			=> 'Ligne du bas',
 	'img_with_lazyload'		=> 'Utiliser le mode “chargement différé” pour les images',
-	'auto_read_when'		=> 'Marquer comme lu lorsque',
-	'article_selected'		=> 'l’article est sélectionné',
-	'article_open_on_website'	=> 'l’article est ouvert sur le site d’origine',
+	'auto_read_when'		=> 'Marquer un article comme lu…',
+	'article_selected'		=> 'lorsque l’article est sélectionné',
+	'article_open_on_website'	=> 'lorsque l’article est ouvert sur le site d’origine',
 	'scroll'			=> 'au défilement de la page',
+	'upon_reception'		=> 'dès la réception du nouvel article',
 	'your_shaarli'			=> 'Votre Shaarli',
+	'your_poche'			=> 'Votre Poche',
+	'your_diaspora_pod'		=> 'Votre pod Diaspora*',
 	'sharing'			=> 'Partage',
 	'share'				=> 'Partager',
-	'by_email'			=> 'Par mail',
-	'on_shaarli'			=> 'Sur votre Shaarli',
+	'by_email'			=> 'Par courriel',
 	'optimize_bdd'			=> 'Optimiser la base de données',
 	'optimize_todo_sometimes'	=> 'À faire de temps en temps pour réduire la taille de la BDD',
 	'theme'				=> 'Thème',
+	'more_information'		=> 'Plus d’informations',
+	'activate_sharing'		=> 'Activer le partage',
+	'shaarli'			=> 'Shaarli',
+	'poche'				=> 'Poche',
+	'diaspora'			=> 'Diaspora*',
+	'twitter'			=> 'Twitter',
+	'g+'				=> 'Google+',
+	'facebook'			=> 'Facebook',
+	'email'				=> 'Courriel',
+	'print'				=> 'Imprimer',
 
 	'article'			=> 'Article',
 	'title'				=> 'Titre',
@@ -214,6 +235,7 @@ return array (
 
 	'logs'				=> 'Logs',
 	'logs_empty'			=> 'Les logs sont vides',
+	'clear_logs'			=> 'Effacer les logs',
 
 	'forbidden_access'		=> 'Accès interdit',
 	'forbidden_access_description'	=> 'L’accès est protégé par un mot de passe, veuillez <a class="signin" href="#">vous connecter</a> pour accéder aux flux.',
@@ -249,59 +271,4 @@ return array (
 	// format pour la fonction date(), %s permet d'indiquer le mois en toutes lettres
 	'format_date'			=> 'd %s Y',
 	'format_date_hour'		=> '\l\e d %s Y \à H\:i',
-
-	// INSTALLATION
-	'freshrss_installation'		=> 'Installation - FreshRSS',
-	'freshrss'			=> 'FreshRSS',
-	'installation_step'		=> 'Installation - étape %d',
-	'steps'				=> 'Étapes',
-	'checks'			=> 'Vérifications',
-	'bdd_configuration'		=> 'Configuration de la base de données',
-	'this_is_the_end'		=> 'This is the end',
-
-	'ok'				=> 'Ok !',
-	'congratulations'		=> 'Félicitations !',
-	'attention'			=> 'Attention !',
-	'damn'				=> 'Arf !',
-	'oops'				=> 'Oups !',
-	'next_step'			=> 'Passer à l’étape suivante',
-
-	'language_defined'		=> 'La langue a bien été définie.',
-	'choose_language'		=> 'Choisissez la langue pour FreshRSS',
-
-	'javascript_is_better'		=> 'FreshRSS est plus agréable à utiliser avec JavaScript activé',
-	'php_is_ok'			=> 'Votre version de PHP est la %s qui est compatible avec FreshRSS',
-	'php_is_nok'			=> 'Votre version de PHP est la %s mais FreshRSS requiert au moins la version %s',
-	'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',
-	'pdomysql_is_ok'		=> 'Vous disposez de PDO et de son driver pour 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 (voir du côté du paquet php-xml ?)',
-	'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',
-	'conf_is_ok'			=> 'Les droits sur le répertoire de configuration sont bons',
-	'data_is_ok'			=> 'Les droits sur le répertoire de data sont bons',
-	'file_is_nok'			=> 'Veuillez vérifier les droits sur le répertoire <em>%s</em>. Le serveur HTTP doit être capable d’écrire dedans',
-	'fix_errors_before'		=> 'Veuillez corriger les erreurs avant de passer à l’étape suivante.',
-
-	'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',
-	'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.',
-	'bdd_conf_is_ko'		=> 'Vérifiez les informations d’accès à la base de données.',
-	'host'				=> 'Hôte',
-	'username'			=> 'Nom utilisateur',
-	'password'			=> 'Mot de passe',
-	'bdd'				=> 'Base de données',
-	'prefix'			=> 'Préfixe des tables',
-
-	'installation_is_ok'		=> 'L’installation s’est bien passée. Il faut maintenant supprimer le fichier <em>install.php</em> pour pouvoir accéder à FreshRSS… ou simplement cliquer sur le bouton ci-dessous :)',
-	'finish_installation'		=> 'Terminer l’installation',
-	'install_not_deleted'		=> 'Quelque chose s’est mal passé, vous devez supprimer le fichier <em>%s</em> à la main.',
 );

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

@@ -0,0 +1,61 @@
+<?php
+return array (
+	'freshrss_installation'		=> 'Installation - FreshRSS',
+	'freshrss'			=> 'FreshRSS',
+	'installation_step'		=> 'Installation - step %d',
+	'steps'				=> 'Steps',
+	'checks'			=> 'Checks',
+	'bdd_configuration'		=> 'Database configuration',
+	'bdd_type'		=> 'Type of database',
+	'version_update'		=> 'Update',
+	'this_is_the_end'		=> 'This is the end',
+
+	'ok'				=> 'Ok!',
+	'congratulations'		=> 'Congratulations!',
+	'attention'			=> 'Attention!',
+	'damn'				=> 'Damn!',
+	'oops'				=> 'Oops!',
+	'next_step'			=> 'Go to the next step',
+
+	'language_defined'		=> 'Language has been defined.',
+	'choose_language'		=> 'Choose a language for FreshRSS',
+
+	'javascript_is_better'		=> 'FreshRSS is more pleasant with JavaScript enabled',
+	'php_is_ok'			=> 'Your PHP version is %s, which is compatible with FreshRSS',
+	'php_is_nok'			=> 'Your PHP version is %s but FreshRSS requires at least version %s',
+	'minz_is_ok'			=> 'You have the Minz framework',
+	'minz_is_nok'			=> 'You lack the Minz framework. You should execute <em>build.sh</em> script or <a href="https://github.com/marienfressinaud/MINZ">download it on Github</a> and install in <em>%s</em> directory the content of its <em>/lib</em> directory.',
+	'curl_is_ok'			=> 'You have version %s of cURL',
+	'curl_is_nok'			=> 'You lack cURL (php5-curl package)',
+	'pdomysql_is_ok'		=> 'You have PDO and its driver for MySQL',
+	'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)',
+	'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',
+	'data_is_ok'			=> 'Permissions on data directory are good',
+	'file_is_nok'			=> 'Check permissions on <em>%s</em> directory. HTTP server must have rights to write into',
+	'fix_errors_before'		=> 'Fix errors before skip to the next step.',
+
+	'general_conf_is_ok'		=> 'General configuration has been saved.',
+	'random_string'			=> 'Random string',
+	'change_value'			=> 'You should change this value by any other',
+	'base_url'			=> 'Base URL',
+	'do_not_change_if_doubt'	=> 'Don’t change if you doubt about it',
+
+	'bdd_conf_is_ok'		=> 'Database configuration has been saved.',
+	'bdd_conf_is_ko'		=> 'Verify your database information.',
+	'host'				=> 'Host',
+	'username'			=> 'Username',
+	'password'			=> 'Password',
+	'bdd'				=> 'Database',
+	'prefix'			=> 'Table prefix',
+
+	'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.',
+	'finish_installation'		=> 'Complete installation',
+	'install_not_deleted'		=> 'Something went wrong; you must delete the file <em>%s</em> manually.',
+);

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

@@ -0,0 +1,61 @@
+<?php
+return array (
+	'freshrss_installation'		=> 'Installation - FreshRSS',
+	'freshrss'			=> 'FreshRSS',
+	'installation_step'		=> 'Installation - étape %d',
+	'steps'				=> 'Étapes',
+	'checks'			=> 'Vérifications',
+	'bdd_configuration'		=> 'Base de données',
+	'bdd_type'		=> 'Type de base de données',
+	'version_update'		=> 'Mise à jour',
+	'this_is_the_end'		=> 'This is the end',
+
+	'ok'				=> 'Ok !',
+	'congratulations'		=> 'Félicitations !',
+	'attention'			=> 'Attention !',
+	'damn'				=> 'Arf !',
+	'oops'				=> 'Oups !',
+	'next_step'			=> 'Passer à l’étape suivante',
+
+	'language_defined'		=> 'La langue a bien été définie.',
+	'choose_language'		=> 'Choisissez la langue pour FreshRSS',
+
+	'javascript_is_better'		=> 'FreshRSS est plus agréable à utiliser avec JavaScript activé',
+	'php_is_ok'			=> 'Votre version de PHP est la %s, qui est compatible avec FreshRSS',
+	'php_is_nok'			=> 'Votre version de PHP est la %s mais FreshRSS requiert au moins la version %s',
+	'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)',
+	'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)',
+	'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',
+	'data_is_ok'			=> 'Les droits sur le répertoire de data sont bons',
+	'file_is_nok'			=> 'Veuillez vérifier les droits sur le répertoire <em>%s</em>. Le serveur HTTP doit être capable d’écrire dedans',
+	'fix_errors_before'		=> 'Veuillez corriger les erreurs avant de passer à l’étape suivante.',
+
+	'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',
+	'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.',
+	'bdd_conf_is_ko'		=> 'Vérifiez les informations d’accès à la base de données.',
+	'host'				=> 'Hôte',
+	'username'			=> 'Nom utilisateur',
+	'password'			=> 'Mot de passe',
+	'bdd'				=> 'Base de données',
+	'prefix'			=> 'Préfixe des tables',
+
+	'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.',
+	'finish_installation'		=> 'Terminer l’installation',
+	'install_not_deleted'		=> 'Quelque chose s’est mal passé, vous devez supprimer le fichier <em>%s</em> à la main.',
+);

+ 8 - 5
app/layout/aside_configure.phtml

@@ -1,10 +1,13 @@
 <div class="nav nav-list aside">
-	<li class="nav-header"><?php echo Translate::t ('configuration'); ?></li>
+	<li class="nav-header"><?php echo Minz_Translate::t ('configuration'); ?></li>
 
-	<li class="item<?php echo Request::actionName () == 'display' ? ' active' : ''; ?>">
-		<a href="<?php echo Url::display (array ('c' => 'configure', 'a' => 'display')); ?>"><?php echo Translate::t ('general_and_reading'); ?></a>
+	<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>
 	</li>
-	<li class="item<?php echo Request::actionName () == 'shortcut' ? ' active' : ''; ?>">
-		<a href="<?php echo Url::display (array ('c' => 'configure', 'a' => 'shortcut')); ?>"><?php echo Translate::t ('shortcuts'); ?></a>
+	<li class="item<?php echo Minz_Request::actionName () == 'sharing' ? ' active' : ''; ?>">
+		<a href="<?php echo _url ('configure', 'sharing'); ?>"><?php echo Minz_Translate::t ('sharing'); ?></a>
+	</li>
+	<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>

+ 17 - 17
app/layout/aside_feed.phtml

@@ -1,22 +1,22 @@
 <ul class="nav nav-list aside aside_feed">
-	<li class="nav-header"><?php echo Translate::t ('your_rss_feeds'); ?></li>
+	<li class="nav-header"><?php echo Minz_Translate::t ('your_rss_feeds'); ?></li>
 
-	<li class="nav-form"><form id="add_rss" method="post" action="<?php echo Url::display (array ('c' => 'feed', 'a' => 'add')); ?>">
+	<li class="nav-form"><form id="add_rss" method="post" action="<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'add')); ?>">
 		<div class="stick">
-			<input type="url" name="url_rss" placeholder="<?php echo Translate::t ('add_rss_feed'); ?>" />
+			<input type="url" name="url_rss" placeholder="<?php echo Minz_Translate::t ('add_rss_feed'); ?>" />
 			<div class="dropdown">
 				<div id="dropdown-cat" class="dropdown-target"></div>
 
-				<a class="dropdown-toggle btn" href="#dropdown-cat"><i class="icon i_down"></i></a>
+				<a class="dropdown-toggle btn" href="#dropdown-cat"><?php echo FreshRSS_Themes::icon('down'); ?></a>
 				<ul class="dropdown-menu">
-					<li class="dropdown-close"><a href="#close">&nbsp;</a></li>
+					<li class="dropdown-close"><a href="#close"></a></li>
 
-					<li class="dropdown-header"><?php echo Translate::t ('category'); ?></li>
+					<li class="dropdown-header"><?php echo Minz_Translate::t ('category'); ?></li>
 
 					<li class="input">
 						<select name="category" id="category">
 						<?php foreach ($this->categories as $cat) { ?>
-						<option value="<?php echo $cat->id (); ?>"<?php echo $cat->id () == '000000' ? ' selected="selected"' : ''; ?>>
+						<option value="<?php echo $cat->id (); ?>"<?php echo $cat->id () == 1 ? ' selected="selected"' : ''; ?>>
 							<?php echo $cat->name (); ?>
 						</option>
 						<?php } ?>
@@ -25,25 +25,25 @@
 
 					<li class="separator"></li>
 
-					<li class="dropdown-header"><?php echo Translate::t ('http_authentication'); ?></li>
+					<li class="dropdown-header"><?php echo Minz_Translate::t ('http_authentication'); ?></li>
 					<li class="input">
-						<input type="text" name="username" id="username" placeholder="<?php echo Translate::t ('username'); ?>" />
+						<input type="text" name="username" id="username" placeholder="<?php echo Minz_Translate::t ('username'); ?>" />
 					</li>
 					<li class="input">
-						<input type="password" name="password" id="password" placeholder="<?php echo Translate::t ('password'); ?>" />
+						<input type="password" name="password" id="password" placeholder="<?php echo Minz_Translate::t ('password'); ?>" />
 					</li>
 				</ul>
 			</div>
-			<button class="btn" type="submit"><i class="icon i_add"></i></button>
+			<button class="btn" type="submit"><?php echo FreshRSS_Themes::icon('add'); ?></button>
 		</div>
 	</form></li>
 
-	<li class="item<?php echo Request::actionName () == 'importExport' ? ' active' : ''; ?>">
-		<a href="<?php echo _url ('configure', 'importExport'); ?>"><?php echo Translate::t ('import_export_opml'); ?></a>
+	<li class="item<?php echo Minz_Request::actionName () == 'importExport' ? ' active' : ''; ?>">
+		<a href="<?php echo _url ('configure', 'importExport'); ?>"><?php echo Minz_Translate::t ('import_export_opml'); ?></a>
 	</li>
 
-	<li class="item<?php echo Request::actionName () == 'categorize' ? ' active' : ''; ?>">
-		<a href="<?php echo _url ('configure', 'categorize'); ?>"><?php echo Translate::t ('categories_management'); ?></a>
+	<li class="item<?php echo Minz_Request::actionName () == 'categorize' ? ' active' : ''; ?>">
+		<a href="<?php echo _url ('configure', 'categorize'); ?>"><?php echo Minz_Translate::t ('categories_management'); ?></a>
 	</li>
 
 	<li class="separator"></li>
@@ -54,11 +54,11 @@
 	<li class="item<?php echo ($this->flux && $this->flux->id () == $feed->id ()) ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>">
 		<a href="<?php echo _url ('configure', 'feed', 'id', $feed->id ()); ?>">
 			<img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" />
-			<?php echo htmlspecialchars($feed->name (), ENT_NOQUOTES, 'UTF-8'); ?>
+			<?php echo $feed->name (); ?>
 		</a>
 	</li>
 	<?php } ?>
 	<?php } else { ?>
-	<li class="item disable"><?php echo Translate::t ('no_rss_feed'); ?></li>
+	<li class="item disable"><?php echo Minz_Translate::t ('no_rss_feed'); ?></li>
 	<?php } ?>
 </ul>

+ 20 - 22
app/layout/aside_flux.phtml

@@ -1,32 +1,32 @@
 <div class="aside aside_flux" id="aside_flux">
-	<a class="toggle_aside" href="#close"><i class="icon i_close"></i></a>
+	<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 ()) { ?>
 		<li>
 			<div class="stick">
-				<a class="btn btn-important" href="<?php echo _url ('configure', 'feed'); ?>"><?php echo Translate::t ('subscription_management'); ?></a>
-				<a class="btn btn-important" href="<?php echo _url ('configure', 'categorize'); ?>"><i class="icon i_category"></i></a>
+				<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)) { ?>
-		<li><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Translate::t ('about_freshrss'); ?></a></li>
+		<li><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Minz_Translate::t ('about_freshrss'); ?></a></li>
 		<?php } ?>
 
 		<li>
 			<div class="category all">
-				<a data-unread="<?php echo $this->nb_not_read; ?>" class="btn<?php echo $this->get_c == 'all' ? ' active' : ''; ?>" href="<?php echo _url ('index', 'index'); ?>">
-					<i class="icon i_all"></i>
-					<?php echo Translate::t ('all_feeds', $this->nb_total); ?>
+				<a data-unread="<?php echo $this->nb_not_read; ?>" class="btn<?php echo $this->get_c == 'a' ? ' active' : ''; ?>" href="<?php echo _url ('index', 'index'); ?>">
+					<?php echo FreshRSS_Themes::icon('all'); ?>
+					<?php echo Minz_Translate::t ('all_feeds'); ?>
 				</a>
 			</div>
 		</li>
 
 		<li>
 			<div class="category favorites">
-				<a data-unread="<?php echo $this->nb_favorites['unread']; ?>" class="btn<?php echo $this->get_c == 'favoris' ? ' active' : ''; ?>" href="<?php echo _url ('index', 'index', 'get', 'favoris'); ?>">
-					<i class="icon i_bookmark"></i>
-					<?php echo Translate::t ('favorite_feeds', $this->nb_favorites['read'] + $this->nb_favorites['unread']); ?>
+				<a data-unread="<?php echo $this->nb_favorites['unread']; ?>" class="btn<?php echo $this->get_c == 's' ? ' active' : ''; ?>" href="<?php echo _url ('index', 'index', 'get', 's'); ?>">
+					<?php echo FreshRSS_Themes::icon('bookmark'); ?>
+					<?php echo Minz_Translate::t ('favorite_feeds', $this->nb_favorites['all']); ?>
 				</a>
 			</div>
 		</li>
@@ -37,10 +37,8 @@
 		<li>
 			<?php $c_active = false; if ($this->get_c == $cat->id ()) { $c_active = true; } ?>
 			<div class="category stick<?php echo $c_active ? ' active' : ''; ?>">
-				<a data-unread="<?php echo $cat->nbNotRead (); ?>" class="btn<?php echo $c_active ? ' active' : ''; ?>" href="<?php echo _url ('index', 'index', 'get', 'c_' . $cat->id ()); ?>">
-					<?php echo htmlspecialchars($cat->name (), ENT_NOQUOTES, 'UTF-8'); ?>
-				</a>
-				<a class="btn dropdown-toggle" href="#"><i class="icon <?php echo $c_active ? 'i_up' : 'i_down'; ?>"></i></a>
+				<a data-unread="<?php echo $cat->nbNotRead (); ?>" class="btn<?php echo $c_active ? ' active' : ''; ?>" href="<?php echo _url ('index', 'index', 'get', 'c_' . $cat->id ()); ?>"><?php echo $cat->name (); ?></a>
+				<a class="btn dropdown-toggle" href="#"><?php echo FreshRSS_Themes::icon($c_active ? 'up' : 'down'); ?></a>
 			</div>
 
 			<ul class="feeds<?php echo $c_active ? ' active' : ''; ?>">
@@ -51,11 +49,11 @@
 				<li id="f_<?php echo $feed_id; ?>" class="item<?php echo $f_active ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>">
 					<div class="dropdown">
 						<div class="dropdown-target"></div>
-						<a class="dropdown-toggle" data-fweb="<?php echo $feed->website (); ?>"><i class="icon i_configure"></i></a>
+						<a class="dropdown-toggle" data-fweb="<?php echo $feed->website (); ?>"><?php echo FreshRSS_Themes::icon('configure'); ?></a>
 <?php /* feed_config_template */ ?>
 					</div>
 					<img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" />
-					<a class="feed" data-unread="<?php echo $feed->nbNotRead (); ?>" data-priority="<?php echo $feed->priority (); ?>" href="<?php echo _url ('index', 'index', 'get', 'f_' . $feed_id); ?>"><?php echo htmlspecialchars($feed->name(), ENT_NOQUOTES, 'UTF-8'); ?></a>
+					<a class="feed" data-unread="<?php echo $feed->nbNotRead (); ?>" data-priority="<?php echo $feed->priority (); ?>" href="<?php echo _url ('index', 'index', 'get', 'f_' . $feed_id); ?>"><?php echo $feed->name(); ?></a>
 				</li>
 				<?php } ?>
 			</ul>
@@ -68,14 +66,14 @@
 
 <script id="feed_config_template" type="text/html">
 	<ul class="dropdown-menu">
-		<li class="dropdown-close"><a href="#close">&nbsp;</a></li>
-		<li class="item"><a href="<?php echo _url ('index', 'index', 'get', 'f_!!!!!!'); ?>"><?php echo Translate::t ('filter'); ?></a></li>
-		<li class="item"><a target="_blank" href="http://example.net/"><?php echo Translate::t ('see_website'); ?></a></li>
+		<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 ()) { ?>
 		<li class="separator"></li>
-		<li class="item"><a href="<?php echo _url ('configure', 'feed', 'id', '!!!!!!'); ?>"><?php echo Translate::t ('administration'); ?></a></li>
-		<li class="item"><a href="<?php echo _url ('feed', 'actualize', 'id', '!!!!!!'); ?>"><?php echo Translate::t ('actualize'); ?></a></li>
-		<li class="item"><a href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', 'f_!!!!!!'); ?>"><?php echo Translate::t ('mark_read'); ?></a></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>
+		<li class="item"><a href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', 'f_!!!!!!'); ?>"><?php echo Minz_Translate::t ('mark_read'); ?></a></li>
 		<?php } ?>
 	</ul>
 </script>

+ 20 - 19
app/layout/header.phtml

@@ -1,9 +1,9 @@
 <?php if (login_is_conf ($this->conf)) { ?>
 <ul class="nav nav-head nav-login">
 	<?php if (!is_logged ()) { ?>
-	<li class="item"><i class="icon i_login"></i> <a class="signin" href="#"><?php echo Translate::t ('login'); ?></a></li>
+	<li class="item"><?php echo FreshRSS_Themes::icon('login'); ?> <a class="signin" href="#"><?php echo Minz_Translate::t ('login'); ?></a></li>
 	<?php } else { ?>
-	<li class="item"><i class="icon i_logout"></i> <a class="signout" href="#"><?php echo Translate::t ('logout'); ?></a></li>
+	<li class="item"><?php echo FreshRSS_Themes::icon('logout'); ?> <a class="signout" href="#"><?php echo Minz_Translate::t ('logout'); ?></a></li>
 	<?php } ?>
 </ul>
 <?php } ?>
@@ -12,8 +12,8 @@
 	<div class="item title">
 		<h1>
 			<a href="<?php echo _url ('index', 'index'); ?>">
-				<img class="logo" width="32" height="32" src="<?php echo Url::display ('/themes/icons/icon-32.png'); ?>" alt="[logo]" />
-				<?php echo Configuration::title (); ?>
+				<img class="logo" src="<?php echo Minz_Url::display ('/themes/icons/icon.svg'); ?>" alt="⊚" />
+				<?php echo Minz_Configuration::title (); ?>
 			</a>
 		</h1>
 	</div>
@@ -24,25 +24,25 @@
 		         $this->conf->anonAccess() == 'yes') { ?>
 		<form action="<?php echo _url ('index', 'index'); ?>" method="get">
 			<div class="stick">
-				<?php $search = Request::param ('search', ''); ?>
-				<input type="text" name="search" id="search" value="<?php echo $search; ?>" placeholder="<?php echo Translate::t ('search'); ?>" />
+				<?php $search = Minz_Request::param ('search', ''); ?>
+				<input type="search" name="search" id="search" value="<?php echo $search; ?>" placeholder="<?php echo Minz_Translate::t ('search'); ?>" />
 
-				<?php $get = Request::param ('get', ''); ?>
+				<?php $get = Minz_Request::param ('get', ''); ?>
 				<?php if($get != '') { ?>
 				<input type="hidden" name="get" value="<?php echo $get; ?>" />
 				<?php } ?>
 
-				<?php $order = Request::param ('order', ''); ?>
+				<?php $order = Minz_Request::param ('order', ''); ?>
 				<?php if($order != '') { ?>
 				<input type="hidden" name="order" value="<?php echo $order; ?>" />
 				<?php } ?>
 
-				<?php $state = Request::param ('state', ''); ?>
+				<?php $state = Minz_Request::param ('state', ''); ?>
 				<?php if($state != '') { ?>
 				<input type="hidden" name="state" value="<?php echo $state; ?>" />
 				<?php } ?>
 
-				<button class="btn" type="submit"><i class="icon i_search"></i></button>
+				<button class="btn" type="submit"><?php echo FreshRSS_Themes::icon('search'); ?></button>
 			</div>
 		</form>
 		<?php } ?>
@@ -53,18 +53,19 @@
 		<div class="dropdown">
 			<div id="dropdown-configure" class="dropdown-target"></div>
 
-			<a class="btn dropdown-toggle" href="#dropdown-configure"><i class="icon i_configure"></i></a>
+			<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">&nbsp;</a></li>
-				<li class="dropdown-header"><?php echo Translate::t ('configuration'); ?></li>
-				<li class="item"><a href="<?php echo _url ('configure', 'display'); ?>"><?php echo Translate::t ('general_and_reading'); ?></a></li>
-				<li class="item"><a href="<?php echo _url ('configure', 'shortcut'); ?>"><?php echo Translate::t ('shortcuts'); ?></a></li>
+				<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', '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 Translate::t ('about'); ?></a></li>
-				<li class="item"><a href="<?php echo _url ('index', 'logs'); ?>"><?php echo Translate::t ('logs'); ?></a></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 ()) { ?>
 				<li class="separator"></li>
-				<li class="item"><a class="signout" href="#"><i class="icon i_logout"></i> <?php echo Translate::t ('logout'); ?></a></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>
@@ -73,7 +74,7 @@
 
 	if (login_is_conf ($this->conf) && !is_logged ()) { ?>
 	<div class="item configure">
-		<i class="icon i_login"></i> <a class="signin" href="#"><?php echo Translate::t ('login'); ?></a>
+		<?php echo FreshRSS_Themes::icon('login'); ?> <a class="signin" href="#"><?php echo Minz_Translate::t ('login'); ?></a>
 	</div>
 	<?php } ?>
 </div>

+ 12 - 9
app/layout/layout.phtml

@@ -10,17 +10,20 @@
 <?php $this->renderHelper ('javascript_vars'); ?>
 		//]]></script>
 <?php
-	$next = isset($this->entryPaginator) ? $this->entryPaginator->next() : '';
-	if (!empty($next)) {
-		$params = Request::params ();
-		$params['next'] = $next;
+	if (!empty($this->nextId)) {
+		$params = Minz_Request::params ();
+		$params['next'] = $this->nextId;
 ?>
-		<link id="prefetch" rel="next prefetch" href="<?php echo Url::display (array ('c' => Request::controllerName (), 'a' => Request::actionName (), 'params' => $params)); ?>" />
+		<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 Url::display ('/favicon.ico'); ?>" />
+		<link rel="icon" href="<?php echo Minz_Url::display ('/favicon.ico'); ?>" />
 <?php if (isset ($this->rss_url)) { ?>
-		<link rel="alternate" type="application/rss+xml" title="<?php echo htmlspecialchars($this->rss_title, ENT_COMPAT, 'UTF-8'); ?>" href="<?php echo Url::display ($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 } ?>
+		<link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('starred', true); ?>">
+		<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="robots" content="noindex,nofollow" />
 	</head>
 	<body>
@@ -32,11 +35,11 @@
 
 <?php
 	if (isset ($this->notification)) {
-		touch(PUBLIC_PATH . '/data/touch.txt', time() + 1);
+		invalidateHttpCache();
 ?>
 <div class="notification <?php echo $this->notification['type']; ?>">
 	<?php echo $this->notification['content']; ?>
-	<a class="close" href=""><i class="icon i_close"></i></a>
+	<a class="close" href=""><?php echo FreshRSS_Themes::icon('close'); ?></a>
 </div>
 <?php } ?>
 	</body>

+ 3 - 3
app/layout/nav_entries.phtml

@@ -1,5 +1,5 @@
 <ul id="nav_entries">
-	<li class="item"><a class="previous_entry" href="#"><i class="icon i_prev"></i></a></li>
-	<li class="item"><a class="up" href="#"><i class="icon i_up"></i></a></li>
-	<li class="item"><a class="next_entry" href="#"><i class="icon i_next"></i></a></li>
+	<li class="item"><a class="previous_entry" href="#"><?php echo FreshRSS_Themes::icon('prev'); ?></a></li>
+	<li class="item"><a class="up" href="#"><?php echo FreshRSS_Themes::icon('up'); ?></a></li>
+	<li class="item"><a class="next_entry" href="#"><?php echo FreshRSS_Themes::icon('next'); ?></a></li>
 </ul>

+ 69 - 43
app/layout/nav_menu.phtml

@@ -1,21 +1,22 @@
 <div class="nav_menu">
-	<a class="btn toggle_aside" href="#aside_flux"><i class="icon i_category"></i></a>
+	<a class="btn toggle_aside" href="#aside_flux"><?php echo FreshRSS_Themes::icon('category'); ?></a>
 
 	<?php if (!login_is_conf ($this->conf) || is_logged ()) { ?>
-	<a id="actualize" class="btn" href="<?php echo _url ('feed', 'actualize'); ?>"><i class="icon i_refresh"></i></a>
+	<a id="actualize" class="btn" href="<?php echo _url ('feed', 'actualize'); ?>"><?php echo FreshRSS_Themes::icon('refresh'); ?></a>
 
 	<?php
 		$get = false;
-		$string_mark = Translate::t ('mark_all_read');
+		$string_mark = Minz_Translate::t ('mark_all_read');
 		if ($this->get_f) {
 			$get = 'f_' . $this->get_f;
-			$string_mark = Translate::t ('mark_feed_read');
-		} elseif ($this->get_c &&
-		          $this->get_c != 'all' &&
-		          $this->get_c != 'favoris' &&
-		          $this->get_c != 'public') {
-			$get = 'c_' . $this->get_c;
-			$string_mark = Translate::t ('mark_cat_read');
+			$string_mark = Minz_Translate::t ('mark_feed_read');
+		} elseif ($this->get_c && $this->get_c != 'a') {
+			if ($this->get_c === 's') {
+				$get = 's';
+			} else {
+				$get = 'c_' . $this->get_c;
+			}
+			$string_mark = Minz_Translate::t ('mark_cat_read');
 		}
 		$nextGet = $get;
 		if (($this->conf->onread_jump_next () === 'yes') && (strlen ($get) > 2)) {
@@ -32,7 +33,7 @@
 						$anotherUnreadId = $cat->id ();
 						if ($foundCurrent) break;
 					}
-					$nextGet = strlen ($anotherUnreadId) > 1 ? 'c_' . $anotherUnreadId : 'all';
+					$nextGet = empty ($anotherUnreadId) ? 'a' : 'c_' . $anotherUnreadId;
 					break;
 				case 'f':
 					foreach ($this->cat_aside as $cat) {
@@ -49,37 +50,40 @@
 							break;
 						}
 					}
-					$nextGet = strlen ($anotherUnreadId) > 1 ? 'f_' . $anotherUnreadId : 'c_' . $this->get_c;
+					$nextGet = empty ($anotherUnreadId) ? 'c_' . $this->get_c : 'f_' . $anotherUnreadId;
 					break;
 			}
 		}
+		$p = isset($this->entries[0]) ? $this->entries[0] : null;
+		$idMax = $p === null ? '0' : $p->id();
+		$markReadUrl = _url ('entry', 'read', 'is_read', 1, 'get', $get, 'nextGet', $nextGet, 'idMax', $idMax);
+		Minz_Session::_param ('markReadUrl', $markReadUrl);
 	?>
 
 	<div class="stick" id="nav_menu_read_all">
-		<a class="read_all btn" href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', $get, 'nextGet', $nextGet); ?>"><?php echo Translate::t ('mark_read'); ?></a>
+		<a class="read_all btn" href="<?php echo $markReadUrl; ?>"><?php echo Minz_Translate::t ('mark_read'); ?></a>
 		<div class="dropdown">
 			<div id="dropdown-read" class="dropdown-target"></div>
 
-			<a class="dropdown-toggle btn" href="#dropdown-read"><i class="icon i_down"></i></a>
+			<a class="dropdown-toggle btn" href="#dropdown-read"><?php echo FreshRSS_Themes::icon('down'); ?></a>
 			<ul class="dropdown-menu">
-				<li class="dropdown-close"><a href="#close">&nbsp;</a></li>
+				<li class="dropdown-close"><a href="#close"></a></li>
 
-				<li class="item"><a href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', $get, 'nextGet', $nextGet); ?>"><?php echo $string_mark; ?></a></li> 
+				<li class="item"><a href="<?php echo $markReadUrl; ?>"><?php echo $string_mark; ?></a></li> 
 				<li class="separator"></li>
 <?php
-	$date = getdate ();
-	$today = mktime (0, 0, 0, $date['mon'], $date['mday'], $date['year']);
+	$today = $this->today;
 	$one_week = $today - 604800;
 ?>
-				<li class="item"><a href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', $get, 'dateMax', $today); ?>"><?php echo Translate::t ('before_one_day'); ?></a></li>
-				<li class="item"><a href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', $get, 'dateMax', $one_week); ?>"><?php echo Translate::t ('before_one_week'); ?></a></li>
+				<li class="item"><a href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', $get, 'idMax', $today . '000000'); ?>"><?php echo Minz_Translate::t ('before_one_day'); ?></a></li>
+				<li class="item"><a href="<?php echo _url ('entry', 'read', 'is_read', 1, 'get', $get, 'idMax', $one_week . '000000'); ?>"><?php echo Minz_Translate::t ('before_one_week'); ?></a></li>
 			</ul>
 		</div>
 	</div>
 	<?php } ?>
 
 	<?php
-		$params = Request::params ();
+		$params = Minz_Request::params ();
 		if (isset ($params['search'])) {
 			$params['search'] = urlencode ($params['search']);
 		}
@@ -91,33 +95,33 @@
 	?>
 	<div class="dropdown" id="nav_menu_views">
 		<div id="dropdown-views" class="dropdown-target"></div>
-		<a class="dropdown-toggle btn" href="#dropdown-views"><?php echo Translate::t ('display'); ?> <i class="icon i_down"></i></a>
+		<a class="dropdown-toggle btn" href="#dropdown-views"><?php echo Minz_Translate::t ('display'); ?> <?php echo FreshRSS_Themes::icon('down'); ?></a>
 		<ul class="dropdown-menu">
-			<li class="dropdown-close"><a href="#close">&nbsp;</a></li>
+			<li class="dropdown-close"><a href="#close"></a></li>
 
 			<?php
 				$url_output = $url;
-				$actual_view = Request::param('output', 'normal');
+				$actual_view = Minz_Request::param('output', 'normal');
 			?>
 			<?php if($actual_view != 'normal') { ?>
 			<li class="item">
 				<?php $url_output['params']['output'] = 'normal'; ?>
-				<a class="view_normal" href="<?php echo Url::display ($url_output); ?>">
-					<?php echo Translate::t ('normal_view'); ?>
+				<a class="view_normal" href="<?php echo Minz_Url::display ($url_output); ?>">
+					<?php echo Minz_Translate::t ('normal_view'); ?>
 				</a>
 			</li>
 			<?php } if($actual_view != 'reader') { ?>
 			<li class="item">
 				<?php $url_output['params']['output'] = 'reader'; ?>
-				<a class="view_normal" href="<?php echo Url::display ($url_output); ?>">
-					<?php echo Translate::t ('reader_view'); ?>
+				<a class="view_normal" href="<?php echo Minz_Url::display ($url_output); ?>">
+					<?php echo Minz_Translate::t ('reader_view'); ?>
 				</a>
 			</li>
 			<?php } if($actual_view != 'global') { ?>
 			<li class="item">
 				<?php $url_output['params']['output'] = 'global'; ?>
-				<a class="view_normal" href="<?php echo Url::display ($url_output); ?>">
-					<?php echo Translate::t ('global_view'); ?>
+				<a class="view_normal" href="<?php echo Minz_Url::display ($url_output); ?>">
+					<?php echo Minz_Translate::t ('global_view'); ?>
 				</a>
 			</li>
 			<?php } ?>
@@ -130,15 +134,15 @@
 					if ($this->state == 'not_read') {
 						$url_state['params']['state'] = 'all';
 				?>
-				<a class="print_all" href="<?php echo Url::display ($url_state); ?>">
-					<?php echo Translate::t ('show_all_articles'); ?>
+				<a class="print_all" href="<?php echo Minz_Url::display ($url_state); ?>">
+					<?php echo Minz_Translate::t ('show_all_articles'); ?>
 				</a>
 				<?php
 					} else {
 						$url_state['params']['state'] = 'not_read';
 				?>
-				<a class="print_non_read" href="<?php echo Url::display ($url_state); ?>">
-					<?php echo Translate::t ('show_not_reads'); ?>
+				<a class="print_non_read" href="<?php echo Minz_Url::display ($url_state); ?>">
+					<?php echo Minz_Translate::t ('show_not_reads'); ?>
 				</a>
 				<?php } ?>
 			</li>
@@ -146,18 +150,18 @@
 			<li class="item">
 				<?php
 					$url_order = $url;
-					if ($this->order == 'low_to_high') {
-						$url_order['params']['order'] = 'high_to_low';
+					if ($this->order === 'DESC') {
+						$url_order['params']['order'] = 'ASC';
 				?>
-				<a href="<?php echo Url::display ($url_order); ?>">
-					<?php echo Translate::t ('older_first'); ?>
+				<a href="<?php echo Minz_Url::display ($url_order); ?>">
+					<?php echo Minz_Translate::t ('older_first'); ?>
 				</a>
 				<?php
 					} else {
-						$url_order['params']['order'] = 'low_to_high';
+						$url_order['params']['order'] = 'DESC';
 				?>
-				<a href="<?php echo Url::display ($url_order); ?>">
-					<?php echo Translate::t ('newer_first'); ?>
+				<a href="<?php echo Minz_Url::display ($url_order); ?>">
+					<?php echo Minz_Translate::t ('newer_first'); ?>
 				</a>
 				<?php } ?>
 			</li>
@@ -165,10 +169,32 @@
 			<li class="separator"></li>
 
 			<li class="item">
-				<a class="view_rss" target="_blank" href="<?php echo Url::display ($this->rss_url); ?>">
-					<?php echo Translate::t ('rss_view'); ?>
+				<a class="view_rss" target="_blank" href="<?php echo Minz_Url::display ($this->rss_url); ?>">
+					<?php echo Minz_Translate::t ('rss_view'); ?>
 				</a>
 			</li>
 		</ul>
 	</div>
+
+	<div class="item search">
+		<form action="<?php echo _url ('index', 'index'); ?>" method="get">
+			<?php $search = Minz_Request::param ('search', ''); ?>
+			<input type="search" name="search" value="<?php echo $search; ?>" placeholder="<?php echo Minz_Translate::t ('search_short'); ?>" />
+
+			<?php $get = Minz_Request::param ('get', ''); ?>
+			<?php if($get != '') { ?>
+			<input type="hidden" name="get" value="<?php echo $get; ?>" />
+			<?php } ?>
+
+			<?php $order = Minz_Request::param ('order', ''); ?>
+			<?php if($order != '') { ?>
+			<input type="hidden" name="order" value="<?php echo $order; ?>" />
+			<?php } ?>
+
+			<?php $state = Minz_Request::param ('state', ''); ?>
+			<?php if($state != '') { ?>
+			<input type="hidden" name="state" value="<?php echo $state; ?>" />
+			<?php } ?>
+		</form>
+	</div>
 </div>

+ 0 - 332
app/models/Category.php

@@ -1,332 +0,0 @@
-<?php
-
-class Category extends Model {
-	private $id = false;
-	private $name;
-	private $color;
-	private $nbFeed = -1;
-	private $nbNotRead = -1;
-	private $feeds = null;
-
-	public function __construct ($name = '', $color = '#0062BE', $feeds = null) {
-		$this->_name ($name);
-		$this->_color ($color);
-		if (isset ($feeds)) {
-			$this->_feeds ($feeds);
-			$this->nbFeed = 0;
-			$this->nbNotRead = 0;
-			foreach ($feeds as $feed) {
-				$this->nbFeed++;
-				$this->nbNotRead += $feed->nbNotRead ();
-			}
-		}
-	}
-
-	public function id () {
-		if (!$this->id) {
-			return small_hash ($this->name . time () . Configuration::selApplication ());
-		} else {
-			return $this->id;
-		}
-	}
-	public function name () {
-		return $this->name;
-	}
-	public function color () {
-		return $this->color;
-	}
-	public function nbFeed () {
-		if ($this->nbFeed < 0) {
-			$catDAO = new CategoryDAO ();
-			$this->nbFeed = $catDAO->countFeed ($this->id ());
-		}
-
-		return $this->nbFeed;
-	}
-	public function nbNotRead () {
-		if ($this->nbNotRead < 0) {
-			$catDAO = new CategoryDAO ();
-			$this->nbNotRead = $catDAO->countNotRead ($this->id ());
-		}
-
-		return $this->nbNotRead;
-	}
-	public function feeds () {
-		if (is_null ($this->feeds)) {
-			$feedDAO = new FeedDAO ();
-			$this->feeds = $feedDAO->listByCategory ($this->id ());
-			$this->nbFeed = 0;
-			$this->nbNotRead = 0;
-			foreach ($this->feeds as $feed) {
-				$this->nbFeed++;
-				$this->nbNotRead += $feed->nbNotRead ();
-			}
-		}
-
-		return $this->feeds;
-	}
-
-	public function _id ($value) {
-		$this->id = $value;
-	}
-	public function _name ($value) {
-		$this->name = $value;
-	}
-	public function _color ($value) {
-		if (preg_match ('/^#([0-9a-f]{3}|[0-9a-f]{6})$/i', $value)) {
-			$this->color = $value;
-		} else {
-			$this->color = '#0062BE';
-		}
-	}
-	public function _feeds ($values) {
-		if (!is_array ($values)) {
-			$values = array ($values);
-		}
-
-		$this->feeds = $values;
-	}
-}
-
-class CategoryDAO extends Model_pdo {
-	public function addCategory ($valuesTmp) {
-		$sql = 'INSERT INTO ' . $this->prefix . 'category (id, name, color) VALUES(?, ?, ?)';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array (
-			$valuesTmp['id'],
-			$valuesTmp['name'],
-			$valuesTmp['color'],
-		);
-
-		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 updateCategory ($id, $valuesTmp) {
-		$sql = 'UPDATE ' . $this->prefix . 'category SET name=?, color=? WHERE id=?';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array (
-			$valuesTmp['name'],
-			$valuesTmp['color'],
-			$id
-		);
-
-		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 deleteCategory ($id) {
-		$sql = 'DELETE FROM ' . $this->prefix . 'category WHERE id=?';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array ($id);
-
-		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 searchById ($id) {
-		$sql = 'SELECT * FROM ' . $this->prefix . 'category WHERE id=?';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array ($id);
-
-		$stm->execute ($values);
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-		$cat = HelperCategory::daoToCategory ($res);
-
-		if (isset ($cat[0])) {
-			return $cat[0];
-		} else {
-			return false;
-		}
-	}
-	public function searchByName ($name) {
-		$sql = 'SELECT * FROM ' . $this->prefix . 'category WHERE name=?';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array ($name);
-
-		$stm->execute ($values);
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-		$cat = HelperCategory::daoToCategory ($res);
-
-		if (isset ($cat[0])) {
-			return $cat[0];
-		} else {
-			return false;
-		}
-	}
-
-	public function listCategories ($prePopulateFeeds = true) {
-		if ($prePopulateFeeds) {
-			$sql = 'SELECT c.id AS c_id, c.name AS c_name, c.color AS c_color, '
-			     . 'COUNT(CASE WHEN e.is_read = 0 THEN 1 END) AS nbNotRead, '
-			     . 'COUNT(e.id) AS nbEntries, '
-			     . 'f.* '
-			     . 'FROM ' . $this->prefix . 'category c '
-			     . 'LEFT OUTER JOIN ' . $this->prefix . 'feed f ON f.category = c.id '
-			     . 'LEFT OUTER JOIN ' . $this->prefix . 'entry e ON e.id_feed = f.id '
-			     . 'GROUP BY f.id '
-			     . 'ORDER BY c.name, f.name';
-			$stm = $this->bd->prepare ($sql);
-			$stm->execute ();
-			return HelperCategory::daoToCategoryPrepopulated ($stm->fetchAll (PDO::FETCH_ASSOC));
-		} else {
-			$sql = 'SELECT * FROM ' . $this->prefix . 'category ORDER BY name';
-			$stm = $this->bd->prepare ($sql);
-			$stm->execute ();
-			return HelperCategory::daoToCategory ($stm->fetchAll (PDO::FETCH_ASSOC));
-		}
-	}
-
-	public function getDefault () {
-		$sql = 'SELECT * FROM ' . $this->prefix . 'category WHERE id="000000"';
-		$stm = $this->bd->prepare ($sql);
-
-		$stm->execute ();
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-		$cat = HelperCategory::daoToCategory ($res);
-
-		if (isset ($cat[0])) {
-			return $cat[0];
-		} else {
-			return false;
-		}
-	}
-	public function checkDefault () {
-		$def_cat = $this->searchById ('000000');
-
-		if ($def_cat === false) {
-			$cat = new Category (Translate::t ('default_category'));
-			$cat->_id ('000000');
-
-			$values = array (
-				'id' => $cat->id (),
-				'name' => $cat->name (),
-				'color' => $cat->color ()
-			);
-
-			$this->addCategory ($values);
-		}
-	}
-
-	public function count () {
-		$sql = 'SELECT COUNT(*) AS count FROM ' . $this->prefix . 'category';
-		$stm = $this->bd->prepare ($sql);
-		$stm->execute ();
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-
-		return $res[0]['count'];
-	}
-
-	public function countFeed ($id) {
-		$sql = 'SELECT COUNT(*) AS count FROM ' . $this->prefix . 'feed WHERE category=?';
-		$stm = $this->bd->prepare ($sql);
-		$values = array ($id);
-		$stm->execute ($values);
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-
-		return $res[0]['count'];
-	}
-
-	public function countNotRead ($id) {
-		$sql = 'SELECT COUNT(*) AS count FROM ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id WHERE category=? AND e.is_read=0';
-		$stm = $this->bd->prepare ($sql);
-		$values = array ($id);
-		$stm->execute ($values);
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-
-		return $res[0]['count'];
-	}
-}
-
-class HelperCategory {
-	public static function findFeed($categories, $feed_id) {
-		foreach ($categories as $category) {
-			foreach ($category->feeds () as $feed) {
-				if ($feed->id () === $feed_id) {
-					return $feed;
-				}
-			}
-		}
-		return null;
-	}
-
-	public static function daoToCategoryPrepopulated ($listDAO) {
-		$list = array ();
-
-		if (!is_array ($listDAO)) {
-			$listDAO = array ($listDAO);
-		}
-
-		$previousLine = null;
-		$feedsDao = array();
-		foreach ($listDAO as $line) {
-			if ($previousLine['c_id'] != null && $line['c_id'] !== $previousLine['c_id']) {
-				// End of the current category, we add it to the $list
-				$cat = new Category (
-					$previousLine['c_name'],
-					$previousLine['c_color'],
-					HelperFeed::daoToFeed ($feedsDao)
-				);
-				$cat->_id ($previousLine['c_id']);
-				$list[] = $cat;
-
-				$feedsDao = array();	//Prepare for next category
-			}
-
-			$previousLine = $line;
-			$feedsDao[] = $line;
-		}
-
-		// add the last category
-		if ($previousLine != null) {
-			$cat = new Category (
-				$previousLine['c_name'],
-				$previousLine['c_color'],
-				HelperFeed::daoToFeed ($feedsDao)
-			);
-			$cat->_id ($previousLine['c_id']);
-			$list[] = $cat;
-		}
-
-		return $list;
-	}
-
-	public static function daoToCategory ($listDAO) {
-		$list = array ();
-
-		if (!is_array ($listDAO)) {
-			$listDAO = array ($listDAO);
-		}
-
-		foreach ($listDAO as $key => $dao) {
-			$cat = new Category (
-				$dao['name'],
-				$dao['color']
-			);
-			$cat->_id ($dao['id']);
-			$list[$key] = $cat;
-		}
-
-		return $list;
-	}
-}

+ 0 - 156
app/models/EntriesGetter.php

@@ -1,156 +0,0 @@
-<?php
-
-class EntriesGetter {
-	private $type = array (
-		'type' => 'all',
-		'id' => 'all'
-	);
-	private $state = 'all';
-	private $filter = array (
-		'words' => array (),
-		'tags' => array (),
-	);
-	private $order = 'high_to_low';
-	private $entries = array ();
-
-	private $nb = 1;
-	private $first = '';
-	private $next = '';
-
-	public function __construct ($type, $state, $filter, $order, $nb, $first = '') {
-		$this->_type ($type);
-		$this->_state ($state);
-		$this->_filter ($filter);
-		$this->_order ($order);
-		$this->nb = $nb;
-		$this->first = $first;
-	}
-
-	public function type () {
-		return $this->type;
-	}
-	public function state () {
-		return $this->state;
-	}
-	public function filter () {
-		return $this->filter;
-	}
-	public function order () {
-		return $this->order;
-	}
-	public function entries () {
-		return $this->entries;
-	}
-
-	public function _type ($value) {
-		if (!is_array ($value) ||
-		    !isset ($value['type']) ||
-		    !isset ($value['id'])) {
-			throw new EntriesGetterException ('Bad type line ' . __LINE__ . ' in file ' . __FILE__);
-		}
-
-		$type = $value['type'];
-		$id = $value['id'];
-
-		if ($type != 'all' && $type != 'favoris' && $type != 'public' && $type != 'c' && $type != 'f') {
-			throw new EntriesGetterException ('Bad type line ' . __LINE__ . ' in file ' . __FILE__);
-		}
-
-		if (($type == 'all' || $type == 'favoris' || $type == 'public') &&
-		    ($type != $id)) {
-			throw new EntriesGetterException ('Bad type line ' . __LINE__ . ' in file ' . __FILE__);
-		}
-
-		$this->type = $value;
-	}
-	public function _state ($value) {
-		if ($value != 'all' && $value != 'not_read' && $value != 'read') {
-			throw new EntriesGetterException ('Bad state line ' . __LINE__ . ' in file ' . __FILE__);
-		}
-
-		$this->state = $value;
-	}
-	public function _filter ($value) {
-		$value = trim ($value);
-		$terms = explode (' ', $value);
-
-		foreach ($terms as $word) {
-			if (!empty ($word) && $word[0] == '#' && isset ($word[1])) {
-				$tag = substr ($word, 1);
-				$this->filter['tags'][$tag] = $tag;
-			} elseif (!empty ($word)) {
-				$this->filter['words'][$word] = $word;
-			}
-		}
-	}
-	public function _order ($value) {
-		if ($value != 'high_to_low' && $value != 'low_to_high') {
-			throw new EntriesGetterException ('Bad order line ' . __LINE__ . ' in file ' . __FILE__);
-		}
-
-		$this->order = $value;
-	}
-
-	public function execute () {
-		$entryDAO = new EntryDAO ();
-
-		HelperEntry::$nb = $this->nb;	//TODO: Update: Now done in SQL
-		HelperEntry::$first = $this->first;	//TODO: Update: Now done in SQL
-		HelperEntry::$filter = $this->filter;
-
-		$sqlLimit = (empty ($this->filter['words']) && empty ($this->filter['tags'])) ? $this->nb : '';	//Disable SQL LIMIT optimisation during search	//TODO: Do better!
-
-		switch ($this->type['type']) {
-		case 'all':
-			list ($this->entries, $this->next) = $entryDAO->listEntries (
-				$this->state,
-				$this->order,
-				$this->first,
-				$sqlLimit
-			);
-			break;
-		case 'favoris':
-			list ($this->entries, $this->next) = $entryDAO->listFavorites (
-				$this->state,
-				$this->order,
-				$this->first,
-				$sqlLimit
-			);
-			break;
-		case 'public':
-			list ($this->entries, $this->next) = $entryDAO->listPublic (
-				$this->state,
-				$this->order,
-				$this->first,
-				$sqlLimit
-			);
-			break;
-		case 'c':
-			list ($this->entries, $this->next) = $entryDAO->listByCategory (
-				$this->type['id'],
-				$this->state,
-				$this->order,
-				$this->first,
-				$sqlLimit
-			);
-			break;
-		case 'f':
-			list ($this->entries, $this->next) = $entryDAO->listByFeed (
-				$this->type['id'],
-				$this->state,
-				$this->order,
-				$this->first,
-				$sqlLimit
-			);
-			break;
-		default:
-			throw new EntriesGetterException ('Bad type line ' . __LINE__ . ' in file ' . __FILE__);
-		}
-	}
-
-	public function getPaginator () {
-		$paginator = new RSSPaginator ($this->entries, $this->next);
-
-		return $paginator;
-	}
-}

+ 0 - 590
app/models/Entry.php

@@ -1,590 +0,0 @@
-<?php
-
-class Entry extends Model {
-
-	private $id = null;
-	private $guid;
-	private $title;
-	private $author;
-	private $content;
-	private $link;
-	private $date;
-	private $is_read;
-	private $is_favorite;
-	private $feed;
-	private $tags;
-
-	public function __construct ($feed = '', $guid = '', $title = '', $author = '', $content = '',
-	                             $link = '', $pubdate = 0, $is_read = false, $is_favorite = false) {
-		$this->_guid ($guid);
-		$this->_title ($title);
-		$this->_author ($author);
-		$this->_content ($content);
-		$this->_link ($link);
-		$this->_date ($pubdate);
-		$this->_isRead ($is_read);
-		$this->_isFavorite ($is_favorite);
-		$this->_feed ($feed);
-		$this->_tags (array ());
-	}
-
-	public function id () {
-		if(is_null($this->id)) {
-			return small_hash ($this->guid . Configuration::selApplication ());
-		} else {
-			return $this->id;
-		}
-	}
-	public function guid () {
-		return $this->guid;
-	}
-	public function title () {
-		return $this->title;
-	}
-	public function author () {
-		if (is_null ($this->author)) {
-			return '';
-		} else {
-			return $this->author;
-		}
-	}
-	public function content () {
-		return $this->content;
-	}
-	public function link () {
-		return $this->link;
-	}
-	public function date ($raw = false) {
-		if ($raw) {
-			return $this->date;
-		} else {
-			return timestamptodate ($this->date);
-		}
-	}
-	public function isRead () {
-		return $this->is_read;
-	}
-	public function isFavorite () {
-		return $this->is_favorite;
-	}
-	public function feed ($object = false) {
-		if ($object) {
-			$feedDAO = new FeedDAO ();
-			return $feedDAO->searchById ($this->feed);
-		} else {
-			return $this->feed;
-		}
-	}
-	public function tags ($inString = false) {
-		if ($inString) {
-			if (!empty ($this->tags)) {
-				return '#' . implode(' #', $this->tags);
-			} else {
-				return '';
-			}
-		} else {
-			return $this->tags;
-		}
-	}
-
-	public function _id ($value) {
-		$this->id = $value;
-	}
-	public function _guid ($value) {
-		$this->guid = $value;
-	}
-	public function _title ($value) {
-		$this->title = $value;
-	}
-	public function _author ($value) {
-		$this->author = $value;
-	}
-	public function _content ($value) {
-		$this->content = $value;
-	}
-	public function _link ($value) {
-		$this->link = $value;
-	}
-	public function _date ($value) {
-		if (is_int (intval ($value))) {
-			$this->date = $value;
-		} else {
-			$this->date = time ();
-		}
-	}
-	public function _isRead ($value) {
-		$this->is_read = $value;
-	}
-	public function _isFavorite ($value) {
-		$this->is_favorite = $value;
-	}
-	public function _feed ($value) {
-		$this->feed = $value;
-	}
-	public function _tags ($value) {
-		if (!is_array ($value)) {
-			$value = array ($value);
-		}
-
-		foreach ($value as $key => $t) {
-			if (!$t) {
-				unset ($value[$key]);
-			}
-		}
-
-		$this->tags = $value;
-	}
-
-	public function isDay ($day) {
-		$date = getdate ();
-		$today = mktime (0, 0, 0, $date['mon'], $date['mday'], $date['year']);
-		$yesterday = $today - 86400;
-
-		if ($day == Days::TODAY &&
-		    $this->date >= $today && $this->date < $today + 86400) {
-			return true;
-		} elseif ($day == Days::YESTERDAY &&
-		    $this->date >= $yesterday && $this->date < $yesterday + 86400) {
-			return true;
-		} elseif ($day == Days::BEFORE_YESTERDAY && $this->date < $yesterday) {
-			return true;
-		} else {
-			return false;
-		}
-	}
-
-	public function loadCompleteContent($pathEntries) {
-		// Gestion du contenu
-		// On cherche à récupérer les articles en entier... même si le flux ne le propose pas
-		if ($pathEntries) {
-			$entryDAO = new EntryDAO();
-			$entry = $entryDAO->searchByGuid($this->feed, $this->guid);
-
-			if($entry) {
-				// l'article existe déjà en BDD, en se contente de recharger ce contenu
-				$this->content = $entry->content();
-			} else {
-				try {
-					// l'article n'est pas en BDD, on va le chercher sur le site
-					$this->content = get_content_by_parsing(
-						$this->link(), $pathEntries
-					);
-				} catch (Exception $e) {
-					// rien à faire, on garde l'ancien contenu (requête a échoué)
-				}
-			}
-		}
-	}
-
-	public function toArray () {
-		return array (
-			'id' => $this->id (),
-			'guid' => $this->guid (),
-			'title' => $this->title (),
-			'author' => $this->author (),
-			'content' => $this->content (),
-			'link' => $this->link (),
-			'date' => $this->date (true),
-			'is_read' => $this->isRead (),
-			'is_favorite' => $this->isFavorite (),
-			'id_feed' => $this->feed (),
-			'tags' => $this->tags (true)
-		);
-	}
-}
-
-class EntryDAO extends Model_pdo {
-	public function addEntry ($valuesTmp) {
-		$sql = 'INSERT INTO ' . $this->prefix . 'entry(id, guid, title, author, content, link, date, is_read, is_favorite, id_feed, tags) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array (
-			$valuesTmp['id'],
-			$valuesTmp['guid'],
-			$valuesTmp['title'],
-			$valuesTmp['author'],
-			base64_encode (gzdeflate (serialize ($valuesTmp['content']))),
-			$valuesTmp['link'],
-			$valuesTmp['date'],
-			$valuesTmp['is_read'],
-			$valuesTmp['is_favorite'],
-			$valuesTmp['id_feed'],
-			$valuesTmp['tags'],
-		);
-
-		if ($stm && $stm->execute ($values)) {
-			return true;
-		} else {
-			$info = $stm->errorInfo();
-			if ((int)($info[0] / 1000) !== 23) {	//Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries
-				Minz_Log::record ('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2], Minz_Log::NOTICE);	//TODO: Consider adding a Minz_Log::DEBUG level
-			}
-			return false;
-		}
-	}
-
-	public function updateEntry ($id, $valuesTmp) {
-		if (isset ($valuesTmp['content'])) {
-			$valuesTmp['content'] = base64_encode (gzdeflate (serialize ($valuesTmp['content'])));
-		}
-
-		$set = '';
-		foreach ($valuesTmp as $key => $v) {
-			$set .= $key . '=?, ';
-		}
-		$set = substr ($set, 0, -2);
-
-		$sql = 'UPDATE ' . $this->prefix . 'entry SET ' . $set . ' WHERE id=?';
-		$stm = $this->bd->prepare ($sql);
-
-		foreach ($valuesTmp as $v) {
-			$values[] = $v;
-		}
-		$values[] = $id;
-
-		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 markReadEntries ($read, $dateMax = 0) {
-		$sql = 'UPDATE ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id SET is_read = ? WHERE priority > 0';
-
-		$values = array ($read);
-		if ($dateMax > 0) {
-			$sql .= ' AND date < ?';
-			$values[] = $dateMax;
-		}
-
-		$stm = $this->bd->prepare ($sql);
-
-		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 markReadCat ($id, $read, $dateMax = 0) {
-		$sql = 'UPDATE ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id SET is_read = ? WHERE category = ?';
-
-		$values = array ($read, $id);
-		if ($dateMax > 0) {
-			$sql .= ' AND date < ?';
-			$values[] = $dateMax;
-		}
-
-		$stm = $this->bd->prepare ($sql);
-
-		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 markReadFeed ($id, $read, $dateMax = 0) {
-		$sql = 'UPDATE ' . $this->prefix . 'entry SET is_read = ? WHERE id_feed = ?';
-
-		$values = array ($read, $id);
-		if ($dateMax > 0) {
-			$sql .= ' AND date < ?';
-			$values[] = $dateMax;
-		}
-
-		$stm = $this->bd->prepare ($sql);
-
-		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 updateEntries ($valuesTmp) {
-		if (isset ($valuesTmp['content'])) {
-			$valuesTmp['content'] = base64_encode (gzdeflate (serialize ($valuesTmp['content'])));
-		}
-
-		$set = '';
-		foreach ($valuesTmp as $key => $v) {
-			$set .= $key . '=?, ';
-		}
-		$set = substr ($set, 0, -2);
-
-		$sql = 'UPDATE ' . $this->prefix . 'entry SET ' . $set;
-		$stm = $this->bd->prepare ($sql);
-
-		foreach ($valuesTmp as $v) {
-			$values[] = $v;
-		}
-
-		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 cleanOldEntries ($nb_month) {
-		$date = 60 * 60 * 24 * 30 * $nb_month;
-		$sql = 'DELETE e.* FROM ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id WHERE e.date <= ? AND e.is_favorite = 0 AND f.keep_history = 0';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array (
-			time () - $date
-		);
-
-		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 searchByGuid ($feed_id, $id) {
-		// un guid est unique pour un flux donné
-		$sql = 'SELECT * FROM ' . $this->prefix . 'entry WHERE id_feed=? AND guid=?';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array (
-			$feed_id,
-			$id
-		);
-
-		$stm->execute ($values);
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-		list ($entry, $next) = HelperEntry::daoToEntry ($res);
-
-		if (isset ($entry[0])) {
-			return $entry[0];
-		} else {
-			return false;
-		}
-	}
-
-	public function searchById ($id) {
-		$sql = 'SELECT * FROM ' . $this->prefix . 'entry WHERE id=?';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array ($id);
-
-		$stm->execute ($values);
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-		list ($entry, $next) = HelperEntry::daoToEntry ($res);
-
-		if (isset ($entry[0])) {
-			return $entry[0];
-		} else {
-			return false;
-		}
-	}
-
-	private function listWhere ($where, $state, $order, $limitFromId = '', $limitCount = '', $values = array ()) {
-		if ($state == 'not_read') {
-			$where .= ' AND is_read = 0';
-		} elseif ($state == 'read') {
-			$where .= ' AND is_read = 1';
-		}
-		if (!empty($limitFromId)) {	//TODO: Consider using LPAD(e.date, 11)	//CONCAT is for cases when many entries have the same date
-			$where .= ' AND CONCAT(e.date, e.id) ' . ($order === 'low_to_high' ? '<=' : '>=') . ' (SELECT CONCAT(s.date, s.id) FROM ' . $this->prefix . 'entry s WHERE s.id = "' . $limitFromId . '")';
-		}
-
-		if ($order == 'low_to_high') {
-			$order = ' DESC';
-		} else {
-			$order = '';
-		}
-
-		$sql = 'SELECT e.* FROM ' . $this->prefix . 'entry e'
-		     . ' INNER JOIN  ' . $this->prefix . 'feed f ON e.id_feed = f.id' . $where
-		     . ' ORDER BY e.date' . $order . ', e.id' . $order;
-
-		if (empty($limitCount)) {
-			$limitCount = 20000;	//TODO: FIXME: Hack temporaire en attendant la recherche côté base-de-données
-		}
-		//if (!empty($limitCount)) {
-			$sql .= ' LIMIT ' . ($limitCount + 2);	//TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
-		//}
-
-		$stm = $this->bd->prepare ($sql);
-		$stm->execute ($values);
-
-		return HelperEntry::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC));
-	}
-	public function listEntries ($state, $order = 'high_to_low', $limitFromId = '', $limitCount = '') {
-		return $this->listWhere (' WHERE priority > 0', $state, $order, $limitFromId, $limitCount);
-	}
-	public function listFavorites ($state, $order = 'high_to_low', $limitFromId = '', $limitCount = '') {
-		return $this->listWhere (' WHERE is_favorite = 1', $state, $order, $limitFromId, $limitCount);
-	}
-	public function listPublic ($state, $order = 'high_to_low', $limitFromId = '', $limitCount = '') {
-		return $this->listWhere (' WHERE is_public = 1', $state, $order, $limitFromId, $limitCount);
-	}
-	public function listByCategory ($cat, $state, $order = 'high_to_low', $limitFromId = '', $limitCount = '') {
-		return $this->listWhere (' WHERE category = ?', $state, $order, $limitFromId, $limitCount, array ($cat));
-	}
-	public function listByFeed ($feed, $state, $order = 'high_to_low', $limitFromId = '', $limitCount = '') {
-		return $this->listWhere (' WHERE id_feed = ?', $state, $order, $limitFromId, $limitCount, array ($feed));
-	}
-	
-	public function listLastIdsByFeed($id, $n) {
-		$sql = 'SELECT id FROM ' . $this->prefix . 'entry WHERE id_feed=? ORDER BY date DESC LIMIT ' . intval($n);
-		$stm = $this->bd->prepare ($sql);
-		$values = array ($id);
-		$stm->execute ($values);
-		return $stm->fetchAll (PDO::FETCH_COLUMN, 0);
-	}
-
-	public function countUnreadRead () {
-		$sql = 'SELECT is_read, COUNT(*) AS count FROM ' . $this->prefix . 'entry e INNER JOIN ' . $this->prefix . 'feed f ON e.id_feed = f.id WHERE priority > 0 GROUP BY is_read';
-		$stm = $this->bd->prepare ($sql);
-		$stm->execute ();
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-
-		$readUnread = array('unread' => 0, 'read' => 0);
-		foreach ($res as $line) {
-			switch (intval($line['is_read'])) {
-				case 0: $readUnread['unread'] = intval($line['count']); break;
-				case 1: $readUnread['read'] = intval($line['count']); break;
-			}
-		}
-		return $readUnread;
-	}
-	public function count () {	//Deprecated: use countUnreadRead() instead
-		$unreadRead = $this->countUnreadRead ();	//This makes better use of caching
-		return $unreadRead['unread'] + $unreadRead['read'];
-	}
-	public function countNotRead () {	//Deprecated: use countUnreadRead() instead
-		$unreadRead = $this->countUnreadRead ();	//This makes better use of caching
-		return $unreadRead['unread'];
-	}
-
-	public function countUnreadReadFavorites () {
-		$sql = 'SELECT is_read, COUNT(*) AS count FROM ' . $this->prefix . 'entry WHERE is_favorite=1 GROUP BY is_read';
-		$stm = $this->bd->prepare ($sql);
-		$stm->execute ();
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-		$readUnread = array('unread' => 0, 'read' => 0);
-		foreach ($res as $line) {
-			switch (intval($line['is_read'])) {
-				case 0: $readUnread['unread'] = intval($line['count']); break;
-				case 1: $readUnread['read'] = intval($line['count']); break;
-			}
-		}
-		return $readUnread;
-	}
-
-	public function optimizeTable() {
-		$sql = 'OPTIMIZE TABLE ' . $this->prefix . 'entry';
-		$stm = $this->bd->prepare ($sql);
-		$stm->execute ();
-	}
-}
-
-class HelperEntry {
-	public static $nb = 1;
-	public static $first = '';
-
-	public static $filter = array (
-		'words' => array (),
-		'tags' => array (),
-	);
-
-	public static function daoToEntry ($listDAO) {
-		$list = array ();
-
-		if (!is_array ($listDAO)) {
-			$listDAO = array ($listDAO);
-		}
-
-		$count = 0;
-		$first_is_found = false;
-		$break_after = false;
-		$next = '';
-		foreach ($listDAO as $key => $dao) {
-			$dao['content'] = unserialize (gzinflate (base64_decode ($dao['content'])));
-			$dao['tags'] = preg_split('/[\s#]/', $dao['tags']);
-
-			if (self::tagsMatchEntry ($dao) &&
-			    self::searchMatchEntry ($dao)) {
-				if ($break_after) {
-					$next = $dao['id'];
-					break;
-				}
-				if ($first_is_found || $dao['id'] == self::$first || self::$first == '') {
-					$list[$key] = self::createEntry ($dao);
-
-					$count++;
-					$first_is_found = true;	//TODO: Update: Now done in SQL
-				}
-				if ($count >= self::$nb) {
-					$break_after = true;
-				}
-			}
-		}
-
-		unset ($listDAO);
-
-		return array ($list, $next);
-	}
-
-	private static function createEntry ($dao) {
-		$entry = new Entry (
-			$dao['id_feed'],
-			$dao['guid'],
-			$dao['title'],
-			$dao['author'],
-			$dao['content'],
-			$dao['link'],
-			$dao['date'],
-			$dao['is_read'],
-			$dao['is_favorite']
-		);
-
-		$entry->_tags ($dao['tags']);
-
-		if (isset ($dao['id'])) {
-			$entry->_id ($dao['id']);
-		}
-
-		return $entry;
-	}
-
-	private static function tagsMatchEntry ($dao) {
-		$tags = self::$filter['tags'];
-		foreach ($tags as $tag) {
-			if (!in_array ($tag, $dao['tags'])) {
-				return false;
-			}
-		}
-
-		return true;
-	}
-	private static function searchMatchEntry ($dao) {
-		$words = self::$filter['words'];
-
-		foreach ($words as $word) {
-			$word = strtolower ($word);
-			if (strpos (strtolower ($dao['title']), $word) === false &&
-			    strpos (strtolower ($dao['content']), $word) === false &&
-			    strpos (strtolower ($dao['link']), $word) === false) {
-				return false;
-			}
-		}
-
-		return true;
-	}
-}

+ 0 - 19
app/models/Exception/FeedException.php

@@ -1,19 +0,0 @@
-<?php
-
-class FeedException extends Exception {
-	public function __construct ($message) {
-		parent::__construct ($message);
-	}
-}
-
-class BadUrlException extends FeedException {
-	public function __construct ($url) {
-		parent::__construct ('`' . $url . '` is not a valid URL');
-	}
-}
-
-class OpmlException extends FeedException {
-	public function __construct ($name_file) {
-		parent::__construct ('OPML file is invalid');
-	}
-}

+ 0 - 564
app/models/Feed.php

@@ -1,564 +0,0 @@
-<?php
-
-class Feed extends Model {
-	private $id = null;
-	private $url;
-	private $category = '000000';
-	private $nbEntries = -1;
-	private $nbNotRead = -1;
-	private $entries = null;
-	private $name = '';
-	private $website = '';
-	private $description = '';
-	private $lastUpdate = 0;
-	private $priority = 10;
-	private $pathEntries = '';
-	private $httpAuth = '';
-	private $error = false;
-	private $keep_history = false;
-
-	public function __construct ($url, $validate=true) {
-		if ($validate) {
-			$this->_url ($url);
-		} else {
-			$this->url = $url;
-		}
-	}
-
-	public function id () {
-		if(is_null($this->id)) {
-			return small_hash ($this->url . Configuration::selApplication ());
-		} else {
-			return $this->id;
-		}
-	}
-	public function url () {
-		return $this->url;
-	}
-	public function category () {
-		return $this->category;
-	}
-	public function entries () {
-		if (!is_null ($this->entries)) {
-			return $this->entries;
-		} else {
-			return array ();
-		}
-	}
-	public function name () {
-		return $this->name;
-	}
-	public function website () {
-		return $this->website;
-	}
-	public function description () {
-		return $this->description;
-	}
-	public function lastUpdate () {
-		return $this->lastUpdate;
-	}
-	public function priority () {
-		return $this->priority;
-	}
-	public function pathEntries () {
-		return $this->pathEntries;
-	}
-	public function httpAuth ($raw = true) {
-		if ($raw) {
-			return $this->httpAuth;
-		} else {
-			$pos_colon = strpos ($this->httpAuth, ':');
-			$user = substr ($this->httpAuth, 0, $pos_colon);
-			$pass = substr ($this->httpAuth, $pos_colon + 1);
-
-			return array (
-				'username' => $user,
-				'password' => $pass
-			);
-		}
-	}
-	public function inError () {
-		return $this->error;
-	}
-	public function keepHistory () {
-		return $this->keep_history;
-	}
-	public function nbEntries () {
-		if ($this->nbEntries < 0) {
-			$feedDAO = new FeedDAO ();
-			$this->nbEntries = $feedDAO->countEntries ($this->id ());
-		}
-
-		return $this->nbEntries;
-	}
-	public function nbNotRead () {
-		if ($this->nbNotRead < 0) {
-			$feedDAO = new FeedDAO ();
-			$this->nbNotRead = $feedDAO->countNotRead ($this->id ());
-		}
-
-		return $this->nbNotRead;
-	}
-	public function favicon () {
-		$file = '/data/favicons/' . $this->id () . '.ico';
-
-		$favicon_url = Url::display ($file);
-		if (!file_exists (PUBLIC_PATH . $file)) {
-			$favicon_url = dowload_favicon ($this->website (), $this->id ());
-		}
-
-		return $favicon_url;
-	}
-
-	public function _id ($value) {
-		$this->id = $value;
-	}
-	public function _url ($value) {
-		if (empty ($value)) {
-			throw new BadUrlException ($value);
-		}
-		if (!preg_match ('#^https?://#i', $value)) {
-			$value = 'http://' . $value;
-		}
-
-		if (filter_var ($value, FILTER_VALIDATE_URL)) {
-			$this->url = $value;
-		} elseif (version_compare(PHP_VERSION, '5.3.3', '<') && (strpos($value, '-') > 0) && ($value === filter_var($value, FILTER_SANITIZE_URL))) {	//PHP bug #51192
-			$this->url = $value;
-		} else {
-			throw new BadUrlException ($value);
-		}
-	}
-	public function _category ($value) {
-		$this->category = $value;
-	}
-	public function _name ($value) {
-		if (is_null ($value)) {
-			$value = '';
-		}
-		$this->name = $value;
-	}
-	public function _website ($value) {
-		if (is_null ($value)) {
-			$value = '';
-		}
-		$this->website = $value;
-	}
-	public function _description ($value) {
-		if (is_null ($value)) {
-			$value = '';
-		}
-		$this->description = $value;
-	}
-	public function _lastUpdate ($value) {
-		$this->lastUpdate = $value;
-	}
-	public function _priority ($value) {
-		$this->priority = is_numeric ($value) ? intval ($value) : 10;
-	}
-	public function _pathEntries ($value) {
-		$this->pathEntries = $value;
-	}
-	public function _httpAuth ($value) {
-		$this->httpAuth = $value;
-	}
-	public function _error ($value) {
-		if ($value) {
-			$value = true;
-		} else {
-			$value = false;
-		}
-		$this->error = $value;
-	}
-	public function _keepHistory ($value) {
-		if ($value) {
-			$value = true;
-		} else {
-			$value = false;
-		}
-		$this->keep_history = $value;
-	}
-	public function _nbNotRead ($value) {
-		$this->nbNotRead = is_numeric ($value) ? intval ($value) : -1;
-	}
-	public function _nbEntries ($value) {
-		$this->nbEntries = is_numeric ($value) ? intval ($value) : -1;
-	}
-
-	public function load () {
-		if (!is_null ($this->url)) {
-			if (CACHE_PATH === false) {
-				throw new FileNotExistException (
-					'CACHE_PATH',
-					MinzException::ERROR
-				);
-			} else {
-				$feed = new SimplePie ();
-				$url = str_replace ('&amp;', '&', $this->url);
-				if ($this->httpAuth != '') {
-					$url = preg_replace ('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $url);
-				}
-
-				$feed->set_feed_url ($url);
-				$feed->set_cache_location (CACHE_PATH);
-				$feed->set_cache_duration(1500);
-				$feed->strip_htmltags (array (
-					'base', 'blink', 'body', 'doctype',
-					'font', 'form', 'frame', 'frameset', 'html',
-					'input', 'marquee', 'meta', 'noscript',
-					'param', 'script', 'style'
-				));
-				$feed->strip_attributes(array_merge($feed->strip_attributes, array(
-					'onload', 'onunload', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup',
-					'onmouseover', 'onmousemove', 'onmouseout', 'onfocus', 'onblur',
-					'onkeypress', 'onkeydown', 'onkeyup', 'onselect', 'onchange')));
-				$feed->init ();
-
-				if ($feed->error ()) {
-					throw new FeedException ($feed->error . ' [' . $url . ']');
-				}
-
-				// 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 ($this->httpAuth != '') {
-						// on enlève les id si authentification HTTP
-						$subscribe_url = preg_replace ('#((.+)://)((.+)@)(.+)#', '${1}${5}', $subscribe_url);
-					}
-					$this->_url ($subscribe_url);
-				}
-
-				if (empty($this->name)) {	// May come from OPML
-					$title = $feed->get_title ();
-					$this->_name (!is_null ($title) ? $title : $this->url);
-				}
-
-				$this->_website ($feed->get_link ());
-				$this->_description ($feed->get_description ());
-
-				// et on charge les articles du flux
-				$this->loadEntries ($feed);
-			}
-		}
-	}
-	private function loadEntries ($feed) {
-		$entries = array ();
-
-		foreach ($feed->get_items () as $item) {
-			$title = strip_tags($item->get_title ());
-			$author = $item->get_author ();
-			$link = $item->get_permalink ();
-			$date = strtotime ($item->get_date ());
-
-			// gestion des tags (catégorie == tag)
-			$tags_tmp = $item->get_categories ();
-			$tags = array ();
-			if (!is_null ($tags_tmp)) {
-				foreach ($tags_tmp as $tag) {
-					$tags[] = $tag->get_label ();
-				}
-			}
-
-			$content = $item->get_content ();
-			$elinks = array();
-			foreach ($item->get_enclosures() as $enclosure) {
-				$elink = $enclosure->get_link();
-				if (array_key_exists($elink, $elinks)) continue;
-				$elinks[$elink] = '1';
-				$mime = strtolower($enclosure->get_type());
-				if (strpos($mime, 'image/') === 0) {
-					$content .= '<br /><img src="' . $elink . '" alt="" />';
-				}
-			}
-
-			$entry = new Entry (
-				$this->id (),
-				$item->get_id (),
-				!is_null ($title) ? $title : '',
-				!is_null ($author) ? $author->name : '',
-				!is_null ($content) ? $content : '',
-				!is_null ($link) ? $link : '',
-				$date ? $date : time ()
-			);
-			$entry->_tags ($tags);
-			// permet de récupérer le contenu des flux tronqués
-			$entry->loadCompleteContent($this->pathEntries());
-
-			$entries[$entry->id ()] = $entry;
-		}
-
-		$this->entries = $entries;
-	}
-}
-
-class FeedDAO extends Model_pdo {
-	public function addFeed ($valuesTmp) {
-		$sql = 'INSERT INTO ' . $this->prefix . 'feed (id, url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history) VALUES(?, ?, ?, ?, ?, ?, ?, 10, ?, 0, 0)';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array (
-			$valuesTmp['id'],
-			$valuesTmp['url'],
-			$valuesTmp['category'],
-			$valuesTmp['name'],
-			$valuesTmp['website'],
-			$valuesTmp['description'],
-			$valuesTmp['lastUpdate'],
-			base64_encode ($valuesTmp['httpAuth']),
-		);
-
-		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 updateFeed ($id, $valuesTmp) {
-		$set = '';
-		foreach ($valuesTmp as $key => $v) {
-			$set .= $key . '=?, ';
-
-			if ($key == 'httpAuth') {
-				$valuesTmp[$key] = base64_encode ($v);
-			}
-		}
-		$set = substr ($set, 0, -2);
-
-		$sql = 'UPDATE ' . $this->prefix . 'feed SET ' . $set . ' WHERE id=?';
-		$stm = $this->bd->prepare ($sql);
-
-		foreach ($valuesTmp as $v) {
-			$values[] = $v;
-		}
-		$values[] = $id;
-
-		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 updateLastUpdate ($id) {
-		$sql = 'UPDATE ' . $this->prefix . 'feed SET lastUpdate=?, error=0 WHERE id=?';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array (
-			time (),
-			$id
-		);
-
-		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 isInError ($id) {
-		$sql = 'UPDATE ' . $this->prefix . 'feed SET error=1 WHERE id=?';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array (
-			$id
-		);
-
-		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 changeCategory ($idOldCat, $idNewCat) {
-		$catDAO = new CategoryDAO ();
-		$newCat = $catDAO->searchById ($idNewCat);
-		if (!$newCat) {
-			$newCat = $catDAO->getDefault ();
-		}
-
-		$sql = 'UPDATE ' . $this->prefix . 'feed SET category=? WHERE category=?';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array (
-			$newCat->id (),
-			$idOldCat
-		);
-
-		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 deleteFeed ($id) {
-		$sql = 'DELETE FROM ' . $this->prefix . 'feed WHERE id=?';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array ($id);
-
-		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 deleteFeedByCategory ($id) {
-		$sql = 'DELETE FROM ' . $this->prefix . 'feed WHERE category=?';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array ($id);
-
-		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 searchById ($id) {
-		$sql = 'SELECT * FROM ' . $this->prefix . 'feed WHERE id=?';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array ($id);
-
-		$stm->execute ($values);
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-		$feed = HelperFeed::daoToFeed ($res);
-
-		if (isset ($feed[$id])) {
-			return $feed[$id];
-		} else {
-			return false;
-		}
-	}
-	public function searchByUrl ($url) {
-		$sql = 'SELECT * FROM ' . $this->prefix . 'feed WHERE url=?';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array ($url);
-
-		$stm->execute ($values);
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-		$feed = current (HelperFeed::daoToFeed ($res));
-
-		if (isset ($feed)) {
-			return $feed;
-		} else {
-			return false;
-		}
-	}
-
-	public function listFeeds () {
-		$sql = 'SELECT * FROM ' . $this->prefix . 'feed ORDER BY name';
-		$stm = $this->bd->prepare ($sql);
-		$stm->execute ();
-
-		return HelperFeed::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
-	}
-
-	public function listFeedsOrderUpdate () {
-		$sql = 'SELECT * FROM ' . $this->prefix . 'feed ORDER BY lastUpdate';
-		$stm = $this->bd->prepare ($sql);
-		$stm->execute ();
-
-		return HelperFeed::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
-	}
-
-	public function listByCategory ($cat) {
-		$sql = 'SELECT * FROM ' . $this->prefix . 'feed WHERE category=? ORDER BY name';
-		$stm = $this->bd->prepare ($sql);
-
-		$values = array ($cat);
-
-		$stm->execute ($values);
-
-		return HelperFeed::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
-	}
-
-	public function count () {	//Is this used?
-		$sql = 'SELECT COUNT(*) AS count FROM ' . $this->prefix . 'feed';
-		$stm = $this->bd->prepare ($sql);
-		$stm->execute ();
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-
-		return $res[0]['count'];
-	}
-
-	public function countEntries ($id) {
-		$sql = 'SELECT COUNT(*) AS count FROM ' . $this->prefix . 'entry WHERE id_feed=?';
-		$stm = $this->bd->prepare ($sql);
-		$values = array ($id);
-		$stm->execute ($values);
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-
-		return $res[0]['count'];
-	}
-	public function countNotRead ($id) {	//Is this used?
-		$sql = 'SELECT COUNT(*) AS count FROM ' . $this->prefix . 'entry WHERE is_read=0 AND id_feed=?';
-		$stm = $this->bd->prepare ($sql);
-		$values = array ($id);
-		$stm->execute ($values);
-		$res = $stm->fetchAll (PDO::FETCH_ASSOC);
-
-		return $res[0]['count'];
-	}
-}
-
-class HelperFeed {
-	public static function daoToFeed ($listDAO) {
-		$list = array ();
-
-		if (!is_array ($listDAO)) {
-			$listDAO = array ($listDAO);
-		}
-
-		foreach ($listDAO as $key => $dao) {
-			if (empty ($dao['url'])) {
-				continue;
-			}
-			if (isset ($dao['id'])) {
-				$key = $dao['id'];
-			}
-
-			$list[$key] = new Feed ($dao['url'], false);
-			$list[$key]->_category ($dao['category']);
-			$list[$key]->_name ($dao['name']);
-			$list[$key]->_website ($dao['website']);
-			$list[$key]->_description ($dao['description']);
-			$list[$key]->_lastUpdate ($dao['lastUpdate']);
-			$list[$key]->_priority ($dao['priority']);
-			$list[$key]->_pathEntries ($dao['pathEntries']);
-			$list[$key]->_httpAuth (base64_decode ($dao['httpAuth']));
-			$list[$key]->_error ($dao['error']);
-			$list[$key]->_keepHistory ($dao['keep_history']);
-			if (isset ($dao['nbNotRead'])) {
-				$list[$key]->_nbNotRead ($dao['nbNotRead']);
-			}
-			if (isset ($dao['nbEntries'])) {
-				$list[$key]->_nbEntries ($dao['nbEntries']);
-			}
-			if (isset ($dao['id'])) {
-				$list[$key]->_id ($dao['id']);
-			}
-		}
-
-		return $list;
-	}
-}

+ 0 - 47
app/models/Log_Model.php

@@ -1,47 +0,0 @@
-<?php
-
-class Log_Model extends Model {
-	private $date;
-	private $level;
-	private $information;
-
-	public function date () {
-		return $this->date;
-	}
-	public function level () {
-		return $this->level;
-	}
-	public function info () {
-		return $this->information;
-	}
-	public function _date ($date) {
-		$this->date = $date;
-	}
-	public function _level ($level) {
-		$this->level = $level;
-	}
-	public function _info ($information) {
-		$this->information = $information;
-	}
-}
-
-class LogDAO extends Model_txt {
-	public function __construct () {
-		parent::__construct (LOG_PATH . '/application.log', 'r+');
-	}
-	
-	public function lister () {
-		$logs = array ();
-
-		$i = 0;
-		while (($line = $this->readLine ()) !== false) {
-			$logs[$i] = new Log_Model ();
-			$logs[$i]->_date (preg_replace ("'\[(.*?)\] \[(.*?)\] --- (.*?)'U", "\\1", $line));
-			$logs[$i]->_level (preg_replace ("'\[(.*?)\] \[(.*?)\] --- (.*?)'U", "\\2", $line));
-			$logs[$i]->_info (preg_replace ("'\[(.*?)\] \[(.*?)\] --- (.*?)'U", "\\3", $line));
-			$i++;
-		}
-
-		return $logs;
-	}
-}

+ 0 - 33
app/models/RSSPaginator.php

@@ -1,33 +0,0 @@
-<?php
-
-// Un système de pagination beaucoup plus simple que Paginator
-// mais mieux adapté à nos besoins
-class RSSPaginator {
-	private $items = array ();
-	private $next = '';
-
-	public function __construct ($items, $next) {
-		$this->items = $items;
-		$this->next = $next;
-	}
-
-	public function isEmpty () {
-		return empty ($this->items);
-	}
-
-	public function items () {
-		return $this->items;
-	}
-
-	public function next () {
-		return $this->next;
-	}
-
-	public function render ($view, $getteur) {
-		$view = APP_PATH . '/views/helpers/'.$view;
-
-		if (file_exists ($view)) {
-			include ($view);
-		}
-	}
-}

+ 0 - 47
app/models/RSSThemes.php

@@ -1,47 +0,0 @@
-<?php
-
-class RSSThemes extends Model {
-	private static $themes_dir = '/themes';
-
-	private static $list = array();
-
-	public static function init() {
-		$basedir = PUBLIC_PATH . self::$themes_dir;
-
-		$themes_list = array_diff(
-			scandir($basedir),
-			array('..', '.')
-		);
-
-		foreach ($themes_list as $theme_dir) {
-			$json_filename = $basedir . '/' . $theme_dir . '/metadata.json';
-			if(file_exists($json_filename)) {
-				$content = file_get_contents($json_filename);
-				$res = json_decode($content, true);
-
-				if($res &&
-					isset($res['name']) &&
-					isset($res['author']) &&
-					isset($res['description']) &&
-					isset($res['version']) &&
-					isset($res['files']) && is_array($res['files'])) {
-					$theme = $res;
-					$theme['path'] = $theme_dir;
-					self::$list[$theme_dir] = $theme;
-				}
-			}
-		}
-	}
-
-	public static function get() {
-		return self::$list;
-	}
-
-	public static function get_infos($theme_id) {
-		if (isset(self::$list[$theme_id])) {
-			return self::$list[$theme_id];
-		}
-
-		return false;
-	}
-}

+ 11 - 11
app/views/configure/categorize.phtml

@@ -1,28 +1,28 @@
 <?php $this->partial ('aside_feed'); ?>
 
 <div class="post">
-	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a>
+	<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', 'categorize'); ?>">
-		<legend><?php echo Translate::t ('categories_management'); ?></legend>
+		<legend><?php echo Minz_Translate::t ('categories_management'); ?></legend>
 
-		<p class="alert alert-warn"><?php echo Translate::t ('feeds_moved_category_deleted', $this->defaultCategory->name ()); ?></p>
+		<p class="alert alert-warn"><?php echo Minz_Translate::t ('feeds_moved_category_deleted', $this->defaultCategory->name ()); ?></p>
 
 		<?php $i = 0; foreach ($this->categories as $cat) { $i++; ?>
 		<div class="form-group">
 			<label class="group-name" for="cat_<?php echo $cat->id (); ?>">
-				<?php echo Translate::t ('category_number', $i); ?>
+				<?php echo Minz_Translate::t ('category_number', $i); ?>
 			</label>
 			<div class="group-controls">
 				<input type="text" id="cat_<?php echo $cat->id (); ?>" name="categories[]" value="<?php echo $cat->name (); ?>" />
 
 				<?php if ($cat->nbFeed () > 0) { ?>
-				<a class="confirm" href="<?php echo _url ('feed', 'delete', 'id', $cat->id (), 'type', 'category'); ?>"><?php echo Translate::t ('ask_empty'); ?></a>
+				<a class="confirm" href="<?php echo _url ('feed', 'delete', 'id', $cat->id (), 'type', 'category'); ?>"><?php echo Minz_Translate::t ('ask_empty'); ?></a>
 				<?php } ?>
-				(<?php echo Translate::t ('number_feeds', $cat->nbFeed ()); ?>)
+				(<?php echo Minz_Translate::t ('number_feeds', $cat->nbFeed ()); ?>)
 
 				<?php if ($cat->id () == $this->defaultCategory->id ()) { ?>
-				<i class="icon i_help"></i> <?php echo Translate::t ('can_not_be_deleted'); ?>
+				<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t ('can_not_be_deleted'); ?>
 				<?php } ?>
 
 				<input type="hidden" name="ids[]" value="<?php echo $cat->id (); ?>" />
@@ -31,16 +31,16 @@
 		<?php } ?>
 
 		<div class="form-group">
-			<label class="group-name" for="new_category"><?php echo Translate::t ('add_category'); ?></label>
+			<label class="group-name" for="new_category"><?php echo Minz_Translate::t ('add_category'); ?></label>
 			<div class="group-controls">
-				<input type="text" id="new_category" name="new_category" placeholder="<?php echo Translate::t ('new_category'); ?>" />
+				<input type="text" id="new_category" name="new_category" placeholder="<?php echo Minz_Translate::t ('new_category'); ?>" />
 			</div>
 		</div>
 
 		<div class="form-group form-actions">
 			<div class="group-controls">
-				<button type="submit" class="btn btn-important"><?php echo Translate::t ('save'); ?></button>
-				<button type="reset" class="btn"><?php echo Translate::t ('cancel'); ?></button>
+				<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>

+ 59 - 62
app/views/configure/display.phtml

@@ -1,13 +1,13 @@
 <?php $this->partial ('aside_configure'); ?>
 
 <div class="post">
-	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a>
+	<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 Translate::t ('general_configuration'); ?></legend>
+		<legend><?php echo Minz_Translate::t ('general_configuration'); ?></legend>
 
 		<div class="form-group">
-			<label class="group-name" for="language"><?php echo Translate::t ('language'); ?></label>
+			<label class="group-name" for="language"><?php echo Minz_Translate::t ('language'); ?></label>
 			<div class="group-controls">
 				<select name="language" id="language">
 				<?php $languages = $this->conf->availableLanguages (); ?>
@@ -19,12 +19,12 @@
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="theme"><?php echo Translate::t ('theme'); ?></label>
+			<label class="group-name" for="theme"><?php echo Minz_Translate::t ('theme'); ?></label>
 			<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"' : ''; ?>>
-					<?php echo $theme['name'] . ' ' . Translate::t ('by') . ' ' . $theme['author']; ?> 
+					<?php echo $theme['name'] . ' ' . Minz_Translate::t ('by') . ' ' . $theme['author']; ?> 
 				</option>
 				<?php } ?>
 				</select>
@@ -32,68 +32,68 @@
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="old_entries"><?php echo Translate::t ('delete_articles_every'); ?></label>
+			<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" value="<?php echo $this->conf->oldEntries (); ?>" /> <?php echo Translate::t ('month'); ?>
+				<input type="number" id="old_entries" name="old_entries" value="<?php echo $this->conf->oldEntries (); ?>" /> <?php echo Minz_Translate::t ('month'); ?>
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="mail_login"><?php echo Translate::t ('persona_connection_email'); ?></label>
+			<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 Translate::t ('blank_to_disable'); ?>" />
-				<noscript><b><?php echo Translate::t ('javascript_should_be_activated'); ?></b></noscript>
+				<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 Translate::t ('allow_anonymous'); ?>
+					<?php echo Minz_Translate::t ('allow_anonymous'); ?>
 				</label>
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="token"><?php echo Translate::t ('auth_token'); ?></label>
+			<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 Translate::t ('blank_to_disable'); ?>"/>
-				<i class="icon i_help"></i> <?php echo Translate::t('explain_token', Url::display(null, 'html', true), $token); ?>
+				<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>
 	
-		<legend><?php echo Translate::t ('reading_configuration'); ?></legend>
+		<legend><?php echo Minz_Translate::t ('reading_configuration'); ?></legend>
 
 		<div class="form-group">
-			<label class="group-name" for="posts_per_page"><?php echo Translate::t ('articles_per_page'); ?></label>
+			<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 (); ?>" />
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="sort_order"><?php echo Translate::t ('sort_order'); ?></label>
+			<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="low_to_high"<?php echo $this->conf->sortOrder () == 'low_to_high' ? ' selected="selected"' : ''; ?>><?php echo Translate::t ('newer_first'); ?></option>
-					<option value="high_to_low"<?php echo $this->conf->sortOrder () == 'high_to_low' ? ' selected="selected"' : ''; ?>><?php echo Translate::t ('older_first'); ?></option>
+					<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>
 				</select>
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="view_mode"><?php echo Translate::t ('default_view'); ?></label>
+			<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 Translate::t ('normal_view'); ?></option>
-					<option value="reader"<?php echo $this->conf->viewMode () == 'reader' ? ' selected="selected"' : ''; ?>><?php echo Translate::t ('reader_view'); ?></option>
-					<option value="global"<?php echo $this->conf->viewMode () == 'global' ? ' selected="selected"' : ''; ?>><?php echo Translate::t ('global_view'); ?></option>
+					<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>
 				</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"' : ''; ?> />
-					<?php echo Translate::t ('show_all_articles'); ?>
+					<?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"' : ''; ?> />
-					<?php echo Translate::t ('show_not_reads'); ?>
+					<?php echo Minz_Translate::t ('show_not_reads'); ?>
 				</label>
 			</div>
 		</div>
@@ -102,8 +102,8 @@
 			<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"' : ''; ?> />
-					<?php echo Translate::t ('auto_load_more'); ?>
-					<?php echo $this->conf->displayPosts () == 'no' ? '<noscript> - <b>' . Translate::t ('javascript_should_be_activated') . '</b></noscript>' : ''; ?>
+					<?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>' : ''; ?>
 				</label>
 			</div>
 		</div>
@@ -112,8 +112,8 @@
 			<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"' : ''; ?> />
-					<?php echo Translate::t ('display_articles_unfolded'); ?>
-					<?php echo $this->conf->displayPosts () == 'no' ? '<noscript> - <b>' . Translate::t ('javascript_should_be_activated') . '</b></noscript>' : ''; ?>
+					<?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>' : ''; ?>
 				</label>
 			</div>
 		</div>
@@ -122,57 +122,61 @@
 			<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"' : ''; ?> />
-					<?php echo Translate::t ('img_with_lazyload'); ?>
-					<?php echo $this->conf->lazyload () == 'yes' ? '<noscript> - <b>' . Translate::t ('javascript_should_be_activated') . '</b></noscript>' : ''; ?>
+					<?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>' : ''; ?>
 				</label>
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name"><?php echo Translate::t ('auto_read_when'); ?></label>
+			<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"' : ''; ?> />
-					<?php echo Translate::t ('article_selected'); ?>
+					<?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"' : ''; ?> />
-					<?php echo Translate::t ('article_open_on_website'); ?>
+					<?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"' : ''; ?> />
-					<?php echo Translate::t ('scroll'); ?>
+					<?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"' : ''; ?> />
+					<?php echo Minz_Translate::t ('upon_reception'); ?>
 				</label>
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name"><?php echo Translate::t ('after_onread'); ?></label>
+			<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"' : ''; ?> />
-					<?php echo Translate::t ('jump_next'); ?>
+					<?php echo Minz_Translate::t ('jump_next'); ?>
 				</label>
 			</div>
 		</div>
 
-		<legend><?php echo Translate::t ('reading_icons'); ?></legend>
+		<legend><?php echo Minz_Translate::t ('reading_icons'); ?></legend>
 		<div class="form-group">
 			<table>
 				<thead>
 					<tr>
-						<th>&nbsp;</th>
-						<th><a class="read" title="<?php echo Translate::t ('mark_read'); ?>">&nbsp;</span></th>
-						<th><a class="bookmark" title="<?php echo Translate::t ('mark_favorite'); ?>">&nbsp;</span></th>
-						<th><?php echo Translate::t ('sharing'); ?></th>
-						<th><?php echo Translate::t ('related_tags'); ?></th>
-						<th><?php echo Translate::t ('publication_date'); ?></th>
-						<th class="item link"><a>&nbsp;</a></th>
+						<th> </th>
+						<th title="<?php echo Minz_Translate::t ('mark_read'); ?>"><?php echo FreshRSS_Themes::icon('read'); ?></th>
+						<th title="<?php echo Minz_Translate::t ('mark_favorite'); ?>"><?php echo FreshRSS_Themes::icon('bookmark'); ?></th>
+						<th><?php echo Minz_Translate::t ('sharing'); ?></th>
+						<th><?php echo Minz_Translate::t ('related_tags'); ?></th>
+						<th><?php echo Minz_Translate::t ('publication_date'); ?></th>
+						<th><?php echo FreshRSS_Themes::icon('link'); ?></th>
 					</tr>
 				</thead>
 				<tbody>
 					<tr>
-						<th><?php echo Translate::t ('top_line'); ?></th>
+						<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" disabled="disabled" /></td>
@@ -180,7 +184,7 @@
 						<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>
 					</tr><tr>
-						<th><?php echo Translate::t ('bottom_line'); ?></th>
+						<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>
@@ -192,29 +196,22 @@
 			</table>
 		</div>
 
-		<legend><?php echo Translate::t ('sharing'); ?></legend>
-		<div class="form-group">
-			<label class="group-name" for="shaarli"><?php echo Translate::t ('your_shaarli'); ?></label>
-			<div class="group-controls">
-				<input type="text" id="shaarli" name="shaarli" value="<?php echo $this->conf->urlShaarli (); ?>" placeholder="<?php echo Translate::t ('blank_to_disable'); ?>"/>
-			</div>
-		</div>
-
-		<legend><?php echo Translate::t ('advanced'); ?></legend>
+		<legend><?php echo Minz_Translate::t ('advanced'); ?></legend>
 		<div class="form-group">
 			<label class="group-name"></label>
 			<div class="group-controls">
-				<a class="btn" href="<?php echo _url('entry', 'optimize'); ?>">
-					<?php echo Translate::t('optimize_bdd'); ?>
-				</a>
-				<i class="icon i_help"></i> <?php echo Translate::t('optimize_todo_sometimes'); ?>
+				<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>
 
 		<div class="form-group form-actions">
 			<div class="group-controls">
-				<button type="submit" class="btn btn-important"><?php echo Translate::t ('save'); ?></button>
-				<button type="reset" class="btn"><?php echo Translate::t ('cancel'); ?></button>
+				<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>

+ 46 - 38
app/views/configure/feed.phtml

@@ -2,100 +2,108 @@
 
 <?php if ($this->flux) { ?>
 <div class="post">
-	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a> <?php echo Translate::t ('or'); ?> <a href="<?php echo _url ('index', 'index', 'get', 'f_' . $this->flux->id ()); ?>"><?php echo Translate::t ('filter'); ?></a>
+	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a> <?php echo Minz_Translate::t ('or'); ?> <a href="<?php echo _url ('index', 'index', 'get', 'f_' . $this->flux->id ()); ?>"><?php echo Minz_Translate::t ('filter'); ?></a>
 
 	<h1><?php echo $this->flux->name (); ?></h1>
 	<?php echo $this->flux->description (); ?>
 
 	<?php if ($this->flux->inError ()) { ?>
-	<p class="alert alert-error"><span class="alert-head"><?php echo Translate::t ('damn'); ?></span> <?php echo Translate::t ('feed_in_error'); ?></p>
+	<p class="alert alert-error"><span class="alert-head"><?php echo Minz_Translate::t ('damn'); ?></span> <?php echo Minz_Translate::t ('feed_in_error'); ?></p>
 	<?php } ?>
 
 	<form method="post" action="<?php echo _url ('configure', 'feed', 'id', $this->flux->id ()); ?>">
-		<legend><?php echo Translate::t ('informations'); ?></legend>
+		<legend><?php echo Minz_Translate::t ('informations'); ?></legend>
 		<div class="form-group">
-			<label class="group-name" for="name"><?php echo Translate::t ('title'); ?></label>
+			<label class="group-name" for="name"><?php echo Minz_Translate::t ('title'); ?></label>
 			<div class="group-controls">
 				<input type="text" name="name" id="name" value="<?php echo $this->flux->name () ; ?>" />
 			</div>
 		</div>
 		<div class="form-group">
-			<label class="group-name"><?php echo Translate::t ('website_url'); ?></label>
+			<label class="group-name"><?php echo Minz_Translate::t ('feed_description'); ?></label>
 			<div class="group-controls">
-				<span class="control">
-					<?php echo $this->flux->website (); ?>
-					<a target="_blank" href="<?php echo $this->flux->website (); ?>"><i class="icon i_link"></i></a>
-				</span>
+				<textarea name="description" id="description"><?php echo htmlspecialchars($this->flux->description(), ENT_NOQUOTES, 'UTF-8'); ?></textarea>
 			</div>
 		</div>
 		<div class="form-group">
-			<label class="group-name"><?php echo Translate::t ('feed_url'); ?></label>
+			<label class="group-name"><?php echo Minz_Translate::t ('website_url'); ?></label>
 			<div class="group-controls">
-				<span class="control">
-					<?php echo $this->flux->url (); ?>
-					<a target="_blank" href="<?php echo $this->flux->url (); ?>"><i class="icon i_link"></i></a>
-				</span>
+				<input type="text" name="website" id="website" value="<?php echo $this->flux->website (); ?>" />
+				<a target="_blank" href="<?php echo $this->flux->website (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
+			</div>
+		</div>
+		<div class="form-group">
+			<label class="group-name"><?php echo Minz_Translate::t ('feed_url'); ?></label>
+			<div class="group-controls">
+				<input type="text" name="url" id="url" value="<?php echo $this->flux->url (); ?>" />
+				<a target="_blank" href="<?php echo $this->flux->url (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
+				  <a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->flux->url (); ?>"><?php echo Minz_Translate::t ('feed_validator'); ?></a>
+			</div>
+		</div>
+		<div class="form-group">
+			<label class="group-name" for="category"><?php echo Minz_Translate::t ('category'); ?></label>
+			<div class="group-controls">
+				<select name="category" id="category">
+				<?php foreach ($this->categories as $cat) { ?>
+				<option value="<?php echo $cat->id (); ?>"<?php echo $cat->id ()== $this->flux->category () ? ' selected="selected"' : ''; ?>>
+					<?php echo $cat->name (); ?>
+				</option>
+				<?php } ?>
+				</select>
 			</div>
 		</div>
 		<div class="form-group">
 			<label class="group-name"></label>
 			<div class="group-controls">
 				<a class="btn" href="<?php echo _url ('feed', 'actualize', 'id', $this->flux->id ()); ?>">
-					<i class="icon i_refresh"></i> <?php echo Translate::t('actualize'); ?>
+					<?php echo FreshRSS_Themes::icon('refresh'); ?> <?php echo Minz_Translate::t('actualize'); ?>
 				</a>
 			</div>
 		</div>
 		<div class="form-group">
-			<label class="group-name"><?php echo Translate::t ('number_articles'); ?></label>
+			<label class="group-name"><?php echo Minz_Translate::t ('number_articles'); ?></label>
 			<div class="group-controls">
 				<span class="control"><?php echo $this->flux->nbEntries (); ?></span>
 				<label class="checkbox" for="keep_history">
 					<input type="checkbox" name="keep_history" id="keep_history" value="yes"<?php echo $this->flux->keepHistory () == 'yes' ? ' checked="checked"' : ''; ?> />
-					<?php echo Translate::t ('keep_history'); ?>
+					<?php echo Minz_Translate::t ('keep_history'); ?>
 				</label>
 			</div>
 		</div>
-
 		<div class="form-group">
-			<label class="group-name" for="category"><?php echo Translate::t ('category'); ?></label>
+			<label class="group-name"></label>
 			<div class="group-controls">
-				<select name="category" id="category">
-				<?php foreach ($this->categories as $cat) { ?>
-				<option value="<?php echo $cat->id (); ?>"<?php echo $cat->id ()== $this->flux->category () ? ' selected="selected"' : ''; ?>>
-					<?php echo $cat->name (); ?>
-				</option>
-				<?php } ?>
-				</select>
+				<button class="btn btn-attention confirm" formmethod="post" formaction="<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'truncate', 'params' => array ('id' => $this->flux->id ()))); ?>"><?php echo Minz_Translate::t ('truncate'); ?></button>
 			</div>
 		</div>
 
-		<legend><?php echo Translate::t ('advanced'); ?></legend>
+		<legend><?php echo Minz_Translate::t ('advanced'); ?></legend>
 		<div class="form-group">
-			<label class="group-name" for="priority"><?php echo Translate::t ('show_in_all_flux'); ?></label>
+			<label class="group-name" for="priority"><?php echo Minz_Translate::t ('show_in_all_flux'); ?></label>
 			<div class="group-controls">
 				<label class="checkbox" for="priority">
 					<input type="checkbox" name="priority" id="priority" value="10"<?php echo $this->flux->priority () > 0 ? ' checked="checked"' : ''; ?> />
-					<?php echo Translate::t ('yes'); ?>
+					<?php echo Minz_Translate::t ('yes'); ?>
 				</label>
 			</div>
 		</div>
 		<div class="form-group">
-			<label class="group-name" for="path_entries"><?php echo Translate::t ('css_path_on_website'); ?></label>
+			<label class="group-name" for="path_entries"><?php echo Minz_Translate::t ('css_path_on_website'); ?></label>
 			<div class="group-controls">
-				<input type="text" name="path_entries" id="path_entries" value="<?php echo $this->flux->pathEntries (); ?>" placeholder="<?php echo Translate::t ('blank_to_disable'); ?>" />
-				<i class="icon i_help"></i> <?php echo Translate::t ('retrieve_truncated_feeds'); ?>
+				<input type="text" name="path_entries" id="path_entries" value="<?php echo $this->flux->pathEntries (); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" />
+				<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t ('retrieve_truncated_feeds'); ?>
 			</div>
 		</div>
 
 		<?php $auth = $this->flux->httpAuth (false); ?>
 		<div class="form-group">
-			<label class="group-name" for="http_user"><?php echo Translate::t ('http_username'); ?></label>
+			<label class="group-name" for="http_user"><?php echo Minz_Translate::t ('http_username'); ?></label>
 			<div class="group-controls">
 				<input type="text" name="http_user" id="http_user" value="<?php echo $auth['username']; ?>" autocomplete="off" />
-				<i class="icon i_help"></i> <?php echo Translate::t ('access_protected_feeds'); ?>
+				<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t ('access_protected_feeds'); ?>
 			</div>
 
-			<label class="group-name" for="http_pass"><?php echo Translate::t ('http_password'); ?></label>
+			<label class="group-name" for="http_pass"><?php echo Minz_Translate::t ('http_password'); ?></label>
 			<div class="group-controls">
 				<input type="password" name="http_pass" id="http_pass" value="<?php echo $auth['password']; ?>" autocomplete="off" />
 			</div>
@@ -104,13 +112,13 @@
 
 		<div class="form-group form-actions">
 			<div class="group-controls">
-				<button class="btn btn-important"><?php echo Translate::t ('save'); ?></button>
-				<button class="btn btn-attention confirm" formaction="<?php echo Url::display (array ('c' => 'feed', 'a' => 'delete', 'params' => array ('id' => $this->flux->id ()))); ?>"><?php echo Translate::t ('delete'); ?></button>
+				<button class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button>
+				<button class="btn btn-attention confirm" formmethod="post" formaction="<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'delete', 'params' => array ('id' => $this->flux->id ()))); ?>"><?php echo Minz_Translate::t ('delete'); ?></button>
 			</div>
 		</div>
 	</form>
 </div>
 
 <?php } else { ?>
-<div class="alert alert-warn"><span class="alert-head"><?php echo Translate::t ('no_selected_feed'); ?></span> <?php echo Translate::t ('think_to_add'); ?></div>
+<div class="alert alert-warn"><span class="alert-head"><?php echo Minz_Translate::t ('no_selected_feed'); ?></span> <?php echo Minz_Translate::t ('think_to_add'); ?></div>
 <?php } ?>

+ 9 - 9
app/views/configure/importExport.phtml

@@ -1,9 +1,9 @@
 <?php if ($this->req == 'export') { ?>
 <?php echo '<?xml version="1.0" encoding="UTF-8" ?>'; // résout bug sur certain serveur ?>
-<!-- Generated by <?php echo Configuration::title (); ?> -->
+<!-- Generated by <?php echo Minz_Configuration::title (); ?> -->
 <opml version="2.0">
 	<head>
-		<title><?php echo Configuration::title (); ?> OPML Feed</title>
+		<title><?php echo Minz_Configuration::title (); ?> OPML Feed</title>
 		<dateCreated><?php echo date('D, d M Y H:i:s'); ?></dateCreated>
 	</head>
 	<body>
@@ -14,12 +14,12 @@
 <?php $this->partial ('aside_feed'); ?>
 
 <div class="post ">
-	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a>
+	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
 
-	<form method="post" action="<?php echo Url::display (array ('c' => 'configure', 'a' => 'importExport', 'params' => array ('q' => 'import'))); ?>" enctype="multipart/form-data">
-		<legend><?php echo Translate::t ('import_export_opml'); ?></legend>
+	<form method="post" action="<?php echo Minz_Url::display (array ('c' => 'configure', 'a' => 'importExport', 'params' => array ('q' => 'import'))); ?>" enctype="multipart/form-data">
+		<legend><?php echo Minz_Translate::t ('import_export_opml'); ?></legend>
 		<div class="form-group">
-			<label class="group-name" for="file"><?php echo Translate::t ('file_to_import'); ?></label>
+			<label class="group-name" for="file"><?php echo Minz_Translate::t ('file_to_import'); ?></label>
 			<div class="group-controls">
 				<input type="file" name="file" id="file" />
 			</div>
@@ -27,9 +27,9 @@
 
 		<div class="form-group form-actions">
 			<div class="group-controls">
-				<button type="submit" class="btn btn-important"><?php echo Translate::t ('import'); ?></button>
-				<?php echo Translate::t ('or'); ?>
-				<a target="_blank" class="btn btn-important" href="<?php echo _url ('configure', 'importExport', 'q', 'export'); ?>"><?php echo Translate::t ('export'); ?></a>
+				<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('import'); ?></button>
+				<?php echo Minz_Translate::t ('or'); ?>
+				<a target="_blank" class="btn btn-important" href="<?php echo _url ('configure', 'importExport', 'q', 'export'); ?>"><?php echo Minz_Translate::t ('export'); ?></a>
 			</div>
 		</div>
 	</form>

+ 64 - 0
app/views/configure/sharing.phtml

@@ -0,0 +1,64 @@
+<?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', 'sharing'); ?>">
+		<legend><?php echo Minz_Translate::t ('sharing'); ?></legend>
+		<div class="form-group">
+			<label class="group-name" for="shaarli">
+				<?php echo Minz_Translate::t ('your_shaarli'); ?>
+			</label>
+			<div class="group-controls">
+				<input type="url" id="shaarli" name="shaarli" value="<?php echo $this->conf->sharing ('shaarli'); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" size="64" />
+
+				<?php echo FreshRSS_Themes::icon('help'); ?> <a target="_blank" href="http://sebsauvage.net/wiki/doku.php?id=php:shaarli"><?php echo Minz_Translate::t ('more_information'); ?></a>
+			</div>
+		</div>
+
+		<div class="form-group">
+			<label class="group-name" for="poche">
+				<?php echo Minz_Translate::t ('your_poche'); ?>
+			</label>
+			<div class="group-controls">
+				<input type="url" id="poche" name="poche" value="<?php echo $this->conf->sharing ('poche'); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" size="64" />
+
+				<?php echo FreshRSS_Themes::icon('help'); ?> <a target="_blank" href="http://www.inthepoche.com/"><?php echo Minz_Translate::t ('more_information'); ?></a>
+			</div>
+		</div>
+
+		<div class="form-group">
+			<label class="group-name" for="diaspora">
+				<?php echo Minz_Translate::t ('your_diaspora_pod'); ?>
+			</label>
+			<div class="group-controls">
+				<input type="url" id="diaspora" name="diaspora" value="<?php echo $this->conf->sharing ('diaspora'); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" size="64" />
+
+				<?php echo FreshRSS_Themes::icon('help'); ?> <a target="_blank" href="https://diasporafoundation.org/"><?php echo Minz_Translate::t ('more_information'); ?></a>
+			</div>
+		</div>
+
+		<div class="form-group">
+			<label class="group-name"><?php echo Minz_Translate::t ('activate_sharing'); ?></label>
+			<div class="group-controls">
+				<?php
+					$services = array ('twitter', 'g+', 'facebook', 'email', 'print');
+
+					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"' : ''; ?> />
+					<?php echo Minz_Translate::t ($service); ?>
+				</label>
+				<?php } ?>
+			</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>
+</div>

+ 27 - 13
app/views/configure/shortcut.phtml

@@ -1,7 +1,7 @@
 <?php $this->partial ('aside_configure'); ?>
 
 <div class="post">
-	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a>
+	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
 
 	<datalist id="keys">
 		<?php foreach ($this->list_keys as $key) { ?>
@@ -12,52 +12,66 @@
 	<?php $s = $this->conf->shortcuts (); ?>
 
 	<form method="post" action="<?php echo _url ('configure', 'shortcut'); ?>">
-		<legend><?php echo Translate::t ('shortcuts_management'); ?></legend>
+		<legend><?php echo Minz_Translate::t ('shortcuts_management'); ?></legend>
 
-		<noscript><p class="alert alert-error"><?php echo Translate::t ('javascript_for_shortcuts'); ?></p></noscript>
+		<noscript><p class="alert alert-error"><?php echo Minz_Translate::t ('javascript_for_shortcuts'); ?></p></noscript>
 
 		<div class="form-group">
-			<label class="group-name" for="mark_read"><?php echo Translate::t ('mark_read'); ?></label>
+			<label class="group-name" for="mark_read"><?php echo Minz_Translate::t ('mark_read'); ?></label>
 			<div class="group-controls">
 				<input type="text" id="mark_read" name="shortcuts[mark_read]" list="keys" value="<?php echo $s['mark_read']; ?>" />
-				<?php echo Translate::t ('shift_for_all_read'); ?>
+				<?php echo Minz_Translate::t ('shift_for_all_read'); ?>
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="mark_favorite"><?php echo Translate::t ('mark_favorite'); ?></label>
+			<label class="group-name" for="mark_favorite"><?php echo Minz_Translate::t ('mark_favorite'); ?></label>
 			<div class="group-controls">
 				<input type="text" id="mark_favorite" name="shortcuts[mark_favorite]" list="keys" value="<?php echo $s['mark_favorite']; ?>" />
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="go_website"><?php echo Translate::t ('see_on_website'); ?></label>
+			<label class="group-name" for="go_website"><?php echo Minz_Translate::t ('see_on_website'); ?></label>
 			<div class="group-controls">
 				<input type="text" id="go_website" name="shortcuts[go_website]" list="keys" value="<?php echo $s['go_website']; ?>" />
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="next_entry"><?php echo Translate::t ('next_article'); ?></label>
+			<label class="group-name" for="next_entry"><?php echo Minz_Translate::t ('next_article'); ?></label>
 			<div class="group-controls">
 				<input type="text" id="next_entry" name="shortcuts[next_entry]" list="keys" value="<?php echo $s['next_entry']; ?>" />
-				<?php echo Translate::t ('shift_for_last'); ?>
+				<?php echo Minz_Translate::t ('shift_for_last'); ?>
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="prev_entry"><?php echo Translate::t ('previous_article'); ?></label>
+			<label class="group-name" for="prev_entry"><?php echo Minz_Translate::t ('previous_article'); ?></label>
 			<div class="group-controls">
 				<input type="text" id="prev_entry" name="shortcuts[prev_entry]" list="keys" value="<?php echo $s['prev_entry']; ?>" />
-				<?php echo Translate::t ('shift_for_first'); ?>
+				<?php echo Minz_Translate::t ('shift_for_first'); ?>
+			</div>
+		</div>
+
+		<div class="form-group">
+			<label class="group-name" for="collapse_entry"><?php echo Minz_Translate::t ('collapse_article'); ?></label>
+			<div class="group-controls">
+				<input type="text" id="collapse_entry" name="shortcuts[collapse_entry]" list="keys" value="<?php echo $s['collapse_entry']; ?>" />
+			</div>
+		</div>
+
+		<div class="form-group">
+			<label class="group-name" for="load_more_shortcut"><?php echo Minz_Translate::t ('load_more'); ?></label>
+			<div class="group-controls">
+				<input type="text" id="load_more_shortcut" name="shortcuts[load_more]" list="keys" value="<?php echo $s['load_more']; ?>" />
 			</div>
 		</div>
 
 		<div class="form-group form-actions">
 			<div class="group-controls">
-				<button type="submit" class="btn btn-important"><?php echo Translate::t ('save'); ?></button>
-				<button type="reset" class="btn"><?php echo Translate::t ('cancel'); ?></button>
+				<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>

+ 9 - 8
app/views/entry/bookmark.phtml

@@ -1,15 +1,16 @@
 <?php
+header('Content-Type: application/json; charset=UTF-8');
 
-if (Request::param ('is_favorite')) {
-	Request::_param ('is_favorite', 0);
+if (Minz_Request::param ('is_favorite')) {
+	Minz_Request::_param ('is_favorite', 0);
 } else {
-	Request::_param ('is_favorite', 1);
+	Minz_Request::_param ('is_favorite', 1);
 }
 
-$url = Url::display (array (
-	'c' => Request::controllerName (),
-	'a' => Request::actionName (),
-	'params' => Request::params (),
+$url = Minz_Url::display (array (
+	'c' => Minz_Request::controllerName (),
+	'a' => Minz_Request::actionName (),
+	'params' => Minz_Request::params (),
 ));
 
-echo json_encode (array ('url' => str_ireplace ('&amp;', '&', $url)));
+echo json_encode (array ('url' => str_ireplace ('&amp;', '&', $url), 'icon' => FreshRSS_Themes::icon(Minz_Request::param ('is_favorite') ? 'non-starred' : 'starred')));

+ 9 - 8
app/views/entry/read.phtml

@@ -1,15 +1,16 @@
 <?php
+header('Content-Type: application/json; charset=UTF-8');
 
-if (Request::param ('is_read')) {
-	Request::_param ('is_read', 0);
+if (Minz_Request::param ('is_read')) {
+	Minz_Request::_param ('is_read', 0);
 } else {
-	Request::_param ('is_read', 1);
+	Minz_Request::_param ('is_read', 1);
 }
 
-$url = Url::display (array (
-	'c' => Request::controllerName (),
-	'a' => Request::actionName (),
-	'params' => Request::params (),
+$url = Minz_Url::display (array (
+	'c' => Minz_Request::controllerName (),
+	'a' => Minz_Request::actionName (),
+	'params' => Minz_Request::params (),
 ));
 
-echo json_encode (array ('url' => str_ireplace ('&amp;', '&', $url)));
+echo json_encode (array ('url' => str_ireplace ('&amp;', '&', $url), 'icon' => FreshRSS_Themes::icon(Minz_Request::param ('is_read') ? 'unread' : 'read')));

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

@@ -3,8 +3,8 @@
 		<h1 class="alert-head"><?php echo $this->code; ?></h1>
 
 		<p>
-			<?php echo Translate::t ('page_not_found'); ?><br />
-			<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a>
+			<?php echo Minz_Translate::t ('page_not_found'); ?><br />
+			<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
 		</p>
 	</div>
 </div>

+ 15 - 9
app/views/helpers/javascript_vars.phtml

@@ -2,12 +2,12 @@
 	echo '"use strict";', "\n";
 	$mark = $this->conf->markWhen ();
 	echo 'var ',
-		'hide_posts=', ($this->conf->displayPosts () === 'yes' || Request::param ('output') === 'reader') ? 'false' : 'true',
+		'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' || Request::param ('output') === 'reader') ? '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';
 
 	$s = $this->conf->shortcuts ();
@@ -16,10 +16,16 @@
 			'mark_favorite:"', $s['mark_favorite'], '",',
 			'go_website:"', $s['go_website'], '",',
 			'prev_entry:"', $s['prev_entry'], '",',
-			'next_entry:"', $s['next_entry'], '"',
+			'next_entry:"', $s['next_entry'], '",',
+			'collapse_entry:"', $s['collapse_entry'], '",',
+			'load_more:"', $s['load_more'], '"',
 		"},\n";
 
-	$mail = Session::param ('mail', 'null');
+	if (Minz_Request::param ('output') === 'global') {
+		echo "iconClose='", FreshRSS_Themes::icon('close'), "',\n";
+	}
+
+	$mail = Minz_Session::param ('mail', 'null');
 	if ($mail != 'null') {
 		$mail = '"' . $mail . '"';
 	}
@@ -29,11 +35,11 @@
 		'url_logout="', _url ('index', 'logout'), '",',
 		'current_user_mail=', $mail, ",\n";
 
-	echo 'load_shortcuts=', Request::controllerName () === 'index' && Request::actionName () === 'index' ? 'true' : 'false', ",\n";
+	echo 'load_shortcuts=', Minz_Request::controllerName () === 'index' && Minz_Request::actionName () === 'index' ? 'true' : 'false', ",\n";
 
-	echo 'str_confirmation="', Translate::t('confirm_action'), '"', ",\n";
+	echo 'str_confirmation="', Minz_Translate::t('confirm_action'), '"', ",\n";
 
-	echo 'auto_actualize_feeds=', Session::param('actualize_feeds', false) ? 'true' : 'false', ";\n";
-	if (Session::param('actualize_feeds', false)) {
-		Session::_param('actualize_feeds');
+	echo 'auto_actualize_feeds=', Minz_Session::param('actualize_feeds', false) ? 'true' : 'false', ";\n";
+	if (Minz_Session::param('actualize_feeds', false)) {
+		Minz_Session::_param('actualize_feeds');
 	}

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

@@ -1,7 +1,7 @@
 <?php
-	$c = Request::controllerName ();
-	$a = Request::actionName ();
-	$params = Request::params ();
+	$c = Minz_Request::controllerName ();
+	$a = Minz_Request::actionName ();
+	$params = Minz_Request::params ();
 ?>
 
 <?php if ($this->nbPage > 1) { ?>
@@ -9,14 +9,14 @@
 	<?php $params[$getteur] = 1; ?>
 	<li class="item pager-first">
 		<?php if ($this->currentPage > 1) { ?>
-		<a href="<?php echo Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">« <?php echo Translate::t('first'); ?></a>
+		<a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">« <?php echo Minz_Translate::t('first'); ?></a>
 		<?php } ?>
 	</li>
 
 	<?php $params[$getteur] = $this->currentPage - 1; ?>
 	<li class="item pager-previous">
 		<?php if ($this->currentPage > 1) { ?>
-		<a href="<?php echo Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">‹ <?php echo Translate::t('previous'); ?></a>
+		<a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">‹ <?php echo Minz_Translate::t('previous'); ?></a>
 		<?php } ?>
 	</li>
 
@@ -24,7 +24,7 @@
 		<?php if($i > 0 && $i <= $this->nbPage) { ?>
 			<?php if ($i != $this->currentPage) { ?>
 			<?php $params[$getteur] = $i; ?>
-			<li class="item pager-item"><a href="<?php echo Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo $i; ?></a></li>
+			<li class="item pager-item"><a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo $i; ?></a></li>
 			<?php } else { ?>
 			<li class="item pager-current"><?php echo $i; ?></li>
 			<?php } ?>
@@ -34,13 +34,13 @@
 	<?php $params[$getteur] = $this->currentPage + 1; ?>
 	<li class="item pager-next">
 		<?php if ($this->currentPage < $this->nbPage) { ?>
-		<a href="<?php echo Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Translate::t('next'); ?> ›</a>
+		<a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Minz_Translate::t('next'); ?> ›</a>
 		<?php } ?>
 	</li>
 	<?php $params[$getteur] = $this->nbPage; ?>
 	<li class="item pager-last">
 		<?php if ($this->currentPage < $this->nbPage) { ?>
-		<a href="<?php echo Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Translate::t('last'); ?> »</a>
+		<a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Minz_Translate::t('last'); ?> »</a>
 		<?php } ?>
 	</li>
 </ul>

+ 17 - 11
app/views/helpers/pagination.phtml

@@ -1,20 +1,26 @@
 <?php
-	$c = Request::controllerName ();
-	$a = Request::actionName ();
-	$params = Request::params ();
+	$c = Minz_Request::controllerName ();
+	$a = Minz_Request::actionName ();
+	$params = Minz_Request::params ();
+	$markReadUrl = Minz_Session::param ('markReadUrl');
+	Minz_Session::_param ('markReadUrl', false);
 ?>
 
 <ul class="pagination">
 	<li class="item pager-next">
-	<?php if ($this->next != '') { ?>
-	<?php $params[$getteur] = $this->next; ?>
-	<a id="load_more" href="<?php echo Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Translate::t ('load_more'); ?></a>
+	<?php if (!empty($this->nextId)) { ?>
+	<?php $params['next'] = $this->nextId; ?>
+	<a id="load_more" href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Minz_Translate::t ('load_more'); ?></a>
+	<?php } elseif ($markReadUrl) { ?>
+	<a id="bigMarkAsRead" href="<?php echo $markReadUrl; ?>">
+		<?php echo Minz_Translate::t ('nothing_to_load'); ?><br />
+		<span class="bigTick">✔</span><br />
+		<?php echo Minz_Translate::t ('mark_all_read'); ?>
+	</a>
 	<?php } else { ?>
-	<div class="bigMarkAsRead">
-		<p><?php echo Translate::t ('nothing_to_load'); ?></p>
-		<p class="bigTick">✔</p>
-		<p><?php echo Translate::t ('mark_all_read'); ?></p>
-	</div>
+	<a id="bigMarkAsRead" href=".">
+		<?php echo Minz_Translate::t ('nothing_to_load'); ?><br />
+	</a>
 	<?php } ?>
 	</li>
 </ul>

+ 3 - 5
app/views/helpers/view/global_view.phtml

@@ -9,18 +9,16 @@
 	<div class="box-category">
 		<div class="category">
 			<a data-unread="<?php echo $cat->nbNotRead (); ?>" class="btn" href="<?php echo _url ('index', 'index', 'get', 'c_' . $cat->id (), 'output', 'normal'); ?>">
-			<?php echo htmlspecialchars($cat->name(), ENT_NOQUOTES, 'UTF-8'); ?>
+			<?php echo $cat->name(); ?>
 			</a>
 		</div>
-
 		<ul class="feeds">
 			<?php foreach ($feeds as $feed) { ?>
 			<?php $not_read = $feed->nbNotRead (); ?>
 			<li id="f_<?php echo $feed->id (); ?>" class="item<?php echo $feed->inError () ? ' error' : ''; ?><?php echo $feed->nbEntries () == 0 ? ' empty' : ''; ?>">
 				<img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" />
-
 				<a class="feed" data-unread="<?php echo $feed->nbNotRead (); ?>" data-priority="<?php echo $feed->priority (); ?>" href="<?php echo _url ('index', 'index', 'get', 'f_' . $feed->id (), 'output', 'normal'); ?>">
-				<?php echo htmlspecialchars($feed->name(), ENT_NOQUOTES, 'UTF-8'); ?>
+				<?php echo $feed->name(); ?>
 				</a>
 			</li>
 			<?php } ?>
@@ -34,5 +32,5 @@
 
 <div id="overlay"></div>
 <div id="panel"<?php echo $this->conf->displayPosts () === 'no' ? ' class="hide_posts"' : ''; ?>>
-	<a class="close" href="#"><i class="icon i_close"></i></a>
+	<a class="close" href="#"><?php echo FreshRSS_Themes::icon('close'); ?></a>
 </div>

+ 115 - 58
app/views/helpers/view/normal_view.phtml

@@ -3,53 +3,72 @@
 $this->partial ('aside_flux');
 $this->partial ('nav_menu');
 
-if (isset ($this->entryPaginator) && !$this->entryPaginator->isEmpty ()) {
-	$items = $this->entryPaginator->items ();
+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');
+	$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';
 ?>
 
-<div id="stream" class="normal<?php echo $this->conf->displayPosts () === 'no' ? ' hide_posts' : ''; ?>">
-	<?php
-		$display_today = true;
-		$display_yesterday = true;
-		$display_others = true;
-	?>
-	<?php foreach ($items as $item) { ?>
+<div id="stream" class="normal<?php echo $hidePosts ? ' hide_posts' : ''; ?>">
+	<?php foreach ($this->entries as $item) { ?>
 
-	<?php if ($display_today && $item->isDay (Days::TODAY)) { ?>
+	<?php if ($display_today && $item->isDay (FreshRSS_Days::TODAY, $today)) { ?>
 	<div class="day" id="day_today">
-		<?php echo Translate::t ('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 (Days::YESTERDAY)) { ?>
+	<?php if ($display_yesterday && $item->isDay (FreshRSS_Days::YESTERDAY, $today)) { ?>
 	<div class="day" id="day_yesterday">
-		<?php echo Translate::t ('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 (Days::BEFORE_YESTERDAY)) { ?>
+	<?php if ($display_others && $item->isDay (FreshRSS_Days::BEFORE_YESTERDAY, $today)) { ?>
 	<div class="day" id="day_before_yesterday">
-		<?php echo Translate::t ('before_yesterday'); ?>
+		<?php echo Minz_Translate::t ('before_yesterday'); ?>
 		<span class="name"><?php echo $this->currentName; ?></span>
 	</div>
 	<?php $display_others = false; } ?>
 
 	<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 (!login_is_conf ($this->conf) || is_logged ()) { ?>
-				<?php if ($this->conf->toplineRead ()) { ?><li class="item manage"><a class="read" href="<?php echo _url ('entry', 'read', 'id', $item->id (), 'is_read', $item->isRead () ? 0 : 1); ?>">&nbsp;</a></li><?php } ?>
-				<?php if ($this->conf->toplineFavorite ()) { ?><li class="item manage"><a class="bookmark" href="<?php echo _url ('entry', 'bookmark', 'id', $item->id (), 'is_favorite', $item->isFavorite () ? 0 : 1); ?>">&nbsp;</a></li><?php } ?>
-			<?php
+		<ul class="horizontal-list flux_header"><?php
+			if ($logged) {
+				if ($this->conf->toplineRead ()) {
+					?><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 ()) {
+					 ?><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
+					?></li><?php
 				}
-				$feed = HelperCategory::findFeed($this->cat_aside, $item->feed ());	//We most likely already have the feed object in cache
-				if (empty($feed)) $feed = $item->feed (true);
+			}
+			$feed = FreshRSS_CategoryDAO::findFeed($this->cat_aside, $item->feed ());	//We most likely already have the feed object in cache
+			if (empty($feed)) $feed = $item->feed (true);
 			?>
-			<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 htmlspecialchars($feed->name(), ENT_NOQUOTES, 'UTF-8'); ?></span></a></li>
+			<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 (); ?>&nbsp;</li><?php } ?>
-			<?php if ($this->conf->toplineLink ()) { ?><li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>">&nbsp;</a></li><?php } ?>
+			<?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 } ?>
 		</ul>
 
 		<div class="flux_content">
@@ -57,64 +76,99 @@ if (isset ($this->entryPaginator) && !$this->entryPaginator->isEmpty ()) {
 				<h1 class="title"><?php echo $item->title (); ?></h1>
 				<?php
 					$author = $item->author ();
-					echo $author != '' ? '<div class="author">' . Translate::t ('by_author', $author) . '</div>' : '';
-					if($this->conf->lazyload() == 'yes') {
-						echo lazyimg($item->content ());
+					echo $author != '' ? '<div class="author">' . Minz_Translate::t ('by_author', $author) . '</div>' : '';
+					if ($lazyload) {
+						echo $hidePosts ? lazyIframe(lazyimg($item->content())) : lazyimg($item->content());
 					} else {
 						echo $item->content();
 					}
 				?>
 			</div>
-
-			<ul class="horizontal-list bottom">
-				<?php if (!login_is_conf ($this->conf) || is_logged ()) { ?>
-					<?php if ($this->conf->bottomlineRead ()) { ?><li class="item manage"><a class="read" href="<?php echo _url ('entry', 'read', 'id', $item->id (), 'is_read', $item->isRead () ? 0 : 1); ?>">&nbsp;</a></li><?php } ?>
-					<?php if ($this->conf->bottomlineFavorite ()) { ?><li class="item manage"><a class="bookmark" href="<?php echo _url ('entry', 'bookmark', 'id', $item->id (), 'is_favorite', $item->isFavorite () ? 0 : 1); ?>">&nbsp;</a></li><?php } ?>
-				<?php } ?>
+			<ul class="horizontal-list bottom"><?php
+				if ($logged) {
+					if ($this->conf->bottomlineRead ()) {
+						?><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 ()) {
+						?><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
+						?></li><?php
+					}
+				} ?>
 				<li class="item">
 					<?php
-						if ($this->conf->bottomlineSharing ()) {
+						if ($this->conf->bottomlineSharing () && (
+								$shaarli || $poche || $diaspora || $twitter ||
+								$google_plus || $facebook || $email
+							)) {
 							$link = urlencode ($item->link ());
 							$title = urlencode ($item->title () . ' - ' . $feed->name ());
 					?>
-						<div class="dropdown">
+					<div class="dropdown">
 						<div id="dropdown-share-<?php echo $item->id ();?>" class="dropdown-target"></div>
-						<i class="icon i_share"></i> <a class="dropdown-toggle" href="#dropdown-share-<?php echo $item->id ();?>"><?php echo Translate::t ('share'); ?></a>
+						<a class="dropdown-toggle" href="#dropdown-share-<?php echo $item->id ();?>">
+							<?php echo FreshRSS_Themes::icon('share'); ?>
+							<?php echo Minz_Translate::t ('share'); ?>
+						</a>
 
 						<ul class="dropdown-menu">
-							<li class="dropdown-close"><a href="#close">&nbsp;</a></li>
-							<?php
-								$shaarli = $this->conf->urlShaarli ();
-								if ((!login_is_conf ($this->conf) || is_logged ()) && $shaarli) {
-							?>
+							<li class="dropdown-close"><a href="#close">❌</a></li>
+							<?php if ($logged && $shaarli) { ?>
 							<li class="item">
-								<a target="_blank" href="<?php echo $shaarli . '?post=' . $link . '&amp;title=' . $title . '&amp;source=bookmarklet'; ?>">
-									Shaarli
+								<a target="_blank" href="<?php echo $shaarli . '?post=' . $link . '&amp;title=' . $title . '&amp;source=FreshRSS'; ?>">
+									<?php echo Minz_Translate::t ('shaarli'); ?>
 								</a>
 							</li>
-							<?php } ?>
+							<?php } if ($logged && $poche) { ?>
 							<li class="item">
-								<a href="mailto:?subject=<?php echo urldecode($title); ?>&amp;body=<?php echo $link; ?>">
-									<?php echo Translate::t ('by_email'); ?>
+								<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) { ?>
+							<li class="item">
+								<a target="_blank" href="<?php echo $diaspora . '/bookmarklet?url=' . $link . '&amp;title=' . $title; ?>">
+									<?php echo Minz_Translate::t ('diaspora'); ?>
 								</a>
 							</li>
+							<?php } if ($twitter) { ?>
 							<li class="item">
 								<a target="_blank" href="https://twitter.com/share?url=<?php echo $link; ?>&amp;text=<?php echo $title; ?>">
-									Twitter
+									<?php echo Minz_Translate::t ('twitter'); ?>
 								</a>
 							</li>
+							<?php } if ($google_plus) { ?>
+							<li class="item">
+								<a target="_blank" href="https://plus.google.com/share?url=<?php echo $link; ?>">
+									<?php echo Minz_Translate::t ('g+'); ?>
+								</a>
+							</li>
+							<?php } if ($facebook) { ?>
 							<li class="item">
 								<a target="_blank" href="https://www.facebook.com/sharer.php?u=<?php echo $link; ?>&amp;t=<?php echo $title; ?>">
-									Facebook
+									<?php echo Minz_Translate::t ('facebook'); ?>
 								</a>
 							</li>
+							<?php } if ($email) { ?>
 							<li class="item">
-								<a target="_blank" href="https://plus.google.com/share?url=<?php echo $link; ?>">
-									Google+
+								<a href="mailto:?subject=<?php echo urldecode($title); ?>&amp;body=<?php echo $link; ?>">
+									<?php echo Minz_Translate::t ('by_email'); ?>
 								</a>
 							</li>
+							<?php } if ($print) { ?>
+							<li class="item">
+								<a href="#" class="print-article">
+									<?php echo Minz_Translate::t ('print'); ?>
+								</a>
+							</li>
+							<?php } ?>
 						</ul>
-					</div><?php } ?>
+					</div>
+					<?php } ?>
 				</li>
 				<?php
 					$tags = $this->conf->bottomlineTags () ? $item->tags() : null;
@@ -123,9 +177,12 @@ if (isset ($this->entryPaginator) && !$this->entryPaginator->isEmpty ()) {
 				<li class="item">
 					<div class="dropdown">
 						<div id="dropdown-tags-<?php echo $item->id ();?>" class="dropdown-target"></div>
-						<i class="icon i_tag"></i> <a class="dropdown-toggle" href="#dropdown-tags-<?php echo $item->id ();?>"><?php echo Translate::t ('related_tags'); ?></a>
+						<a class="dropdown-toggle" href="#dropdown-tags-<?php echo $item->id ();?>">
+							<?php echo FreshRSS_Themes::icon('tag'); ?>
+							<?php echo Minz_Translate::t ('related_tags'); ?>
+						</a>
 						<ul class="dropdown-menu">
-							<li class="dropdown-close"><a href="#close">&nbsp;</a></li>
+							<li class="dropdown-close"><a href="#close"></a></li>
 							<?php foreach($tags as $tag) { ?>
 							<li class="item"><a href="<?php echo _url ('index', 'index', 'search', urlencode ('#' . $tag)); ?>"><?php echo $tag; ?></a></li>
 							<?php } ?>
@@ -133,20 +190,20 @@ if (isset ($this->entryPaginator) && !$this->entryPaginator->isEmpty ()) {
 					</div>
 				</li>
 				<?php } ?>
-				<?php if ($this->conf->bottomlineDate ()) { ?><li class="item date"><?php echo $item->date (); ?>&nbsp;</li><?php } ?>
-				<?php if ($this->conf->bottomlineLink ()) { ?><li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>">&nbsp;</a></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 } ?>
 			</ul>
 		</div>
 	</div>
 	<?php } ?>
 
-	<?php $this->entryPaginator->render ('pagination.phtml', 'next'); ?>
+	<?php $this->renderHelper('pagination'); ?>
 </div>
 
 <?php $this->partial ('nav_entries'); ?>
 
 <?php } else { ?>
 <div id="stream" class="alert alert-warn normal">
-	<span class="alert-head"><?php echo Translate::t ('no_feed_to_display'); ?></span>
+	<span class="alert-head"><?php echo Minz_Translate::t ('no_feed_to_display'); ?></span>
 </div>
 <?php } ?>

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

@@ -1,33 +1,33 @@
 <?php
 $this->partial ('nav_menu');
 
-if (isset ($this->entryPaginator) && !$this->entryPaginator->isEmpty ()) {
-	$items = $this->entryPaginator->items ();
+if (!empty($this->entries)) {
+	$lazyload = $this->conf->lazyload() === 'yes';
 ?>
 
 <div id="stream" class="reader">
-	<?php foreach ($items as $item) { ?>
+	<?php foreach ($this->entries as $item) { ?>
 
 	<div class="flux<?php echo !$item->isRead () ? ' not_read' : ''; ?><?php echo $item->isFavorite () ? ' favorite' : ''; ?>" id="flux_<?php echo $item->id (); ?>">
 		<div class="flux_content">
 			<div class="content">
 				<?php
-					$feed = HelperCategory::findFeed($this->cat_aside, $item->feed ());	//We most likely already have the feed object in cache
+					$feed = FreshRSS_CategoryDAO::findFeed($this->cat_aside, $item->feed ());	//We most likely already have the feed object in cache
 					if (empty($feed)) $feed = $item->feed (true);
 				?>
 				<a href="<?php echo $item->link (); ?>">
-					<img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> <span><?php echo htmlspecialchars($feed->name(), ENT_NOQUOTES, 'UTF-8'); ?></span>
+					<img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span>
 				</a>
 				<h1 class="title"><?php echo $item->title (); ?></h1>
 
 				<div class="author">
 					<?php $author = $item->author (); ?>
-					<?php echo $author != '' ? Translate::t ('by_author', $author) . ' - ' : ''; ?>
+					<?php echo $author != '' ? Minz_Translate::t ('by_author', $author) . ' - ' : ''; ?>
 					<?php echo $item->date (); ?>
 				</div>
 
 				<?php
-					if($this->conf->lazyload() == 'yes') {
+					if ($lazyload) {
 						echo lazyimg($item->content ());
 					} else {
 						echo $item->content();
@@ -38,11 +38,11 @@ if (isset ($this->entryPaginator) && !$this->entryPaginator->isEmpty ()) {
 	</div>
 	<?php } ?>
 
-	<?php $this->entryPaginator->render ('pagination.phtml', 'next'); ?>
+	<?php $this->renderHelper('pagination'); ?>
 </div>
 
 <?php } else { ?>
 <div id="stream" class="alert alert-warn reader">
-	<span class="alert-head"><?php echo Translate::t ('no_feed_to_display'); ?></span>
+	<span class="alert-head"><?php echo Minz_Translate::t ('no_feed_to_display'); ?></span>
 </div>
 <?php } ?>

+ 5 - 6
app/views/helpers/view/rss_view.phtml

@@ -2,17 +2,16 @@
 <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
 	<channel>
 		<title><?php echo $this->rss_title; ?></title>
-		<link><?php echo Url::display(null, 'html', true); ?></link>
-		<description><?php echo Translate::t ('rss_feeds_of', $this->rss_title); ?></description>
+		<link><?php echo Minz_Url::display(null, 'html', true); ?></link>
+		<description><?php echo Minz_Translate::t ('rss_feeds_of', $this->rss_title); ?></description>
 		<pubDate><?php echo date('D, d M Y H:i:s O'); ?></pubDate>
 		<lastBuildDate><?php echo gmdate('D, d M Y H:i:s'); ?> GMT</lastBuildDate>
-		<atom:link href="<?php echo Url::display ($this->rss_url, 'html', true); ?>" rel="self" type="application/rss+xml" />
+		<atom:link href="<?php echo Minz_Url::display ($this->rss_url, 'html', true); ?>" rel="self" type="application/rss+xml" />
 <?php
-$items = $this->entryPaginator->items ();
-foreach ($items as $item) {
+foreach ($this->entries as $item) {
 ?>
 		<item>
-			<title><?php echo htmlspecialchars(html_entity_decode($item->title (), ENT_NOQUOTES, 'UTF-8'), ENT_NOQUOTES, 'UTF-8'); ?></title>
+			<title><?php echo $item->title (); ?></title>
 			<link><?php echo $item->link (); ?></link>
 			<?php $author = $item->author (); ?>
 			<?php if ($author != '') { ?>

+ 13 - 13
app/views/index/about.phtml

@@ -1,24 +1,24 @@
 <div class="post content">
-	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a>
+	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
 
-	<h1><?php echo Translate::t ('about_freshrss'); ?></h1>
+	<h1><?php echo Minz_Translate::t ('about_freshrss'); ?></h1>
 
 	<dl class="infos">
-		<dt><?php echo Translate::t ('project_website'); ?></dt>
-		<dd><a href="http://marienfressinaud.github.io/FreshRSS/">http://marienfressinaud.github.io/FreshRSS/</a></dd>
+		<dt><?php echo Minz_Translate::t ('project_website'); ?></dt>
+		<dd><a href="<?php echo FRESHRSS_WEBSITE; ?>"><?php echo FRESHRSS_WEBSITE; ?></a></dd>
 
-		<dt><?php echo Translate::t ('lead_developer'); ?></dt>
-		<dd><a href="mailto:contact@marienfressinaud.fr">Marien Fressinaud</a> - <a href="http://marienfressinaud.fr"><?php echo Translate::t ('website'); ?></a></dd>
+		<dt><?php echo Minz_Translate::t ('lead_developer'); ?></dt>
+		<dd><a href="mailto:contact@marienfressinaud.fr">Marien Fressinaud</a> - <a href="http://marienfressinaud.fr"><?php echo Minz_Translate::t ('website'); ?></a></dd>
 
-		<dt><?php echo Translate::t ('bugs_reports'); ?></dt>
-		<dd><?php echo Translate::t ('github_or_email'); ?></dd>
+		<dt><?php echo Minz_Translate::t ('bugs_reports'); ?></dt>
+		<dd><?php echo Minz_Translate::t ('github_or_email'); ?></dd>
 
-		<dt><?php echo Translate::t ('license'); ?></dt>
-		<dd><?php echo Translate::t ('agpl3'); ?></dd>
+		<dt><?php echo Minz_Translate::t ('license'); ?></dt>
+		<dd><?php echo Minz_Translate::t ('agpl3'); ?></dd>
 	</dl>
 
-	<p><?php echo Translate::t ('freshrss_description'); ?></p>
+	<p><?php echo Minz_Translate::t ('freshrss_description'); ?></p>
 
-	<h1><?php echo Translate::t ('credits'); ?></h1>
-	<p><?php echo Translate::t ('credits_content'); ?></p>
+	<h1><?php echo Minz_Translate::t ('credits'); ?></h1>
+	<p><?php echo Minz_Translate::t ('credits_content'); ?></p>
 </div>

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

@@ -1,8 +1,8 @@
 <?php
 
-$output = Request::param ('output', 'normal');
+$output = Minz_Request::param ('output', 'normal');
 $token = $this->conf->token();
-$token_param = Request::param ('token', '');
+$token_param = Minz_Request::param ('token', '');
 $token_is_ok = ($token != '' && $token == $token_param);
 
 if(!login_is_conf ($this->conf) ||
@@ -21,9 +21,9 @@ if(!login_is_conf ($this->conf) ||
 } else {
 ?>
 <div class="post content">
-	<h1><?php echo Translate::t ('forbidden_access'); ?></h1>
-	<p><?php echo Translate::t ('forbidden_access_description'); ?></p>
-	<p><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Translate::t ('about_freshrss'); ?></a></p>
+	<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
 }

+ 10 - 6
app/views/index/logs.phtml

@@ -1,21 +1,25 @@
 <div class="post content">
-	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Translate::t ('back_to_rss_feeds'); ?></a>
+	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
 
-	<h1><?php echo Translate::t ('logs'); ?></h1>
+	<h1><?php echo Minz_Translate::t ('logs'); ?></h1>
+	<form method="post" action="<?php echo _url ('index', 'logs'); ?>"><p>
+		<input type="hidden" name="clearLogs" />
+		<button type="submit" class="btn"><?php echo Minz_Translate::t ('clear_logs'); ?></button>
+	</p></form>
 
 	<?php $items = $this->logsPaginator->items (); ?>
 
 	<?php if (!empty ($items)) { ?>
 	<div class="logs">
 		<?php $this->logsPaginator->render ('logs_pagination.phtml', 'page'); ?>
-		
+
 		<?php foreach ($items as $log) { ?>
-		<div class="log <?php echo $log->level (); ?>"><span class="date"><?php echo date ('d/m/Y - H:i:s', strtotime ($log->date ())); ?></span><?php echo htmlspecialchars ($log->info (), ENT_NOQUOTES, 'UTF-8'); ?></div>
+		<div class="log <?php echo $log->level (); ?>"><span class="date"><?php echo @date ('Y-m-d H:i:s', @strtotime ($log->date ())); ?></span><?php echo htmlspecialchars ($log->info (), ENT_NOQUOTES, 'UTF-8'); ?></div>
 		<?php } ?>
-		
+
 		<?php $this->logsPaginator->render ('logs_pagination.phtml','page'); ?>
 	</div>
 	<?php } else { ?>
-	<p class="alert alert-warn"><?php echo Translate::t ('logs_empty'); ?></p>
+	<p class="alert alert-warn"><?php echo Minz_Translate::t ('logs_empty'); ?></p>
 	<?php } ?>
 </div>

+ 2 - 2
app/views/javascript/actualize.phtml

@@ -1,12 +1,12 @@
 var feeds = new Array ();
 <?php foreach ($this->feeds as $feed) { ?>
-feeds.push ("<?php echo 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) {
 	if (init) {
 		$("body").after ("\<div id=\"actualizeProgress\" class=\"actualizeProgress\">\
-			<?php echo Translate::t ('refresh'); ?> <span class=\"progress\">0 / " + feeds.length + "</span><br />\
+			<?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 {

+ 0 - 1
cache/.gitignore

@@ -1 +0,0 @@
-*

+ 13 - 0
constants.php

@@ -0,0 +1,13 @@
+<?php
+define('FRESHRSS_VERSION', '0.7-dev');
+define('FRESHRSS_WEBSITE', 'http://marienfressinaud.github.io/FreshRSS/');
+
+// Constantes de chemins
+define ('FRESHRSS_PATH', realpath (dirname (__FILE__)));
+define ('PUBLIC_PATH', FRESHRSS_PATH . '/public');
+define ('DATA_PATH', FRESHRSS_PATH . '/data');
+define ('LIB_PATH', FRESHRSS_PATH . '/lib');
+define ('APP_PATH', FRESHRSS_PATH . '/app');
+
+define ('LOG_PATH', DATA_PATH . '/log');
+define ('CACHE_PATH', DATA_PATH . '/cache');

+ 6 - 0
data/.gitignore

@@ -0,0 +1,6 @@
+application.ini
+config.php
+*_user.php
+*.sqlite
+touch.txt
+no-cache.txt

+ 3 - 0
data/.htaccess

@@ -0,0 +1,3 @@
+Order	Allow,Deny
+Deny	from all
+Satisfy	all

+ 1 - 0
data/cache/.gitignore

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

+ 2 - 0
data/favicons/.gitignore

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

+ 13 - 0
data/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,follow" />
+</head>
+
+<body>
+<p><a href="/">Redirection</a></p>
+</body>
+</html>

+ 1 - 0
data/log/.gitignore

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

+ 13 - 0
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=public/" />
+<title>Redirection</title>
+<meta name="robots" content="noindex,follow" />
+</head>
+
+<body>
+<p><a href="./public/">FreshRSS</a></p>
+</body>
+</html>

+ 0 - 2
index.php

@@ -1,2 +0,0 @@
-<?php
-header('Location: public/');

+ 933 - 0
lib/JSON.php

@@ -0,0 +1,933 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+/**
+ * Converts to and from JSON format.
+ *
+ * JSON (JavaScript Object Notation) is a lightweight data-interchange
+ * format. It is easy for humans to read and write. It is easy for machines
+ * to parse and generate. It is based on a subset of the JavaScript
+ * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
+ * This feature can also be found in  Python. JSON is a text format that is
+ * completely language independent but uses conventions that are familiar
+ * to programmers of the C-family of languages, including C, C++, C#, Java,
+ * JavaScript, Perl, TCL, and many others. These properties make JSON an
+ * ideal data-interchange language.
+ *
+ * This package provides a simple encoder and decoder for JSON notation. It
+ * is intended for use with client-side Javascript applications that make
+ * use of HTTPRequest to perform server communication functions - data can
+ * be encoded into JSON notation for use in a client-side javascript, or
+ * decoded from incoming Javascript requests. JSON format is native to
+ * Javascript, and can be directly eval()'ed with no further parsing
+ * overhead
+ *
+ * All strings should be in ASCII or UTF-8 format!
+ *
+ * LICENSE: Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met: Redistributions of source code must retain the
+ * above copyright notice, this list of conditions and the following
+ * disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * @category
+ * @package     Services_JSON
+ * @author      Michal Migurski <mike-json@teczno.com>
+ * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
+ * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
+ * @copyright   2005 Michal Migurski
+ * @version     CVS: $Id: JSON.php 305040 2010-11-02 23:19:03Z alan_k $
+ * @license     http://www.opensource.org/licenses/bsd-license.php
+ * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
+ */
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_SLICE',   1);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_STR',  2);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_ARR',  3);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_OBJ',  4);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_CMT', 5);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_LOOSE_TYPE', 16);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_USE_TO_JSON', 64);
+
+/**
+ * Converts to and from JSON format.
+ *
+ * Brief example of use:
+ *
+ * <code>
+ * // create a new instance of Services_JSON
+ * $json = new Services_JSON();
+ *
+ * // convert a complexe value to JSON notation, and send it to the browser
+ * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
+ * $output = $json->encode($value);
+ *
+ * print($output);
+ * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
+ *
+ * // accept incoming POST data, assumed to be in JSON notation
+ * $input = file_get_contents('php://input', 1000000);
+ * $value = $json->decode($input);
+ * </code>
+ */
+class Services_JSON
+{
+   /**
+    * constructs a new JSON instance
+    *
+    * @param    int     $use    object behavior flags; combine with boolean-OR
+    *
+    *                           possible values:
+    *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
+    *                                   "{...}" syntax creates associative arrays
+    *                                   instead of objects in decode().
+    *                           - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
+    *                                   Values which can't be encoded (e.g. resources)
+    *                                   appear as NULL instead of throwing errors.
+    *                                   By default, a deeply-nested resource will
+    *                                   bubble up with an error, so all return values
+    *                                   from encode() should be checked with isError()
+    *                           - SERVICES_JSON_USE_TO_JSON:  call toJSON when serializing objects
+    *                                   It serializes the return value from the toJSON call rather 
+    *                                   than the object it'self,  toJSON can return associative arrays, 
+    *                                   strings or numbers, if you return an object, make sure it does
+    *                                   not have a toJSON method, otherwise an error will occur.
+    */
+    function Services_JSON($use = 0)
+    {
+        $this->use = $use;
+        $this->_mb_strlen            = function_exists('mb_strlen');
+        $this->_mb_convert_encoding  = function_exists('mb_convert_encoding');
+        $this->_mb_substr            = function_exists('mb_substr');
+    }
+    // private - cache the mbstring lookup results..
+    var $_mb_strlen = false;
+    var $_mb_substr = false;
+    var $_mb_convert_encoding = false;
+    
+   /**
+    * convert a string from one UTF-16 char to one UTF-8 char
+    *
+    * Normally should be handled by mb_convert_encoding, but
+    * provides a slower PHP-only method for installations
+    * that lack the multibye string extension.
+    *
+    * @param    string  $utf16  UTF-16 character
+    * @return   string  UTF-8 character
+    * @access   private
+    */
+    function utf162utf8($utf16)
+    {
+        // oh please oh please oh please oh please oh please
+        if($this->_mb_convert_encoding) {
+            return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
+        }
+
+        $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
+
+        switch(true) {
+            case ((0x7F & $bytes) == $bytes):
+                // this case should never be reached, because we are in ASCII range
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0x7F & $bytes);
+
+            case (0x07FF & $bytes) == $bytes:
+                // return a 2-byte UTF-8 character
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0xC0 | (($bytes >> 6) & 0x1F))
+                     . chr(0x80 | ($bytes & 0x3F));
+
+            case (0xFFFF & $bytes) == $bytes:
+                // return a 3-byte UTF-8 character
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0xE0 | (($bytes >> 12) & 0x0F))
+                     . chr(0x80 | (($bytes >> 6) & 0x3F))
+                     . chr(0x80 | ($bytes & 0x3F));
+        }
+
+        // ignoring UTF-32 for now, sorry
+        return '';
+    }
+
+   /**
+    * convert a string from one UTF-8 char to one UTF-16 char
+    *
+    * Normally should be handled by mb_convert_encoding, but
+    * provides a slower PHP-only method for installations
+    * that lack the multibye string extension.
+    *
+    * @param    string  $utf8   UTF-8 character
+    * @return   string  UTF-16 character
+    * @access   private
+    */
+    function utf82utf16($utf8)
+    {
+        // oh please oh please oh please oh please oh please
+        if($this->_mb_convert_encoding) {
+            return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
+        }
+
+        switch($this->strlen8($utf8)) {
+            case 1:
+                // this case should never be reached, because we are in ASCII range
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return $utf8;
+
+            case 2:
+                // return a UTF-16 character from a 2-byte UTF-8 char
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0x07 & (ord($utf8{0}) >> 2))
+                     . chr((0xC0 & (ord($utf8{0}) << 6))
+                         | (0x3F & ord($utf8{1})));
+
+            case 3:
+                // return a UTF-16 character from a 3-byte UTF-8 char
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr((0xF0 & (ord($utf8{0}) << 4))
+                         | (0x0F & (ord($utf8{1}) >> 2)))
+                     . chr((0xC0 & (ord($utf8{1}) << 6))
+                         | (0x7F & ord($utf8{2})));
+        }
+
+        // ignoring UTF-32 for now, sorry
+        return '';
+    }
+
+   /**
+    * encodes an arbitrary variable into JSON format (and sends JSON Header)
+    *
+    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
+    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
+    *                           if var is a strng, note that encode() always expects it
+    *                           to be in ASCII or UTF-8 format!
+    *
+    * @return   mixed   JSON string representation of input var or an error if a problem occurs
+    * @access   public
+    */
+    function encode($var)
+    {
+        header('Content-type: application/json');
+        return $this->encodeUnsafe($var);
+    }
+    /**
+    * encodes an arbitrary variable into JSON format without JSON Header - warning - may allow XSS!!!!)
+    *
+    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
+    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
+    *                           if var is a strng, note that encode() always expects it
+    *                           to be in ASCII or UTF-8 format!
+    *
+    * @return   mixed   JSON string representation of input var or an error if a problem occurs
+    * @access   public
+    */
+    function encodeUnsafe($var)
+    {
+        // see bug #16908 - regarding numeric locale printing
+        $lc = setlocale(LC_NUMERIC, 0);
+        setlocale(LC_NUMERIC, 'C');
+        $ret = $this->_encode($var);
+        setlocale(LC_NUMERIC, $lc);
+        return $ret;
+        
+    }
+    /**
+    * PRIVATE CODE that does the work of encodes an arbitrary variable into JSON format 
+    *
+    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
+    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
+    *                           if var is a strng, note that encode() always expects it
+    *                           to be in ASCII or UTF-8 format!
+    *
+    * @return   mixed   JSON string representation of input var or an error if a problem occurs
+    * @access   public
+    */
+    function _encode($var) 
+    {
+         
+        switch (gettype($var)) {
+            case 'boolean':
+                return $var ? 'true' : 'false';
+
+            case 'NULL':
+                return 'null';
+
+            case 'integer':
+                return (int) $var;
+
+            case 'double':
+            case 'float':
+                return  (float) $var;
+
+            case 'string':
+                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
+                $ascii = '';
+                $strlen_var = $this->strlen8($var);
+
+               /*
+                * Iterate over every character in the string,
+                * escaping with a slash or encoding to UTF-8 where necessary
+                */
+                for ($c = 0; $c < $strlen_var; ++$c) {
+
+                    $ord_var_c = ord($var{$c});
+
+                    switch (true) {
+                        case $ord_var_c == 0x08:
+                            $ascii .= '\b';
+                            break;
+                        case $ord_var_c == 0x09:
+                            $ascii .= '\t';
+                            break;
+                        case $ord_var_c == 0x0A:
+                            $ascii .= '\n';
+                            break;
+                        case $ord_var_c == 0x0C:
+                            $ascii .= '\f';
+                            break;
+                        case $ord_var_c == 0x0D:
+                            $ascii .= '\r';
+                            break;
+
+                        case $ord_var_c == 0x22:
+                        case $ord_var_c == 0x2F:
+                        case $ord_var_c == 0x5C:
+                            // double quote, slash, slosh
+                            $ascii .= '\\'.$var{$c};
+                            break;
+
+                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
+                            // characters U-00000000 - U-0000007F (same as ASCII)
+                            $ascii .= $var{$c};
+                            break;
+
+                        case (($ord_var_c & 0xE0) == 0xC0):
+                            // characters U-00000080 - U-000007FF, mask 110XXXXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            if ($c+1 >= $strlen_var) {
+                                $c += 1;
+                                $ascii .= '?';
+                                break;
+                            }
+                            
+                            $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
+                            $c += 1;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xF0) == 0xE0):
+                            if ($c+2 >= $strlen_var) {
+                                $c += 2;
+                                $ascii .= '?';
+                                break;
+                            }
+                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         @ord($var{$c + 1}),
+                                         @ord($var{$c + 2}));
+                            $c += 2;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xF8) == 0xF0):
+                            if ($c+3 >= $strlen_var) {
+                                $c += 3;
+                                $ascii .= '?';
+                                break;
+                            }
+                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}));
+                            $c += 3;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xFC) == 0xF8):
+                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            if ($c+4 >= $strlen_var) {
+                                $c += 4;
+                                $ascii .= '?';
+                                break;
+                            }
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}),
+                                         ord($var{$c + 4}));
+                            $c += 4;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xFE) == 0xFC):
+                        if ($c+5 >= $strlen_var) {
+                                $c += 5;
+                                $ascii .= '?';
+                                break;
+                            }
+                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}),
+                                         ord($var{$c + 4}),
+                                         ord($var{$c + 5}));
+                            $c += 5;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+                    }
+                }
+                return  '"'.$ascii.'"';
+
+            case 'array':
+               /*
+                * As per JSON spec if any array key is not an integer
+                * we must treat the the whole array as an object. We
+                * also try to catch a sparsely populated associative
+                * array with numeric keys here because some JS engines
+                * will create an array with empty indexes up to
+                * max_index which can cause memory issues and because
+                * the keys, which may be relevant, will be remapped
+                * otherwise.
+                *
+                * As per the ECMA and JSON specification an object may
+                * have any string as a property. Unfortunately due to
+                * a hole in the ECMA specification if the key is a
+                * ECMA reserved word or starts with a digit the
+                * parameter is only accessible using ECMAScript's
+                * bracket notation.
+                */
+
+                // treat as a JSON object
+                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
+                    $properties = array_map(array($this, 'name_value'),
+                                            array_keys($var),
+                                            array_values($var));
+
+                    foreach($properties as $property) {
+                        if(Services_JSON::isError($property)) {
+                            return $property;
+                        }
+                    }
+
+                    return '{' . join(',', $properties) . '}';
+                }
+
+                // treat it like a regular array
+                $elements = array_map(array($this, '_encode'), $var);
+
+                foreach($elements as $element) {
+                    if(Services_JSON::isError($element)) {
+                        return $element;
+                    }
+                }
+
+                return '[' . join(',', $elements) . ']';
+
+            case 'object':
+            
+                // support toJSON methods.
+                if (($this->use & SERVICES_JSON_USE_TO_JSON) && method_exists($var, 'toJSON')) {
+                    // this may end up allowing unlimited recursion
+                    // so we check the return value to make sure it's not got the same method.
+                    $recode = $var->toJSON();
+                    
+                    if (method_exists($recode, 'toJSON')) {
+                        
+                        return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
+                        ? 'null'
+                        : new Services_JSON_Error(class_name($var).
+                            " toJSON returned an object with a toJSON method.");
+                            
+                    }
+                    
+                    return $this->_encode( $recode );
+                } 
+                
+                $vars = get_object_vars($var);
+                
+                $properties = array_map(array($this, 'name_value'),
+                                        array_keys($vars),
+                                        array_values($vars));
+
+                foreach($properties as $property) {
+                    if(Services_JSON::isError($property)) {
+                        return $property;
+                    }
+                }
+
+                return '{' . join(',', $properties) . '}';
+
+            default:
+                return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
+                    ? 'null'
+                    : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
+        }
+    }
+
+   /**
+    * array-walking function for use in generating JSON-formatted name-value pairs
+    *
+    * @param    string  $name   name of key to use
+    * @param    mixed   $value  reference to an array element to be encoded
+    *
+    * @return   string  JSON-formatted name-value pair, like '"name":value'
+    * @access   private
+    */
+    function name_value($name, $value)
+    {
+        $encoded_value = $this->_encode($value);
+
+        if(Services_JSON::isError($encoded_value)) {
+            return $encoded_value;
+        }
+
+        return $this->_encode(strval($name)) . ':' . $encoded_value;
+    }
+
+   /**
+    * reduce a string by removing leading and trailing comments and whitespace
+    *
+    * @param    $str    string      string value to strip of comments and whitespace
+    *
+    * @return   string  string value stripped of comments and whitespace
+    * @access   private
+    */
+    function reduce_string($str)
+    {
+        $str = preg_replace(array(
+
+                // eliminate single line comments in '// ...' form
+                '#^\s*//(.+)$#m',
+
+                // eliminate multi-line comments in '/* ... */' form, at start of string
+                '#^\s*/\*(.+)\*/#Us',
+
+                // eliminate multi-line comments in '/* ... */' form, at end of string
+                '#/\*(.+)\*/\s*$#Us'
+
+            ), '', $str);
+
+        // eliminate extraneous space
+        return trim($str);
+    }
+
+   /**
+    * decodes a JSON string into appropriate variable
+    *
+    * @param    string  $str    JSON-formatted string
+    *
+    * @return   mixed   number, boolean, string, array, or object
+    *                   corresponding to given JSON input string.
+    *                   See argument 1 to Services_JSON() above for object-output behavior.
+    *                   Note that decode() always returns strings
+    *                   in ASCII or UTF-8 format!
+    * @access   public
+    */
+    function decode($str)
+    {
+        $str = $this->reduce_string($str);
+
+        switch (strtolower($str)) {
+            case 'true':
+                return true;
+
+            case 'false':
+                return false;
+
+            case 'null':
+                return null;
+
+            default:
+                $m = array();
+
+                if (is_numeric($str)) {
+                    // Lookie-loo, it's a number
+
+                    // This would work on its own, but I'm trying to be
+                    // good about returning integers where appropriate:
+                    // return (float)$str;
+
+                    // Return float or int, as appropriate
+                    return ((float)$str == (integer)$str)
+                        ? (integer)$str
+                        : (float)$str;
+
+                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
+                    // STRINGS RETURNED IN UTF-8 FORMAT
+                    $delim = $this->substr8($str, 0, 1);
+                    $chrs = $this->substr8($str, 1, -1);
+                    $utf8 = '';
+                    $strlen_chrs = $this->strlen8($chrs);
+
+                    for ($c = 0; $c < $strlen_chrs; ++$c) {
+
+                        $substr_chrs_c_2 = $this->substr8($chrs, $c, 2);
+                        $ord_chrs_c = ord($chrs{$c});
+
+                        switch (true) {
+                            case $substr_chrs_c_2 == '\b':
+                                $utf8 .= chr(0x08);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\t':
+                                $utf8 .= chr(0x09);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\n':
+                                $utf8 .= chr(0x0A);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\f':
+                                $utf8 .= chr(0x0C);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\r':
+                                $utf8 .= chr(0x0D);
+                                ++$c;
+                                break;
+
+                            case $substr_chrs_c_2 == '\\"':
+                            case $substr_chrs_c_2 == '\\\'':
+                            case $substr_chrs_c_2 == '\\\\':
+                            case $substr_chrs_c_2 == '\\/':
+                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
+                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
+                                    $utf8 .= $chrs{++$c};
+                                }
+                                break;
+
+                            case preg_match('/\\\u[0-9A-F]{4}/i', $this->substr8($chrs, $c, 6)):
+                                // single, escaped unicode character
+                                $utf16 = chr(hexdec($this->substr8($chrs, ($c + 2), 2)))
+                                       . chr(hexdec($this->substr8($chrs, ($c + 4), 2)));
+                                $utf8 .= $this->utf162utf8($utf16);
+                                $c += 5;
+                                break;
+
+                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
+                                $utf8 .= $chrs{$c};
+                                break;
+
+                            case ($ord_chrs_c & 0xE0) == 0xC0:
+                                // characters U-00000080 - U-000007FF, mask 110XXXXX
+                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= $this->substr8($chrs, $c, 2);
+                                ++$c;
+                                break;
+
+                            case ($ord_chrs_c & 0xF0) == 0xE0:
+                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= $this->substr8($chrs, $c, 3);
+                                $c += 2;
+                                break;
+
+                            case ($ord_chrs_c & 0xF8) == 0xF0:
+                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= $this->substr8($chrs, $c, 4);
+                                $c += 3;
+                                break;
+
+                            case ($ord_chrs_c & 0xFC) == 0xF8:
+                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= $this->substr8($chrs, $c, 5);
+                                $c += 4;
+                                break;
+
+                            case ($ord_chrs_c & 0xFE) == 0xFC:
+                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= $this->substr8($chrs, $c, 6);
+                                $c += 5;
+                                break;
+
+                        }
+
+                    }
+
+                    return $utf8;
+
+                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
+                    // array, or object notation
+
+                    if ($str{0} == '[') {
+                        $stk = array(SERVICES_JSON_IN_ARR);
+                        $arr = array();
+                    } else {
+                        if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                            $stk = array(SERVICES_JSON_IN_OBJ);
+                            $obj = array();
+                        } else {
+                            $stk = array(SERVICES_JSON_IN_OBJ);
+                            $obj = new stdClass();
+                        }
+                    }
+
+                    array_push($stk, array('what'  => SERVICES_JSON_SLICE,
+                                           'where' => 0,
+                                           'delim' => false));
+
+                    $chrs = $this->substr8($str, 1, -1);
+                    $chrs = $this->reduce_string($chrs);
+
+                    if ($chrs == '') {
+                        if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                            return $arr;
+
+                        } else {
+                            return $obj;
+
+                        }
+                    }
+
+                    //print("\nparsing {$chrs}\n");
+
+                    $strlen_chrs = $this->strlen8($chrs);
+
+                    for ($c = 0; $c <= $strlen_chrs; ++$c) {
+
+                        $top = end($stk);
+                        $substr_chrs_c_2 = $this->substr8($chrs, $c, 2);
+
+                        if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
+                            // found a comma that is not inside a string, array, etc.,
+                            // OR we've reached the end of the character list
+                            $slice = $this->substr8($chrs, $top['where'], ($c - $top['where']));
+                            array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
+                            //print("Found split at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                            if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                                // we are in an array, so just push an element onto the stack
+                                array_push($arr, $this->decode($slice));
+
+                            } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+                                // we are in an object, so figure
+                                // out the property name and set an
+                                // element in an associative array,
+                                // for now
+                                $parts = array();
+                                
+                               if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:/Uis', $slice, $parts)) {
+ 	                              // "name":value pair
+                                    $key = $this->decode($parts[1]);
+                                    $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B"));
+                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                                        $obj[$key] = $val;
+                                    } else {
+                                        $obj->$key = $val;
+                                    }
+                                } elseif (preg_match('/^\s*(\w+)\s*:/Uis', $slice, $parts)) {
+                                    // name:value pair, where name is unquoted
+                                    $key = $parts[1];
+                                    $val = $this->decode(trim(substr($slice, strlen($parts[0])), ", \t\n\r\0\x0B"));
+
+                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                                        $obj[$key] = $val;
+                                    } else {
+                                        $obj->$key = $val;
+                                    }
+                                }
+
+                            }
+
+                        } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
+                            // found a quote, and we are not inside a string
+                            array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
+                            //print("Found start of string at {$c}\n");
+
+                        } elseif (($chrs{$c} == $top['delim']) &&
+                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
+                                 (($this->strlen8($this->substr8($chrs, 0, $c)) - $this->strlen8(rtrim($this->substr8($chrs, 0, $c), '\\'))) % 2 != 1)) {
+                            // found a quote, we're in a string, and it's not escaped
+                            // we know that it's not escaped becase there is _not_ an
+                            // odd number of backslashes at the end of the string so far
+                            array_pop($stk);
+                            //print("Found end of string at {$c}: ".$this->substr8($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
+
+                        } elseif (($chrs{$c} == '[') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a left-bracket, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
+                            //print("Found start of array at {$c}\n");
+
+                        } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
+                            // found a right-bracket, and we're in an array
+                            array_pop($stk);
+                            //print("Found end of array at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        } elseif (($chrs{$c} == '{') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a left-brace, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
+                            //print("Found start of object at {$c}\n");
+
+                        } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
+                            // found a right-brace, and we're in an object
+                            array_pop($stk);
+                            //print("Found end of object at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        } elseif (($substr_chrs_c_2 == '/*') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a comment start, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
+                            $c++;
+                            //print("Found start of comment at {$c}\n");
+
+                        } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
+                            // found a comment end, and we're in one now
+                            array_pop($stk);
+                            $c++;
+
+                            for ($i = $top['where']; $i <= $c; ++$i)
+                                $chrs = substr_replace($chrs, ' ', $i, 1);
+
+                            //print("Found end of comment at {$c}: ".$this->substr8($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        }
+
+                    }
+
+                    if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                        return $arr;
+
+                    } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+                        return $obj;
+
+                    }
+
+                }
+        }
+    }
+
+    /**
+     * @todo Ultimately, this should just call PEAR::isError()
+     */
+    function isError($data, $code = null)
+    {
+        if (class_exists('pear')) {
+            return PEAR::isError($data, $code);
+        } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
+                                 is_subclass_of($data, 'services_json_error'))) {
+            return true;
+        }
+
+        return false;
+    }
+    
+    /**
+    * Calculates length of string in bytes
+    * @param string 
+    * @return integer length
+    */
+    function strlen8( $str ) 
+    {
+        if ( $this->_mb_strlen ) {
+            return mb_strlen( $str, "8bit" );
+        }
+        return strlen( $str );
+    }
+    
+    /**
+    * Returns part of a string, interpreting $start and $length as number of bytes.
+    * @param string 
+    * @param integer start 
+    * @param integer length 
+    * @return integer length
+    */
+    function substr8( $string, $start, $length=false ) 
+    {
+        if ( $length === false ) {
+            $length = $this->strlen8( $string ) - $start;
+        }
+        if ( $this->_mb_substr ) {
+            return mb_substr( $string, $start, $length, "8bit" );
+        }
+        return substr( $string, $start, $length );
+    }
+
+}
+
+if (class_exists('PEAR_Error')) {
+
+    class Services_JSON_Error extends PEAR_Error
+    {
+        function Services_JSON_Error($message = 'unknown error', $code = null,
+                                     $mode = null, $options = null, $userinfo = null)
+        {
+            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
+        }
+    }
+
+} else {
+
+    /**
+     * @todo Ultimately, this class shall be descended from PEAR_Error
+     */
+    class Services_JSON_Error
+    {
+        function Services_JSON_Error($message = 'unknown error', $code = null,
+                                     $mode = null, $options = null, $userinfo = null)
+        {
+
+        }
+    }
+    
+}

+ 2 - 2
lib/minz/ActionController.php → lib/Minz/ActionController.php

@@ -7,7 +7,7 @@
 /**
  * La classe ActionController représente le contrôleur de l'application
  */
-class ActionController {
+class Minz_ActionController {
 	protected $router;
 	protected $view;
 
@@ -18,7 +18,7 @@ class ActionController {
 	 */
 	public function __construct ($router) {
 		$this->router = $router;
-		$this->view = new View ();
+		$this->view = new Minz_View ();
 		$this->view->attributeParams ();
 	}
 

+ 9 - 0
lib/Minz/ActionException.php

@@ -0,0 +1,9 @@
+<?php
+class Minz_ActionException extends Minz_Exception {
+	public function __construct ($controller_name, $action_name, $code = self::ERROR) {
+		$message = '`' . $action_name . '` cannot be invoked on `'
+		         . $controller_name . '`';
+		
+		parent::__construct ($message, $code);
+	}
+}

+ 9 - 0
lib/Minz/BadConfigurationException.php

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

+ 3 - 3
lib/minz/Minz_Cache.php → lib/Minz/Cache.php

@@ -35,7 +35,7 @@ class Minz_Cache {
 	 * Setteurs
 	 */
 	public function _fileName () {
-		$file = md5 (Request::getURI ());
+		$file = md5 (Minz_Request::getURI ());
 
 		$this->file = CACHE_PATH . '/'.$file;
 	}
@@ -43,7 +43,7 @@ class Minz_Cache {
 	public function _expire () {
 		if ($this->exist ()) {
 			$this->expire = filemtime ($this->file)
-			              + Configuration::delayCache ();
+			              + Minz_Configuration::delayCache ();
 		}
 	}
 
@@ -52,7 +52,7 @@ class Minz_Cache {
 	 * @return true si activé, false sinon
 	 */
 	public static function isEnabled () {
-		return Configuration::cacheEnabled () && self::$enabled;
+		return Minz_Configuration::cacheEnabled () && self::$enabled;
 	}
 
 	/**

+ 48 - 40
lib/minz/Configuration.php → lib/Minz/Configuration.php

@@ -7,8 +7,8 @@
 /**
  * La classe Configuration permet de gérer la configuration de l'application
  */
-class Configuration {
-	const CONF_PATH_NAME = '/configuration/application.ini';
+class Minz_Configuration {
+	const CONF_PATH_NAME = '/config.php';
 
 	/**
 	 * VERSION est la version actuelle de MINZ
@@ -43,13 +43,15 @@ class Configuration {
 	 *     - base le nom de la base de données
 	 */
 	private static $sel_application = '';
-	private static $environment = Configuration::PRODUCTION;
+	private static $environment = Minz_Configuration::PRODUCTION;
 	private static $base_url = '';
 	private static $use_url_rewriting = false;
 	private static $title = '';
 	private static $language = 'en';
 	private static $cache_enabled = false;
 	private static $delay_cache = 3600;
+	private static $default_user = '';
+	private static $current_user = '';
 
 	private static $db = array (
 		'host' => false,
@@ -74,7 +76,7 @@ class Configuration {
 		return self::$use_url_rewriting;
 	}
 	public static function title () {
-		return self::$title;
+		return stripslashes(self::$title);
 	}
 	public static function language () {
 		return self::$language;
@@ -88,63 +90,65 @@ class Configuration {
 	public static function dataBase () {
 		return self::$db;
 	}
+	public static function defaultUser () {
+		return self::$default_user;
+	}
+	public static function currentUser () {
+		return self::$current_user;
+	}
 
 	/**
 	 * Initialise les variables de configuration
-	 * @exception FileNotExistException si le CONF_PATH_NAME n'existe pas
-	 * @exception BadConfigurationException si CONF_PATH_NAME mal formaté
+	 * @exception Minz_FileNotExistException si le CONF_PATH_NAME n'existe pas
+	 * @exception Minz_BadConfigurationException si CONF_PATH_NAME mal formaté
 	 */
 	public static function init () {
 		try {
 			self::parseFile ();
 			self::setReporting ();
-		} catch (FileNotExistException $e) {
+		} catch (Minz_FileNotExistException $e) {
 			throw $e;
-		} catch (BadConfigurationException $e) {
+		} catch (Minz_BadConfigurationException $e) {
 			throw $e;
 		}
 	}
 
 	/**
 	 * Parse un fichier de configuration de type ".ini"
-	 * @exception FileNotExistException si le CONF_PATH_NAME n'existe pas
-	 * @exception BadConfigurationException si CONF_PATH_NAME mal formaté
+	 * @exception Minz_FileNotExistException si le CONF_PATH_NAME n'existe pas
+	 * @exception Minz_BadConfigurationException si CONF_PATH_NAME mal formaté
 	 */
 	private static function parseFile () {
-		if (!file_exists (APP_PATH . self::CONF_PATH_NAME)) {
-			throw new FileNotExistException (
-				APP_PATH . self::CONF_PATH_NAME,
-				MinzException::ERROR
+		if (!file_exists (DATA_PATH . self::CONF_PATH_NAME)) {
+			throw new Minz_FileNotExistException (
+				DATA_PATH . self::CONF_PATH_NAME,
+				Minz_Exception::ERROR
 			);
 		}
 
-		$ini_array = parse_ini_file (
-			APP_PATH . self::CONF_PATH_NAME,
-			true
-		);
+		$ini_array = include(DATA_PATH . self::CONF_PATH_NAME);
 
 		if (!$ini_array) {
-			throw new PermissionDeniedException (
-				APP_PATH . self::CONF_PATH_NAME,
-				MinzException::ERROR
+			throw new Minz_PermissionDeniedException (
+				DATA_PATH . self::CONF_PATH_NAME,
+				Minz_Exception::ERROR
 			);
 		}
 
 		// [general] est obligatoire
 		if (!isset ($ini_array['general'])) {
-			throw new BadConfigurationException (
+			throw new Minz_BadConfigurationException (
 				'[general]',
-				MinzException::ERROR
+				Minz_Exception::ERROR
 			);
 		}
 		$general = $ini_array['general'];
 
-
 		// sel_application est obligatoire
 		if (!isset ($general['sel_application'])) {
-			throw new BadConfigurationException (
+			throw new Minz_BadConfigurationException (
 				'sel_application',
-				MinzException::ERROR
+				Minz_Exception::ERROR
 			);
 		}
 		self::$sel_application = $general['sel_application'];
@@ -152,18 +156,18 @@ class Configuration {
 		if (isset ($general['environment'])) {
 			switch ($general['environment']) {
 			case 'silent':
-				self::$environment = Configuration::SILENT;
+				self::$environment = Minz_Configuration::SILENT;
 				break;
 			case 'development':
-				self::$environment = Configuration::DEVELOPMENT;
+				self::$environment = Minz_Configuration::DEVELOPMENT;
 				break;
 			case 'production':
-				self::$environment = Configuration::PRODUCTION;
+				self::$environment = Minz_Configuration::PRODUCTION;
 				break;
 			default:
-				throw new BadConfigurationException (
+				throw new Minz_BadConfigurationException (
 					'environment',
-					MinzException::ERROR
+					Minz_Exception::ERROR
 				);
 			}
 
@@ -186,13 +190,17 @@ class Configuration {
 			if (CACHE_PATH === false && self::$cache_enabled) {
 				throw new FileNotExistException (
 					'CACHE_PATH',
-					MinzException::ERROR
+					Minz_Exception::ERROR
 				);
 			}
 		}
 		if (isset ($general['delay_cache'])) {
 			self::$delay_cache = $general['delay_cache'];
 		}
+		if (isset ($general['default_user'])) {
+			self::$default_user = $general['default_user'];
+			self::$current_user = self::$default_user;
+		}
 
 		// Base de données
 		$db = false;
@@ -201,27 +209,27 @@ class Configuration {
 		}
 		if ($db) {
 			if (!isset ($db['host'])) {
-				throw new BadConfigurationException (
+				throw new Minz_BadConfigurationException (
 					'host',
-					MinzException::ERROR
+					Minz_Exception::ERROR
 				);
 			}
 			if (!isset ($db['user'])) {
-				throw new BadConfigurationException (
+				throw new Minz_BadConfigurationException (
 					'user',
-					MinzException::ERROR
+					Minz_Exception::ERROR
 				);
 			}
 			if (!isset ($db['password'])) {
-				throw new BadConfigurationException (
+				throw new Minz_BadConfigurationException (
 					'password',
-					MinzException::ERROR
+					Minz_Exception::ERROR
 				);
 			}
 			if (!isset ($db['base'])) {
-				throw new BadConfigurationException (
+				throw new Minz_BadConfigurationException (
 					'base',
-					MinzException::ERROR
+					Minz_Exception::ERROR
 				);
 			}
 

+ 9 - 0
lib/Minz/ControllerNotActionControllerException.php

@@ -0,0 +1,9 @@
+<?php
+class Minz_ControllerNotActionControllerException extends Minz_Exception {
+	public function __construct ($controller_name, $code = self::ERROR) {
+		$message = 'Controller `' . $controller_name
+		         . '` isn\'t instance of ActionController';
+		
+		parent::__construct ($message, $code);
+	}
+}

+ 9 - 0
lib/Minz/ControllerNotExistException.php

@@ -0,0 +1,9 @@
+<?php
+class Minz_ControllerNotExistException extends Minz_Exception {
+	public function __construct ($controller_name, $code = self::ERROR) {
+		$message = 'Controller `' . $controller_name
+		         . '` doesn\'t exist';
+		
+		parent::__construct ($message, $code);
+	}
+}

+ 8 - 0
lib/Minz/CurrentPagePaginationException.php

@@ -0,0 +1,8 @@
+<?php
+class Minz_CurrentPagePaginationException extends Minz_Exception {
+	public function __construct ($page) {
+		$message = 'Page number `' . $page . '` doesn\'t exist';
+		
+		parent::__construct ($message, self::ERROR);
+	}
+}

+ 20 - 34
lib/minz/Dispatcher.php → lib/Minz/Dispatcher.php

@@ -9,8 +9,8 @@
  * déterminée dans la Request
  * C'est un singleton
  */
-class Dispatcher {
-	const CONTROLLERS_PATH_NAME = '/controllers';
+class Minz_Dispatcher {
+	const CONTROLLERS_PATH_NAME = '/Controllers';
 
 	/* singleton */
 	private static $instance = null;
@@ -23,7 +23,7 @@ class Dispatcher {
 	 */
 	public static function getInstance ($router) {
 		if (is_null (self::$instance)) {
-			self::$instance = new Dispatcher ($router);
+			self::$instance = new Minz_Dispatcher ($router);
 		}
 		return self::$instance;
 	}
@@ -38,7 +38,7 @@ class Dispatcher {
 	/**
 	 * Lance le controller indiqué dans Request
 	 * Remplit le body de Response à partir de la Vue
-	 * @exception MinzException
+	 * @exception Minz_Exception
 	 */
 	public function run () {
 		$cache = new Minz_Cache();
@@ -53,29 +53,25 @@ class Dispatcher {
 			$cache->render ();
 			$text = ob_get_clean();
 		} else {
-			while (Request::$reseted) {
-				Request::$reseted = false;
+			while (Minz_Request::$reseted) {
+				Minz_Request::$reseted = false;
 
 				try {
-					$this->createController (
-						Request::controllerName ()
-						. 'Controller'
-					);
-
+					$this->createController ('FreshRSS_' . Minz_Request::controllerName () . '_Controller');
 					$this->controller->init ();
 					$this->controller->firstAction ();
 					$this->launchAction (
-						Request::actionName ()
+						Minz_Request::actionName ()
 						. 'Action'
 					);
 					$this->controller->lastAction ();
 
-					if (!Request::$reseted) {
+					if (!Minz_Request::$reseted) {
 						ob_start ();
 						$this->controller->view ()->build ();
 						$text = ob_get_clean();
 					}
-				} catch (MinzException $e) {
+				} catch (Minz_Exception $e) {
 					throw $e;
 				}
 			}
@@ -85,14 +81,12 @@ class Dispatcher {
 			}
 		}
 
-		Response::setBody ($text);
+		Minz_Response::setBody ($text);
 	}
 
 	/**
 	 * Instancie le Controller
 	 * @param $controller_name le nom du controller à instancier
-	 * @exception FileNotExistException le fichier correspondant au
-	 *          > controller n'existe pas
 	 * @exception ControllerNotExistException le controller n'existe pas
 	 * @exception ControllerNotActionControllerException controller n'est
 	 *          > pas une instance de ActionController
@@ -101,26 +95,18 @@ class Dispatcher {
 		$filename = APP_PATH . self::CONTROLLERS_PATH_NAME . '/'
 		          . $controller_name . '.php';
 
-		if (!file_exists ($filename)) {
-			throw new FileNotExistException (
-				$filename,
-				MinzException::ERROR
-			);
-		}
-		require_once ($filename);
-
 		if (!class_exists ($controller_name)) {
-			throw new ControllerNotExistException (
+			throw new Minz_ControllerNotExistException (
 				$controller_name,
-				MinzException::ERROR
+				Minz_Exception::ERROR
 			);
 		}
 		$this->controller = new $controller_name ($this->router);
 
-		if (! ($this->controller instanceof ActionController)) {
-			throw new ControllerNotActionControllerException (
+		if (! ($this->controller instanceof Minz_ActionController)) {
+			throw new Minz_ControllerNotActionControllerException (
 				$controller_name,
-				MinzException::ERROR
+				Minz_Exception::ERROR
 			);
 		}
 	}
@@ -129,18 +115,18 @@ class Dispatcher {
 	 * Lance l'action sur le controller du dispatcher
 	 * @param $action_name le nom de l'action
 	 * @exception ActionException si on ne peut pas exécuter l'action sur
-	 *          > le controller
+	 *  le controller
 	 */
 	private function launchAction ($action_name) {
-		if (!Request::$reseted) {
+		if (!Minz_Request::$reseted) {
 			if (!is_callable (array (
 				$this->controller,
 				$action_name
 			))) {
-				throw new ActionException (
+				throw new Minz_ActionException (
 					get_class ($this->controller),
 					$action_name,
-					MinzException::ERROR
+					Minz_Exception::ERROR
 				);
 			}
 			call_user_func (array (

+ 20 - 20
lib/minz/Error.php → lib/Minz/Error.php

@@ -1,5 +1,5 @@
 <?php
-/** 
+/**
  * MINZ - Copyright 2011 Marien Fressinaud
  * Sous licence AGPL3 <http://www.gnu.org/licenses/>
 */
@@ -7,7 +7,7 @@
 /**
  * La classe Error permet de lancer des erreurs HTTP
  */
-class Error {
+class Minz_Error {
 	public function __construct () { }
 
 	/**
@@ -21,28 +21,28 @@ class Error {
 	*/
 	public static function error ($code = 404, $logs = array (), $redirect = false) {
 		$logs = self::processLogs ($logs);
-		$error_filename = APP_PATH . '/controllers/errorController.php';
-		
+		$error_filename = APP_PATH . '/Controllers/ErrorController.php';
+
 		if (file_exists ($error_filename)) {
 			$params = array (
 				'code' => $code,
 				'logs' => $logs
 			);
-			
-			Response::setHeader ($code);
+
+			Minz_Response::setHeader ($code);
 			if ($redirect) {
-				Request::forward (array (
+				Minz_Request::forward (array (
 					'c' => 'error'
 				), true);
 			} else {
-				Request::forward (array (
+				Minz_Request::forward (array (
 					'c' => 'error',
 					'params' => $params
 				), false);
 			}
 		} else {
 			$text = '<h1>An error occured</h1>'."\n";
-			
+
 			if (!empty ($logs)) {
 				$text .= '<ul>'."\n";
 				foreach ($logs as $log) {
@@ -50,14 +50,14 @@ class Error {
 				}
 				$text .= '</ul>'."\n";
 			}
-			
-			Response::setHeader ($code);
-			Response::setBody ($text);
-			Response::send ();
+
+			Minz_Response::setHeader ($code);
+			Minz_Response::setBody ($text);
+			Minz_Response::send ();
 			exit ();
 		}
 	}
-	
+
 	/**
 	 * Permet de retourner les logs de façon à n'avoir que
 	 * ceux que l'on veut réellement
@@ -66,12 +66,12 @@ class Error {
 	 *       > en fonction de l'environment
 	 */
 	private static function processLogs ($logs) {
-		$env = Configuration::environment ();
+		$env = Minz_Configuration::environment ();
 		$logs_ok = array ();
 		$error = array ();
 		$warning = array ();
 		$notice = array ();
-		
+
 		if (isset ($logs['error'])) {
 			$error = $logs['error'];
 		}
@@ -81,14 +81,14 @@ class Error {
 		if (isset ($logs['notice'])) {
 			$notice = $logs['notice'];
 		}
-		
-		if ($env == Configuration::PRODUCTION) {
+
+		if ($env == Minz_Configuration::PRODUCTION) {
 			$logs_ok = $error;
 		}
-		if ($env == Configuration::DEVELOPMENT) {
+		if ($env == Minz_Configuration::DEVELOPMENT) {
 			$logs_ok = array_merge ($error, $warning, $notice);
 		}
-		
+
 		return $logs_ok;
 	}
 }

+ 16 - 0
lib/Minz/Exception.php

@@ -0,0 +1,16 @@
+<?php
+class Minz_Exception extends Exception {
+	const ERROR = 0;
+	const WARNING = 10;
+	const NOTICE = 20;
+
+	public function __construct ($message, $code = self::ERROR) {
+		if ($code != Minz_Exception::ERROR
+		 && $code != Minz_Exception::WARNING
+		 && $code != Minz_Exception::NOTICE) {
+			$code = Minz_Exception::ERROR;
+		}
+
+		parent::__construct ($message, $code);
+	}
+}

+ 8 - 0
lib/Minz/FileNotExistException.php

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

+ 26 - 56
lib/minz/FrontController.php → lib/Minz/FrontController.php

@@ -2,109 +2,79 @@
 # ***** BEGIN LICENSE BLOCK *****
 # MINZ - a free PHP Framework like Zend Framework
 # Copyright (C) 2011 Marien Fressinaud
-# 
+#
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Affero General Public License as
 # published by the Free Software Foundation, either version 3 of the
 # License, or (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU Affero General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU Affero General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 # ***** END LICENSE BLOCK *****
 
 /**
- * La classe FrontController est le noyau du framework, elle lance l'application
+ * La classe FrontController est le Dispatcher du framework, elle lance l'application
  * Elle est appelée en général dans le fichier index.php à la racine du serveur
  */
-class FrontController {
+class Minz_FrontController {
 	protected $dispatcher;
 	protected $router;
-	
+
 	/**
 	 * Constructeur
 	 * Initialise le router et le dispatcher
 	 */
 	public function __construct () {
-		$this->loadLib ();
-
 		if (LOG_PATH === false) {
-			$this->killApp ('Path doesn\'t exist : LOG_PATH');
+			$this->killApp ('Path doesn’t exist : LOG_PATH');
 		}
-		
+
 		try {
-			Configuration::init ();
+			Minz_Configuration::init ();
 
-			Request::init ();
+			Minz_Request::init ();
 
-			$this->router = new Router ();
+			$this->router = new Minz_Router ();
 			$this->router->init ();
-		} catch (RouteNotFoundException $e) {
+		} catch (Minz_RouteNotFoundException $e) {
 			Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
-			Error::error (
+			Minz_Error::error (
 				404,
 				array ('error' => array ($e->getMessage ()))
 			);
-		} catch (MinzException $e) {
+		} catch (Minz_Exception $e) {
 			Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
 			$this->killApp ($e->getMessage ());
 		}
-		
-		$this->dispatcher = Dispatcher::getInstance ($this->router);
-	}
-	
-	/**
-	 * Inclue les fichiers de la librairie
-	 */
-	private function loadLib () {
-		require ('ActionController.php');
-		require ('Minz_Cache.php');
-		require ('Configuration.php');
-		require ('Dispatcher.php');
-		require ('Error.php');
-		require ('Helper.php');
-		require ('Minz_Log.php');
-		require ('Model.php');
-		require ('Paginator.php');
-		require ('Request.php');
-		require ('Response.php');
-		require ('Router.php');
-		require ('Session.php');
-		require ('Translate.php');
-		require ('Url.php');
-		require ('View.php');
-		
-		require ('dao/Model_pdo.php');
-		require ('dao/Model_txt.php');
-		require ('dao/Model_array.php');
-		
-		require ('exceptions/MinzException.php');
+
+		$this->dispatcher = Minz_Dispatcher::getInstance ($this->router);
 	}
-	
+
 	/**
 	 * Démarre l'application (lance le dispatcher et renvoie la réponse
 	 */
 	public function run () {
 		try {
 			$this->dispatcher->run ();
-			Response::send ();
-		} catch (MinzException $e) {
+			Minz_Response::send ();
+		} catch (Minz_Exception $e) {
 			try {
 				Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
-			} catch (PermissionDeniedException $e) {
+			} catch (Minz_PermissionDeniedException $e) {
 				$this->killApp ($e->getMessage ());
 			}
 
-			if ($e instanceof FileNotExistException ||
-					$e instanceof ControllerNotExistException ||
-					$e instanceof ControllerNotActionControllerException ||
-					$e instanceof ActionException) {
-				Error::error (
+			if ($e instanceof Minz_FileNotExistException ||
+					$e instanceof Minz_ControllerNotExistException ||
+					$e instanceof Minz_ControllerNotActionControllerException ||
+					$e instanceof Minz_ActionException) {
+				Minz_Error::error (
 					404,
 					array ('error' => array ($e->getMessage ())),
 					true
@@ -114,7 +84,7 @@ class FrontController {
 			}
 		}
 	}
-	
+
 	/**
 	* Permet d'arrêter le programme en urgence
 	*/

+ 1 - 1
lib/minz/Helper.php → lib/Minz/Helper.php

@@ -7,7 +7,7 @@
 /**
  * La classe Helper représente une aide pour des tâches récurrentes
  */
-class Helper {
+class Minz_Helper {
 	/**
 	 * Annule les effets des magic_quotes pour une variable donnée
 	 * @param $var variable à traiter (tableau ou simple variable)

+ 18 - 13
lib/minz/Minz_Log.php → lib/Minz/Log.php

@@ -12,11 +12,13 @@ class Minz_Log {
 	 * Les différents niveau de log
 	 * ERROR erreurs bloquantes de l'application
 	 * WARNING erreurs pouvant géner le bon fonctionnement, mais non bloquantes
-	 * NOTICE messages d'informations, affichés pour le déboggage
+	 * NOTICE erreurs mineures ou messages d'informations
+	 * DEBUG Informations affichées pour le déboggage
 	 */
-	const ERROR = 0;
-	const WARNING = 10;
-	const NOTICE = 20;
+	const ERROR = 2;
+	const WARNING = 4;
+	const NOTICE = 8;
+	const DEBUG = 16;
 	
 	/**
 	 * Enregistre un message dans un fichier de log spécifique
@@ -29,11 +31,11 @@ class Minz_Log {
 	 * @param $file_name fichier de log, par défaut LOG_PATH/application.log
 	 */
 	public static function record ($information, $level, $file_name = null) {
-		$env = Configuration::environment ();
+		$env = Minz_Configuration::environment ();
 		
-		if (! ($env == Configuration::SILENT
-		       || ($env == Configuration::PRODUCTION
-		       && ($level == Minz_Log::WARNING || $level == Minz_Log::NOTICE)))) {
+		if (! ($env === Minz_Configuration::SILENT
+		       || ($env === Minz_Configuration::PRODUCTION
+		       && ($level >= Minz_Log::NOTICE)))) {
 			if (is_null ($file_name)) {
 				$file_name = LOG_PATH . '/application.log';
 			}
@@ -48,11 +50,14 @@ class Minz_Log {
 			case Minz_Log::NOTICE :
 				$level_label = 'notice';
 				break;
+			case Minz_Log::DEBUG :
+				$level_label = 'debug';
+				break;
 			default :
 				$level_label = 'unknown';
 			}
 			
-			if ($env == Configuration::PRODUCTION) {
+			if ($env == Minz_Configuration::PRODUCTION) {
 				$file = @fopen ($file_name, 'a');
 			} else {
 				$file = fopen ($file_name, 'a');
@@ -65,9 +70,9 @@ class Minz_Log {
 				fwrite ($file, $log); 
 				fclose ($file);
 			} else {
-				throw new PermissionDeniedException (
+				throw new Minz_PermissionDeniedException (
 					$file_name,
-					MinzException::ERROR
+					Minz_Exception::ERROR
 				);
 			}
 		}
@@ -83,7 +88,7 @@ class Minz_Log {
 		$msg_get = str_replace("\n", '', '$_GET content : ' . print_r($_GET, true));
 		$msg_post = str_replace("\n", '', '$_POST content : ' . print_r($_POST, true));
 
-		self::record($msg_get, Minz_Log::NOTICE, $file_name);
-		self::record($msg_post, Minz_Log::NOTICE, $file_name);
+		self::record($msg_get, Minz_Log::DEBUG, $file_name);
+		self::record($msg_post, Minz_Log::DEBUG, $file_name);
 	}
 }

+ 1 - 1
lib/minz/Model.php → lib/Minz/Model.php

@@ -7,6 +7,6 @@
 /**
  * La classe Model représente un modèle de l'application (représentation MVC)
  */
-class Model {
+class Minz_Model {
 
 }

+ 3 - 3
lib/minz/dao/Model_array.php → lib/Minz/ModelArray.php

@@ -7,7 +7,7 @@
 /**
  * La classe Model_array représente le modèle interragissant avec les fichiers de type texte gérant des tableaux php
  */
-class Model_array extends Model_txt {
+class Minz_ModelArray extends Minz_ModelTxt {
 	/**
 	 * $array Le tableau php contenu dans le fichier $nameFile
 	 */
@@ -22,7 +22,7 @@ class Model_array extends Model_txt {
 		parent::__construct ($nameFile);
 		
 		if (!$this->getLock ('read')) {
-			throw new PermissionDeniedException ($this->filename);
+			throw new Minz_PermissionDeniedException ($this->filename);
 		} else {
 			$this->array = include ($this->filename);
 			$this->releaseLock ();
@@ -41,7 +41,7 @@ class Model_array extends Model_txt {
 	 **/
 	public function writeFile ($array) {
 		if (!$this->getLock ('write')) {
-			throw new PermissionDeniedException ($this->namefile);
+			throw new Minz_PermissionDeniedException ($this->namefile);
 		} else {
 			$this->erase ();
 		

+ 28 - 9
lib/minz/dao/Model_pdo.php → lib/Minz/ModelPdo.php

@@ -8,7 +8,7 @@
  * La classe Model_sql représente le modèle interragissant avec les bases de données
  * Seul la connexion MySQL est prise en charge pour le moment
  */
-class Model_pdo {
+class Minz_ModelPdo {
 
 	/**
 	 * Partage la connexion à la base de données entre toutes les instances.
@@ -35,7 +35,7 @@ class Model_pdo {
 			return;
 		}
 
-		$db = Configuration::dataBase ();
+		$db = Minz_Configuration::dataBase ();
 		$driver_options = null;
 
 		try {
@@ -49,9 +49,7 @@ class Model_pdo {
 					PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'
 				);
 			} elseif($type == 'sqlite') {
-				$string = $type
-				        . ':/' . PUBLIC_PATH
-				        . '/data/' . $db['base'] . '.sqlite';	//TODO: DEBUG UTF-8 http://www.siteduzero.com/forum/sujet/sqlite-connexion-utf-8-18797
+				$string = $type . ':/' . DATA_PATH . $db['base'] . '.sqlite';	//TODO: DEBUG UTF-8 http://www.siteduzero.com/forum/sujet/sqlite-connexion-utf-8-18797
 			}
 
 			$this->bd = new FreshPDO (
@@ -62,21 +60,42 @@ class Model_pdo {
 			);
 			self::$sharedBd = $this->bd;
 
-			$this->prefix = $db['prefix'];
+			$userPrefix = Minz_Configuration::currentUser ();
+			$this->prefix = $db['prefix'] . (empty($userPrefix) ? '' : ($userPrefix . '_'));
 			self::$sharedPrefix = $this->prefix;
 		} catch (Exception $e) {
-			throw new PDOConnectionException (
+			throw new Minz_PDOConnectionException (
 				$string,
-				$db['user'], MinzException::WARNING
+				$db['user'], Minz_Exception::ERROR
 			);
 		}
 	}
+
+	public function beginTransaction() {
+		$this->bd->beginTransaction();
+	}
+	public function commit() {
+		$this->bd->commit();
+	}
+	public function rollBack() {
+		$this->bd->rollBack();
+	}
+
+	public function size() {
+		$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']);
+		$stm->execute ($values);
+		$res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
+		return $res[0];
+	}
 }
 
 class FreshPDO extends PDO {
 	private static function check($statement) {
 		if (preg_match('/^(?:UPDATE|INSERT|DELETE)/i', $statement)) {
-			touch(PUBLIC_PATH . '/data/touch.txt');
+			invalidateHttpCache();
 		}
 	}
 

+ 5 - 5
lib/minz/dao/Model_txt.php → lib/Minz/ModelTxt.php

@@ -7,7 +7,7 @@
 /**
  * La classe Model_txt représente le modèle interragissant avec les fichiers de type texte
  */
-class Model_txt {
+class Minz_ModelTxt {
 	/**
 	 * $file représente le fichier à ouvrir
 	 */
@@ -28,18 +28,18 @@ class Model_txt {
 	public function __construct ($nameFile, $mode = 'a+') {
 		$this->filename = $nameFile;
 		if (!file_exists($this->filename)) {
-			throw new FileNotExistException (
+			throw new Minz_FileNotExistException (
 				$this->filename,
-				MinzException::WARNING
+				Minz_Exception::WARNING
 			);
 		}
 
 		$this->file = @fopen ($this->filename, $mode);
 		
 		if (!$this->file) {
-			throw new PermissionDeniedException (
+			throw new Minz_PermissionDeniedException (
 				$this->filename,
-				MinzException::WARNING
+				Minz_Exception::WARNING
 			);
 		}
 	}

+ 9 - 0
lib/Minz/PDOConnectionException.php

@@ -0,0 +1,9 @@
+<?php
+class Minz_PDOConnectionException extends Minz_Exception {
+	public function __construct ($string_connection, $user, $code = self::ERROR) {
+		$message = 'Access to database is denied for `' . $user . '`'
+		         . ' (`' . $string_connection . '`)';
+		
+		parent::__construct ($message, $code);
+	}
+}

Some files were not shown because too many files changed in this diff