Przeglądaj źródła

Merge branch 'dev'

Marien Fressinaud 12 lat temu
rodzic
commit
3aeea28ac7
75 zmienionych plików z 918 dodań i 432 usunięć
  1. 6 0
      CHANGELOG
  2. 4 3
      README.md
  3. 2 4
      app/Controllers/configureController.php
  4. 58 32
      app/Controllers/feedController.php
  5. 20 6
      app/Controllers/indexController.php
  6. 1 1
      app/Controllers/javascriptController.php
  7. 6 1
      app/Controllers/usersController.php
  8. 0 4
      app/FreshRSS.php
  9. 1 13
      app/Models/Category.php
  10. 3 10
      app/Models/CategoryDAO.php
  11. 2 0
      app/Models/Configuration.php
  12. 33 5
      app/Models/Feed.php
  13. 20 11
      app/Models/FeedDAO.php
  14. 15 19
      app/actualize_script.php
  15. 16 6
      app/i18n/en.php
  16. 16 6
      app/i18n/fr.php
  17. 2 2
      app/layout/aside_flux.phtml
  18. 1 1
      app/layout/header.phtml
  19. 8 3
      app/layout/layout.phtml
  20. 3 1
      app/layout/nav_menu.phtml
  21. 0 1
      app/sql.php
  22. 5 1
      app/views/configure/feed.phtml
  23. 38 17
      app/views/configure/shortcut.phtml
  24. 15 5
      app/views/configure/users.phtml
  25. 9 1
      app/views/error/index.phtml
  26. 9 1
      app/views/helpers/javascript_vars.phtml
  27. 8 8
      app/views/helpers/view/normal_view.phtml
  28. 27 29
      app/views/index/formLogin.phtml
  29. 6 11
      app/views/index/index.phtml
  30. 33 20
      app/views/javascript/actualize.phtml
  31. 4 1
      constants.php
  32. 18 1
      lib/Minz/Configuration.php
  33. 1 1
      lib/Minz/Request.php
  34. 61 12
      lib/SimplePie/SimplePie.php
  35. 1 1
      lib/SimplePie/SimplePie/Author.php
  36. 1 1
      lib/SimplePie/SimplePie/Cache.php
  37. 1 1
      lib/SimplePie/SimplePie/Cache/Base.php
  38. 1 1
      lib/SimplePie/SimplePie/Cache/DB.php
  39. 1 1
      lib/SimplePie/SimplePie/Cache/File.php
  40. 5 7
      lib/SimplePie/SimplePie/Cache/Memcache.php
  41. 4 3
      lib/SimplePie/SimplePie/Cache/MySQL.php
  42. 1 1
      lib/SimplePie/SimplePie/Caption.php
  43. 1 1
      lib/SimplePie/SimplePie/Category.php
  44. 1 1
      lib/SimplePie/SimplePie/Content/Type/Sniffer.php
  45. 1 1
      lib/SimplePie/SimplePie/Copyright.php
  46. 1 1
      lib/SimplePie/SimplePie/Core.php
  47. 1 1
      lib/SimplePie/SimplePie/Credit.php
  48. 1 1
      lib/SimplePie/SimplePie/Decode/HTML/Entities.php
  49. 2 2
      lib/SimplePie/SimplePie/Enclosure.php
  50. 3 3
      lib/SimplePie/SimplePie/File.php
  51. 1 1
      lib/SimplePie/SimplePie/HTTP/Parser.php
  52. 1 1
      lib/SimplePie/SimplePie/IRI.php
  53. 3 3
      lib/SimplePie/SimplePie/Item.php
  54. 2 2
      lib/SimplePie/SimplePie/Locator.php
  55. 23 30
      lib/SimplePie/SimplePie/Misc.php
  56. 1 1
      lib/SimplePie/SimplePie/Net/IPv6.php
  57. 1 1
      lib/SimplePie/SimplePie/Parse/Date.php
  58. 6 1
      lib/SimplePie/SimplePie/Parser.php
  59. 1 1
      lib/SimplePie/SimplePie/Rating.php
  60. 1 1
      lib/SimplePie/SimplePie/Registry.php
  61. 1 1
      lib/SimplePie/SimplePie/Restriction.php
  62. 6 2
      lib/SimplePie/SimplePie/Sanitize.php
  63. 1 1
      lib/SimplePie/SimplePie/Source.php
  64. 1 1
      lib/SimplePie/SimplePie/XML/Declaration/Parser.php
  65. 1 1
      lib/SimplePie/SimplePie/gzdecode.php
  66. 1 2
      lib/lib_opml.php
  67. 1 1
      lib/lib_rss.php
  68. 7 6
      p/i/install.php
  69. 226 26
      p/scripts/main.js
  70. 29 19
      p/themes/Dark/freshrss.css
  71. 21 14
      p/themes/Dark/global.css
  72. 33 13
      p/themes/Flat/freshrss.css
  73. 21 14
      p/themes/Flat/global.css
  74. 31 11
      p/themes/Origine/freshrss.css
  75. 21 14
      p/themes/Origine/global.css

+ 6 - 0
CHANGELOG

@@ -1,5 +1,11 @@
 # Journal des modifications
 
+## 2014-0x-xx FreshRSS 0.8
+
+* Mise à jour des flux plus rapide grâce à une meilleure utilisation du cache
+	* Utilisation d’une signature MD5 du contenu intéressant pour les flux n’implémentant pas les requêtes conditionnelles
+
+
 ## 2014-01-29 FreshRSS 0.7
 
 * Nouveau mode multi-utilisateur

+ 4 - 3
README.md

@@ -8,8 +8,8 @@ Il permet de gérer plusieurs utilisateurs, et dispose d’un mode de lecture an
 * Site officiel : http://freshrss.org
 * Démo : http://demo.freshrss.org/
 * Développeur : Marien Fressinaud <dev@marienfressinaud.fr>
-* Version actuelle : 0.7
-* Date de publication 2014-01-29
+* Version actuelle : 0.8-dev
+* Date de publication 2014-0x-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)
@@ -25,7 +25,7 @@ Privilégiez pour cela des demandes sur GitHub
 * Serveur modeste, par exemple sous Linux ou Windows
 	* Fonctionne même sur un Raspberry Pi avec des temps de réponse < 1s (testé sur 150 flux, 22k articles, soit 32Mo de données partiellement compressées)
 * Serveur Web Apache2 ou Nginx (non testé sur les autres)
