diff options
| author | 2022-07-04 09:53:26 +0200 | |
|---|---|---|
| committer | 2022-07-04 09:53:26 +0200 | |
| commit | 509c8cae6381ec46af7c8303eb92fda6ce496a4a (patch) | |
| tree | 653f7f44df842f9d7135decd89467879a0098c50 /app/Controllers | |
| parent | 57d571230eeb2d3ede57e640b640f17c7a2298a2 (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.php | 86 | ||||
| -rwxr-xr-x | app/Controllers/feedController.php | 25 | ||||
| -rw-r--r-- | app/Controllers/importExportController.php | 8 | ||||
| -rwxr-xr-x | app/Controllers/indexController.php | 70 | ||||
| -rwxr-xr-x | app/Controllers/javascriptController.php | 4 | ||||
| -rw-r--r-- | app/Controllers/subscriptionController.php | 27 |
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(), ]; |
