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

Merge pull request #1455 from FreshRSS/dev

Release 1.6.3
Alexandre Alapetite 9 лет назад
Родитель
Сommit
be0bcfef7e
49 измененных файлов с 1399 добавлено и 92 удалено
  1. 28 0
      CHANGELOG.md
  2. 5 1
      CREDITS.md
  3. 1 0
      README.md
  4. 1 1
      app/Controllers/javascriptController.php
  5. 2 2
      app/Controllers/subscriptionController.php
  6. 1 1
      app/Controllers/updateController.php
  7. 17 4
      app/Controllers/userController.php
  8. 2 2
      app/Models/Auth.php
  9. 1 1
      app/Models/EntryDAO.php
  10. 1 1
      app/Models/Feed.php
  11. 1 1
      app/Models/FeedDAO.php
  12. 1 1
      app/Models/UserDAO.php
  13. 2 0
      app/i18n/cz/gen.php
  14. 2 0
      app/i18n/de/gen.php
  15. 2 0
      app/i18n/en/gen.php
  16. 2 0
      app/i18n/fr/gen.php
  17. 2 0
      app/i18n/it/gen.php
  18. 3 1
      app/i18n/nl/gen.php
  19. 2 0
      app/i18n/ru/gen.php
  20. 2 0
      app/i18n/tr/gen.php
  21. 1 1
      app/install.php
  22. 2 0
      app/layout/aside_configure.phtml
  23. 2 0
      app/layout/header.phtml
  24. 1 1
      app/views/auth/formLogin.phtml
  25. 1 1
      app/views/auth/register.phtml
  26. 1 0
      app/views/helpers/javascript_vars.phtml
  27. 2 0
      app/views/index/about.phtml
  28. 1 1
      app/views/user/manage.phtml
  29. 14 3
      cli/README.md
  30. 1 1
      cli/_cli.php
  31. 1 1
      cli/create-user.php
  32. 1 1
      cli/delete-user.php
  33. 31 22
      cli/do-install.php
  34. 1 1
      cli/list-users.php
  35. 59 0
      cli/reconfigure.php
  36. 15 1
      cli/user-info.php
  37. 1 1
      constants.php
  38. 3 0
      data/config.default.php
  39. 16 4
      data/shares.php
  40. 26 26
      lib/Favicon/DataAccess.php
  41. 3 0
      lib/Favicon/Favicon.php
  42. 31 4
      lib/lib_install.php
  43. 4 6
      lib/lib_rss.php
  44. 1 1
      p/api/greader.php
  45. 4 0
      p/scripts/main.js
  46. BIN
      p/themes/Origine-compact/loader.gif
  47. 7 0
      p/themes/Origine-compact/metadata.json
  48. 1091 0
      p/themes/Origine-compact/origine-compact.css
  49. BIN
      p/themes/Origine-compact/thumbs/original.png

+ 28 - 0
CHANGELOG.md

@@ -1,5 +1,33 @@
 # Changelog
 