-* PHP 5.2+ (PHP 5.3.7+ recommandé)
+* PHP 5.2.1+ (PHP 5.3.7+ recommandé)
 	* Requis : [PDO_MySQL](http://php.net/pdo-mysql), [cURL](http://php.net/curl), [LibXML](http://php.net/xml), [PCRE](http://php.net/pcre), [ctype](http://php.net/ctype)
 	* Recommandés : [JSON](http://php.net/json), [zlib](http://php.net/zlib), [mbstring](http://php.net/mbstring), [iconv](http://php.net/iconv)
 * MySQL 5.0.3+ (ou SQLite 3.7.4+ à venir)
@@ -80,6 +80,7 @@ mysqldump -u utilisateur -p --databases freshrss > freshrss.sql
 * [php-http-304](http://alexandre.alapetite.fr/doc-alex/php-http-304/)
 * [jQuery](http://jquery.com/)
 * [keyboard_shortcuts](http://www.openjs.com/scripts/events/keyboard_shortcuts/)
+* [flotr2](http://www.humblesoftware.com/flotr2)
 
 ## Uniquement pour certaines options
 * [bcrypt.js](https://github.com/dcodeIO/bcrypt.js)

+ 2 - 4
app/Controllers/configureController.php

@@ -29,7 +29,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 					$cat = new FreshRSS_Category ($name);
 					$values = array (
 						'name' => $cat->name (),
-						'color' => $cat->color ()
 					);
 					$catDAO->updateCategory ($ids[$key], $values);
 				} elseif ($ids[$key] != $defaultId) {
@@ -43,7 +42,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 				$values = array (
 					'id' => $cat->id (),
 					'name' => $cat->name (),
-					'color' => $cat->color ()
 				);
 
 				if ($catDAO->searchByName ($newCat) == false) {
@@ -116,7 +114,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 
 					if ($feedDAO->updateFeed ($id, $values)) {
 						$this->view->flux->_category ($cat);
-
+						$this->view->flux->faviconPrepare();
 						$notif = array (
 							'type' => 'good',
 							'content' => Minz_Translate::t ('feed_updated')
@@ -286,7 +284,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 
 	public function shortcutAction () {
 		$list_keys = array ('a', 'b', 'backspace', 'c', 'd', 'delete', 'down', 'e', 'end', 'enter',
-		                    'escape', 'f', 'g', 'h', 'i', 'insert', 'j', 'k', 'l', 'left',
+		                    'escape', 'f', 'g', 'h', 'home', 'i', 'insert', 'j', 'k', 'l', 'left',
 		                    'm', 'n', 'o', 'p', 'page_down', 'page_up', 'q', 'r', 'return', 'right',
 		                    's', 'space', 't', 'tab', 'u', 'up', 'v', 'w', 'x', 'y',
 		                    'z', '0', '1', '2', '3', '4', '5', '6', '7', '8',

+ 58 - 32
app/Controllers/feedController.php

@@ -3,26 +3,31 @@
 class FreshRSS_feed_Controller extends Minz_ActionController {
 	public function firstAction () {
 		if (!$this->view->loginOk) {
-			$token = $this->view->conf->token;	//TODO: check the token logic again, and if it is still needed
+			// Token is useful in the case that anonymous refresh is forbidden
+			// and CRON task cannot be used with php command so the user can
+			// set a CRON task to refresh his feeds by using token inside url
+			$token = $this->view->conf->token;
 			$token_param = Minz_Request::param ('token', '');
 			$token_is_ok = ($token != '' && $token == $token_param);
 			$action = Minz_Request::actionName ();
-			if (!($token_is_ok && $action === 'actualize')) {
+			if (!(($token_is_ok || Minz_Configuration::allowAnonymousRefresh()) &&
+				$action === 'actualize')
+			) {
 				Minz_Error::error (
 					403,
 					array ('error' => array (Minz_Translate::t ('access_denied')))
 				);
 			}
 		}
-
-		$this->catDAO = new FreshRSS_CategoryDAO ();
-		$this->catDAO->checkDefault ();
 	}
 
 	public function addAction () {
 		@set_time_limit(300);
 
 		if (Minz_Request::isPost ()) {
+			$this->catDAO = new FreshRSS_CategoryDAO ();
+			$this->catDAO->checkDefault ();
+
 			$url = Minz_Request::param ('url_rss');
 			$cat = Minz_Request::param ('category', false);
 			if ($cat === false) {
@@ -189,38 +194,51 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 		$flux_update = 0;
 		$is_read = $this->view->conf->mark_when['reception'] ? 1 : 0;
 		foreach ($feeds as $feed) {
+			if (!$feed->lock()) {
+				Minz_Log::record('Feed already being actualized: ' . $feed->url(), Minz_Log::NOTICE);
+				continue;
+			}
 			try {
 				$url = $feed->url();
+				$feedHistory = $feed->keepHistory();
+
 				$feed->load(false);
 				$entries = array_reverse($feed->entries());	//We want chronological order and SimplePie uses reverse order
+				$hasTransaction = false;
 
-				//For this feed, check last n entry GUIDs already in database
-				$existingGuids = array_fill_keys ($entryDAO->listLastGuidsByFeed ($feed->id (), count($entries) + 10), 1);
-				$useDeclaredDate = empty($existingGuids);
+				if (count($entries) > 0) {
+					//For this feed, check last n entry GUIDs already in database
+					$existingGuids = array_fill_keys ($entryDAO->listLastGuidsByFeed ($feed->id (), count($entries) + 10), 1);
+					$useDeclaredDate = empty($existingGuids);
 
-				$feedHistory = $feed->keepHistory();
-				if ($feedHistory == -2) {	//default
-					$feedHistory = $this->view->conf->keep_history_default;
-				}
+					if ($feedHistory == -2) {	//default
+						$feedHistory = $this->view->conf->keep_history_default;
+					}
+
+					$hasTransaction = true;
+					$feedDAO->beginTransaction();
 
-				// 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) {
-					$eDate = $entry->date (true);
-					if ((!isset ($existingGuids[$entry->guid ()])) &&
-						(($feedHistory != 0) || ($eDate  >= $date_min))) {
-						$values = $entry->toArray ();
-						//Use declared date at first import, otherwise use discovery date
-						$values['id'] = ($useDeclaredDate || $eDate < $date_min) ?
-							min(time(), $eDate) . uSecString() :
-							uTimeString();
-						$values['is_read'] = $is_read;
-						$entryDAO->addEntry ($values);
+					// 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
+					foreach ($entries as $entry) {
+						$eDate = $entry->date (true);
+						if ((!isset ($existingGuids[$entry->guid ()])) &&
+							(($feedHistory != 0) || ($eDate  >= $date_min))) {
+							$values = $entry->toArray ();
+							//Use declared date at first import, otherwise use discovery date
+							$values['id'] = ($useDeclaredDate || $eDate < $date_min) ?
+								min(time(), $eDate) . uSecString() :
+								uTimeString();
+							$values['is_read'] = $is_read;
+							$entryDAO->addEntry ($values);
+						}
 					}
 				}
 
 				if (($feedHistory >= 0) && (rand(0, 30) === 1)) {
+					if (!$hasTransaction) {
+						$feedDAO->beginTransaction();
+					}
 					$nb = $feedDAO->cleanOldEntries ($feed->id (), $date_min, max($feedHistory, count($entries) + 10));
 					if ($nb > 0) {
 						Minz_Log::record ($nb . ' old entries cleaned in feed [' . $feed->url() . ']', Minz_Log::DEBUG);
@@ -228,18 +246,23 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 				}
 
 				// on indique que le flux vient d'être mis à jour en BDD
-				$feedDAO->updateLastUpdate ($feed->id ());
-				$feedDAO->commit ();
+				$feedDAO->updateLastUpdate ($feed->id (), 0, $hasTransaction);
+				if ($hasTransaction) {
+					$feedDAO->commit();
+				}
 				$flux_update++;
 				if ($feed->url() !== $url) {	//URL has changed (auto-discovery)
 					$feedDAO->updateFeed($feed->id(), array('url' => $feed->url()));
 				}
-				$feed->faviconPrepare();
 			} catch (FreshRSS_Feed_Exception $e) {
 				Minz_Log::record ($e->getMessage (), Minz_Log::NOTICE);
 				$feedDAO->updateLastUpdate ($feed->id (), 1);
 			}
 
+			$feed->faviconPrepare();
+			$feed->unlock();
+			unset($feed);
+
 			// On arrête à 10 flux pour ne pas surcharger le serveur
 			// sauf si le paramètre $force est à vrai
 			$i++;
@@ -251,6 +274,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 		$url = array ();
 		if ($flux_update === 1) {
 			// on a mis un seul flux à jour
+			$feed = reset ($feeds);
 			$notif = array (
 				'type' => 'good',
 				'content' => Minz_Translate::t ('feed_actualized', $feed->name ())
@@ -264,8 +288,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 		} else {
 			// aucun flux n'a été mis à jour, oups
 			$notif = array (
-				'type' => 'bad',
-				'content' => Minz_Translate::t ('no_feed_actualized')
+				'type' => 'good',
+				'content' => Minz_Translate::t ('no_feed_to_refresh')
 			);
 		}
 
@@ -298,6 +322,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 	public function massiveImportAction () {
 		@set_time_limit(300);
 
+		$this->catDAO = new FreshRSS_CategoryDAO ();
+		$this->catDAO->checkDefault ();
+
 		$entryDAO = new FreshRSS_EntryDAO ();
 		$feedDAO = new FreshRSS_FeedDAO ();
 
@@ -416,7 +443,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
 				$values = array (
 					'id' => $cat->id (),
 					'name' => $cat->name (),
-					'color' => $cat->color ()
 				);
 				$catDAO->addCategory ($values);
 			}

+ 20 - 6
app/Controllers/indexController.php

@@ -5,18 +5,24 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 
 	public function indexAction () {
 		$output = Minz_Request::param ('output');
-		$token = '';
+		$token = $this->view->conf->token;
 
 		// check if user is logged in
-		if (!$this->view->loginOk && !Minz_Configuration::allowAnonymous())
-		{
-			$token = $this->view->conf->token;
+		if (!$this->view->loginOk && !Minz_Configuration::allowAnonymous()) {
 			$token_param = Minz_Request::param ('token', '');
 			$token_is_ok = ($token != '' && $token === $token_param);
-			if (!($output === 'rss' && $token_is_ok)) {
+			if ($output === 'rss' && !$token_is_ok) {
+				Minz_Error::error (
+					403,
+					array ('error' => array (Minz_Translate::t ('access_denied')))
+				);
+				return;
+			} elseif ($output !== 'rss') {
+				// "hard" redirection is not required, just ask dispatcher to
+				// forward to the login form without 302 redirection
+				Minz_Request::forward(array('c' => 'index', 'a' => 'formLogin'));
 				return;
 			}
-			$params['token'] = $token;
 		}
 
 		// construction of RSS url of this feed
@@ -25,6 +31,9 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 		if (isset ($params['search'])) {
 			$params['search'] = urlencode ($params['search']);
 		}
+		if (!Minz_Configuration::allowAnonymous()) {
+			$params['token'] = $token;
+		}
 		$this->view->rss_url = array (
 			'c' => 'index',
 			'a' => 'index',
@@ -342,6 +351,11 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 			}
 			$this->view->_useLayout(false);
 			Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true);
+		} elseif (!Minz_Configuration::canLogIn()) {
+			Minz_Error::error (
+				403,
+				array ('error' => array (Minz_Translate::t ('access_denied')))
+			);
 		}
 		invalidateHttpCache();
 	}

+ 1 - 1
app/Controllers/javascriptController.php

@@ -8,7 +8,7 @@ class FreshRSS_javascript_Controller extends Minz_ActionController {
 	public function actualizeAction () {
 		header('Content-Type: text/javascript; charset=UTF-8');
 		$feedDAO = new FreshRSS_FeedDAO ();
-		$this->view->feeds = $feedDAO->listFeeds ();
+		$this->view->feeds = $feedDAO->listFeedsOrderUpdate();
 	}
 
 	public function nbUnreadsPerFeedAction() {

+ 6 - 1
app/Controllers/usersController.php

@@ -54,11 +54,16 @@ class FreshRSS_users_Controller extends Minz_ActionController {
 
 				$anon = Minz_Request::param('anon_access', false);
 				$anon = ((bool)$anon) && ($anon !== 'no');
+				$anon_refresh = Minz_Request::param('anon_refresh', false);
+				$anon_refresh = ((bool)$anon_refresh) && ($anon_refresh !== 'no');
 				$auth_type = Minz_Request::param('auth_type', 'none');
 				if ($anon != Minz_Configuration::allowAnonymous() ||
-					$auth_type != Minz_Configuration::authType()) {
+					$auth_type != Minz_Configuration::authType() ||
+					$anon_refresh != Minz_Configuration::allowAnonymousRefresh()) {
+
 					Minz_Configuration::_authType($auth_type);
 					Minz_Configuration::_allowAnonymous($anon);
+					Minz_Configuration::_allowAnonymousRefresh($anon_refresh);
 					$ok &= Minz_Configuration::writeFile();
 				}
 			}

+ 0 - 4
app/FreshRSS.php

@@ -94,10 +94,6 @@ class FreshRSS extends Minz_FrontController {
 					$loginOk = false;
 					break;
 			}
-			if ((!$loginOk) && (PHP_SAPI === 'cli') && (Minz_Request::actionName() === 'actualize')) {	//Command line
-				Minz_Configuration::_authType('none');
-				$loginOk = true;
-			}
 		}
 		Minz_View::_param ('loginOk', $loginOk);
 		return $loginOk;

+ 1 - 13
app/Models/Category.php

@@ -3,14 +3,12 @@
 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) {
+	public function __construct ($name = '', $feeds = null) {
 		$this->_name ($name);
-		$this->_color ($color);
 		if (isset ($feeds)) {
 			$this->_feeds ($feeds);
 			$this->nbFeed = 0;
@@ -28,9 +26,6 @@ class FreshRSS_Category extends Minz_Model {
 	public function name () {
 		return $this->name;
 	}
-	public function color () {
-		return $this->color;
-	}
 	public function nbFeed () {
 		if ($this->nbFeed < 0) {
 			$catDAO = new FreshRSS_CategoryDAO ();
@@ -68,13 +63,6 @@ class FreshRSS_Category extends Minz_Model {
 	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);

+ 3 - 10
app/Models/CategoryDAO.php

@@ -2,12 +2,11 @@
 
 class FreshRSS_CategoryDAO extends Minz_ModelPdo {
 	public function addCategory ($valuesTmp) {
-		$sql = 'INSERT INTO `' . $this->prefix . 'category` (name, color) VALUES(?, ?)';
+		$sql = 'INSERT INTO `' . $this->prefix . 'category` (name) VALUES(?)';
 		$stm = $this->bd->prepare ($sql);
 
 		$values = array (
 			substr($valuesTmp['name'], 0, 255),
-			substr($valuesTmp['color'], 0, 7),
 		);
 
 		if ($stm && $stm->execute ($values)) {
@@ -20,12 +19,11 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
 	}
 
 	public function updateCategory ($id, $valuesTmp) {
-		$sql = 'UPDATE `' . $this->prefix . 'category` SET name=?, color=? WHERE id=?';
+		$sql = 'UPDATE `' . $this->prefix . 'category` SET name=? WHERE id=?';
 		$stm = $this->bd->prepare ($sql);
 
 		$values = array (
 			$valuesTmp['name'],
-			$valuesTmp['color'],
 			$id
 		);
 
@@ -89,7 +87,6 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
 	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.url, 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 '
@@ -130,7 +127,6 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
 			$values = array (
 				'id' => $cat->id (),
 				'name' => $cat->name (),
-				'color' => $cat->color ()
 			);
 
 			$this->addCategory ($values);
@@ -203,7 +199,6 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
 				// 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']);
@@ -220,7 +215,6 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
 		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']);
@@ -239,8 +233,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo {
 
 		foreach ($listDAO as $key => $dao) {
 			$cat = new FreshRSS_Category (
-				$dao['name'],
-				$dao['color']
+				$dao['name']
 			);
 			$cat->_id ($dao['id']);
 			$list[$key] = $cat;

+ 2 - 0
app/Models/Configuration.php

@@ -32,6 +32,8 @@ class FreshRSS_Configuration {
 			'go_website' => 'space',
 			'next_entry' => 'j',
 			'prev_entry' => 'k',
+			'first_entry' => 'home',
+			'last_entry' => 'end',
 			'collapse_entry' => 'c',
 			'load_more' => 'm',
 			'auto_share' => 's',

+ 33 - 5
app/Models/Feed.php

@@ -193,10 +193,10 @@ class FreshRSS_Feed extends Minz_Model {
 				}
 				$feed = customSimplePie();
 				$feed->set_feed_url ($url);
-				$feed->init ();
+				$mtime = $feed->init();
 
-				if ($feed->error ()) {
-					throw new FreshRSS_Feed_Exception ($feed->error . ' [' . $url . ']');
+				if ((!$mtime) || $feed->error()) {
+					throw new FreshRSS_Feed_Exception ($feed->error() . ' [' . $url . ']');
 				}
 
 				// si on a utilisé l'auto-discover, notre url va avoir changé
@@ -217,11 +217,20 @@ class FreshRSS_Feed extends Minz_Model {
 					$this->_description(html_only_entity_decode($feed->get_description()));
 				}
 
-				// et on charge les articles du flux
-				$this->loadEntries ($feed);
+				if (($mtime === true) || ($mtime > $this->lastUpdate)) {
+					syslog(LOG_DEBUG, 'FreshRSS no cache ' . $mtime . ' > ' . $this->lastUpdate . ' for ' . $subscribe_url);
+					$this->loadEntries($feed);	// et on charge les articles du flux
+				} else {
+					syslog(LOG_DEBUG, 'FreshRSS use cache for ' . $subscribe_url);
+					$this->entries = array();
+				}
+
+				$feed->__destruct();	//http://simplepie.org/wiki/faq/i_m_getting_memory_leaks
+				unset($feed);
 			}
 		}
 	}
+
 	private function loadEntries ($feed) {
 		$entries = array ();
 
@@ -267,8 +276,27 @@ class FreshRSS_Feed extends Minz_Model {
 			$entry->loadCompleteContent($this->pathEntries());
 
 			$entries[] = $entry;
+			unset($item);
 		}
 
 		$this->entries = $entries;
 	}
+
+	function lock() {
+		$lock = TMP_PATH . '/' . md5(Minz_Configuration::salt() . $this->url) . '.freshrss.lock';
+		if (file_exists($lock) && ((time() - @filemtime($lock)) > 3600)) {
+			@unlink($lock);
+		}
+		if (($handle = @fopen($lock, 'x')) === false) {
+			return false;
+		}
+		//register_shutdown_function('unlink', $lock);
+		@fclose($handle);
+		return true;
+	}
+
+	function unlock() {
+		$lock = TMP_PATH . '/' . md5(Minz_Configuration::salt() . $this->url) . '.freshrss.lock';
+		@unlink($lock);
+	}
 }

+ 20 - 11
app/Models/FeedDAO.php

@@ -52,21 +52,27 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
 		}
 	}
 
-	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);
+	public function updateLastUpdate ($id, $inError = 0, $updateCache = true) {
+		if ($updateCache) {
+			$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=?';
+		} else {
+			$sql = 'UPDATE `' . $this->prefix . 'feed` f '
+			     . 'SET lastUpdate=?, error=? '
+			     . 'WHERE f.id=?';
+		}
 
 		$values = array (
-			time (),
+			time(),
 			$inError,
 			$id,
 		);
 
+		$stm = $this->bd->prepare ($sql);
+
 		if ($stm && $stm->execute ($values)) {
 			return $stm->rowCount();
 		} else {
@@ -192,8 +198,11 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo {
 		return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC));
 	}
 
-	public function listFeedsOrderUpdate () {
-		$sql = 'SELECT id, name, url, pathEntries, httpAuth, keep_history FROM `' . $this->prefix . 'feed` ORDER BY lastUpdate';
+	public function listFeedsOrderUpdate ($cacheDuration = 1500) {
+		$sql = 'SELECT id, name, url, lastUpdate, pathEntries, httpAuth, keep_history '
+		     . 'FROM `' . $this->prefix . 'feed` '
+		     . 'WHERE lastUpdate < ' . (time() - intval($cacheDuration))
+		     . ' ORDER BY lastUpdate';
 		$stm = $this->bd->prepare ($sql);
 		$stm->execute ();
 

+ 15 - 19
app/actualize_script.php

@@ -1,21 +1,5 @@
 <?php
 require(dirname(__FILE__) . '/../constants.php');
-
-//<Mutex>
-$lock = DATA_PATH . '/actualize.lock.txt';
-if (file_exists($lock) && ((time() - @filemtime($lock)) > 3600)) {
-	@unlink($lock);
-}
-if (($handle = @fopen($lock, 'x')) === false) {
-	syslog(LOG_NOTICE, 'FreshRSS actualize already running?');
-	fwrite(STDERR, 'FreshRSS actualize already running?' . "\n");
-	return;
-}
-register_shutdown_function('unlink', $lock);
-//Could use http://php.net/function.pcntl-signal.php to catch interruptions
-@fclose($handle);
-//</Mutex>
-
 require(LIB_PATH . '/lib_rss.php');	//Includes class autoloader
 
 session_cache_limiter('');
@@ -32,7 +16,9 @@ $users = array_unique($users);
 
 foreach ($users as $myUser) {
 	syslog(LOG_INFO, 'FreshRSS actualize ' . $myUser);
-	fwrite(STDOUT, 'Actualize ' . $myUser . "...\n");	//Unbuffered
+	if (defined('STDOUT')) {
+		fwrite(STDOUT, 'Actualize ' . $myUser . "...\n");	//Unbuffered
+	}
 	echo $myUser, ' ';	//Buffered
 
 	$_GET['c'] = 'feed';
@@ -44,16 +30,26 @@ foreach ($users as $myUser) {
 	$freshRSS = new FreshRSS();
 	$freshRSS->_useOb(false);
 
+	Minz_Configuration::_authType('none');
+
 	Minz_Session::init('FreshRSS');
 	Minz_Session::_param('currentUser', $myUser);
 
 	$freshRSS->init();
 	$freshRSS->run();
 
-	invalidateHttpCache();
+	if (!invalidateHttpCache()) {
+		syslog(LOG_NOTICE, 'FreshRSS write access problem in ' . LOG_PATH . '/*.log!');
+		if (defined('STDERR')) {
+			fwrite(STDERR, 'Write access problem in ' . LOG_PATH . '/*.log!' . "\n");
+		}
+	}
 	Minz_Session::unset_session(true);
 	Minz_ModelPdo::clean();
 }
 syslog(LOG_INFO, 'FreshRSS actualize done.');
+if (defined('STDOUT')) {
+	fwrite(STDOUT, 'Done.' . "\n");
+}
+echo 'End.', "\n";
 ob_end_flush();
-fwrite(STDOUT, 'Done.' . "\n");

+ 16 - 6
app/i18n/en.php

@@ -3,6 +3,7 @@
 return array (
 	// LAYOUT
 	'login'				=> 'Login',
+	'login_with_persona'		=> 'Login with Persona',
 	'logout'			=> 'Logout',
 	'search'			=> 'Search words or #tags',
 	'search_short'			=> 'Search',
@@ -78,6 +79,10 @@ return array (
 	'bad_opml_file'			=> 'Your OPML file is invalid',
 	'shortcuts_updated'		=> 'Shortcuts have been updated',
 	'shortcuts_management'		=> 'Shortcuts management',
+	'shortcuts_navigation'		=> 'Navigation',
+	'shortcuts_navigation_help'	=> 'With the "Shift" modifier, navigation shortcuts apply on feeds.<br/>With the "Alt" modifier, navigation shortcuts apply on categories.',
+	'shortcuts_article_action'	=> 'Article actions',
+	'shortcuts_other_action'	=> 'Other actions',
 	'feeds_marked_read'		=> 'Feeds have been marked as read',
 	'updated'			=> 'Modifications have been updated',
 
@@ -121,15 +126,16 @@ return array (
 	'javascript_for_shortcuts'	=> 'JavaScript must be enabled in order to use shortcuts',
 	'javascript_should_be_activated'=> 'JavaScript must be enabled',
 	'shift_for_all_read'		=> '+ <code>shift</code> to mark all articles as read',
-	'see_on_website'		=> 'See article on its original website',
+	'see_on_website'		=> 'See on original website',
 	'next_article'			=> 'Skip to the next article',
-	'shift_for_last'		=> '+ <code>shift</code> to skip to the last article of page',
+	'last_article'			=> 'Skip to the last article',
 	'previous_article'		=> 'Skip to the previous article',
-	'shift_for_first'		=> '+ <code>shift</code> to skip to the first article of page',
+	'first_article'			=> 'Skip to the first article',
 	'next_page'			=> 'Skip to the next page',
 	'previous_page'			=> 'Skip to the previous page',
-	'collapse_article'		=> 'Collapse current article',
-	'auto_share'			=> 'Share current article',
+	'collapse_article'		=> 'Collapse',
+	'auto_share'			=> 'Share',
+	'auto_share_help'		=> 'If there is only one sharing mode, it is used. Else modes are accessible by their number.',
 
 	'file_to_import'		=> 'File to import',
 	'import'			=> 'Import',
@@ -137,7 +143,9 @@ return array (
 	'or'				=> 'or',
 
 	'informations'			=> 'Information',
+	'damn'				=> 'Damn!',
 	'feed_in_error'			=> 'This feed has encountered a problem. Please verify that it is always reachable then actualize it.',
+	'feed_empty'			=> 'This feed is empty. Please verify that it is still maintained.',
 	'feed_description'		=> 'Description',
 	'website_url'			=> 'Website URL',
 	'feed_url'			=> 'Feed URL',
@@ -168,6 +176,7 @@ return array (
 	'password_form'			=> 'Password<br /><small>(for the Web-form login method)</small>',
 	'persona_connection_email'	=> 'Login mail address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
 	'allow_anonymous'		=> 'Allow anonymous reading of the articles of the default user (%s)',
+	'allow_anonymous_refresh'	=> 'Allow anonymous refresh of the articles',
 	'auth_token'			=> 'Authentication token',
 	'explain_token'			=> 'Allows to access RSS output of the default user without authentication.<br /><kbd>%s?output=rss&token=%s</kbd>',
 	'login_configuration'		=> 'Login',
@@ -241,6 +250,7 @@ return array (
 	'rss_feeds_of'			=> 'RSS feed of %s',
 
 	'refresh'			=> 'Refresh',
+	'no_feed_to_refresh'		=> 'There is no feed to refresh…',
 
 	'today'				=> 'Today',
 	'yesterday'			=> 'Yesterday',
@@ -267,7 +277,7 @@ return array (
 	'logs_empty'			=> 'Log file is empty',
 	'clear_logs'			=> 'Clear the logs',
 
-	'forbidden_access'		=> 'Access forbidden! (%s)',
+	'forbidden_access'		=> 'Access is forbidden!',
 	'login_required'		=> 'Login required:',
 
 	'confirm_action'		=> 'Are you sure you want to perform this action? It cannot be cancelled!',

+ 16 - 6
app/i18n/fr.php

@@ -3,6 +3,7 @@
 return array (
 	// LAYOUT
 	'login'				=> 'Connexion',
+	'login_with_persona'		=> 'Connexion avec Persona',
 	'logout'			=> 'Déconnexion',
 	'search'			=> 'Rechercher des mots ou des #tags',
 	'search_short'			=> 'Rechercher',
@@ -78,6 +79,10 @@ return array (
 	'bad_opml_file'			=> 'Votre fichier OPML n’est pas valide',
 	'shortcuts_updated'		=> 'Les raccourcis ont été mis à jour',
 	'shortcuts_management'		=> 'Gestion des raccourcis',
+	'shortcuts_navigation'		=> 'Navigation',
+	'shortcuts_navigation_help'	=> 'Avec le modificateur "Shift", les raccourcis de navigation s’appliquent aux flux.<br/>Avec le modificateur "Alt", les raccourcis de navigation s’appliquent aux catégories.',
+	'shortcuts_article_action'	=> 'Actions associées à l’article courant',
+	'shortcuts_other_action'	=> 'Autres actions',
 	'feeds_marked_read'		=> 'Les flux ont été marqués comme lus',
 	'updated'			=> 'Modifications enregistrées',
 
@@ -121,15 +126,16 @@ return array (
 	'javascript_for_shortcuts'	=> 'Le JavaScript doit être activé pour pouvoir profiter des raccourcis',
 	'javascript_should_be_activated'=> 'Le JavaScript doit être activé',
 	'shift_for_all_read'		=> '+ <code>shift</code> pour marquer tous les articles comme lus',
-	'see_on_website'		=> 'Voir l’article sur le site d’origine',
+	'see_on_website'		=> 'Voir sur le site d’origine',
 	'next_article'			=> 'Passer à l’article suivant',
-	'shift_for_last'		=> '+ <code>shift</code> pour passer au dernier article de la page',
+	'last_article'			=> 'Passer au dernier article',
 	'previous_article'		=> 'Passer à l’article précédent',
-	'shift_for_first'		=> '+ <code>shift</code> pour passer au premier article de la page',
+	'first_article'			=> 'Passer au premier article',
 	'next_page'			=> 'Passer à la page suivante',
 	'previous_page'			=> 'Passer à la page précédente',
-	'collapse_article'		=> 'Refermer l’article courant',
-	'auto_share'			=> 'Partager l’article courant',
+	'collapse_article'		=> 'Refermer',
+	'auto_share'			=> 'Partager',
+	'auto_share_help'		=> 'Si il n’y a qu’un mode de partage, celui ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.',
 
 	'file_to_import'		=> 'Fichier à importer',
 	'import'			=> 'Importer',
@@ -137,7 +143,9 @@ return array (
 	'or'				=> 'ou',
 
 	'informations'			=> 'Informations',
+	'damn'				=> 'Arf !',
 	'feed_in_error'			=> 'Ce flux a rencontré un problème. Veuillez vérifier qu’il est toujours accessible puis actualisez-le.',
+	'feed_empty'			=> 'Ce flux est vide. Veuillez vérifier qu’il est toujours maintenu.',
 	'feed_description'		=> 'Description',
 	'website_url'			=> 'URL du site',
 	'feed_url'			=> 'URL du flux',
@@ -168,6 +176,7 @@ return array (
 	'default_user'			=> 'Nom de l’utilisateur par défaut <small>(16 caractères alphanumériques maximum)</small>',
 	'persona_connection_email'	=> 'Adresse courriel de connexion<br /><small>(pour <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
 	'allow_anonymous'		=> 'Autoriser la lecture anonyme des articles de l’utilisateur par défaut (%s)',
+	'allow_anonymous_refresh'	=> 'Autoriser le rafraîchissement anonyme des flux',
 	'auth_token'			=> 'Jeton d’identification',
 	'explain_token'			=> 'Permet d’accéder à la sortie RSS de l’utilisateur par défaut sans besoin de s’authentifier.<br /><kbd>%s?output=rss&token=%s</kbd>',
 	'login_configuration'		=> 'Identification',
@@ -241,6 +250,7 @@ return array (
 	'rss_feeds_of'			=> 'Flux RSS de %s',
 
 	'refresh'			=> 'Actualisation',
+	'no_feed_to_refresh'		=> 'Il n’y a aucun flux à actualiser…',
 
 	'today'				=> 'Aujourd’hui',
 	'yesterday'			=> 'Hier',
@@ -267,7 +277,7 @@ return array (
 	'logs_empty'			=> 'Les logs sont vides',
 	'clear_logs'			=> 'Effacer les logs',
 
-	'forbidden_access'		=> 'Accès interdit ! (%s)',
+	'forbidden_access'		=> 'L’accès vous est interdit !',
 	'login_required'		=> 'Accès protégé par mot de passe :',
 
 	'confirm_action'		=> 'Êtes-vous sûr(e) de vouloir continuer ? Cette action ne peut être annulée !',

+ 2 - 2
app/layout/aside_flux.phtml

@@ -20,7 +20,7 @@
 			}
 		?>
 		<li>
-			<div class="category all">
+			<div class="category all<?php echo $this->get_c == 'a' ? ' active' : ''; ?>">
 				<a data-unread="<?php echo formatNumber($this->nb_not_read); ?>" class="btn<?php echo $this->get_c == 'a' ? ' active' : ''; ?>" href="<?php echo Minz_Url::display($arUrl); ?>">
 					<?php echo FreshRSS_Themes::icon('all'); ?>
 					<?php echo Minz_Translate::t ('main_stream'); ?>
@@ -29,7 +29,7 @@
 		</li>
 
 		<li>
-			<div class="category favorites">
+			<div class="category favorites<?php echo $this->get_c == 's' ? ' active' : ''; ?>">
 				<a data-unread="<?php echo formatNumber($this->nb_favorites['unread']); ?>" class="btn<?php echo $this->get_c == 's' ? ' active' : ''; ?>" href="<?php $arUrl['params']['get'] = 's'; echo Minz_Url::display($arUrl); ?>">
 					<?php echo FreshRSS_Themes::icon('bookmark'); ?>
 					<?php echo Minz_Translate::t('favorite_feeds', formatNumber($this->nb_favorites['all'])); ?>

+ 1 - 1
app/layout/header.phtml

@@ -75,8 +75,8 @@ if (Minz_Configuration::canLogIn()) {
 				<li class="item"><a href="<?php echo _url ('configure', 'users'); ?>"><?php echo Minz_Translate::t ('users'); ?></a></li>
 				<li class="separator"></li>
 				<li class="item"><a href="<?php echo _url ('index', 'stats'); ?>"><?php echo Minz_Translate::t ('stats'); ?></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>
+				<li class="item"><a href="<?php echo _url ('index', 'about'); ?>"><?php echo Minz_Translate::t ('about'); ?></a></li>
 				<?php
 				if (Minz_Configuration::canLogIn()) {
 					?><li class="separator"></li><?php

+ 8 - 3
app/layout/layout.phtml

@@ -36,13 +36,18 @@
 </div>
 
 <?php
+	$msg = '';
+	$status = 'closed';
 	if (isset ($this->notification)) {
+		$msg = $this->notification['content'];
+		$status = $this->notification['type'];
+
 		invalidateHttpCache();
+	}
 ?>
-<div class="notification <?php echo $this->notification['type']; ?>">
-	<?php echo $this->notification['content']; ?>
+<div id="notification" class="<?php echo $status; ?>">
+	<span class="msg"><?php echo $msg; ?></span>
 	<a class="close" href=""><?php echo FreshRSS_Themes::icon('close'); ?></a>
 </div>
-<?php } ?>
 	</body>
 </html>

+ 3 - 1
app/layout/nav_menu.phtml

@@ -6,9 +6,11 @@
 	<a class="btn toggle_aside" href="#aside_flux"><?php echo FreshRSS_Themes::icon('category'); ?></a>
 	<?php } ?>
 
-	<?php if ($this->loginOk) { ?>
+	<?php if ($this->loginOk || Minz_Configuration::allowAnonymousRefresh()) { ?>
 	<a id="actualize" class="btn" href="<?php echo _url ('feed', 'actualize'); ?>"><?php echo FreshRSS_Themes::icon('refresh'); ?></a>
+	<?php } ?>
 
+	<?php if ($this->loginOk) { ?>
 	<?php
 		$get = false;
 		$string_mark = Minz_Translate::t ('mark_all_read');

+ 0 - 1
app/sql.php

@@ -3,7 +3,6 @@ define('SQL_CREATE_TABLES', '
 CREATE TABLE IF NOT EXISTS `%1$scategory` (
 	`id` SMALLINT NOT NULL AUTO_INCREMENT,	-- v0.7
 	`name` varchar(255) NOT NULL,
-	`color` char(7),
 	PRIMARY KEY (`id`),
 	UNIQUE KEY (`name`)	-- v0.7
 ) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci

+ 5 - 1
app/views/configure/feed.phtml

@@ -7,8 +7,12 @@
 	<h1><?php echo $this->flux->name (); ?></h1>
 	<?php echo $this->flux->description (); ?>
 
+	<?php $nbEntries = $this->flux->nbEntries (); ?>
+
 	<?php if ($this->flux->inError ()) { ?>
 	<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 } elseif ($nbEntries === 0) { ?>
+	<p class="alert alert-warn"><?php echo Minz_Translate::t ('feed_empty'); ?></p>
 	<?php } ?>
 
 	<form method="post" action="<?php echo _url ('configure', 'feed', 'id', $this->flux->id ()); ?>" autocomplete="off">
@@ -81,7 +85,7 @@
 		<div class="form-group">
 			<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>
+				<span class="control"><?php echo $nbEntries; ?></span>
 			</div>
 		</div>
 		<div class="form-group">

+ 38 - 17
app/views/configure/shortcut.phtml

@@ -16,55 +16,59 @@
 
 		<noscript><p class="alert alert-error"><?php echo Minz_Translate::t ('javascript_for_shortcuts'); ?></p></noscript>
 
+		<legend><?php echo Minz_Translate::t ('shortcuts_navigation'); ?></legend>
+
 		<div class="form-group">
-			<label class="group-name" for="mark_read"><?php echo Minz_Translate::t ('mark_read'); ?></label>
+			<label class="group-name" for="next_entry"><?php echo Minz_Translate::t ('next_article'); ?></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 Minz_Translate::t ('shift_for_all_read'); ?>
+				<input type="text" id="next_entry" name="shortcuts[next_entry]" list="keys" value="<?php echo $s['next_entry']; ?>" />
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="mark_favorite"><?php echo Minz_Translate::t ('mark_favorite'); ?></label>
+			<label class="group-name" for="prev_entry"><?php echo Minz_Translate::t ('previous_article'); ?></label>
 			<div class="group-controls">
-				<input type="text" id="mark_favorite" name="shortcuts[mark_favorite]" list="keys" value="<?php echo $s['mark_favorite']; ?>" />
+				<input type="text" id="prev_entry" name="shortcuts[prev_entry]" list="keys" value="<?php echo $s['prev_entry']; ?>" />
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="go_website"><?php echo Minz_Translate::t ('see_on_website'); ?></label>
+			<label class="group-name" for="first_entry"><?php echo Minz_Translate::t ('first_article'); ?></label>
 			<div class="group-controls">
-				<input type="text" id="go_website" name="shortcuts[go_website]" list="keys" value="<?php echo $s['go_website']; ?>" />
+				<input type="text" id="first_entry" name="shortcuts[first_entry]" list="keys" value="<?php echo $s['first_entry']; ?>" />
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="next_entry"><?php echo Minz_Translate::t ('next_article'); ?></label>
+			<label class="group-name" for="last_entry"><?php echo Minz_Translate::t ('last_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 Minz_Translate::t ('shift_for_last'); ?>
+				<input type="text" id="last_entry" name="shortcuts[last_entry]" list="keys" value="<?php echo $s['last_entry']; ?>" />
 			</div>
 		</div>
 
+		<div><?php echo Minz_Translate::t ('shortcuts_navigation_help');?></div>
+
+		<legend><?php echo Minz_Translate::t ('shortcuts_article_action');?></legend>
+
 		<div class="form-group">
-			<label class="group-name" for="prev_entry"><?php echo Minz_Translate::t ('previous_article'); ?></label>
+			<label class="group-name" for="mark_read"><?php echo Minz_Translate::t ('mark_read'); ?></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 Minz_Translate::t ('shift_for_first'); ?>
+				<input type="text" id="mark_read" name="shortcuts[mark_read]" list="keys" value="<?php echo $s['mark_read']; ?>" />
+				<?php echo Minz_Translate::t ('shift_for_all_read'); ?>
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="collapse_entry"><?php echo Minz_Translate::t ('collapse_article'); ?></label>
+			<label class="group-name" for="mark_favorite"><?php echo Minz_Translate::t ('mark_favorite'); ?></label>
 			<div class="group-controls">
-				<input type="text" id="collapse_entry" name="shortcuts[collapse_entry]" list="keys" value="<?php echo $s['collapse_entry']; ?>" />
+				<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="load_more_shortcut"><?php echo Minz_Translate::t ('load_more'); ?></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="load_more_shortcut" name="shortcuts[load_more]" list="keys" value="<?php echo $s['load_more']; ?>" />
+				<input type="text" id="go_website" name="shortcuts[go_website]" list="keys" value="<?php echo $s['go_website']; ?>" />
 			</div>
 		</div>
 
@@ -72,6 +76,23 @@
 			<label class="group-name" for="auto_share_shortcut"><?php echo Minz_Translate::t ('auto_share'); ?></label>
 			<div class="group-controls">
 				<input type="text" id="auto_share_shortcut" name="shortcuts[auto_share]" list="keys" value="<?php echo $s['auto_share']; ?>" />
+				<?php echo Minz_Translate::t ('auto_share_help'); ?>
+			</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>
+
+		<legend><?php echo Minz_Translate::t ('shortcuts_other_action');?></legend>
+
+		<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>
 

+ 15 - 5
app/views/configure/users.phtml

@@ -20,7 +20,7 @@
 		<div class="form-group">
 			<label class="group-name" for="passwordPlain"><?php echo Minz_Translate::t('password_form'); ?></label>
 			<div class="group-controls">
-				<input type="password" id="passwordPlain" name="passwordPlain" pattern=".{7,}" />
+				<input type="password" id="passwordPlain" name="passwordPlain" autocomplete="off" pattern=".{7,}" />
 				<noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript>
 			</div>
 		</div>
@@ -29,7 +29,7 @@
 			<label class="group-name" for="mail_login"><?php echo Minz_Translate::t('persona_connection_email'); ?></label>
 			<?php $mail = $this->conf->mail_login; ?>
 			<div class="group-controls">
-				<input type="email" id="mail_login" name="mail_login" class="extend" value="<?php echo $mail; ?>" <?php echo Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_')) ? '' : 'disabled="disabled"'; ?> placeholder="alice@example.net" />
+				<input type="email" id="mail_login" name="mail_login" class="extend" autocomplete="off" value="<?php echo $mail; ?>" <?php echo Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_')) ? '' : 'disabled="disabled"'; ?> placeholder="alice@example.net" />
 				<noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript>
 			</div>
 		</div>
@@ -70,6 +70,16 @@
 			</div>
 		</div>
 
+		<div class="form-group">
+			<div class="group-controls">
+				<label class="checkbox" for="anon_refresh">
+					<input type="checkbox" name="anon_refresh" id="anon_refresh" value="1"<?php echo Minz_Configuration::allowAnonymousRefresh() ? ' checked="checked"' : '',
+						Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> />
+					<?php echo Minz_Translate::t('allow_anonymous_refresh'); ?>
+				</label>
+			</div>
+		</div>
+
 		<?php if (Minz_Configuration::canLogIn()) { ?>
 		<div class="form-group">
 			<label class="group-name" for="token"><?php echo Minz_Translate::t('auth_token'); ?></label>
@@ -129,14 +139,14 @@
 		<div class="form-group">
 			<label class="group-name" for="new_user_name"><?php echo Minz_Translate::t('username'); ?></label>
 			<div class="group-controls">
-				<input id="new_user_name" name="new_user_name" type="text" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" placeholder="demo" />
+				<input id="new_user_name" name="new_user_name" type="text" size="16" required="required" maxlength="16" autocomplete="off" pattern="[0-9a-zA-Z]{1,16}" placeholder="demo" />
 			</div>
 		</div>
 
 		<div class="form-group">
 			<label class="group-name" for="new_user_passwordPlain"><?php echo Minz_Translate::t('password_form'); ?></label>
 			<div class="group-controls">
-				<input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" pattern=".{7,}" />
+				<input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" autocomplete="off" pattern=".{7,}" />
 				<noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript>
 			</div>
 		</div>
@@ -145,7 +155,7 @@
 			<label class="group-name" for="new_user_email"><?php echo Minz_Translate::t('persona_connection_email'); ?></label>
 			<?php $mail = $this->conf->mail_login; ?>
 			<div class="group-controls">
-				<input type="email" id="new_user_email" name="new_user_email" class="extend" placeholder="alice@example.net" />
+				<input type="email" id="new_user_email" name="new_user_email" class="extend" autocomplete="off" placeholder="alice@example.net" />
 			</div>
 		</div>
 

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

@@ -3,7 +3,15 @@
 		<h1 class="alert-head"><?php echo $this->code; ?></h1>
 
 		<p>
-			<?php echo Minz_Translate::t ('page_not_found'); ?><br />
+			<?php
+			switch(Minz_Request::param ('code')) {
+			case 403:
+				echo Minz_Translate::t ('forbidden_access');
+				break;
+			case 404:
+			default:
+				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>

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

@@ -19,6 +19,8 @@ echo ',shortcuts={',
 	'go_website:"', $s['go_website'], '",',
 	'prev_entry:"', $s['prev_entry'], '",',
 	'next_entry:"', $s['next_entry'], '",',
+	'first_entry:"', $s['first_entry'], '",',
+	'last_entry:"', $s['last_entry'], '",',
 	'collapse_entry:"', $s['collapse_entry'], '",',
 	'load_more:"', $s['load_more'], '",',
 	'auto_share:"', $s['auto_share'], '"',
@@ -30,7 +32,13 @@ if (Minz_Request::param ('output') === 'global') {
 
 $authType = Minz_Configuration::authType();
 if ($authType === 'persona') {
-	echo 'current_user_mail="' . Minz_Session::param ('mail', '') . '",';
+	// If user is disconnected, current_user_mail MUST be null
+	$mail = Minz_Session::param ('mail', false);
+	if ($mail) {
+		echo 'current_user_mail="' . $mail . '",';
+	} else {
+		echo 'current_user_mail=null,';
+	}
 }
 
 echo 'authType="', $authType, '",',

+ 8 - 8
app/views/helpers/view/normal_view.phtml

@@ -147,49 +147,49 @@ if (!empty($this->entries)) {
 						<ul class="dropdown-menu">
 							<li class="dropdown-close"><a href="#close">❌</a></li>
 							<?php if ($shaarli) { ?>
-							<li class="item">
+							<li class="item share">
 								<a target="_blank" href="<?php echo $shaarli . '?post=' . $link . '&amp;title=' . $title . '&amp;source=FreshRSS'; ?>">
 									<?php echo Minz_Translate::t ('shaarli'); ?>
 								</a>
 							</li>
 							<?php } if ($wallabag) { ?>
-							<li class="item">
+							<li class="item share">
 								<a target="_blank" href="<?php echo $wallabag . '?action=add&amp;url=' . base64_encode (urldecode($link)); ?>">
 									<?php echo Minz_Translate::t ('wallabag'); ?>
 								</a>
 							</li>
 							<?php } if ($diaspora) { ?>
-							<li class="item">
+							<li class="item share">
 								<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">
+							<li class="item share">
 								<a target="_blank" href="https://twitter.com/share?url=<?php echo $link; ?>&amp;text=<?php echo $title; ?>">
 									<?php echo Minz_Translate::t ('twitter'); ?>
 								</a>
 							</li>
 							<?php } if ($google_plus) { ?>
-							<li class="item">
+							<li class="item share">
 								<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">
+							<li class="item share">
 								<a target="_blank" href="https://www.facebook.com/sharer.php?u=<?php echo $link; ?>&amp;t=<?php echo $title; ?>">
 									<?php echo Minz_Translate::t ('facebook'); ?>
 								</a>
 							</li>
 							<?php } if ($email) { ?>
-							<li class="item">
+							<li class="item share">
 								<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">
+							<li class="item share">
 								<a href="#" class="print-article">
 									<?php echo Minz_Translate::t ('print'); ?>
 								</a>

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

@@ -1,34 +1,32 @@
 <div class="prompt">
-<?php
-if (Minz_Configuration::canLogIn()) {
-	?><h1><?php echo Minz_Translate::t('login'); ?></h1><?php
-	switch (Minz_Configuration::authType()) {
+	<h1><?php echo Minz_Translate::t('login'); ?></h1><?php
 
-		case 'form':
-		?><form id="loginForm" method="post" action="<?php echo _url('index', 'formLogin'); ?>">
-			<p>
-				<label for="username"><?php echo Minz_Translate::t('username'); ?></label>
-				<input type="text" id="username" name="username" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" autofocus="autofocus" />
-			</p><p>
-				<label for="passwordPlain"><?php echo Minz_Translate::t('password'); ?></label>
-					<input type="password" id="passwordPlain" required="required" />
-					<input type="hidden" id="challenge" name="challenge" /><br />
-					<noscript><strong><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></strong></noscript>
-			</p><p>
-				<button id="loginButton" type="submit" class="btn btn-important"><?php echo Minz_Translate::t('login'); ?></button>
-			</p>
-		</form><?php
-			break;
+	switch (Minz_Configuration::authType()) {
+	case 'form':
+	?><form id="loginForm" method="post" action="<?php echo _url('index', 'formLogin'); ?>">
+		<div>
+			<label for="username"><?php echo Minz_Translate::t('username'); ?></label>
+			<input type="text" id="username" name="username" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" autofocus="autofocus" />
+		</div>
+		<div>
+			<label for="passwordPlain"><?php echo Minz_Translate::t('password'); ?></label>
+				<input type="password" id="passwordPlain" required="required" />
+				<input type="hidden" id="challenge" name="challenge" /><br />
+				<noscript><strong><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></strong></noscript>
+		</div>
+		<div>
+			<button id="loginButton" type="submit" class="btn btn-important"><?php echo Minz_Translate::t('login'); ?></button>
+		</div>
+	</form><?php
+		break;
 
-		case 'persona':
-			?><p><?php echo FreshRSS_Themes::icon('login'); ?> <a class="signin" href="#"><?php echo Minz_Translate::t('login'); ?></a></p><?php
-			break;
-	}
-} else {
-	?><h1>FreshRSS</h1>
-	<p><?php echo Minz_Translate::t('forbidden_access', Minz_Configuration::authType()); ?></p><?php
-}
-?>
+	case 'persona':
+		?><p>
+			<?php echo FreshRSS_Themes::icon('login'); ?>
+			<a class="signin" href="#"><?php echo Minz_Translate::t('login_with_persona'); ?></a>
+		</p><?php
+		break;
+	} ?>
 
-<p><a href="<?php echo _url('index', 'about'); ?>"><?php echo Minz_Translate::t('about_freshrss'); ?></a></p>
+	<p><a href="<?php echo _url('index', 'about'); ?>"><?php echo Minz_Translate::t('about_freshrss'); ?></a></p>
 </div>

+ 6 - 11
app/views/index/index.phtml

@@ -5,26 +5,21 @@ $output = Minz_Request::param ('output', 'normal');
 if ($this->loginOk || Minz_Configuration::allowAnonymous()) {
 	if ($output === 'normal') {
 		$this->renderHelper ('view/normal_view');
-	} elseif ($output === 'rss') {
-		$this->renderHelper ('view/rss_view');
 	} elseif ($output === 'reader') {
 		$this->renderHelper ('view/reader_view');
 	} elseif ($output === 'global') {
 		$this->renderHelper ('view/global_view');
+	} elseif ($output === 'rss') {
+		$this->renderHelper ('view/rss_view');
 	} else {
 		Minz_Request::_param ('output', 'normal');
 		$output = 'normal';
 		$this->renderHelper ('view/normal_view');
 	}
 } elseif ($output === 'rss') {
-	$token = $this->conf->token;
-	$token_param = Minz_Request::param ('token', '');
-	$token_is_ok = ($token != '' && $token == $token_param);
-	if ($token_is_ok) {
-		$this->renderHelper ('view/rss_view');
-	} else {
-		Minz_Request::forward(array('c' => 'index', 'a' => 'formLogin'), true);
-	}
+	// token has already been checked in the controller so we can show the view
+	$this->renderHelper ('view/rss_view');
 } else {
-	Minz_Request::forward(array('c' => 'index', 'a' => 'formLogin'), true);
+	// Normally, it should not happen, but log it anyway
+	Minz_Log::record ('Something is wrong in ' . __FILE__ . ' line ' . __LINE__, Minz_Log::ERROR);
 }

+ 33 - 20
app/views/javascript/actualize.phtml

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

+ 4 - 1
constants.php

@@ -1,5 +1,5 @@
 <?php
-define('FRESHRSS_VERSION', '0.7');
+define('FRESHRSS_VERSION', '0.8-dev');
 define('FRESHRSS_WEBSITE', 'http://freshrss.org');
 
 // Constantes de chemins
@@ -15,3 +15,6 @@ define('FRESHRSS_PATH', dirname(__FILE__));
 
 	define('LIB_PATH', FRESHRSS_PATH . '/lib');
 		define('APP_PATH', FRESHRSS_PATH . '/app');
+
+//define('TMP_PATH', sys_get_temp_dir());  // need more tests so...
+define('TMP_PATH', DATA_PATH);  // ... we use DATA_PATH for the 0.7.1

+ 18 - 1
lib/Minz/Configuration.php

@@ -52,6 +52,7 @@ class Minz_Configuration {
 	private static $delay_cache = 3600;
 	private static $default_user = '';
 	private static $allow_anonymous = false;
+	private static $allow_anonymous_refresh = false;
 	private static $auth_type = 'none';
 
 	private static $db = array (
@@ -118,6 +119,9 @@ class Minz_Configuration {
 	public static function allowAnonymous() {
 		return self::$allow_anonymous;
 	}
+	public static function allowAnonymousRefresh() {
+		return self::$allow_anonymous_refresh;
+	}
 	public static function authType() {
 		return self::$auth_type;
 	}
@@ -131,6 +135,9 @@ class Minz_Configuration {
 	public static function _allowAnonymous($allow = false) {
 		self::$allow_anonymous = ((bool)$allow) && self::canLogIn();
 	}
+	public static function _allowAnonymousRefresh($allow = false) {
+		self::$allow_anonymous_refresh = ((bool)$allow) && self::allowAnonymous();
+	}
 	public static function _authType($value) {
 		$value = strtolower($value);
 		switch ($value) {
@@ -170,6 +177,7 @@ class Minz_Configuration {
 				'title' => self::$title,
 				'default_user' => self::$default_user,
 				'allow_anonymous' => self::$allow_anonymous,
+				'allow_anonymous_refresh' => self::$allow_anonymous_refresh,
 				'auth_type' => self::$auth_type,
 			),
 			'db' => self::$db,
@@ -276,7 +284,16 @@ class Minz_Configuration {
 			self::_authType($general['auth_type']);
 		}
 		if (isset ($general['allow_anonymous'])) {
-			self::$allow_anonymous = ((bool)($general['allow_anonymous'])) && ($general['allow_anonymous'] !== 'no');
+			self::$allow_anonymous = (
+				((bool)($general['allow_anonymous'])) &&
+				($general['allow_anonymous'] !== 'no')
+			);
+		}
+		if (isset ($general['allow_anonymous_refresh'])) {
+			self::$allow_anonymous_refresh = (
+				((bool)($general['allow_anonymous_refresh'])) &&
+				($general['allow_anonymous_refresh'] !== 'no')
+			);
 		}
 
 		// Base de données

+ 1 - 1
lib/Minz/Request.php

@@ -199,6 +199,6 @@ class Minz_Request {
 	}
 
 	public static function isPost () {
-		return !empty ($_POST) || !empty ($_FILES);
+		return $_SERVER['REQUEST_METHOD'] === 'POST';
 	}
 }

+ 61 - 12
lib/SimplePie/SimplePie.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev-FreshRSS
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon
@@ -50,7 +50,7 @@ define('SIMPLEPIE_NAME', 'SimplePie');
 /**
  * SimplePie Version
  */
-define('SIMPLEPIE_VERSION', '1.3.1');
+define('SIMPLEPIE_VERSION', '1.4-dev-FreshRSS');
 
 /**
  * SimplePie Build
@@ -602,7 +602,7 @@ class SimplePie
 	public $strip_attributes = array('bgsound', 'class', 'expr', 'id', 'style', 'onclick', 'onerror', 'onfinish', 'onmouseover', 'onmouseout', 'onfocus', 'onblur', 'lowsrc', 'dynsrc');
 
 	/**
-	 * @var array Stores the default attributes to add to differet tags by add_attributes().
+	 * @var array Stores the default attributes to add to different tags by add_attributes().
 	 * @see SimplePie::add_attributes()
 	 * @access private
 	 */
@@ -644,7 +644,7 @@ class SimplePie
 		if (func_num_args() > 0)
 		{
 			$level = defined('E_USER_DEPRECATED') ? E_USER_DEPRECATED : E_USER_WARNING;
-			trigger_error('Passing parameters to the constructor is no longer supported. Please use set_feed_url(), set_cache_location(), and set_cache_location() directly.', $level);
+			trigger_error('Passing parameters to the constructor is no longer supported. Please use set_feed_url(), set_cache_location(), and set_cache_duration() directly.', $level);
 
 			$args = func_get_args();
 			switch (count($args)) {
@@ -1212,6 +1212,20 @@ class SimplePie
 		$this->item_limit = (int) $limit;
 	}
 
+	/**
+	 * Enable throwing exceptions
+	 *
+	 * @param boolean $enable Should we throw exceptions, or use the old-style error property?
+	 */
+	public function enable_exceptions($enable = true)
+	{
+		$this->enable_exceptions = $enable;
+	}
+
+	function cleanMd5($rss) {	//FreshRSS
+		return md5(preg_replace(array('#<(lastBuildDate|pubDate|updated|feedDate|dc:date|slash:comments)>[^<]+</\\1>#', '#<!--.+?-->#s'), '', $rss));
+	}
+
 	/**
 	 * Initialize the feed object
 	 *
@@ -1219,7 +1233,7 @@ class SimplePie
 	 * configuration options get processed, feeds are fetched, cached, and
 	 * parsed, and all of that other good stuff.
 	 *
-	 * @return boolean True if successful, false otherwise
+	 * @return positive integer with modification time if using cache, boolean true if otherwise successful, false otherwise
 	 */
 	public function init()
 	{
@@ -1298,13 +1312,17 @@ class SimplePie
 			// Fetch the data via SimplePie_File into $this->raw_data
 			if (($fetched = $this->fetch_data($cache)) === true)
 			{
-				return true;
+				return $this->data['mtime'];	//FreshRSS
 			}
 			elseif ($fetched === false) {
 				return false;
 			}
 
 			list($headers, $sniffed) = $fetched;
+
+			if (isset($this->data['md5'])) {	//FreshRSS
+				$md5 = $this->data['md5'];
+			}
 		}
 
 		// Set up array of possible encodings
@@ -1313,7 +1331,7 @@ class SimplePie
 		// First check to see if input has been overridden.
 		if ($this->input_encoding !== false)
 		{
-			$encodings[] = strtoupper($this->input_encoding);
+			$encodings[] = strtoupper($this->input_encoding);	//FreshRSS
 		}
 
 		$application_types = array('application/xml', 'application/xml-dtd', 'application/xml-external-parsed-entity');
@@ -1337,7 +1355,7 @@ class SimplePie
 			{
 				if (isset($headers['content-type']) && preg_match('/;\x20?charset=([^;]*)/i', $headers['content-type'], $charset))
 				{
-					$encodings[] = strtoupper($charset[1]);
+					$encodings[] = strtoupper($charset[1]);	//FreshRSS
 				}
 				else
 				{
@@ -1386,6 +1404,8 @@ class SimplePie
 						$this->data['headers'] = $headers;
 					}
 					$this->data['build'] = SIMPLEPIE_BUILD;
+					$this->data['mtime'] = time();	//FreshRSS
+					$this->data['md5'] = empty($md5) ? $this->cleanMd5($this->raw_data) : $md5;	//FreshRSS
 
 					// Cache the file if caching is enabled
 					if ($cache && !$cache->save($this))
@@ -1461,7 +1481,7 @@ class SimplePie
 				elseif ($cache->mtime() + $this->cache_duration < time())
 				{
 					// If we have last-modified and/or etag set
-					if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag']))
+					//if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag']))	//FreshRSS removed
 					{
 						$headers = array(
 							'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1',
@@ -1475,7 +1495,7 @@ class SimplePie
 							$headers['if-none-match'] = $this->data['headers']['etag'];
 						}
 
-						$file = $this->registry->create('File', array($this->feed_url, $this->timeout/10, 5, $headers, $this->useragent, $this->force_fsockopen));
+						$file = $this->registry->create('File', array($this->feed_url, $this->timeout, 5, $headers, $this->useragent, $this->force_fsockopen));	//FreshRSS
 
 						if ($file->success)
 						{
@@ -1487,7 +1507,20 @@ class SimplePie
 						}
 						else
 						{
-							unset($file);
+							$this->error = $file->error;	//FreshRSS
+							return !empty($this->data);	//FreshRSS
+							//unset($file);	//FreshRSS removed
+						}
+					}
+					{	//FreshRSS
+						$md5 = $this->cleanMd5($file->body);
+						if ($this->data['md5'] === $md5) {
+							syslog(LOG_DEBUG, 'SimplePie MD5 cache match for ' . $this->feed_url);
+							$cache->touch();
+							return true;	//Content unchanged even though server did not send a 304
+						} else {
+							syslog(LOG_DEBUG, 'SimplePie MD5 cache no match for ' . $this->feed_url);
+							$this->data['md5'] = $md5;
 						}
 					}
 				}
@@ -1555,6 +1588,8 @@ class SimplePie
 				if ($cache)
 				{
 					$this->data = array('url' => $this->feed_url, 'feed_url' => $file->url, 'build' => SIMPLEPIE_BUILD);
+					$this->data['mtime'] = time();	//FreshRSS
+					$this->data['md5'] = empty($md5) ? $this->cleanMd5($file->body) : $md5;	//FreshRSS
 					if (!$cache->save($this))
 					{
 						trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING);
@@ -1987,7 +2022,21 @@ class SimplePie
 	 */
 	public function sanitize($data, $type, $base = '')
 	{
-		return $this->sanitize->sanitize($data, $type, $base);
+		try
+		{
+			return $this->sanitize->sanitize($data, $type, $base);
+		}
+		catch (SimplePie_Exception $e)
+		{
+			if (!$this->enable_exceptions)
+			{
+				$this->error = $e->getMessage();
+				$this->registry->call('Misc', 'error', array($this->error, E_USER_WARNING, $e->getFile(), $e->getLine()));
+				return '';
+			}
+
+			throw $e;
+		}
 	}
 
 	/**

+ 1 - 1
lib/SimplePie/SimplePie/Author.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/Cache.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/Cache/Base.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/Cache/DB.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

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

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 5 - 7
lib/SimplePie/SimplePie/Cache/Memcache.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon
@@ -95,10 +95,8 @@ class SimplePie_Cache_Memcache implements SimplePie_Cache_Base
 				'prefix' => 'simplepie_',
 			),
 		);
-		$parsed = SimplePie_Cache::parse_URL($location);
-		$this->options['host'] = empty($parsed['host']) ? $this->options['host'] : $parsed['host'];
-		$this->options['port'] = empty($parsed['port']) ? $this->options['port'] : $parsed['port'];
-		$this->options['extras'] = array_merge($this->options['extras'], $parsed['extras']);
+		$this->options = SimplePie_Misc::array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location));
+
 		$this->name = $this->options['extras']['prefix'] . md5("$name:$type");
 
 		$this->cache = new Memcache();
@@ -147,7 +145,7 @@ class SimplePie_Cache_Memcache implements SimplePie_Cache_Base
 
 		if ($data !== false)
 		{
-			// essentially ignore the mtime because Memcache expires on it's own
+			// essentially ignore the mtime because Memcache expires on its own
 			return time();
 		}
 
@@ -165,7 +163,7 @@ class SimplePie_Cache_Memcache implements SimplePie_Cache_Base
 
 		if ($data !== false)
 		{
-			return $this->cache->set($this->name, $data, MEMCACHE_COMPRESSED, (int) $this->duration);
+			return $this->cache->set($this->name, $data, MEMCACHE_COMPRESSED, (int) $this->options['extras']['timeout']);
 		}
 
 		return false;

+ 4 - 3
lib/SimplePie/SimplePie/Cache/MySQL.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon
@@ -96,7 +96,8 @@ class SimplePie_Cache_MySQL extends SimplePie_Cache_DB
 				'prefix' => '',
 			),
 		);
-		$this->options = array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location));
+		
+		$this->options = SimplePie_Misc::array_merge_recursive($this->options, SimplePie_Cache::parse_URL($location));
 
 		// Path is prefixed with a "/"
 		$this->options['dbname'] = substr($this->options['path'], 1);
@@ -136,7 +137,7 @@ class SimplePie_Cache_MySQL extends SimplePie_Cache_DB
 
 		if (!in_array($this->options['extras']['prefix'] . 'items', $db))
 		{
-			$query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` TEXT CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))');
+			$query = $this->mysql->exec('CREATE TABLE `' . $this->options['extras']['prefix'] . 'items` (`feed_id` TEXT CHARACTER SET utf8 NOT NULL, `id` TEXT CHARACTER SET utf8 NOT NULL, `data` MEDIUMBLOB CHARACTER SET utf8 NOT NULL, `posted` INT UNSIGNED NOT NULL, INDEX `feed_id` (`feed_id`(125)))');
 			if ($query === false)
 			{
 				$this->mysql = null;

+ 1 - 1
lib/SimplePie/SimplePie/Caption.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/Category.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/Content/Type/Sniffer.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/Copyright.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/Core.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/Credit.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/Decode/HTML/Entities.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 2 - 2
lib/SimplePie/SimplePie/Enclosure.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon
@@ -942,7 +942,7 @@ class SimplePie_Enclosure
 	 * - `height` (integer): The height of the embedded media. Accepts any
 	 *    numeric pixel value (such as `360`) or `auto`. Defaults to `auto`,
 	 *    and it is recommended that you use this default.
-	 * - `loop` (boolean): Do you want the media to loop when its done?
+	 * - `loop` (boolean): Do you want the media to loop when it's done?
 	 *    Defaults to `false`.
 	 * - `mediaplayer` (string): The location of the included
 	 *    `mediaplayer.swf` file. This allows for the playback of Flash Video

+ 3 - 3
lib/SimplePie/SimplePie/File.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon
@@ -108,7 +108,7 @@ class SimplePie_File
 				curl_setopt($fp, CURLOPT_REFERER, $url);
 				curl_setopt($fp, CURLOPT_USERAGENT, $useragent);
 				curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2);
-				curl_setopt($fp, CURLOPT_SSL_VERIFYPEER, false);
+				curl_setopt($fp, CURLOPT_SSL_VERIFYPEER, false);	//FreshRSS
 				if (!ini_get('open_basedir') && !ini_get('safe_mode') && version_compare(SimplePie_Misc::get_curl_version(), '7.15.2', '>='))
 				{
 					curl_setopt($fp, CURLOPT_FOLLOWLOCATION, 1);
@@ -284,7 +284,7 @@ class SimplePie_File
 		else
 		{
 			$this->method = SIMPLEPIE_FILE_SOURCE_LOCAL | SIMPLEPIE_FILE_SOURCE_FILE_GET_CONTENTS;
-			if (!$this->body = file_get_contents($url))
+			if (empty($url) || !($this->body = file_get_contents($url)))
 			{
 				$this->error = 'file_get_contents could not read the file';
 				$this->success = false;

+ 1 - 1
lib/SimplePie/SimplePie/HTTP/Parser.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/IRI.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 3 - 3
lib/SimplePie/SimplePie/Item.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon
@@ -821,7 +821,7 @@ class SimplePie_Item
 			if (!empty($this->data['updated']['raw']))
 			{
 				$parser = $this->registry->call('Parse_Date', 'get');
-				$this->data['updated']['parsed'] = $parser->parse($this->data['date']['raw']);
+				$this->data['updated']['parsed'] = $parser->parse($this->data['updated']['raw']);
 			}
 			else
 			{
@@ -1080,7 +1080,7 @@ class SimplePie_Item
 	 *
 	 * @since Beta 2
 	 * @todo Add support for end-user defined sorting of enclosures by type/handler (so we can prefer the faster-loading FLV over MP4).
-	 * @todo If an element exists at a level, but it's value is empty, we should fall back to the value from the parent (if it exists).
+	 * @todo If an element exists at a level, but its value is empty, we should fall back to the value from the parent (if it exists).
 	 * @return array|null List of SimplePie_Enclosure items
 	 */
 	public function get_enclosures()

+ 2 - 2
lib/SimplePie/SimplePie/Locator.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon
@@ -277,7 +277,7 @@ class SimplePie_Locator
 				$parsed = $this->registry->call('Misc', 'parse_url', array($href));
 				if ($parsed['scheme'] === '' || preg_match('/^(http(s)|feed)?$/i', $parsed['scheme']))
 				{
-					if ($this->base_location < $link->getLineNo())
+					if (method_exists($link, 'getLineNo') && $this->base_location < $link->getLineNo())
 					{
 						$href = $this->registry->call('Misc', 'absolutize_url', array(trim($link->getAttribute('href')), $this->base));
 					}

+ 23 - 30
lib/SimplePie/SimplePie/Misc.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon
@@ -128,7 +128,7 @@ class SimplePie_Misc
 						{
 							$attribs[$j][2] = $attribs[$j][1];
 						}
-						$return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8');
+						$return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8');	//FreshRSS
 					}
 				}
 			}
@@ -142,7 +142,7 @@ class SimplePie_Misc
 		foreach ($element['attribs'] as $key => $value)
 		{
 			$key = strtolower($key);
-			$full .= " $key=\"" . htmlspecialchars($value['data'], ENT_COMPAT, 'UTF-8') . '"';
+			$full .= " $key=\"" . htmlspecialchars($value['data'], ENT_COMPAT, 'UTF-8') . '"';	//FreshRSS
 		}
 		if ($element['self_closing'])
 		{
@@ -228,6 +228,23 @@ class SimplePie_Misc
 		}
 	}
 
+	public static function array_merge_recursive($array1, $array2)
+	{
+		foreach ($array2 as $key => $value)
+		{
+			if (is_array($value))
+			{
+				$array1[$key] = SimplePie_Misc::array_merge_recursive($array1[$key], $value);
+			}
+			else
+			{
+				$array1[$key] = $value;
+			}            
+		}
+		
+		return $array1;
+	}
+
 	public static function parse_url($url)
 	{
 		$iri = new SimplePie_IRI($url);
@@ -2161,36 +2178,12 @@ function embed_wmedia(width, height, link) {
 	/**
 	 * Get the SimplePie build timestamp
 	 *
-	 * Uses the git index if it exists, otherwise uses the modification time
-	 * of the newest file.
+	 * Return SimplePie.php modification time.
 	 */
 	public static function get_build()
 	{
-		$root = dirname(dirname(__FILE__));
-		if (file_exists($root . '/.git/index'))
-		{
-			return filemtime($root . '/.git/index');
-		}
-		elseif (file_exists($root . '/SimplePie'))
-		{
-			$time = 0;
-			foreach (glob($root . '/SimplePie/*.php') as $file)
-			{
-				if (($mtime = filemtime($file)) > $time)
-				{
-					$time = $mtime;
-				}
-			}
-			return $time;
-		}
-		elseif (file_exists(dirname(__FILE__) . '/Core.php'))
-		{
-			return filemtime(dirname(__FILE__) . '/Core.php');
-		}
-		else
-		{
-			return filemtime(__FILE__);
-		}
+		$mtime = @filemtime(dirname(dirname(__FILE__)) . '/SimplePie.php');	//FreshRSS
+		return $mtime ? $mtime : filemtime(__FILE__);
 	}
 
 	/**

+ 1 - 1
lib/SimplePie/SimplePie/Net/IPv6.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/Parse/Date.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 6 - 1
lib/SimplePie/SimplePie/Parser.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon
@@ -145,10 +145,15 @@ class SimplePie_Parser
 				$dom->loadXML($data);
 				$this->encoding = $encoding = $dom->encoding = 'UTF-8';
 				$data2 = $dom->saveXML();
+				if (function_exists('mb_convert_encoding'))
+				{
+					$data2 = mb_convert_encoding($data2, 'UTF-8', 'UTF-8');
+				}
 				if (strlen($data2) > (strlen($data) / 2.0))
 				{
 					$data = $data2;
 				}
+				unset($data2);
 			}
 			catch (Exception $e)
 			{

+ 1 - 1
lib/SimplePie/SimplePie/Rating.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/Registry.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/Restriction.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 6 - 2
lib/SimplePie/SimplePie/Sanitize.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon
@@ -267,6 +267,10 @@ class SimplePie_Sanitize
 			if ($type & (SIMPLEPIE_CONSTRUCT_HTML | SIMPLEPIE_CONSTRUCT_XHTML))
 			{
 
+				if (!class_exists('DOMDocument'))
+				{
+					throw new SimplePie_Exception('DOMDocument not found, unable to use sanitizer');
+				}
 				$document = new DOMDocument();
 				$document->encoding = 'UTF-8';
 				$data = $this->preprocess($data, $type);
@@ -339,7 +343,7 @@ class SimplePie_Sanitize
 							}
 							else
 							{
-								$file = $this->registry->create('File', array($img['attribs']['src']['data'], $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen));
+								$file = $this->registry->create('File', array($img->getAttribute('src'), $this->timeout, 5, array('X-FORWARDED-FOR' => $_SERVER['REMOTE_ADDR']), $this->useragent, $this->force_fsockopen));
 								$headers = $file->headers;
 
 								if ($file->success && ($file->method & SIMPLEPIE_FILE_SOURCE_REMOTE === 0 || ($file->status_code === 200 || $file->status_code > 206 && $file->status_code < 300)))

+ 1 - 1
lib/SimplePie/SimplePie/Source.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/XML/Declaration/Parser.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 1
lib/SimplePie/SimplePie/gzdecode.php

@@ -33,7 +33,7 @@
  * POSSIBILITY OF SUCH DAMAGE.
  *
  * @package SimplePie
- * @version 1.3.1
+ * @version 1.4-dev
  * @copyright 2004-2012 Ryan Parman, Geoffrey Sneddon, Ryan McCue
  * @author Ryan Parman
  * @author Geoffrey Sneddon

+ 1 - 2
lib/lib_opml.php

@@ -61,8 +61,7 @@ function opml_import ($xml) {
 				if ($cat === false) {
 					$cat = new FreshRSS_Category ($title);
 					$values = array (
-						'name' => $cat->name (),
-						'color' => $cat->color ()
+						'name' => $cat->name ()
 					);
 					$cat->_id ($catDAO->addCategory ($values));
 				}

+ 1 - 1
lib/lib_rss.php

@@ -214,8 +214,8 @@ function uSecString() {
 }
 
 function invalidateHttpCache() {
-	touch(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log');
 	Minz_Session::_param('touch', uTimeString());
+	return touch(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log');
 }
 
 function usernameFromPath($userPath) {

+ 7 - 6
p/i/install.php

@@ -44,8 +44,8 @@ UPDATE `%1$sfeed006` f
 INNER JOIN `%1$scategory006` c ON f.category = c.id
 SET f.category2 = c.id2;
 
-INSERT IGNORE INTO `%2$scategory` (name, color)
-SELECT name, color
+INSERT IGNORE INTO `%2$scategory` (name)
+SELECT name
 FROM `%1$scategory006`
 ORDER BY id2;
 
@@ -548,8 +548,9 @@ function checkStep0 () {
 		'all' => $language ? 'ok' : 'ko'
 	);
 }
+
 function checkStep1 () {
-	$php = version_compare (PHP_VERSION, '5.2.0') >= 0;
+	$php = version_compare (PHP_VERSION, '5.2.1') >= 0;
 	$minz = file_exists (LIB_PATH . '/Minz');
 	$curl = extension_loaded ('curl');
 	$pdo = extension_loaded ('pdo_mysql');
@@ -721,7 +722,7 @@ function printStep1 () {
 	<?php if ($res['php'] == 'ok') { ?>
 	<p class="alert alert-success"><span class="alert-head"><?php echo _t ('ok'); ?></span> <?php echo _t ('php_is_ok', PHP_VERSION); ?></p>
 	<?php } else { ?>
-	<p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('php_is_nok', PHP_VERSION, '5.1.0'); ?></p>
+	<p class="alert alert-error"><span class="alert-head"><?php echo _t ('damn'); ?></span> <?php echo _t ('php_is_nok', PHP_VERSION, '5.2.1'); ?></p>
 	<?php } ?>
 
 	<?php if ($res['minz'] == 'ok') { ?>
@@ -954,7 +955,7 @@ function printStep4 () {
 		<legend><?php echo _t ('version_update'); ?></legend>
 
 		<?php if (updateDatabase(false)) { ?>
-		<p class="alert"><?php echo _t ('update_long'); ?></p>
+		<p class="alert alert-warn"><?php echo _t ('update_long'); ?></p>
 
 		<div class="form-group form-actions">
 			<div class="group-controls">
@@ -964,7 +965,7 @@ function printStep4 () {
 		</div>
 
 		<?php } else { ?>
-		<p class="alert"><?php echo _t ('update_end'); ?></p>
+		<p class="alert alert-warn"><?php echo _t ('update_end'); ?></p>
 
 		<div class="form-group form-actions">
 			<div class="group-controls">

+ 226 - 26
p/scripts/main.js

@@ -20,6 +20,16 @@ function redirect(url, new_tab) {
 	}
 }
 
+function needsScroll($elem) {
+	var $win = $(window),
+		winTop = $win.scrollTop(),
+		winHeight = $win.height(),
+		winBottom = winTop + winHeight,
+		elemTop = $elem.offset().top,
+		elemBottom = elemTop + $elem.outerHeight();
+	return (elemTop < winTop || elemBottom > winBottom) ? elemTop - (winHeight / 2) : 0;
+}
+
 function str2int(str) {
 	if (str == '') {
 		return 0;
@@ -96,8 +106,10 @@ function incUnreadsFeed(article, feed_id, nb) {
 	return isCurrentView;
 }
 
+var pending_feeds = [];
 function mark_read(active, only_not_read) {
-	if (active.length === 0 || (only_not_read === true && !active.hasClass("not_read"))) {
+	if (active.length === 0 ||
+		(only_not_read === true && !active.hasClass("not_read"))) {
 		return false;
 	}
 
@@ -106,6 +118,16 @@ function mark_read(active, only_not_read) {
 		return false;
 	}
 
+	var feed_url = active.find(".website>a").attr("href"),
+		feed_id = feed_url.substr(feed_url.lastIndexOf('f_')),
+		index_pending = pending_feeds.indexOf(feed_id);
+
+	if (index_pending !== -1) {
+		return false;
+	}
+
+	pending_feeds.push(feed_id);
+
 	$.ajax({
 		type: 'POST',
 		url: url,
@@ -122,9 +144,9 @@ function mark_read(active, only_not_read) {
 		}
 		$r.find('.icon').replaceWith(data.icon);
 
-		var feed_url = active.find(".website>a").attr("href"),
-			feed_id = feed_url.substr(feed_url.lastIndexOf('f_'));
 		incUnreadsFeed(active, feed_id, inc);
+
+		pending_feeds.splice(index_pending, 1);
 	});
 }
 
@@ -138,6 +160,16 @@ function mark_favorite(active) {
 		return false;
 	}
 
+	var feed_url = active.find(".website>a").attr("href"),
+		feed_id = feed_url.substr(feed_url.lastIndexOf('f_')),
+		index_pending = pending_feeds.indexOf(feed_id);
+
+	if (index_pending !== -1) {
+		return false;
+	}
+
+	pending_feeds.push(feed_id);
+
 	$.ajax({
 		type: 'POST',
 		url: url,
@@ -168,16 +200,20 @@ function mark_favorite(active) {
 				elem.setAttribute('data-unread', numberFormat(feed_unreads + inc));
 			}
 		}
+
+		pending_feeds.splice(index_pending, 1);
 	});
 }
 
 function toggleContent(new_active, old_active) {
-	old_active.removeClass("active").removeClass("current");
+	old_active.removeClass("active");
 
 	if (new_active.length === 0) {
 		return;
 	}
 
+	old_active.removeClass("current");
+
 	if (does_lazyload) {
 		new_active.find('img[data-original], iframe[data-original]').each(function () {
 			this.setAttribute('src', this.getAttribute('data-original'));
@@ -247,15 +283,111 @@ function next_entry() {
 	}
 }
 
+function prev_feed() {
+	var active_feed = $("#aside_flux .feeds li.active");
+	if (active_feed.length > 0) {
+		active_feed.prev().find('a.feed').each(function(){this.click();});
+	} else {
+		last_feed();
+	}
+}
+
+function next_feed() {
+	var active_feed = $("#aside_flux .feeds li.active");
+	if (active_feed.length > 0) {
+		active_feed.next().find('a.feed').each(function(){this.click();});
+	} else {
+		first_feed();
+	}
+}
+
+function first_feed() {
+	var feed = $("#aside_flux .feeds.active li:first");
+	if (feed.length > 0) {
+		feed.find('a')[1].click();
+	}
+}
+
+function last_feed() {
+	var feed = $("#aside_flux .feeds.active li:last");
+	if (feed.length > 0) {
+		feed.find('a')[1].click();
+	}
+}
+
+function prev_category() {
+	var active_cat = $("#aside_flux .category.stick.active");
+
+	if (active_cat.length > 0) {
+		var prev_cat = active_cat.parent('li').prev().find('.category.stick a.btn');
+		if (prev_cat.length > 0) {
+			prev_cat[0].click();
+		}
+	} else {
+		last_category();
+	}
+	return;
+}
+
+function next_category() {
+	var active_cat = $("#aside_flux .category.stick.active");
+
+	if (active_cat.length > 0) {
+		var next_cat = active_cat.parent('li').next().find('.category.stick a.btn');
+		if (next_cat.length > 0) {
+			next_cat[0].click();
+		}
+	} else {
+		first_category();
+	}
+	return;
+}
+
+function first_category() {
+	var cat = $("#aside_flux .category.stick:first");
+	if (cat.length > 0) {
+		cat.find('a.btn')[0].click();
+	}
+}
+
+function last_category() {
+	var cat = $("#aside_flux .category.stick:last");
+	if (cat.length > 0) {
+		cat.find('a.btn')[0].click();
+	}
+}
+
 function collapse_entry() {
 	isCollapsed = !isCollapsed;
 	$(".flux.current").toggleClass("active");
 }
 
-function auto_share() {
+function auto_share(key) {
 	var share = $(".flux.current.active").find('.dropdown-target[id^="dropdown-share"]');
-	if (share.length) {
+	var shares = share.siblings('.dropdown-menu').find('.item a');
+	if (typeof key === "undefined") {
+		if (!share.length) {
+			return;
+		}
+		// Display the share div
 		window.location.hash = share.attr('id');
+		// Force scrolling to the share div
+		var scroll = needsScroll(share.closest('.bottom'));
+		if (scroll !== 0) {
+			$('html,body').scrollTop(scroll);
+		}
+		// Force the key value if there is only one action, so we can trigger it automatically
+		if (shares.length === 1) {
+			key = 1;
+		} else {
+			return;
+		}
+	}
+	// Trigger selected share action and hide the share div
+	key = parseInt(key);
+	if (key <= shares.length) {
+		shares[key - 1].click();
+		share.siblings('.dropdown-menu').find('.dropdown-close a')[0].click();
 	}
 }
 
@@ -390,12 +522,19 @@ function init_shortcuts() {
 	}, {
 		'disable_in_input': true
 	});
+	for(var i = 1; i < 10; i++){
+		shortcut.add(i.toString(), function (e) {
+			auto_share(String.fromCharCode(e.keyCode));
+		}, {
+			'disable_in_input': true
+		});
+	}
 
-	// Touches de navigation
+	// Touches de navigation pour les articles
 	shortcut.add(shortcuts.prev_entry, prev_entry, {
 		'disable_in_input': true
 	});
-	shortcut.add("shift+" + shortcuts.prev_entry, function () {
+	shortcut.add(shortcuts.first_entry, function () {
 		var old_active = $(".flux.current"),
 			first = $(".flux:first");
 
@@ -408,7 +547,7 @@ function init_shortcuts() {
 	shortcut.add(shortcuts.next_entry, next_entry, {
 		'disable_in_input': true
 	});
-	shortcut.add("shift+" + shortcuts.next_entry, function () {
+	shortcut.add(shortcuts.last_entry, function () {
 		var old_active = $(".flux.current"),
 			last = $(".flux:last");
 
@@ -418,8 +557,35 @@ function init_shortcuts() {
 	}, {
 		'disable_in_input': true
 	});
+	// Touches de navigation pour les flux
+	shortcut.add("shift+" + shortcuts.prev_entry, prev_feed, {
+		'disable_in_input': true
+	});
+	shortcut.add("shift+" + shortcuts.next_entry, next_feed, {
+		'disable_in_input': true
+	});
+	shortcut.add("shift+" + shortcuts.first_entry, first_feed, {
+		'disable_in_input': true
+	});
+	shortcut.add("shift+" + shortcuts.last_entry, last_feed, {
+		'disable_in_input': true
+	});
+	// Touches de navigation pour les categories
+	shortcut.add("alt+" + shortcuts.prev_entry, prev_category, {
+		'disable_in_input': true
+	});
+	shortcut.add("alt+" + shortcuts.next_entry, next_category, {
+		'disable_in_input': true
+	});
+	shortcut.add("alt+" + shortcuts.first_entry, first_category, {
+		'disable_in_input': true
+	});
+	shortcut.add("alt+" + shortcuts.last_entry, last_category, {
+		'disable_in_input': true
+	});
+
 	shortcut.add(shortcuts.go_website, function () {
-		var url_website = $(".flux.active .link a").attr("href");
+		var url_website = $(".flux.current .link a").attr("href");
 
 		if (auto_mark_site) {
 			$(".flux.current").each(function () {
@@ -441,7 +607,7 @@ function init_shortcuts() {
 
 function init_stream(divStream) {
 	divStream.on('click', '.flux_header', function (e) {	//flux_header_toggle
-		if ($(e.target).closest('.item.website > a').length > 0) {
+		if ($(e.target).closest('.item.website, .item.link').length > 0) {
 			return;
 		}
 		var old_active = $(".flux.current"),
@@ -468,7 +634,7 @@ function init_stream(divStream) {
 		return false;
 	});
 
-	divStream.on('click', '.item.title>a', function (e) {
+	divStream.on('click', '.item.title > a', function (e) {
 		if (e.ctrlKey) {
 			return true;	//Allow default control-click behaviour such as open in backround-tab
 		}
@@ -481,7 +647,7 @@ function init_stream(divStream) {
 	});
 
 	if (auto_mark_site) {
-		divStream.on('click', '.flux .link a', function () {
+		divStream.on('click', '.flux .link > a', function () {
 			mark_read($(this).parent().parent().parent(), true);
 		});
 	}
@@ -512,37 +678,71 @@ function init_nav_entries() {
 }
 
 function init_actualize() {
+	var auto = false;
+
 	$("#actualize").click(function () {
 		$.getScript('./?c=javascript&a=actualize').done(function () {
+			if (auto && feed_count < 1) {
+				auto = false;
+				return;
+			}
+
 			updateFeeds();
 		});
 		return false;
 	});
 
-	if(auto_actualize_feeds) {
-		$.getScript('./?c=javascript&a=actualize').done(function () {
-			updateFeeds();
-		});
+	if (auto_actualize_feeds) {
+		auto = true;
+		$("#actualize").click();
 	}
 }
 
+
+// <notification>
+var notification = null,
+	notification_interval = null,
+	notification_working = false;
+
+function openNotification(msg, status) {
+	if (notification_working === true) {
+		return false;
+	}
+
+	notification_working = true;
+
+	notification.removeClass();
+	notification.addClass(status);
+	notification.find(".msg").html(msg);
+	notification.fadeIn(300);
+
+	notification_interval = window.setInterval(closeNotification, 4000);
+}
+
 function closeNotification() {
-	$(".notification").fadeOut(600, function () {
-		$(".notification").remove();
+	notification.fadeOut(600, function() {
+		notification.removeClass();
+		notification.addClass('closed');
+
+		window.clearInterval(notification_interval);
+		notification_working = false;
 	});
 }
 
 function init_notifications() {
-	var notif = $(".notification");
-	if (notif.length > 0) {
-		window.setInterval(closeNotification, 4000);
+	notification = $("#notification");
 
-		notif.find("a.close").click(function () {
-			closeNotification();
-			return false;
-		});
+	notification.find("a.close").click(function () {
+		closeNotification();
+		return false;
+	});
+
+	if (notification.find(".msg").html().length > 0) {
+		notification_working = true;
+		notification_interval = window.setInterval(closeNotification, 4000);
 	}
 }
+// </notification>
 
 function refreshUnreads() {
 	$.getJSON('./?c=javascript&a=nbUnreadsPerFeed').done(function (data) {

+ 29 - 19
p/themes/Dark/freshrss.css

@@ -354,13 +354,15 @@
 			font-size: 120%;
 		}
 			#stream.global .btn:not([data-unread="0"]) {
-				font-weight:bold;
+				background: #34495e;
+				color: #fff;
+				font-weight: bold;
 			}
 			#stream.global .btn:first-child:not([data-unread="0"]):after {
 				top: 0; right: 5px;
 				border: 0;
 				background: none;
-				color: #666;
+				color: #fff;
 				font-weight: bold;
 				box-shadow: none;
 			}
@@ -558,7 +560,7 @@
 	}
 
 /*** NOTIFICATION ***/
-.notification {
+#notification {
 	position: absolute;
 	top: 10px;
 	left: 25%; right: 25%;
@@ -570,32 +572,25 @@
 	box-shadow: 0 0 5px #666;
 	background: #1a1a1a;
 	color: #888;
+	border: 1px solid #f4f899;
 	font-weight: bold;
 	z-index: 10;
 }
-	.notification.good {
-		border:1px solid #f4f899;
-	}
-	.notification.bad {
-		border:1px solid #f4a899;
+	#notification.closed {
+		display: none;
 	}
-	.notification a.close {
+	#notification a.close {
+		position: absolute;
+		top: -10px; right: -10px;
 		display: inline-block;
 		width: 16px;
 		height: 16px;
-		float: right;
-		margin: -20px -20px 0 0;
 		padding: 5px;
 		background: #1a1a1a;
 		border-radius: 50px;
 		line-height: 16px;
-	}
-	.notification.good a.close{
 		border:1px solid #f4f899;
 	}
-	.notification.bad a.close{
-		border:1px solid #f4a899;
-	}
 
 .toggle_aside, .btn.toggle_aside {
 	display: none;
@@ -777,10 +772,25 @@ select.number option {
 		text-shadow: none;
 	}
 
-	.notification,
+	#notification,
 	.actualizeProgress {
-		left: 10px;
-		right: 10px;
+		top: 0;
+		left: 0;
+		right: 0;
+		border-radius: 0;
+		border: none;
+		border-bottom: 1px solid #f4f899;
+	}
+	#notification a.close {
+		left: 0; right: 0;
+		top: 0; bottom: 0;
+		width: auto;
+		height: auto;
+		background: transparent;
+		border: none;
+	}
+	#notification a.close .icon {
+		display: none;
 	}
 }
 

+ 21 - 14
p/themes/Dark/global.css

@@ -407,6 +407,11 @@ input, select, textarea {
 				padding: 0 25px;
 				line-height: 30px;
 			}
+			.dropdown-menu > .item.share > a {
+				display: list-item;
+				list-style-position:inside;
+				list-style-type:decimal;
+			}
 			.dropdown-menu > .item:hover {
 				background: #26303F;
 				color: #888;
@@ -507,18 +512,20 @@ input, select, textarea {
 }
 
 /* Prompt (centré) */
-.prompt > h1, .prompt > p {
-	text-align:center;
-}
-.prompt > form {
-	margin:1em auto 2.5em auto;
-	width:10em;
-}
-.prompt .btn {
-	display:block;
-	margin:.5em auto;
-}
-.prompt input {
-	margin:.4em auto 1.1em auto;
-	width:99%;
+.prompt {
+	text-align: center;
 }
+	.prompt label {
+		text-align: left;
+	}
+	.prompt form {
+		margin: 1em auto 2.5em auto;
+		width: 10em;
+	}
+	.prompt input {
+		margin: .4em auto 1.1em auto;
+		width: 100%;
+	}
+	.prompt p {
+		margin: 20px 0;
+	}

+ 33 - 13
p/themes/Flat/freshrss.css

@@ -350,13 +350,15 @@ body {
 			font-size: 120%;
 		}
 			#stream.global .btn:not([data-unread="0"]) {
-				font-weight:bold;
+				background: #3498db;
+				color: #fff;
+				font-weight: bold;
 			}
 			#stream.global .btn:first-child:not([data-unread="0"]):after {
 				top: 0; right: 5px;
 				border: 0;
 				background: none;
-				color: #333;
+				color: #fff;
 				font-weight: bold;
 				box-shadow: none;
 			}
@@ -561,7 +563,7 @@ body {
 	}
 
 /*** NOTIFICATION ***/
-.notification {
+#notification {
 	position: absolute;
 	top: 10px;
 	left: 25%; right: 25%;
@@ -575,28 +577,31 @@ body {
 	font-weight: bold;
 	z-index: 10;
 }
-	.notification.good {
+	#notification.closed {
+		display: none;
+	}
+	#notification.good {
 		background: #1abc9c;
 		color: #fff;
 	}
-	.notification.bad {
+	#notification.bad {
 		background: #e74c3c;
 		color: #fff;
 	}
-	.notification a.close {
+	#notification a.close {
+		position: absolute;
+		top: -6px; right: -6px;
 		display: inline-block;
 		width: 16px;
 		height: 16px;
-		float: right;
-		margin: -16px -16px 0 0;
 		padding: 5px;
 		border-radius: 3px;
 		line-height: 16px;
 	}
-		.notification.good a.close {
+		#notification.good a.close {
 			background: #1abc9c;
 		}
-		.notification.bad a.close {
+		#notification.bad a.close {
 			background: #e74c3c;
 		}
 
@@ -785,10 +790,25 @@ select.number option {
 		text-shadow: none;
 	}
 
-	.notification,
+	#notification,
 	.actualizeProgress {
-		left: 10px;
-		right: 10px;
+		top: 0;
+		left: 0;
+		right: 0;
+		border-radius: 0;
+	}
+	#notification a.close,
+	#notification.good a.close,
+	#notification.bad a.close {
+		left: 0; right: 0;
+		top: 0; bottom: 0;
+		width: auto;
+		height: auto;
+		background: transparent;
+		border: none;
+	}
+	#notification a.close .icon {
+		display: none;
 	}
 }
 

+ 21 - 14
p/themes/Flat/global.css

@@ -403,6 +403,11 @@ input, select, textarea {
 				padding: 0 25px;
 				line-height: 30px;
 			}
+			.dropdown-menu > .item.share > a {
+				display: list-item;
+				list-style-position:inside;
+				list-style-type:decimal;
+			}
 			.dropdown-menu > .item:hover > a {
 				background: #2980b9;
 				color: #fff;
@@ -510,18 +515,20 @@ input, select, textarea {
 }
 
 /* Prompt (centré) */
-.prompt > h1, .prompt > p {
-	text-align:center;
-}
-.prompt > form {
-	margin:1em auto 2.5em auto;
-	width:10em;
-}
-.prompt .btn {
-	display:block;
-	margin:.5em auto;
-}
-.prompt input {
-	margin:.4em auto 1.1em auto;
-	width:99%;
+.prompt {
+	text-align: center;
 }
+	.prompt label {
+		text-align: left;
+	}
+	.prompt form {
+		margin: 1em auto 2.5em auto;
+		width: 10em;
+	}
+	.prompt input {
+		margin: .4em auto 1.1em auto;
+		width: 100%;
+	}
+	.prompt p {
+		margin: 20px 0;
+	}

+ 31 - 11
p/themes/Origine/freshrss.css

@@ -364,15 +364,19 @@
 			font-size: 120%;
 		}
 			#stream.global .btn:not([data-unread="0"]) {
-				font-weight:bold;
+				background: #0084CC;
+				color: #fff;
+				font-weight: bold;
+				text-shadow: none;
 			}
 			#stream.global .btn:first-child:not([data-unread="0"]):after {
 				top: 0; right: 5px;
 				border: 0;
 				background: none;
-				color: #666;
+				color: #fff;
 				font-weight: bold;
 				box-shadow: none;
+				text-shadow: none;
 			}
 		#stream.global .box-category .feeds {
 			display: block;
@@ -569,7 +573,7 @@
 	}
 
 /*** NOTIFICATION ***/
-.notification {
+#notification {
 	position: absolute;
 	top: 10px;
 	left: 25%; right: 25%;
@@ -584,18 +588,21 @@
 	font-weight: bold;
 	z-index: 10;
 }
-	.notification.good {
+	#notification.closed {
+		display: none;
+	}
+	#notification.good {
 		background: #f4f899;
 	}
-	.notification.bad {
+	#notification.bad {
 		background: #f4a899;
 	}
-	.notification a.close {
+	#notification a.close {
+		position: absolute;
+		top: -10px; right: -10px;
 		display: inline-block;
 		width: 16px;
 		height: 16px;
-		float: right;
-		margin: -20px -20px 0 0;
 		padding: 5px;
 		background: #fff;
 		border-radius: 50px;
@@ -783,10 +790,23 @@ select.number option {
 		text-shadow: none;
 	}
 
-	.notification,
+	#notification,
 	.actualizeProgress {
-		left: 10px;
-		right: 10px;
+		top: 0;
+		left: 0;
+		right: 0;
+		border-radius: 0;
+	}
+	#notification a.close {
+		left: 0; right: 0;
+		top: 0; bottom: 0;
+		width: auto;
+		height: auto;
+		background: transparent;
+		border: none;
+	}
+	#notification a.close .icon {
+		display: none;
 	}
 }
 

+ 21 - 14
p/themes/Origine/global.css

@@ -419,6 +419,11 @@ input, select, textarea {
 				padding: 0 25px;
 				line-height: 30px;
 			}
+			.dropdown-menu > .item.share > a {
+				display: list-item;
+				list-style-position:inside;
+				list-style-type:decimal;
+			}
 			.dropdown-menu > .item:hover {
 				background: #0062BE;
 				color: #fff;
@@ -523,18 +528,20 @@ input, select, textarea {
 }
 
 /* Prompt (centré) */
-.prompt > h1, .prompt > p {
-	text-align:center;
-}
-.prompt > form {
-	margin:1em auto 2.5em auto;
-	width:10em;
-}
-.prompt .btn {
-	display:block;
-	margin:.5em auto;
-}
-.prompt input {
-	margin:.4em auto 1.1em auto;
-	width:99%;
+.prompt {
+	text-align: center;
 }
+	.prompt label {
+		text-align: left;
+	}
+	.prompt form {
+		margin: 1em auto 2.5em auto;
+		width: 10em;
+	}
+	.prompt input {
+		margin: .4em auto 1.1em auto;
+		width: 100%;
+	}
+	.prompt p {
+		margin: 20px 0;
+	}