Browse Source

Merge branch 'dev' into beta

Marien Fressinaud 12 years ago
parent
commit
7f51bf0d02
55 changed files with 1073 additions and 479 deletions
  1. 4 4
      CHANGELOG
  2. 4 4
      README.md
  3. 1 1
      app/Controllers/configureController.php
  4. 23 17
      app/Controllers/indexController.php
  5. 1 1
      app/Controllers/javascriptController.php
  6. 1 0
      app/Controllers/usersController.php
  7. 2 2
      app/FreshRSS.php
  8. 2 2
      app/Models/Configuration.php
  9. 1 43
      app/Models/Feed.php
  10. 205 0
      app/Models/StatsDAO.php
  11. 24 7
      app/i18n/en.php
  12. 23 6
      app/i18n/fr.php
  13. 2 0
      app/i18n/install.en.php
  14. 1 0
      app/i18n/install.fr.php
  15. 4 3
      app/layout/aside_configure.phtml
  16. 2 2
      app/layout/aside_feed.phtml
  17. 1 1
      app/layout/aside_flux.phtml
  18. 4 2
      app/layout/header.phtml
  19. 1 1
      app/layout/layout.phtml
  20. 7 4
      app/layout/nav_menu.phtml
  21. 1 1
      app/views/configure/archiving.phtml
  22. 11 8
      app/views/configure/display.phtml
  23. 9 9
      app/views/configure/feed.phtml
  24. 6 6
      app/views/configure/sharing.phtml
  25. 5 3
      app/views/configure/users.phtml
  26. 9 7
      app/views/helpers/view/normal_view.phtml
  27. 11 20
      app/views/index/formLogin.phtml
  28. 2 0
      app/views/index/index.phtml
  29. 125 0
      app/views/index/stats.phtml
  30. 29 10
      lib/Minz/Configuration.php
  31. 1 1
      lib/Minz/Error.php
  32. 56 5
      lib/lib_rss.php
  33. BIN
      p/favicon.ico
  34. 70 30
      p/i/install.php
  35. 9 0
      p/scripts/flotr2.min.js
  36. 0 0
      p/scripts/jquery-2.0.3.min.map
  37. 0 3
      p/scripts/jquery.min.js
  38. 22 10
      p/scripts/main.js
  39. 78 23
      p/themes/Dark/freshrss.css
  40. 36 3
      p/themes/Dark/global.css
  41. 80 12
      p/themes/Flat/freshrss.css
  42. 28 0
      p/themes/Flat/global.css
  43. 31 0
      p/themes/Flat/icons/category.svg
  44. 77 16
      p/themes/Origine/freshrss.css
  45. 34 1
      p/themes/Origine/global.css
  46. BIN
      p/themes/icons/favicon-128.png
  47. BIN
      p/themes/icons/favicon-16-32-48-64.ico
  48. BIN
      p/themes/icons/favicon-16.png
  49. BIN
      p/themes/icons/favicon-256.png
  50. BIN
      p/themes/icons/favicon-32.png
  51. BIN
      p/themes/icons/favicon-48.png
  52. BIN
      p/themes/icons/favicon-512.png
  53. BIN
      p/themes/icons/favicon-64.png
  54. 17 0
      p/themes/icons/favicon.svg
  55. 13 211
      p/themes/icons/icon.svg

+ 4 - 4
CHANGELOG

@@ -1,6 +1,6 @@
 # Journal des modifications
 
-## 2014-01-xx FreshRSS 0.7
+## 2014-01-29 FreshRSS 0.7
 
 * Nouveau mode multi-utilisateur
 	* L’utilisateur par défaut (administrateur) peut créer et supprimer d’autres utilisateurs
@@ -14,10 +14,10 @@
 * Installateur supportant les mises à jour :
 	* Depuis une v0.6, placer application.ini et Configuration.array.php dans le nouveau répertoire “./data/”
 		(voir réorganisation ci-dessous)
-	* Pour les versions suivantes, juste garder “./data/config.php” et “./data/*_user.php”,
-		éventuellement “./data/persona/*”
-* Rafraîchissement automatique du nombre d’articles non lus toutes les minutes (utilise le cache HTTP à bon escient)
+	* Pour les versions suivantes, juste garder le répertoire “./data/”
+* Rafraîchissement automatique du nombre d’articles non lus toutes les deux minutes (utilise le cache HTTP à bon escient)
 	* Permet aussi de conserver la session valide, surtout dans le cas de Persona
+* Nouvelle page de statistiques (nombres d’articles par jour / catégorie)
 * Importation OPML instantanée et plus tolérante
 * Nouvelle gestion des favicons avec téléchargement en parallèle
 * Nouvelles options

+ 4 - 4
README.md

@@ -6,10 +6,10 @@ Il se veut léger et facile à prendre en main tout en étant un outil puissant
 Il permet de gérer plusieurs utilisateurs, et dispose d’un mode de lecture anonyme.
 
 * Site officiel : http://freshrss.org
-* Démo : http://marienfressinaud.fr/projets/freshrss/
+* Démo : http://demo.freshrss.org/
 * Développeur : Marien Fressinaud <dev@marienfressinaud.fr>
-* Version actuelle : 0.7-RC1
-* Date de publication 2014-01-xx
+* Version actuelle : 0.7
+* Date de publication 2014-01-29
 * 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)
@@ -29,7 +29,7 @@ Privilégiez pour cela des demandes sur GitHub
 	* Requis : [PDO_MySQL](http://php.net/pdo-mysql), [cURL](http://php.net/curl), [LibXML](http://php.net/xml), [PCRE](http://php.net/pcre), [ctype](http://php.net/ctype)
 	* Recommandés : [JSON](http://php.net/json), [zlib](http://php.net/zlib), [mbstring](http://php.net/mbstring), [iconv](http://php.net/iconv)
 * MySQL 5.0.3+ (ou SQLite 3.7.4+ à venir)
-* Un navigateur Web récent tel Firefox, Chrome, Opera, Safari, Internet Explorer 9+
+* Un navigateur Web récent tel Firefox 4+, Chrome, Opera, Safari, Internet Explorer 9+
 	* Fonctionne aussi sur mobile
 
 ![Capture d’écran de FreshRSS](http://marienfressinaud.fr/data/images/freshrss/freshrss_default-design.png)

+ 1 - 1
app/Controllers/configureController.php

@@ -196,7 +196,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
 		if (Minz_Request::isPost ()) {
 			$this->view->conf->_sharing (array(
 				'shaarli' => Minz_Request::param ('shaarli', false),
-				'poche' => Minz_Request::param ('poche', false),
+				'wallabag' => Minz_Request::param ('wallabag', false),
 				'diaspora' => Minz_Request::param ('diaspora', false),
 				'twitter' => Minz_Request::param ('twitter', false),
 				'g+' => Minz_Request::param ('g+', false),

+ 23 - 17
app/Controllers/indexController.php

@@ -1,18 +1,7 @@
 <?php
 
 class FreshRSS_index_Controller extends Minz_ActionController {
-	private $get = false;
 	private $nb_not_read_cat = 0;
-	private $entryDAO;
-	private $feedDAO;
-	private $catDAO;
-
-	function __construct($router) {
-		parent::__construct($router);
-		$this->entryDAO = new FreshRSS_EntryDAO ();
-		$this->feedDAO = new FreshRSS_FeedDAO ();
-		$this->catDAO = new FreshRSS_CategoryDAO ();
-	}
 
 	public function indexAction () {
 		$output = Minz_Request::param ('output');
@@ -50,8 +39,11 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 			Minz_View::appendScript (Minz_Url::display ('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js')));
 		}
 
-		$this->view->cat_aside = $this->catDAO->listCategories ();
-		$this->view->nb_favorites = $this->entryDAO->countUnreadReadFavorites ();
+		$catDAO = new FreshRSS_CategoryDAO();
+		$entryDAO = new FreshRSS_EntryDAO();
+
+		$this->view->cat_aside = $catDAO->listCategories ();
+		$this->view->nb_favorites = $entryDAO->countUnreadReadFavorites ();
 		$this->view->currentName = '';
 
 		$this->view->get_c = '';
@@ -125,14 +117,14 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 		$keepHistoryDefault = $this->view->conf->keep_history_default;
 
 		try {
-			$entries = $this->entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter, $date_min, $keepHistoryDefault);
+			$entries = $entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter, $date_min, $keepHistoryDefault);
 
 			// Si on a récupéré aucun article "non lus"
 			// on essaye de récupérer tous les articles
 			if ($state === 'not_read' && empty($entries)) {
 				Minz_Log::record ('Conflicting information about nbNotRead!', Minz_Log::DEBUG);
 				$this->view->state = 'all';
-				$entries = $this->entryDAO->listWhere($getType, $getId, 'all', $order, $nb, $first, $filter, $date_min, $keepHistoryDefault);
+				$entries = $entryDAO->listWhere($getType, $getId, 'all', $order, $nb, $first, $filter, $date_min, $keepHistoryDefault);
 			}
 
 			if (count($entries) <= $nb) {
@@ -170,7 +162,8 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 			case 'c':
 				$cat = isset($this->view->cat_aside[$getId]) ? $this->view->cat_aside[$getId] : null;
 				if ($cat === null) {
-					$cat = $this->catDAO->searchById ($getId);
+					$catDAO = new FreshRSS_CategoryDAO();
+					$cat = $catDAO->searchById($getId);
 				}
 				if ($cat) {
 					$this->view->currentName = $cat->name ();
@@ -183,7 +176,8 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 			case 'f':
 				$feed = FreshRSS_CategoryDAO::findFeed($this->view->cat_aside, $getId);
 				if (empty($feed)) {
-					$feed = $this->feedDAO->searchById ($getId);
+					$feedDAO = new FreshRSS_FeedDAO();
+					$feed = $feedDAO->searchById($getId);
 				}
 				if ($feed) {
 					$this->view->currentName = $feed->name ();
@@ -198,6 +192,16 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 				return false;
 		}
 	}
+	
+	public function statsAction () {
+		$statsDAO = new FreshRSS_StatsDAO ();
+		Minz_View::appendScript (Minz_Url::display ('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js')));
+		$this->view->repartition = $statsDAO->calculateEntryRepartition();
+		$this->view->count = ($statsDAO->calculateEntryCount());
+		$this->view->feedByCategory = $statsDAO->calculateFeedByCategory();
+		$this->view->entryByCategory = $statsDAO->calculateEntryByCategory();
+		$this->view->topFeed = $statsDAO->calculateTopFeed();
+	}
 
 	public function aboutAction () {
 		Minz_View::prependTitle (Minz_Translate::t ('about') . ' · ');
@@ -316,6 +320,8 @@ class FreshRSS_index_Controller extends Minz_ActionController {
 				} catch (Minz_Exception $me) {
 					Minz_Log::record('Login failure: ' . $me->getMessage(), Minz_Log::WARNING);
 				}
+			} else {
+				Minz_Log::record('Invalid credential parameters: user=' . $username . ' challenge=' . $c . ' nonce=' . $nonce, Minz_Log::DEBUG);
 			}
 			if (!$ok) {
 				$notif = array(

+ 1 - 1
app/Controllers/javascriptController.php

@@ -37,7 +37,7 @@ class FreshRSS_javascript_Controller extends Minz_ActionController {
 					return;	//Success
 				}
 			} catch (Minz_Exception $me) {
-				Minz_Log::record('Login failure: ' . $me->getMessage(), Minz_Log::WARNING);
+				Minz_Log::record('Nonce failure: ' . $me->getMessage(), Minz_Log::WARNING);
 			}
 		}
 		$this->view->nonce = '';	//Failure

+ 1 - 0
app/Controllers/usersController.php

@@ -106,6 +106,7 @@ class FreshRSS_users_Controller extends Minz_ActionController {
 					}
 					$passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST));
 					$passwordPlain = '';
+					$passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash);	//Compatibility with bcrypt.js
 					$ok &= ($passwordHash != '');
 				}
 				if (empty($passwordHash)) {

+ 2 - 2
app/FreshRSS.php

@@ -106,8 +106,8 @@ class FreshRSS extends Minz_FrontController {
 	private function loadParamsView () {
 		Minz_Session::_param ('language', $this->conf->language);
 		Minz_Translate::init();
-		$output = Minz_Request::param ('output');
-		if (!$output) {
+		$output = Minz_Request::param ('output', '');
+		if (($output === '') || ($output !== 'normal' && $output !== 'rss' && $output !== 'reader' && $output !== 'global')) {
 			$output = $this->conf->view_mode;
 			Minz_Request::_param ('output', $output);
 		}

+ 2 - 2
app/Models/Configuration.php

@@ -48,7 +48,7 @@ class FreshRSS_Configuration {
 		'bottomline_link' => true,
 		'sharing' => array(
 			'shaarli' => '',
-			'poche' => '',
+			'wallabag' => '',
 			'diaspora' => '',
 			'twitter' => true,
 			'g+' => true,
@@ -185,7 +185,7 @@ class FreshRSS_Configuration {
 		}
 	}
 	public function _sharing ($values) {
-		$are_url = array ('shaarli', 'poche', 'diaspora');
+		$are_url = array ('shaarli', 'wallabag', 'diaspora');
 		foreach ($values as $key => $value) {
 			if (in_array($key, $are_url)) {
 				$is_url = (

+ 1 - 43
app/Models/Feed.php

@@ -187,54 +187,12 @@ class FreshRSS_Feed extends Minz_Model {
 					Minz_Exception::ERROR
 				);
 			} else {
-				$feed = new SimplePie ();
-				$feed->set_useragent(Minz_Translate::t ('freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ') ' . SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION);
 				$url = htmlspecialchars_decode ($this->url, ENT_QUOTES);
 				if ($this->httpAuth != '') {
 					$url = preg_replace ('#((.+)://)(.+)#', '${1}' . $this->httpAuth . '@${3}', $url);
 				}
-
+				$feed = customSimplePie();
 				$feed->set_feed_url ($url);
-				$feed->set_cache_location (CACHE_PATH);
-				$feed->set_cache_duration(1500);
-				$feed->strip_htmltags (array (
-					'base', 'blink', 'body', 'doctype', 'embed',
-					'font', 'form', 'frame', 'frameset', 'html',
-					'input', 'marquee', 'meta', 'noscript',
-					'object', 'param', 'plaintext', 'script', 'style',
-				));
-				$feed->strip_attributes(array_merge($feed->strip_attributes, array(
-					'autoplay', 'onload', 'onunload', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup',
-					'onmouseover', 'onmousemove', 'onmouseout', 'onfocus', 'onblur',
-					'onkeypress', 'onkeydown', 'onkeyup', 'onselect', 'onchange', 'seamless')));
-				$feed->add_attributes(array(
-					'img' => array('lazyload' => ''),	//http://www.w3.org/TR/resource-priorities/
-					'audio' => array('preload' => 'none'),
-					'iframe' => array('postpone' => '', 'sandbox' => 'allow-scripts allow-same-origin'),
-					'video' => array('postpone' => '', 'preload' => 'none'),
-				));
-				$feed->set_url_replacements(array(
-					'a' => 'href',
-					'area' => 'href',
-					'audio' => 'src',
-					'blockquote' => 'cite',
-					'del' => 'cite',
-					'form' => 'action',
-					'iframe' => 'src',
-					'img' => array(
-						'longdesc',
-						'src'
-					),
-					'input' => 'src',
-					'ins' => 'cite',
-					'q' => 'cite',
-					'source' => 'src',
-					'track' => 'src',
-					'video' => array(
-						'poster',
-						'src',
-					),
-				));
 				$feed->init ();
 
 				if ($feed->error ()) {

+ 205 - 0
app/Models/StatsDAO.php

@@ -0,0 +1,205 @@
+<?php
+
+class FreshRSS_StatsDAO extends Minz_ModelPdo {
+
+	/**
+	 * Calculates entry repartition for all feeds and for main stream.
+	 * The repartition includes:
+	 *   - total entries
+	 *   - read entries
+	 *   - unread entries
+	 *   - favorite entries
+	 * 
+	 * @return type
+	 */
+	public function calculateEntryRepartition() {
+		$repartition = array();
+
+		// Generates the repartition for the main stream of entry
+		$sql = <<<SQL
+SELECT COUNT(1) AS `total`,
+COUNT(1) - SUM(e.is_read) AS `unread`,
+SUM(e.is_read) AS `read`,
+SUM(e.is_favorite) AS `favorite`
+FROM {$this->prefix}entry AS e
+, {$this->prefix}feed AS f
+WHERE e.id_feed = f.id
+AND f.priority = 10
+SQL;
+		$stm = $this->bd->prepare($sql);
+		$stm->execute();
+		$res = $stm->fetchAll(PDO::FETCH_ASSOC);
+		$repartition['main_stream'] = $res[0];
+
+		// Generates the repartition for all entries
+		$sql = <<<SQL
+SELECT COUNT(1) AS `total`,
+COUNT(1) - SUM(e.is_read) AS `unread`,
+SUM(e.is_read) AS `read`,
+SUM(e.is_favorite) AS `favorite`
+FROM {$this->prefix}entry AS e
+SQL;
+		$stm = $this->bd->prepare($sql);
+		$stm->execute();
+		$res = $stm->fetchAll(PDO::FETCH_ASSOC);
+		$repartition['all_feeds'] = $res[0];
+
+		return $repartition;
+	}
+
+	/**
+	 * Calculates entry count per day on a 30 days period.
+	 * Returns the result as a JSON string.
+	 * 
+	 * @return string
+	 */
+	public function calculateEntryCount() {
+		$count = array();
+
+		// Generates a list of 30 last day to be sure we always have 30 days.
+		// If we do not do that kind of thing, we'll end up with holes in the
+		// days if the user do not have a lot of feeds.
+		$sql = <<<SQL
+SELECT - (tens.val + units.val + 1) AS day
+FROM (
+    SELECT 0 AS val
+    UNION ALL SELECT 1
+    UNION ALL SELECT 2
+    UNION ALL SELECT 3
+    UNION ALL SELECT 4
+    UNION ALL SELECT 5
+    UNION ALL SELECT 6
+    UNION ALL SELECT 7
+    UNION ALL SELECT 8
+    UNION ALL SELECT 9
+) AS units
+CROSS JOIN (
+    SELECT 0 AS val
+    UNION ALL SELECT 10
+    UNION ALL SELECT 20
+) AS tens
+ORDER BY day ASC
+SQL;
+		$stm = $this->bd->prepare($sql);
+		$stm->execute();
+		$res = $stm->fetchAll(PDO::FETCH_ASSOC);
+		foreach ($res as $value) {
+			$count[$value['day']] = 0;
+		}
+
+		// Get stats per day for the last 30 days and applies the result on 
+		// the array created with the last query.
+		$sql = <<<SQL
+SELECT DATEDIFF(FROM_UNIXTIME(e.date), NOW()) AS day,
+COUNT(1) AS count
+FROM {$this->prefix}entry AS e
+WHERE FROM_UNIXTIME(e.date, '%Y%m%d') BETWEEN DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -30 DAY), '%Y%m%d') AND DATE_FORMAT(DATE_ADD(NOW(), INTERVAL -1 DAY), '%Y%m%d')
+GROUP BY day
+ORDER BY day ASC
+SQL;
+		$stm = $this->bd->prepare($sql);
+		$stm->execute();
+		$res = $stm->fetchAll(PDO::FETCH_ASSOC);
+
+		foreach ($res as $value) {
+			$count[$value['day']] = (int) $value['count'];
+		}
+
+		return $this->convertToSerie($count);
+	}
+
+	/**
+	 * Calculates feed count per category.
+	 * Returns the result as a JSON string.
+	 * 
+	 * @return string
+	 */
+	public function calculateFeedByCategory() {
+		$sql = <<<SQL
+SELECT c.name AS label
+, COUNT(f.id) AS data
+FROM {$this->prefix}category AS c,
+{$this->prefix}feed AS f
+WHERE c.id = f.category
+GROUP BY label
+ORDER BY data DESC
+SQL;
+		$stm = $this->bd->prepare($sql);
+		$stm->execute();
+		$res = $stm->fetchAll(PDO::FETCH_ASSOC);
+
+		return $this->convertToPieSerie($res);
+	}
+
+	/**
+	 * Calculates entry count per category.
+	 * Returns the result as a JSON string.
+	 * 
+	 * @return string
+	 */
+	public function calculateEntryByCategory() {
+		$sql = <<<SQL
+SELECT c.name AS label
+, COUNT(e.id) AS data
+FROM {$this->prefix}category AS c,
+{$this->prefix}feed AS f,
+{$this->prefix}entry AS e
+WHERE c.id = f.category
+AND f.id = e.id_feed
+GROUP BY label
+ORDER BY data DESC
+SQL;
+		$stm = $this->bd->prepare($sql);
+		$stm->execute();
+		$res = $stm->fetchAll(PDO::FETCH_ASSOC);
+
+		return $this->convertToPieSerie($res);
+	}
+
+	/**
+	 * Calculates the 10 top feeds based on their number of entries
+	 * 
+	 * @return array
+	 */
+	public function calculateTopFeed() {
+		$sql = <<<SQL
+SELECT f.id AS id
+, MAX(f.name) AS name
+, MAX(c.name) AS category
+, COUNT(e.id) AS count
+FROM {$this->prefix}category AS c,
+{$this->prefix}feed AS f,
+{$this->prefix}entry AS e
+WHERE c.id = f.category
+AND f.id = e.id_feed
+GROUP BY id
+ORDER BY count DESC
+LIMIT 10
+SQL;
+		$stm = $this->bd->prepare($sql);
+		$stm->execute();
+		return $stm->fetchAll(PDO::FETCH_ASSOC);
+	}
+
+	private function convertToSerie($data) {
+		$serie = array();
+
+		foreach ($data as $key => $value) {
+			$serie[] = array($key, $value);
+		}
+
+		return json_encode($serie);
+	}
+
+	private function convertToPieSerie($data) {
+		$serie = array();
+
+		foreach ($data as $value) {
+			$value['data'] = array(array(0, (int) $value['data']));
+			$serie[] = $value;
+		}
+
+		return json_encode($serie);
+	}
+
+}

