aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md28
-rw-r--r--CREDITS.md6
-rw-r--r--README.md1
-rwxr-xr-xapp/Controllers/javascriptController.php2
-rw-r--r--app/Controllers/subscriptionController.php4
-rw-r--r--app/Controllers/updateController.php2
-rw-r--r--app/Controllers/userController.php21
-rw-r--r--app/Models/Auth.php4
-rw-r--r--app/Models/EntryDAO.php2
-rw-r--r--app/Models/Feed.php2
-rw-r--r--app/Models/FeedDAO.php2
-rw-r--r--app/Models/UserDAO.php2
-rw-r--r--app/i18n/cz/gen.php2
-rw-r--r--app/i18n/de/gen.php2
-rw-r--r--app/i18n/en/gen.php2
-rw-r--r--app/i18n/fr/gen.php2
-rw-r--r--app/i18n/it/gen.php2
-rw-r--r--app/i18n/nl/gen.php4
-rw-r--r--app/i18n/ru/gen.php2
-rw-r--r--app/i18n/tr/gen.php2
-rw-r--r--app/install.php2
-rw-r--r--app/layout/aside_configure.phtml2
-rw-r--r--app/layout/header.phtml2
-rw-r--r--app/views/auth/formLogin.phtml2
-rw-r--r--app/views/auth/register.phtml2
-rw-r--r--app/views/helpers/javascript_vars.phtml1
-rw-r--r--app/views/index/about.phtml2
-rw-r--r--app/views/user/manage.phtml2
-rw-r--r--cli/README.md17
-rw-r--r--cli/_cli.php2
-rwxr-xr-xcli/create-user.php2
-rwxr-xr-xcli/delete-user.php2
-rwxr-xr-xcli/do-install.php53
-rwxr-xr-xcli/list-users.php2
-rwxr-xr-xcli/reconfigure.php59
-rwxr-xr-xcli/user-info.php16
-rw-r--r--constants.php2
-rw-r--r--data/config.default.php3
-rw-r--r--data/shares.php20
-rw-r--r--lib/Favicon/DataAccess.php52
-rw-r--r--lib/Favicon/Favicon.php3
-rw-r--r--lib/lib_install.php35
-rw-r--r--lib/lib_rss.php10
-rw-r--r--p/api/greader.php2
-rw-r--r--p/scripts/main.js4
-rw-r--r--p/themes/Origine-compact/loader.gifbin0 -> 4167 bytes
-rw-r--r--p/themes/Origine-compact/metadata.json7
-rw-r--r--p/themes/Origine-compact/origine-compact.css1091
-rw-r--r--p/themes/Origine-compact/thumbs/original.pngbin0 -> 167369 bytes
49 files changed, 1399 insertions, 92 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index da73c285e..caed250ca 100644
--- a/CHANGELOG.md
+++ b/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
diff --git a/CREDITS.md b/CREDITS.md
index 827a8cfd3..57635669a 100644
--- a/CREDITS.md
+++ b/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)
diff --git a/README.md b/README.md
index 64ced52c9..d599afaa4 100644
--- a/README.md
+++ b/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)
diff --git a/app/Controllers/javascriptController.php b/app/Controllers/javascriptController.php
index 00a7b5c38..6336106a9 100755
--- a/app/Controllers/javascriptController.php
+++ b/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);
diff --git a/app/Controllers/subscriptionController.php b/app/Controllers/subscriptionController.php
index 03d3ee15e..aa9f18663 100644
--- a/app/Controllers/subscriptionController.php
+++ b/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)),
diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php
index 8f939dbdb..b4e8a0bff 100644
--- a/app/Controllers/updateController.php
+++ b/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);
}
diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php
index 9d6ae18e6..f910cecd9 100644
--- a/app/Controllers/userController.php
+++ b/app/Controllers/userController.php
@@ -35,6 +35,16 @@ class FreshRSS_user_Controller extends Minz_ActionController {
}
/**
+ * 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.
*/
public function profileAction() {
@@ -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
diff --git a/app/Models/Auth.php b/app/Models/Auth.php
index b3255cfbd..476627e10 100644
--- a/app/Models/Auth.php
+++ b/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);
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 397471baa..afcde3d7f 100644
--- a/app/Models/EntryDAO.php
+++ b/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 . ']!');
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index 97cb1c47e..7a9cf8612 100644
--- a/app/Models/Feed.php
+++ b/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');
}
}
diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php
index 68398efd5..0168aebd9 100644
--- a/app/Models/FeedDAO.php
+++ b/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);
diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php
index 32bc6de2f..a60caf395 100644
--- a/app/Models/UserDAO.php
+++ b/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'));
diff --git a/app/i18n/cz/gen.php b/app/i18n/cz/gen.php
index 3db3a31da..e6aadfc02 100644
--- a/app/i18n/cz/gen.php
+++ b/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í!',
diff --git a/app/i18n/de/gen.php b/app/i18n/de/gen.php
index c73aedbfe..4b171a04d 100644
--- a/app/i18n/de/gen.php
+++ b/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!',
diff --git a/app/i18n/en/gen.php b/app/i18n/en/gen.php
index 3f86cfd19..1ee5336bd 100644
--- a/app/i18n/en/gen.php
+++ b/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!',
diff --git a/app/i18n/fr/gen.php b/app/i18n/fr/gen.php
index b5dc098ae..43825f798 100644
--- a/app/i18n/fr/gen.php
+++ b/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 !',
diff --git a/app/i18n/it/gen.php b/app/i18n/it/gen.php
index a9a8709d3..ec6de84de 100644
--- a/app/i18n/it/gen.php
+++ b/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!',
diff --git a/app/i18n/nl/gen.php b/app/i18n/nl/gen.php
index 83811ce68..11e82cb4d 100644
--- a/app/i18n/nl/gen.php
+++ b/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!',
diff --git a/app/i18n/ru/gen.php b/app/i18n/ru/gen.php
index bc42afaa8..c913b8720 100644
--- a/app/i18n/ru/gen.php
+++ b/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!',
diff --git a/app/i18n/tr/gen.php b/app/i18n/tr/gen.php
index bcc839daf..4da0206ec 100644
--- a/app/i18n/tr/gen.php
+++ b/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!',
diff --git a/app/install.php b/app/install.php
index 986a7dc60..9a88e0f37 100644
--- a/app/install.php
+++ b/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>
diff --git a/app/layout/aside_configure.phtml b/app/layout/aside_configure.phtml
index d956ec21f..94f5b1f6c 100644
--- a/app/layout/aside_configure.phtml
+++ b/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>
diff --git a/app/layout/header.phtml b/app/layout/header.phtml
index 238c664b0..e589ed7ef 100644
--- a/app/layout/header.phtml
+++ b/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>
diff --git a/app/views/auth/formLogin.phtml b/app/views/auth/formLogin.phtml
index a8213b7ae..99be6059c 100644
--- a/app/views/auth/formLogin.phtml
+++ b/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>
diff --git a/app/views/auth/register.phtml b/app/views/auth/register.phtml
index 1f9976391..23bda25ce 100644
--- a/app/views/auth/register.phtml
+++ b/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>
diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml
index 745baa195..059224305 100644
--- a/app/views/helpers/javascript_vars.phtml
+++ b/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),
diff --git a/app/views/index/about.phtml b/app/views/index/about.phtml
index 3fdb5160d..649729952 100644
--- a/app/views/index/about.phtml
+++ b/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>
diff --git a/app/views/user/manage.phtml b/app/views/user/manage.phtml
index a32247d14..793a3a0bd 100644
--- a/app/views/user/manage.phtml
+++ b/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>
diff --git a/cli/README.md b/cli/README.md
index e4d3409a2..0123e8d6e 100644
--- a/cli/README.md
+++ b/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
+```
diff --git a/cli/_cli.php b/cli/_cli.php
index 7d1a7c6b2..f5e36eabc 100644
--- a/cli/_cli.php
+++ b/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");
}
diff --git a/cli/create-user.php b/cli/create-user.php
index 444264cc7..c9e350c14 100755
--- a/cli/create-user.php
+++ b/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 . '”');
}
diff --git a/cli/delete-user.php b/cli/delete-user.php
index 6f0e86e17..baa81b893 100755
--- a/cli/delete-user.php
+++ b/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 . '”');
}
diff --git a/cli/do-install.php b/cli/do-install.php
index 100d4947f..143ca5c3e 100755
--- a/cli/do-install.php
+++ b/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'));
}
diff --git a/cli/list-users.php b/cli/list-users.php
index e690ff451..610a9dd7b 100755
--- a/cli/list-users.php
+++ b/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);
}
diff --git a/cli/reconfigure.php b/cli/reconfigure.php
new file mode 100755
index 000000000..c6902da67
--- /dev/null
+++ b/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();
diff --git a/cli/user-info.php b/cli/user-info.php
index dd38e6c7f..aa3e239b8 100755
--- a/cli/user-info.php
+++ b/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";
}
}
diff --git a/constants.php b/constants.php
index cb00839e2..7dd2238bf 100644
--- a/constants.php
+++ b/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');
diff --git a/data/config.default.php b/data/config.default.php
index 433207a9c..748df1884 100644
--- a/data/config.default.php
+++ b/data/config.default.php
@@ -146,4 +146,7 @@ return array(
# List of enabled FreshRSS extensions.
'extensions_enabled' => array(),
+
+ # Disable self-update,
+ 'disable_update' => false,
);
diff --git a/data/shares.php b/data/shares.php
index 1295a26ac..16e528d14 100644
--- a/data/shares.php
+++ b/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',
+ ),
);
diff --git a/lib/Favicon/DataAccess.php b/lib/Favicon/DataAccess.php
index ae7509881..4c1a29541 100644
--- a/lib/Favicon/DataAccess.php
+++ b/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",
- )
- )
- );
- }
-} \ No newline at end of file
+}
diff --git a/lib/Favicon/Favicon.php b/lib/Favicon/Favicon.php
index 1912050d6..8571a1b95 100644
--- a/lib/Favicon/Favicon.php
+++ b/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]) ) {
diff --git a/lib/lib_install.php b/lib/lib_install.php
index c5cae4293..76871c98a 100644
--- a/lib/lib_install.php
+++ b/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,
);
diff --git a/lib/lib_rss.php b/lib/lib_rss.php
index 560e5b256..4298e90bf 100644
--- a/lib/lib_rss.php
+++ b/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;
}
diff --git a/p/api/greader.php b/p/api/greader.php
index 4965ffd3b..01eca6d4f 100644
--- a/p/api/greader.php
+++ b/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.');
diff --git a/p/scripts/main.js b/p/scripts/main.js
index 14e0c5be5..5dbb95c91 100644
--- a/p/scripts/main.js
+++ b/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');
});
diff --git a/p/themes/Origine-compact/loader.gif b/p/themes/Origine-compact/loader.gif
new file mode 100644
index 000000000..5ff26f0e3
--- /dev/null
+++ b/p/themes/Origine-compact/loader.gif
Binary files differ
diff --git a/p/themes/Origine-compact/metadata.json b/p/themes/Origine-compact/metadata.json
new file mode 100644
index 000000000..eba82defd
--- /dev/null
+++ b/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"]
+}
diff --git a/p/themes/Origine-compact/origine-compact.css b/p/themes/Origine-compact/origine-compact.css
new file mode 100644
index 000000000..87d04e93e
--- /dev/null
+++ b/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;
+ }
+}
diff --git a/p/themes/Origine-compact/thumbs/original.png b/p/themes/Origine-compact/thumbs/original.png
new file mode 100644
index 000000000..b5f18fd43
--- /dev/null
+++ b/p/themes/Origine-compact/thumbs/original.png
Binary files differ