aboutsummaryrefslogtreecommitdiff
path: root/app/Controllers
diff options
context:
space:
mode:
Diffstat (limited to 'app/Controllers')
-rw-r--r--app/Controllers/authController.php14
-rwxr-xr-xapp/Controllers/configureController.php59
-rwxr-xr-xapp/Controllers/entryController.php22
-rw-r--r--app/Controllers/extensionController.php4
-rwxr-xr-xapp/Controllers/feedController.php28
-rw-r--r--app/Controllers/importExportController.php22
-rwxr-xr-xapp/Controllers/indexController.php21
-rwxr-xr-xapp/Controllers/javascriptController.php2
-rw-r--r--app/Controllers/subscriptionController.php67
-rw-r--r--app/Controllers/tagController.php4
-rw-r--r--app/Controllers/updateController.php2
-rw-r--r--app/Controllers/userController.php276
12 files changed, 408 insertions, 113 deletions
diff --git a/app/Controllers/authController.php b/app/Controllers/authController.php
index ca44b1a96..e2e1aaa22 100644
--- a/app/Controllers/authController.php
+++ b/app/Controllers/authController.php
@@ -169,10 +169,6 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
return;
}
- if (!function_exists('password_verify')) {
- include_once(LIB_PATH . '/password_compat.php');
- }
-
$s = $conf->passwordHash;
$ok = password_verify($password, $s);
unset($password);
@@ -203,12 +199,22 @@ class FreshRSS_auth_Controller extends Minz_ActionController {
/**
* This action gives possibility to a user to create an account.
+ *
+ * The user is redirected to the home if he's connected.
+ *
+ * A 403 is sent if max number of registrations is reached.
*/
public function registerAction() {
+ if (FreshRSS_Auth::hasAccess()) {
+ Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true);
+ }
+
if (max_registrations_reached()) {
Minz_Error::error(403);
}
+ $this->view->show_tos_checkbox = file_exists(join_path(DATA_PATH, 'tos.html'));
+ $this->view->show_email_field = FreshRSS_Context::$system_conf->force_email_validation;
Minz_View::prependTitle(_t('gen.auth.registration.title') . ' · ');
}
}
diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php
index 6d3c4dcce..b38d3289a 100755
--- a/app/Controllers/configureController.php
+++ b/app/Controllers/configureController.php
@@ -48,6 +48,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
FreshRSS_Context::$user_conf->topline_favorite = Minz_Request::param('topline_favorite', false);
FreshRSS_Context::$user_conf->topline_date = Minz_Request::param('topline_date', false);
FreshRSS_Context::$user_conf->topline_link = Minz_Request::param('topline_link', false);
+ FreshRSS_Context::$user_conf->topline_display_authors = Minz_Request::param('topline_display_authors', false);
FreshRSS_Context::$user_conf->bottomline_read = Minz_Request::param('bottomline_read', false);
FreshRSS_Context::$user_conf->bottomline_favorite = Minz_Request::param('bottomline_favorite', false);
FreshRSS_Context::$user_conf->bottomline_sharing = Minz_Request::param('bottomline_sharing', false);
@@ -166,8 +167,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
* tab and up.
*/
public function shortcutAction() {
- global $SHORTCUT_KEYS;
- $this->view->list_keys = $SHORTCUT_KEYS;
+ $this->view->list_keys = SHORTCUT_KEYS;
if (Minz_Request::isPost()) {
FreshRSS_Context::$user_conf->shortcuts = validateShortcutList(Minz_Request::param('shortcuts'));
@@ -196,9 +196,31 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
*/
public function archivingAction() {
if (Minz_Request::isPost()) {
- FreshRSS_Context::$user_conf->old_entries = Minz_Request::param('old_entries', 3);
- FreshRSS_Context::$user_conf->keep_history_default = Minz_Request::param('keep_history_default', 0);
+ if (!Minz_Request::paramBoolean('enable_keep_max')) {
+ $keepMax = false;
+ } elseif (!$keepMax = Minz_Request::param('keep_max')) {
+ $keepMax = FreshRSS_Feed::ARCHIVING_RETENTION_COUNT_LIMIT;
+ }
+ if ($enableRetentionPeriod = Minz_Request::paramBoolean('enable_keep_period')) {
+ $keepPeriod = FreshRSS_Feed::ARCHIVING_RETENTION_PERIOD;
+ if (is_numeric(Minz_Request::param('keep_period_count')) && preg_match('/^PT?1[YMWDH]$/', Minz_Request::param('keep_period_unit'))) {
+ $keepPeriod = str_replace('1', Minz_Request::param('keep_period_count'), Minz_Request::param('keep_period_unit'));
+ }
+ } else {
+ $keepPeriod = false;
+ }
+
FreshRSS_Context::$user_conf->ttl_default = Minz_Request::param('ttl_default', FreshRSS_Feed::TTL_DEFAULT);
+ FreshRSS_Context::$user_conf->archiving = [
+ 'keep_period' => $keepPeriod,
+ 'keep_max' => $keepMax,
+ 'keep_min' => Minz_Request::param('keep_min_default', 0),
+ 'keep_favourites' => Minz_Request::paramBoolean('keep_favourites'),
+ 'keep_labels' => Minz_Request::paramBoolean('keep_labels'),
+ 'keep_unreads' => Minz_Request::paramBoolean('keep_unreads'),
+ ];
+ FreshRSS_Context::$user_conf->keep_history_default = null; //Legacy < FreshRSS 1.15
+ FreshRSS_Context::$user_conf->old_entries = null; //Legacy < FreshRSS 1.15
FreshRSS_Context::$user_conf->save();
invalidateHttpCache();
@@ -206,7 +228,20 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
array('c' => 'configure', 'a' => 'archiving'));
}
- Minz_View::prependTitle(_t('conf.archiving.title') . ' · ');
+ $volatile = [
+ 'enable_keep_period' => false,
+ 'keep_period_count' => '3',
+ 'keep_period_unit' => 'P1M',
+ ];
+ $keepPeriod = FreshRSS_Context::$user_conf->archiving['keep_period'];
+ if (preg_match('/^PT?(?P<count>\d+)[YMWDH]$/', $keepPeriod, $matches)) {
+ $volatile = [
+ 'enable_keep_period' => true,
+ 'keep_period_count' => $matches['count'],
+ 'keep_period_unit' => str_replace($matches['count'], 1, $keepPeriod),
+ ];
+ }
+ FreshRSS_Context::$user_conf->volatile = $volatile;
$entryDAO = FreshRSS_Factory::createEntryDao();
$this->view->nb_total = $entryDAO->count();
@@ -217,6 +252,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
if (FreshRSS_Auth::hasAccess('admin')) {
$this->view->size_total = $databaseDAO->size(true);
}
+
+ Minz_View::prependTitle(_t('conf.archiving.title') . ' · ');
}
/**
@@ -292,15 +329,24 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
* configuration values then sends a notification to the user.
*
* The options available on the page are:
+ * - instance name (default: FreshRSS)
+ * - auto update URL (default: false)
+ * - force emails validation (default: false)
* - user limit (default: 1)
* - user category limit (default: 16384)
* - user feed limit (default: 16384)
* - user login duration for form auth (default: 2592000)
+ *
+ * The `force-email-validation` is ignored with PHP < 5.5
*/
public function systemAction() {
if (!FreshRSS_Auth::hasAccess('admin')) {
Minz_Error::error(403);
}
+
+ $can_enable_email_validation = version_compare(PHP_VERSION, '5.5') >= 0;
+ $this->view->can_enable_email_validation = $can_enable_email_validation;
+
if (Minz_Request::isPost()) {
$limits = FreshRSS_Context::$system_conf->limits;
$limits['max_registrations'] = Minz_Request::param('max-registrations', 1);
@@ -310,6 +356,9 @@ class FreshRSS_configure_Controller extends Minz_ActionController {
FreshRSS_Context::$system_conf->limits = $limits;
FreshRSS_Context::$system_conf->title = Minz_Request::param('instance-name', 'FreshRSS');
FreshRSS_Context::$system_conf->auto_update_url = Minz_Request::param('auto-update-url', false);
+ if ($can_enable_email_validation) {
+ FreshRSS_Context::$system_conf->force_email_validation = Minz_Request::param('force-email-validation', false);
+ }
FreshRSS_Context::$system_conf->save();
invalidateHttpCache();
diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php
index 9c5ee2616..7881cb3ec 100755
--- a/app/Controllers/entryController.php
+++ b/app/Controllers/entryController.php
@@ -17,7 +17,7 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
// If ajax request, we do not print layout
$this->ajax = Minz_Request::param('ajax');
if ($this->ajax) {
- $this->view->_useLayout(false);
+ $this->view->_layout(false);
Minz_Request::_param('ajax');
}
}
@@ -181,32 +181,20 @@ class FreshRSS_entry_Controller extends Minz_ActionController {
public function purgeAction() {
@set_time_limit(300);
- $nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1);
- $date_min = time() - (3600 * 24 * 30 * $nb_month_old);
-
- $entryDAO = FreshRSS_Factory::createEntryDao();
$feedDAO = FreshRSS_Factory::createFeedDao();
$feeds = $feedDAO->listFeeds();
$nb_total = 0;
invalidateHttpCache();
- foreach ($feeds as $feed) {
- $feed_history = $feed->keepHistory();
- if (FreshRSS_Feed::KEEP_HISTORY_DEFAULT === $feed_history) {
- $feed_history = FreshRSS_Context::$user_conf->keep_history_default;
- }
+ $feedDAO->beginTransaction();
- if ($feed_history >= 0) {
- $nb = $entryDAO->cleanOldEntries($feed->id(), $date_min, $feed_history);
- if ($nb > 0) {
- $nb_total += $nb;
- Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url(false) . ']');
- }
- }
+ foreach ($feeds as $feed) {
+ $nb_total += $feed->cleanOldEntries();
}
$feedDAO->updateCachedValues();
+ $feedDAO->commit();
$databaseDAO = FreshRSS_Factory::createDatabaseDAO();
$databaseDAO->minorDbMaintenance();
diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php
index 806e5a696..68bd90f4c 100644
--- a/app/Controllers/extensionController.php
+++ b/app/Controllers/extensionController.php
@@ -80,10 +80,10 @@ class FreshRSS_extension_Controller extends Minz_ActionController {
*/
public function configureAction() {
if (Minz_Request::param('ajax')) {
- $this->view->_useLayout(false);
+ $this->view->_layout(false);
} else {
$this->indexAction();
- $this->view->change_view('extension', 'index');
+ $this->view->_path('extension/index.phtml');
}
$ext_name = urldecode(Minz_Request::param('e'));
diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php
index 862bb10fb..aabeb80ff 100755
--- a/app/Controllers/feedController.php
+++ b/app/Controllers/feedController.php
@@ -267,10 +267,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$maxFeeds = 10;
}
- // Calculate date of oldest entries we accept in DB.
- $nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1);
- $date_min = time() - (3600 * 24 * 30 * $nb_month_old);
-
// WebSub (PubSubHubbub) support
$pubsubhubbubEnabledGeneral = FreshRSS_Context::$system_conf->pubsubhubbub_enabled;
$pshbMinAge = time() - (3600 * 24); //TODO: Make a configuration.
@@ -323,12 +319,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
continue;
}
- $feed_history = $feed->keepHistory();
- if ($isNewFeed) {
- $feed_history = FreshRSS_Feed::KEEP_HISTORY_INFINITE;
- } elseif (FreshRSS_Feed::KEEP_HISTORY_DEFAULT === $feed_history) {
- $feed_history = FreshRSS_Context::$user_conf->keep_history_default;
- }
$needFeedCacheRefresh = false;
// We want chronological order and SimplePie uses reverse order.
@@ -376,15 +366,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
}
$entryDAO->updateEntry($entry->toArray());
}
- } elseif ($feed_history == 0 && $entry_date < $date_min) {
- // This entry should not be added considering configuration and date.
- $oldGuids[] = $entry->guid();
} else {
$id = uTimeString();
$entry->_id($id);
- if ($entry_date < $date_min) {
- $entry->_isRead(true); //Old article that was not in database. Probably an error, so mark as read
- }
$entry->applyFilterActions();
@@ -413,23 +397,19 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
$entryDAO->updateLastSeen($feed->id(), $oldGuids, $mtime);
}
- if ($feed_history >= 0 && mt_rand(0, 30) === 1) {
- // TODO: move this function in web cron when available (see entry::purge)
- // Remove old entries once in 30.
+ if (mt_rand(0, 30) === 1) { // Remove old entries once in 30.
if (!$entryDAO->inTransaction()) {
$entryDAO->beginTransaction();
}
-
- $nb = $entryDAO->cleanOldEntries($feed->id(), $date_min, max($feed_history, count($entries) + 10));
+ $nb = $feed->cleanOldEntries();
if ($nb > 0) {
$needFeedCacheRefresh = true;
- Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url(false) . ']');
}
}
$feedDAO->updateLastUpdate($feed->id(), false, $mtime);
if ($needFeedCacheRefresh) {
- $feedDAO->updateCachedValue($feed->id());
+ $feedDAO->updateCachedValues($feed->id());
}
if ($entryDAO->inTransaction()) {
$entryDAO->commit();
@@ -530,7 +510,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController {
);
Minz_Session::_param('notification', $notif);
// No layout in ajax request.
- $this->view->_useLayout(false);
+ $this->view->_layout(false);
} else {
// Redirect to the main page with correct notification.
if ($updated_feeds === 1) {
diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php
index 1d7176929..f2ae8238e 100644
--- a/app/Controllers/importExportController.php
+++ b/app/Controllers/importExportController.php
@@ -29,7 +29,25 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
Minz_View::prependTitle(_t('sub.import_export.title') . ' · ');
}
+ private static function megabytes($size_str) {
+ switch (substr($size_str, -1)) {
+ case 'M': case 'm': return (int)$size_str;
+ case 'K': case 'k': return (int)$size_str / 1024;
+ case 'G': case 'g': return (int)$size_str * 1024;
+ }
+ return $size_str;
+ }
+
+ private static function minimumMemory($mb) {
+ $mb = (int)$mb;
+ $ini = self::megabytes(ini_get('memory_limit'));
+ if ($ini < $mb) {
+ ini_set('memory_limit', $mb . 'M');
+ }
+ }
+
public function importFile($name, $path, $username = null) {
+ self::minimumMemory(256);
require_once(LIB_PATH . '/lib_opml.php');
$this->catDAO = new FreshRSS_CategoryDAO($username);
@@ -709,8 +727,6 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
$this->entryDAO = FreshRSS_Factory::createEntryDao($username);
$this->feedDAO = FreshRSS_Factory::createFeedDao($username);
- $this->entryDAO->disableBuffering();
-
if ($export_feeds === true) {
//All feeds
$export_feeds = $this->feedDAO->listFeedsIds();
@@ -773,7 +789,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController {
if (!Minz_Request::isPost()) {
Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true);
}
- $this->view->_useLayout(false);
+ $this->view->_layout(false);
$nb_files = 0;
try {
diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php
index f536113dd..967029fd1 100755
--- a/app/Controllers/indexController.php
+++ b/app/Controllers/indexController.php
@@ -155,7 +155,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
// No layout for RSS output.
$this->view->url = PUBLIC_TO_INDEX_PATH . '/' . (empty($_SERVER['QUERY_STRING']) ? '' : '?' . $_SERVER['QUERY_STRING']);
$this->view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title();
- $this->view->_useLayout(false);
+ $this->view->_layout(false);
header('Content-Type: application/rss+xml; charset=utf-8');
}
@@ -173,7 +173,7 @@ class FreshRSS_index_Controller extends Minz_ActionController {
private function updateContext() {
if (empty(FreshRSS_Context::$categories)) {
$catDAO = FreshRSS_Factory::createCategoryDao();
- FreshRSS_Context::$categories = $catDAO->listCategories();
+ FreshRSS_Context::$categories = $catDAO->listSortedCategories();
}
// Update number of read / unread variables.
@@ -260,6 +260,23 @@ class FreshRSS_index_Controller extends Minz_ActionController {
}
/**
+ * This action displays the EULA page of FreshRSS.
+ * This page is enabled only if admin created a data/tos.html file.
+ * The content of the page is the content of data/tos.html.
+ * It returns 404 if there is no EULA.
+ */
+ public function tosAction() {
+ $terms_of_service = file_get_contents(join_path(DATA_PATH, 'tos.html'));
+ if (!$terms_of_service) {
+ Minz_Error::error(404);
+ }
+
+ $this->view->terms_of_service = $terms_of_service;
+ $this->view->can_register = !max_registrations_reached();
+ Minz_View::prependTitle(_t('index.tos.title') . ' · ');
+ }
+
+ /**
* This action displays logs of FreshRSS for the current user.
*/
public function logsAction() {
diff --git a/app/Controllers/javascriptController.php b/app/Controllers/javascriptController.php
index d56da9cbb..c84e5483b 100755
--- a/app/Controllers/javascriptController.php
+++ b/app/Controllers/javascriptController.php
@@ -2,7 +2,7 @@
class FreshRSS_javascript_Controller extends Minz_ActionController {
public function firstAction() {
- $this->view->_useLayout(false);
+ $this->view->_layout(false);
}
public function actualizeAction() {
diff --git a/app/Controllers/subscriptionController.php b/app/Controllers/subscriptionController.php
index 79da39751..b4520c8e6 100644
--- a/app/Controllers/subscriptionController.php
+++ b/app/Controllers/subscriptionController.php
@@ -19,7 +19,7 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
$catDAO->checkDefault();
$feedDAO->updateTTL();
- $this->view->categories = $catDAO->listCategories(false);
+ $this->view->categories = $catDAO->listSortedCategories(false);
$this->view->default_category = $catDAO->getDefault();
}
@@ -74,7 +74,7 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
*/
public function feedAction() {
if (Minz_Request::param('ajax')) {
- $this->view->_useLayout(false);
+ $this->view->_layout(false);
}
$feedDAO = FreshRSS_Factory::createFeedDao();
@@ -121,6 +121,32 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
$feed->_attributes('timeout', null);
}
+ if (Minz_Request::paramBoolean('use_default_purge_options')) {
+ $feed->_attributes('archiving', null);
+ } else {
+ if (!Minz_Request::paramBoolean('enable_keep_max')) {
+ $keepMax = false;
+ } elseif (!$keepMax = Minz_Request::param('keep_max')) {
+ $keepMax = FreshRSS_Feed::ARCHIVING_RETENTION_COUNT_LIMIT;
+ }
+ if ($enableRetentionPeriod = Minz_Request::paramBoolean('enable_keep_period')) {
+ $keepPeriod = FreshRSS_Feed::ARCHIVING_RETENTION_PERIOD;
+ if (is_numeric(Minz_Request::param('keep_period_count')) && preg_match('/^PT?1[YMWDH]$/', Minz_Request::param('keep_period_unit'))) {
+ $keepPeriod = str_replace(1, Minz_Request::param('keep_period_count'), Minz_Request::param('keep_period_unit'));
+ }
+ } else {
+ $keepPeriod = false;
+ }
+ $feed->_attributes('archiving', [
+ 'keep_period' => $keepPeriod,
+ 'keep_max' => $keepMax,
+ 'keep_min' => intval(Minz_Request::param('keep_min', 0)),
+ 'keep_favourites' => Minz_Request::paramBoolean('keep_favourites'),
+ 'keep_labels' => Minz_Request::paramBoolean('keep_labels'),
+ 'keep_unreads' => Minz_Request::paramBoolean('keep_unreads'),
+ ]);
+ }
+
$feed->_filtersAction('read', preg_split('/[\n\r]+/', Minz_Request::param('filteractions_read', '')));
$values = array(
@@ -132,7 +158,6 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
'pathEntries' => Minz_Request::param('path_entries', ''),
'priority' => intval(Minz_Request::param('priority', FreshRSS_Feed::PRIORITY_MAIN_STREAM)),
'httpAuth' => $httpAuth,
- 'keep_history' => intval(Minz_Request::param('keep_history', FreshRSS_Feed::KEEP_HISTORY_DEFAULT)),
'ttl' => $ttl * ($mute ? -1 : 1),
'attributes' => $feed->attributes(),
);
@@ -152,7 +177,7 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
}
public function categoryAction() {
- $this->view->_useLayout(false);
+ $this->view->_layout(false);
$categoryDAO = FreshRSS_Factory::createCategoryDao();
@@ -165,9 +190,39 @@ class FreshRSS_subscription_Controller extends Minz_ActionController {
$this->view->category = $category;
if (Minz_Request::isPost()) {
- $values = array(
+ if (Minz_Request::paramBoolean('use_default_purge_options')) {
+ $category->_attributes('archiving', null);
+ } else {
+ if (!Minz_Request::paramBoolean('enable_keep_max')) {
+ $keepMax = false;
+ } elseif (!$keepMax = Minz_Request::param('keep_max')) {
+ $keepMax = FreshRSS_Feed::ARCHIVING_RETENTION_COUNT_LIMIT;
+ }
+ if ($enableRetentionPeriod = Minz_Request::paramBoolean('enable_keep_period')) {
+ $keepPeriod = FreshRSS_Feed::ARCHIVING_RETENTION_PERIOD;
+ if (is_numeric(Minz_Request::param('keep_period_count')) && preg_match('/^PT?1[YMWDH]$/', Minz_Request::param('keep_period_unit'))) {
+ $keepPeriod = str_replace(1, Minz_Request::param('keep_period_count'), Minz_Request::param('keep_period_unit'));
+ }
+ } else {
+ $keepPeriod = false;
+ }
+ $category->_attributes('archiving', [
+ 'keep_period' => $keepPeriod,
+ 'keep_max' => $keepMax,
+ 'keep_min' => intval(Minz_Request::param('keep_min', 0)),
+ 'keep_favourites' => Minz_Request::paramBoolean('keep_favourites'),
+ 'keep_labels' => Minz_Request::paramBoolean('keep_labels'),
+ 'keep_unreads' => Minz_Request::paramBoolean('keep_unreads'),
+ ]);
+ }
+
+ $position = Minz_Request::param('position');
+ $category->_attributes('position', '' === $position ? null : (int) $position);
+
+ $values = [
'name' => Minz_Request::param('name', ''),
- );
+ 'attributes' => $category->attributes(),
+ ];
invalidateHttpCache();
diff --git a/app/Controllers/tagController.php b/app/Controllers/tagController.php
index 106e0afa8..ba9efe2fc 100644
--- a/app/Controllers/tagController.php
+++ b/app/Controllers/tagController.php
@@ -16,7 +16,7 @@ class FreshRSS_tag_Controller extends Minz_ActionController {
// If ajax request, we do not print layout
$this->ajax = Minz_Request::param('ajax');
if ($this->ajax) {
- $this->view->_useLayout(false);
+ $this->view->_layout(false);
Minz_Request::_param('ajax');
}
}
@@ -70,7 +70,7 @@ class FreshRSS_tag_Controller extends Minz_ActionController {
}
public function getTagsForEntryAction() {
- $this->view->_useLayout(false);
+ $this->view->_layout(false);
header('Content-Type: application/json; charset=UTF-8');
header('Cache-Control: private, no-cache, no-store, must-revalidate');
$id_entry = Minz_Request::param('id_entry', 0);
diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php
index 2be644c85..ebe5e4cc8 100644
--- a/app/Controllers/updateController.php
+++ b/app/Controllers/updateController.php
@@ -89,7 +89,7 @@ class FreshRSS_update_Controller extends Minz_ActionController {
}
public function checkAction() {
- $this->view->change_view('update', 'index');
+ $this->view->_path('update/index.phtml');
if (file_exists(UPDATE_FILENAME)) {
// There is already an update file to apply: we don't need to check
diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php
index 6d0fced5b..6afc91b4e 100644
--- a/app/Controllers/userController.php
+++ b/app/Controllers/userController.php
@@ -8,26 +8,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
// so do not use a too high cost
const BCRYPT_COST = 9;
- /**
- * 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() && !(
- Minz_Request::actionName() === 'create' &&
- !max_registrations_reached()
- )) {
- Minz_Error::error(403);
- }
- }
-
public static function hashPassword($passwordPlain) {
- if (!function_exists('password_hash')) {
- include_once(LIB_PATH . '/password_compat.php');
- }
$passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST));
$passwordPlain = '';
$passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js
@@ -52,12 +33,23 @@ class FreshRSS_user_Controller extends Minz_ActionController {
return false;
}
- public static function updateUser($user, $passwordPlain, $apiPasswordPlain, $userConfigUpdated = array()) {
+ public static function updateUser($user, $email, $passwordPlain, $apiPasswordPlain, $userConfigUpdated = array()) {
$userConfig = get_user_configuration($user);
if ($userConfig === null) {
return false;
}
+ if ($email !== null && $userConfig->mail_login !== $email) {
+ $userConfig->mail_login = $email;
+
+ if (FreshRSS_Context::$system_conf->force_email_validation) {
+ $salt = FreshRSS_Context::$system_conf->salt;
+ $userConfig->email_validation_token = sha1($salt . uniqid(mt_rand(), true));
+ $mailer = new FreshRSS_User_Mailer();
+ $mailer->send_email_need_validation($user, $userConfig);
+ }
+ }
+
if ($passwordPlain != '') {
$passwordHash = self::hashPassword($passwordPlain);
$userConfig->passwordHash = $passwordHash;
@@ -103,7 +95,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
$apiPasswordPlain = Minz_Request::param('apiPasswordPlain', '', true);
$username = Minz_Request::param('username');
- $ok = self::updateUser($username, $passwordPlain, $apiPasswordPlain, array(
+ $ok = self::updateUser($username, null, $passwordPlain, $apiPasswordPlain, array(
'token' => Minz_Request::param('token', null),
));
@@ -126,25 +118,63 @@ class FreshRSS_user_Controller extends Minz_ActionController {
* This action displays the user profile page.
*/
public function profileAction() {
+ if (!FreshRSS_Auth::hasAccess()) {
+ Minz_Error::error(403);
+ }
+
+ $email_not_verified = FreshRSS_Context::$user_conf->email_validation_token !== '';
+ $this->view->disable_aside = false;
+ if ($email_not_verified) {
+ $this->view->_layout('simple');
+ $this->view->disable_aside = true;
+ }
+
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()) {
+ $system_conf = FreshRSS_Context::$system_conf;
+ $user_config = FreshRSS_Context::$user_conf;
+ $old_email = $user_config->mail_login;
+
+ $email = trim(Minz_Request::param('email', ''));
$passwordPlain = Minz_Request::param('newPasswordPlain', '', true);
Minz_Request::_param('newPasswordPlain'); //Discard plain-text password ASAP
$_POST['newPasswordPlain'] = '';
$apiPasswordPlain = Minz_Request::param('apiPasswordPlain', '', true);
- $ok = self::updateUser(Minz_Session::param('currentUser'), $passwordPlain, $apiPasswordPlain, array(
+ if ($system_conf->force_email_validation && empty($email)) {
+ Minz_Request::bad(
+ _t('user.email.feedback.required'),
+ array('c' => 'user', 'a' => 'profile')
+ );
+ }
+
+ if (!empty($email) && !validateEmailAddress($email)) {
+ Minz_Request::bad(
+ _t('user.email.feedback.invalid'),
+ array('c' => 'user', 'a' => 'profile')
+ );
+ }
+
+ $ok = self::updateUser(
+ Minz_Session::param('currentUser'),
+ $email,
+ $passwordPlain,
+ $apiPasswordPlain,
+ array(
'token' => Minz_Request::param('token', null),
- ));
+ )
+ );
Minz_Session::_param('passwordHash', FreshRSS_Context::$user_conf->passwordHash);
if ($ok) {
- if ($passwordPlain == '') {
+ if ($system_conf->force_email_validation && $email !== $old_email) {
+ Minz_Request::good(_t('feedback.profile.updated'), array('c' => 'user', 'a' => 'validateEmail'));
+ } elseif ($passwordPlain == '') {
Minz_Request::good(_t('feedback.profile.updated'), array('c' => 'user', 'a' => 'profile'));
} else {
Minz_Request::good(_t('feedback.profile.updated'), array('c' => 'index', 'a' => 'index'));
@@ -166,6 +196,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
Minz_View::prependTitle(_t('admin.user.title') . ' · ');
+ $this->view->show_email_field = FreshRSS_Context::$system_conf->force_email_validation;
$this->view->current_user = Minz_Request::param('u');
$this->view->nb_articles = 0;
@@ -180,9 +211,19 @@ class FreshRSS_user_Controller extends Minz_ActionController {
}
}
- public static function createUser($new_user_name, $passwordPlain, $apiPasswordPlain, $userConfig = array(), $insertDefaultFeeds = true) {
- if (!is_array($userConfig)) {
- $userConfig = array();
+ public static function createUser($new_user_name, $email, $passwordPlain, $apiPasswordPlain = '', $userConfigOverride = [], $insertDefaultFeeds = true) {
+ $userConfig = [];
+
+ $customUserConfigPath = join_path(DATA_PATH, 'config-user.custom.php');
+ if (file_exists($customUserConfigPath)) {
+ $customUserConfig = include($customUserConfigPath);
+ if (is_array($customUserConfig)) {
+ $userConfig = $customUserConfig;
+ }
+ }
+
+ if (is_array($userConfigOverride)) {
+ $userConfig = array_merge($userConfig, $userConfigOverride);
}
$ok = self::checkUsername($new_user_name);
@@ -206,9 +247,9 @@ class FreshRSS_user_Controller extends Minz_ActionController {
$ok &= (file_put_contents($configPath, "<?php\n return " . var_export($userConfig, true) . ';') !== false);
}
if ($ok) {
- $userDAO = new FreshRSS_UserDAO();
- $ok &= $userDAO->createUser($new_user_name, $userConfig['language'], $insertDefaultFeeds);
- $ok &= self::updateUser($new_user_name, $passwordPlain, $apiPasswordPlain);
+ $newUserDAO = FreshRSS_Factory::createUserDao($new_user_name);
+ $ok &= $newUserDAO->createUser($insertDefaultFeeds);
+ $ok &= self::updateUser($new_user_name, $email, $passwordPlain, $apiPasswordPlain);
}
return $ok;
}
@@ -219,6 +260,7 @@ class FreshRSS_user_Controller extends Minz_ActionController {
* Request parameters are:
* - new_user_language
* - new_user_name
+ * - new_user_email
* - new_user_passwordPlain
* - r (i.e. a redirection url, optional)
*
@@ -226,15 +268,43 @@ class FreshRSS_user_Controller extends Minz_ActionController {
* @todo handle r redirection in Minz_Request::forward directly?
*/
public function createAction() {
- if (Minz_Request::isPost() && (
- FreshRSS_Auth::hasAccess('admin') ||
- !max_registrations_reached()
- )) {
+ if (!FreshRSS_Auth::hasAccess('admin') && max_registrations_reached()) {
+ Minz_Error::error(403);
+ }
+
+ if (Minz_Request::isPost()) {
+ $system_conf = FreshRSS_Context::$system_conf;
+
$new_user_name = Minz_Request::param('new_user_name');
+ $email = Minz_Request::param('new_user_email', '');
$passwordPlain = Minz_Request::param('new_user_passwordPlain', '', true);
$new_user_language = Minz_Request::param('new_user_language', FreshRSS_Context::$user_conf->language);
- $ok = self::createUser($new_user_name, $passwordPlain, '', array('language' => $new_user_language));
+ $tos_enabled = file_exists(join_path(DATA_PATH, 'tos.html'));
+ $accept_tos = Minz_Request::param('accept_tos', false);
+
+ if ($system_conf->force_email_validation && empty($email)) {
+ Minz_Request::bad(
+ _t('user.email.feedback.required'),
+ array('c' => 'auth', 'a' => 'register')
+ );
+ }
+
+ if (!empty($email) && !validateEmailAddress($email)) {
+ Minz_Request::bad(
+ _t('user.email.feedback.invalid'),
+ array('c' => 'auth', 'a' => 'register')
+ );
+ }
+
+ if ($tos_enabled && !$accept_tos) {
+ Minz_Request::bad(
+ _t('user.tos.feedback.invalid'),
+ array('c' => 'auth', 'a' => 'register')
+ );
+ }
+
+ $ok = self::createUser($new_user_name, $email, $passwordPlain, '', array('language' => $new_user_language));
Minz_Request::_param('new_user_passwordPlain'); //Discard plain-text password ASAP
$_POST['new_user_passwordPlain'] = '';
invalidateHttpCache();
@@ -266,9 +336,6 @@ class FreshRSS_user_Controller extends Minz_ActionController {
}
public static function deleteUser($username) {
- $db = FreshRSS_Context::$system_conf->db;
- require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php');
-
$ok = self::checkUsername($username);
if ($ok) {
$default_user = FreshRSS_Context::$system_conf->default_user;
@@ -278,8 +345,8 @@ class FreshRSS_user_Controller extends Minz_ActionController {
$ok &= is_dir($user_data);
if ($ok) {
self::deleteFeverKey($username);
- $userDAO = new FreshRSS_UserDAO();
- $ok &= $userDAO->deleteUser($username);
+ $oldUserDAO = FreshRSS_Factory::createUserDao($username);
+ $ok &= $oldUserDAO->deleteUser();
$ok &= recursive_unlink($user_data);
array_map('unlink', glob(PSHB_PATH . '/feeds/*/' . $username . '.txt'));
}
@@ -287,6 +354,122 @@ class FreshRSS_user_Controller extends Minz_ActionController {
}
/**
+ * This action validates an email address, based on the token sent by email.
+ * It also serves the main page when user is blocked.
+ *
+ * Request parameters are:
+ * - username
+ * - token
+ *
+ * This route works with GET requests since the URL is provided by email.
+ * The security risks (e.g. forged URL by an attacker) are not very high so
+ * it's ok.
+ *
+ * It returns 404 error if `force_email_validation` is disabled or if the
+ * user doesn't exist.
+ *
+ * It returns 403 if user isn't logged in and `username` param isn't passed.
+ */
+ public function validateEmailAction() {
+ if (!FreshRSS_Context::$system_conf->force_email_validation) {
+ Minz_Error::error(404);
+ }
+
+ Minz_View::prependTitle(_t('user.email.validation.title') . ' · ');
+ $this->view->_layout('simple');
+
+ $username = Minz_Request::param('username');
+ $token = Minz_Request::param('token');
+
+ if ($username) {
+ $user_config = get_user_configuration($username);
+ } elseif (FreshRSS_Auth::hasAccess()) {
+ $user_config = FreshRSS_Context::$user_conf;
+ } else {
+ Minz_Error::error(403);
+ }
+
+ if (!FreshRSS_UserDAO::exists($username) || $user_config === null) {
+ Minz_Error::error(404);
+ }
+
+ if ($user_config->email_validation_token === '') {
+ Minz_Request::good(
+ _t('user.email.validation.feedback.unnecessary'),
+ array('c' => 'index', 'a' => 'index')
+ );
+ }
+
+ if ($token) {
+ if ($user_config->email_validation_token !== $token) {
+ Minz_Request::bad(
+ _t('user.email.validation.feedback.wrong_token'),
+ array('c' => 'user', 'a' => 'validateEmail')
+ );
+ }
+
+ $user_config->email_validation_token = '';
+ if ($user_config->save()) {
+ Minz_Request::good(
+ _t('user.email.validation.feedback.ok'),
+ array('c' => 'index', 'a' => 'index')
+ );
+ } else {
+ Minz_Request::bad(
+ _t('user.email.validation.feedback.error'),
+ array('c' => 'user', 'a' => 'validateEmail')
+ );
+ }
+ }
+ }
+
+ /**
+ * This action resends a validation email to the current user.
+ *
+ * It only acts on POST requests but doesn't require any param (except the
+ * CSRF token).
+ *
+ * It returns 403 error if the user is not logged in or 404 if request is
+ * not POST. Else it redirects silently to the index if user has already
+ * validated its email, or to the user#validateEmail route.
+ */
+ public function sendValidationEmailAction() {
+ if (!FreshRSS_Auth::hasAccess()) {
+ Minz_Error::error(403);
+ }
+
+ if (!Minz_Request::isPost()) {
+ Minz_Error::error(404);
+ }
+
+ $username = Minz_Session::param('currentUser', '_');
+ $user_config = FreshRSS_Context::$user_conf;
+
+ if ($user_config->email_validation_token === '') {
+ Minz_Request::forward(array(
+ 'c' => 'index',
+ 'a' => 'index',
+ ), true);
+ }
+
+ $mailer = new FreshRSS_User_Mailer();
+ $ok = $mailer->send_email_need_validation($username, $user_config);
+
+ $redirect_url = array('c' => 'user', 'a' => 'validateEmail');
+ if ($ok) {
+ Minz_Request::good(
+ _t('user.email.validation.feedback.email_sent'),
+ $redirect_url
+ );
+ } else {
+ Minz_Request::bad(
+ _t('user.email.validation.feedback.email_failed'),
+ $redirect_url
+ );
+ }
+ }
+
+ /**
* This action delete an existing user.
*
* Request parameter is:
@@ -296,17 +479,18 @@ class FreshRSS_user_Controller extends Minz_ActionController {
*/
public function deleteAction() {
$username = Minz_Request::param('username');
+ $self_deletion = Minz_Session::param('currentUser', '_') === $username;
+
+ if (!FreshRSS_Auth::hasAccess('admin') && !$self_deletion) {
+ Minz_Error::error(403);
+ }
+
$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
- )) {
+ if (Minz_Request::isPost()) {
$ok = true;
if ($ok && $self_deletion) {
// We check the password if it's a self-destruction