aboutsummaryrefslogtreecommitdiff
path: root/app/Controllers
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2022-07-04 09:53:26 +0200
committerGravatar GitHub <noreply@github.com> 2022-07-04 09:53:26 +0200
commit509c8cae6381ec46af7c8303eb92fda6ce496a4a (patch)
tree653f7f44df842f9d7135decd89467879a0098c50 /app/Controllers
parent57d571230eeb2d3ede57e640b640f17c7a2298a2 (diff)
Dynamic OPML (#4407)
* Dynamic OPML draft #fix https://github.com/FreshRSS/FreshRSS/issues/4191 * Export dynamic OPML http://opml.org/spec2.opml#1629043127000 * Restart with simpler approach * Minor revert * Export dynamic OPML also for single feeds * Special category type for importing dynamic OPML * Parameter for excludeMutedFeeds * Details * More draft * i18n * Fix update * Draft manual import working * Working manual refresh * Draft automatic update * Working Web refresh + fixes * Import/export dynamic OPML settings * Annoying numerous lines in SQL logs * Fix minor JavaScript error * Fix auto adding new columns * Add require * Add missing 🗲 * Missing space * Disable adding new feeds to dynamic categories * Link from import * i18n typo * Improve theme icon function * Fix pink-dark
Diffstat (limited to 'app/Controllers')
-rw-r--r--app/Controllers/categoryController.php86
-rwxr-xr-xapp/Controllers/feedController.php25
-rw-r--r--app/Controllers/importExportController.php8
-rwxr-xr-xapp/Controllers/indexController.php70
-rwxr-xr-xapp/Controllers/javascriptController.php4
-rw-r--r--app/Controllers/subscriptionController.php27
6 files changed, 184 insertions, 36 deletions
diff --git a/app/Controllers/categoryController.php b/app/Controllers/categoryController.php
index 7226e44af..62901c78e 100644
--- a/app/Controllers/categoryController.php
+++ b/app/Controllers/categoryController.php
@@ -40,8 +40,8 @@ class FreshRSS_category_Controller extends FreshRSS_ActionController {
if (Minz_Request::isPost()) {
invalidateHttpCache();
- $cat_name = Minz_Request::param('new-category');
- if (!$cat_name) {
+ $cat_name = trim(Minz_Request::param('new-category', ''));
+ if ($cat_name == '') {
Minz_Request::bad(_t('feedback.sub.category.no_name'), $url_redirect);
}
@@ -51,12 +51,16 @@ class FreshRSS_category_Controller extends FreshRSS_ActionController {
Minz_Request::bad(_t('feedback.sub.category.name_exists'), $url_redirect);
}
- $values = array(
- 'id' => $cat->id(),
- 'name' => $cat->name(),
- );
+ $opml_url = checkUrl(Minz_Request::param('opml_url', ''));
+ if ($opml_url != '') {
+ $cat->_kind(FreshRSS_Category::KIND_DYNAMIC_OPML);
+ $cat->_attributes('opml_url', $opml_url);
+ } else {
+ $cat->_kind(FreshRSS_Category::KIND_NORMAL);
+ $cat->_attributes('opml_url', null);
+ }
- if ($catDAO->addCategory($values)) {
+ if ($catDAO->addCategoryObject($cat)) {
$url_redirect['a'] = 'index';
Minz_Request::good(_t('feedback.sub.category.created', $cat->name()), $url_redirect);
} else {
@@ -156,6 +160,7 @@ class FreshRSS_category_Controller extends FreshRSS_ActionController {
*
* Request parameter is:
* - id (of a category)
+ * - muted (truthy to remove only muted feeds, or falsy otherwise)
*/
public function emptyAction() {
$feedDAO = FreshRSS_Factory::createFeedDao();
@@ -169,10 +174,15 @@ class FreshRSS_category_Controller extends FreshRSS_ActionController {
Minz_Request::bad(_t('feedback.sub.category.no_id'), $url_redirect);
}
+ $muted = Minz_Request::param('muted', null);
+ if ($muted !== null) {
+ $muted = boolval($muted);
+ }
+
// List feeds to remove then related user queries.
- $feeds = $feedDAO->listByCategory($id);
+ $feeds = $feedDAO->listByCategory($id, $muted);
- if ($feedDAO->deleteFeedByCategory($id)) {
+ if ($feedDAO->deleteFeedByCategory($id, $muted)) {
// TODO: Delete old favicons
// Remove related queries
@@ -190,4 +200,62 @@ class FreshRSS_category_Controller extends FreshRSS_ActionController {
Minz_Request::forward($url_redirect, true);
}
+
+ /**
+ * Request parameter is:
+ * - id (of a category)
+ */
+ public function refreshOpmlAction() {
+ $catDAO = FreshRSS_Factory::createCategoryDao();
+ $url_redirect = array('c' => 'subscription', 'a' => 'index');
+
+ if (Minz_Request::isPost()) {
+ invalidateHttpCache();
+
+ $id = Minz_Request::param('id');
+ if (!$id) {
+ Minz_Request::bad(_t('feedback.sub.category.no_id'), $url_redirect);
+ }
+
+ $category = $catDAO->searchById($id);
+ if ($category == null) {
+ Minz_Request::bad(_t('feedback.sub.category.not_exist'), $url_redirect);
+ }
+
+ invalidateHttpCache();
+
+ $ok = $category->refreshDynamicOpml();
+
+ if (Minz_Request::param('ajax')) {
+ Minz_Request::setGoodNotification(_t('feedback.sub.category.updated'));
+ $this->view->_layout(false);
+ } else {
+ if ($ok) {
+ Minz_Request::good(_t('feedback.sub.category.updated'), $url_redirect);
+ } else {
+ Minz_Request::bad(_t('feedback.sub.category.error'), $url_redirect);
+ }
+ Minz_Request::forward($url_redirect, true);
+ }
+ }
+ }
+
+ /** @return array<string,int> */
+ public static function refreshDynamicOpmls() {
+ $successes = 0;
+ $errors = 0;
+ $catDAO = FreshRSS_Factory::createCategoryDao();
+ $categories = $catDAO->listCategoriesOrderUpdate(FreshRSS_Context::$user_conf->dynamic_opml_ttl_default ?? 86400);
+ foreach ($categories as $category) {
+ if ($category->refreshDynamicOpml()) {
+ $successes++;
+ } else {
+ $errors++;
+ }
+ }
+ return [
+ 'successes' => $successes,
+ 'errors' => $errors,
+ ];
+ }
}
diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php
index fe5641642..8621cb535 100755
--- a/app/Controllers/feedController.php
+++ b/app/Controllers/feedController.php
@@ -67,6 +67,10 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
$cat_id = $cat == null ? FreshRSS_CategoryDAO::DEFAULTCATEGORYID : $cat->id();
$feed = new FreshRSS_Feed($url); //Throws FreshRSS_BadUrl_Exception
+ $title = trim($title);
+ if ($title != '') {
+ $feed->_name($title);
+ }
$feed->_kind($kind);
$feed->_attributes('', $attributes);
$feed->_httpAuth($http_auth);
@@ -92,19 +96,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
throw new FreshRSS_FeedNotAdded_Exception($url);
}
- $values = array(
- 'url' => $feed->url(),
- 'kind' => $feed->kind(),
- 'category' => $feed->category(),
- 'name' => $title != '' ? $title : $feed->name(true),
- 'website' => $feed->website(),
- 'description' => $feed->description(),
- 'lastUpdate' => 0,
- 'httpAuth' => $feed->httpAuth(),
- 'attributes' => $feed->attributes(),
- );
-
- $id = $feedDAO->addFeed($values);
+ $id = $feedDAO->addFeedObject($feed);
if (!$id) {
// There was an error in database… we cannot say what here.
throw new FreshRSS_FeedNotAdded_Exception($url);
@@ -469,7 +461,8 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
}
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 .
+ $text = 'An article was discovered by pull although we use PubSubHubbub!: Feed ' .
+ SimplePie_Misc::url_remove_credentials($url) .
' GUID ' . $entry->guid();
Minz_Log::warning($text, PSHB_LOG);
Minz_Log::warning($text);
@@ -528,7 +521,8 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
}
}
} elseif ($feed->url() !== $url) { // HTTP 301 Moved Permanently
- Minz_Log::notice('Feed ' . $url . ' moved permanently to ' . $feed->url(false));
+ Minz_Log::notice('Feed ' . SimplePie_Misc::url_remove_credentials($url) .
+ ' moved permanently to ' . SimplePie_Misc::url_remove_credentials($feed->url(false)));
$feedProperties['url'] = $feed->url();
}
@@ -629,6 +623,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
$databaseDAO = FreshRSS_Factory::createDatabaseDAO();
$databaseDAO->minorDbMaintenance();
} else {
+ FreshRSS_category_Controller::refreshDynamicOpmls();
list($updated_feeds, $feed, $nb_new_articles) = self::actualizeFeed($id, $url, $force, null, $noCommit, $maxFeeds);
}
diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php
index 8b2b9cf27..c7b2d579f 100644
--- a/app/Controllers/importExportController.php
+++ b/app/Controllers/importExportController.php
@@ -5,7 +5,10 @@
*/
class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
+ /** @var FreshRSS_EntryDAO */
private $entryDAO;
+
+ /** @var FreshRSS_FeedDAO */
private $feedDAO;
/**
@@ -96,7 +99,8 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
$importService = new FreshRSS_Import_Service($username);
foreach ($list_files['opml'] as $opml_file) {
- if (!$importService->importOpml($opml_file)) {
+ $importService->importOpml($opml_file);
+ if (!$importService->lastStatus()) {
$ok = false;
if (FreshRSS_Context::$isCli) {
fwrite(STDERR, 'FreshRSS error during OPML import' . "\n");
@@ -520,7 +524,7 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
$feed->_name($name);
$feed->_website($website);
if (!empty($origin['disable'])) {
- $feed->_ttl(-1 * FreshRSS_Context::$user_conf->ttl_default);
+ $feed->_mute(true);
}
// Call the extension hook
diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php
index 34770fffb..70b8824c3 100755
--- a/app/Controllers/indexController.php
+++ b/app/Controllers/indexController.php
@@ -174,6 +174,76 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
header('Content-Type: application/rss+xml; charset=utf-8');
}
+ public function opmlAction() {
+ $allow_anonymous = FreshRSS_Context::$system_conf->allow_anonymous;
+ $token = FreshRSS_Context::$user_conf->token;
+ $token_param = Minz_Request::param('token', '');
+ $token_is_ok = ($token != '' && $token === $token_param);
+
+ // Check if user has access.
+ if (!FreshRSS_Auth::hasAccess() && !$allow_anonymous && !$token_is_ok) {
+ Minz_Error::error(403);
+ }
+
+ try {
+ $this->updateContext();
+ } catch (FreshRSS_Context_Exception $e) {
+ Minz_Error::error(404);
+ }
+
+ $get = FreshRSS_Context::currentGet(true);
+ if (is_array($get)) {
+ $type = $get[0];
+ $id = $get[1];
+ } else {
+ $type = $get;
+ $id = '';
+ }
+
+ $catDAO = FreshRSS_Factory::createCategoryDao();
+ $categories = $catDAO->listCategories(true, true);
+ $this->view->excludeMutedFeeds = true;
+
+ switch ($type) {
+ case 'a':
+ $this->view->categories = $categories;
+ break;
+ case 'c':
+ $cat = $categories[$id] ?? null;
+ if ($cat == null) {
+ Minz_Error::error(404);
+ return;
+ }
+ $this->view->categories = [ $cat ];
+ break;
+ case 'f':
+ // We most likely already have the feed object in cache
+ $feed = FreshRSS_CategoryDAO::findFeed($categories, $id);
+ if ($feed == null) {
+ $feedDAO = FreshRSS_Factory::createFeedDao();
+ $feed = $feedDAO->searchById($id);
+ if ($feed == null) {
+ Minz_Error::error(404);
+ return;
+ }
+ }
+ $this->view->feeds = [ $feed ];
+ break;
+ case 's':
+ case 't':
+ case 'T':
+ default:
+ Minz_Error::error(404);
+ return;
+ }
+
+ require_once(LIB_PATH . '/lib_opml.php');
+
+ // No layout for OPML output.
+ $this->view->_layout(false);
+ header('Content-Type: application/xml; charset=utf-8');
+ }
+
/**
* This action updates the Context object by using request parameters.
*
diff --git a/app/Controllers/javascriptController.php b/app/Controllers/javascriptController.php
index 3eaae486a..c2a5cb872 100755
--- a/app/Controllers/javascriptController.php
+++ b/app/Controllers/javascriptController.php
@@ -8,6 +8,10 @@ class FreshRSS_javascript_Controller extends FreshRSS_ActionController {
public function actualizeAction() {
header('Content-Type: application/json; charset=UTF-8');
Minz_Session::_param('actualize_feeds', false);
+
+ $catDAO = FreshRSS_Factory::createCategoryDao();
+ $this->view->categories = $catDAO->listCategoriesOrderUpdate(FreshRSS_Context::$user_conf->dynamic_opml_ttl_default);
+
$feedDAO = FreshRSS_Factory::createFeedDao();
$this->view->feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$user_conf->ttl_default);
}
diff --git a/app/Controllers/subscriptionController.php b/app/Controllers/subscriptionController.php
index 8c7cf7e4a..cdf30b378 100644
--- a/app/Controllers/subscriptionController.php
+++ b/app/Controllers/subscriptionController.php
@@ -19,7 +19,7 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
$catDAO->checkDefault();
$feedDAO->updateTTL();
- $this->view->categories = $catDAO->listSortedCategories(false);
+ $this->view->categories = $catDAO->listSortedCategories(false, true, true);
$this->view->default_category = $catDAO->getDefault();
$signalError = false;
@@ -120,11 +120,8 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
$cat = intval(Minz_Request::param('category', 0));
- $mute = Minz_Request::param('mute', false);
- $ttl = intval(Minz_Request::param('ttl', FreshRSS_Feed::TTL_DEFAULT));
- if ($mute && FreshRSS_Feed::TTL_DEFAULT === $ttl) {
- $ttl = FreshRSS_Context::$user_conf->ttl_default;
- }
+ $feed->_ttl(intval(Minz_Request::param('ttl', FreshRSS_Feed::TTL_DEFAULT)));
+ $feed->_mute(boolval(Minz_Request::param('mute', false)));
$feed->_attributes('read_upon_gone', Minz_Request::paramTernary('read_upon_gone'));
$feed->_attributes('mark_updated_article_unread', Minz_Request::paramTernary('mark_updated_article_unread'));
@@ -196,8 +193,8 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
$feed->_filtersAction('read', preg_split('/[\n\r]+/', Minz_Request::param('filteractions_read', '')));
- $feed_kind = Minz_Request::param('feed_kind', FreshRSS_Feed::KIND_RSS);
- if ($feed_kind == FreshRSS_Feed::KIND_HTML_XPATH) {
+ $feed->_kind(intval(Minz_Request::param('feed_kind', FreshRSS_Feed::KIND_RSS)));
+ if ($feed->kind() == FreshRSS_Feed::KIND_HTML_XPATH) {
$xPathSettings = [];
if (Minz_Request::param('xPathItem', '') != '') $xPathSettings['item'] = Minz_Request::param('xPathItem', '', true);
if (Minz_Request::param('xPathItemTitle', '') != '') $xPathSettings['itemTitle'] = Minz_Request::param('xPathItemTitle', '', true);
@@ -214,7 +211,7 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
$values = array(
'name' => Minz_Request::param('name', ''),
- 'kind' => $feed_kind,
+ 'kind' => $feed->kind(),
'description' => sanitizeHTML(Minz_Request::param('description', '', true)),
'website' => checkUrl(Minz_Request::param('website', '')),
'url' => checkUrl(Minz_Request::param('url', '')),
@@ -222,7 +219,7 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
'pathEntries' => Minz_Request::param('path_entries', ''),
'priority' => intval(Minz_Request::param('priority', FreshRSS_Feed::PRIORITY_MAIN_STREAM)),
'httpAuth' => $httpAuth,
- 'ttl' => $ttl * ($mute ? -1 : 1),
+ 'ttl' => $feed->ttl(true),
'attributes' => $feed->attributes(),
);
@@ -300,7 +297,17 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
$position = Minz_Request::param('position');
$category->_attributes('position', '' === $position ? null : (int) $position);
+ $opml_url = checkUrl(Minz_Request::param('opml_url', ''));
+ if ($opml_url != '') {
+ $category->_kind(FreshRSS_Category::KIND_DYNAMIC_OPML);
+ $category->_attributes('opml_url', $opml_url);
+ } else {
+ $category->_kind(FreshRSS_Category::KIND_NORMAL);
+ $category->_attributes('opml_url', null);
+ }
+
$values = [
+ 'kind' => $category->kind(),
'name' => Minz_Request::param('name', ''),
'attributes' => $category->attributes(),
];