+## 2017-03-11 FreshRSS 1.6.3
+
+* Features
+	* New option `disable_update` (also from CLI) to hide the system to update to new FreshRSS versions [#1436](https://github.com/FreshRSS/FreshRSS/pull/1436)
+	* Share with Ⓚnown [#1420](https://github.com/FreshRSS/FreshRSS/pull/1420)
+	* Share with GNU social [#1422](https://github.com/FreshRSS/FreshRSS/issues/1422)
+* UI
+	* New theme *Origine-compact* [#1388](https://github.com/FreshRSS/FreshRSS/pull/1388)
+	* Chrome parity with Firefox: auto-focus tab when clicking on notification [#1409](https://github.com/FreshRSS/FreshRSS/pull/1409)
+* CLI
+	* New command `./cli/reconfigure.php` to update an existing installation [#1439](https://github.com/FreshRSS/FreshRSS/pull/1439)
+	* Many CLI improvements [#1447](https://github.com/FreshRSS/FreshRSS/pull/1447)
+		* More information (number of feeds, articles, etc.) in `./cli/user-info.php`
+		* Better idempotency of `./cli/do-install.php` and language parameter [#1449](https://github.com/FreshRSS/FreshRSS/issues/1449) 
+* Bug fixing
+	* Fix several CLI issues [#1445](https://github.com/FreshRSS/FreshRSS/issues/1445)
+		* Fix CLI install bugs with SQLite [#1443](https://github.com/FreshRSS/FreshRSS/issues/1443), [#1448](https://github.com/FreshRSS/FreshRSS/issues/1448)
+		* Allow empty strings in CLI do-install [#1435](https://github.com/FreshRSS/FreshRSS/pull/1435)
+	* Fix PostgreSQL bugs with API and feed modifications [#1417](https://github.com/FreshRSS/FreshRSS/pull/1417)
+	* Do not mark as read in anonymous mode [#1431](https://github.com/FreshRSS/FreshRSS/issues/1431)
+	* Fix Favicons warnings [#59dfc64](https://github.com/FreshRSS/FreshRSS/commit/59dfc64512372eaba7609d84500d943bb7274399), [#1452](https://github.com/FreshRSS/FreshRSS/pull/1452)
+* Security
+	* Sanitize feed Web site URL [#1434](https://github.com/FreshRSS/FreshRSS/issues/1434)
+	* No version number for anonymous users [#1404](https://github.com/FreshRSS/FreshRSS/issues/1404)
+* Misc.
+	* Relaxed requirements for username to `/^[0-9a-zA-Z]|[0-9a-zA-Z_]{2,38}$/` [#1423](https://github.com/FreshRSS/FreshRSS/pull/1423)
+
+
 ## 2016-12-26 FreshRSS 1.6.2
 
 * Features

+ 5 - 1
CREDITS.md

@@ -11,11 +11,14 @@ People are sorted by name so please keep this order.
 * [Alwaysin](https://github.com/Alwaysin): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Alwaysin)
 * [Amaury Carrade](https://github.com/AmauryCarrade): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=AmauryCarrade), [Web](https://amaury.carrade.eu/)
 * [ASMfreaK](https://github.com/ASMfreaK): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=ASMfreaK)
-* [Damstre](https://github.com/Damstre): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:Damstre),
+* [Crupuk](https://github.com/Crupuk): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:Crupuk)
+* [Damstre](https://github.com/Damstre): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:Damstre)
 * [danc](https://github.com/danc): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=danc), [Web](http://tintouli.free.fr/)
+* [dswd](https://github.com/dswd): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:dswd)
 * [ealdraed](https://github.com/ealdraed): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=ealdraed)
 * [Frans de Jonge](https://github.com/Frenzie): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Frenzie), [Web](http://fransdejonge.com/)
 * [Guillaume Fillon](https://github.com/kokaz): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:kokaz), [Web](http://www.guillaume-fillon.com/)
+* [Guillaume Hayot](https://github.com/postblue): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:postblue), [Web](https://postblue.info/)
 * [hckweb](https://github.com/hckweb): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=hckweb)
 * [Jaussoin Timothée](https://github.com/edhelas): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=edhelas), [Web](http://edhelas.movim.eu/)
 * [Julien Reichardt](https://github.com/j8r): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=j8r), [Web](https://blog.jrei.ch/)
@@ -33,5 +36,6 @@ People are sorted by name so please keep this order.
 * [romibi](https://github.com/romibi): [contributions](https://github.com/FreshRSS/FreshRSS/commits/dev?author=romibi)
 * [subic](https://github.com/subic): [contributions](https://github.com/FreshRSS/documentation/commits?author=subic)
 * [Tets42](https://github.com/Tets42): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Tets42)
+* [Thomas Citharel](https://github.com/tcitworld): [contributions](https://github.com/FreshRSS/FreshRSS/pulls?q=is:pr+author:tomgue), [Web](https://www.tcit.fr/)
 * [tomgue](https://github.com/tomgue): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=tomgue)
 * [Wanabo](https://github.com/Wanabo): [contributions](https://github.com/FreshRSS/FreshRSS/commits?author=Wanabo)

+ 1 - 0
README.md

@@ -51,6 +51,7 @@ We are a friendly community.
 6. Advanced configuration settings can be seen in [config.php](./data/config.default.php).
 
 ## Automated install
+* [![Install on Cloudron](https://cloudron.io/img/button.svg)](https://cloudron.io/button.html?app=org.freshrss.cloudronapp)
 * [![DP deploy](https://raw.githubusercontent.com/DFabric/DPlatform-ShellCore/gh-pages/img/deploy.png)](https://dfabric.github.io/DPlatform-ShellCore)
 * [YunoHost](https://github.com/YunoHost-Apps/freshrss_ynh)
 

+ 1 - 1
app/Controllers/javascriptController.php

@@ -26,7 +26,7 @@ class FreshRSS_javascript_Controller extends Minz_ActionController {
 		header('Pragma: no-cache');
 
 		$user = isset($_GET['user']) ? $_GET['user'] : '';
-		if (ctype_alnum($user)) {
+		if (FreshRSS_user_Controller::checkUsername($user)) {
 			try {
 				$salt = FreshRSS_Context::$system_conf->salt;
 				$conf = get_user_configuration($user);

+ 2 - 2
app/Controllers/subscriptionController.php

@@ -90,8 +90,8 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
 			$values = array(
 				'name' => Minz_Request::param('name', ''),
 				'description' => sanitizeHTML(Minz_Request::param('description', '', true)),
-				'website' => Minz_Request::param('website', ''),
-				'url' => Minz_Request::param('url', ''),
+				'website' => checkUrl(Minz_Request::param('website', '')),
+				'url' => checkUrl(Minz_Request::param('url', '')),
 				'category' => $cat,
 				'pathEntries' => Minz_Request::param('path_entries', ''),
 				'priority' => intval(Minz_Request::param('priority', 0)),

+ 1 - 1
app/Controllers/updateController.php

@@ -162,7 +162,7 @@ class FreshRSS_update_Controller extends Minz_ActionController {
 	}
 
 	public function applyAction() {
-		if (!file_exists(UPDATE_FILENAME) || !is_writable(FRESHRSS_PATH)) {
+		if (!file_exists(UPDATE_FILENAME) || !is_writable(FRESHRSS_PATH) || Minz_Configuration::get('system')->disable_update) {
 			Minz_Request::forward(array('c' => 'update'), true);
 		}
 

+ 17 - 4
app/Controllers/userController.php

@@ -34,6 +34,16 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 		return $passwordHash == '' ? '' : $passwordHash;
 	}
 
+	/**
+	 * The username is also used as folder name, file name, and part of SQL table name.
+	 * '_' is a reserved internal username.
+	 */
+	const USERNAME_PATTERN = '[0-9a-zA-Z]|[0-9a-zA-Z_]{2,38}';
+
+	public static function checkUsername($username) {
+		return preg_match('/^' . self::USERNAME_PATTERN . '$/', $username) === 1;
+	}
+
 	/**
 	 * This action displays the user profile page.
 	 */
@@ -104,7 +114,8 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 			$userConfig = array();
 		}
 
-		$ok = ($new_user_name != '') && ctype_alnum($new_user_name);
+		$ok = self::checkUsername($new_user_name);
+		$homeDir = join_path(DATA_PATH, 'users', $new_user_name);
 
 		if ($ok) {
 			$languages = Minz_Translate::availableLanguages();
@@ -114,7 +125,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 
 			$ok &= !in_array(strtoupper($new_user_name), array_map('strtoupper', listUsers()));	//Not an existing user, case-insensitive
 
-			$configPath = join_path(DATA_PATH, 'users', $new_user_name, 'config.php');
+			$configPath = join_path($homeDir, 'config.php');
 			$ok &= !file_exists($configPath);
 		}
 		if ($ok) {
@@ -131,7 +142,9 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 			}
 		}
 		if ($ok) {
-			mkdir(join_path(DATA_PATH, 'users', $new_user_name));
+			if (!is_dir($homeDir)) {
+				mkdir($homeDir);
+			}
 			$userConfig['passwordHash'] = $passwordHash;
 			$userConfig['apiPasswordHash'] = $apiPasswordHash;
 			$ok &= (file_put_contents($configPath, "<?php\n return " . var_export($userConfig, true) . ';') !== false);
@@ -187,7 +200,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
 		$db = FreshRSS_Context::$system_conf->db;
 		require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
 
-		$ok = ctype_alnum($username);
+		$ok = self::checkUsername($username);
 		if ($ok) {
 			$default_user = FreshRSS_Context::$system_conf->default_user;
 			$ok &= (strcasecmp($username, $default_user) !== 0);	//It is forbidden to delete the default user

+ 2 - 2
app/Models/Auth.php

@@ -182,7 +182,7 @@ class FreshRSS_Auth {
 
 class FreshRSS_FormAuth {
 	public static function checkCredentials($username, $hash, $nonce, $challenge) {
-		if (!ctype_alnum($username) ||
+		if (!FreshRSS_user_Controller::checkUsername($username) ||
 				!ctype_graph($challenge) ||
 				!ctype_alnum($nonce)) {
 			Minz_Log::debug('Invalid credential parameters:' .
@@ -211,7 +211,7 @@ class FreshRSS_FormAuth {
 			// Token has expired (> 1 month) or does not exist.
 			// TODO: 1 month -> use a configuration instead
 			@unlink($token_file);
-			return array(); 	
+			return array();
 		}
 
 		$credentials = @file_get_contents($token_file);

+ 1 - 1
app/Models/EntryDAO.php

@@ -649,7 +649,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 			$values[] = intval($id);
 			break;
 		case 'A':
-			$where .= '1 ';
+			$where .= '1=1 ';
 			break;
 		default:
 			throw new FreshRSS_EntriesGetter_Exception('Bad type in Entry->listByType: [' . $type . ']!');

+ 1 - 1
app/Models/Feed.php

@@ -442,7 +442,7 @@ class FreshRSS_Feed extends Minz_Model {
 				file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND);
 			}
 			$currentUser = Minz_Session::param('currentUser');
-			if (ctype_alnum($currentUser) && !file_exists($path . '/' . $currentUser . '.txt')) {
+			if (FreshRSS_user_Controller::checkUsername($currentUser) && !file_exists($path . '/' . $currentUser . '.txt')) {
 				touch($path . '/' . $currentUser . '.txt');
 			}
 		}

+ 1 - 1
app/Models/FeedDAO.php

@@ -67,7 +67,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
 
 		$set = '';
 		foreach ($valuesTmp as $key => $v) {
-			$set .= $key . '=?, ';
+			$set .= '`' . $key . '`=?, ';
 
 			if ($key == 'httpAuth') {
 				$valuesTmp[$key] = base64_encode($v);

+ 1 - 1
app/Models/UserDAO.php

@@ -85,7 +85,7 @@ class FreshRSS_UserDAO extends Minz_ModelPdo {
 	}
 
 	public static function touch($username = '') {
-		if (($username == '') || (!ctype_alnum($username))) {
+		if (!FreshRSS_user_Controller::checkUsername($username)) {
 			$username = Minz_Session::param('currentUser', '_');
 		}
 		return touch(join_path(DATA_PATH , 'users', $username, 'config.php'));

+ 2 - 0
app/i18n/cz/gen.php

@@ -165,6 +165,8 @@ return array(
 		'wallabag' => 'wallabag v1',
 		'wallabagv2' => 'wallabag v2',
 		'jdh' => 'Journal du hacker',
+		'Known' => 'Known based sites',
+		'gnusocial' => 'GNU social',
 	),
 	'short' => array(
 		'attention' => 'Upozornění!',

+ 2 - 0
app/i18n/de/gen.php

@@ -165,6 +165,8 @@ return array(
 		'wallabag' => 'wallabag v1',
 		'wallabagv2' => 'wallabag v2',
 		'jdh' => 'Journal du hacker',
+		'Known' => 'Known based sites',
+		'gnusocial' => 'GNU social',
 	),
 	'short' => array(
 		'attention' => 'Achtung!',

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

@@ -165,6 +165,8 @@ return array(
 		'wallabag' => 'wallabag v1',
 		'wallabagv2' => 'wallabag v2',
 		'jdh' => 'Journal du hacker',
+		'Known' => 'Known based sites',
+		'gnusocial' => 'GNU social',
 	),
 	'short' => array(
 		'attention' => 'Warning!',

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

@@ -165,6 +165,8 @@ return array(
 		'wallabag' => 'wallabag v1',
 		'wallabagv2' => 'wallabag v2',
 		'jdh' => 'Journal du hacker',
+		'Known' => 'Sites basés sur Known',
+		'gnusocial' => 'GNU social',
 	),
 	'short' => array(
 		'attention' => 'Attention !',

+ 2 - 0
app/i18n/it/gen.php

@@ -165,6 +165,8 @@ return array(
 		'wallabag' => 'wallabag v1',
 		'wallabagv2' => 'wallabag v2',
 		'jdh' => 'Journal du hacker',
+		'Known' => 'Siti basati su Known',
+		'gnusocial' => 'GNU social',
 	),
 	'short' => array(
 		'attention' => 'Attenzione!',

+ 3 - 1
app/i18n/nl/gen.php

@@ -163,8 +163,10 @@ return array(
 		'shaarli' => 'Shaarli',
 		'twitter' => 'Twitter',
 		'wallabag' => 'wallabag v1',
-		'wallabagv2' => 'wallabag v2',             
+		'wallabagv2' => 'wallabag v2',
 		'jdh' => 'Journal du hacker',
+		'Known' => 'Known based sites',
+		'gnusocial' => 'GNU social',
 	),
 	'short' => array(
 		'attention' => 'Attentie!',

+ 2 - 0
app/i18n/ru/gen.php

@@ -165,6 +165,8 @@ return array(
 		'twitter' => 'Twitter',
 		'wallabag' => 'wallabag v1',
 		'wallabagv2' => 'wallabag v2',
+		'Known' => 'Known based sites',
+		'gnusocial' => 'GNU social',
 	),
 	'short' => array(
 		'attention' => 'Warning!',

+ 2 - 0
app/i18n/tr/gen.php

@@ -165,6 +165,8 @@ return array(
 		'wallabag' => 'wallabag v1',
 		'wallabagv2' => 'wallabag v2',
 		'jdh' => 'Journal du hacker',
+		'Known' => 'Known based sites',
+		'gnusocial' => 'GNU social',
 	),
 	'short' => array(
 		'attention' => 'Tehlike!',

+ 1 - 1
app/install.php

@@ -553,7 +553,7 @@ function printStep2() {
 		<div class="form-group">
 			<label class="group-name" for="default_user"><?php echo _t('install.default_user'); ?></label>
 			<div class="group-controls">
-				<input type="text" id="default_user" name="default_user" required="required" size="16" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" value="<?php echo isset($_SESSION['default_user']) ? $_SESSION['default_user'] : ''; ?>" placeholder="<?php echo httpAuthUser() == '' ? 'alice' : httpAuthUser(); ?>" tabindex="3" />
+				<input type="text" id="default_user" name="default_user" required="required" size="16" pattern="<?php echo FreshRSS_user_Controller::USERNAME_PATTERN; ?>" value="<?php echo isset($_SESSION['default_user']) ? $_SESSION['default_user'] : ''; ?>" placeholder="<?php echo httpAuthUser() == '' ? 'alice' : httpAuthUser(); ?>" tabindex="3" />
 			</div>
 		</div>
 

+ 2 - 0
app/layout/aside_configure.phtml

@@ -41,9 +41,11 @@
 	                          Minz_Request::actionName() === 'checkInstall' ? ' active' : ''; ?>">
 		<a href="<?php echo _url('update', 'checkInstall'); ?>"><?php echo _t('gen.menu.check_install'); ?></a>
 	</li>
+	<?php if (!Minz_Configuration::get('system')->disable_update) { ?>
 	<li class="item<?php echo Minz_Request::controllerName() === 'update' &&
 	                          Minz_Request::actionName() === 'index' ? ' active' : ''; ?>">
 		<a href="<?php echo _url('update', 'index'); ?>"><?php echo _t('gen.menu.update'); ?></a>
 	</li>
 	<?php } ?>
+	<?php } ?>
 </ul>

+ 2 - 0
app/layout/header.phtml

@@ -71,8 +71,10 @@ if (FreshRSS_Auth::accessNeedsAction()) {
 				<li class="item"><a href="<?php echo _url('user', 'manage'); ?>"><?php echo _t('gen.menu.user_management'); ?></a></li>
 				<li class="item"><a href="<?php echo _url('auth', 'index'); ?>"><?php echo _t('gen.menu.authentication'); ?></a></li>
 				<li class="item"><a href="<?php echo _url('update', 'checkInstall'); ?>"><?php echo _t('gen.menu.check_install'); ?></a></li>
+				<?php if (!Minz_Configuration::get('system')->disable_update) { ?>
 				<li class="item"><a href="<?php echo _url('update', 'index'); ?>"><?php echo _t('gen.menu.update'); ?></a></li>
 				<?php } ?>
+				<?php } ?>
 				<li class="separator"></li>
 				<li class="item"><a href="<?php echo _url('stats', 'index'); ?>"><?php echo _t('gen.menu.stats'); ?></a></li>
 				<li class="item"><a href="<?php echo _url('index', 'logs'); ?>"><?php echo _t('gen.menu.logs'); ?></a></li>

+ 1 - 1
app/views/auth/formLogin.phtml

@@ -9,7 +9,7 @@
 		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<div>
 			<label for="username"><?php echo _t('gen.auth.username'); ?></label>
-			<input type="text" id="username" name="username" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" autofocus="autofocus" />
+			<input type="text" id="username" name="username" size="16" required="required" pattern="<?php echo FreshRSS_user_Controller::USERNAME_PATTERN; ?>" autofocus="autofocus" />
 		</div>
 		<div>
 			<label for="passwordPlain"><?php echo _t('gen.auth.password'); ?></label>

+ 1 - 1
app/views/auth/register.phtml

@@ -5,7 +5,7 @@
 		<input type="hidden" name="_csrf" value="<?php echo FreshRSS_Auth::csrfToken(); ?>" />
 		<div>
 			<label class="group-name" for="new_user_name"><?php echo _t('gen.auth.username'), '<br />', _i('help'), ' ', _t('gen.auth.username.format'); ?></label>
-			<input id="new_user_name" name="new_user_name" type="text" size="16" required="required" maxlength="16" autocomplete="off" pattern="[0-9a-zA-Z]{1,16}" />
+			<input id="new_user_name" name="new_user_name" type="text" size="16" required="required" autocomplete="off" pattern="<?php echo FreshRSS_user_Controller::USERNAME_PATTERN; ?>" />
 		</div>
 
 		<div>

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

@@ -3,6 +3,7 @@ $mark = FreshRSS_Context::$user_conf->mark_when;
 $s = FreshRSS_Context::$user_conf->shortcuts;
 echo htmlspecialchars(json_encode(array(
 	'context' => array(
+		'anonymous' => !FreshRSS_Auth::hasAccess(),
 		'auto_remove_article' => !!FreshRSS_Context::isAutoRemoveAvailable(),
 		'hide_posts' => !(FreshRSS_Context::$user_conf->display_posts || Minz_Request::actionName() === 'reader'),
 		'display_order' => Minz_Request::param('order', FreshRSS_Context::$user_conf->sort_order),

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

@@ -13,8 +13,10 @@
 		<dt><?php echo _t('index.about.license'); ?></dt>
 		<dd><?php echo _t('index.about.agpl3'); ?></dd>
 
+		<?php if (FreshRSS_Auth::hasAccess()): ?>
 		<dt><?php echo _t('index.about.version'); ?></dt>
 		<dd><?php echo FRESHRSS_VERSION; ?></dd>
+		<?php endif; ?>
 	</dl>
 
 	<p><?php echo _t('index.about.freshrss_description'); ?></p>

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

@@ -22,7 +22,7 @@
 		<div class="form-group">
 			<label class="group-name" for="new_user_name"><?php echo _t('admin.user.username'); ?></label>
 			<div class="group-controls">
-				<input id="new_user_name" name="new_user_name" type="text" size="16" required="required" maxlength="16" autocomplete="off" pattern="[0-9a-zA-Z]{1,16}" placeholder="demo" />
+				<input id="new_user_name" name="new_user_name" type="text" size="16" required="required" autocomplete="off" pattern="<?php echo FreshRSS_user_Controller::USERNAME_PATTERN; ?>" placeholder="demo" />
 			</div>
 		</div>
 

+ 14 - 3
cli/README.md

@@ -32,13 +32,17 @@ Options in parenthesis are optional.
 ```sh
 cd /usr/share/FreshRSS
 
-./cli/do-install.php --default_user admin ( --auth_type form --environment production --base_url https://rss.example.net/ --title FreshRSS --allow_anonymous --api_enabled --db-type mysql --db-host localhost:3306 --db-user freshrss --db-password dbPassword123 --db-base freshrss --db-prefix freshrss )
+./cli/do-install.php --default_user admin ( --auth_type form --environment production --base_url https://rss.example.net/ --language en --title FreshRSS --allow_anonymous --api_enabled --db-type mysql --db-host localhost:3306 --db-user freshrss --db-password dbPassword123 --db-base freshrss --db-prefix freshrss )
 # --auth_type can be: 'form' (default), 'http_auth' (using the Web server access control), 'none' (dangerous)
 # --db-type can be: 'sqlite' (default), 'mysql' (MySQL or MariaDB), 'pgsql' (PostgreSQL)
 # --environment can be: 'production' (default), 'development' (for additional log messages)
+# --language can be: 'en' (default), 'fr', or one of the [supported languages](../app/i18n/)
 # --db-prefix is an optional prefix in front of the names of the tables. We suggest using 'freshrss_'
 # This command does not create the default user. Do that with ./cli/create-user.php
 
+./cli/reconfigure.php
+# Same parameters as for do-install.php. Used to update an existing installation.
+
 ./cli/create-user.php --user username ( --password 'password' --api-password 'api_password' --language en --email user@example.net --token 'longRandomString' --no-default-feeds )
 # --language can be: 'en' (default), 'fr', or one of the [supported languages](../app/i18n/)
 
@@ -59,14 +63,15 @@ cd /usr/share/FreshRSS
 ./cli/user-info.php -h --user username
 # -h is to use a human-readable format
 # --user can be a username, or '*' to loop on all users
-# Returns a * if the user is admin, the name of the user, the date/time of last action, and the size occupied
+# Returns: 1) a * iff the user is admin, 2) the name of the user,
+#  3) the date/time of last user action, 4) the size occupied,
+#  and the number of: 5) categories, 6) feeds, 7) read articles, 8) unread articles, and 9) favourites
 ```
 
 
 ## Unix piping
 
 It is possible to invoke a command multiple times, e.g. with different usernames, thanks to the `xargs -n1` command.
-
 Example showing user information for all users which username starts with 'a':
 
 ```sh
@@ -78,3 +83,9 @@ Example showing all users ranked by date of last activity:
 ```sh
 ./cli/user-info.php -h --user '*' | sort -k2 -r
 ```
+
+Example to get the number of feeds of a given user:
+
+```sh
+./cli/user-info.php --user alex | cut -f6
+```

+ 1 - 1
cli/_cli.php

@@ -20,7 +20,7 @@ function fail($message) {
 }
 
 function cliInitUser($username) {
-	if (!ctype_alnum($username)) {
+	if (!FreshRSS_user_Controller::checkUsername($username)) {
 		fail('FreshRSS error: invalid username: ' . $username . "\n");
 	}
 

+ 1 - 1
cli/create-user.php

@@ -17,7 +17,7 @@ if (empty($options['user'])) {
 		" --language en --email user@example.net --token 'longRandomString --no-default-feeds' )");
 }
 $username = $options['user'];
-if (!ctype_alnum($username)) {
+if (!FreshRSS_user_Controller::checkUsername($username)) {
 	fail('FreshRSS error: invalid username “' . $username . '”');
 }
 

+ 1 - 1
cli/delete-user.php

@@ -10,7 +10,7 @@ if (empty($options['user'])) {
 	fail('Usage: ' . basename(__FILE__) . " --user username");
 }
 $username = $options['user'];
-if (!ctype_alnum($username)) {
+if (!FreshRSS_user_Controller::checkUsername($username)) {
 	fail('FreshRSS error: invalid username “' . $username . '”');
 }
 

+ 31 - 22
cli/do-install.php

@@ -3,9 +3,14 @@
 require('_cli.php');
 require(LIB_PATH . '/lib_install.php');
 
+if (!file_exists(DATA_PATH . '/do-install.txt')) {
+	fail('FreshRSS looks to be already installed! Please use `./cli/reconfigure.php` instead.');
+}
+
 $params = array(
 		'environment:',
 		'base_url:',
+		'language:',
 		'title:',
 		'default_user:',
 		'allow_anonymous',
@@ -13,6 +18,7 @@ $params = array(
 		'auth_type:',
 		'api_enabled',
 		'allow_robots',
+		'disable_update',
 	);
 
 $dBparams = array(
@@ -29,32 +35,13 @@ $options = getopt('', array_merge($params, $dBparams));
 if (empty($options['default_user'])) {
 	fail('Usage: ' . basename(__FILE__) . " --default_user admin ( --auth_type form" .
 		" --environment production --base_url https://rss.example.net/" .
-		" --title FreshRSS --allow_anonymous --api_enabled" .
+		" --language en --title FreshRSS --allow_anonymous --api_enabled" .
 		" --db-type mysql --db-host localhost:3306 --db-user freshrss --db-password dbPassword123" .
-		" --db-base freshrss --db-prefix freshrss_ )");
+		" --db-base freshrss --db-prefix freshrss_ --disable_update )");
 }
 
 fwrite(STDERR, 'FreshRSS install…' . "\n");
 
-$requirements = checkRequirements();
-if ($requirements['all'] !== 'ok') {
-	$message = 'FreshRSS install failed requirements:' . "\n";
-	foreach ($requirements as $requirement => $check) {
-		if ($check !== 'ok' && $requirement !== 'all') {
-			$message .= '• ' . $requirement . "\n";
-		}
-	}
-	fail($message);
-}
-
-if (!ctype_alnum($options['default_user'])) {
-	fail('FreshRSS invalid default username (must be ASCII alphanumeric): ' . $options['default_user']);
-}
-
-if (isset($options['auth_type']) && !in_array($options['auth_type'], array('form', 'http_auth', 'none'))) {
-	fail('FreshRSS invalid authentication method (auth_type must be one of { form, http_auth, none }: ' . $options['auth_type']);
-}
-
 $config = array(
 		'salt' => generateSalt(),
 		'db' => FreshRSS_Context::$system_conf->db,
@@ -73,12 +60,34 @@ if ((!empty($config['base_url'])) && server_is_public($config['base_url'])) {
 
 foreach ($dBparams as $dBparam) {
 	$dBparam = rtrim($dBparam, ':');
-	if (!empty($options[$dBparam])) {
+	if (isset($options[$dBparam])) {
 		$param = substr($dBparam, strlen('db-'));
 		$config['db'][$param] = $options[$dBparam];
 	}
 }
 
+$requirements = checkRequirements($config['db']['type']);
+if ($requirements['all'] !== 'ok') {
+	$message = 'FreshRSS install failed requirements:' . "\n";
+	foreach ($requirements as $requirement => $check) {
+		if ($check !== 'ok' && !in_array($requirement, array('all', 'pdo', 'message'))) {
+			$message .= '• ' . $requirement . "\n";
+		}
+	}
+	if (!empty($requirements['message'])) {
+		$message .= '• ' . $requirements['message'] . "\n";
+	}
+	fail($message);
+}
+
+if (!FreshRSS_user_Controller::checkUsername($options['default_user'])) {
+	fail('FreshRSS invalid default username (must be ASCII alphanumeric): ' . $options['default_user']);
+}
+
+if (isset($options['auth_type']) && !in_array($options['auth_type'], array('form', 'http_auth', 'none'))) {
+	fail('FreshRSS invalid authentication method (auth_type must be one of { form, http_auth, none }: ' . $options['auth_type']);
+}
+
 if (file_put_contents(join_path(DATA_PATH, 'config.php'), "<?php\n return " . var_export($config, true) . ";\n") === false) {
 	fail('FreshRSS could not write configuration file!: ' . join_path(DATA_PATH, 'config.php'));
 }

+ 1 - 1
cli/list-users.php

@@ -4,7 +4,7 @@ require('_cli.php');
 
 $users = listUsers();
 sort($users);
-if (FreshRSS_Context::$system_conf->default_user !== '') {
+if (FreshRSS_Context::$system_conf->default_user !== '' && in_array(FreshRSS_Context::$system_conf->default_user, $users, true)) {
 	array_unshift($users, FreshRSS_Context::$system_conf->default_user);
 	$users = array_unique($users);
 }

+ 59 - 0
cli/reconfigure.php

@@ -0,0 +1,59 @@
+#!/usr/bin/php
+<?php
+require('_cli.php');
+
+$params = array(
+		'environment:',
+		'base_url:',
+		'language:',
+		'title:',
+		'default_user:',
+		'allow_anonymous',
+		'allow_anonymous_refresh',
+		'auth_type:',
+		'api_enabled',
+		'allow_robots',
+		'disable_update',
+	);
+
+$dBparams = array(
+		'db-type:',
+		'db-host:',
+		'db-user:',
+		'db-password:',
+		'db-base:',
+		'db-prefix:',
+	);
+
+$options = getopt('', array_merge($params, $dBparams));
+
+fwrite(STDERR, 'Reconfiguring FreshRSS…' . "\n");
+
+$config = Minz_Configuration::get('system');
+foreach ($params as $param) {
+	$param = rtrim($param, ':');
+	if (isset($options[$param])) {
+		$config->$param = $options[$param] === false ? true : $options[$param];
+	}
+}
+$db = $config->db;
+foreach ($dBparams as $dBparam) {
+	$dBparam = rtrim($dBparam, ':');
+	if (isset($options[$dBparam])) {
+		$param = substr($dBparam, strlen('db-'));
+		$db[$param] = $options[$dBparam];
+	}
+}
+$config->db = $db;
+
+if (!FreshRSS_user_Controller::checkUsername($config->default_user)) {
+	fail('FreshRSS invalid default username (must be ASCII alphanumeric): ' . $config->default_user);
+}
+
+if (isset($config->auth_type) && !in_array($config->auth_type, array('form', 'http_auth', 'none'))) {
+	fail('FreshRSS invalid authentication method (auth_type must be one of { form, http_auth, none }: ' . $config->auth_type);
+}
+
+$config->save();
+
+done();

+ 15 - 1
cli/user-info.php

@@ -14,22 +14,36 @@ $users = $options['user'] === '*' ? listUsers() : array($options['user']);
 
 foreach ($users as $username) {
 	$username = cliInitUser($username);
+	echo $username === FreshRSS_Context::$system_conf->default_user ? '*' : ' ', "\t";
 
+	$catDAO = new FreshRSS_CategoryDAO();
+	$feedDAO = FreshRSS_Factory::createFeedDao($username);
 	$entryDAO = FreshRSS_Factory::createEntryDao($username);
 
-	echo $username === FreshRSS_Context::$system_conf->default_user ? '*' : ' ', "\t";
+	$nbEntries = $entryDAO->countUnreadRead();
+	$nbFavorites = $entryDAO->countUnreadReadFavorites();
 
 	if (isset($options['h'])) {	//Human format
 		echo
 			$username, "\t",
 			date('c', FreshRSS_UserDAO::mtime($username)), "\t",
 			format_bytes($entryDAO->size()), "\t",
+			$catDAO->count(), " categories\t",
+			count($feedDAO->listFeedsIds()), " feeds\t",
+			$nbEntries['read'], " reads\t",
+			$nbEntries['unread'], " unreads\t",
+			$nbFavorites['all'], " favourites\t",
 			"\n";
 	} else {
 		echo
 			$username, "\t",
 			FreshRSS_UserDAO::mtime($username), "\t",
 			$entryDAO->size(), "\t",
+			$catDAO->count(), "\t",
+			count($feedDAO->listFeedsIds()), "\t",
+			$nbEntries['read'], "\t",
+			$nbEntries['unread'], "\t",
+			$nbFavorites['all'], "\t",
 			"\n";
 	}
 }

+ 1 - 1
constants.php

@@ -1,5 +1,5 @@
 <?php
-define('FRESHRSS_VERSION', '1.6.2');
+define('FRESHRSS_VERSION', '1.6.3');
 define('FRESHRSS_WEBSITE', 'http://freshrss.org');
 define('FRESHRSS_WIKI', 'http://doc.freshrss.org');
 

+ 3 - 0
data/config.default.php

@@ -146,4 +146,7 @@ return array(
 
 	# List of enabled FreshRSS extensions.
 	'extensions_enabled' => array(),
+
+	# Disable self-update,
+	'disable_update' => false,
 );

+ 16 - 4
data/shares.php

@@ -85,8 +85,20 @@ return array(
 		'form' => 'simple',
 	),
 	'jdh' => array(
-                'url' => 'https://www.journalduhacker.net/stories/new?url=~LINK~&title=~TITLE~',
-                'transform' => array('rawurlencode'),
-                'form' => 'simple',
-        ),
+		'url' => 'https://www.journalduhacker.net/stories/new?url=~LINK~&title=~TITLE~',
+		'transform' => array('rawurlencode'),
+		'form' => 'simple',
+	),
+	'Known' => array(
+		'url' => '~URL~/share?share_url=~LINK~&share_title=~TITLE~',
+		'transform' => array('rawurlencode'),
+		'help' => 'https://withknown.com/',
+		'form' => 'advanced',
+	),
+	'gnusocial' => array(
+		'url' => '~URL~/notice/new?content=~TITLE~%20~LINK~',
+		'transform' => array('urlencode'),
+		'help' => 'https://gnu.io/social/',
+		'form' => 'advanced',
+	),
 );

+ 26 - 26
lib/Favicon/DataAccess.php

@@ -9,33 +9,33 @@ namespace Favicon;
  **/
 class DataAccess {
 	public function retrieveUrl($url) {
-	    $this->set_context();
-	    return @file_get_contents($url);
+		$this->set_context();
+		return @file_get_contents($url);
 	}
-	
+
 	public function retrieveHeader($url) {
-	    $this->set_context();
+		$this->set_context();
 		$headers = @get_headers($url, 1);
-		return $headers ? array_change_key_case($headers) : array();
+		return is_array($headers) ? array_change_key_case($headers) : array();
+	}
+
+	public function saveCache($file, $data) {
+		file_put_contents($file, $data);
+	}
+
+	public function readCache($file) {
+		return file_get_contents($file);
+	}
+
+	private function set_context() {
+		stream_context_set_default(
+			array(
+				'http' => array(
+					'method' => 'GET',
+					'timeout' => 10,
+					'header' => "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:20.0; Favicon; +https://github.com/ArthurHoaro/favicon) Gecko/20100101 Firefox/32.0\r\n",
+				)
+			)
+		);
 	}
-	
-    public function saveCache($file, $data) {
-        file_put_contents($file, $data);
-    }
-    
-    public function readCache($file) {
-    	return file_get_contents($file);
-    }
-    
-    private function set_context() {
-        stream_context_set_default(
-            array(
-                'http' => array(
-                    'method' => 'GET',
-                    'timeout' => 10,
-                    'header' => "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:20.0; Favicon; +https://github.com/ArthurHoaro/favicon) Gecko/20100101 Firefox/32.0\r\n",
-                )
-            )
-        );
-    }
-}
+}

+ 3 - 0
lib/Favicon/Favicon.php

@@ -89,6 +89,9 @@ class Favicon
         $loop = TRUE;
         while ($loop && $max_loop-- > 0) {
             $headers = $this->dataAccess->retrieveHeader($url);
+            if (empty($headers)) {
+                return false;
+            }
             $exploded = explode(' ', $headers[0]);
             
             if( !isset($exploded[1]) ) { 

+ 31 - 4
lib/lib_install.php

@@ -5,14 +5,36 @@ define('BCRYPT_COST', 9);
 Minz_Configuration::register('default_system', join_path(DATA_PATH, 'config.default.php'));
 Minz_Configuration::register('default_user', join_path(USERS_PATH, '_', 'config.default.php'));
 
-function checkRequirements() {
+function checkRequirements($dbType = '') {
 	$php = version_compare(PHP_VERSION, '5.3.3') >= 0;
 	$minz = file_exists(join_path(LIB_PATH, 'Minz'));
 	$curl = extension_loaded('curl');
 	$pdo_mysql = extension_loaded('pdo_mysql');
 	$pdo_sqlite = extension_loaded('pdo_sqlite');
 	$pdo_pgsql = extension_loaded('pdo_pgsql');
-	$pdo = $pdo_mysql || $pdo_sqlite || $pdo_pgsql;
+	$message = '';
+	switch ($dbType) {
+		case 'mysql':
+			$pdo_sqlite = $pdo_pgsql = true;
+			$pdo = $pdo_mysql;
+			break;
+		case 'sqlite':
+			$pdo_mysql = $pdo_pgsql = true;
+			$pdo = $pdo_sqlite;
+			break;
+		case 'pgsql':
+			$pdo_mysql = $pdo_sqlite = true;
+			$pdo = $pdo_pgsql;
+			break;
+		case '':
+			$pdo = $pdo_mysql || $pdo_sqlite || $pdo_pgsql;
+			break;
+		default:
+			$pdo_mysql = $pdo_sqlite = $pdo_pgsql = true;
+			$pdo = false;
+			$message = 'Invalid database type!';
+			break;
+	}
 	$pcre = extension_loaded('pcre');
 	$ctype = extension_loaded('ctype');
 	$fileinfo = extension_loaded('fileinfo');
@@ -44,8 +66,9 @@ function checkRequirements() {
 		'users' => $users ? 'ok' : 'ko',
 		'favicons' => $favicons ? 'ok' : 'ko',
 		'http_referer' => $http_referer ? 'ok' : 'ko',
+		'message' => $message ?: 'ok',
 		'all' => $php && $minz && $curl && $pdo && $pcre && $ctype && $fileinfo && $dom && $xml &&
-		         $data && $cache && $users && $favicons && $http_referer ?
+		         $data && $cache && $users && $favicons && $http_referer && $message == '' ?
 		         'ok' : 'ko'
 	);
 }
@@ -77,7 +100,11 @@ function checkDb(&$dbOptions) {
 			break;
 		case 'sqlite':
 			include_once(APP_PATH . '/SQL/install.sql.sqlite.php');
-			$dsn = 'sqlite:' . join_path(USERS_PATH, $dbOptions['default_user'], 'db.sqlite');
+			$path = join_path(USERS_PATH, $dbOptions['default_user']);
+			if (!is_dir($path)) {
+				mkdir($path);
+			}
+			$dsn = 'sqlite:' . join_path($path, 'db.sqlite');
 			$driver_options = array(
 				PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
 			);

+ 4 - 6
lib/lib_rss.php

@@ -69,10 +69,10 @@ function idn_to_puny($url) {
 }
 
 function checkUrl($url) {
-	if (empty ($url)) {
+	if ($url == '') {
 		return '';
 	}
-	if (!preg_match ('#^https?://#i', $url)) {
+	if (!preg_match('#^https?://#i', $url)) {
 		$url = 'http://' . $url;
 	}
 	$url = idn_to_puny($url);	//PHP bug #53474 IDN
@@ -285,7 +285,7 @@ function uSecString() {
 }
 
 function invalidateHttpCache($username = '') {
-	if (($username == '') || (!ctype_alnum($username))) {
+	if (!FreshRSS_user_Controller::checkUsername($username)) {
 		Minz_Session::_param('touch', uTimeString());
 		$username = Minz_Session::param('currentUser', '_');
 	}
@@ -299,13 +299,11 @@ function listUsers() {
 		scandir($base_path),
 		array('..', '.', '_')
 	));
-
 	foreach ($dir_list as $file) {
-		if (is_dir(join_path($base_path, $file))) {
+		if ($file[0] !== '.' && is_dir(join_path($base_path, $file)) && file_exists(join_path($base_path, $file, 'config.php'))) {
 			$final_list[] = $file;
 		}
 	}
-
 	return $final_list;
 }
 

+ 1 - 1
p/api/greader.php

@@ -152,7 +152,7 @@ function authorizationToUser() {
 		$headerAuthX = explode('/', $headerAuth, 2);
 		if (count($headerAuthX) === 2) {
 			$user = $headerAuthX[0];
-			if (ctype_alnum($user)) {
+			if (FreshRSS_user_Controller::checkUsername($user)) {
 				FreshRSS_Context::$user_conf = get_user_configuration($user);
 				if (FreshRSS_Context::$user_conf == null) {
 					Minz_Log::warning('Invalid API user ' . $user . ': configuration cannot be found.');

+ 4 - 0
p/scripts/main.js

@@ -117,6 +117,7 @@ function incUnreadsFeed(article, feed_id, nb) {
 var pending_entries = {};
 function mark_read(active, only_not_read) {
 	if ((active.length === 0) || (!active.attr('id')) ||
+		context.anonymous ||
 		(only_not_read && !active.hasClass("not_read"))) {
 		return false;
 	}
@@ -935,6 +936,8 @@ function notifs_html5_show(nb) {
 
 	notification.onclick = function() {
 		window.location.reload();
+		window.focus();
+		notification.close();
 	};
 
 	if (context.html5_notif_timeout !== 0) {
@@ -1014,6 +1017,7 @@ function load_more_posts() {
 		init_load_more(box_load_more);
 
 		$('#load_more').removeClass('loading');
+		$('#bigMarkAsRead').removeAttr('disabled');
 		load_more = false;
 		$(document.body).trigger('sticky_kit:recalc');
 	});

BIN
p/themes/Origine-compact/loader.gif


+ 7 - 0
p/themes/Origine-compact/metadata.json

@@ -0,0 +1,7 @@
+{
+  "name": "Origine-compact",
+  "author": "Kevin Papst",
+  "description": "A theme that tries to use the screen size more efficiently, based on Origine",
+  "version": 0.1,
+  "files": ["_template.css", "origine-compact.css"]
+}

+ 1091 - 0
p/themes/Origine-compact/origine-compact.css

@@ -0,0 +1,1091 @@
+@charset "UTF-8";
+
+/*=== FONTS */
+@font-face {
+	font-family: "OpenSans";
+	src: url("../fonts/openSans.woff") format("woff");
+}
+
+/*=== GENERAL */
+/*============*/
+html, body {
+	height: 100%;
+	font-family: "OpenSans", "Cantarell", "Helvetica", "Arial", sans-serif;
+	background: #fafafa;
+}
+
+/*=== Links */
+a, button.as-link {
+	color: #0062be;
+	outline: none;
+}
+
+/*=== Forms */
+legend {
+	margin: 20px 0 5px;
+	padding: 5px 0;
+	border-bottom: 1px solid #ddd;
+	font-size: 1.4em;
+}
+label {
+	min-height: 25px;
+	padding: 5px 0;
+	cursor: pointer;
+}
+textarea {
+	width: 360px;
+	height: 100px;
+}
+input, select, textarea {
+	min-height: 25px;
+	padding: 3px 5px 2px 5px;
+	background: #fdfdfd;
+	border: 1px solid #bbb;
+	border-radius: 3px;
+	color: #666;
+	line-height: 25px;
+	vertical-align: middle;
+	box-shadow: 0 2px 2px #eee inset;
+}
+option {
+	padding: 0 .5em;
+}
+input:focus, select:focus, textarea:focus {
+	color: #0062be;
+	border-color: #33bbff;
+	box-shadow: 0 2px 2px #ddddff inset;
+}
+input:invalid, select:invalid {
+	border-color: #f00;
+	box-shadow: 0 0 2px 2px #fdd inset;
+}
+input:disabled, select:disabled {
+	background: #eee;
+}
+input.extend {
+	transition: width 200ms linear;
+	-moz-transition: width 200ms linear;
+	-webkit-transition: width 200ms linear;
+	-o-transition: width 200ms linear;
+	-ms-transition: width 200ms linear;
+}
+
+/*=== Tables */
+table {
+	border-collapse: collapse;
+}
+
+tr, th, td {
+	padding: 0.5em;
+	border: 1px solid #ddd;
+}
+th {
+	background: #f6f6f6;
+}
+form td,
+form th {
+	font-weight: normal;
+	text-align: center;
+}
+
+/*=== COMPONENTS */
+/*===============*/
+/*=== Forms */
+.form-group.form-actions {
+	padding: 5px 0;
+	background: #f4f4f4;
+	border-top: 1px solid #ddd;
+}
+.form-group.form-actions .btn {
+	margin: 0 10px;
+}
+.form-group .group-name {
+	padding: 10px 0;
+	text-align: right;
+}
+.form-group .group-controls {
+	min-height: 25px;
+	padding: 8px 0;
+}
+.form-group table {
+	margin: 10px 0 0 220px;
+}
+
+/*=== Buttons */
+.stick {
+	vertical-align: middle;
+	font-size: 0;
+}
+.stick input,
+.stick .btn {
+	border-radius: 0;
+}
+.stick .btn:first-child,
+.stick input:first-child {
+	border-radius: 3px 0 0 3px;
+}
+.stick .btn-important:first-child {
+	border-right: 1px solid #06f;
+}
+.stick .btn:last-child,
+.stick input:last-child {
+	border-radius: 0 3px 3px 0;
+}
+.stick .btn + .btn,
+.stick .btn + input,
+.stick .btn + .dropdown > .btn,
+.stick input + .btn,
+.stick input + input,
+.stick input + .dropdown > .btn,
+.stick .dropdown + .btn,
+.stick .dropdown + input,
+.stick .dropdown + .dropdown > .btn {
+	border-left: none;
+}
+.stick input + .btn {
+	border-top: 1px solid #bbb;
+}
+.stick .btn + .dropdown > .btn {
+	border-left: none;
+	border-radius: 0 3px 3px 0;
+}
+
+.btn {
+	display: inline-block;
+	min-height: 32px;
+	min-width: 15px;
+	margin: 0;
+	padding: 5px 10px;
+	background: #fff;
+	background: linear-gradient(to bottom, #fff 0%, #eee 100%);
+	background: -moz-linear-gradient(top, #fff 0%, #eee 100%);
+	background: -webkit-linear-gradient(top, #fff 0%, #eee 100%);
+	background: -o-linear-gradient(top, #fff 0%, #eee 100%);
+	background: -ms-linear-gradient(top, #fff 0%, #eee 100%);
+	border-radius: 3px;
+	border: 1px solid #ddd;
+	border-bottom: 1px solid #aaa;
+	border-right: 1px solid #aaa;
+	color: #666;
+	text-shadow: 0px -1px 0 #ddd;
+	font-size: 0.9rem;
+	vertical-align: middle;
+	cursor: pointer;
+	overflow: hidden;
+}
+a.btn {
+	min-height: 20px;
+	line-height: 20px;
+}
+.btn:hover {
+	background: #f0f0f0;
+	background: linear-gradient(to bottom, #f8f8f8, #f0f0f0);
+	background: -moz-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%);
+	background: -webkit-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%);
+	background: -o-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%);
+	background: -ms-linear-gradient(top, #f8f8f8 0%, #f0f0f0 100%);
+	text-decoration: none;
+}
+.btn.active,
+.btn:active,
+.dropdown-target:target ~ .btn.dropdown-toggle {
+	box-shadow: 0px 2px 4px #e0e0e0 inset, 0px 1px 2px #fafafa;
+	background: #eee;
+}
+
+.btn-important {
+	background: #0084CC;
+	background: linear-gradient(to bottom, #0084CC, #0045CC);
+	background: -moz-linear-gradient(top, #0084CC 0%, #0045CC 100%);
+	background: -webkit-linear-gradient(top, #0084CC 0%, #0045CC 100%);
+	background: -o-linear-gradient(top, #0084CC 0%, #0045CC 100%);
+	background: -ms-linear-gradient(top, #0084CC 0%, #0045CC 100%);
+	color: #fff;
+	border: 1px solid #0062B7;
+	text-shadow: 0px -1px 0 #aaa;
+	font-weight: normal;
+}
+.btn-important:hover {
+	background: linear-gradient(to bottom, #0066CC, #0045CC);
+	background: -moz-linear-gradient(top, #0066CC 0%, #0045CC 100%);
+	background: -webkit-linear-gradient(top, #0066CC 0%, #0045CC 100%);
+	background: -o-linear-gradient(top, #0066CC 0%, #0045CC 100%);
+	background: -ms-linear-gradient(top, #0066CC 0%, #0045CC 100%);
+}
+.btn-important:active {
+	background: #0044CB;
+	box-shadow: none;
+}
+
+.btn-attention {
+	background: #E95B57;
+	background: linear-gradient(to bottom, #E95B57, #BD362F);
+	background: -moz-linear-gradient(top, #E95B57 0%, #BD362F 100%);
+	background: -webkit-linear-gradient(top, #E95B57 0%, #BD362F 100%);
+	background: -o-linear-gradient(top, #E95B57 0%, #BD362F 100%);
+	background: -ms-linear-gradient(top, #E95B57 0%, #BD362F 100%);
+	color: #fff;
+	border: 1px solid #C44742;
+	text-shadow: 0px -1px 0px #666;
+}
+.btn-attention:hover {
+	background: linear-gradient(to bottom, #D14641, #BD362F);
+	background: -moz-linear-gradient(top, #D14641 0%, #BD362F 100%);
+	background: -webkit-linear-gradient(top, #D14641 0%, #BD362F 100%);
+	background: -o-linear-gradient(top, #D14641 0%, #BD362F 100%);
+	background: -ms-linear-gradient(top, #D14641 0%, #BD362F 100%);
+}
+.btn-attention:active {
+	background: #BD362F;
+	box-shadow: none;
+}
+
+/*=== Navigation */
+.nav-list .nav-header,
+.nav-list .item {
+	height: 2.5em;
+	line-height: 2.5em;
+	font-size: 0.9rem;
+}
+.nav-list .item:hover {
+	background: #fafafa;
+}
+.nav-list .item:hover a {
+	color: #003388;
+}
+.nav-list .item.active {
+	background: #0062BE;
+	color: #fff;
+}
+.nav-list .item.active a {
+	color: #fff;
+}
+.nav-list .disable {
+	color: #aaa;
+	background: #fafafa;
+	text-align: center;
+}
+.nav-list .item > a {
+	padding: 0 10px;
+}
+.nav-list a:hover {
+	text-decoration: none;
+}
+.nav-list .item.empty a {
+	color: #f39c12;
+}
+.nav-list .item.active.empty a {
+	color: #fff;
+	background: #f39c12;
+}
+.nav-list .item.error a {
+	color: #BD362F;
+}
+.nav-list .item.active.error a {
+	color: #fff;
+	background: #BD362F;
+}
+
+.nav-list .nav-header {
+	padding: 0 10px;
+	color: #888;
+	background: #f4f4f4;
+	border-bottom: 1px solid #ddd;
+	font-weight: bold;
+	text-shadow: 0 0 1px #ddd;
+}
+
+.nav-list .nav-form {
+	padding: 3px;
+	text-align: center;
+}
+
+.nav-head {
+	margin: 0;
+	background: #fff;
+	background: linear-gradient(to bottom, #fff, #f0f0f0);
+	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%);
+	border-bottom: 1px solid #ddd;
+	text-align: right;
+}
+.nav-head .item {
+	padding: 5px 10px;
+	font-size: 0.9rem;
+	line-height: 1.5rem;
+}
+
+/*=== Horizontal-list */
+.horizontal-list {
+	margin: 0;
+	padding: 0;
+	font-size: 0.9rem;
+}
+.horizontal-list .item {
+	vertical-align: middle;
+	line-height: 30px;
+}
+
+/*=== Dropdown */
+.dropdown-menu {
+	margin: 5px 0 0;
+	padding: 5px 0;
+	border: 1px solid #ddd;
+	border-radius: 5px;
+	box-shadow: 3px 3px 3px #ddd;
+	font-size: 0.8rem;
+	text-align: left;
+}
+.dropdown-menu::after {
+	content: "";
+	position: absolute;
+	top: -6px;
+	right: 13px;
+	width: 10px;
+	height: 10px;
+	background: #fff;
+	border-top: 1px solid #ddd;
+	border-left: 1px solid #ddd;
+	z-index: -10;
+	transform: rotate(45deg);
+	-moz-transform: rotate(45deg);
+	-webkit-transform: rotate(45deg);
+	-ms-transform: rotate(45deg);
+}
+.dropdown-header {
+	padding: 0 5px 5px;
+	color: #888;
+	font-weight: bold;
+	text-align: left;
+}
+.dropdown-menu > .item {
+}
+.dropdown-menu > .item > a {
+	padding: 0 25px;
+	line-height: 2.5em;
+}
+.dropdown-menu > .item > span,
+.dropdown-menu > .item > .as-link {
+	padding: 0 22px;
+	line-height: 2em;
+}
+.dropdown-menu > .item:hover {
+	background: #0062BE;
+	color: #fff;
+}
+.dropdown-menu > .item[aria-checked="true"] > a::before {
+	font-weight: bold;
+	margin: 0 0 0 -14px;
+}
+.dropdown-menu > .item:hover > a {
+	color: #fff;
+	text-decoration: none;
+}
+.dropdown-menu .input select,
+.dropdown-menu .input input {
+	margin: 0 auto 5px;
+	padding: 2px 5px;
+	border-radius: 3px;
+}
+
+.separator {
+	margin: 5px 0;
+	border-bottom: 1px solid #ddd;
+}
+
+/*=== Alerts */
+.alert {
+	margin: 15px auto;
+	padding: 10px 15px;
+	background: #f4f4f4;
+	border: 1px solid #ccc;
+	border-right: 1px solid #aaa;
+	border-bottom: 1px solid #aaa;
+	border-radius: 5px;
+	color: #aaa;
+	text-shadow: 0 0 1px #eee;
+	font-size: 0.9em;
+}
+.alert-head {
+	font-size: 1.15em;
+}
+.alert > a {
+	color: inherit;
+	text-decoration: underline;
+}
+.alert-warn {
+	background: #ffe;
+	border: 1px solid #eeb;
+	color: #c95;
+}
+.alert-success {
+	background: #dfd;
+	border: 1px solid #cec;
+	color: #484;
+}
+.alert-error {
+	background: #fdd;
+	border: 1px solid #ecc;
+	color: #844;
+}
+
+/*=== Pagination */
+.pagination {
+	background: #fafafa;
+	text-align: center;
+	color: #333;
+	font-size: 0.8em;
+}
+.content .pagination {
+	margin: 0;
+	padding: 0;
+}
+.pagination .item.pager-current {
+	font-weight: bold;
+	font-size: 1.5em;
+}
+.pagination .item a {
+	display: block;
+	color: #333;
+	font-style: italic;
+	line-height: 3em;
+	text-decoration: none;
+}
+.pagination .item a:hover {
+	background: #ddd;
+}
+.pagination:first-child .item {
+	border-bottom: 1px solid #aaa;
+}
+.pagination:last-child .item {
+	border-top: 1px solid #aaa;
+}
+
+.pagination .loading,
+.pagination a:hover.loading {
+	background: url("loader.gif") center center no-repeat #fff;
+	font-size: 0;
+}
+
+/*=== Boxes */
+.box {
+	background: #fff;
+	border-radius: 5px;
+	box-shadow: 0 0 3px #bbb;
+}
+.box .box-title {
+	margin: 0;
+	padding: 5px 10px;
+	background: #f6f6f6;
+	border-bottom: 1px solid #ddd;
+	border-radius: 5px 5px 0 0;
+}
+.box .box-content {
+	min-height: 2.5em;
+	max-height: 260px;
+}
+
+.box .box-content .item {
+	padding: 0 10px;
+	font-size: 0.9rem;
+	line-height: 2.5em;
+}
+
+.box .box-content .item .configure {
+	visibility: hidden;
+}
+.box .box-content .item:hover .configure {
+	visibility: visible;
+}
+
+/*=== Tree */
+.tree {
+	margin: 10px 0;
+}
+.tree-folder-title {
+	position: relative;
+	padding: 0 5px;
+	background: #fff;
+	line-height: 2rem;
+	font-size: 0.9rem;
+}
+.tree-folder-title .title {
+	background: inherit;
+	color: #444;
+}
+.tree-folder-title .title:hover {
+	text-decoration: none;
+}
+.tree-folder.active .tree-folder-title {
+	background: #f0f0f0;
+	font-weight: bold;
+}
+.tree-folder.active .tree-folder-title .title {
+	color: #0062BE;
+}
+.tree-folder-items {
+	border-top: 1px solid #ccc;
+	border-bottom: 1px solid #ccc;
+	background: #f6f6f6;
+}
+.tree-folder-items > .item {
+	padding: 0 10px;
+	line-height: 2.2rem;
+	font-size: 0.8rem;
+}
+.tree-folder-items > .item.active {
+	background: #0062be;
+}
+.tree-folder-items > .item > a {
+	text-decoration: none;
+}
+.tree-folder-items > .item.active > a {
+	color: #fff;
+}
+
+/*=== STRUCTURE */
+/*===============*/
+/*=== Header */
+.header {
+	height: 40px;
+	background: #f4f4f4;
+}
+.header > .item {
+	padding: 0px;
+	border-bottom: 1px solid #aaa;
+	vertical-align: middle;
+	text-align: center;
+}
+.header > .item.title{
+	width: 230px;
+}
+.header > .item.title h1 {
+	margin: 0;
+	font-size: 1em;
+}
+.header > .item.title h1 a {
+	text-decoration: none;
+}
+.header .item.configure .btn,
+.header .item.search .btn {
+	min-height: 18px;
+	padding: 4px 10px;
+	line-height: 18px;
+}
+.header > .item.title .logo {
+	height: 25px;
+	width: 25px;
+}
+
+.header > .item.search input {
+	width: 230px;
+	padding: 1px 5px 0px 5px;
+}
+.header .item.search input:focus {
+	width: 350px;
+}
+
+/*=== Body */
+#global {
+	height: calc(100% - 85px);
+}
+.aside {
+	border-right: 1px solid #aaa;
+	background: #fff;
+}
+.aside.aside_feed {
+	padding: 10px 0;
+	text-align: center;
+	background: #fff;
+}
+.aside.aside_feed .tree {
+	margin: 10px 0 50px;
+}
+
+/*=== Aside main page (categories) */
+.aside_feed .category .title:not([data-unread="0"])::after {
+	position: absolute;
+	right: 0;
+	margin: 10px 0;
+	padding: 0 10px;
+	font-size: 0.8rem;
+	line-height: 0.9rem;
+	background: inherit;
+}
+
+/*=== Aside main page (feeds) */
+.feed.item.empty.active {
+	background: #e67e22;
+}
+.feed.item.error.active {
+	background: #bd362f;
+}
+.feed.item.empty,
+.feed.item.empty > a {
+	color: #e67e22;
+}
+.feed.item.error,
+.feed.item.error > a {
+	color: #bd362f;
+}
+.feed.item.empty.active,
+.feed.item.error.active,
+.feed.item.empty.active > a,
+.feed.item.error.active > a {
+	color: #fff;
+}
+.aside_feed .tree-folder-items .dropdown-menu::after {
+	left: 2px;
+}
+.aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon,
+.aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon {
+	background-color: #fff;
+	border-radius: 3px;
+}
+
+/*=== Configuration pages */
+.post {
+	padding: 10px 50px;
+	font-size: 0.9em;
+}
+.post form {
+	margin: 10px 0;
+}
+.post.content {
+	max-width: 550px;
+}
+
+/*=== Prompt (centered) */
+.prompt {
+	text-align: center;
+}
+.prompt label {
+	text-align: left;
+}
+.prompt form {
+	margin: 10px auto 20px auto;
+	width: 200px;
+}
+.prompt input {
+	margin: 5px auto;
+	width: 100%;
+}
+.prompt p {
+	margin: 20px 0;
+}
+
+/*=== New article notification */
+#new-article {
+	background: #0084CC;
+	text-align: center;
+	font-size: 0.9em;
+}
+#new-article:hover {
+	background: #0066CC;
+}
+#new-article > a {
+	line-height: 3em;
+	color: #fff;
+	font-weight: bold;
+}
+#new-article > a:hover {
+	text-decoration: none;
+}
+
+/*=== Day indication */
+.day {
+	font-size: 0.9rem;
+	padding: 0 10px;
+	font-weight: bold;
+	line-height: 2em;
+	background: #fff;
+	border-top: 1px solid #aaa;
+	border-bottom: 1px solid #aaa;
+}
+#new-article + .day {
+	border-top: none;
+}
+.day .name {
+	padding: 0 10px 0 0;
+	color: #aab;
+	font-size: 1em;
+	opacity: 0.6;
+	font-style: italic;
+	text-align: right;
+}
+
+/*=== Index menu */
+.nav_menu {
+	background: #fafafa;
+	border-bottom: 1px solid #aaa;
+	text-align: center;
+	padding: 5px 0;
+}
+
+/*=== Feed articles */
+.flux {
+	border-left: 2px solid #aaa;
+	background: #fafafa;
+}
+.flux:hover {
+	background: #fff;
+}
+.flux.current {
+	border-left: 2px solid #0062BE;
+}
+.flux.not_read {
+	border-left: 2px solid #FF5300;
+	background: #FFF3ED;
+}
+.flux.not_read:not(.current):hover .item.title {
+	background: #FFF3ED;
+}
+.flux.favorite {
+	border-left: 2px solid #FFC300;
+	background: #FFF6DA;
+}
+.flux.favorite:not(.current):hover .item.title {
+	background: #FFF6DA;
+}
+.flux.current {
+	background: #fff;
+}
+
+
+.flux_header {
+	border-top: 1px solid #ddd;
+	font-size: 0.8rem;
+	cursor: pointer;
+}
+.flux_header .title {
+	font-size: 0.8rem;
+}
+.flux .website .favicon {
+	padding: 5px;
+}
+.flux .date {
+	color: #666;
+	font-size: 0.7rem;
+}
+
+.flux .bottom {
+	font-size: 0.8rem;
+	text-align: center;
+}
+
+/*=== Content of feed articles */
+.content {
+	padding: 10px 10px;
+}
+#stream.normal .content > h1.title {
+	display:none;
+}
+.content > h1.title > a {
+	color: #000;
+}
+
+.content hr {
+	margin: 30px 10px;
+	height: 1px;
+	background: #ddd;
+	border: 0;
+	box-shadow: 0 2px 5px #ccc;
+}
+
+.content pre {
+	margin: 10px auto;
+	padding: 10px 20px;
+	overflow: auto;
+	background: #222;
+	color: #fff;
+	font-size: 0.9rem;
+	border-radius: 3px;
+}
+.content code {
+	padding: 2px 5px;
+	color: #dd1144;
+	background: #fafafa;
+	border: 1px solid #eee;
+	border-radius: 3px;
+}
+.content pre code {
+	background: transparent;
+	color: #fff;
+	border: none;
+}
+
+.content blockquote {
+	display: block;
+	margin: 0;
+	padding: 5px 20px;
+	border-top: 1px solid #ddd;
+	border-bottom: 1px solid #ddd;
+	background: #fafafa;
+	color: #333;
+}
+.content blockquote p {
+	margin: 0;
+}
+
+/*=== Notification and actualize notification */
+.notification {
+	padding: 0 0 0 5px;
+	text-align: center;
+	border: 1px solid #eeb;
+	border-radius: 3px;
+	box-shadow: 0 0 5px #ddd;
+	font-weight: bold;
+	font-size: 0.9em;
+	line-height: 3em;
+	z-index: 10;
+	vertical-align: middle;
+}
+.notification.good {
+	background: #ffe;
+	border: 1px solid #eeb;
+	color: #c95;
+}
+.notification.bad {
+	background: #fdd;
+	border: 1px solid #ecc;
+	color: #844;
+}
+.notification a.close {
+	padding: 0 15px;
+	line-height: 3em;
+}
+.notification.good a.close:hover {
+	background: #eeb;
+}
+.notification.bad a.close:hover {
+	background: #ecc;
+}
+
+.notification#actualizeProgress {
+	line-height: 2em;
+}
+
+/*=== "Load more" part */
+#bigMarkAsRead {
+	text-align: center;
+	text-decoration: none;	
+	color: #666;
+	background: #fafafa;
+	font-size: 1.2em;
+}
+#bigMarkAsRead:hover {
+	color: #0062be;
+	background: #fff;
+	box-shadow: 0 -5px 10px #eee inset;
+}
+#bigMarkAsRead .bigTick {
+	font-size: 3em;
+}
+#bigMarkAsRead:hover .bigTick {
+	text-shadow: 0 0 5px #0062be;
+}
+
+/*=== Navigation menu (for articles) */
+#nav_entries {
+	margin: 0;
+	background: #fff;
+	border-top: 1px solid #ddd;
+	text-align: center;
+	line-height: 2.2em;
+	table-layout: fixed;
+}
+
+/*=== READER VIEW */
+/*================*/
+#stream.reader .flux {
+	padding: 0 0 50px;
+	border: none;
+	background: #f0f0f0;
+	color: #333;
+}
+#stream.reader .flux .author {
+	margin: 0 0 10px;
+	font-size: 90%;
+	color: #666;
+}
+
+/*=== GLOBAL VIEW */
+/*================*/
+.box.category .box-title .title {
+	font-weight: normal;
+	text-decoration: none;
+	text-align: left;
+}
+.box.category:not([data-unread="0"]) .box-title {
+	background: #0084CC;
+}
+.box.category:not([data-unread="0"]) .box-title:active {
+	background: #3498db;
+}
+.box.category:not([data-unread="0"]) .box-title .title {
+	color: #fff;
+	font-weight: bold;
+}
+.box.category .title:not([data-unread="0"])::after {
+	position: absolute;
+	top: 5px; right: 10px;
+	border: 0;
+	background: none;
+	color: #fff;
+	font-weight: bold;
+	box-shadow: none;
+	text-shadow: none;
+}
+.box.category .item.feed {
+	padding: 2px 10px;
+	font-size: 0.8rem;
+}
+
+/*=== DIVERS */
+/*===========*/
+.aside.aside_feed .nav-form input,
+.aside.aside_feed .nav-form select {
+	width: 140px;
+}
+.aside.aside_feed .nav-form .dropdown .dropdown-menu {
+	right: -20px;
+}
+.aside.aside_feed .nav-form .dropdown .dropdown-menu::after {
+	right: 33px;
+}
+
+/*=== STATISTICS */
+/*===============*/
+.stat {
+	margin: 10px 0 20px;
+}
+
+.stat th,
+.stat td,
+.stat tr {
+	border: none;
+}
+.stat > table td,
+.stat > table th {
+	border-bottom: 1px solid #ddd;
+}
+
+.stat > .horizontal-list {
+	margin: 0 0 5px;
+}
+.stat > .horizontal-list .item {
+	overflow: hidden;
+	white-space: nowrap;
+	text-overflow: ellipsis;
+}
+.stat > .horizontal-list .item:first-child {
+	width: 270px;
+}
+
+/*=== LOGS */
+/*=========*/
+.loglist {
+	border: 1px solid #aaa;
+	border-radius: 5px;
+	overflow: hidden;
+}
+.log {
+	padding: 5px 10px;
+	background: #fafafa;
+	color: #333;
+	font-size: 0.8rem;
+}
+.log+.log {
+	border-top: 1px solid #aaa;
+}
+.log .date {
+	display: block;
+	font-weight: bold;
+}
+.log.error {
+	background: #fdd;
+	color: #844;
+}
+.log.warning {
+	background: #ffe;
+	color: #c95;
+}
+.log.notice {
+	background: #f4f4f4;
+	color: #aaa;
+}
+.log.debug {
+	background: #333;
+	color: #eee;
+}
+
+/*=== MOBILE */
+/*===========*/
+@media(max-width: 840px) {
+	.aside {
+		box-shadow: 3px 0 3px #aaa;
+		transition: width 200ms linear;
+		-moz-transition: width 200ms linear;
+		-webkit-transition: width 200ms linear;
+		-o-transition: width 200ms linear;
+		-ms-transition: width 200ms linear;
+	}
+	.aside .toggle_aside,
+	#panel .close {
+		display: block;
+		width: 100%;
+		height: 50px;
+		line-height: 50px;
+		text-align: center;
+		background: #f6f6f6;
+		border-bottom: 1px solid #ddd;
+	}
+
+	.aside.aside_feed {
+		padding: 0;
+	}
+
+	.nav_menu .btn {
+		margin: 5px 10px;
+	}
+	.nav_menu .stick {
+		margin: 0 10px;
+	}
+	.nav_menu .stick .btn {
+		margin: 5px 0;
+	}
+	.nav_menu .search {
+		display: inline-block;
+		max-width: 97%;
+	}
+	.nav_menu .search input {
+		max-width: 97%;
+		width: 90px;
+	}
+	.nav_menu .search input:focus {
+		width: 400px;
+	}
+
+	.day .name {
+		font-size: 1.1rem;
+		text-shadow: none;
+	}
+
+	.pagination {
+		margin: 0 0 3.5em;
+	}
+
+	.notification a.close {
+		display: block;
+		left: 0;
+		background: transparent;
+	}
+	.notification a.close:hover {
+		opacity: 0.5;
+	}
+	.notification a.close .icon {
+		display: none;
+	}
+}

BIN
p/themes/Origine-compact/thumbs/original.png