+ 24 - 7
app/i18n/en.php

@@ -11,8 +11,11 @@ return array (
 	'users'				=> 'Users',
 	'categories'			=> 'Categories',
 	'category'			=> 'Category',
+	'feed'				=> 'Feed',
+	'feeds'				=> 'Feeds',
 	'shortcuts'			=> 'Shortcuts',
 	'about'				=> 'About',
+	'stats'				=> 'Statistics',
 
 	'your_rss_feeds'		=> 'Your RSS feeds',
 	'add_rss_feed'			=> 'Add a RSS feed',
@@ -20,7 +23,8 @@ return array (
 	'import_export_opml'		=> 'Import / export (OPML)',
 
 	'subscription_management'	=> 'Subscriptions management',
-	'all_feeds'			=> 'Main stream',
+	'main_stream'			=> 'Main stream',
+	'all_feeds'			=> 'All feeds',
 	'favorite_feeds'		=> 'Favourites (%d)',
 	'not_read'			=> '%d unread',
 	'not_reads'			=> '%d unread',
@@ -157,7 +161,7 @@ return array (
 	'not_yet_implemented'		=> 'Not yet implemented',
 	'access_protected_feeds'	=> 'Connection allows to access HTTP protected RSS feeds',
 	'no_selected_feed'		=> 'No feed selected.',
-	'think_to_add'			=> '<a href="./?c=configure&amp;a=feed">Remember to add some RSS feeds!</a>',
+	'think_to_add'			=> '<a href="./?c=configure&amp;a=feed">You may add some feeds</a>.',
 
 	'current_user'			=> 'Current user',
 	'default_user'			=> 'Username of the default user <small>(maximum 16 alphanumeric characters)</small>',
@@ -206,7 +210,7 @@ return array (
 	'scroll'			=> 'during page scrolls',
 	'upon_reception'		=> 'upon reception of the article',
 	'your_shaarli'			=> 'Your Shaarli',
-	'your_poche'			=> 'Your Poche',
+	'your_wallabag'			=> 'Your wallabag',
 	'your_diaspora_pod'		=> 'Your Diaspora* pod',
 	'sharing'			=> 'Sharing',
 	'share'				=> 'Share',
@@ -217,7 +221,7 @@ return array (
 	'more_information'		=> 'More information',
 	'activate_sharing'		=> 'Activate sharing',
 	'shaarli'			=> 'Shaarli',
-	'poche'				=> 'Poche',
+	'wallabag'			=> 'wallabag',
 	'diaspora'			=> 'Diaspora*',
 	'twitter'			=> 'Twitter',
 	'g+'				=> 'Google+',
@@ -232,7 +236,7 @@ return array (
 	'by'				=> 'by',
 
 	'load_more'			=> 'Load more articles',
-	'nothing_to_load'		=> 'There is no more articles',
+	'nothing_to_load'		=> 'There are no more articles',
 
 	'rss_feeds_of'			=> 'RSS feed of %s',
 
@@ -241,9 +245,10 @@ return array (
 	'today'				=> 'Today',
 	'yesterday'			=> 'Yesterday',
 	'before_yesterday'		=> 'Before yesterday',
+	'new_article'			=> 'There are new available articles, click to refresh the page.',
 	'by_author'			=> 'By <em>%s</em>',
 	'related_tags'			=> 'Related tags',
-	'no_feed_to_display'		=> 'There is no feed to show yet.',
+	'no_feed_to_display'		=> 'There is no article to show.',
 
 	'about_freshrss'		=> 'About FreshRSS',
 	'project_website'		=> 'Project website',
@@ -295,5 +300,17 @@ return array (
 	'Dec'				=> '\D\e\c\e\m\b\e\r',
 	// format for date() function, %s allows to indicate month in letter
 	'format_date'			=> '%s j\<\s\u\p\>S\<\/\s\u\p\> Y',
-	'format_date_hour'		=> '%s j\<\s\u\p\>S\<\/\s\u\p\> Y \a\t H\.i',
+	'format_date_hour'		=> '%s j\<\s\u\p\>S\<\/\s\u\p\> Y \a\t H\:i',
+	
+	'status_favorites'		=> 'Favourites',
+	'status_read'			=> 'Read',
+	'status_unread'			=> 'Unread',
+	'status_total'			=> 'Total',
+	
+	'stats_entry_repartition'	=> 'Entries repartition',
+	'stats_entry_per_day'		=> 'Entries per day (last 30 days)',
+	'stats_feed_per_category'	=> 'Feeds per category',
+	'stats_entry_per_category'	=> 'Entries per category',
+	'stats_top_feed'		=> 'Top ten feeds',
+	'stats_entry_count'		=> 'Entry count',
 );

+ 23 - 6
app/i18n/fr.php

@@ -11,8 +11,11 @@ return array (
 	'users'				=> 'Utilisateurs',
 	'categories'			=> 'Catégories',
 	'category'			=> 'Catégorie',
+	'feed'				=> 'Flux',
+	'feeds'				=> 'Flux',
 	'shortcuts'			=> 'Raccourcis',
 	'about'				=> 'À propos',
+	'stats'				=> 'Statistiques',
 
 	'your_rss_feeds'		=> 'Vos flux RSS',
 	'add_rss_feed'			=> 'Ajouter un flux RSS',
@@ -20,7 +23,8 @@ return array (
 	'import_export_opml'		=> 'Importer / exporter (OPML)',
 
 	'subscription_management'	=> 'Gestion des abonnements',
-	'all_feeds'			=> 'Flux principal',
+	'main_stream'			=> 'Flux principal',
+	'all_feeds'			=> 'Tous les flux',
 	'favorite_feeds'		=> 'Favoris (%d)',
 	'not_read'			=> '%d non lu',
 	'not_reads'			=> '%d non lus',
@@ -157,7 +161,7 @@ return array (
 	'not_yet_implemented'		=> 'Pas encore implémenté',
 	'access_protected_feeds'	=> 'La connexion permet d’accéder aux flux protégés par une authentification HTTP',
 	'no_selected_feed'		=> 'Aucun flux sélectionné.',
-	'think_to_add'			=> '<a href="./?c=configure&amp;a=feed">Pensez à en ajouter !</a>',
+	'think_to_add'			=> '<a href="./?c=configure&amp;a=feed">Vous pouvez ajouter des flux</a>.',
 
 	'current_user'			=> 'Utilisateur actuel',
 	'password_form'			=> 'Mot de passe<br /><small>(pour connexion par formulaire)</small>',
@@ -206,7 +210,7 @@ return array (
 	'scroll'			=> 'au défilement de la page',
 	'upon_reception'		=> 'dès la réception du nouvel article',
 	'your_shaarli'			=> 'Votre Shaarli',
-	'your_poche'			=> 'Votre Poche',
+	'your_wallabag'			=> 'Votre wallabag',
 	'your_diaspora_pod'		=> 'Votre pod Diaspora*',
 	'sharing'			=> 'Partage',
 	'share'				=> 'Partager',
@@ -217,7 +221,7 @@ return array (
 	'more_information'		=> 'Plus d’informations',
 	'activate_sharing'		=> 'Activer le partage',
 	'shaarli'			=> 'Shaarli',
-	'poche'				=> 'Poche',
+	'wallabag'			=> 'wallabag',
 	'diaspora'			=> 'Diaspora*',
 	'twitter'			=> 'Twitter',
 	'g+'				=> 'Google+',
@@ -241,9 +245,10 @@ return array (
 	'today'				=> 'Aujourd’hui',
 	'yesterday'			=> 'Hier',
 	'before_yesterday'		=> 'À partir d’avant-hier',
+	'new_article'			=> 'Il y a de nouveaux articles disponibles, cliquez pour rafraîchir la page.',
 	'by_author'			=> 'Par <em>%s</em>',
 	'related_tags'			=> 'Tags associés',
-	'no_feed_to_display'		=> 'Il n’y a aucun flux à afficher pour l’instant.',
+	'no_feed_to_display'		=> 'Il n’y a aucun article à afficher.',
 
 	'about_freshrss'		=> 'À propos de FreshRSS',
 	'project_website'		=> 'Site du projet',
@@ -295,5 +300,17 @@ return array (
 	'Dec'				=> '\d\é\c\e\m\b\r\e',
 	// format pour la fonction date(), %s permet d'indiquer le mois en toutes lettres
 	'format_date'			=> 'j %s Y',
-	'format_date_hour'		=> '\l\e j %s Y \à H\:i',
+	'format_date_hour'		=> 'j %s Y \à H\:i',
+	
+	'status_favorites'		=> 'favoris',
+	'status_read'			=> 'lus',
+	'status_unread'			=> 'non lus',
+	'status_total'			=> 'total',
+	
+	'stats_entry_repartition'	=> 'Répartition des articles',
+	'stats_entry_per_day'		=> 'Nombre d’articles par jour (30 derniers jours)',
+	'stats_feed_per_category'	=> 'Flux par catégorie',
+	'stats_entry_per_category'	=> 'Articles par catégorie',
+	'stats_top_feed'		=> 'Les dix plus gros flux',
+	'stats_entry_count'		=> 'Nombre d’articles',
 );

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

@@ -58,6 +58,8 @@ return array (
 
 	'update_start'			=> 'Start update process',
 	'update_long'			=> 'This can take a long time, depending on the size of your database. You may have to wait for this page to time out (~5 minutes) and then refresh this page.',
+	'update_end'			=> 'Update process is completed, now you can go to the final step.',
+
 
 	'installation_is_ok'		=> 'The installation process was successful.<br />The final step will now attempt to delete the <kbd>./p/i/install.php</kbd> file and any database backup created during the update process.<br />You may choose to skip this step and delete <kbd>./p/i/install.php</kbd> manually.',
 	'finish_installation'		=> 'Complete installation',

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

@@ -58,6 +58,7 @@ return array (
 
 	'update_start'			=> 'Lancer la mise à jour',
 	'update_long'			=> 'Ce processus peut prendre longtemps, selon la taille de votre base de données. Vous aurez peut-être à attendre que cette page dépasse son temps maximum d’exécution (~5 minutes) puis à la recharger.',
+	'update_end'			=> 'La mise à jour est terminée, vous pouvez maintenant passer à l’étape finale.',
 
 	'installation_is_ok'		=> 'L’installation s’est bien passée.<br />La dernière étape va maintenant tenter de supprimer le fichier <kbd>./p/i/install.php</kbd>, ainsi que d’éventuelles copies de base de données créées durant le processus de mise à jour.<br />Vous pouvez choisir de sauter cette étape et de supprimer <kbd>./p/i/install.php</kbd> manuellement.',
 	'finish_installation'		=> 'Terminer l’installation',

+ 4 - 3
app/layout/aside_configure.phtml

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

+ 2 - 2
app/layout/aside_feed.phtml

@@ -27,10 +27,10 @@
 
 					<li class="dropdown-header"><?php echo Minz_Translate::t ('http_authentication'); ?></li>
 					<li class="input">
-						<input type="text" name="http_user" id="http_user" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('username'); ?>" />
+						<input type="text" name="http_user" id="http_user_add" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('username'); ?>" />
 					</li>
 					<li class="input">
-						<input type="password" name="http_pass" id="http_pass" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('password'); ?>" />
+						<input type="password" name="http_pass" id="http_pass_add" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('password'); ?>" />
 					</li>
 				</ul>
 			</div>

+ 1 - 1
app/layout/aside_flux.phtml

@@ -23,7 +23,7 @@
 			<div class="category all">
 				<a data-unread="<?php echo $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 ('all_feeds'); ?>
+					<?php echo Minz_Translate::t ('main_stream'); ?>
 				</a>
 			</div>
 		</li>

+ 4 - 2
app/layout/header.phtml

@@ -36,7 +36,7 @@ if (Minz_Configuration::canLogIn()) {
 		<form action="<?php echo _url ('index', 'index'); ?>" method="get">
 			<div class="stick">
 				<?php $search = Minz_Request::param ('search', ''); ?>
-				<input type="search" name="search" id="search" value="<?php echo $search; ?>" placeholder="<?php echo Minz_Translate::t ('search'); ?>" />
+				<input type="search" name="search" id="search" class="extend" value="<?php echo $search; ?>" placeholder="<?php echo Minz_Translate::t ('search'); ?>" />
 
 				<?php $get = Minz_Request::param ('get', ''); ?>
 				<?php if($get != '') { ?>
@@ -67,12 +67,14 @@ if (Minz_Configuration::canLogIn()) {
 			<ul class="dropdown-menu">
 				<li class="dropdown-close"><a href="#close">❌</a></li>
 				<li class="dropdown-header"><?php echo Minz_Translate::t ('configuration'); ?></li>
-				<li class="item"><a href="<?php echo _url ('configure', 'users'); ?>"><?php echo Minz_Translate::t ('users'); ?></a></li>
 				<li class="item"><a href="<?php echo _url ('configure', 'display'); ?>"><?php echo Minz_Translate::t ('reading_configuration'); ?></a></li>
 				<li class="item"><a href="<?php echo _url ('configure', 'archiving'); ?>"><?php echo Minz_Translate::t ('archiving_configuration'); ?></a></li>
 				<li class="item"><a href="<?php echo _url ('configure', 'sharing'); ?>"><?php echo Minz_Translate::t ('sharing'); ?></a></li>
 				<li class="item"><a href="<?php echo _url ('configure', 'shortcut'); ?>"><?php echo Minz_Translate::t ('shortcuts'); ?></a></li>
 				<li class="separator"></li>
+				<li class="item"><a href="<?php echo _url ('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>
 				<?php

+ 1 - 1
app/layout/layout.phtml

@@ -28,7 +28,7 @@
 		<meta name="msapplication-TileColor" content="#FFF" />
 		<meta name="robots" content="noindex,nofollow" />
 	</head>
-	<body>
+	<body class="<?php echo Minz_Request::param('output', 'normal'); ?>">
 <?php $this->partial ('header'); ?>
 
 <div id="global">

+ 7 - 4
app/layout/nav_menu.phtml

@@ -1,5 +1,10 @@
+<?php
+	$actual_view = Minz_Request::param('output', 'normal');
+?>
 <div class="nav_menu">
+	<?php if ($actual_view === 'normal') { ?>
 	<a class="btn toggle_aside" href="#aside_flux"><?php echo FreshRSS_Themes::icon('category'); ?></a>
+	<?php } ?>
 
 	<?php if ($this->loginOk) { ?>
 	<a id="actualize" class="btn" href="<?php echo _url ('feed', 'actualize'); ?>"><?php echo FreshRSS_Themes::icon('refresh'); ?></a>
@@ -107,9 +112,7 @@
 
 			<?php
 				$url_output = $url;
-				$actual_view = Minz_Request::param('output', 'normal');
-			?>
-			<?php if($actual_view !== 'normal') { ?>
+				if ($actual_view !== 'normal') { ?>
 			<li class="item">
 				<?php $url_output['params']['output'] = 'normal'; ?>
 				<a class="view_normal" href="<?php echo Minz_Url::display ($url_output); ?>">
@@ -202,7 +205,7 @@
 	<div class="item search">
 		<form action="<?php echo _url ('index', 'index'); ?>" method="get">
 			<?php $search = Minz_Request::param ('search', ''); ?>
-			<input type="search" name="search" value="<?php echo $search; ?>" placeholder="<?php echo Minz_Translate::t ('search_short'); ?>" />
+			<input type="search" name="search" class="extend" value="<?php echo $search; ?>" placeholder="<?php echo Minz_Translate::t ('search_short'); ?>" />
 
 			<?php $get = Minz_Request::param ('get', ''); ?>
 			<?php if($get != '') { ?>

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

@@ -39,7 +39,7 @@
 		<div class="form-group">
 		<p class="group-name"><?php echo Minz_Translate::t('current_user'); ?></p>
 			<div class="group-controls">
-				<p><?php echo $this->nb_total, ' ', Minz_Translate::t('articles'), ', ', formatBytes($this->size_user); ?></p>
+				<p><?php echo formatNumber($this->nb_total), ' ', Minz_Translate::t('articles'), ', ', formatBytes($this->size_user); ?></p>
 				<input type="hidden" name="optimiseDatabase" value="1" />
 				<button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('optimize_bdd'); ?></button>
 				<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('optimize_todo_sometimes'); ?>

+ 11 - 8
app/views/configure/display.phtml

@@ -21,14 +21,17 @@
 		<div class="form-group">
 			<label class="group-name" for="theme"><?php echo Minz_Translate::t ('theme'); ?></label>
 			<div class="group-controls">
-				<select name="theme" id="theme" required="">
-				<option></option>
-				<?php foreach ($this->themes as $theme) { ?>
-				<option value="<?php echo $theme['id']; ?>"<?php echo $this->conf->theme === $theme['id'] ? ' selected="selected"' : ''; ?>>
-					<?php echo $theme['name'] . ' — ' . Minz_Translate::t ('by') . ' ' . $theme['author']; ?> 
-				</option>
-				<?php } ?>
-				</select>
+				<select name="theme" id="theme" required=""><?php
+					$found = false;
+					foreach ($this->themes as $theme) {
+						?><option value="<?php echo $theme['id']; ?>"<?php if ($this->conf->theme === $theme['id']) { echo ' selected="selected"'; $found = true; } ?>><?php
+							echo $theme['name'] . ' — ' . Minz_Translate::t ('by') . ' ' . $theme['author'];
+						?></option><?php
+					}
+					if (!$found) {
+						?><option selected="selected"></option><?php
+					}
+				?></select>
 			</div>
 		</div>
 

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

@@ -16,26 +16,26 @@
 		<div class="form-group">
 			<label class="group-name" for="name"><?php echo Minz_Translate::t ('title'); ?></label>
 			<div class="group-controls">
-				<input type="text" name="name" id="name" value="<?php echo $this->flux->name () ; ?>" />
+				<input type="text" name="name" id="name" class="extend" value="<?php echo $this->flux->name () ; ?>" />
 			</div>
 		</div>
 		<div class="form-group">
-			<label class="group-name"><?php echo Minz_Translate::t ('feed_description'); ?></label>
+			<label class="group-name" for="description"><?php echo Minz_Translate::t ('feed_description'); ?></label>
 			<div class="group-controls">
 				<textarea name="description" id="description"><?php echo htmlspecialchars($this->flux->description(), ENT_NOQUOTES, 'UTF-8'); ?></textarea>
 			</div>
 		</div>
 		<div class="form-group">
-			<label class="group-name"><?php echo Minz_Translate::t ('website_url'); ?></label>
+			<label class="group-name" for="website"><?php echo Minz_Translate::t ('website_url'); ?></label>
 			<div class="group-controls">
-				<input type="text" name="website" id="website" value="<?php echo $this->flux->website (); ?>" />
+				<input type="text" name="website" id="website" class="extend" value="<?php echo $this->flux->website (); ?>" />
 				<a target="_blank" href="<?php echo $this->flux->website (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
 			</div>
 		</div>
 		<div class="form-group">
-			<label class="group-name"><?php echo Minz_Translate::t ('feed_url'); ?></label>
+			<label class="group-name" for="url"><?php echo Minz_Translate::t ('feed_url'); ?></label>
 			<div class="group-controls">
-				<input type="text" name="url" id="url" value="<?php echo $this->flux->url (); ?>" />
+				<input type="text" name="url" id="url" class="extend" value="<?php echo $this->flux->url (); ?>" />
 				<a target="_blank" href="<?php echo $this->flux->url (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a>
 				  <a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->flux->url (); ?>"><?php echo Minz_Translate::t ('feed_validator'); ?></a>
 			</div>
@@ -106,13 +106,13 @@
 		<div class="form-group">
 			<label class="group-name" for="http_user"><?php echo Minz_Translate::t ('http_username'); ?></label>
 			<div class="group-controls">
-				<input type="text" name="http_user" id="http_user" value="<?php echo $auth['username']; ?>" autocomplete="off" />
+				<input type="text" name="http_user" id="http_user" class="extend" value="<?php echo $auth['username']; ?>" autocomplete="off" />
 				<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t ('access_protected_feeds'); ?>
 			</div>
 
 			<label class="group-name" for="http_pass"><?php echo Minz_Translate::t ('http_password'); ?></label>
 			<div class="group-controls">
-				<input type="password" name="http_pass" id="http_pass" value="<?php echo $auth['password']; ?>" autocomplete="off" />
+				<input type="password" name="http_pass" id="http_pass" class="extend" value="<?php echo $auth['password']; ?>" autocomplete="off" />
 			</div>
 		</div>
 
@@ -127,7 +127,7 @@
 		<div class="form-group">
 			<label class="group-name" for="path_entries"><?php echo Minz_Translate::t ('css_path_on_website'); ?></label>
 			<div class="group-controls">
-				<input type="text" name="path_entries" id="path_entries" value="<?php echo $this->flux->pathEntries (); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" />
+				<input type="text" name="path_entries" id="path_entries" class="extend" value="<?php echo $this->flux->pathEntries (); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" />
 				<?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t ('retrieve_truncated_feeds'); ?>
 			</div>
 		</div>

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

@@ -10,20 +10,20 @@
 				<?php echo Minz_Translate::t ('your_shaarli'); ?>
 			</label>
 			<div class="group-controls">
-				<input type="url" id="shaarli" name="shaarli" value="<?php echo $this->conf->sharing ('shaarli'); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" size="64" />
+				<input type="url" id="shaarli" name="shaarli" class="extend" value="<?php echo $this->conf->sharing ('shaarli'); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" size="64" />
 
 				<?php echo FreshRSS_Themes::icon('help'); ?> <a target="_blank" href="http://sebsauvage.net/wiki/doku.php?id=php:shaarli"><?php echo Minz_Translate::t ('more_information'); ?></a>
 			</div>
 		</div>
 
 		<div class="form-group">
-			<label class="group-name" for="poche">
-				<?php echo Minz_Translate::t ('your_poche'); ?>
+			<label class="group-name" for="wallabag">
+				<?php echo Minz_Translate::t ('your_wallabag'); ?>
 			</label>
 			<div class="group-controls">
-				<input type="url" id="poche" name="poche" value="<?php echo $this->conf->sharing ('poche'); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" size="64" />
+				<input type="url" id="wallabag" name="wallabag" class="extend" value="<?php echo $this->conf->sharing ('wallabag'); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" size="64" />
 
-				<?php echo FreshRSS_Themes::icon('help'); ?> <a target="_blank" href="http://www.inthepoche.com/"><?php echo Minz_Translate::t ('more_information'); ?></a>
+				<?php echo FreshRSS_Themes::icon('help'); ?> <a target="_blank" href="http://www.wallabag.org"><?php echo Minz_Translate::t ('more_information'); ?></a>
 			</div>
 		</div>
 
@@ -32,7 +32,7 @@
 				<?php echo Minz_Translate::t ('your_diaspora_pod'); ?>
 			</label>
 			<div class="group-controls">
-				<input type="url" id="diaspora" name="diaspora" value="<?php echo $this->conf->sharing ('diaspora'); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" size="64" />
+				<input type="url" id="diaspora" name="diaspora" class="extend" value="<?php echo $this->conf->sharing ('diaspora'); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" size="64" />
 
 				<?php echo FreshRSS_Themes::icon('help'); ?> <a target="_blank" href="https://diasporafoundation.org/"><?php echo Minz_Translate::t ('more_information'); ?></a>
 			</div>

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

@@ -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" 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" 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>
@@ -49,7 +49,9 @@
 			<label class="group-name" for="auth_type"><?php echo Minz_Translate::t('auth_type'); ?></label>
 			<div class="group-controls">
 				<select id="auth_type" name="auth_type" required="required">
-					<option value=""></option>
+					<?php if (!in_array(Minz_Configuration::authType(), array('form', 'persona', 'http_auth', 'none'))) { ?>
+						<option selected="selected"></option>
+					<?php } ?>
 					<option value="form"<?php echo Minz_Configuration::authType() === 'form' ? ' selected="selected"' : '', version_compare(PHP_VERSION, '5.3', '<') ? ' disabled="disabled"' : ''; ?>><?php echo Minz_Translate::t('auth_form'); ?></option>
 					<option value="persona"<?php echo Minz_Configuration::authType() === 'persona' ? ' selected="selected"' : '', $this->conf->mail_login == '' ? ' disabled="disabled"' : ''; ?>><?php echo Minz_Translate::t('auth_persona'); ?></option>
 					<option value="http_auth"<?php echo Minz_Configuration::authType() === 'http_auth' ? ' selected="selected"' : '', httpAuthUser() == '' ? ' disabled="disabled"' : ''; ?>><?php echo Minz_Translate::t('http_auth'); ?> (REMOTE_USER = '<?php echo httpAuthUser(); ?>')</option>
@@ -143,7 +145,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" placeholder="alice@example.net" />
+				<input type="email" id="new_user_email" name="new_user_email" class="extend" placeholder="alice@example.net" />
 			</div>
 		</div>
 

+ 9 - 7
app/views/helpers/view/normal_view.phtml

@@ -9,11 +9,11 @@ if (!empty($this->entries)) {
 	$display_others = true;
 	if ($this->loginOk) {
 		$shaarli = $this->conf->sharing ('shaarli');
-		$poche = $this->conf->sharing ('poche');
+		$wallabag = $this->conf->sharing ('wallabag');
 		$diaspora = $this->conf->sharing ('diaspora');
 	} else {
 		$shaarli = '';
-		$poche = '';
+		$wallabag = '';
 		$diaspora = '';
 	}
 	$twitter = $this->conf->sharing ('twitter');
@@ -30,7 +30,7 @@ if (!empty($this->entries)) {
 	$bottomline_read = $this->conf->bottomline_read;
 	$bottomline_favorite = $this->conf->bottomline_favorite;
 	$bottomline_sharing = $this->conf->bottomline_sharing && (
-		$shaarli || $poche || $diaspora || $twitter ||
+		$shaarli || $wallabag || $diaspora || $twitter ||
 		$google_plus || $facebook || $email || $print);
 	$bottomline_tags = $this->conf->bottomline_tags;
 	$bottomline_date = $this->conf->bottomline_date;
@@ -38,6 +38,9 @@ if (!empty($this->entries)) {
 ?>
 
 <div id="stream" class="normal<?php echo $hidePosts ? ' hide_posts' : ''; ?>"><?php
+	?><div id="new-article">
+		<a href="<?php echo _url('index', 'index'); ?>"><?php echo Minz_Translate::t ('new_article'); ?></a>
+	</div><?php
 	foreach ($this->entries as $item) {
 		if ($display_today && $item->isDay (FreshRSS_Days::TODAY, $this->today)) {
 			?><div class="day" id="day_today"><?php
@@ -62,7 +65,6 @@ if (!empty($this->entries)) {
 			?></div><?php
 			$display_others = false;
 		}
-
 	?><div class="flux<?php echo !$item->isRead () ? ' not_read' : ''; ?><?php echo $item->isFavorite () ? ' favorite' : ''; ?>" id="flux_<?php echo $item->id (); ?>">
 		<ul class="horizontal-list flux_header"><?php
 			if ($this->loginOk) {
@@ -150,10 +152,10 @@ if (!empty($this->entries)) {
 									<?php echo Minz_Translate::t ('shaarli'); ?>
 								</a>
 							</li>
-							<?php } if ($poche) { ?>
+							<?php } if ($wallabag) { ?>
 							<li class="item">
-								<a target="_blank" href="<?php echo $poche . '?action=add&amp;url=' . base64_encode (urldecode($link)); ?>">
-									<?php echo Minz_Translate::t ('poche'); ?>
+								<a 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) { ?>

+ 11 - 20
app/views/index/formLogin.phtml

@@ -1,5 +1,4 @@
-<div class="post content">
-
+<div class="prompt">
 <?php
 if (Minz_Configuration::canLogIn()) {
 	?><h1><?php echo Minz_Translate::t('login'); ?></h1><?php
@@ -7,25 +6,17 @@ if (Minz_Configuration::canLogIn()) {
 
 		case 'form':
 		?><form id="loginForm" method="post" action="<?php echo _url('index', 'formLogin'); ?>">
-			<div class="form-group">
-				<label class="group-name" for="username"><?php echo Minz_Translate::t('username'); ?></label>
-				<div class="group-controls">
-					<input type="text" id="username" name="username" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" autofocus="autofocus" />
-				</div>
-			</div>
-			<div class="form-group">
-				<label class="group-name" for="passwordPlain"><?php echo Minz_Translate::t('password'); ?></label>
-				<div class="group-controls">
+			<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" />
+					<input type="hidden" id="challenge" name="challenge" /><br />
 					<noscript><strong><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></strong></noscript>
-				</div>
-			</div>
-			<div class="form-group form-actions">
-				<div class="group-controls">
-					<button id="loginButton" type="submit" class="btn btn-important"><?php echo Minz_Translate::t('login'); ?></button>
-				</div>
-			</div>
+			</p><p>
+				<button id="loginButton" type="submit" class="btn btn-important"><?php echo Minz_Translate::t('login'); ?></button>
+			</p>
 		</form><?php
 			break;
 
@@ -39,5 +30,5 @@ if (Minz_Configuration::canLogIn()) {
 }
 ?>
 
-	<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>

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

@@ -12,6 +12,8 @@ if ($this->loginOk || Minz_Configuration::allowAnonymous()) {
 	} elseif ($output === 'global') {
 		$this->renderHelper ('view/global_view');
 	} else {
+		Minz_Request::_param ('output', 'normal');
+		$output = 'normal';
 		$this->renderHelper ('view/normal_view');
 	}
 } elseif ($output === 'rss') {

+ 125 - 0
app/views/index/stats.phtml

@@ -0,0 +1,125 @@
+<div class="post content">
+	<a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a>
+	
+	<h1><?php echo Minz_Translate::t ('stats'); ?></h1>
+	
+	<div class="stat">
+		<h2><?php echo Minz_Translate::t ('stats_entry_repartition')?></h2>
+		<table>
+			<thead>
+				<tr>
+					<th> </th>
+					<th><?php echo Minz_Translate::t ('main_stream')?></th>
+					<th><?php echo Minz_Translate::t ('all_feeds')?></th>
+				</tr>
+			</thead>
+			<tbody>
+				<tr>
+					<th><?php echo Minz_Translate::t ('status_total')?></th>
+					<td class="numeric"><?php echo $this->repartition['main_stream']['total']?></td>
+					<td class="numeric"><?php echo $this->repartition['all_feeds']['total']?></td>
+				</tr>
+				<tr>
+					<th><?php echo Minz_Translate::t ('status_read')?></th>
+					<td class="numeric"><?php echo $this->repartition['main_stream']['read']?></td>
+					<td class="numeric"><?php echo $this->repartition['all_feeds']['read']?></td>
+				</tr>
+				<tr>
+					<th><?php echo Minz_Translate::t ('status_unread')?></th>
+					<td class="numeric"><?php echo $this->repartition['main_stream']['unread']?></td>
+					<td class="numeric"><?php echo $this->repartition['all_feeds']['unread']?></td>
+				</tr>
+				<tr>
+					<th><?php echo Minz_Translate::t ('status_favorites')?></th>
+					<td class="numeric"><?php echo $this->repartition['main_stream']['favorite']?></td>
+					<td class="numeric"><?php echo $this->repartition['all_feeds']['favorite']?></td>
+				</tr>
+			</tbody>
+		</table>
+	</div>
+	
+	<div class="stat">
+		<h2><?php echo Minz_Translate::t ('stats_entry_per_day')?></h2>
+		<div id="statsEntryPerDay" style="height: 300px"></div>
+	</div>
+	
+	<div class="stat">
+		<h2><?php echo Minz_Translate::t ('stats_feed_per_category')?></h2>
+		<div id="statsFeedPerCategory" style="height: 300px"></div>
+		<div id="statsFeedPerCategoryLegend"></div>
+	</div>
+	
+	<div class="stat">
+		<h2><?php echo Minz_Translate::t ('stats_entry_per_category')?></h2>
+		<div id="statsEntryPerCategory" style="height: 300px"></div>
+		<div id="statsEntryPerCategoryLegend"></div>
+	</div>
+	
+	<div class="stat">
+		<h2><?php echo Minz_Translate::t ('stats_top_feed')?></h2>
+		<table>
+			<thead>
+				<tr>
+					<th><?php echo Minz_Translate::t ('feed')?></th>
+					<th><?php echo Minz_Translate::t ('category')?></th>
+					<th><?php echo Minz_Translate::t ('stats_entry_count')?></th>
+				</tr>
+			</thead>
+			<tbody>
+				<?php foreach ($this->topFeed as $feed):?>
+					<tr>
+						<td><?php echo $feed['name']?></td>
+						<td><?php echo $feed['category']?></td>
+						<td class="numeric"><?php echo $feed['count']?></td>
+					</tr>
+				<?php endforeach;?>
+			</tbody>
+		</table>
+	</div>
+</div>
+
+<script>
+"use strict";
+function initStats() {
+	if (!window.Flotr) {
+		if (window.console) {
+			console.log('FreshRSS waiting for Flotr…');
+		}
+		window.setTimeout(initStats, 50);
+		return;
+	}
+	// Entry per day
+	Flotr.draw(document.getElementById('statsEntryPerDay'),
+		[<?php echo $this->count ?>],
+		{
+			grid: {verticalLines: false},
+			bars: {horizontal: false, show: true},
+			xaxis: {noTicks: 6, showLabels: false, tickDecimals: 0},
+			yaxis: {min: 0},
+			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.y;}}
+		});
+	// Feed per category
+	Flotr.draw(document.getElementById('statsFeedPerCategory'),
+		<?php echo $this->feedByCategory ?>,
+		{
+			grid: {verticalLines: false, horizontalLines: false},
+			pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
+			xaxis: {showLabels: false},
+			yaxis: {showLabels: false},
+			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ obj.y + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}},
+			legend: {container: document.getElementById('statsFeedPerCategoryLegend'), noColumns: 3}
+		});
+	// Entry per category
+	Flotr.draw(document.getElementById('statsEntryPerCategory'),
+		<?php echo $this->entryByCategory ?>,
+		{
+			grid: {verticalLines: false, horizontalLines: false},
+			pie: {explode: 10, show: true, labelFormatter: function(){return '';}},
+			xaxis: {showLabels: false},
+			yaxis: {showLabels: false},
+			mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return obj.series.label + ' - '+ obj.y + ' ('+ (obj.fraction * 100).toFixed(1) + '%)';}},
+			legend: {container: document.getElementById('statsEntryPerCategoryLegend'), noColumns: 3}
+		});
+}
+initStats();
+</script>

+ 29 - 10
lib/Minz/Configuration.php

@@ -69,8 +69,24 @@ class Minz_Configuration {
 	public static function salt () {
 		return self::$salt;
 	}
-	public static function environment () {
-		return self::$environment;
+	public static function environment ($str = false) {
+		$env = self::$environment;
+
+		if ($str) {
+			switch (self::$environment) {
+			case self::SILENT:
+				$env = 'silent';
+				break;
+			case self::DEVELOPMENT:
+				$env = 'development';
+				break;
+			case self::PRODUCTION:
+			default:
+				$env = 'production';
+			}
+		}
+
+		return $env;
 	}
 	public static function baseUrl () {
 		return self::$base_url;
@@ -147,7 +163,7 @@ class Minz_Configuration {
 	public static function writeFile() {
 		$ini_array = array(
 			'general' => array(
-				'environment' => self::$environment,
+				'environment' => self::environment(true),
 				'use_url_rewriting' => self::$use_url_rewriting,
 				'salt' => self::$salt,
 				'base_url' => self::$base_url,
@@ -205,23 +221,26 @@ class Minz_Configuration {
 
 		if (isset ($general['environment'])) {
 			switch ($general['environment']) {
-			case Minz_Configuration::SILENT:
 			case 'silent':
 				self::$environment = Minz_Configuration::SILENT;
 				break;
-			case Minz_Configuration::DEVELOPMENT:
 			case 'development':
 				self::$environment = Minz_Configuration::DEVELOPMENT;
 				break;
-			case Minz_Configuration::PRODUCTION:
 			case 'production':
 				self::$environment = Minz_Configuration::PRODUCTION;
 				break;
 			default:
-				throw new Minz_BadConfigurationException (
-					'environment',
-					Minz_Exception::ERROR
-				);
+				if ($general['environment'] >= 0 &&
+					$general['environment'] <= 2) {
+					// fallback 0.7-beta
+					self::$environment = $general['environment'];
+				} else {
+					throw new Minz_BadConfigurationException (
+						'environment',
+						Minz_Exception::ERROR
+					);
+				}
 			}
 
 		}

+ 1 - 1
lib/Minz/Error.php

@@ -21,7 +21,7 @@ class Minz_Error {
 	*/
 	public static function error ($code = 404, $logs = array (), $redirect = false) {
 		$logs = self::processLogs ($logs);
-		$error_filename = APP_PATH . '/Controllers/ErrorController.php';
+		$error_filename = APP_PATH . '/Controllers/errorController.php';
 
 		if (file_exists ($error_filename)) {
 			$params = array (

+ 56 - 5
lib/lib_rss.php

@@ -62,6 +62,11 @@ function small_hash ($txt) {
 	return strtr ($t, '+/', '-_');
 }
 
+function formatNumber($n, $precision = 0) {
+	return str_replace(' ', '&#8239;',	//Espace fine insécable
+		number_format($n, $precision, '.', ' '));	//number_format does not seem to be Unicode-compatible
+}
+
 function formatBytes($bytes, $precision = 2, $system = 'IEC') {
 	if ($system === 'IEC') {
 		$base = 1024;
@@ -74,7 +79,7 @@ function formatBytes($bytes, $precision = 2, $system = 'IEC') {
 	$pow = $bytes === 0 ? 0 : floor(log($bytes) / log($base));
 	$pow = min($pow, count($units) - 1);
 	$bytes /= pow($base, $pow);
-	return round($bytes, $precision) . ' ' . $units[$pow];
+	return formatNumber($bytes, $precision) . ' ' . $units[$pow];
 }
 
 function timestamptodate ($t, $hour = true) {
@@ -106,13 +111,59 @@ function html_only_entity_decode($text) {
 	return strtr($text, $htmlEntitiesOnly);
 }
 
-function sanitizeHTML($data) {
+function customSimplePie() {
+	$simplePie = new SimplePie();
+	$simplePie->set_useragent(Minz_Translate::t('freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ') ' . SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION);
+	$simplePie->set_cache_location(CACHE_PATH);
+	$simplePie->set_cache_duration(1500);
+	$simplePie->strip_htmltags(array(
+		'base', 'blink', 'body', 'doctype', 'embed',
+		'font', 'form', 'frame', 'frameset', 'html',
+		'link', 'input', 'marquee', 'meta', 'noscript',
+		'object', 'param', 'plaintext', 'script', 'style',
+	));
+	$simplePie->strip_attributes(array_merge($simplePie->strip_attributes, array(
+		'autoplay', 'onload', 'onunload', 'onclick', 'ondblclick', 'onmousedown', 'onmouseup',
+		'onmouseover', 'onmousemove', 'onmouseout', 'onfocus', 'onblur',
+		'onkeypress', 'onkeydown', 'onkeyup', 'onselect', 'onchange', 'seamless')));
+	$simplePie->add_attributes(array(
+		'img' => array('lazyload' => ''),	//http://www.w3.org/TR/resource-priorities/
+		'audio' => array('preload' => 'none'),
+		'iframe' => array('postpone' => '', 'sandbox' => 'allow-scripts allow-same-origin'),
+		'video' => array('postpone' => '', 'preload' => 'none'),
+	));
+	$simplePie->set_url_replacements(array(
+		'a' => 'href',
+		'area' => 'href',
+		'audio' => 'src',
+		'blockquote' => 'cite',
+		'del' => 'cite',
+		'form' => 'action',
+		'iframe' => 'src',
+		'img' => array(
+			'longdesc',
+			'src'
+		),
+		'input' => 'src',
+		'ins' => 'cite',
+		'q' => 'cite',
+		'source' => 'src',
+		'track' => 'src',
+		'video' => array(
+			'poster',
+			'src',
+		),
+	));
+	return $simplePie;
+}
+
+function sanitizeHTML($data, $base = '') {
 	static $simplePie = null;
 	if ($simplePie == null) {
-		$simplePie = new SimplePie();
+		$simplePie = customSimplePie();
 		$simplePie->init();
 	}
-	return html_only_entity_decode($simplePie->sanitize->sanitize($data, SIMPLEPIE_CONSTRUCT_MAYBE_HTML));
+	return html_only_entity_decode($simplePie->sanitize->sanitize($data, SIMPLEPIE_CONSTRUCT_HTML, $base));
 }
 
 /* permet de récupérer le contenu d'un article pour un flux qui n'est pas complet */
@@ -125,7 +176,7 @@ function get_content_by_parsing ($url, $path) {
 	if ($html) {
 		$doc = phpQuery::newDocument ($html);
 		$content = $doc->find ($path);
-		return sanitizeHTML($content->__toString());
+		return sanitizeHTML($content->__toString(), $url);
 	} else {
 		throw new Exception ();
 	}

BIN
p/favicon.ico


+ 70 - 30
p/i/install.php

@@ -4,7 +4,7 @@ if (function_exists('opcache_reset')) {
 }
 
 require('../../constants.php');
-const BCRYPT_COST = 9;
+define('BCRYPT_COST', 9);
 
 include(LIB_PATH . '/lib_rss.php');
 
@@ -87,6 +87,8 @@ SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads
 ');
 
 define('SQL_UPDATE_HISTORYv007b', 'UPDATE `%1$sfeed` SET keep_history = CASE WHEN keep_history = 0 THEN -2 WHEN keep_history = 1 THEN -1 ELSE keep_history END;');
+
+define('SQL_GET_FEEDS', 'SELECT id, url, website FROM `%1$sfeed`;');
 //</updates>
 
 // gestion internationalisation
@@ -170,6 +172,9 @@ function saveStep2 () {
 		$_SESSION['default_user'] = substr(preg_replace('/[^a-zA-Z0-9]/', '', $_POST['default_user']), 0, 16);
 		$_SESSION['auth_type'] = $_POST['auth_type'];
 		if (!empty($_POST['passwordPlain'])) {
+			if (!function_exists('password_hash')) {
+				include_once(LIB_PATH . '/password_compat.php');
+			}
 			$passwordHash = password_hash($_POST['passwordPlain'], PASSWORD_BCRYPT, array('cost' => BCRYPT_COST));
 			$passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash);	//Compatibility with bcrypt.js
 			$_SESSION['passwordHash'] = $passwordHash;
@@ -307,14 +312,6 @@ function updateDatabase($perform = false) {
 			$stm->execute();
 		}
 
-		$sql = sprintf(SQL_UPDATE_HISTORYv007b, $_SESSION['bd_prefix_user']);
-		$stm = $c->prepare($sql);
-		$stm->execute();
-
-		$sql = sprintf(SQL_UPDATE_CACHED_VALUES, $_SESSION['bd_prefix_user']);
-		$stm = $c->prepare($sql);
-		$stm->execute();
-
 		$sql = sprintf(SQL_CONVERT_SELECTv006, $_SESSION['bd_prefix'], $_SESSION['bd_prefix_user']);
 		if (!$perform) {
 			$sql .= ' LIMIT 1';
@@ -336,6 +333,7 @@ function updateDatabase($perform = false) {
 			$content = unserialize(gzinflate(base64_decode($row['content'])));
 			$stm2->execute(array($content, $id));
 		}
+
 		return true;
 	} catch (PDOException $e) {
 		return false;
@@ -343,6 +341,51 @@ function updateDatabase($perform = false) {
 	return false;
 }
 
+function newPdo() {
+	switch ($_SESSION['bd_type']) {
+		case 'mysql':
+			$str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base'];
+			$driver_options = array(
+				PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
+			);
+			break;
+		case 'sqlite':
+			$str = 'sqlite:' . DATA_PATH . $_SESSION['bd_base'] . '.sqlite';
+			$driver_options = null;
+			break;
+		default:
+			return false;
+	}
+	return new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options);
+}
+
+function postUpdate() {
+	$c = newPdo();
+
+	$sql = sprintf(SQL_UPDATE_HISTORYv007b, $_SESSION['bd_prefix_user']);
+	$stm = $c->prepare($sql);
+	$stm->execute();
+
+	$sql = sprintf(SQL_UPDATE_CACHED_VALUES, $_SESSION['bd_prefix_user']);
+	$stm = $c->prepare($sql);
+	$stm->execute();
+
+	//<favicons>
+	$sql = sprintf(SQL_GET_FEEDS, $_SESSION['bd_prefix_user']);
+	$stm = $c->prepare($sql);
+	$stm->execute();
+	$res = $stm->fetchAll(PDO::FETCH_ASSOC);
+	foreach ($res as $feed) {
+		if (empty($feed['url'])) {
+			continue;
+		}
+		$hash = hash('crc32b', $_SESSION['salt'] . $feed['url']);
+		@file_put_contents(DATA_PATH . '/favicons/' . $hash . '.txt',
+			empty($feed['website']) ? $feed['url'] : $feed['website']);
+	}
+	//</favicons>
+}
+
 function deleteInstall () {
 	$res = unlink (INDEX_PATH . '/install.php');
 	if ($res) {
@@ -357,22 +400,7 @@ function deleteInstall () {
 	}
 
 	try {
-		switch ($_SESSION['bd_type']) {
-			case 'mysql':
-				$str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base'];
-				$driver_options = array(
-					PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
-				);
-				break;
-			case 'sqlite':
-				$str = 'sqlite:' . DATA_PATH . $_SESSION['bd_base'] . '.sqlite';
-				$driver_options = null;
-				break;
-			default:
-				return false;
-		}
-
-		$c = new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options);
+		$c = newPdo();
 		$sql = sprintf(SQL_DROP_BACKUPv006, $_SESSION['bd_prefix']);
 		$stm = $c->prepare($sql);
 		$stm->execute();
@@ -805,7 +833,9 @@ function printStep2 () {
 			<label class="group-name" for="auth_type"><?php echo _t('auth_type'); ?></label>
 			<div class="group-controls">
 				<select id="auth_type" name="auth_type" required="required">
-					<option value=""></option>
+					<?php if (!in_array($_SESSION['auth_type'], array('form', 'persona', 'http_auth', 'none'))) { ?>
+						<option selected="selected"></option>
+					<?php } ?>
 					<option value="form"<?php echo $_SESSION['auth_type'] === 'form' ? ' selected="selected"' : '', version_compare(PHP_VERSION, '5.3', '<') ? ' disabled="disabled"' : ''; ?>><?php echo _t('auth_form'); ?></option>
 					<option value="persona"<?php echo $_SESSION['auth_type'] === 'persona' ? ' selected="selected"' : ''; ?>><?php echo _t('auth_persona'); ?></option>
 					<option value="http_auth"<?php echo $_SESSION['auth_type'] === 'http_auth' ? ' selected="selected"' : '', httpAuthUser() == '' ? ' disabled="disabled"' : ''; ?>><?php echo _t('http_auth'); ?> (REMOTE_USER = '<?php echo httpAuthUser(); ?>')</option>
@@ -922,17 +952,26 @@ function printStep4 () {
 ?>
 	<form action="index.php?step=4" method="post">
 		<legend><?php echo _t ('version_update'); ?></legend>
+
+		<?php if (updateDatabase(false)) { ?>
+		<p class="alert"><?php echo _t ('update_long'); ?></p>
+
 		<div class="form-group form-actions">
 			<div class="group-controls">
-				<?php if (updateDatabase(false)) { ?>
 				<input type="hidden" name="updateDatabase" value="1" />
 				<button type="submit" class="btn btn-important"><?php echo _t ('update_start'); ?></button>
-				<p><?php echo _t ('update_long'); ?></p>
-				<?php } else { ?>
+			</div>
+		</div>
+
+		<?php } else { ?>
+		<p class="alert"><?php echo _t ('update_end'); ?></p>
+
+		<div class="form-group form-actions">
+			<div class="group-controls">
 				<a class="btn btn-important next-step" href="?step=5"><?php echo _t ('next_step'); ?></a>
-				<?php } ?>
 			</div>
 		</div>
+		<?php } ?>
 	</form>
 <?php
 }
@@ -973,6 +1012,7 @@ case 4:
 	}
 	break;
 case 5:
+	postUpdate();
 	break;
 case 6:
 	deleteInstall ();

File diff suppressed because it is too large
+ 9 - 0
p/scripts/flotr2.min.js


File diff suppressed because it is too large
+ 0 - 0
p/scripts/jquery-2.0.3.min.map


File diff suppressed because it is too large
+ 0 - 3
p/scripts/jquery.min.js


+ 22 - 10
p/scripts/main.js

@@ -59,14 +59,18 @@ function incUnreadsFeed(article, feed_id, nb) {
 		}
 	}
 
+	var isCurrentView = false;
 	//Update unread: title
 	document.title = document.title.replace(/((?: \(\d+\))?)( · .*?)((?: \(\d+\))?)$/, function (m, p1, p2, p3) {
-		if (article || ($('#' + feed_id).closest('.active').length > 0)) {
+		var $feed = $('#' + feed_id);
+		if (article || ($feed.closest('.active').length > 0 && $feed.siblings('.active').length === 0)) {
+			isCurrentView = true;
 			return incLabel(p1, nb) + p2 + incLabel(p3, feed_priority > 0 ? nb : 0);
 		} else {
 			return p1 + p2 + incLabel(p3, feed_priority > 0 ? nb : 0);
 		}
 	});
+	return isCurrentView;
 }
 
 function mark_read(active, only_not_read) {
@@ -519,11 +523,15 @@ function init_notifications() {
 
 function refreshUnreads() {
 	$.getJSON('./?c=javascript&a=nbUnreadsPerFeed').done(function (data) {
+		var isAll = $('.category.all > .active').length > 0;
 		$.each(data, function(feed_id, nbUnreads) {
 			feed_id = 'f_' + feed_id;
 			var elem = $('#' + feed_id + '>.feed').get(0),
 				feed_unreads = elem ? (parseInt(elem.getAttribute('data-unread'), 10) || 0) : 0;
-			incUnreadsFeed(null, feed_id, nbUnreads - feed_unreads);
+			if ((incUnreadsFeed(null, feed_id, nbUnreads - feed_unreads) || isAll) &&	//Update of current view?
+				(nbUnreads - feed_unreads > 0)) {
+				$('#new-article').show();
+			};
 		});
 	});
 }
@@ -618,14 +626,18 @@ function init_loginForm() {
 			if (data.salt1 == '' || data.nonce == '') {
 				alert('Invalid user!');
 			} else {
-				var strong = window.Uint32Array && window.crypto && (typeof window.crypto.getRandomValues === 'function'),
-					s = dcodeIO.bcrypt.hashSync($('#passwordPlain').val(), data.salt1),
-					c = dcodeIO.bcrypt.hashSync(data.nonce + s, strong ? 4 : poormanSalt());
-				$('#challenge').val(c);
-				if (s == '' || c == '') {
-					alert('Crypto error!');
-				} else {
-					success = true;
+				try {
+					var strong = window.Uint32Array && window.crypto && (typeof window.crypto.getRandomValues === 'function'),
+						s = dcodeIO.bcrypt.hashSync($('#passwordPlain').val(), data.salt1),
+						c = dcodeIO.bcrypt.hashSync(data.nonce + s, strong ? 4 : poormanSalt());
+					$('#challenge').val(c);
+					if (s == '' || c == '') {
+						alert('Crypto error!');
+					} else {
+						success = true;
+					}
+				} catch (e) {
+					alert('Crypto exception! ' + e);
 				}
 			}
 		}).fail(function() {

+ 78 - 23
p/themes/Dark/freshrss.css

@@ -31,7 +31,6 @@
 			}
 		.header > .item.search input {
 			width: 230px;
-			transition: width 200ms linear;
 		}
 			.header .item.search input:focus {
 				width: 330px;
@@ -201,10 +200,9 @@
 	font-weight: bold;
 	line-height: 50px;
 	background: #1c1c1c;
-	border-top: 1px solid #888;
-	border-bottom: 1px solid #888;
+	border-top: 1px solid #2f2f2f;
 }
-	.day:first-child {
+	#new-article + .day {
 		border-top: none;
 	}
 	.day .name {
@@ -224,8 +222,27 @@
 		text-align: right;
 	}
 
+#new-article {
+	display: none;
+	min-height: 40px;
+	background: #26303F;
+	text-align: center;
+}
+	#new-article:hover {
+		background: #4A5D7A;
+	}
+	#new-article > a {
+		display: block;
+		line-height: 40px;
+		color: #fff;
+		font-weight: bold;
+	}
+		#new-article > a:hover {
+			text-decoration: none;
+		}
+
 .flux {
-	border-left: 3px solid #aaa;
+	border-left: 3px solid #2f2f2f;
 	background: #1c1c1c;
 }
 	.flux.not_read {
@@ -241,6 +258,10 @@
 		background: #1a1a1a;
 	}
 
+	.horizontal-list > .item:not(.title):not(.website) > a {
+		display: block;
+	}
+
 	.flux_header {
 		background: inherit;
 		height: 25px;
@@ -268,11 +289,6 @@
 			}
 		.flux .item.title {
 			background: inherit;
-		}
-		.flux:hover .item.title {
-			border-right: 2px solid rgba(127, 127, 127, 0.1);
-			padding-right: 1em;
-			position: absolute;
 		}
 			.flux .item.title a {
 				color: #888;
@@ -283,7 +299,7 @@
 				font-weight: bold;
 			}
 		.flux .item.date {
-			width: 200px;
+			width: 145px;
 			padding:0 5px 0 0;
 			text-align: right;
 			font-size: 10px;
@@ -652,6 +668,14 @@ select.number option {
 	text-align:right;
 }
 
+@media(min-width: 841px) {
+	.flux:not(.current):hover .item.title {
+		max-width: calc(100% - 580px);
+		padding-right: 1.5em;
+		position: absolute;
+	}
+}
+
 @media(max-width: 840px) {
 	.header,
 	.aside .btn-important,
@@ -672,11 +696,6 @@ select.number option {
 		display: block;
 	}
 
-	.content {
-		font-size: 120%;
-		padding: 0;
-	}
-
 	.pagination {
 		margin: 0 0 40px;
 	}
@@ -801,16 +820,12 @@ select.number option {
 }
 
 .nav-head {
-	background: #fff;
-	background: -moz-linear-gradient(top, #fff 0%, #f0f0f0 100%);
-	background: -webkit-linear-gradient(top, #fff 0%, #f0f0f0 100%);
-	background: -o-linear-gradient(top, #fff 0%, #f0f0f0 100%);
-	background: -ms-linear-gradient(top, #fff 0%, #f0f0f0 100%);
+	background: #1c1c1c;
 }
 
-.header > .item.search input {
+input.extend {
 	-moz-transition: width 200ms linear;
-	 -webkit-transition: width 200ms linear;
+	-webkit-transition: width 200ms linear;
 	-o-transition: width 200ms linear;
 	-ms-transition: width 200ms linear;
 }
@@ -860,3 +875,43 @@ select.number option {
 		text-decoration: underline;
 	}
 }
+
+.stat {
+	border:1px solid #2f2f2f;
+	border-radius:10px;
+	margin:10px 0;
+	padding:0 5px;
+}
+.stat > h2 {
+	border-bottom:1px solid #2f2f2f;
+	margin:0 -5px;
+	padding-left:5px;
+}
+.stat > div {
+	margin:5px 0;
+}
+.stat > table {
+	border-collapse:collapse;
+	margin:5px 0;
+	width:100%;
+}
+.stat > table > thead > tr {
+	border-bottom:2px solid #2f2f2f;
+}
+.stat > table > tbody > tr {
+	border-bottom:1px solid #2f2f2f;
+}
+.stat > table > tbody > tr:last-child {
+	border-bottom:0;
+}
+.stat > table th, .stat > table td {
+	border-left:2px solid #2f2f2f;
+	padding:5px;
+}
+.stat > table th:first-child, .stat > table td:first-child {
+	border-left:0;
+}
+.stat > table td.numeric{
+	margin:5px 0;
+	text-align:center;
+}

+ 36 - 3
p/themes/Dark/global.css

@@ -87,6 +87,13 @@ label {
 	line-height: 25px;
 	cursor: pointer;
 }
+input {
+	width: 180px;
+}
+textarea {
+	width: 360px;
+	height: 100px;
+}
 input, select, textarea {
 	display: inline-block;
 	max-width: 100%;
@@ -116,6 +123,10 @@ input, select, textarea {
 		border-color: red;
 		box-shadow: 0 0 2px 1px red;
 	}
+	input:focus.extend {
+		width: 300px;
+		transition: width 200ms linear;
+	}
 
 .form-group {
 	margin: 0;
@@ -179,8 +190,13 @@ input, select, textarea {
 	}
 	.stick .btn + .btn,
 	.stick .btn + input,
+	.stick .btn + .dropdown > .btn,
 	.stick input + .btn,
-	.stick input + input {
+	.stick input + input,
+	.stick input + .dropdown > .btn,
+	.stick .dropdown + .btn,
+	.stick .dropdown + input,
+	.stick .dropdown + .dropdown > .btn {
 		border-left: none;
 	}
 	.stick .btn + .dropdown > .btn {
@@ -305,7 +321,7 @@ input, select, textarea {
 		display: block;
 		height: 0;
 		margin: 5px 0;
-		border-bottom: 1px solid #ddd;
+		border-bottom: 1px solid #2f2f2f;
 	}
 
 	.nav-list .nav-form {
@@ -317,7 +333,7 @@ input, select, textarea {
 	display: block;
 	margin: 0;
 	background: linear-gradient(to bottom, #fff, #f0f0f0);
-	border-bottom: 1px solid #ddd;
+	border-bottom: 1px solid #2f2f2f;
 	text-align: right;
 }
 	.nav-head .item {
@@ -489,3 +505,20 @@ input, select, textarea {
 	vertical-align: middle;
 	line-height: 16px;
 }
+
+/* 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%;
+}

+ 80 - 12
p/themes/Flat/freshrss.css

@@ -34,7 +34,6 @@ body {
 			}
 		.header > .item.search input {
 			width: 230px;
-			transition: width 200ms linear;
 		}
 			.header .item.search input:focus {
 				width: 330px;
@@ -217,6 +216,25 @@ body {
 		z-index: -10;
 	}
 
+#new-article {
+	display: none;
+	min-height: 40px;
+	background: #3498db;
+	text-align: center;
+}
+	#new-article:hover {
+		background: #2980b9;
+	}
+	#new-article > a {
+		display: block;
+		line-height: 40px;
+		color: #fff;
+		font-weight: bold;
+	}
+		#new-article > a:hover {
+			text-decoration: none;
+		}
+
 .flux {
 	border-left: 3px solid #ecf0f1;
 }
@@ -236,6 +254,10 @@ body {
 		background: #fff;
 	}
 
+	.horizontal-list > .item:not(.title):not(.website) > a {
+		display: block;
+	}
+
 	.flux_header {
 		background: inherit;
 		height: 25px;
@@ -263,11 +285,6 @@ body {
 			}
 		.flux .item.title {
 			background: inherit;
-		}
-		.flux:hover .item.title {
-			border-right: 2px solid rgba(127, 127, 127, 0.1);
-			padding-right: 1em;
-			position: absolute;
 		}
 			.flux .item.title a {
 				color: #333;
@@ -278,7 +295,7 @@ body {
 				font-weight: bold;
 			}
 		.flux .item.date {
-			width: 200px;
+			width: 145px;
 			padding:0 5px 0 0;
 			text-align: right;
 			font-size: 10px;
@@ -656,6 +673,14 @@ select.number option {
 	text-align:right;
 }
 
+@media(min-width: 841px) {
+	.flux:not(.current):hover .item.title {
+		max-width: calc(100% - 580px);
+		padding-right: 1.5em;
+		position: absolute;
+	}
+}
+
 @media(max-width: 840px) {
 	.header,
 	.aside .btn-important,
@@ -676,11 +701,6 @@ select.number option {
 		display: block;
 	}
 
-	.content {
-		font-size: 120%;
-		padding: 0;
-	}
-
 	.pagination {
 		margin: 0 0 40px;
 	}
@@ -779,6 +799,13 @@ select.number option {
 	-ms-transform: rotate(45deg);
 }
 
+input.extend {
+	-moz-transition: width 200ms linear;
+	-webkit-transition: width 200ms linear;
+	-o-transition: width 200ms linear;
+	-ms-transition: width 200ms linear;
+}
+
 @media print {
 	.header,
 	.aside,
@@ -815,3 +842,44 @@ select.number option {
 		text-decoration: underline;
 	}
 }
+
+.stat {
+	border:1px solid #aaa;
+	border-radius:10px;
+	box-shadow:2px 2px 5px #aaa;
+	margin:10px 0;
+	padding:0 5px;
+}
+.stat > h2 {
+	border-bottom:1px solid #aaa;
+	margin:0 -5px;
+	padding-left:5px;
+}
+.stat > div {
+	margin:5px 0;
+}
+.stat > table {
+	border-collapse:collapse;
+	margin:5px 0;
+	width:100%;
+}
+.stat > table > thead > tr {
+	border-bottom:2px solid #aaa;
+}
+.stat > table > tbody > tr {
+	border-bottom:1px solid #aaa;
+}
+.stat > table > tbody > tr:last-child {
+	border-bottom:0;
+}
+.stat > table th, .stat > table td {
+	border-left:2px solid #aaa;
+	padding:5px;
+}
+.stat > table th:first-child, .stat > table td:first-child {
+	border-left:0;
+}
+.stat > table td.numeric{
+	margin:5px 0;
+	text-align:center;
+}

+ 28 - 0
p/themes/Flat/global.css

@@ -88,6 +88,13 @@ label {
 	font-weight: bold;
 	color: #444;
 }
+input {
+	width: 180px;
+}
+textarea {
+	width: 360px;
+	height: 100px;
+}
 input, select, textarea {
 	display: inline-block;
 	max-width: 100%;
@@ -117,6 +124,10 @@ input, select, textarea {
 		border-color: red;
 		box-shadow: 0 0 2px 1px red;
 	}
+	input:focus.extend {
+		width: 300px;
+		transition: width 200ms linear;
+	}
 
 .form-group {
 	margin: 5px 0;
@@ -497,3 +508,20 @@ input, select, textarea {
 	vertical-align: middle;
 	line-height: 16px;
 }
+
+/* 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%;
+}

+ 31 - 0
p/themes/Flat/icons/category.svg

@@ -0,0 +1,31 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg xmlns:cc='http://creativecommons.org/ns#' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' xmlns:svg='http://www.w3.org/2000/svg' id='svg7384' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' sodipodi:docname='folder-symbolic.svg' version='1.1' inkscape:version='0.48.0 r9654' height='16' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns='http://www.w3.org/2000/svg' width='16'>
+  <metadata id='metadata90'>
+    <rdf:RDF>
+      <cc:Work rdf:about=''>
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type rdf:resource='http://purl.org/dc/dcmitype/StillImage'/>
+        <dc:title>Gnome Symbolic Icon Theme</dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview inkscape:cy='-173.07332' pagecolor='#555753' borderopacity='1' showborder='false' inkscape:bbox-paths='false' guidetolerance='10' inkscape:window-width='1310' showguides='true' inkscape:object-nodes='true' inkscape:snap-bbox='true' inkscape:pageshadow='2' inkscape:guide-bbox='true' inkscape:snap-nodes='true' bordercolor='#FFFFFF' objecttolerance='10' id='namedview88' showgrid='false' inkscape:window-maximized='0' inkscape:window-x='52' inkscape:snap-global='true' inkscape:window-y='24' gridtolerance='10' inkscape:window-height='690' inkscape:snap-to-guides='true' inkscape:current-layer='layer13' inkscape:zoom='1' inkscape:cx='-157.67647' inkscape:snap-grids='true' inkscape:pageopacity='1'>
+    <inkscape:grid spacingx='1px' spacingy='1px' id='grid4866' empspacing='2' enabled='true' type='xygrid' snapvisiblegridlinesonly='true' visible='true'/>
+  </sodipodi:namedview>
+  <title id='title9167'>Gnome Symbolic Icon Theme</title>
+  <defs id='defs7386'/>
+  <g inkscape:label='status' transform='translate(-442,-176)' inkscape:groupmode='layer' id='layer9' style='display:inline'/>
+  <g inkscape:label='devices' transform='translate(-442,-176)' inkscape:groupmode='layer' id='layer10'/>
+  <g inkscape:label='apps' transform='translate(-442,-176)' inkscape:groupmode='layer' id='layer11'/>
+  <g inkscape:label='actions' transform='translate(-442,-176)' inkscape:groupmode='layer' id='layer12'/>
+  <g inkscape:label='places' transform='translate(-442,-176)' inkscape:groupmode='layer' id='layer13'>
+    <g transform='translate(234.0002,-820)' id='g14154'>
+      <path inkscape:connector-curvature='0' d='m 208.53105,997 c -0.28913,0 -0.53125,0.24212 -0.53125,0.53125 l 0,13.93755 c 0,0.2985 0.23264,0.5312 0.53125,0.5312 l 14.9375,0 c 0.2986,0 0.53125,-0.2326 0.53125,-0.5312 l 0,-8.9376 c 0,-0.2891 -0.24212,-0.5312 -0.53125,-0.5312 l -12.46875,0 0,7.5 c 0,0.277 -0.223,0.5 -0.5,0.5 -0.277,0 -0.5,-0.223 -0.5,-0.5 l 0,-8 c 0,-0.277 0.223,-0.5 0.5,-0.5 l 2.96875,0 8.53125,0 0,-1.4062 c 0,-0.3272 -0.26666,-0.5938 -0.59375,-0.5938 l -7.40625,0 0,-1.46875 C 213.9998,997.2421 213.75768,997 213.46855,997 z' id='rect3845' sodipodi:nodetypes='ccccccccccsccccccccccc' style='fill:#FFFFFF;fill-opacity:1;stroke:none;display:inline'/>
+      
+    </g>
+  </g>
+  <g inkscape:label='mimetypes' transform='translate(-442,-176)' inkscape:groupmode='layer' id='layer14'/>
+  <g inkscape:label='emblems' transform='translate(-442,-176)' inkscape:groupmode='layer' id='layer15' style='display:inline'/>
+</svg>

+ 77 - 16
p/themes/Origine/freshrss.css

@@ -32,7 +32,6 @@
 			}
 		.header > .item.search input {
 			width: 230px;
-			transition: width 200ms linear;
 		}
 			.header .item.search input:focus {
 				width: 330px;
@@ -210,7 +209,7 @@
 	border-top: 1px solid #aaa;
 	border-bottom: 1px solid #aaa;
 }
-	.day:first-child {
+	#new-article + .day {
 		border-top: none;
 	}
 	.day .name {
@@ -230,6 +229,25 @@
 		text-align: right;
 	}
 
+#new-article {
+	display: none;
+	min-height: 40px;
+	background: #0084CC;
+	text-align: center;
+}
+	#new-article:hover {
+		background: #0066CC;
+	}
+	#new-article > a {
+		display: block;
+		line-height: 40px;
+		color: #fff;
+		font-weight: bold;
+	}
+		#new-article > a:hover {
+			text-decoration: none;
+		}
+
 .flux {
 	border-left: 3px solid #aaa;
 	background: #fafafa;
@@ -250,6 +268,10 @@
 		background: #fff;
 	}
 
+	.horizontal-list > .item:not(.title):not(.website) > a {
+		display: block;
+	}
+
 	.flux_header {
 		background: inherit;
 		height: 25px;
@@ -277,11 +299,6 @@
 			}
 		.flux .item.title {
 			background: inherit;
-		}
-		.flux:hover .item.title {
-			border-right: 2px solid rgba(127, 127, 127, 0.1);
-			padding-right: 1em;
-			position: absolute;
 		}
 			.flux .item.title a {
 				color: #000;
@@ -292,7 +309,7 @@
 				font-weight: bold;
 			}
 		.flux .item.date {
-			width: 200px;
+			width: 145px;
 			padding:0 5px 0 0;
 			text-align: right;
 			font-size: 10px;
@@ -657,6 +674,14 @@ select.number option {
 	text-align:right;
 }
 
+@media(min-width: 841px) {
+	.flux:not(.current):hover .item.title {
+		max-width: calc(100% - 580px);
+		padding-right: 1.5em;
+		position: absolute;
+	}
+}
+
 @media(max-width: 840px) {
 	.header,
 	.aside .btn-important,
@@ -677,11 +702,6 @@ select.number option {
 		display: block;
 	}
 
-	.content {
-		font-size: 120%;
-		padding: 0;
-	}
-
 	.pagination {
 		margin: 0 0 40px;
 	}
@@ -826,9 +846,9 @@ select.number option {
 	background: -ms-linear-gradient(top, #fff 0%, #f0f0f0 100%);
 }
 
-.header > .item.search input {
+input.extend {
 	-moz-transition: width 200ms linear;
-	 -webkit-transition: width 200ms linear;
+	-webkit-transition: width 200ms linear;
 	-o-transition: width 200ms linear;
 	-ms-transition: width 200ms linear;
 }
@@ -836,7 +856,7 @@ select.number option {
 @media(max-width: 840px) {
 	.aside {
 		-moz-transition: width 200ms linear;
-		 -webkit-transition: width 200ms linear;
+		-webkit-transition: width 200ms linear;
 		-o-transition: width 200ms linear;
 		-ms-transition: width 200ms linear;
 	}
@@ -878,3 +898,44 @@ select.number option {
 		text-decoration: underline;
 	}
 }
+
+.stat {
+	border:1px solid #aaa;
+	border-radius:10px;
+	box-shadow:2px 2px 5px #aaa;
+	margin:10px 0;
+	padding:0 5px;
+}
+.stat > h2 {
+	border-bottom:1px solid #aaa;
+	margin:0 -5px;
+	padding-left:5px;
+}
+.stat > div {
+	margin:5px 0;
+}
+.stat > table {
+	border-collapse:collapse;
+	margin:5px 0;
+	width:100%;
+}
+.stat > table > thead > tr {
+	border-bottom:2px solid #aaa;
+}
+.stat > table > tbody > tr {
+	border-bottom:1px solid #aaa;
+}
+.stat > table > tbody > tr:last-child {
+	border-bottom:0;
+}
+.stat > table th, .stat > table td {
+	border-left:2px solid #aaa;
+	padding:5px;
+}
+.stat > table th:first-child, .stat > table td:first-child {
+	border-left:0;
+}
+.stat > table td.numeric{
+	margin:5px 0;
+	text-align:center;
+}

+ 34 - 1
p/themes/Origine/global.css

@@ -86,6 +86,13 @@ label {
 	line-height: 25px;
 	cursor: pointer;
 }
+input {
+	width: 180px;
+}
+textarea {
+	width: 360px;
+	height: 100px;
+}
 input, select, textarea {
 	display: inline-block;
 	max-width: 100%;
@@ -116,6 +123,10 @@ input, select, textarea {
 		border-color: red;
 		box-shadow: 0 0 2px 1px red;
 	}
+	input:focus.extend {
+		width: 300px;
+		transition: width 200ms linear;
+	}
 
 .form-group {
 	margin: 0;
@@ -179,8 +190,13 @@ input, select, textarea {
 	}
 	.stick .btn + .btn,
 	.stick .btn + input,
+	.stick .btn + .dropdown > .btn,
 	.stick input + .btn,
-	.stick input + input {
+	.stick input + input,
+	.stick input + .dropdown > .btn,
+	.stick .dropdown + .btn,
+	.stick .dropdown + input,
+	.stick .dropdown + .dropdown > .btn {
 		border-left: none;
 	}
 	.stick input + .btn {
@@ -505,3 +521,20 @@ input, select, textarea {
 	vertical-align: middle;
 	line-height: 16px;
 }
+
+/* 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%;
+}

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


BIN
p/themes/icons/favicon-16-32-48-64.ico


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


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


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


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


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


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


+ 17 - 0
p/themes/icons/favicon.svg

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
+	<title>Logo FreshRSS</title>
+
+	<circle fill="#FFF" cx="128" cy="128" r="128"/>
+
+	<circle fill="#0062BE" cx="128" cy="128" r="33"/>
+
+	<g fill="none" stroke="#0062BE" stroke-width="24">
+		<g stroke-opacity="0.3">
+			<path d="M12,128 A116,116 0 1,1 128,244"/>
+			<path d="M54,128 A74,74 0 1,1 128,202"/>
+		</g>
+		<path d="M128,12 A116,116 0 0,1 244,128"/>
+		<path d="M128,54 A74,74 0 0,1 202,128"/>
+	</g>
+</svg>

+ 13 - 211
p/themes/icons/icon.svg

@@ -1,213 +1,15 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
+	<title>Logo FreshRSS</title>
 
-<svg
-   xmlns:dc="http://purl.org/dc/elements/1.1/"
-   xmlns:cc="http://creativecommons.org/ns#"
-   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-   xmlns:svg="http://www.w3.org/2000/svg"
-   xmlns="http://www.w3.org/2000/svg"
-   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
-   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
-   width="256"
-   height="256"
-   id="svg2"
-   version="1.1"
-   inkscape:version="0.48.4 r9939"
-   sodipodi:docname="icon.svg"
-   inkscape:export-filename="/home/cyp/Bureau/FreshRSS.png"
-   inkscape:export-xdpi="197"
-   inkscape:export-ydpi="197">
-  <title
-     id="title2991">Logo FreshRSS</title>
-  <defs
-     id="defs4" />
-  <sodipodi:namedview
-     id="base"
-     pagecolor="#ffffff"
-     bordercolor="#666666"
-     borderopacity="1.0"
-     inkscape:pageopacity="0"
-     inkscape:pageshadow="2"
-     inkscape:zoom="1.0000001"
-     inkscape:cx="211.05579"
-     inkscape:cy="58.65406"
-     inkscape:document-units="px"
-     inkscape:current-layer="layer1"
-     showgrid="false"
-     inkscape:window-width="1366"
-     inkscape:window-height="711"
-     inkscape:window-x="0"
-     inkscape:window-y="25"
-     inkscape:window-maximized="1"
-     inkscape:snap-global="true"
-     fit-margin-top="0"
-     fit-margin-left="0"
-     fit-margin-right="0"
-     fit-margin-bottom="0"
-     inkscape:showpageshadow="false" />
-  <metadata
-     id="metadata7">
-    <rdf:RDF>
-      <cc:Work
-         rdf:about="">
-        <dc:format>image/svg+xml</dc:format>
-        <dc:type
-           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
-        <dc:title>Logo FreshRSS</dc:title>
-        <dc:date>2013-08-20</dc:date>
-        <dc:creator>
-          <cc:Agent>
-            <dc:title>Cyprien POUZENC</dc:title>
-          </cc:Agent>
-        </dc:creator>
-        <dc:rights>
-          <cc:Agent>
-            <dc:title />
-          </cc:Agent>
-        </dc:rights>
-        <cc:license
-           rdf:resource="http://creativecommons.org/licenses/by-sa/3.0/" />
-      </cc:Work>
-      <cc:License
-         rdf:about="http://creativecommons.org/licenses/by-sa/3.0/">
-        <cc:permits
-           rdf:resource="http://creativecommons.org/ns#Reproduction" />
-        <cc:permits
-           rdf:resource="http://creativecommons.org/ns#Distribution" />
-        <cc:requires
-           rdf:resource="http://creativecommons.org/ns#Notice" />
-        <cc:requires
-           rdf:resource="http://creativecommons.org/ns#Attribution" />
-        <cc:permits
-           rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
-        <cc:requires
-           rdf:resource="http://creativecommons.org/ns#ShareAlike" />
-      </cc:License>
-    </rdf:RDF>
-  </metadata>
-  <g
-     inkscape:label="Calque 1"
-     inkscape:groupmode="layer"
-     id="layer1"
-     transform="translate(27,-823.3622)">
-    <path
-       style="color:#000000;fill:#0062be;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-       d="m 101,823.3622 0,23.704 c 57.60125,0 104.2963,46.6944 104.2963,104.296 l 23.7037,0 c 0,-70.6912 -57.30755,-128 -128,-128 z"
-       id="path2990-22"
-       inkscape:connector-curvature="0"
-       inkscape:transform-center-x="-64"
-       inkscape:transform-center-y="-63.999956"
-       inkscape:export-xdpi="197"
-       inkscape:export-ydpi="197" />
-    <path
-       style="color:#000000;fill:#0062be;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-       d="m 101,866.0294 0,23.704 c 34.0371,0 61.62963,27.592 61.62963,61.6288 l 23.7037,0 c 0,-47.128 -38.20503,-85.3328 -85.33333,-85.3328 z"
-       id="path2990-3"
-       inkscape:connector-curvature="0"
-       inkscape:transform-center-x="-42.666665"
-       inkscape:transform-center-y="-42.666356"
-       inkscape:export-xdpi="197"
-       inkscape:export-ydpi="197" />
-    <path
-       sodipodi:type="arc"
-       style="color:#000000;fill:#0062be;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-       id="path2990"
-       sodipodi:cx="-0.088388346"
-       sodipodi:cy="50"
-       sodipodi:rx="3.9774756"
-       sodipodi:ry="3.9774756"
-       d="M -0.08838827,46.022524 A 3.9774756,3.9774756 0 0 1 3.8890873,50 l -3.97747565,0 z"
-       transform="matrix(8.3432784,0,0,8.3432808,101.73744,534.199)"
-       sodipodi:start="4.712389"
-       sodipodi:end="6.2831853"
-       inkscape:export-xdpi="197"
-       inkscape:export-ydpi="197"
-       inkscape:transform-center-x="-16.592584"
-       inkscape:transform-center-y="-16.591714" />
-    <path
-       style="color:#000000;fill:#0062be;fill-opacity:0.29591836;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-       d="m 229,951.3622 -23.7037,0 c 0,57.6016 -46.69488,104.296 -104.2963,104.296 l 0,23.704 c 70.6925,0 128,-57.3072 128,-128 z"
-       id="path2990-22-9"
-       inkscape:connector-curvature="0"
-       inkscape:transform-center-x="-64"
-       inkscape:transform-center-y="64.000044"
-       inkscape:export-xdpi="197"
-       inkscape:export-ydpi="197" />
-    <path
-       style="color:#000000;fill:#0062be;fill-opacity:0.29591836;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-       d="m 186.33333,951.3622 -23.7037,0 c 0,34.0368 -27.59253,61.6304 -61.62963,61.6304 l 0,23.704 c 47.12818,0 85.33333,-38.2064 85.33333,-85.3344 z"
-       id="path2990-3-6"
-       inkscape:connector-curvature="0"
-       inkscape:transform-center-x="-42.666665"
-       inkscape:transform-center-y="42.667244"
-       inkscape:export-xdpi="197"
-       inkscape:export-ydpi="197" />
-    <path
-       style="color:#000000;fill:#0062be;fill-opacity:0.29591836;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-       d="m -27,951.3622 23.703712,0 c 0,-57.6016 46.694864,-104.296 104.296288,-104.296 l 0,-23.704 c -70.692496,0 -128,57.3088 -128,128 z"
-       id="path2990-22-93"
-       inkscape:connector-curvature="0"
-       inkscape:transform-center-x="64"
-       inkscape:transform-center-y="-63.999956"
-       inkscape:export-xdpi="197"
-       inkscape:export-ydpi="197" />
-    <path
-       style="color:#000000;fill:#0062be;fill-opacity:0.29591836;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-       d="m 15.666656,951.3622 23.703712,0 c 0,-34.0368 27.592544,-61.6288 61.629632,-61.6288 l 0,-23.704 c -47.128176,0 -85.333344,38.2048 -85.333344,85.3328 z"
-       id="path2990-3-9"
-       inkscape:connector-curvature="0"
-       inkscape:transform-center-x="42.666672"
-       inkscape:transform-center-y="-42.666356"
-       inkscape:export-xdpi="197"
-       inkscape:export-ydpi="197" />
-    <path
-       sodipodi:type="arc"
-       style="color:#000000;fill:#0062be;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-       id="path2990-1"
-       sodipodi:cx="-0.088388346"
-       sodipodi:cy="50"
-       sodipodi:rx="3.9774756"
-       sodipodi:ry="3.9774756"
-       d="M -0.08838827,46.022524 A 3.9774756,3.9774756 0 0 1 3.8890873,50 l -3.97747565,0 z"
-       transform="matrix(0,-8.3432784,8.3432808,0,-315.97884,950.62475)"
-       sodipodi:start="4.712389"
-       sodipodi:end="6.2831853"
-       inkscape:export-xdpi="197"
-       inkscape:export-ydpi="197"
-       inkscape:transform-center-x="16.591713"
-       inkscape:transform-center-y="-16.592581" />
-    <path
-       sodipodi:type="arc"
-       style="color:#000000;fill:#0062be;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-       id="path2990-6"
-       sodipodi:cx="-0.088388346"
-       sodipodi:cy="50"
-       sodipodi:rx="3.9774756"
-       sodipodi:ry="3.9774756"
-       d="M -0.08838827,46.022524 A 3.9774756,3.9774756 0 0 1 3.8890873,50 l -3.97747565,0 z"
-       transform="matrix(-8.3432784,0,0,-8.3432808,100.44774,1368.3403)"
-       sodipodi:start="4.712389"
-       sodipodi:end="6.2831853"
-       inkscape:export-xdpi="197"
-       inkscape:export-ydpi="197"
-       inkscape:transform-center-x="16.592584"
-       inkscape:transform-center-y="16.591761" />
-    <path
-       sodipodi:type="arc"
-       style="color:#000000;fill:#0062be;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:4;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
-       id="path2990-1-6"
-       sodipodi:cx="-0.088388346"
-       sodipodi:cy="50"
-       sodipodi:rx="3.9774756"
-       sodipodi:ry="3.9774756"
-       d="M -0.08838827,46.022524 A 3.9774756,3.9774756 0 0 1 3.8890873,50 l -3.97747565,0 z"
-       transform="matrix(0,8.3432784,-8.3432808,0,518.16402,951.91446)"
-       sodipodi:start="4.712389"
-       sodipodi:end="6.2831853"
-       inkscape:export-xdpi="197"
-       inkscape:export-ydpi="197"
-       inkscape:transform-center-x="-16.591713"
-       inkscape:transform-center-y="16.592578" />
-  </g>
+	<circle fill="#0062BE" cx="128" cy="128" r="33"/>
+
+	<g fill="none" stroke="#0062BE" stroke-width="24">
+		<g stroke-opacity="0.3">
+			<path d="M12,128 A116,116 0 1,1 128,244"/>
+			<path d="M54,128 A74,74 0 1,1 128,202"/>
+		</g>
+		<path d="M128,12 A116,116 0 0,1 244,128"/>
+		<path d="M128,54 A74,74 0 0,1 202,128"/>
+	</g>
 </svg>

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