aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Marien Fressinaud <dev@marienfressinaud.fr> 2015-07-30 16:04:11 +0200
committerGravatar Marien Fressinaud <dev@marienfressinaud.fr> 2015-07-30 16:04:11 +0200
commit5fbbfdea8afdb399175abf18f1dcf1b6cabc2714 (patch)
tree6506dafeb093f78169feee0df648c48d3e97ebc6
parent214a5cc9a4c2b821961bc21f22b4b08e34b5be68 (diff)
parent3d4a75aa3d37a239a34e1fa35def261a2c988665 (diff)
Merge branch 'dev' into beta
-rw-r--r--CHANGELOG.md29
-rw-r--r--README.fr.md3
-rw-r--r--README.md3
-rw-r--r--app/Controllers/authController.php13
-rwxr-xr-xapp/Controllers/feedController.php98
-rw-r--r--app/Controllers/importExportController.php6
-rw-r--r--app/Controllers/subscriptionController.php6
-rw-r--r--app/Controllers/updateController.php2
-rw-r--r--app/Controllers/userController.php101
-rw-r--r--app/Models/CategoryDAO.php2
-rw-r--r--app/Models/ConfigurationSetter.php8
-rw-r--r--app/Models/EntryDAO.php17
-rw-r--r--app/Models/Feed.php150
-rw-r--r--app/SQL/install.sql.mysql.php2
-rw-r--r--app/SQL/install.sql.sqlite.php2
-rw-r--r--app/i18n/cz/admin.php7
-rw-r--r--app/i18n/cz/conf.php5
-rw-r--r--app/i18n/cz/feedback.php1
-rw-r--r--app/i18n/cz/gen.php18
-rw-r--r--app/i18n/cz/install.php6
-rw-r--r--app/i18n/cz/sub.php1
-rw-r--r--app/i18n/de/admin.php7
-rw-r--r--app/i18n/de/conf.php4
-rw-r--r--app/i18n/de/feedback.php1
-rw-r--r--app/i18n/de/gen.php18
-rw-r--r--app/i18n/de/install.php6
-rw-r--r--app/i18n/de/sub.php1
-rw-r--r--app/i18n/en/admin.php7
-rw-r--r--app/i18n/en/conf.php4
-rw-r--r--app/i18n/en/feedback.php1
-rw-r--r--app/i18n/en/gen.php18
-rw-r--r--app/i18n/en/install.php6
-rw-r--r--app/i18n/en/sub.php1
-rw-r--r--app/i18n/fr/admin.php7
-rw-r--r--app/i18n/fr/conf.php4
-rw-r--r--app/i18n/fr/feedback.php1
-rw-r--r--app/i18n/fr/gen.php18
-rw-r--r--app/i18n/fr/install.php6
-rw-r--r--app/i18n/fr/sub.php3
-rw-r--r--app/install.php182
-rw-r--r--app/views/auth/formLogin.phtml4
-rw-r--r--app/views/auth/personaLogin.phtml4
-rw-r--r--app/views/auth/register.phtml38
-rw-r--r--app/views/auth/reset.phtml2
-rw-r--r--app/views/configure/display.phtml2
-rw-r--r--app/views/configure/reading.phtml2
-rw-r--r--app/views/feed/add.phtml2
-rw-r--r--app/views/helpers/feed/update.phtml16
-rw-r--r--app/views/subscription/index.phtml4
-rw-r--r--app/views/user/manage.phtml28
-rw-r--r--app/views/user/profile.phtml37
-rw-r--r--constants.php3
-rw-r--r--data/PubSubHubbub/feeds/.gitignore1
-rw-r--r--data/PubSubHubbub/feeds/README.md7
-rw-r--r--data/PubSubHubbub/keys/.gitignore1
-rw-r--r--data/PubSubHubbub/keys/README.md4
-rw-r--r--data/config.default.php45
-rw-r--r--data/users/_/config.default.php2
-rw-r--r--lib/Minz/Configuration.php28
-rw-r--r--lib/Minz/Extension.php2
-rw-r--r--lib/Minz/ModelPdo.php10
-rw-r--r--lib/Minz/Request.php45
-rw-r--r--lib/Minz/Url.php7
-rw-r--r--lib/Minz/View.php1
-rw-r--r--lib/SimplePie/SimplePie.php149
-rw-r--r--lib/SimplePie/SimplePie/Cache/File.php12
-rw-r--r--lib/SimplePie/SimplePie/Decode/HTML/Entities.php1
-rw-r--r--lib/SimplePie/SimplePie/File.php11
-rw-r--r--lib/SimplePie/SimplePie/Item.php42
-rw-r--r--lib/SimplePie/SimplePie/Locator.php2
-rw-r--r--lib/SimplePie/SimplePie/Misc.php8
-rw-r--r--lib/SimplePie/SimplePie/Parse/Date.php6
-rwxr-xr-xlib/SimplePie/SimplePie/Registry.php4
-rw-r--r--lib/SimplePie/SimplePie/Sanitize.php4
-rw-r--r--lib/lib_rss.php73
-rw-r--r--p/api/pshb.php133
76 files changed, 1233 insertions, 282 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b989b113e..f50b88060 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,34 @@
# Changelog
+## 2015-07-30 FreshRSS 1.1.2-beta
+
+* Features
+ * Support for PubSubHubbub for instant notifications from compatible Web sites. [#312](https://github.com/FreshRSS/FreshRSS/issues/312)
+ * cURL options to use a proxy for retrieving feeds. [#897](https://github.com/FreshRSS/FreshRSS/issues/897) [#675](https://github.com/FreshRSS/FreshRSS/issues/675)
+ * Allow anonymous users to create an account. [#679](https://github.com/FreshRSS/FreshRSS/issues/679)
+* Security
+ * cURL options to verify or not SSL/TLS certificates (now enabled by default). [#897](https://github.com/FreshRSS/FreshRSS/issues/897) [#502](https://github.com/FreshRSS/FreshRSS/issues/502)
+ * Support for SSL connection to MySQL. [#868](https://github.com/FreshRSS/FreshRSS/issues/868)
+ * Workaround for browsers that have disabled support for `<form autocomplete="off">`. [#880](https://github.com/FreshRSS/FreshRSS/issues/880)
+* UI
+ * Force UTF-8 for responses. [#870](https://github.com/FreshRSS/FreshRSS/issues/870)
+ * Increased pagination limit to 500 articles. [#872](https://github.com/FreshRSS/FreshRSS/issues/872)
+ * Improved UI for installation. [#855](https://github.com/FreshRSS/FreshRSS/issues/855)
+* Misc.
+ * PHP 7 officially supported (~70% speed improvements on early tests). [#889](https://github.com/FreshRSS/FreshRSS/issues/889)
+ * Restore support for PHP 5.2.1+. [#214a5cc](https://github.com/Alkarex/FreshRSS/commit/214a5cc9a4c2b821961bc21f22b4b08e34b5be68) [#894](https://github.com/FreshRSS/FreshRSS/issues/894)
+ * Support for data-src for images of articles retrieved via the full-content module. [#877](https://github.com/FreshRSS/FreshRSS/issues/877)
+ * Add a couple of default feeds for fresh installations. [#886](https://github.com/FreshRSS/FreshRSS/issues/886)
+ * Changed some log visibilities. [#885](https://github.com/FreshRSS/FreshRSS/issues/885)
+ * Fix broken links for extension script / style files. [#862](https://github.com/FreshRSS/FreshRSS/issues/862)
+ * Load default configuration during installation to avoid hard-coded values. [#890](https://github.com/FreshRSS/FreshRSS/issues/890)
+ * Fix non-consistent behaviour in Minz_Request::getBaseUrl() and introduce Minz_Request::guessBaseUrl(). [#906](https://github.com/FreshRSS/FreshRSS/issues/906)
+ * Generate `base_url` during the installation and add a `pubsubhubbub_enabled` configuration key. [#865](https://github.com/FreshRSS/FreshRSS/issues/865)
+ * Load configuration by recursion to overwrite array values. [#923](https://github.com/FreshRSS/FreshRSS/issues/923)
+ * Cast `$limits` configuration values in integer. [#925](https://github.com/FreshRSS/FreshRSS/issues/925)
+ * Don't hide errors in configuration. [#920](https://github.com/FreshRSS/FreshRSS/issues/920)
+
+
## 2015-05-31 FreshRSS 1.1.1 (beta)
* Features
diff --git a/README.fr.md b/README.fr.md
index 99281d80a..729dc9826 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -6,6 +6,7 @@ FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de [Leed]
Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable.
Il permet de gérer plusieurs utilisateurs, et dispose d’un mode de lecture anonyme.
+Il supporte [PubSubHubbub](https://code.google.com/p/pubsubhubbub/) pour des notifications instantanées depuis les sites compatibles.
* Site officiel : http://freshrss.org
* Démo : http://demo.freshrss.org/
@@ -32,7 +33,7 @@ Nous sommes une communauté amicale.
* Serveur modeste, par exemple sous Linux ou Windows
* Fonctionne même sur un Raspberry Pi avec des temps de réponse < 1s (testé sur 150 flux, 22k articles, soit 32Mo de données partiellement compressées)
* Serveur Web Apache2 (recommandé), ou nginx, lighttpd (non testé sur les autres)
-* PHP 5.2.1+ (PHP 5.3.7+ recommandé)
+* PHP 5.2.1+ (PHP 5.3.7+ recommandé, et PHP 5.5+ pour les performances) (support bêta de PHP 7 avec encore meilleures performances)
* Requis : [PDO_MySQL](http://php.net/pdo-mysql) ou [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (pour accès API sur plateformes < 64 bits), [IDN](http://php.net/intl.idn) (pour les noms de domaines internationalisés)
* Recommandés : [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [zlib](http://php.net/zlib), [Zip](http://php.net/zip)
* MySQL 5.0.3+ (recommandé) ou SQLite 3.7.4+
diff --git a/README.md b/README.md
index e0996508c..a105dd4ca 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@ FreshRSS is a self-hosted RSS feed aggregator such as [Leed](http://projet.idlem
It is at the same time lightweight, easy to work with, powerful and customizable.
It is a multi-user application with an anonymous reading mode.
+It supports [PubSubHubbub](https://code.google.com/p/pubsubhubbub/) for instant notifications from compatible Web sites.
* Official website: http://freshrss.org
* Demo: http://demo.freshrss.org/
@@ -32,7 +33,7 @@ We are a friendly community.
* Light server running Linux or Windows
* It even works on Raspberry Pi with response time under a second (tested with 150 feeds, 22k articles, or 32Mo of compressed data)
* A web server: Apache2 (recommended), nginx, lighttpd (not tested on others)
-* PHP 5.2.1+ (PHP 5.3.7+ recommended)
+* PHP 5.2.1+ (PHP 5.3.7+ recommended, and PHP 5.5+ for performance) (beta support for PHP 7 with even higher performance)
* Required extensions: [PDO_MySQL](http://php.net/pdo-mysql) or [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (for API access on platforms < 64 bits), [IDN](http://php.net/intl.idn) (for Internationalized Domain Names)
* Recommended extensions: [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [zlib](http://php.net/zlib), [Zip](http://php.net/zip)
* MySQL 5.0.3+ (recommended) or SQLite 3.7.4+
diff --git a/app/Controllers/authController.php b/app/Controllers/authController.php
index 937c0759d..aff184263 100644
--- a/app/Controllers/authController.php
+++ b/app/Controllers/authController.php
@@ -253,7 +253,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
FreshRSS_Auth::giveAccess();
invalidateHttpCache();
} else {
- Minz_Log::error($reason);
+ Minz_Log::warning($reason);
$res = array();
$res['status'] = 'failure';
@@ -346,4 +346,15 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
}
}
}
+
+ /**
+ * This action gives possibility to a user to create an account.
+ */
+ public function registerAction() {
+ if (max_registrations_reached()) {
+ Minz_Error::error(403);
+ }
+
+ Minz_View::prependTitle(_t('gen.auth.registration.title') . ' · ');
+ }
}
diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php
index 8db273aca..ec3dce777 100755
--- a/app/Controllers/feedController.php
+++ b/app/Controllers/feedController.php
@@ -98,10 +98,10 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
// HTTP information are useful if feed is protected behind a
// HTTP authentication
- $user = Minz_Request::param('http_user');
- $pass = Minz_Request::param('http_pass');
+ $user = trim(Minz_Request::param('http_user', ''));
+ $pass = Minz_Request::param('http_pass', '');
$http_auth = '';
- if ($user != '' || $pass != '') {
+ if ($user != '' && $pass != '') { //TODO: Sanitize
$http_auth = $user . ':' . $pass;
}
@@ -168,6 +168,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
// Ok, feed has been added in database. Now we have to refresh entries.
$feed->_id($id);
$feed->faviconPrepare();
+ //$feed->pubSubHubbubPrepare(); //TODO: prepare PubSubHubbub already when adding the feed
$is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0;
@@ -261,12 +262,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
* This action actualizes entries from one or several feeds.
*
* Parameters are:
- * - id (default: false)
+ * - id (default: false): Feed ID
+ * - url (default: false): Feed URL
* - force (default: false)
- * If id is not specified, all the feeds are actualized. But if force is
+ * If id and url are not specified, all the feeds are actualized. But if force is
* false, process stops at 10 feeds to avoid time execution problem.
*/
- public function actualizeAction() {
+ public function actualizeAction($simplePiePush = null) {
@set_time_limit(300);
$feedDAO = FreshRSS_Factory::createFeedDao();
@@ -274,14 +276,15 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
Minz_Session::_param('actualize_feeds', false);
$id = Minz_Request::param('id');
+ $url = Minz_Request::param('url');
$force = Minz_Request::param('force');
// Create a list of feeds to actualize.
// If id is set and valid, corresponding feed is added to the list but
// alone in order to automatize further process.
$feeds = array();
- if ($id) {
- $feed = $feedDAO->searchById($id);
+ if ($id || $url) {
+ $feed = $id ? $feedDAO->searchById($id) : $feedDAO->searchByUrl($url);
if ($feed) {
$feeds[] = $feed;
}
@@ -293,20 +296,36 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1);
$date_min = time() - (3600 * 24 * 30 * $nb_month_old);
+ // PubSubHubbub support
+ $pubsubhubbubEnabledGeneral = FreshRSS_Context::$system_conf->pubsubhubbub_enabled;
+ $pshbMinAge = time() - (3600 * 24); //TODO: Make a configuration.
+
$updated_feeds = 0;
$is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0;
foreach ($feeds as $feed) {
+ $url = $feed->url(); //For detection of HTTP 301
+
+ $pubSubHubbubEnabled = $pubsubhubbubEnabledGeneral && $feed->pubSubHubbubEnabled();
+ if ((!$simplePiePush) && (!$id) && $pubSubHubbubEnabled && ($feed->lastUpdate() > $pshbMinAge)) {
+ $text = 'Skip pull of feed using PubSubHubbub: ' . $url;
+ //Minz_Log::debug($text);
+ file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND);
+ continue; //When PubSubHubbub is used, do not pull refresh so often
+ }
+
if (!$feed->lock()) {
Minz_Log::notice('Feed already being actualized: ' . $feed->url());
continue;
}
- $url = $feed->url(); //For detection of HTTP 301
try {
- // Load entries
- $feed->load(false);
+ if ($simplePiePush) {
+ $feed->loadEntries($simplePiePush); //Used by PubSubHubbub
+ } else {
+ $feed->load(false);
+ }
} catch (FreshRSS_Feed_Exception $e) {
- Minz_Log::notice($e->getMessage());
+ Minz_Log::warning($e->getMessage());
$feedDAO->updateLastUpdate($feed->id(), true);
$feed->unlock();
continue;
@@ -368,6 +387,14 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
continue;
}
+ if ($pubSubHubbubEnabled && !$simplePiePush) { //We use push, but have discovered an article by pull!
+ $text = 'An article was discovered by pull although we use PubSubHubbub!: Feed ' . $url . ' GUID ' . $entry->guid();
+ file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND);
+ Minz_Log::warning($text);
+ $pubSubHubbubEnabled = false;
+ $feed->pubSubHubbubError(true);
+ }
+
if (!$entryDAO->hasTransaction()) {
$entryDAO->beginTransaction();
}
@@ -398,13 +425,32 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$entryDAO->commit();
}
- if ($feed->url() !== $url) {
- // HTTP 301 Moved Permanently
+ if ($feed->hubUrl() && $feed->selfUrl()) { //selfUrl has priority for PubSubHubbub
+ if ($feed->selfUrl() !== $url) { //https://code.google.com/p/pubsubhubbub/wiki/MovingFeedsOrChangingHubs
+ $selfUrl = checkUrl($feed->selfUrl());
+ if ($selfUrl) {
+ Minz_Log::debug('PubSubHubbub unsubscribe ' . $feed->url());
+ if (!$feed->pubSubHubbubSubscribe(false)) { //Unsubscribe
+ Minz_Log::warning('Error while PubSubHubbub unsubscribing from ' . $feed->url());
+ }
+ $feed->_url($selfUrl, false);
+ Minz_Log::notice('Feed ' . $url . ' canonical address moved to ' . $feed->url());
+ $feedDAO->updateFeed($feed->id(), array('url' => $feed->url()));
+ }
+ }
+ }
+ elseif ($feed->url() !== $url) { // HTTP 301 Moved Permanently
Minz_Log::notice('Feed ' . $url . ' moved permanently to ' . $feed->url());
$feedDAO->updateFeed($feed->id(), array('url' => $feed->url()));
}
$feed->faviconPrepare();
+ if ($pubsubhubbubEnabledGeneral && $feed->pubSubHubbubPrepare()) {
+ Minz_Log::notice('PubSubHubbub subscribe ' . $feed->url());
+ if (!$feed->pubSubHubbubSubscribe(true)) { //Subscribe
+ Minz_Log::warning('Error while PubSubHubbub subscribing to ' . $feed->url());
+ }
+ }
$feed->unlock();
$updated_feeds++;
unset($feed);
@@ -427,20 +473,20 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
Minz_Session::_param('notification', $notif);
// No layout in ajax request.
$this->view->_useLayout(false);
- return;
- }
-
- // Redirect to the main page with correct notification.
- if ($updated_feeds === 1) {
- $feed = reset($feeds);
- Minz_Request::good(_t('feedback.sub.feed.actualized', $feed->name()), array(
- 'params' => array('get' => 'f_' . $feed->id())
- ));
- } elseif ($updated_feeds > 1) {
- Minz_Request::good(_t('feedback.sub.feed.n_actualized', $updated_feeds), array());
} else {
- Minz_Request::good(_t('feedback.sub.feed.no_refresh'), array());
+ // Redirect to the main page with correct notification.
+ if ($updated_feeds === 1) {
+ $feed = reset($feeds);
+ Minz_Request::good(_t('feedback.sub.feed.actualized', $feed->name()), array(
+ 'params' => array('get' => 'f_' . $feed->id())
+ ));
+ } elseif ($updated_feeds > 1) {
+ Minz_Request::good(_t('feedback.sub.feed.n_actualized', $updated_feeds), array());
+ } else {
+ Minz_Request::good(_t('feedback.sub.feed.no_refresh'), array());
+ }
}
+ return $updated_feeds;
}
/**
diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php
index 26b163e43..60e467255 100644
--- a/app/Controllers/importExportController.php
+++ b/app/Controllers/importExportController.php
@@ -47,7 +47,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$status_file = $file['error'];
if ($status_file !== 0) {
- Minz_Log::error('File cannot be uploaded. Error code: ' . $status_file);
+ Minz_Log::warning('File cannot be uploaded. Error code: ' . $status_file);
Minz_Request::bad(_t('feedback.import_export.file_cannot_be_uploaded'),
array('c' => 'importExport', 'a' => 'index'));
}
@@ -69,7 +69,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
if (!is_resource($zip)) {
// zip_open cannot open file: something is wrong
- Minz_Log::error('Zip archive cannot be imported. Error code: ' . $zip);
+ Minz_Log::warning('Zip archive cannot be imported. Error code: ' . $zip);
Minz_Request::bad(_t('feedback.import_export.zip_error'),
array('c' => 'importExport', 'a' => 'index'));
}
@@ -77,7 +77,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
while (($zipfile = zip_read($zip)) !== false) {
if (!is_resource($zipfile)) {
// zip_entry() can also return an error code!
- Minz_Log::error('Zip file cannot be imported. Error code: ' . $zipfile);
+ Minz_Log::warning('Zip file cannot be imported. Error code: ' . $zipfile);
} else {
$type_zipfile = $this->guessFileType(zip_entry_name($zipfile));
if ($type_file !== 'unknown') {
diff --git a/app/Controllers/subscriptionController.php b/app/Controllers/subscriptionController.php
index 333565faf..03d3ee15e 100644
--- a/app/Controllers/subscriptionController.php
+++ b/app/Controllers/subscriptionController.php
@@ -77,11 +77,11 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
Minz_View::prependTitle(_t('sub.title.feed_management') . ' · ' . $this->view->feed->name() . ' · ');
if (Minz_Request::isPost()) {
- $user = Minz_Request::param('http_user', '');
- $pass = Minz_Request::param('http_pass', '');
+ $user = trim(Minz_Request::param('http_user_feed' . $id, ''));
+ $pass = Minz_Request::param('http_pass_feed' . $id, '');
$httpAuth = '';
- if ($user != '' || $pass != '') {
+ if ($user != '' && $pass != '') { //TODO: Sanitize
$httpAuth = $user . ':' . $pass;
}
diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php
index 4797a3486..84a33fe85 100644
--- a/app/Controllers/updateController.php
+++ b/app/Controllers/updateController.php
@@ -63,7 +63,7 @@ class FreshRSS_update_Controller extends Minz_ActionController {
curl_close($c);
if ($c_status !== 200) {
- Minz_Log::error(
+ Minz_Log::warning(
'Error during update (HTTP code ' . $c_status . '): ' . $c_error
);
diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php
index ed01b83c5..428cd145d 100644
--- a/app/Controllers/userController.php
+++ b/app/Controllers/userController.php
@@ -12,9 +12,14 @@ class FreshRSS_user_Controller extends Minz_ActionController {
* This action is called before every other action in that class. It is
* the common boiler plate for every action. It is triggered by the
* underlying framework.
+ *
+ * @todo clean up the access condition.
*/
public function firstAction() {
- if (!FreshRSS_Auth::hasAccess()) {
+ if (!FreshRSS_Auth::hasAccess() && !(
+ Minz_Request::actionName() === 'create' &&
+ !max_registrations_reached()
+ )) {
Minz_Error::error(403);
}
}
@@ -25,13 +30,17 @@ class FreshRSS_user_Controller extends Minz_ActionController {
public function profileAction() {
Minz_View::prependTitle(_t('conf.profile.title') . ' · ');
+ Minz_View::appendScript(Minz_Url::display(
+ '/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js')
+ ));
+
if (Minz_Request::isPost()) {
$ok = true;
- $passwordPlain = Minz_Request::param('passwordPlain', '', true);
+ $passwordPlain = Minz_Request::param('newPasswordPlain', '', true);
if ($passwordPlain != '') {
- Minz_Request::_param('passwordPlain'); //Discard plain-text password ASAP
- $_POST['passwordPlain'] = '';
+ Minz_Request::_param('newPasswordPlain'); //Discard plain-text password ASAP
+ $_POST['newPasswordPlain'] = '';
if (!function_exists('password_hash')) {
include_once(LIB_PATH . '/password_compat.php');
}
@@ -103,8 +112,24 @@ class FreshRSS_user_Controller extends Minz_ActionController {
$this->view->size_user = $entryDAO->size();
}
+ /**
+ * This action creates a new user.
+ *
+ * Request parameters are:
+ * - new_user_language
+ * - new_user_name
+ * - new_user_passwordPlain
+ * - new_user_email
+ * - r (i.e. a redirection url, optional)
+ *
+ * @todo clean up this method. Idea: write a method to init a user with basic information.
+ * @todo handle r redirection in Minz_Request::forward directly?
+ */
public function createAction() {
- if (Minz_Request::isPost() && FreshRSS_Auth::hasAccess('admin')) {
+ if (Minz_Request::isPost() && (
+ FreshRSS_Auth::hasAccess('admin') ||
+ !max_registrations_reached()
+ )) {
$db = FreshRSS_Context::$system_conf->db;
require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
@@ -175,15 +200,37 @@ class FreshRSS_user_Controller extends Minz_ActionController {
Minz_Session::_param('notification', $notif);
}
- Minz_Request::forward(array('c' => 'user', 'a' => 'manage'), true);
+ $redirect_url = urldecode(Minz_Request::param('r', false, true));
+ if (!$redirect_url) {
+ $redirect_url = array('c' => 'user', 'a' => 'manage');
+ }
+ Minz_Request::forward($redirect_url, true);
}
+ /**
+ * This action delete an existing user.
+ *
+ * Request parameter is:
+ * - username
+ *
+ * @todo clean up this method. Idea: create a User->clean() method.
+ */
public function deleteAction() {
- if (Minz_Request::isPost() && FreshRSS_Auth::hasAccess('admin')) {
+ $username = Minz_Request::param('username');
+ $redirect_url = urldecode(Minz_Request::param('r', false, true));
+ if (!$redirect_url) {
+ $redirect_url = array('c' => 'user', 'a' => 'manage');
+ }
+
+ $self_deletion = Minz_Session::param('currentUser', '_') === $username;
+
+ if (Minz_Request::isPost() && (
+ FreshRSS_Auth::hasAccess('admin') ||
+ $self_deletion
+ )) {
$db = FreshRSS_Context::$system_conf->db;
require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
- $username = Minz_Request::param('username');
$ok = ctype_alnum($username);
$user_data = join_path(DATA_PATH, 'users', $username);
@@ -191,6 +238,16 @@ class FreshRSS_user_Controller extends Minz_ActionController {
$default_user = FreshRSS_Context::$system_conf->default_user;
$ok &= (strcasecmp($username, $default_user) !== 0); //It is forbidden to delete the default user
}
+ if ($ok && $self_deletion) {
+ // We check the password if it's a self-destruction
+ $nonce = Minz_Session::param('nonce');
+ $challenge = Minz_Request::param('challenge', '');
+
+ $ok &= FreshRSS_FormAuth::checkCredentials(
+ $username, FreshRSS_Context::$user_conf->passwordHash,
+ $nonce, $challenge
+ );
+ }
if ($ok) {
$ok &= is_dir($user_data);
}
@@ -200,6 +257,10 @@ class FreshRSS_user_Controller extends Minz_ActionController {
$ok &= recursive_unlink($user_data);
//TODO: delete Persona file
}
+ if ($ok && $self_deletion) {
+ FreshRSS_Auth::removeAccess();
+ $redirect_url = array('c' => 'index', 'a' => 'index');
+ }
invalidateHttpCache();
$notif = array(
@@ -209,6 +270,30 @@ class FreshRSS_user_Controller extends Minz_ActionController {
Minz_Session::_param('notification', $notif);
}
+ Minz_Request::forward($redirect_url, true);
+ }
+
+ /**
+ * This action updates the max number of registrations.
+ *
+ * Request parameter is:
+ * - max-registrations (int >= 0)
+ */
+ public function setRegistrationAction() {
+ if (Minz_Request::isPost() && FreshRSS_Auth::hasAccess('admin')) {
+ $limits = FreshRSS_Context::$system_conf->limits;
+ $limits['max_registrations'] = Minz_Request::param('max-registrations', 1);
+ FreshRSS_Context::$system_conf->limits = $limits;
+ FreshRSS_Context::$system_conf->save();
+
+ invalidateHttpCache();
+
+ Minz_Session::_param('notification', array(
+ 'type' => 'good',
+ 'content' => _t('feedback.user.set_registration')
+ ));
+ }
+
Minz_Request::forward(array('c' => 'user', 'a' => 'manage'), true);
}
}
diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php
index 189a5f0e4..b5abac519 100644
--- a/app/Models/CategoryDAO.php
+++ b/app/Models/CategoryDAO.php
@@ -13,7 +13,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable
return $this->bd->lastInsertId();
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
- Minz_Log::error('SQL error addCategory: ' . $info[2] );
+ Minz_Log::error('SQL error addCategory: ' . $info[2]);
return false;
}
}
diff --git a/app/Models/ConfigurationSetter.php b/app/Models/ConfigurationSetter.php
index 4bd29ecb0..992a3a387 100644
--- a/app/Models/ConfigurationSetter.php
+++ b/app/Models/ConfigurationSetter.php
@@ -352,6 +352,9 @@ class FreshRSS_ConfigurationSetter {
'min' => 0,
'max' => $max_small_int,
),
+ 'max_registrations' => array(
+ 'min' => 0,
+ ),
);
foreach ($values as $key => $value) {
@@ -359,10 +362,11 @@ class FreshRSS_ConfigurationSetter {
continue;
}
+ $value = intval($value);
$limits = $limits_keys[$key];
if (
- (!isset($limits['min']) || $value > $limits['min']) &&
- (!isset($limits['max']) || $value < $limits['max'])
+ (!isset($limits['min']) || $value >= $limits['min']) &&
+ (!isset($limits['max']) || $value <= $limits['max'])
) {
$data['limits'][$key] = $value;
}
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 9ddcfcfb3..f74055835 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -6,6 +6,10 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
return parent::$sharedDbType !== 'sqlite';
}
+ public function hasNativeHex() {
+ return parent::$sharedDbType !== 'sqlite';
+ }
+
protected function addColumn($name) {
Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn: ' . $name);
$hasTransaction = false;
@@ -64,7 +68,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
. ', link, date, lastSeen, hash, is_read, is_favorite, id_feed, tags) '
. 'VALUES(?, ?, ?, ?, '
. ($this->isCompressed() ? 'COMPRESS(?)' : '?')
- . ', ?, ?, ?, ?, ?, ?, ?, ?)';
+ . ', ?, ?, ?, '
+ . ($this->hasNativeHex() ? 'X?' : '?')
+ . ', ?, ?, ?, ?)';
$this->addEntryPrepared = $this->bd->prepare($sql);
}
@@ -77,7 +83,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
substr($valuesTmp['link'], 0, 1023),
$valuesTmp['date'],
time(),
- hex2bin($valuesTmp['hash']), // X'09AF' hexadecimal literals do not work with SQLite/PDO
+ $this->hasNativeHex() ? $valuesTmp['hash'] : pack('H*', $valuesTmp['hash']), // X'09AF' hexadecimal literals do not work with SQLite/PDO //hex2bin() is PHP5.4+
$valuesTmp['is_read'] ? 1 : 0,
$valuesTmp['is_favorite'] ? 1 : 0,
$valuesTmp['id_feed'],
@@ -109,8 +115,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
$sql = 'UPDATE `' . $this->prefix . 'entry` '
. 'SET title=?, author=?, '
. ($this->isCompressed() ? 'content_bin=COMPRESS(?)' : 'content=?')
- . ', link=?, date=?, lastSeen=?, hash=?, '
- . ($valuesTmp['is_read'] === null ? '' : 'is_read=?, ')
+ . ', link=?, date=?, lastSeen=?, hash='
+ . ($this->hasNativeHex() ? 'X?' : '?')
+ . ', ' . ($valuesTmp['is_read'] === null ? '' : 'is_read=?, ')
. 'tags=? '
. 'WHERE id_feed=? AND guid=?';
$this->updateEntryPrepared = $this->bd->prepare($sql);
@@ -123,7 +130,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
substr($valuesTmp['link'], 0, 1023),
$valuesTmp['date'],
time(),
- hex2bin($valuesTmp['hash']),
+ $this->hasNativeHex() ? $valuesTmp['hash'] : pack('H*', $valuesTmp['hash']),
);
if ($valuesTmp['is_read'] !== null) {
$values[] = $valuesTmp['is_read'] ? 1 : 0;
diff --git a/app/Models/Feed.php b/app/Models/Feed.php
index 85fb173ec..bf7ed3967 100644
--- a/app/Models/Feed.php
+++ b/app/Models/Feed.php
@@ -19,6 +19,8 @@ class FreshRSS_Feed extends Minz_Model {
private $ttl = -2;
private $hash = null;
private $lockPath = '';
+ private $hubUrl = '';
+ private $selfUrl = '';
public function __construct($url, $validate=true) {
if ($validate) {
@@ -49,6 +51,12 @@ class FreshRSS_Feed extends Minz_Model {
public function url() {
return $this->url;
}
+ public function selfUrl() {
+ return $this->selfUrl;
+ }
+ public function hubUrl() {
+ return $this->hubUrl;
+ }
public function category() {
return $this->category;
}
@@ -96,6 +104,16 @@ class FreshRSS_Feed extends Minz_Model {
public function ttl() {
return $this->ttl;
}
+ // public function ttlExpire() {
+ // $ttl = $this->ttl;
+ // if ($ttl == -2) { //Default
+ // $ttl = FreshRSS_Context::$user_conf->ttl_default;
+ // }
+ // if ($ttl == -1) { //Never
+ // $ttl = 64000000; //~2 years. Good enough for PubSubHubbub logic
+ // }
+ // return $this->lastUpdate + $ttl;
+ // }
public function nbEntries() {
if ($this->nbEntries < 0) {
$feedDAO = FreshRSS_Factory::createFeedDao();
@@ -226,6 +244,11 @@ class FreshRSS_Feed extends Minz_Model {
throw new FreshRSS_Feed_Exception(($errorMessage == '' ? 'Feed error' : $errorMessage) . ' [' . $url . ']');
}
+ $links = $feed->get_links('self');
+ $this->selfUrl = isset($links[0]) ? $links[0] : null;
+ $links = $feed->get_links('hub');
+ $this->hubUrl = isset($links[0]) ? $links[0] : null;
+
if ($loadDetails) {
// si on a utilisé l'auto-discover, notre url va avoir changé
$subscribe_url = $feed->subscribe_url(false);
@@ -259,7 +282,7 @@ class FreshRSS_Feed extends Minz_Model {
}
}
- private function loadEntries($feed) {
+ public function loadEntries($feed) {
$entries = array();
foreach ($feed->get_items() as $item) {
@@ -333,4 +356,129 @@ class FreshRSS_Feed extends Minz_Model {
function unlock() {
@unlink($this->lockPath);
}
+
+ //<PubSubHubbub>
+
+ function pubSubHubbubEnabled() {
+ $url = $this->selfUrl ? $this->selfUrl : $this->url;
+ $hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($url) . '/!hub.json';
+ if ($hubFile = @file_get_contents($hubFilename)) {
+ $hubJson = json_decode($hubFile, true);
+ if ($hubJson && empty($hubJson['error']) &&
+ (empty($hubJson['lease_end']) || $hubJson['lease_end'] > time())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function pubSubHubbubError($error = true) {
+ $url = $this->selfUrl ? $this->selfUrl : $this->url;
+ $hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($url) . '/!hub.json';
+ $hubFile = @file_get_contents($hubFilename);
+ $hubJson = $hubFile ? json_decode($hubFile, true) : array();
+ if (!isset($hubJson['error']) || $hubJson['error'] !== (bool)$error) {
+ $hubJson['error'] = (bool)$error;
+ file_put_contents($hubFilename, json_encode($hubJson));
+ file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t"
+ . 'Set error to ' . ($error ? 1 : 0) . ' for ' . $url . "\n", FILE_APPEND);
+ }
+ return false;
+ }
+
+ function pubSubHubbubPrepare() {
+ $key = '';
+ if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl && @is_dir(PSHB_PATH)) {
+ $path = PSHB_PATH . '/feeds/' . base64url_encode($this->selfUrl);
+ $hubFilename = $path . '/!hub.json';
+ if ($hubFile = @file_get_contents($hubFilename)) {
+ $hubJson = json_decode($hubFile, true);
+ if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key'])) {
+ $text = 'Invalid JSON for PubSubHubbub: ' . $this->url;
+ Minz_Log::warning($text);
+ file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND);
+ return false;
+ }
+ if ((!empty($hubJson['lease_end'])) && ($hubJson['lease_end'] < (time() + (3600 * 23)))) { //TODO: Make a better policy
+ $text = 'PubSubHubbub lease ends at '
+ . date('c', empty($hubJson['lease_end']) ? time() : $hubJson['lease_end'])
+ . ' and needs renewal: ' . $this->url;
+ Minz_Log::warning($text);
+ file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND);
+ $key = $hubJson['key']; //To renew our lease
+ } elseif (((!empty($hubJson['error'])) || empty($hubJson['lease_end'])) &&
+ (empty($hubJson['lease_start']) || $hubJson['lease_start'] < time() - (3600 * 23))) { //Do not renew too often
+ $key = $hubJson['key']; //To renew our lease
+ }
+ } else {
+ @mkdir($path, 0777, true);
+ $key = sha1($path . FreshRSS_Context::$system_conf->salt . uniqid(mt_rand(), true));
+ $hubJson = array(
+ 'hub' => $this->hubUrl,
+ 'key' => $key,
+ );
+ file_put_contents($hubFilename, json_encode($hubJson));
+ @mkdir(PSHB_PATH . '/keys/');
+ file_put_contents(PSHB_PATH . '/keys/' . $key . '.txt', base64url_encode($this->selfUrl));
+ $text = 'PubSubHubbub prepared for ' . $this->url;
+ Minz_Log::debug($text);
+ 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')) {
+ touch($path . '/' . $currentUser . '.txt');
+ }
+ }
+ return $key;
+ }
+
+ //Parameter true to subscribe, false to unsubscribe.
+ function pubSubHubbubSubscribe($state) {
+ if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl) {
+ $hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($this->selfUrl) . '/!hub.json';
+ $hubFile = @file_get_contents($hubFilename);
+ if ($hubFile === false) {
+ Minz_Log::warning('JSON not found for PubSubHubbub: ' . $this->url);
+ return false;
+ }
+ $hubJson = json_decode($hubFile, true);
+ if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key'])) {
+ Minz_Log::warning('Invalid JSON for PubSubHubbub: ' . $this->url);
+ return false;
+ }
+ $callbackUrl = checkUrl(FreshRSS_Context::$system_conf->base_url . 'api/pshb.php?k=' . $hubJson['key']);
+ if ($callbackUrl == '') {
+ Minz_Log::warning('Invalid callback for PubSubHubbub: ' . $this->url);
+ return false;
+ }
+ $ch = curl_init();
+ curl_setopt_array($ch, array(
+ CURLOPT_URL => $this->hubUrl,
+ CURLOPT_FOLLOWLOCATION => true,
+ CURLOPT_RETURNTRANSFER => true,
+ CURLOPT_USERAGENT => _t('gen.freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ')',
+ CURLOPT_POSTFIELDS => 'hub.verify=sync'
+ . '&hub.mode=' . ($state ? 'subscribe' : 'unsubscribe')
+ . '&hub.topic=' . urlencode($this->selfUrl)
+ . '&hub.callback=' . urlencode($callbackUrl)
+ )
+ );
+ $response = curl_exec($ch);
+ $info = curl_getinfo($ch);
+
+ file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" .
+ 'PubSubHubbub ' . ($state ? 'subscribe' : 'unsubscribe') . ' to ' . $this->selfUrl .
+ ' with callback ' . $callbackUrl . ': ' . $info['http_code'] . ' ' . $response . "\n", FILE_APPEND);
+
+ if (!$state) { //unsubscribe
+ $hubJson['lease_end'] = time() - 60;
+ file_put_contents($hubFilename, json_encode($hubJson));
+ }
+
+ return substr($info['http_code'], 0, 1) == '2';
+ }
+ return false;
+ }
+
+ //</PubSubHubbub>
}
diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php
index c5787d25b..0f4e04620 100644
--- a/app/SQL/install.sql.mysql.php
+++ b/app/SQL/install.sql.mysql.php
@@ -57,6 +57,8 @@ CREATE TABLE IF NOT EXISTS `%1$sentry` (
ENGINE = INNODB;
INSERT IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s");
+INSERT IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("http://freshrss.org/feeds/all.atom.xml", 1, "FreshRSS.org", "http://freshrss.org/", "FreshRSS, a free, self-hostable aggregator…", 86400);
+INSERT IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("https://github.com/FreshRSS/FreshRSS/releases.atom", 1, "FreshRSS @ GitHub", "https://github.com/FreshRSS/FreshRSS/", "FreshRSS releases @ GitHub", 86400);
');
define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory');
diff --git a/app/SQL/install.sql.sqlite.php b/app/SQL/install.sql.sqlite.php
index 71bad7311..87d5cf286 100644
--- a/app/SQL/install.sql.sqlite.php
+++ b/app/SQL/install.sql.sqlite.php
@@ -55,6 +55,8 @@ $SQL_CREATE_TABLES = array(
'CREATE INDEX IF NOT EXISTS entry_lastSeen_index ON `%1$sentry`(`lastSeen`);', //v1.1.1
'INSERT OR IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s");',
+'INSERT OR IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("http://freshrss.org/feeds/all.atom.xml", 1, "FreshRSS.org", "http://freshrss.org/", "FreshRSS, a free, self-hostable aggregator…", 86400);',
+'INSERT OR IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) VALUES("https://github.com/FreshRSS/FreshRSS/releases.atom", 1, "FreshRSS releases", "https://github.com/FreshRSS/FreshRSS/", "FreshRSS releases @ GitHub", 86400);',
);
define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory');
diff --git a/app/i18n/cz/admin.php b/app/i18n/cz/admin.php
index b9ef707cf..4ca56cc37 100644
--- a/app/i18n/cz/admin.php
+++ b/app/i18n/cz/admin.php
@@ -160,8 +160,15 @@ return array(
'create' => 'Vytvořit nového uživatele',
'email_persona' => 'Email pro přihlášení<br /><small>(pro <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'language' => 'Jazyk',
+ 'number' => 'Zatím je vytvořen %d účet',
+ 'numbers' => 'Zatím je vytvořeno %d účtů',
'password_form' => 'Heslo<br /><small>(pro přihlášení webovým formulářem)</small>',
'password_format' => 'Alespoň 7 znaků',
+ 'registration' => array(
+ 'allow' => 'Povolit vytváření účtů',
+ 'help' => '0 znamená žádná omezení účtu',
+ 'number' => 'Maximální počet účtů',
+ ),
'title' => 'Správa uživatelů',
'user_list' => 'Seznam uživatelů',
'username' => 'Přihlašovací jméno',
diff --git a/app/i18n/cz/conf.php b/app/i18n/cz/conf.php
index 29fb1e4d4..823ab1ea3 100644
--- a/app/i18n/cz/conf.php
+++ b/app/i18n/cz/conf.php
@@ -72,6 +72,10 @@ return array(
),
'profile' => array(
'_' => 'Správa profilu',
+ 'delete' => array(
+ '_' => 'Smazání účtu',
+ 'warn' => 'Váš účet bude smazán spolu se všemi souvisejícími daty',
+ ),
'email_persona' => 'Email pro přihlášení<br /><small>(pro <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'password_api' => 'Password API<br /><small>(tzn. pro mobilní aplikace)</small>',
'password_form' => 'Heslo<br /><small>(pro přihlášení webovým formulářem)</small>',
@@ -84,6 +88,7 @@ return array(
'articles_per_page' => 'Počet článků na stranu',
'auto_load_more' => 'Načítat další články dole na stránce',
'auto_remove_article' => 'Po přečtení články schovat',
+ 'mark_updated_article_unread' => 'Označte aktualizované položky jako nepřečtené',
'confirm_enabled' => 'Vyžadovat potvrzení pro akci “označit vše jako přečtené”',
'display_articles_unfolded' => 'Ve výchozím stavu zobrazovat články otevřené',
'display_categories_unfolded' => 'Ve výchozím stavu zobrazovat kategorie zavřené',
diff --git a/app/i18n/cz/feedback.php b/app/i18n/cz/feedback.php
index b75a4a15a..5ba64b938 100644
--- a/app/i18n/cz/feedback.php
+++ b/app/i18n/cz/feedback.php
@@ -102,6 +102,7 @@ return array(
'_' => 'Uživatel %s byl smazán',
'error' => 'Uživatele %s nelze smazat',
),
+ 'set_registration' => 'Maximální počet účtů byl změněn',
),
'profile' => array(
'error' => 'Váš profil nelze změnit',
diff --git a/app/i18n/cz/gen.php b/app/i18n/cz/gen.php
index 7c333a9c8..138def772 100644
--- a/app/i18n/cz/gen.php
+++ b/app/i18n/cz/gen.php
@@ -21,15 +21,27 @@ return array(
'truncate' => 'Smazat všechny články',
),
'auth' => array(
+ 'email' => 'Email',
'keep_logged_in' => 'Zapamatovat přihlášení <small>(1 měsíc)</small>',
'login' => 'Login',
'login_persona' => 'Přihlášení pomocí Persona',
'login_persona_problem' => 'Problém s připojením k Persona?',
'logout' => 'Odhlášení',
- 'password' => 'Heslo',
+ 'password' => array(
+ '_' => 'Heslo',
+ 'format' => '<small>Alespoň 7 znaků</small>',
+ ),
+ 'registration' => array(
+ '_' => 'Nový účet',
+ 'ask' => 'Vytvořit účet?',
+ 'title' => 'Vytvoření účtu',
+ ),
'reset' => 'Reset přihlášení',
- 'username' => 'Uživatel',
- 'username_admin' => 'Název administrátorského účtu',
+ 'username' => array(
+ '_' => 'Uživatel',
+ 'admin' => 'Název administrátorského účtu',
+ 'format' => '<small>maximálně 16 alfanumerických znaků</small>',
+ ),
'will_reset' => 'Přihlašovací systém bude vyresetován: místo sytému Persona bude použito přihlášení formulářem.',
),
'date' => array(
diff --git a/app/i18n/cz/install.php b/app/i18n/cz/install.php
index 53257c84f..a8bc62909 100644
--- a/app/i18n/cz/install.php
+++ b/app/i18n/cz/install.php
@@ -4,7 +4,9 @@ return array(
'action' => array(
'finish' => 'Dokončit instalaci',
'fix_errors_before' => 'Chyby prosím před přechodem na další krok opravte.',
+ 'keep_install' => 'Zachovat předchozí instalaci',
'next_step' => 'Přejít na další krok',
+ 'reinstall' => 'Reinstalovat FreshRSS',
),
'auth' => array(
'email_persona' => 'Email pro přihlášení<br /><small>(pro <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
@@ -31,6 +33,7 @@ return array(
),
'check' => array(
'_' => 'Kontrola',
+ 'already_installed' => 'Zjistili jsme, že FreshRSS je již nainstalován!',
'cache' => array(
'nok' => 'Zkontrolujte oprávnění adresáře <em>./data/cache</em>. HTTP server musí mít do tohoto adresáře práva zápisu',
'ok' => 'Oprávnění adresáře cache jsou v pořádku.',
@@ -93,6 +96,9 @@ return array(
'delete_articles_after' => 'Smazat články starší než',
'fix_errors_before' => 'Chyby prosím před přechodem na další krok opravte.',
'javascript_is_better' => 'Práce s FreshRSS je příjemnější se zapnutým JavaScriptem',
+ 'js' => array(
+ 'confirm_reinstall' => 'Reinstalací FreshRSS ztratíte předchozí konfiguraci. Opravdu chcete pokračovat?',
+ ),
'language' => array(
'_' => 'Jazyk',
'choose' => 'Vyberte jazyk FreshRSS',
diff --git a/app/i18n/cz/sub.php b/app/i18n/cz/sub.php
index 78712506c..cea0541e3 100644
--- a/app/i18n/cz/sub.php
+++ b/app/i18n/cz/sub.php
@@ -37,6 +37,7 @@ return array(
'url' => 'URL kanálu',
'validator' => 'Zkontrolovat platnost kanálu',
'website' => 'URL webové stránky',
+ 'pubsubhubbub' => 'Okamžité oznámení s PubSubHubbub',
),
'import_export' => array(
'export' => 'Export',
diff --git a/app/i18n/de/admin.php b/app/i18n/de/admin.php
index c0cbf6787..68dcc2ebf 100644
--- a/app/i18n/de/admin.php
+++ b/app/i18n/de/admin.php
@@ -160,8 +160,15 @@ return array(
'create' => 'Neuen Benutzer erstellen',
'email_persona' => 'Anmelde-E-Mail-Adresse<br /><small>(für <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'language' => 'Sprache',
+ 'number' => 'Es wurde bis jetzt %d Account erstellt',
+ 'numbers' => 'Es wurden bis jetzt %d Accounts erstellt',
'password_form' => 'Passwort<br /><small>(für die Anmeldemethode per Webformular)</small>',
'password_format' => 'mindestens 7 Zeichen',
+ 'registration' => array(
+ 'allow' => 'Erlaube die Accounterstellung',
+ 'help' => '0 meint, dass es kein Account Limit gibt',
+ 'number' => 'Maximale Anzahl von Accounts',
+ ),
'title' => 'Benutzer verwalten',
'user_list' => 'Liste der Benutzer',
'username' => 'Nutzername',
diff --git a/app/i18n/de/conf.php b/app/i18n/de/conf.php
index 4a0a77ddd..c1a762f12 100644
--- a/app/i18n/de/conf.php
+++ b/app/i18n/de/conf.php
@@ -72,6 +72,10 @@ return array(
),
'profile' => array(
'_' => 'Profil-Verwaltung',
+ 'delete' => array(
+ '_' => 'Accountlöschung',
+ 'warn' => 'Dein Account und alle damit bezogenen Daten werden gelöscht.',
+ ),
'email_persona' => 'Anmelde-E-Mail-Adresse<br /><small>(für <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'password_api' => 'Passwort-API<br /><small>(z. B. für mobile Anwendungen)</small>',
'password_form' => 'Passwort<br /><small>(für die Anmeldemethode per Webformular)</small>',
diff --git a/app/i18n/de/feedback.php b/app/i18n/de/feedback.php
index 4c15aadc3..e92dacfe9 100644
--- a/app/i18n/de/feedback.php
+++ b/app/i18n/de/feedback.php
@@ -102,6 +102,7 @@ return array(
'_' => 'Der Benutzer %s ist gelöscht worden',
'error' => 'Der Benutzer %s kann nicht gelöscht werden',
),
+ 'set_registration' => 'Die maximale Anzahl von Accounts wurde aktualisiert.',
),
'profile' => array(
'error' => 'Ihr Profil kann nicht geändert werden',
diff --git a/app/i18n/de/gen.php b/app/i18n/de/gen.php
index f24a52c2e..de2d846c5 100644
--- a/app/i18n/de/gen.php
+++ b/app/i18n/de/gen.php
@@ -21,15 +21,27 @@ return array(
'truncate' => 'Alle Artikel löschen',
),
'auth' => array(
+ 'email' => 'E-Mail-Adresse',
'keep_logged_in' => 'Eingeloggt bleiben <small>(1 Monat)</small>',
'login' => 'Anmelden',
'login_persona' => 'Anmelden mit Persona',
'login_persona_problem' => 'Verbindungsproblem mit Persona?',
'logout' => 'Abmelden',
- 'password' => 'Passwort',
+ 'password' => array(
+ '_' => 'Passwort',
+ 'format' => '<small>mindestens 7 Zeichen</small>',
+ ),
+ 'registration' => array(
+ '_' => 'Neuer Account',
+ 'ask' => 'Erstelle einen Account?',
+ 'title' => 'Accounterstellung',
+ ),
'reset' => 'Zurücksetzen der Authentifizierung',
- 'username' => 'Nutzername',
- 'username_admin' => 'Administrator-Nutzername',
+ 'username' => array(
+ '_' => 'Nutzername',
+ 'admin' => 'Administrator-Nutzername',
+ 'format' => '<small>maximal 16 alphanumerische Zeichen</small>',
+ ),
'will_reset' => 'Authentifikationssystem wird zurückgesetzt: ein Formular wird anstelle von Persona benutzt.',
),
'date' => array(
diff --git a/app/i18n/de/install.php b/app/i18n/de/install.php
index a5899bb52..286272e71 100644
--- a/app/i18n/de/install.php
+++ b/app/i18n/de/install.php
@@ -4,7 +4,9 @@ return array(
'action' => array(
'finish' => 'Installation fertigstellen',
'fix_errors_before' => 'Bitte Fehler korrigieren, bevor zum nächsten Schritt gesprungen wird.',
+ 'keep_install' => 'Vorherige Installation beibehalten (Daten)',
'next_step' => 'Zum nächsten Schritt springen',
+ 'reinstall' => 'Neuinstallation von FreshRSS',
),
'auth' => array(
'email_persona' => 'Anmelde-E-Mail-Adresse<br /><small>(für <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
@@ -31,6 +33,7 @@ return array(
),
'check' => array(
'_' => 'Überprüfungen',
+ 'already_installed' => 'Wir haben festgestellt, dass FreshRSS bereits installiert wurde!',
'cache' => array(
'nok' => 'Überprüfen Sie die Berechtigungen des Verzeichnisses <em>./data/cache</em>. Der HTTP-Server muss Schreibrechte besitzen.',
'ok' => 'Die Berechtigungen des Verzeichnisses <em>./data/cache</em> sind in Ordnung.',
@@ -93,6 +96,9 @@ return array(
'delete_articles_after' => 'Entferne Artikel nach',
'fix_errors_before' => 'Bitte den Fehler korrigieren, bevor zum nächsten Schritt gesprungen wird.',
'javascript_is_better' => 'FreshRSS ist ansprechender mit aktiviertem JavaScript',
+ 'js' => array(
+ 'confirm_reinstall' => 'Du wirst deine vorherige Konfiguration (Daten) verlieren FreshRSS. Bist du sicher, dass du fortfahren willst?',
+ ),
'language' => array(
'_' => 'Sprache',
'choose' => 'Wählen Sie eine Sprache für FreshRSS',
diff --git a/app/i18n/de/sub.php b/app/i18n/de/sub.php
index 0479b8f46..0f05a5635 100644
--- a/app/i18n/de/sub.php
+++ b/app/i18n/de/sub.php
@@ -37,6 +37,7 @@ return array(
'url' => 'Feed-URL',
'validator' => 'Überprüfen Sie die Gültigkeit des Feeds',
'website' => 'Webseiten-URL',
+ 'pubsubhubbub' => 'Sofortbenachrichtigung mit PubSubHubbub',
),
'import_export' => array(
'export' => 'Exportieren',
diff --git a/app/i18n/en/admin.php b/app/i18n/en/admin.php
index 155384afd..aeea61631 100644
--- a/app/i18n/en/admin.php
+++ b/app/i18n/en/admin.php
@@ -160,8 +160,15 @@ return array(
'create' => 'Create new user',
'email_persona' => 'Login mail address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'language' => 'Language',
+ 'number' => 'There is %d account created yet',
+ 'numbers' => 'There are %d accounts created yet',
'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',
'password_format' => 'At least 7 characters',
+ 'registration' => array(
+ 'allow' => 'Allow account creation',
+ 'help' => '0 means that there is no account limit',
+ 'number' => 'Max number of accounts',
+ ),
'title' => 'Manage users',
'user_list' => 'List of users',
'username' => 'Username',
diff --git a/app/i18n/en/conf.php b/app/i18n/en/conf.php
index 42a06906d..69162932f 100644
--- a/app/i18n/en/conf.php
+++ b/app/i18n/en/conf.php
@@ -72,6 +72,10 @@ return array(
),
'profile' => array(
'_' => 'Profile management',
+ 'delete' => array(
+ '_' => 'Account deletion',
+ 'warn' => 'Your account and all the related data will be deleted.',
+ ),
'email_persona' => 'Login email address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'password_api' => 'Password API<br /><small>(e.g., for mobile apps)</small>',
'password_form' => 'Password<br /><small>(for the Web-form login method)</small>',
diff --git a/app/i18n/en/feedback.php b/app/i18n/en/feedback.php
index c9189c0d0..c9f73dc1d 100644
--- a/app/i18n/en/feedback.php
+++ b/app/i18n/en/feedback.php
@@ -102,6 +102,7 @@ return array(
'_' => 'User %s has been deleted',
'error' => 'User %s cannot be deleted',
),
+ 'set_registration' => 'The maximum amount of accounts has been updated.',
),
'profile' => array(
'error' => 'Your profile cannot be modified',
diff --git a/app/i18n/en/gen.php b/app/i18n/en/gen.php
index 6e47e0921..1feb8d6ac 100644
--- a/app/i18n/en/gen.php
+++ b/app/i18n/en/gen.php
@@ -21,15 +21,27 @@ return array(
'truncate' => 'Delete all articles',
),
'auth' => array(
+ 'email' => 'Email address',
'keep_logged_in' => 'Keep me logged in <small>(1 month)</small>',
'login' => 'Login',
'login_persona' => 'Login with Persona',
'login_persona_problem' => 'Connection problem with Persona?',
'logout' => 'Logout',
- 'password' => 'Password',
+ 'password' => array(
+ '_' => 'Password',
+ 'format' => '<small>At least 7 characters</small>',
+ ),
+ 'registration' => array(
+ '_' => 'New account',
+ 'ask' => 'Create an account?',
+ 'title' => 'Account creation',
+ ),
'reset' => 'Authentication reset',
- 'username' => 'Username',
- 'username_admin' => 'Administrator username',
+ 'username' => array(
+ '_' => 'Username',
+ 'admin' => 'Administrator username',
+ 'format' => '<small>maximum 16 alphanumeric characters</small>',
+ ),
'will_reset' => 'Authentication system will be reset: a form will be used instead of Persona.',
),
'date' => array(
diff --git a/app/i18n/en/install.php b/app/i18n/en/install.php
index d4137309a..b94fbc299 100644
--- a/app/i18n/en/install.php
+++ b/app/i18n/en/install.php
@@ -4,7 +4,9 @@ return array(
'action' => array(
'finish' => 'Complete installation',
'fix_errors_before' => 'Please fix errors before skipping to the next step.',
+ 'keep_install' => 'Keep previous installation',
'next_step' => 'Go to the next step',
+ 'reinstall' => 'Reinstall FreshRSS',
),
'auth' => array(
'email_persona' => 'Login email address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
@@ -31,6 +33,7 @@ return array(
),
'check' => array(
'_' => 'Checks',
+ 'already_installed' => 'We have detected that FreshRSS is already installed!',
'cache' => array(
'nok' => 'Check permissions on <em>./data/cache</em> directory. HTTP server must have rights to write into',
'ok' => 'Permissions on cache directory are good.',
@@ -93,6 +96,9 @@ return array(
'delete_articles_after' => 'Remove articles after',
'fix_errors_before' => 'Please fix errors before skipping to the next step.',
'javascript_is_better' => 'FreshRSS is more pleasant with JavaScript enabled',
+ 'js' => array(
+ 'confirm_reinstall' => 'You will lose your previous configuration by reinstalling FreshRSS. Are you sure you want to continue?',
+ ),
'language' => array(
'_' => 'Language',
'choose' => 'Choose a language for FreshRSS',
diff --git a/app/i18n/en/sub.php b/app/i18n/en/sub.php
index 8fe150bb2..aaaa02827 100644
--- a/app/i18n/en/sub.php
+++ b/app/i18n/en/sub.php
@@ -37,6 +37,7 @@ return array(
'url' => 'Feed URL',
'validator' => 'Check the validity of the feed',
'website' => 'Website URL',
+ 'pubsubhubbub' => 'Instant notification with PubSubHubbub',
),
'import_export' => array(
'export' => 'Export',
diff --git a/app/i18n/fr/admin.php b/app/i18n/fr/admin.php
index b740bd0d2..01e0cb3c7 100644
--- a/app/i18n/fr/admin.php
+++ b/app/i18n/fr/admin.php
@@ -160,8 +160,15 @@ return array(
'create' => 'Créer un nouvel utilisateur',
'email_persona' => 'Adresse courriel de connexion<br /><small>(pour <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'language' => 'Langue',
+ 'number' => '%d compte a déjà été créé',
+ 'numbers' => '%d comptes ont déjà été créés',
'password_form' => 'Mot de passe<br /><small>(pour connexion par formulaire)</small>',
'password_format' => '7 caractères minimum',
+ 'registration' => array(
+ 'allow' => 'Autoriser la création de comptes',
+ 'help' => 'Un chiffre de 0 signifie que l’on peut créer un nombre infini de comptes',
+ 'number' => 'Nombre max de comptes',
+ ),
'title' => 'Gestion des utilisateurs',
'user_list' => 'Liste des utilisateurs',
'username' => 'Nom d’utilisateur',
diff --git a/app/i18n/fr/conf.php b/app/i18n/fr/conf.php
index 87f9be290..6193b7a01 100644
--- a/app/i18n/fr/conf.php
+++ b/app/i18n/fr/conf.php
@@ -72,6 +72,10 @@ return array(
),
'profile' => array(
'_' => 'Gestion du profil',
+ 'delete' => array(
+ '_' => 'Suppression du compte',
+ 'warn' => 'Le compte et toutes les données associées vont être supprimées.',
+ ),
'email_persona' => 'Adresse courriel de connexion<br /><small>(pour <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
'password_api' => 'Mot de passe API<br /><small>(ex. : pour applis mobiles)</small>',
'password_form' => 'Mot de passe<br /><small>(pour connexion par formulaire)</small>',
diff --git a/app/i18n/fr/feedback.php b/app/i18n/fr/feedback.php
index e2364a251..99c193d28 100644
--- a/app/i18n/fr/feedback.php
+++ b/app/i18n/fr/feedback.php
@@ -102,6 +102,7 @@ return array(
'_' => 'L’utilisateur %s a été supprimé.',
'error' => 'L’utilisateur %s ne peut pas être supprimé.',
),
+ 'set_registration' => 'Le nombre maximal de comptes a été mis à jour.',
),
'profile' => array(
'error' => 'Votre profil n’a pas pu être mis à jour',
diff --git a/app/i18n/fr/gen.php b/app/i18n/fr/gen.php
index c81e57bf7..67d278be4 100644
--- a/app/i18n/fr/gen.php
+++ b/app/i18n/fr/gen.php
@@ -21,15 +21,27 @@ return array(
'truncate' => 'Supprimer tous les articles',
),
'auth' => array(
+ 'email' => 'Adresse courriel',
'keep_logged_in' => 'Rester connecté <small>(1 mois)</small>',
'login' => 'Connexion',
'login_persona' => 'Connexion avec Persona',
'login_persona_problem' => 'Problème de connexion à Persona ?',
'logout' => 'Déconnexion',
- 'password' => 'Mot de passe',
+ 'password' => array(
+ '_' => 'Mot de passe',
+ 'format' => '<small>7 caractères minimum</small>',
+ ),
+ 'registration' => array(
+ '_' => 'Nouveau compte',
+ 'ask' => 'Créer un compte ?',
+ 'title' => 'Création de compte',
+ ),
'reset' => 'Réinitialisation de l’authentification',
- 'username' => 'Nom d’utilisateur',
- 'username_admin' => 'Nom d’utilisateur administrateur',
+ 'username' => array(
+ '_' => 'Nom d’utilisateur',
+ 'admin' => 'Nom d’utilisateur administrateur',
+ 'format' => '<small>16 caractères alphanumériques maximum</small>',
+ ),
'will_reset' => 'Le système d’authentification va être réinitialisé : un formulaire sera utilisé à la place de Persona.',
),
'date' => array(
diff --git a/app/i18n/fr/install.php b/app/i18n/fr/install.php
index 245a20c56..0401e1bbd 100644
--- a/app/i18n/fr/install.php
+++ b/app/i18n/fr/install.php
@@ -4,7 +4,9 @@ return array(
'action' => array(
'finish' => 'Terminer l’installation',
'fix_errors_before' => 'Veuillez corriger les erreurs avant de passer à l’étape suivante.',
+ 'keep_install' => 'Garder l’ancienne configuration',
'next_step' => 'Passer à l’étape suivante',
+ 'reinstall' => 'Réinstaller FreshRSS',
),
'auth' => array(
'email_persona' => 'Adresse courriel de connexion<br /><small>(pour <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>',
@@ -31,6 +33,7 @@ return array(
),
'check' => array(
'_' => 'Vérifications',
+ 'already_installed' => 'FreshRSS semble avoir déjà été installé !',
'cache' => array(
'nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/cache</em>. Le serveur HTTP doit être capable d’écrire dedans',
'ok' => 'Les droits sur le répertoire de cache sont bons.',
@@ -93,6 +96,9 @@ return array(
'delete_articles_after' => 'Supprimer les articles après',
'fix_errors_before' => 'Veuillez corriger les erreurs avant de passer à l’étape suivante.',
'javascript_is_better' => 'FreshRSS est plus agréable à utiliser avec JavaScript activé',
+ 'js' => array(
+ 'confirm_reinstall' => 'Réinstaller FreshRSS vous fera perdre la configuration précédente. Êtes-vous sûr de vouloir continuer ?',
+ ),
'language' => array(
'_' => 'Langue',
'choose' => 'Choisissez la langue pour FreshRSS',
diff --git a/app/i18n/fr/sub.php b/app/i18n/fr/sub.php
index a3f7c4d6d..e3631eb8b 100644
--- a/app/i18n/fr/sub.php
+++ b/app/i18n/fr/sub.php
@@ -35,8 +35,9 @@ return array(
'title_add' => 'Ajouter un flux RSS',
'ttl' => 'Ne pas automatiquement rafraîchir plus souvent que',
'url' => 'URL du flux',
- 'validator' => 'Vérifier la valididé du flux',
+ 'validator' => 'Vérifier la validité du flux',
'website' => 'URL du site',
+ 'pubsubhubbub' => 'Notification instantanée par PubSubHubbub',
),
'import_export' => array(
'export' => 'Exporter',
diff --git a/app/install.php b/app/install.php
index 3cdd59d11..5a774d7c1 100644
--- a/app/install.php
+++ b/app/install.php
@@ -9,6 +9,9 @@ session_name('FreshRSS');
session_set_cookie_params(0, dirname(empty($_SERVER['REQUEST_URI']) ? '/' : dirname($_SERVER['REQUEST_URI'])), null, false, true);
session_start();
+Minz_Configuration::register('default_system', join_path(DATA_PATH, 'config.default.php'));
+Minz_Configuration::register('default_user', join_path(USERS_PATH, '_', 'config.default.php'));
+
if (isset($_GET['step'])) {
define('STEP',(int)$_GET['step']);
} else {
@@ -76,10 +79,51 @@ function saveLanguage() {
}
}
+function saveStep1() {
+ if (isset($_POST['freshrss-keep-install']) &&
+ $_POST['freshrss-keep-install'] === '1') {
+ // We want to keep our previous installation of FreshRSS
+ // so we need to make next steps valid by setting $_SESSION vars
+ // with values from the previous installation
+
+ // First, we try to get previous configurations
+ Minz_Configuration::register('system',
+ join_path(DATA_PATH, 'config.php'),
+ join_path(DATA_PATH, 'config.default.php'));
+ $system_conf = Minz_Configuration::get('system');
+
+ $current_user = $system_conf->default_user;
+ Minz_Configuration::register('user',
+ join_path(USERS_PATH, $current_user, 'config.php'),
+ join_path(USERS_PATH, '_', 'config.default.php'));
+ $user_conf = Minz_Configuration::get('user');
+
+ // Then, we set $_SESSION vars
+ $_SESSION['title'] = $system_conf->title;
+ $_SESSION['auth_type'] = $system_conf->auth_type;
+ $_SESSION['old_entries'] = $user_conf->old_entries;
+ $_SESSION['mail_login'] = $user_conf->mail_login;
+ $_SESSION['default_user'] = $current_user;
+ $_SESSION['passwordHash'] = $user_conf->passwordHash;
+
+ $db = $system_conf->db;
+ $_SESSION['bd_type'] = $db['type'];
+ $_SESSION['bd_host'] = $db['host'];
+ $_SESSION['bd_user'] = $db['user'];
+ $_SESSION['bd_password'] = $db['password'];
+ $_SESSION['bd_base'] = $db['base'];
+ $_SESSION['bd_prefix'] = $db['prefix'];
+ $_SESSION['bd_error'] = '';
+
+ header('Location: index.php?step=4');
+ }
+}
+
function saveStep2() {
+ $user_default_config = Minz_Configuration::get('default_user');
if (!empty($_POST)) {
$_SESSION['title'] = substr(trim(param('title', _t('gen.freshrss'))), 0, 25);
- $_SESSION['old_entries'] = param('old_entries', 3);
+ $_SESSION['old_entries'] = param('old_entries', $user_default_config->old_entries);
$_SESSION['auth_type'] = param('auth_type', 'form');
$_SESSION['default_user'] = substr(preg_replace('/[^a-zA-Z0-9]/', '', param('default_user', '')), 0, 16);
$_SESSION['mail_login'] = filter_var(param('mail_login', ''), FILTER_VALIDATE_EMAIL);
@@ -108,7 +152,7 @@ function saveStep2() {
$_SESSION['salt'] = sha1(uniqid(mt_rand(), true).implode('', stat(__FILE__)));
if ((!ctype_digit($_SESSION['old_entries'])) ||($_SESSION['old_entries'] < 1)) {
- $_SESSION['old_entries'] = 3;
+ $_SESSION['old_entries'] = $user_default_config->old_entries;
}
$token = '';
@@ -118,7 +162,7 @@ function saveStep2() {
$config_array = array(
'language' => $_SESSION['language'],
- 'theme' => 'Origine',
+ 'theme' => $user_default_config->theme,
'old_entries' => $_SESSION['old_entries'],
'mail_login' => $_SESSION['mail_login'],
'passwordHash' => $_SESSION['passwordHash'],
@@ -165,14 +209,14 @@ function saveStep3() {
$_SESSION['bd_user'] = $_POST['user'];
$_SESSION['bd_password'] = $_POST['pass'];
$_SESSION['bd_prefix'] = substr($_POST['prefix'], 0, 16);
- $_SESSION['bd_prefix_user'] = $_SESSION['bd_prefix'] .(empty($_SESSION['default_user']) ? '' :($_SESSION['default_user'] . '_'));
+ $_SESSION['bd_prefix_user'] = $_SESSION['bd_prefix'] . (empty($_SESSION['default_user']) ? '' : ($_SESSION['default_user'] . '_'));
}
- //TODO: load `config.default.php` as default
+ // We use dirname to remove the /i part
+ $base_url = dirname(Minz_Request::guessBaseUrl());
$config_array = array(
- 'environment' => 'production',
- 'simplepie_syslog_enabled' => true,
'salt' => $_SESSION['salt'],
+ 'base_url' => $base_url,
'title' => $_SESSION['title'],
'default_user' => $_SESSION['default_user'],
'auth_type' => $_SESSION['auth_type'],
@@ -183,7 +227,9 @@ function saveStep3() {
'password' => $_SESSION['bd_password'],
'base' => $_SESSION['bd_base'],
'prefix' => $_SESSION['bd_prefix'],
+ 'pdo_options' => array(),
),
+ 'pubsubhubbub_enabled' => server_is_public($base_url),
);
@unlink(join_path(DATA_PATH, 'config.php')); //To avoid access-rights problems
@@ -300,6 +346,33 @@ function checkStep1() {
);
}
+function freshrss_already_installed() {
+ $conf_path = join_path(DATA_PATH, 'config.php');
+ if (!file_exists($conf_path)) {
+ return false;
+ }
+
+ // A configuration file already exists, we try to load it.
+ $system_conf = null;
+ try {
+ Minz_Configuration::register('system', $conf_path);
+ $system_conf = Minz_Configuration::get('system');
+ } catch (Minz_FileNotExistException $e) {
+ return false;
+ }
+
+ // ok, the global conf exists... but what about default user conf?
+ $current_user = $system_conf->default_user;
+ try {
+ Minz_Configuration::register('user', join_path(USERS_PATH, $current_user, 'config.php'));
+ } catch (Minz_FileNotExistException $e) {
+ return false;
+ }
+
+ // ok, ok, default user exists too!
+ return true;
+}
+
function checkStep2() {
$conf = !empty($_SESSION['title']) &&
!empty($_SESSION['old_entries']) &&
@@ -427,7 +500,7 @@ function printStep0() {
<div class="form-group">
<label class="group-name" for="language"><?php echo _t('install.language'); ?></label>
<div class="group-controls">
- <select name="language" id="language">
+ <select name="language" id="language" tabindex="1" >
<?php foreach ($languages as $lang) { ?>
<option value="<?php echo $lang; ?>"<?php echo $actual == $lang ? ' selected="selected"' : ''; ?>>
<?php echo _t('gen.lang.' . $lang); ?>
@@ -439,10 +512,10 @@ function printStep0() {
<div class="form-group form-actions">
<div class="group-controls">
- <button type="submit" class="btn btn-important"><?php echo _t('gen.action.submit'); ?></button>
- <button type="reset" class="btn"><?php echo _t('gen.action.cancel'); ?></button>
+ <button type="submit" class="btn btn-important" tabindex="2" ><?php echo _t('gen.action.submit'); ?></button>
+ <button type="reset" class="btn" tabindex="3" ><?php echo _t('gen.action.cancel'); ?></button>
<?php if ($s0['all'] == 'ok') { ?>
- <a class="btn btn-important next-step" href="?step=1"><?php echo _t('install.action.next_step'); ?></a>
+ <a class="btn btn-important next-step" href="?step=1" tabindex="4" ><?php echo _t('install.action.next_step'); ?></a>
<?php } ?>
</div>
</div>
@@ -535,8 +608,38 @@ function printStep1() {
<p class="alert alert-error"><span class="alert-head"><?php echo _t('gen.short.damn'); ?></span> <?php echo _t('install.check.http_referer.nok'); ?></p>
<?php } ?>
- <?php if ($res['all'] == 'ok') { ?>
- <a class="btn btn-important next-step" href="?step=2"><?php echo _t('install.action.next_step'); ?></a>
+ <?php if (freshrss_already_installed() && $res['all'] == 'ok') { ?>
+ <p class="alert alert-warn"><span class="alert-head"><?php echo _t('gen.short.attention'); ?></span> <?php echo _t('install.check.already_installed'); ?></p>
+
+ <form action="index.php?step=1" method="post">
+ <input type="hidden" name="freshrss-keep-install" value="1" />
+ <button type="submit" class="btn btn-important next-step" tabindex="1" ><?php echo _t('install.action.keep_install'); ?></button>
+ <a class="btn btn-attention next-step confirm" data-str-confirm="<?php echo _t('install.js.confirm_reinstall'); ?>" href="?step=2" tabindex="2" ><?php echo _t('install.action.reinstall'); ?></a>
+ </form>
+
+ <script>
+ function ask_confirmation(e) {
+ var str_confirmation = this.getAttribute('data-str-confirm');
+ if (!str_confirmation) {
+ str_confirmation = "<?php echo _t('gen.js.confirm_action'); ?>";
+ }
+
+ if (!confirm(str_confirmation)) {
+ e.preventDefault();
+ }
+ }
+
+ function init_confirm() {
+ confirms = document.getElementsByClassName('confirm');
+ for (var i = 0 ; i < confirms.length ; i++) {
+ confirms[i].addEventListener('click', ask_confirmation);
+ }
+ }
+
+ init_confirm();
+ </script>
+ <?php } elseif ($res['all'] == 'ok') { ?>
+ <a class="btn btn-important next-step" href="?step=2" tabindex="1" ><?php echo _t('install.action.next_step'); ?></a>
<?php } else { ?>
<p class="alert alert-error"><?php echo _t('install.action.fix_errors_before'); ?></p>
<?php } ?>
@@ -544,6 +647,7 @@ function printStep1() {
}
function printStep2() {
+ $user_default_config = Minz_Configuration::get('default_user');
?>
<?php $s2 = checkStep2(); if ($s2['all'] == 'ok') { ?>
<p class="alert alert-success"><span class="alert-head"><?php echo _t('gen.short.ok'); ?></span> <?php echo _t('install.conf.ok'); ?></p>
@@ -557,28 +661,28 @@ function printStep2() {
<div class="form-group">
<label class="group-name" for="title"><?php echo _t('install.title'); ?></label>
<div class="group-controls">
- <input type="text" id="title" name="title" value="<?php echo isset($_SESSION['title']) ? $_SESSION['title'] : _t('gen.freshrss'); ?>" />
+ <input type="text" id="title" name="title" value="<?php echo isset($_SESSION['title']) ? $_SESSION['title'] : _t('gen.freshrss'); ?>" tabindex="1" />
</div>
</div>
<div class="form-group">
<label class="group-name" for="old_entries"><?php echo _t('install.delete_articles_after'); ?></label>
<div class="group-controls">
- <input type="number" id="old_entries" name="old_entries" required="required" min="1" max="1200" value="<?php echo isset($_SESSION['old_entries']) ? $_SESSION['old_entries'] : '3'; ?>" /> <?php echo _t('gen.date.month'); ?>
+ <input type="number" id="old_entries" name="old_entries" required="required" min="1" max="1200" value="<?php echo isset($_SESSION['old_entries']) ? $_SESSION['old_entries'] : $user_default_config->old_entries; ?>" tabindex="2" /> <?php echo _t('gen.date.month'); ?>
</div>
</div>
<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(); ?>" />
+ <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" />
</div>
</div>
<div class="form-group">
<label class="group-name" for="auth_type"><?php echo _t('install.auth.type'); ?></label>
<div class="group-controls">
- <select id="auth_type" name="auth_type" required="required" onchange="auth_type_change(true)">
+ <select id="auth_type" name="auth_type" required="required" onchange="auth_type_change(true)" tabindex="4">
<?php
function no_auth($auth_type) {
return !in_array($auth_type, array('form', 'persona', 'http_auth', 'none'));
@@ -597,7 +701,7 @@ function printStep2() {
<label class="group-name" for="passwordPlain"><?php echo _t('install.auth.password_form'); ?></label>
<div class="group-controls">
<div class="stick">
- <input type="password" id="passwordPlain" name="passwordPlain" pattern=".{7,}" autocomplete="off" <?php echo $auth_type === 'form' ? ' required="required"' : ''; ?> />
+ <input type="password" id="passwordPlain" name="passwordPlain" pattern=".{7,}" autocomplete="off" <?php echo $auth_type === 'form' ? ' required="required"' : ''; ?> tabindex="5" />
<a class="btn toggle-password" data-toggle="passwordPlain"><?php echo FreshRSS_Themes::icon('key'); ?></a>
</div>
<?php echo _i('help'); ?> <?php echo _t('install.auth.password_format'); ?>
@@ -608,7 +712,7 @@ function printStep2() {
<div class="form-group">
<label class="group-name" for="mail_login"><?php echo _t('install.auth.email_persona'); ?></label>
<div class="group-controls">
- <input type="email" id="mail_login" name="mail_login" value="<?php echo isset($_SESSION['mail_login']) ? $_SESSION['mail_login'] : ''; ?>" placeholder="alice@example.net" <?php echo $auth_type === 'persona' ? ' required="required"' : ''; ?> />
+ <input type="email" id="mail_login" name="mail_login" value="<?php echo isset($_SESSION['mail_login']) ? $_SESSION['mail_login'] : ''; ?>" placeholder="alice@example.net" <?php echo $auth_type === 'persona' ? ' required="required"' : ''; ?> tabindex="6"/>
<noscript><b><?php echo _t('gen.js.should_be_activated'); ?></b></noscript>
</div>
</div>
@@ -636,7 +740,7 @@ function printStep2() {
toggles[i].addEventListener('mouseup', hide_password);
}
- function auth_type_change(focus) {
+ function auth_type_change() {
var auth_value = document.getElementById('auth_type').value,
password_input = document.getElementById('passwordPlain'),
mail_input = document.getElementById('mail_login');
@@ -644,29 +748,23 @@ function printStep2() {
if (auth_value === 'form') {
password_input.required = true;
mail_input.required = false;
- if (focus) {
- password_input.focus();
- }
} else if (auth_value === 'persona') {
password_input.required = false;
mail_input.required = true;
- if (focus) {
- mail_input.focus();
- }
} else {
password_input.required = false;
mail_input.required = false;
}
}
- auth_type_change(false);
+ auth_type_change();
</script>
<div class="form-group form-actions">
<div class="group-controls">
- <button type="submit" class="btn btn-important"><?php echo _t('gen.action.submit'); ?></button>
- <button type="reset" class="btn"><?php echo _t('gen.action.cancel'); ?></button>
+ <button type="submit" class="btn btn-important" tabindex="7" ><?php echo _t('gen.action.submit'); ?></button>
+ <button type="reset" class="btn" tabindex="8" ><?php echo _t('gen.action.cancel'); ?></button>
<?php if ($s2['all'] == 'ok') { ?>
- <a class="btn btn-important next-step" href="?step=3"><?php echo _t('install.action.next_step'); ?></a>
+ <a class="btn btn-important next-step" href="?step=3" tabindex="9" ><?php echo _t('install.action.next_step'); ?></a>
<?php } ?>
</div>
</div>
@@ -675,6 +773,7 @@ function printStep2() {
}
function printStep3() {
+ $system_default_config = Minz_Configuration::get('default_system');
?>
<?php $s3 = checkStep3(); if ($s3['all'] == 'ok') { ?>
<p class="alert alert-success"><span class="alert-head"><?php echo _t('gen.short.ok'); ?></span> <?php echo _t('install.bdd.conf.ok'); ?></p>
@@ -687,7 +786,7 @@ function printStep3() {
<div class="form-group">
<label class="group-name" for="type"><?php echo _t('install.bdd.type'); ?></label>
<div class="group-controls">
- <select name="type" id="type" onchange="mySqlShowHide()">
+ <select name="type" id="type" onchange="mySqlShowHide()" tabindex="1" >
<?php if (extension_loaded('pdo_mysql')) {?>
<option value="mysql"
<?php echo(isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'mysql') ? 'selected="selected"' : ''; ?>>
@@ -708,35 +807,35 @@ function printStep3() {
<div class="form-group">
<label class="group-name" for="host"><?php echo _t('install.bdd.host'); ?></label>
<div class="group-controls">
- <input type="text" id="host" name="host" pattern="[0-9A-Za-z_.-]{1,64}" value="<?php echo isset($_SESSION['bd_host']) ? $_SESSION['bd_host'] : 'localhost'; ?>" />
+ <input type="text" id="host" name="host" pattern="[0-9A-Za-z_.-]{1,64}" value="<?php echo isset($_SESSION['bd_host']) ? $_SESSION['bd_host'] : $system_default_config->db['host']; ?>" tabindex="2" />
</div>
</div>
<div class="form-group">
<label class="group-name" for="user"><?php echo _t('install.bdd.username'); ?></label>
<div class="group-controls">
- <input type="text" id="user" name="user" maxlength="16" pattern="[0-9A-Za-z_.-]{1,16}" value="<?php echo isset($_SESSION['bd_user']) ? $_SESSION['bd_user'] : ''; ?>" />
+ <input type="text" id="user" name="user" maxlength="16" pattern="[0-9A-Za-z_.-]{1,16}" value="<?php echo isset($_SESSION['bd_user']) ? $_SESSION['bd_user'] : ''; ?>" tabindex="3" />
</div>
</div>
<div class="form-group">
<label class="group-name" for="pass"><?php echo _t('install.bdd.password'); ?></label>
<div class="group-controls">
- <input type="password" id="pass" name="pass" value="<?php echo isset($_SESSION['bd_password']) ? $_SESSION['bd_password'] : ''; ?>" />
+ <input type="password" id="pass" name="pass" value="<?php echo isset($_SESSION['bd_password']) ? $_SESSION['bd_password'] : ''; ?>" tabindex="4" />
</div>
</div>
<div class="form-group">
<label class="group-name" for="base"><?php echo _t('install.bdd'); ?></label>
<div class="group-controls">
- <input type="text" id="base" name="base" maxlength="64" pattern="[0-9A-Za-z_]{1,64}" value="<?php echo isset($_SESSION['bd_base']) ? $_SESSION['bd_base'] : ''; ?>" />
+ <input type="text" id="base" name="base" maxlength="64" pattern="[0-9A-Za-z_]{1,64}" value="<?php echo isset($_SESSION['bd_base']) ? $_SESSION['bd_base'] : ''; ?>" tabindex="5" />
</div>
</div>
<div class="form-group">
<label class="group-name" for="prefix"><?php echo _t('install.bdd.prefix'); ?></label>
<div class="group-controls">
- <input type="text" id="prefix" name="prefix" maxlength="16" pattern="[0-9A-Za-z_]{1,16}" value="<?php echo isset($_SESSION['bd_prefix']) ? $_SESSION['bd_prefix'] : 'freshrss_'; ?>" />
+ <input type="text" id="prefix" name="prefix" maxlength="16" pattern="[0-9A-Za-z_]{1,16}" value="<?php echo isset($_SESSION['bd_prefix']) ? $_SESSION['bd_prefix'] : $system_default_config->db['prefix']; ?>" tabindex="6" />
</div>
</div>
</div>
@@ -756,10 +855,10 @@ function printStep3() {
<div class="form-group form-actions">
<div class="group-controls">
- <button type="submit" class="btn btn-important"><?php echo _t('gen.action.submit'); ?></button>
- <button type="reset" class="btn"><?php echo _t('gen.action.cancel'); ?></button>
+ <button type="submit" class="btn btn-important" tabindex="7" ><?php echo _t('gen.action.submit'); ?></button>
+ <button type="reset" class="btn" tabindex="8" ><?php echo _t('gen.action.cancel'); ?></button>
<?php if ($s3['all'] == 'ok') { ?>
- <a class="btn btn-important next-step" href="?step=4"><?php echo _t('install.action.next_step'); ?></a>
+ <a class="btn btn-important next-step" href="?step=4" tabindex="9" ><?php echo _t('install.action.next_step'); ?></a>
<?php } ?>
</div>
</div>
@@ -770,7 +869,7 @@ function printStep3() {
function printStep4() {
?>
<p class="alert alert-success"><span class="alert-head"><?php echo _t('install.congratulations'); ?></span> <?php echo _t('install.ok'); ?></p>
- <a class="btn btn-important next-step" href="?step=5"><?php echo _t('install.action.finish'); ?></a>
+ <a class="btn btn-important next-step" href="?step=5" tabindex="1"><?php echo _t('install.action.finish'); ?></a>
<?php
}
@@ -790,6 +889,7 @@ default:
saveLanguage();
break;
case 1:
+ saveStep1();
break;
case 2:
saveStep2();
@@ -829,7 +929,7 @@ case 5:
<li class="item<?php echo STEP == 1 ? ' active' : ''; ?>"><a href="?step=1"><?php echo _t('install.check'); ?></a></li>
<li class="item<?php echo STEP == 2 ? ' active' : ''; ?>"><a href="?step=2"><?php echo _t('install.conf'); ?></a></li>
<li class="item<?php echo STEP == 3 ? ' active' : ''; ?>"><a href="?step=3"><?php echo _t('install.bdd.conf'); ?></a></li>
- <li class="item<?php echo STEP == 4 ? ' active' : ''; ?>"><a href="?step=5"><?php echo _t('install.this_is_the_end'); ?></a></li>
+ <li class="item<?php echo STEP == 4 ? ' active' : ''; ?>"><a href="?step=4"><?php echo _t('install.this_is_the_end'); ?></a></li>
</ul>
<div class="post">
diff --git a/app/views/auth/formLogin.phtml b/app/views/auth/formLogin.phtml
index 979e17349..b0083944f 100644
--- a/app/views/auth/formLogin.phtml
+++ b/app/views/auth/formLogin.phtml
@@ -1,6 +1,10 @@
<div class="prompt">
<h1><?php echo _t('gen.auth.login'); ?></h1>
+ <?php if (!max_registrations_reached()) { ?>
+ <a href="<?php echo _url('auth', 'register'); ?>"><?php echo _t('gen.auth.registration.ask'); ?></a>
+ <?php } ?>
+
<form id="crypto-form" method="post" action="<?php echo _url('auth', 'login'); ?>">
<div>
<label for="username"><?php echo _t('gen.auth.username'); ?></label>
diff --git a/app/views/auth/personaLogin.phtml b/app/views/auth/personaLogin.phtml
index 545ed2eac..c6d738bf6 100644
--- a/app/views/auth/personaLogin.phtml
+++ b/app/views/auth/personaLogin.phtml
@@ -2,6 +2,10 @@
<div class="prompt">
<h1><?php echo _t('gen.auth.login'); ?></h1>
+ <?php if (!max_registrations_reached()) { ?>
+ <a href="<?php echo _url('auth', 'register'); ?>"><?php echo _t('gen.auth.registration.ask'); ?></a>
+ <?php } ?>
+
<p>
<a class="signin btn btn-important" href="<?php echo _url('auth', 'login'); ?>">
<?php echo _i('login'); ?> <?php echo _t('gen.auth.login_persona'); ?>
diff --git a/app/views/auth/register.phtml b/app/views/auth/register.phtml
new file mode 100644
index 000000000..306679601
--- /dev/null
+++ b/app/views/auth/register.phtml
@@ -0,0 +1,38 @@
+<div class="prompt">
+ <h1><?php echo _t('gen.auth.registration'); ?></h1>
+
+ <form method="post" action="<?php echo _url('user', 'create'); ?>">
+ <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}" />
+ </div>
+
+ <div>
+ <label class="group-name" for="new_user_passwordPlain"><?php echo _t('gen.auth.password'), '<br />', _i('help'), ' ', _t('gen.auth.password.format'); ?></label>
+ <div class="stick">
+ <input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" required="required" autocomplete="off" pattern=".{7,}" />
+ <a class="btn toggle-password" data-toggle="new_user_passwordPlain"><?php echo _i('key'); ?></a>
+ </div>
+ <noscript><b><?php echo _t('gen.js.should_be_activated'); ?></b></noscript>
+ </div>
+
+ <div>
+ <label class="group-name" for="new_user_email"><?php echo _t('gen.auth.email'); ?></label>
+ <input type="email" id="new_user_email" name="new_user_email" class="extend" required="required" autocomplete="off" />
+ </div>
+
+ <div>
+ <?php
+ $redirect_url = urlencode(Minz_Url::display(
+ array('c' => 'index', 'a' => 'index'),
+ 'php', true
+ ));
+ ?>
+ <input type="hidden" name="r" value="<?php echo $redirect_url; ?>" />
+ <button type="submit" class="btn btn-important"><?php echo _t('gen.action.create'); ?></button>
+ <a class="btn" href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.cancel'); ?></a>
+ </div>
+ </form>
+
+ <p><a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('gen.freshrss.about'); ?></a></p>
+</div>
diff --git a/app/views/auth/reset.phtml b/app/views/auth/reset.phtml
index 6e9816ad3..9c820c7c8 100644
--- a/app/views/auth/reset.phtml
+++ b/app/views/auth/reset.phtml
@@ -16,7 +16,7 @@
</p>
<div>
- <label for="username"><?php echo _t('gen.auth.username_admin'); ?></label>
+ <label for="username"><?php echo _t('gen.auth.username.admin'); ?></label>
<input type="text" id="username" name="username" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" autofocus="autofocus" />
</div>
<div>
diff --git a/app/views/configure/display.phtml b/app/views/configure/display.phtml
index 91b0b8189..db6d7951c 100644
--- a/app/views/configure/display.phtml
+++ b/app/views/configure/display.phtml
@@ -107,7 +107,7 @@
</div>
<div class="form-group">
- <label class="group-name" for="posts_per_page"><?php echo _t('conf.display.notif_html5.timeout'); ?></label>
+ <label class="group-name" for="html5_notif_timeout"><?php echo _t('conf.display.notif_html5.timeout'); ?></label>
<div class="group-controls">
<input type="number" id="html5_notif_timeout" name="html5_notif_timeout" value="<?php echo FreshRSS_Context::$user_conf->html5_notif_timeout; ?>" data-leave-validation="<?php echo FreshRSS_Context::$user_conf->html5_notif_timeout; ?>"/> <?php echo _t('conf.display.notif_html5.seconds'); ?>
</div>
diff --git a/app/views/configure/reading.phtml b/app/views/configure/reading.phtml
index 1b7a101df..9c54b6bd5 100644
--- a/app/views/configure/reading.phtml
+++ b/app/views/configure/reading.phtml
@@ -9,7 +9,7 @@
<div class="form-group">
<label class="group-name" for="posts_per_page"><?php echo _t('conf.reading.articles_per_page'); ?></label>
<div class="group-controls">
- <input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo FreshRSS_Context::$user_conf->posts_per_page; ?>" min="5" max="50" data-leave-validation="<?php echo FreshRSS_Context::$user_conf->posts_per_page; ?>"/>
+ <input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo FreshRSS_Context::$user_conf->posts_per_page; ?>" min="5" max="500" data-leave-validation="<?php echo FreshRSS_Context::$user_conf->posts_per_page; ?>"/>
<?php echo _i('help'); ?> <?php echo _t('conf.reading.number_divided_when_reader'); ?>
</div>
</div>
diff --git a/app/views/feed/add.phtml b/app/views/feed/add.phtml
index 4cdd3f390..35f6fbb12 100644
--- a/app/views/feed/add.phtml
+++ b/app/views/feed/add.phtml
@@ -67,7 +67,7 @@
<div class="form-group">
<label class="group-name" for="http_user"><?php echo _t('sub.feed.auth.username'); ?></label>
<div class="group-controls">
- <input type="text" name="http_user" id="http_user" class="extend" value="<?php echo $auth['username']; ?>" autocomplete="off" />
+ <input type="text" name="http_user" id="http_user" class="extend" value="<?php echo empty($auth['username']) ? ' ' : $auth['username']; ?>" autocomplete="off" />
</div>
<label class="group-name" for="http_pass"><?php echo _t('sub.feed.auth.password'); ?></label>
diff --git a/app/views/helpers/feed/update.phtml b/app/views/helpers/feed/update.phtml
index 0b08d036c..12f485ec3 100644
--- a/app/views/helpers/feed/update.phtml
+++ b/app/views/helpers/feed/update.phtml
@@ -126,6 +126,14 @@
?></select>
</div>
</div>
+ <div class="form-group">
+ <label class="group-name" for="pubsubhubbub"><?php echo _t('sub.feed.pubsubhubbub'); ?></label>
+ <div class="group-controls">
+ <label class="checkbox" for="pubsubhubbub">
+ <input type="checkbox" name="pubsubhubbub" id="pubsubhubbub" disabled="disabled" value="1"<?php echo $this->feed->pubSubHubbubEnabled() ? ' checked="checked"' : ''; ?> />
+ </label>
+ </div>
+ </div>
<div class="form-group form-actions">
<div class="group-controls">
<button class="btn btn-important"><?php echo _t('gen.action.submit'); ?></button>
@@ -136,15 +144,15 @@
<legend><?php echo _t('sub.feed.auth.configuration'); ?></legend>
<?php $auth = $this->feed->httpAuth(false); ?>
<div class="form-group">
- <label class="group-name" for="http_user"><?php echo _t('sub.feed.auth.username'); ?></label>
+ <label class="group-name" for="http_user_feed<?php echo $this->feed->id(); ?>"><?php echo _t('sub.feed.auth.username'); ?></label>
<div class="group-controls">
- <input type="text" name="http_user" id="http_user" class="extend" value="<?php echo $auth['username']; ?>" autocomplete="off" />
+ <input type="text" name="http_user_feed<?php echo $this->feed->id(); ?>" id="http_user_feed<?php echo $this->feed->id(); ?>" class="extend" value="<?php echo empty($auth['username']) ? ' ' : $auth['username']; ?>" autocomplete="off" />
<?php echo _i('help'); ?> <?php echo _t('sub.feed.auth.help'); ?>
</div>
- <label class="group-name" for="http_pass"><?php echo _t('sub.feed.auth.password'); ?></label>
+ <label class="group-name" for="http_pass_feed<?php echo $this->feed->id(); ?>"><?php echo _t('sub.feed.auth.password'); ?></label>
<div class="group-controls">
- <input type="password" name="http_pass" id="http_pass" class="extend" value="<?php echo $auth['password']; ?>" autocomplete="off" />
+ <input type="password" name="http_pass_feed<?php echo $this->feed->id(); ?>" id="http_pass_feed<?php echo $this->feed->id(); ?>" class="extend" value="<?php echo $auth['password']; ?>" autocomplete="off" />
</div>
</div>
diff --git a/app/views/subscription/index.phtml b/app/views/subscription/index.phtml
index 331e8244e..2cfe3f33c 100644
--- a/app/views/subscription/index.phtml
+++ b/app/views/subscription/index.phtml
@@ -36,10 +36,10 @@
<li class="dropdown-header"><?php echo _t('sub.feed.auth.http'); ?></li>
<li class="input">
- <input type="text" name="http_user" id="http_user_add" autocomplete="off" placeholder="<?php echo _t('sub.feed.auth.username'); ?>" />
+ <input type="text" name="http_user" id="http_user_feed" value=" " autocomplete="off" placeholder="<?php echo _t('sub.feed.auth.username'); ?>" />
</li>
<li class="input">
- <input type="password" name="http_pass" id="http_pass_add" autocomplete="off" placeholder="<?php echo _t('sub.feed.auth.password'); ?>" />
+ <input type="password" name="http_pass" id="http_pass_feed" autocomplete="off" placeholder="<?php echo _t('sub.feed.auth.password'); ?>" />
</li>
</ul>
</div>
diff --git a/app/views/user/manage.phtml b/app/views/user/manage.phtml
index fe1b6618b..3d3bc3ddf 100644
--- a/app/views/user/manage.phtml
+++ b/app/views/user/manage.phtml
@@ -3,6 +3,34 @@
<div class="post">
<a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('gen.action.back_to_rss_feeds'); ?></a>
+ <form method="post" action="<?php echo _url('user', 'setRegistration'); ?>">
+ <legend><?php echo _t('admin.user.registration.allow'); ?></legend>
+
+ <div class="form-group">
+ <label class="group-name" for="max-registrations"><?php echo _t('admin.user.registration.number'); ?></label>
+ <div class="group-controls">
+ <input type="number" id="max-registrations" name="max-registrations" value="<?php echo FreshRSS_Context::$system_conf->limits['max_registrations']; ?>" min="0" data-leave-validation="<?php echo FreshRSS_Context::$system_conf->limits['max_registrations']; ?>"/>
+ <?php echo _i('help'); ?> <?php echo _t('admin.user.registration.help'); ?>
+ </div>
+ </div>
+
+ <div class="form-group">
+ <div class="group-controls">
+ <?php
+ $number = count(listUsers());
+ echo _t($number > 1 ? 'admin.user.numbers' : 'admin.user.number', $number);
+ ?>
+ </div>
+ </div>
+
+ <div class="form-group form-actions">
+ <div class="group-controls">
+ <button type="submit" class="btn btn-important"><?php echo _t('gen.action.submit'); ?></button>
+ <button type="reset" class="btn"><?php echo _t('gen.action.cancel'); ?></button>
+ </div>
+ </div>
+ </form>
+
<form method="post" action="<?php echo _url('user', 'create'); ?>">
<legend><?php echo _t('admin.user.create'); ?></legend>
diff --git a/app/views/user/profile.phtml b/app/views/user/profile.phtml
index c44202edd..7ae2c7ede 100644
--- a/app/views/user/profile.phtml
+++ b/app/views/user/profile.phtml
@@ -18,11 +18,11 @@
</div>
<div class="form-group">
- <label class="group-name" for="passwordPlain"><?php echo _t('conf.profile.password_form'); ?></label>
+ <label class="group-name" for="newPasswordPlain"><?php echo _t('conf.profile.password_form'); ?></label>
<div class="group-controls">
<div class="stick">
- <input type="password" id="passwordPlain" name="passwordPlain" autocomplete="off" pattern=".{7,}" <?php echo cryptAvailable() ? '' : 'disabled="disabled" '; ?>/>
- <a class="btn toggle-password" data-toggle="passwordPlain"><?php echo _i('key'); ?></a>
+ <input type="password" id="newPasswordPlain" name="newPasswordPlain" autocomplete="off" pattern=".{7,}" <?php echo cryptAvailable() ? '' : 'disabled="disabled" '; ?>/>
+ <a class="btn toggle-password" data-toggle="newPasswordPlain"><?php echo _i('key'); ?></a>
</div>
<?php echo _i('help'); ?> <?php echo _t('conf.profile.password_format'); ?>
<noscript><b><?php echo _t('gen.js.should_be_activated'); ?></b></noscript>
@@ -57,4 +57,35 @@
</div>
</div>
</form>
+
+ <?php if (!FreshRSS_Auth::hasAccess('admin')) { ?>
+ <form id="crypto-form" method="post" action="<?php echo _url('user', 'delete'); ?>">
+ <legend><?php echo _t('conf.profile.delete'); ?></legend>
+
+ <p class="alert alert-warn"><span class="alert-head"><?php echo _t('gen.short.attention'); ?></span> <?php echo _t('conf.profile.delete.warn'); ?></p>
+
+ <div class="form-group">
+ <label class="group-name" for="passwordPlain"><?php echo _t('gen.auth.password'); ?></label>
+ <div class="group-controls">
+ <input type="password" id="passwordPlain" required="required" />
+ <input type="hidden" id="challenge" name="challenge" /><br />
+ <noscript><strong><?php echo _t('gen.js.should_be_activated'); ?></strong></noscript>
+ </div>
+ </div>
+
+ <div class="form-group form-actions">
+ <div class="group-controls">
+ <?php
+ $redirect_url = urlencode(Minz_Url::display(
+ array('c' => 'user', 'a' => 'profile'),
+ 'php', true
+ ));
+ ?>
+ <input type="hidden" name="r" value="<?php echo $redirect_url; ?>" />
+ <input type="hidden" name="username" id="username" value="<?php echo Minz_Session::param('currentUser', '_'); ?>" />
+ <button type="submit" class="btn btn-attention confirm"><?php echo _t('gen.action.remove'); ?></button>
+ </div>
+ </div>
+ </form>
+ <?php } ?>
</div>
diff --git a/constants.php b/constants.php
index d49d2f065..d5f0cc06b 100644
--- a/constants.php
+++ b/constants.php
@@ -1,5 +1,5 @@
<?php
-define('FRESHRSS_VERSION', '1.1.1-beta');
+define('FRESHRSS_VERSION', '1.1.2-dev');
define('FRESHRSS_WEBSITE', 'http://freshrss.org');
define('FRESHRSS_UPDATE_WEBSITE', 'https://update.freshrss.org?v=' . FRESHRSS_VERSION);
define('FRESHRSS_WIKI', 'http://doc.freshrss.org');
@@ -19,6 +19,7 @@ define('FRESHRSS_PATH', dirname(__FILE__));
define('UPDATE_FILENAME', DATA_PATH . '/update.php');
define('USERS_PATH', DATA_PATH . '/users');
define('CACHE_PATH', DATA_PATH . '/cache');
+ define('PSHB_PATH', DATA_PATH . '/PubSubHubbub');
define('LIB_PATH', FRESHRSS_PATH . '/lib');
define('APP_PATH', FRESHRSS_PATH . '/app');
diff --git a/data/PubSubHubbub/feeds/.gitignore b/data/PubSubHubbub/feeds/.gitignore
new file mode 100644
index 000000000..150f68c80
--- /dev/null
+++ b/data/PubSubHubbub/feeds/.gitignore
@@ -0,0 +1 @@
+*/*
diff --git a/data/PubSubHubbub/feeds/README.md b/data/PubSubHubbub/feeds/README.md
new file mode 100644
index 000000000..a01a3197f
--- /dev/null
+++ b/data/PubSubHubbub/feeds/README.md
@@ -0,0 +1,7 @@
+List of canonical URLS of the various feeds users have subscribed to.
+Several users can have subscribed to the same feed.
+
+* ./base64url(canonicalUrl)/
+ * ./!hub.json
+ * ./user1.txt
+ * ./user2.txt
diff --git a/data/PubSubHubbub/keys/.gitignore b/data/PubSubHubbub/keys/.gitignore
new file mode 100644
index 000000000..2211df63d
--- /dev/null
+++ b/data/PubSubHubbub/keys/.gitignore
@@ -0,0 +1 @@
+*.txt
diff --git a/data/PubSubHubbub/keys/README.md b/data/PubSubHubbub/keys/README.md
new file mode 100644
index 000000000..bb1e57cd4
--- /dev/null
+++ b/data/PubSubHubbub/keys/README.md
@@ -0,0 +1,4 @@
+List of keys given to PubSubHubbub hubs
+
+* ./sha1(random + salt).txt
+ * base64url(canonicalUrl)
diff --git a/data/config.default.php b/data/config.default.php
index 8be203d36..a7a29b12c 100644
--- a/data/config.default.php
+++ b/data/config.default.php
@@ -1,7 +1,7 @@
<?php
-# Do not modify this file, which is only a template.
-# See `config.php` after the install process is completed.
+# Do not modify this file, which defines default values,
+# but edit `config.php` instead, after the install process is completed.
return array(
# Set to `development` to get additional error messages,
@@ -11,9 +11,11 @@ return array(
# Used to make crypto more unique. Generated during install.
'salt' => '',
- # Leave empty for most cases.
- # Ability to override the address of the FreshRSS instance,
- # used when building absolute URLs.
+ # Specify address of the FreshRSS instance,
+ # used when building absolute URLs, e.g. for PubSubHubbub.
+ # Examples:
+ # https://example.net/FreshRSS/p/
+ # https://freshrss.example.net/
'base_url' => '',
# Natural language of the user interface, e.g. `en`, `fr`.
@@ -55,6 +57,10 @@ return array(
# SimplePie, which is retrieving RSS feeds via HTTP requests.
'simplepie_syslog_enabled' => true,
+ # Enable or not support of PubSubHubbub.
+ # /!\ It should NOT be enabled if base_url is not reachable by an external server.
+ 'pubsubhubbub_enabled' => false,
+
'limits' => array(
# Duration in seconds of the SimplePie cache,
@@ -75,6 +81,25 @@ return array(
# Max number of categories for a user.
'max_categories' => 16384,
+ # Max number of accounts that anonymous users can create
+ # 0 for an unlimited number of accounts
+ # 1 is to not allow user registrations (1 is corresponding to the admin account)
+ 'max_registrations' => 1,
+ ),
+
+ # Options used by cURL when making HTTP requests, e.g. when the SimplePie library retrieves feeds.
+ # http://php.net/manual/function.curl-setopt
+ 'curl_options' => array(
+ # Options to disable SSL/TLS certificate check (e.g. for self-signed HTTPS)
+ //CURLOPT_SSL_VERIFYHOST => 0,
+ //CURLOPT_SSL_VERIFYPEER => false,
+
+ # Options to use a proxy for retrieving feeds.
+ //CURLOPT_PROXYTYPE => CURLPROXY_HTTP,
+ //CURLOPT_PROXY => '127.0.0.1',
+ //CURLOPT_PROXYPORT => 8080,
+ //CURLOPT_PROXYAUTH => CURLAUTH_BASIC,
+ //CURLOPT_PROXYUSERPWD => 'user:password',
),
'db' => array(
@@ -83,7 +108,7 @@ return array(
'type' => 'sqlite',
# MySQL host.
- 'host' => '',
+ 'host' => 'localhost',
# MySQL user.
'user' => '',
@@ -95,7 +120,13 @@ return array(
'base' => '',
# MySQL table prefix.
- 'prefix' => '',
+ 'prefix' => 'freshrss_',
+
+ 'pdo_options' => array(
+ //PDO::MYSQL_ATTR_SSL_KEY => '/path/to/client-key.pem',
+ //PDO::MYSQL_ATTR_SSL_CERT => '/path/to/client-cert.pem',
+ //PDO::MYSQL_ATTR_SSL_CA => '/path/to/ca-cert.pem',
+ ),
),
diff --git a/data/users/_/config.default.php b/data/users/_/config.default.php
index bf74ca1de..8f8ff528c 100644
--- a/data/users/_/config.default.php
+++ b/data/users/_/config.default.php
@@ -25,7 +25,7 @@ return array (
# In the case an article has changed (e.g. updated content):
# Set to `true` to mark it unread, or `false` to leave it as-is.
- 'mark_updated_article_unread' => false,
+ 'mark_updated_article_unread' => false, //TODO: -1 => ignore, 0 => update, 1 => update and mark as unread
'sort_order' => 'DESC',
'anon_access' => false,
diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php
index ab5bb4fc2..d695d4a53 100644
--- a/lib/Minz/Configuration.php
+++ b/lib/Minz/Configuration.php
@@ -39,7 +39,7 @@ class Minz_Configuration {
throw new Minz_FileNotExistException($filename);
}
- $data = @include($filename);
+ $data = include($filename);
if (is_array($data)) {
return $data;
} else {
@@ -85,11 +85,6 @@ class Minz_Configuration {
private $data = array();
/**
- * The default values, an empty array by default.
- */
- private $data_default = array();
-
- /**
* An object which help to set good values in configuration.
*/
private $configuration_setter = null;
@@ -119,21 +114,22 @@ class Minz_Configuration {
$configuration_setter = null) {
$this->namespace = $namespace;
$this->config_filename = $config_filename;
+ $this->default_filename = $default_filename;
+ $this->_configurationSetter($configuration_setter);
+
+ if (!is_null($this->default_filename)) {
+ $this->data = self::load($this->default_filename);
+ }
try {
- $this->data = self::load($this->config_filename);
+ $this->data = array_replace_recursive(
+ $this->data, self::load($this->config_filename)
+ );
} catch (Minz_FileNotExistException $e) {
- if (is_null($default_filename)) {
+ if (is_null($this->default_filename)) {
throw $e;
}
}
-
- $this->default_filename = $default_filename;
- if (!is_null($this->default_filename)) {
- $this->data_default = self::load($this->default_filename);
- }
-
- $this->_configurationSetter($configuration_setter);
}
/**
@@ -160,8 +156,6 @@ class Minz_Configuration {
return $this->data[$key];
} elseif (!is_null($default)) {
return $default;
- } elseif (isset($this->data_default[$key])) {
- return $this->data_default[$key];
} else {
Minz_Log::warning($key . ' does not exist in configuration');
return null;
diff --git a/lib/Minz/Extension.php b/lib/Minz/Extension.php
index d7ee8fe81..78b8a2725 100644
--- a/lib/Minz/Extension.php
+++ b/lib/Minz/Extension.php
@@ -168,7 +168,7 @@ class Minz_Extension {
$url = '/ext.php?f=' . $file_name_url .
'&amp;t=' . $type .
'&amp;' . $mtime;
- return Minz_Url::display($url);
+ return Minz_Url::display($url, 'php');
}
/**
diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php
index 3e8ec1f43..25999f02b 100644
--- a/lib/Minz/ModelPdo.php
+++ b/lib/Minz/ModelPdo.php
@@ -53,21 +53,19 @@ class Minz_ModelPdo {
$this->current_user = $currentUser;
self::$sharedCurrentUser = $currentUser;
+ $driver_options = isset($conf->db['pdo_options']) && is_array($conf->db['pdo_options']) ? $conf->db['pdo_options'] : array();
+
try {
$type = $db['type'];
if ($type === 'mysql') {
$string = 'mysql:host=' . $db['host']
. ';dbname=' . $db['base']
. ';charset=utf8';
- $driver_options = array(
- PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8',
- );
+ $driver_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES utf8';
$this->prefix = $db['prefix'] . $currentUser . '_';
} elseif ($type === 'sqlite') {
$string = 'sqlite:' . join_path(DATA_PATH, 'users', $currentUser, 'db.sqlite');
- $driver_options = array(
- //PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
- );
+ //$driver_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION;
$this->prefix = '';
} else {
throw new Minz_PDOConnectionException(
diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php
index b9eda82a5..bf01bc26f 100644
--- a/lib/Minz/Request.php
+++ b/lib/Minz/Request.php
@@ -85,26 +85,37 @@ class Minz_Request {
}
/**
- * Détermine la base de l'url
- * @return la base de l'url
+ * Try to guess the base URL from $_SERVER information
+ *
+ * @return the base url (e.g. http://example.com/)
*/
- public static function getBaseUrl($baseUrlSuffix = '') {
- $conf = Minz_Configuration::get('system');
- $url = $conf->base_url;
- if ($url == '' || !preg_match('%^https?://%i', $url)) {
- $url = 'http';
- $host = empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST'];
- $port = empty($_SERVER['SERVER_PORT']) ? 80 : $_SERVER['SERVER_PORT'];
- if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
- $url .= 's://' . $host . ($port == 443 ? '' : ':' . $port);
- } else {
- $url .= '://' . $host . ($port == 80 ? '' : ':' . $port);
- }
- $url .= isset($_SERVER['REQUEST_URI']) ? dirname($_SERVER['REQUEST_URI']) : '';
+ public static function guessBaseUrl() {
+ $url = 'http';
+ $host = empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] : $_SERVER['HTTP_HOST'];
+ $port = empty($_SERVER['SERVER_PORT']) ? 80 : $_SERVER['SERVER_PORT'];
+ if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
+ $url .= 's://' . $host . ($port == 443 ? '' : ':' . $port);
} else {
- $url = rtrim($url, '/\\') . $baseUrlSuffix;
+ $url .= '://' . $host . ($port == 80 ? '' : ':' . $port);
}
- return filter_var($url . '/', FILTER_SANITIZE_URL);
+ if (isset($_SERVER['REQUEST_URI'])) {
+ $path = $_SERVER['REQUEST_URI'];
+ $url .= substr($path, -1) === '/' ? substr($path, 0, -1) : dirname($path);
+ }
+
+ return filter_var($url, FILTER_SANITIZE_URL);
+ }
+
+ /**
+ * Return the base_url from configuration and add a suffix if given.
+ *
+ * @param $base_url_suffix a string to add at base_url (default: empty string)
+ * @return the base_url with a suffix.
+ */
+ public static function getBaseUrl($base_url_suffix = '') {
+ $conf = Minz_Configuration::get('system');
+ $url = rtrim($conf->base_url, '/\\') . $base_url_suffix;
+ return filter_var($url, FILTER_SANITIZE_URL);
}
/**
diff --git a/lib/Minz/Url.php b/lib/Minz/Url.php
index 879077d0f..4279b045b 100644
--- a/lib/Minz/Url.php
+++ b/lib/Minz/Url.php
@@ -25,14 +25,19 @@ class Minz_Url {
if ($absolute) {
$url_string = Minz_Request::getBaseUrl(PUBLIC_TO_INDEX_PATH);
+ if ($url_string === PUBLIC_TO_INDEX_PATH) {
+ $url_string = Minz_Request::guessBaseUrl();
+ }
} else {
$url_string = $isArray ? '.' : PUBLIC_RELATIVE;
}
if ($isArray) {
$url_string .= self::printUri($url, $encodage);
- } else {
+ } elseif ($encodage === 'html') {
$url_string = Minz_Helper::htmlspecialchars_utf8($url_string . $url);
+ } else {
+ $url_string .= $url;
}
return $url_string;
diff --git a/lib/Minz/View.php b/lib/Minz/View.php
index ff5cce4a5..8c5230ab6 100644
--- a/lib/Minz/View.php
+++ b/lib/Minz/View.php
@@ -91,6 +91,7 @@ class Minz_View {
* Construit le layout
*/
public function buildLayout () {
+ header('Content-Type: text/html; charset=UTF-8');
$this->includeFile(self::LAYOUT_PATH_NAME . self::LAYOUT_FILENAME);
}
diff --git a/lib/SimplePie/SimplePie.php b/lib/SimplePie/SimplePie.php
index 54f4c5770..6c0962a9f 100644
--- a/lib/SimplePie/SimplePie.php
+++ b/lib/SimplePie/SimplePie.php
@@ -456,7 +456,7 @@ class SimplePie
* @see SimplePie::subscribe_url()
* @access private
*/
- public $permanent_url = null; //FreshRSS
+ public $permanent_url = null;
/**
* @var object Instance of SimplePie_File to use as a feed
@@ -480,6 +480,13 @@ class SimplePie
public $timeout = 10;
/**
+ * @var array Custom curl options
+ * @see SimplePie::set_curl_options()
+ * @access private
+ */
+ public $curl_options = array();
+
+ /**
* @var bool Forces fsockopen() to be used for remote files instead
* of cURL, even if a new enough version is installed
* @see SimplePie::force_fsockopen()
@@ -754,7 +761,7 @@ class SimplePie
else
{
$this->feed_url = $this->registry->call('Misc', 'fix_protocol', array($url, 1));
- $this->permanent_url = $this->feed_url; //FreshRSS
+ $this->permanent_url = $this->feed_url;
}
}
@@ -769,7 +776,7 @@ class SimplePie
if ($file instanceof SimplePie_File)
{
$this->feed_url = $file->url;
- $this->permanent_url = $this->feed_url; //FreshRSS
+ $this->permanent_url = $this->feed_url;
$this->file =& $file;
return true;
}
@@ -807,6 +814,19 @@ class SimplePie
{
$this->timeout = (int) $timeout;
}
+
+ /**
+ * Set custom curl options
+ *
+ * This allows you to change default curl options
+ *
+ * @since 1.0 Beta 3
+ * @param array $curl_options Curl options to add to default settings
+ */
+ public function set_curl_options(array $curl_options = array())
+ {
+ $this->curl_options = $curl_options;
+ }
/**
* Force SimplePie to use fsockopen() instead of cURL
@@ -1251,7 +1271,7 @@ class SimplePie
$this->enable_exceptions = $enable;
}
- function cleanMd5($rss) //FreshRSS
+ function cleanMd5($rss)
{
return md5(preg_replace(array('#<(lastBuildDate|pubDate|updated|feedDate|dc:date|slash:comments)>[^<]+</\\1>#', '#<!--.+?-->#s'), '', $rss));
}
@@ -1297,7 +1317,7 @@ class SimplePie
// Pass whatever was set with config options over to the sanitizer.
// Pass the classes in for legacy support; new classes should use the registry instead
$this->sanitize->pass_cache_data($this->cache, $this->cache_location, $this->cache_name_function, $this->registry->get_class('Cache'));
- $this->sanitize->pass_file_data($this->registry->get_class('File'), $this->timeout, $this->useragent, $this->force_fsockopen);
+ $this->sanitize->pass_file_data($this->registry->get_class('File'), $this->timeout, $this->useragent, $this->force_fsockopen, $this->curl_options);
if (!empty($this->multifeed_url))
{
@@ -1342,7 +1362,7 @@ class SimplePie
// Fetch the data via SimplePie_File into $this->raw_data
if (($fetched = $this->fetch_data($cache)) === true)
{
- return $this->data['mtime']; //FreshRSS
+ return $this->data['mtime'];
}
elseif ($fetched === false) {
return false;
@@ -1350,7 +1370,7 @@ class SimplePie
list($headers, $sniffed) = $fetched;
- if (isset($this->data['md5'])) //FreshRSS
+ if (isset($this->data['md5']))
{
$md5 = $this->data['md5'];
}
@@ -1435,8 +1455,8 @@ class SimplePie
$this->data['headers'] = $headers;
}
$this->data['build'] = SIMPLEPIE_BUILD;
- $this->data['mtime'] = time(); //FreshRSS
- $this->data['md5'] = empty($md5) ? $this->cleanMd5($this->raw_data) : $md5; //FreshRSS
+ $this->data['mtime'] = time();
+ $this->data['md5'] = empty($md5) ? $this->cleanMd5($this->raw_data) : $md5;
// Cache the file if caching is enabled
if ($cache && !$cache->save($this))
@@ -1451,7 +1471,7 @@ class SimplePie
if (isset($parser))
{
// We have an error, just set SimplePie_Misc::error to it and quit
- $this->error = sprintf('This XML document is invalid, likely due to invalid characters. XML error: %s at line %d, column %d', $parser->get_error_string(), $parser->get_current_line(), $parser->get_current_column());
+ $this->error = sprintf('This XML document is invalid, likely due to invalid characters. XML error: %s at line %d, column %d, encoding %s, URL: %s', $parser->get_error_string(), $parser->get_current_line(), $parser->get_current_column(), $encoding, $this->feed_url);
}
else
{
@@ -1477,7 +1497,7 @@ class SimplePie
{
// Load the Cache
$this->data = $cache->load();
- if ($cache->mtime() + $this->cache_duration > time()) //FreshRSS
+ if ($cache->mtime() + $this->cache_duration > time())
{
$this->raw_data = false;
return true; // If the cache is still valid, just return true
@@ -1514,71 +1534,58 @@ class SimplePie
}
}
// Check if the cache has been updated
- else //if ($cache->mtime() + $this->cache_duration < time()) //FreshRSS removed
+ else
{
- // If we have last-modified and/or etag set
- //if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag'])) //FreshRSS removed
+ $headers = array(
+ 'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1',
+ );
+ if (isset($this->data['headers']['last-modified']))
{
- $headers = array(
- 'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1',
- );
- if (isset($this->data['headers']['last-modified']))
- {
- $headers['if-modified-since'] = $this->data['headers']['last-modified'];
- }
- if (isset($this->data['headers']['etag']))
- {
- $headers['if-none-match'] = $this->data['headers']['etag'];
- }
+ $headers['if-modified-since'] = $this->data['headers']['last-modified'];
+ }
+ if (isset($this->data['headers']['etag']))
+ {
+ $headers['if-none-match'] = $this->data['headers']['etag'];
+ }
- $file = $this->registry->create('File', array($this->feed_url, $this->timeout, 5, $headers, $this->useragent, $this->force_fsockopen)); //FreshRSS
+ $file = $this->registry->create('File', array($this->feed_url, $this->timeout, 5, $headers, $this->useragent, $this->force_fsockopen, $this->curl_options));
- if ($file->success)
+ if ($file->success)
+ {
+ if ($file->status_code === 304)
{
- if ($file->status_code === 304)
- {
- $cache->touch();
- return true;
- }
+ $cache->touch();
+ return true;
}
- else
+ }
+ else
+ {
+ $cache->touch();
+ $this->error = $file->error;
+ return !empty($this->data);
+ }
+
+ $md5 = $this->cleanMd5($file->body);
+ if ($this->data['md5'] === $md5) {
+ if ($this->syslog_enabled)
{
- $cache->touch(); //FreshRSS
- $this->error = $file->error; //FreshRSS
- return !empty($this->data); //FreshRSS
- //unset($file); //FreshRSS removed
+ syslog(LOG_DEBUG, 'SimplePie MD5 cache match for ' . SimplePie_Misc::url_remove_credentials($this->feed_url));
}
- }
- { //FreshRSS
- $md5 = $this->cleanMd5($file->body);
- if ($this->data['md5'] === $md5) {
- if ($this->syslog_enabled)
- {
- syslog(LOG_DEBUG, 'SimplePie MD5 cache match for ' . SimplePie_Misc::url_remove_credentials($this->feed_url));
- }
- $cache->touch();
- return true; //Content unchanged even though server did not send a 304
- } else {
- if ($this->syslog_enabled)
- {
- syslog(LOG_DEBUG, 'SimplePie MD5 cache no match for ' . SimplePie_Misc::url_remove_credentials($this->feed_url));
- }
- $this->data['md5'] = $md5;
+ $cache->touch();
+ return true; //Content unchanged even though server did not send a 304
+ } else {
+ if ($this->syslog_enabled)
+ {
+ syslog(LOG_DEBUG, 'SimplePie MD5 cache no match for ' . SimplePie_Misc::url_remove_credentials($this->feed_url));
}
+ $this->data['md5'] = $md5;
}
}
- //// If the cache is still valid, just return true
- //else //FreshRSS removed
- //{
- // $this->raw_data = false;
- // return true;
- //}
- }
- // If the cache is empty, delete it
+ }
+ // If the cache is empty
else
{
- //$cache->unlink(); //FreshRSS removed
- $cache->touch(); //FreshRSS
+ $cache->touch(); //To keep the date/time of the last tentative update
$this->data = array();
}
}
@@ -1594,7 +1601,7 @@ class SimplePie
$headers = array(
'Accept' => 'application/atom+xml, application/rss+xml, application/rdf+xml;q=0.9, application/xml;q=0.8, text/xml;q=0.8, text/html;q=0.7, unknown/unknown;q=0.1, application/unknown;q=0.1, */*;q=0.1',
);
- $file = $this->registry->create('File', array($this->feed_url, $this->timeout, 5, $headers, $this->useragent, $this->force_fsockopen));
+ $file = $this->registry->create('File', array($this->feed_url, $this->timeout, 5, $headers, $this->useragent, $this->force_fsockopen, $this->curl_options));
}
}
// If the file connection has an error, set SimplePie::error to that and quit
@@ -1611,15 +1618,15 @@ class SimplePie
if (!$locate->is_feed($file))
{
- $copyStatusCode = $file->status_code; //FreshRSS
- $copyContentType = $file->headers['content-type']; //FreshRSS
+ $copyStatusCode = $file->status_code;
+ $copyContentType = $file->headers['content-type'];
// We need to unset this so that if SimplePie::set_file() has been called that object is untouched
unset($file);
try
{
if (!($file = $locate->find($this->autodiscovery, $this->all_discovered_feeds)))
{
- $this->error = "A feed could not be found at `$this->feed_url`; the status code is `$copyStatusCode` and content-type is `$copyContentType`"; //FreshRSS
+ $this->error = "A feed could not be found at `$this->feed_url`; the status code is `$copyStatusCode` and content-type is `$copyContentType`";
$this->registry->call('Misc', 'error', array($this->error, E_USER_NOTICE, __FILE__, __LINE__));
return false;
}
@@ -1634,8 +1641,8 @@ class SimplePie
if ($cache)
{
$this->data = array('url' => $this->feed_url, 'feed_url' => $file->url, 'build' => SIMPLEPIE_BUILD);
- $this->data['mtime'] = time(); //FreshRSS
- $this->data['md5'] = empty($md5) ? $this->cleanMd5($file->body) : $md5; //FreshRSS
+ $this->data['mtime'] = time();
+ $this->data['md5'] = empty($md5) ? $this->cleanMd5($file->body) : $md5;
if (!$cache->save($this))
{
trigger_error("$this->cache_location is not writeable. Make sure you've set the correct relative or absolute path, and that the location is server-writable.", E_USER_WARNING);
@@ -1648,7 +1655,7 @@ class SimplePie
}
$this->raw_data = $file->body;
- $this->permanent_url = $file->permanent_url; //FreshRSS
+ $this->permanent_url = $file->permanent_url;
$headers = $file->headers;
$sniffer = $this->registry->create('Content_Type_Sniffer', array(&$file));
$sniffed = $sniffer->get_type();
@@ -1852,7 +1859,7 @@ class SimplePie
*/
public function subscribe_url($permanent = false)
{
- if ($permanent) //FreshRSS
+ if ($permanent)
{
if ($this->permanent_url !== null)
{
diff --git a/lib/SimplePie/SimplePie/Cache/File.php b/lib/SimplePie/SimplePie/Cache/File.php
index cb4b528c4..72e75a4b6 100644
--- a/lib/SimplePie/SimplePie/Cache/File.php
+++ b/lib/SimplePie/SimplePie/Cache/File.php
@@ -136,11 +136,7 @@ class SimplePie_Cache_File implements SimplePie_Cache_Base
*/
public function mtime()
{
- //if (file_exists($this->name)) //FreshRSS removed
- {
- return @filemtime($this->name); //FreshRSS
- }
- //return false; //FreshRSS removed
+ return @filemtime($this->name);
}
/**
@@ -150,11 +146,7 @@ class SimplePie_Cache_File implements SimplePie_Cache_Base
*/
public function touch()
{
- //if (file_exists($this->name)) //FreshRSS removed
- {
- return @touch($this->name); //FreshRSS
- }
- //return false; //FreshRSS removed
+ return @touch($this->name);
}
/**
diff --git a/lib/SimplePie/SimplePie/Decode/HTML/Entities.php b/lib/SimplePie/SimplePie/Decode/HTML/Entities.php
index cde06c884..46b3a1dff 100644
--- a/lib/SimplePie/SimplePie/Decode/HTML/Entities.php
+++ b/lib/SimplePie/SimplePie/Decode/HTML/Entities.php
@@ -169,7 +169,6 @@ class SimplePie_Decode_HTML_Entities
case "\x09":
case "\x0A":
case "\x0B":
- case "\x0B":
case "\x0C":
case "\x20":
case "\x3C":
diff --git a/lib/SimplePie/SimplePie/File.php b/lib/SimplePie/SimplePie/File.php
index 1f9e3d502..45994d102 100644
--- a/lib/SimplePie/SimplePie/File.php
+++ b/lib/SimplePie/SimplePie/File.php
@@ -66,7 +66,7 @@ class SimplePie_File
var $method = SIMPLEPIE_FILE_SOURCE_NONE;
var $permanent_url; //FreshRSS
- public function __construct($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false, $syslog_enabled = SIMPLEPIE_SYSLOG)
+ public function __construct($url, $timeout = 10, $redirects = 5, $headers = null, $useragent = null, $force_fsockopen = false, $curl_options = array(), $syslog_enabled = SIMPLEPIE_SYSLOG)
{
if (class_exists('idna_convert'))
{
@@ -75,7 +75,7 @@ class SimplePie_File
$url = SimplePie_Misc::compress_parse_url($parsed['scheme'], $idn->encode($parsed['authority']), $parsed['path'], $parsed['query'], $parsed['fragment']);
}
$this->url = $url;
- $this->permanent_url = $url; //FreshRSS
+ $this->permanent_url = $url;
$this->useragent = $useragent;
if (preg_match('/^http(s)?:\/\//i', $url))
{
@@ -113,12 +113,15 @@ class SimplePie_File
curl_setopt($fp, CURLOPT_REFERER, $url);
curl_setopt($fp, CURLOPT_USERAGENT, $useragent);
curl_setopt($fp, CURLOPT_HTTPHEADER, $headers2);
- curl_setopt($fp, CURLOPT_SSL_VERIFYPEER, false); //FreshRSS
if (!ini_get('open_basedir') && !ini_get('safe_mode') && version_compare(SimplePie_Misc::get_curl_version(), '7.15.2', '>='))
{
curl_setopt($fp, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($fp, CURLOPT_MAXREDIRS, $redirects);
}
+ foreach ($curl_options as $curl_param => $curl_value)
+ {
+ curl_setopt($fp, $curl_param, $curl_value);
+ }
$this->headers = curl_exec($fp);
if (curl_errno($fp) === 23 || curl_errno($fp) === 61)
@@ -149,7 +152,7 @@ class SimplePie_File
$location = SimplePie_Misc::absolutize_url($this->headers['location'], $url);
$previousStatusCode = $this->status_code;
$this->__construct($location, $timeout, $redirects, $headers, $useragent, $force_fsockopen);
- $this->permanent_url = ($previousStatusCode == 301) ? $location : $url; //FreshRSS
+ $this->permanent_url = ($previousStatusCode == 301) ? $location : $url;
return;
}
}
diff --git a/lib/SimplePie/SimplePie/Item.php b/lib/SimplePie/SimplePie/Item.php
index 7bd96c15f..27e93456e 100644
--- a/lib/SimplePie/SimplePie/Item.php
+++ b/lib/SimplePie/SimplePie/Item.php
@@ -406,6 +406,30 @@ class SimplePie_Item
return null;
}
}
+
+ /**
+ * Get the media:thumbnail of the item
+ *
+ * Uses `<media:thumbnail>`
+ *
+ *
+ * @return array|null
+ */
+ public function get_thumbnail()
+ {
+ if (!isset($this->data['thumbnail']))
+ {
+ if ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_MEDIARSS, 'thumbnail'))
+ {
+ $this->data['thumbnail'] = $return[0]['attribs'][''];
+ }
+ else
+ {
+ $this->data['thumbnail'] = null;
+ }
+ }
+ return $this->data['thumbnail'];
+ }
/**
* Get a category for the item
@@ -738,31 +762,31 @@ class SimplePie_Item
{
$this->data['date']['raw'] = $return[0]['data'];
}
- elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated'))
+ elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'pubDate'))
{
$this->data['date']['raw'] = $return[0]['data'];
}
- elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'issued'))
+ elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'date'))
{
$this->data['date']['raw'] = $return[0]['data'];
}
- elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'created'))
+ elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'date'))
{
$this->data['date']['raw'] = $return[0]['data'];
}
- elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'modified'))
+ elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated'))
{
$this->data['date']['raw'] = $return[0]['data'];
}
- elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_RSS_20, 'pubDate'))
+ elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'issued'))
{
$this->data['date']['raw'] = $return[0]['data'];
}
- elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_11, 'date'))
+ elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'created'))
{
$this->data['date']['raw'] = $return[0]['data'];
}
- elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_DC_10, 'date'))
+ elseif ($return = $this->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_03, 'modified'))
{
$this->data['date']['raw'] = $return[0]['data'];
}
@@ -2733,7 +2757,9 @@ class SimplePie_Item
{
foreach ($content['child'][SIMPLEPIE_NAMESPACE_MEDIARSS]['thumbnail'] as $thumbnail)
{
- $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+ if (isset($thumbnail['attribs']['']['url'])) {
+ $thumbnails[] = $this->sanitize($thumbnail['attribs']['']['url'], SIMPLEPIE_CONSTRUCT_IRI);
+ }
}
if (is_array($thumbnails))
{
diff --git a/lib/SimplePie/SimplePie/Locator.php b/lib/SimplePie/SimplePie/Locator.php
index 4e5f7c1ca..ba4a843b0 100644
--- a/lib/SimplePie/SimplePie/Locator.php
+++ b/lib/SimplePie/SimplePie/Locator.php
@@ -148,7 +148,7 @@ class SimplePie_Locator
{
$sniffer = $this->registry->create('Content_Type_Sniffer', array($file));
$sniffed = $sniffer->get_type();
- if (in_array($sniffed, array('application/rss+xml', 'application/rdf+xml', 'text/rdf', 'application/atom+xml', 'text/xml', 'application/xml', 'application/x-rss+xml'))) //FreshRSS
+ if (in_array($sniffed, array('application/rss+xml', 'application/rdf+xml', 'text/rdf', 'application/atom+xml', 'text/xml', 'application/xml', 'application/x-rss+xml')))
{
return true;
}
diff --git a/lib/SimplePie/SimplePie/Misc.php b/lib/SimplePie/SimplePie/Misc.php
index 956a284cb..9e7ac4fa8 100644
--- a/lib/SimplePie/SimplePie/Misc.php
+++ b/lib/SimplePie/SimplePie/Misc.php
@@ -79,8 +79,8 @@ class SimplePie_Misc
public static function absolutize_url($relative, $base)
{
- if (substr($relative, 0, 2) === '//') //FreshRSS: disable absolutize_url for "//www.example.net" which will pick HTTP or HTTPS automatically
- {
+ if (substr($relative, 0, 2) === '//')
+ {//Allow protocol-relative URLs "//www.example.net" which will pick HTTP or HTTPS automatically
return $relative;
}
$iri = SimplePie_IRI::absolutize(new SimplePie_IRI($base), $relative);
@@ -128,7 +128,7 @@ class SimplePie_Misc
{
$attribs[$j][2] = $attribs[$j][1];
}
- $return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8'); //FreshRSS
+ $return[$i]['attribs'][strtolower($attribs[$j][1])]['data'] = SimplePie_Misc::entities_decode(end($attribs[$j]), 'UTF-8');
}
}
}
@@ -142,7 +142,7 @@ class SimplePie_Misc
foreach ($element['attribs'] as $key => $value)
{
$key = strtolower($key);
- $full .= " $key=\"" . htmlspecialchars($value['data'], ENT_COMPAT, 'UTF-8') . '"'; //FreshRSS
+ $full .= " $key=\"" . htmlspecialchars($value['data'], ENT_COMPAT, 'UTF-8') . '"';
}
if ($element['self_closing'])
{
diff --git a/lib/SimplePie/SimplePie/Parse/Date.php b/lib/SimplePie/SimplePie/Parse/Date.php
index ba7c0703e..50bb5cffa 100644
--- a/lib/SimplePie/SimplePie/Parse/Date.php
+++ b/lib/SimplePie/SimplePie/Parse/Date.php
@@ -173,7 +173,7 @@ class SimplePie_Parse_Date
'aug' => 8,
'august' => 8,
'sep' => 9,
- 'september' => 8,
+ 'september' => 9,
'oct' => 10,
'october' => 10,
'nov' => 11,
@@ -331,8 +331,8 @@ class SimplePie_Parse_Date
'CCT' => 23400,
'CDT' => -18000,
'CEDT' => 7200,
- 'CEST' => 7200, //FreshRSS
'CET' => 3600,
+ 'CEST' => 7200,
'CGST' => -7200,
'CGT' => -10800,
'CHADT' => 49500,
@@ -721,7 +721,7 @@ class SimplePie_Parse_Date
{
$output .= substr($string, $position, $pos - $position);
$position = $pos + 1;
- if ($string[$pos - 1] !== '\\')
+ if ($pos === 0 || $string[$pos - 1] !== '\\')
{
$depth++;
while ($depth && $position < $length)
diff --git a/lib/SimplePie/SimplePie/Registry.php b/lib/SimplePie/SimplePie/Registry.php
index bd9c1f535..dac55e34e 100755
--- a/lib/SimplePie/SimplePie/Registry.php
+++ b/lib/SimplePie/SimplePie/Registry.php
@@ -113,7 +113,7 @@ class SimplePie_Registry
*/
public function register($type, $class, $legacy = false)
{
- if (!is_subclass_of($class, $this->default[$type]))
+ if (!@is_subclass_of($class, $this->default[$type]))
{
return false;
}
@@ -222,4 +222,4 @@ class SimplePie_Registry
$result = call_user_func_array(array($class, $method), $parameters);
return $result;
}
-} \ No newline at end of file
+}
diff --git a/lib/SimplePie/SimplePie/Sanitize.php b/lib/SimplePie/SimplePie/Sanitize.php
index e7c9f925f..a6863ec03 100644
--- a/lib/SimplePie/SimplePie/Sanitize.php
+++ b/lib/SimplePie/SimplePie/Sanitize.php
@@ -249,7 +249,7 @@ class SimplePie_Sanitize
{
if ($type & SIMPLEPIE_CONSTRUCT_MAYBE_HTML)
{
- $data = htmlspecialchars_decode($data, ENT_QUOTES); //FreshRSS
+ $data = htmlspecialchars_decode($data, ENT_QUOTES);
if (preg_match('/(&(#(x[0-9a-fA-F]+|[0-9]+)|[a-zA-Z0-9]+)|<\/[A-Za-z][^\x09\x0A\x0B\x0C\x0D\x20\x2F\x3E]*' . SIMPLEPIE_PCRE_HTML_ATTRIBUTE . '>)/', $data))
{
$type |= SIMPLEPIE_CONSTRUCT_HTML;
@@ -280,7 +280,7 @@ class SimplePie_Sanitize
$document->loadHTML($data);
restore_error_handler();
- $xpath = new DOMXPath($document); //FreshRSS
+ $xpath = new DOMXPath($document);
// Strip comments
if ($this->strip_comments)
diff --git a/lib/lib_rss.php b/lib/lib_rss.php
index 6342011c8..2a23fca45 100644
--- a/lib/lib_rss.php
+++ b/lib/lib_rss.php
@@ -83,6 +83,33 @@ function checkUrl($url) {
}
}
+
+/**
+ * Test if a given server address is publicly accessible.
+ *
+ * Note: for the moment it tests only if address is corresponding to a
+ * localhost address.
+ *
+ * @param $address the address to test, can be an IP or a URL.
+ * @return true if server is accessible, false else.
+ * @todo improve test with a more valid technique (e.g. test with an external server?)
+ */
+function server_is_public($address) {
+ $host = parse_url($address, PHP_URL_HOST);
+
+ $is_public = !in_array($host, array(
+ '127.0.0.1',
+ 'localhost',
+ 'localhost.localdomain',
+ '[::1]',
+ 'localhost6',
+ 'localhost6.localdomain6',
+ ));
+
+ return $is_public;
+}
+
+
function format_number($n, $precision = 0) {
// number_format does not seem to be Unicode-compatible
return str_replace(' ', ' ', //Espace fine insécable
@@ -143,6 +170,7 @@ function customSimplePie() {
$simplePie->set_cache_location(CACHE_PATH);
$simplePie->set_cache_duration($limits['cache_duration']);
$simplePie->set_timeout($limits['timeout']);
+ $simplePie->set_curl_options($system_conf->curl_options);
$simplePie->strip_htmltags(array(
'base', 'blink', 'body', 'doctype', 'embed',
'font', 'form', 'frame', 'frameset', 'html',
@@ -195,17 +223,27 @@ function sanitizeHTML($data, $base = '') {
/* permet de récupérer le contenu d'un article pour un flux qui n'est pas complet */
function get_content_by_parsing ($url, $path) {
- require_once (LIB_PATH . '/lib_phpQuery.php');
+ require_once(LIB_PATH . '/lib_phpQuery.php');
Minz_Log::notice('FreshRSS GET ' . SimplePie_Misc::url_remove_credentials($url));
- $html = file_get_contents ($url);
+ $html = file_get_contents($url);
if ($html) {
- $doc = phpQuery::newDocument ($html);
- $content = $doc->find ($path);
+ $doc = phpQuery::newDocument($html);
+ $content = $doc->find($path);
+
+ foreach (pq('img[data-src]') as $img) {
+ $imgP = pq($img);
+ $dataSrc = $imgP->attr('data-src');
+ if (strlen($dataSrc) > 4) {
+ $imgP->attr('src', $dataSrc);
+ $imgP->removeAttr('data-src');
+ }
+ }
+
return sanitizeHTML($content->__toString(), $url);
} else {
- throw new Exception ();
+ throw new Exception();
}
}
@@ -256,6 +294,22 @@ function listUsers() {
/**
+ * Return if the maximum number of registrations has been reached.
+ *
+ * Note a max_regstrations of 0 means there is no limit.
+ *
+ * @return true if number of users >= max registrations, false else.
+ */
+function max_registrations_reached() {
+ $system_conf = Minz_Configuration::get('system');
+ $limit_registrations = $system_conf->limits['max_registrations'];
+ $number_accounts = count(listUsers());
+
+ return $limit_registrations > 0 && $number_accounts >= $limit_registrations;
+}
+
+
+/**
* Register and return the configuration for a given user.
*
* Note this function has been created to generate temporary configuration
@@ -446,3 +500,12 @@ function array_push_unique(&$array, $value) {
function array_remove(&$array, $value) {
$array = array_diff($array, array($value));
}
+
+//RFC 4648
+function base64url_encode($data) {
+ return strtr(rtrim(base64_encode($data), '='), '+/', '-_');
+}
+//RFC 4648
+function base64url_decode($data) {
+ return base64_decode(strtr($data, '-_', '+/'));
+}
diff --git a/p/api/pshb.php b/p/api/pshb.php
new file mode 100644
index 000000000..4bb4694b3
--- /dev/null
+++ b/p/api/pshb.php
@@ -0,0 +1,133 @@
+<?php
+require('../../constants.php');
+require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader
+
+define('MAX_PAYLOAD', 3145728);
+
+header('Content-Type: text/plain; charset=UTF-8');
+
+function logMe($text) {
+ file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND);
+}
+
+$ORIGINAL_INPUT = file_get_contents('php://input', false, null, -1, MAX_PAYLOAD);
+
+//logMe(print_r(array('_SERVER' => $_SERVER, '_GET' => $_GET, '_POST' => $_POST, 'INPUT' => $ORIGINAL_INPUT), true));
+
+$key = isset($_GET['k']) ? substr($_GET['k'], 0, 128) : '';
+if (!ctype_xdigit($key)) {
+ header('HTTP/1.1 422 Unprocessable Entity');
+ die('Invalid feed key format!');
+}
+chdir(PSHB_PATH);
+$canonical64 = @file_get_contents('keys/' . $key . '.txt');
+if ($canonical64 === false) {
+ header('HTTP/1.1 404 Not Found');
+ logMe('Error: Feed key not found!: ' . $key);
+ die('Feed key not found!');
+}
+$canonical64 = trim($canonical64);
+if (!preg_match('/^[A-Za-z0-9_-]+$/D', $canonical64)) {
+ header('HTTP/1.1 500 Internal Server Error');
+ logMe('Error: Invalid key reference!: ' . $canonical64);
+ die('Invalid key reference!');
+}
+$hubFile = @file_get_contents('feeds/' . $canonical64 . '/!hub.json');
+if ($hubFile === false) {
+ header('HTTP/1.1 404 Not Found');
+ //@unlink('keys/' . $key . '.txt');
+ logMe('Error: Feed info not found!: ' . $canonical64);
+ die('Feed info not found!');
+}
+$hubJson = json_decode($hubFile, true);
+if (!$hubJson || empty($hubJson['key']) || $hubJson['key'] !== $key) {
+ header('HTTP/1.1 500 Internal Server Error');
+ logMe('Error: Invalid key cross-check!: ' . $key);
+ die('Invalid key cross-check!');
+}
+chdir('feeds/' . $canonical64);
+$users = glob('*.txt', GLOB_NOSORT);
+if (empty($users)) {
+ header('HTTP/1.1 410 Gone');
+ logMe('Error: Nobody is subscribed to this feed anymore!: ' . $canonical64);
+ die('Nobody is subscribed to this feed anymore!');
+}
+
+if (!empty($_REQUEST['hub_mode']) && $_REQUEST['hub_mode'] === 'subscribe') {
+ $leaseSeconds = empty($_REQUEST['hub_lease_seconds']) ? 0 : intval($_REQUEST['hub_lease_seconds']);
+ if ($leaseSeconds > 60) {
+ $hubJson['lease_end'] = time() + $leaseSeconds;
+ } else {
+ unset($hubJson['lease_end']);
+ }
+ $hubJson['lease_start'] = time();
+ if (!isset($hubJson['error'])) {
+ $hubJson['error'] = true; //Do not assume that PubSubHubbub works until the first successul push
+ }
+ file_put_contents('./!hub.json', json_encode($hubJson));
+ exit(isset($_REQUEST['hub_challenge']) ? $_REQUEST['hub_challenge'] : '');
+}
+
+if ($ORIGINAL_INPUT == '') {
+ header('HTTP/1.1 422 Unprocessable Entity');
+ die('Missing XML payload!');
+}
+
+Minz_Configuration::register('system', DATA_PATH . '/config.php', DATA_PATH . '/config.default.php');
+$system_conf = Minz_Configuration::get('system');
+$system_conf->auth_type = 'none'; // avoid necessity to be logged in (not saved!)
+Minz_Translate::init('en');
+Minz_Request::_param('ajax', true);
+$feedController = new FreshRSS_feed_Controller();
+
+$simplePie = customSimplePie();
+$simplePie->set_raw_data($ORIGINAL_INPUT);
+$simplePie->init();
+unset($ORIGINAL_INPUT);
+
+$links = $simplePie->get_links('self');
+$self = isset($links[0]) ? $links[0] : null;
+
+if ($self !== base64url_decode($canonical64)) {
+ //header('HTTP/1.1 422 Unprocessable Entity');
+ logMe('Warning: Self URL [' . $self . '] does not match registered canonical URL!: ' . base64url_decode($canonical64));
+ //die('Self URL does not match registered canonical URL!');
+ $self = base64url_decode($canonical64);
+}
+Minz_Request::_param('url', $self);
+
+$nb = 0;
+foreach ($users as $userFilename) {
+ $username = basename($userFilename, '.txt');
+ if (!file_exists(USERS_PATH . '/' . $username . '/config.php')) {
+ break;
+ }
+
+ try {
+ Minz_Session::_param('currentUser', $username);
+ Minz_Configuration::register('user',
+ join_path(USERS_PATH, $username, 'config.php'),
+ join_path(USERS_PATH, '_', 'config.default.php'));
+ FreshRSS_Context::init();
+ if ($feedController->actualizeAction($simplePie) > 0) {
+ $nb++;
+ }
+ } catch (Exception $e) {
+ logMe('Error: ' . $e->getMessage());
+ }
+}
+
+$simplePie->__destruct();
+unset($simplePie);
+
+if ($nb === 0) {
+ header('HTTP/1.1 410 Gone');
+ logMe('Error: Nobody is subscribed to this feed anymore after all!: ' . $self);
+ die('Nobody is subscribed to this feed anymore after all!');
+} elseif (!empty($hubJson['error'])) {
+ $hubJson['error'] = false;
+ file_put_contents('./!hub.json', json_encode($hubJson));
+}
+
+logMe('PubSubHubbub ' . $self . ' done: ' . $nb);
+exit('Done: ' . $nb . "\n");