New feature: shareable user query (#6052)

* New feature: shareable user query
Share the output of a user query by RSS / HTML / OPML with other people through unique URLs.
Replaces the global admin token, which was the only option (but unsafe) to share RSS outputs with other people.
Also add a new HTML output for people without an RSS reader.

fix https://github.com/FreshRSS/FreshRSS/issues/3066#issuecomment-648977890
fix https://github.com/FreshRSS/FreshRSS/issues/3178#issuecomment-769435504

* Remove unused method

* Fix token saving

* Implement HTML view

* Update i18n for master token

* Revert i18n get_favorite

* Fix missing i18n for user queries from before this PR

* Remove irrelevant tests

* Add link to RSS version

* Fix getGet

* Fix getState

* Fix getSearch

* Alternative getSearch

* Default getOrder

* Explicit default state

* Fix test

* Add OPML sharing

* Remove many redundant SQL queries from original implementation of user queries

* Fix article tags

* Use default user settings

* Prepare public search

* Fixes

* Allow user search on article tags

* Implement user search

* Revert filter bug

* Revert wrong SQL left outer join change

* Implement checkboxes

* Safe check of OPML

* Fix label

* Remove RSS button to favour new sharing method
That sharing button was using a global admin token

* First version of HTTP 304

* Disallow some recusrivity
fix https://github.com/FreshRSS/FreshRSS/issues/6086

* Draft of nav

* Minor httpConditional

* Add support for offset for pagination

* Fix offset pagination

* Fix explicit order ASC

* Add documentation

* Help links i18n

* Note about deprecated master token

* Typo

* Doc about format
This commit is contained in:
Alexandre Alapetite 2024-02-26 09:01:03 +01:00 committed by GitHub
parent 25166c218b
commit 39cc1c11ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
105 changed files with 1477 additions and 809 deletions

View File

@ -15,7 +15,11 @@ Il y a une API pour les clients (mobiles), ainsi quune [interface en ligne de
Grâce au standard [WebSub](https://freshrss.github.io/FreshRSS/fr/users/08_PubSubHubbub.html), Grâce au standard [WebSub](https://freshrss.github.io/FreshRSS/fr/users/08_PubSubHubbub.html),
FreshRSS est capable de recevoir des notifications push instantanées depuis les sources compatibles, [Friendica](https://friendi.ca), [WordPress](https://wordpress.org/plugins/pubsubhubbub/), Blogger, Medium, etc. FreshRSS est capable de recevoir des notifications push instantanées depuis les sources compatibles, [Friendica](https://friendi.ca), [WordPress](https://wordpress.org/plugins/pubsubhubbub/), Blogger, Medium, etc.
FreshRSS supporte nativement le moissonnage du Web (Web Scraping) basique, basé sur [XPath](https://www.w3.org/TR/xpath-10/), pour les sites Web sans flux RSS / Atom. FreshRSS supporte nativement le [moissonnage du Web (Web Scraping)](https://freshrss.github.io/FreshRSS/en/users/11_website_scraping.html) basique,
basé sur [XPath](https://www.w3.org/TR/xpath-10/), pour les sites Web sans flux RSS / Atom.
Supporte aussi les documents JSON.
FreshRSS permet de [repartager des sélections darticles par HTML, RSS, et OPML](https://freshrss.github.io/FreshRSS/en/users/user_queries.html).
Plusieurs [méthodes de connexion](https://freshrss.github.io/FreshRSS/en/admins/09_AccessControl.html) sont supportées : formulaire Web (avec un mode anonyme), Authentification HTTP (compatible avec proxy), OpenID Connect. Plusieurs [méthodes de connexion](https://freshrss.github.io/FreshRSS/en/admins/09_AccessControl.html) sont supportées : formulaire Web (avec un mode anonyme), Authentification HTTP (compatible avec proxy), OpenID Connect.

View File

@ -15,7 +15,11 @@ There is an API for (mobile) clients, and a [Command-Line Interface](cli/README.
Thanks to the [WebSub](https://freshrss.github.io/FreshRSS/en/users/WebSub.html) standard, Thanks to the [WebSub](https://freshrss.github.io/FreshRSS/en/users/WebSub.html) standard,
FreshRSS is able to receive instant push notifications from compatible sources, such as [Friendica](https://friendi.ca), [WordPress](https://wordpress.org/plugins/pubsubhubbub/), Blogger, Medium, etc. FreshRSS is able to receive instant push notifications from compatible sources, such as [Friendica](https://friendi.ca), [WordPress](https://wordpress.org/plugins/pubsubhubbub/), Blogger, Medium, etc.
FreshRSS natively supports basic Web scraping, based on [XPath](https://www.w3.org/TR/xpath-10/), for Web sites not providing any RSS / Atom feed. FreshRSS natively supports basic [Web scraping](https://freshrss.github.io/FreshRSS/en/users/11_website_scraping.html),
based on [XPath](https://www.w3.org/TR/xpath-10/), for Web sites not providing any RSS / Atom feed.
Also supports JSON documents.
FreshRSS offers the ability to [reshare selections of articles by HTML, RSS, and OPML](https://freshrss.github.io/FreshRSS/en/users/user_queries.html).
Different [login methods](https://freshrss.github.io/FreshRSS/en/admins/09_AccessControl.html) are supported: Web form (including an anonymous option), HTTP Authentication (compatible with proxy delegation), OpenID Connect. Different [login methods](https://freshrss.github.io/FreshRSS/en/admins/09_AccessControl.html) are supported: Web form (including an anonymous option), HTTP Authentication (compatible with proxy delegation), OpenID Connect.

View File

@ -301,12 +301,8 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
public function queriesAction(): void { public function queriesAction(): void {
FreshRSS_View::appendScript(Minz_Url::display('/scripts/draggable.js?' . @filemtime(PUBLIC_PATH . '/scripts/draggable.js'))); FreshRSS_View::appendScript(Minz_Url::display('/scripts/draggable.js?' . @filemtime(PUBLIC_PATH . '/scripts/draggable.js')));
$category_dao = FreshRSS_Factory::createCategoryDao();
$feed_dao = FreshRSS_Factory::createFeedDao();
$tag_dao = FreshRSS_Factory::createTagDao();
if (Minz_Request::isPost()) { if (Minz_Request::isPost()) {
/** @var array<int,array{'get'?:string,'name'?:string,'order'?:string,'search'?:string,'state'?:int,'url'?:string}> $params */ /** @var array<int,array{'get'?:string,'name'?:string,'order'?:string,'search'?:string,'state'?:int,'url'?:string,'token'?:string}> $params */
$params = Minz_Request::paramArray('queries'); $params = Minz_Request::paramArray('queries');
$queries = []; $queries = [];
@ -318,7 +314,7 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
if (!empty($query['search'])) { if (!empty($query['search'])) {
$query['search'] = urldecode($query['search']); $query['search'] = urldecode($query['search']);
} }
$queries[$key] = (new FreshRSS_UserQuery($query, $feed_dao, $category_dao, $tag_dao))->toArray(); $queries[$key] = (new FreshRSS_UserQuery($query, FreshRSS_Context::categories(), FreshRSS_Context::labels()))->toArray();
} }
FreshRSS_Context::userConf()->queries = $queries; FreshRSS_Context::userConf()->queries = $queries;
FreshRSS_Context::userConf()->save(); FreshRSS_Context::userConf()->save();
@ -327,13 +323,13 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
} else { } else {
$this->view->queries = []; $this->view->queries = [];
foreach (FreshRSS_Context::userConf()->queries as $key => $query) { foreach (FreshRSS_Context::userConf()->queries as $key => $query) {
$this->view->queries[intval($key)] = new FreshRSS_UserQuery($query, $feed_dao, $category_dao, $tag_dao); $this->view->queries[intval($key)] = new FreshRSS_UserQuery($query, FreshRSS_Context::categories(), FreshRSS_Context::labels());
} }
} }
$this->view->categories = $category_dao->listCategories(false) ?: []; $this->view->categories = FreshRSS_Context::categories();
$this->view->feeds = $feed_dao->listFeeds(); $this->view->feeds = FreshRSS_Context::feeds();
$this->view->tags = $tag_dao->listTags() ?: []; $this->view->tags = FreshRSS_Context::labels();
if (Minz_Request::paramTernary('id') !== null) { if (Minz_Request::paramTernary('id') !== null) {
$id = Minz_Request::paramInt('id'); $id = Minz_Request::paramInt('id');
@ -363,20 +359,21 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
return; return;
} }
$category_dao = FreshRSS_Factory::createCategoryDao(); $query = new FreshRSS_UserQuery(FreshRSS_Context::userConf()->queries[$id], FreshRSS_Context::categories(), FreshRSS_Context::labels());
$feed_dao = FreshRSS_Factory::createFeedDao();
$tag_dao = FreshRSS_Factory::createTagDao();
$query = new FreshRSS_UserQuery(FreshRSS_Context::userConf()->queries[$id], $feed_dao, $category_dao, $tag_dao);
$this->view->query = $query; $this->view->query = $query;
$this->view->queryId = $id; $this->view->queryId = $id;
$this->view->categories = $category_dao->listCategories(false) ?: []; $this->view->categories = FreshRSS_Context::categories();
$this->view->feeds = $feed_dao->listFeeds(); $this->view->feeds = FreshRSS_Context::feeds();
$this->view->tags = $tag_dao->listTags() ?: []; $this->view->tags = FreshRSS_Context::labels();
if (Minz_Request::isPost()) { if (Minz_Request::isPost()) {
$params = array_filter(Minz_Request::paramArray('query')); $params = array_filter(Minz_Request::paramArray('query'));
$queryParams = []; $queryParams = [];
$name = Minz_Request::paramString('name') ?: _t('conf.query.number', $id + 1);
if ('' === $name) {
$name = _t('conf.query.number', $id + 1);
}
$queryParams['name'] = $name;
if (!empty($params['get']) && is_string($params['get'])) { if (!empty($params['get']) && is_string($params['get'])) {
$queryParams['get'] = htmlspecialchars_decode($params['get'], ENT_QUOTES); $queryParams['get'] = htmlspecialchars_decode($params['get'], ENT_QUOTES);
} }
@ -389,15 +386,21 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
if (!empty($params['state']) && is_array($params['state'])) { if (!empty($params['state']) && is_array($params['state'])) {
$queryParams['state'] = (int)(array_sum($params['state'])); $queryParams['state'] = (int)(array_sum($params['state']));
} }
$name = Minz_Request::paramString('name') ?: _t('conf.query.number', $id + 1); if (empty($params['token']) || !is_string($params['token'])) {
if ('' === $name) { $queryParams['token'] = FreshRSS_UserQuery::generateToken($name);
$name = _t('conf.query.number', $id + 1); } else {
$queryParams['token'] = $params['token'];
}
if (!empty($params['shareRss']) && ctype_digit($params['shareRss'])) {
$queryParams['shareRss'] = (bool)$params['shareRss'];
}
if (!empty($params['shareOpml']) && ctype_digit($params['shareOpml'])) {
$queryParams['shareOpml'] = (bool)$params['shareOpml'];
} }
$queryParams['name'] = $name;
$queryParams['url'] = Minz_Url::display(['params' => $queryParams]); $queryParams['url'] = Minz_Url::display(['params' => $queryParams]);
$queries = FreshRSS_Context::userConf()->queries; $queries = FreshRSS_Context::userConf()->queries;
$queries[$id] = (new FreshRSS_UserQuery($queryParams, $feed_dao, $category_dao, $tag_dao))->toArray(); $queries[$id] = (new FreshRSS_UserQuery($queryParams, FreshRSS_Context::categories(), FreshRSS_Context::labels()))->toArray();
FreshRSS_Context::userConf()->queries = $queries; FreshRSS_Context::userConf()->queries = $queries;
FreshRSS_Context::userConf()->save(); FreshRSS_Context::userConf()->save();
@ -433,18 +436,15 @@ class FreshRSS_configure_Controller extends FreshRSS_ActionController {
* lean data. * lean data.
*/ */
public function bookmarkQueryAction(): void { public function bookmarkQueryAction(): void {
$category_dao = FreshRSS_Factory::createCategoryDao();
$feed_dao = FreshRSS_Factory::createFeedDao();
$tag_dao = FreshRSS_Factory::createTagDao();
$queries = []; $queries = [];
foreach (FreshRSS_Context::userConf()->queries as $key => $query) { foreach (FreshRSS_Context::userConf()->queries as $key => $query) {
$queries[$key] = (new FreshRSS_UserQuery($query, $feed_dao, $category_dao, $tag_dao))->toArray(); $queries[$key] = (new FreshRSS_UserQuery($query, FreshRSS_Context::categories(), FreshRSS_Context::labels()))->toArray();
} }
$params = $_GET; $params = $_GET;
unset($params['rid']); unset($params['rid']);
$params['url'] = Minz_Url::display(['params' => $params]); $params['url'] = Minz_Url::display(['params' => $params]);
$params['name'] = _t('conf.query.number', count($queries) + 1); $params['name'] = _t('conf.query.number', count($queries) + 1);
$queries[] = (new FreshRSS_UserQuery($params, $feed_dao, $category_dao, $tag_dao))->toArray(); $queries[] = (new FreshRSS_UserQuery($params, FreshRSS_Context::categories(), FreshRSS_Context::labels()))->toArray();
FreshRSS_Context::userConf()->queries = $queries; FreshRSS_Context::userConf()->queries = $queries;
FreshRSS_Context::userConf()->save(); FreshRSS_Context::userConf()->save();

View File

@ -776,7 +776,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
*/ */
private static function applyLabelActions(int $nbNewEntries) { private static function applyLabelActions(int $nbNewEntries) {
$tagDAO = FreshRSS_Factory::createTagDao(); $tagDAO = FreshRSS_Factory::createTagDao();
$labels = $tagDAO->listTags() ?: []; $labels = FreshRSS_Context::labels();
$labels = array_filter($labels, static function (FreshRSS_Tag $label) { $labels = array_filter($labels, static function (FreshRSS_Tag $label) {
return !empty($label->filtersAction('label')); return !empty($label->filtersAction('label'));
}); });

View File

@ -364,7 +364,7 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
} }
$tagDAO = FreshRSS_Factory::createTagDao(); $tagDAO = FreshRSS_Factory::createTagDao();
$labels = $tagDAO->listTags() ?: []; $labels = FreshRSS_Context::labels();
$knownLabels = []; $knownLabels = [];
foreach ($labels as $label) { foreach ($labels as $label) {
$knownLabels[$label->name()]['id'] = $label->id(); $knownLabels[$label->name()]['id'] = $label->id();

View File

@ -6,6 +6,10 @@ declare(strict_types=1);
*/ */
class FreshRSS_index_Controller extends FreshRSS_ActionController { class FreshRSS_index_Controller extends FreshRSS_ActionController {
public function firstAction(): void {
$this->view->html_url = Minz_Url::display(['c' => 'index', 'a' => 'index'], 'html', 'root');
}
/** /**
* This action only redirect on the default view mode (normal or global) * This action only redirect on the default view mode (normal or global)
*/ */
@ -36,7 +40,7 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
} }
try { try {
FreshRSS_Context::updateUsingRequest(); FreshRSS_Context::updateUsingRequest(true);
} catch (FreshRSS_Context_Exception $e) { } catch (FreshRSS_Context_Exception $e) {
Minz_Error::error(404); Minz_Error::error(404);
} }
@ -48,7 +52,7 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
'media-src' => '*', 'media-src' => '*',
]); ]);
$this->view->categories = FreshRSS_Context::$categories; $this->view->categories = FreshRSS_Context::categories();
$this->view->rss_title = FreshRSS_Context::$name . ' | ' . FreshRSS_View::title(); $this->view->rss_title = FreshRSS_Context::$name . ' | ' . FreshRSS_View::title();
$title = FreshRSS_Context::$name; $title = FreshRSS_Context::$name;
@ -60,15 +64,10 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
FreshRSS_Context::$id_max = time() . '000000'; FreshRSS_Context::$id_max = time() . '000000';
$this->view->callbackBeforeFeeds = static function (FreshRSS_View $view) { $this->view->callbackBeforeFeeds = static function (FreshRSS_View $view) {
try { $view->tags = FreshRSS_Context::labels(true);
$tagDAO = FreshRSS_Factory::createTagDao(); $view->nbUnreadTags = 0;
$view->tags = $tagDAO->listTags(true) ?: []; foreach ($view->tags as $tag) {
$view->nbUnreadTags = 0; $view->nbUnreadTags += $tag->nbUnread();
foreach ($view->tags as $tag) {
$view->nbUnreadTags += $tag->nbUnread();
}
} catch (Exception $e) {
Minz_Log::notice($e->getMessage());
} }
}; };
@ -117,12 +116,12 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
FreshRSS_View::appendScript(Minz_Url::display('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js'))); FreshRSS_View::appendScript(Minz_Url::display('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js')));
try { try {
FreshRSS_Context::updateUsingRequest(); FreshRSS_Context::updateUsingRequest(true);
} catch (FreshRSS_Context_Exception $e) { } catch (FreshRSS_Context_Exception $e) {
Minz_Error::error(404); Minz_Error::error(404);
} }
$this->view->categories = FreshRSS_Context::$categories; $this->view->categories = FreshRSS_Context::categories();
$this->view->rss_title = FreshRSS_Context::$name . ' | ' . FreshRSS_View::title(); $this->view->rss_title = FreshRSS_Context::$name . ' | ' . FreshRSS_View::title();
$title = _t('index.feed.title_global'); $title = _t('index.feed.title_global');
@ -141,6 +140,7 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
/** /**
* This action displays the RSS feed of FreshRSS. * This action displays the RSS feed of FreshRSS.
* @deprecated See user query RSS sharing instead
*/ */
public function rssAction(): void { public function rssAction(): void {
$allow_anonymous = FreshRSS_Context::systemConf()->allow_anonymous; $allow_anonymous = FreshRSS_Context::systemConf()->allow_anonymous;
@ -156,7 +156,7 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
} }
try { try {
FreshRSS_Context::updateUsingRequest(); FreshRSS_Context::updateUsingRequest(false);
} catch (FreshRSS_Context_Exception $e) { } catch (FreshRSS_Context_Exception $e) {
Minz_Error::error(404); Minz_Error::error(404);
} }
@ -168,13 +168,19 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
Minz_Error::error(404); Minz_Error::error(404);
} }
// No layout for RSS output. $this->view->html_url = Minz_Url::display('', 'html', true);
$this->view->rss_url = PUBLIC_TO_INDEX_PATH . '/' . (empty($_SERVER['QUERY_STRING']) ? '' : '?' . $_SERVER['QUERY_STRING']);
$this->view->rss_title = FreshRSS_Context::$name . ' | ' . FreshRSS_View::title(); $this->view->rss_title = FreshRSS_Context::$name . ' | ' . FreshRSS_View::title();
$this->view->rss_url = htmlspecialchars(
PUBLIC_TO_INDEX_PATH . '/' . (empty($_SERVER['QUERY_STRING']) ? '' : '?' . $_SERVER['QUERY_STRING']), ENT_COMPAT, 'UTF-8');
// No layout for RSS output.
$this->view->_layout(null); $this->view->_layout(null);
header('Content-Type: application/rss+xml; charset=utf-8'); header('Content-Type: application/rss+xml; charset=utf-8');
} }
/**
* @deprecated See user query OPML sharing instead
*/
public function opmlAction(): void { public function opmlAction(): void {
$allow_anonymous = FreshRSS_Context::systemConf()->allow_anonymous; $allow_anonymous = FreshRSS_Context::systemConf()->allow_anonymous;
$token = FreshRSS_Context::userConf()->token; $token = FreshRSS_Context::userConf()->token;
@ -187,7 +193,7 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
} }
try { try {
FreshRSS_Context::updateUsingRequest(); FreshRSS_Context::updateUsingRequest(false);
} catch (FreshRSS_Context_Exception $e) { } catch (FreshRSS_Context_Exception $e) {
Minz_Error::error(404); Minz_Error::error(404);
} }
@ -196,25 +202,23 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
$type = (string)$get[0]; $type = (string)$get[0];
$id = (int)$get[1]; $id = (int)$get[1];
$catDAO = FreshRSS_Factory::createCategoryDao();
$categories = $catDAO->listCategories(true, true);
$this->view->excludeMutedFeeds = true; $this->view->excludeMutedFeeds = true;
switch ($type) { switch ($type) {
case 'a': case 'a':
$this->view->categories = $categories; $this->view->categories = FreshRSS_Context::categories();
break; break;
case 'c': case 'c':
$cat = $categories[$id] ?? null; $cat = FreshRSS_Context::categories()[$id] ?? null;
if ($cat == null) { if ($cat == null) {
Minz_Error::error(404); Minz_Error::error(404);
return; return;
} }
$this->view->categories = [ $cat ]; $this->view->categories = [ $cat->id() => $cat ];
break; break;
case 'f': case 'f':
// We most likely already have the feed object in cache // We most likely already have the feed object in cache
$feed = FreshRSS_CategoryDAO::findFeed($categories, $id); $feed = FreshRSS_Category::findFeed(FreshRSS_Context::categories(), $id);
if ($feed === null) { if ($feed === null) {
$feedDAO = FreshRSS_Factory::createFeedDao(); $feedDAO = FreshRSS_Factory::createFeedDao();
$feed = $feedDAO->searchById($id); $feed = $feedDAO->searchById($id);
@ -223,7 +227,7 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
return; return;
} }
} }
$this->view->feeds = [ $feed ]; $this->view->feeds = [ $feed->id() => $feed ];
break; break;
case 's': case 's':
case 't': case 't':
@ -255,17 +259,14 @@ class FreshRSS_index_Controller extends FreshRSS_ActionController {
$id = 0; $id = 0;
} }
$limit = FreshRSS_Context::$number;
$date_min = 0; $date_min = 0;
if (FreshRSS_Context::$sinceHours) { if (FreshRSS_Context::$sinceHours > 0) {
$date_min = time() - (FreshRSS_Context::$sinceHours * 3600); $date_min = time() - (FreshRSS_Context::$sinceHours * 3600);
$limit = FreshRSS_Context::userConf()->max_posts_per_rss;
} }
foreach ($entryDAO->listWhere( foreach ($entryDAO->listWhere(
$type, $id, FreshRSS_Context::$state, FreshRSS_Context::$order, $type, $id, FreshRSS_Context::$state, FreshRSS_Context::$order,
$limit, FreshRSS_Context::$first_id, FreshRSS_Context::$number, FreshRSS_Context::$offset, FreshRSS_Context::$first_id,
FreshRSS_Context::$search, $date_min) FreshRSS_Context::$search, $date_min)
as $entry) { as $entry) {
yield $entry; yield $entry;

View File

@ -193,7 +193,7 @@ class FreshRSS_stats_Controller extends FreshRSS_ActionController {
if ($id !== 0) { if ($id !== 0) {
$this->view->displaySlider = true; $this->view->displaySlider = true;
$feedDAO = FreshRSS_Factory::createFeedDao(); $feedDAO = FreshRSS_Factory::createFeedDao();
$this->view->feed = $feedDAO->searchById($id); $this->view->feed = $feedDAO->searchById($id) ?? FreshRSS_Feed::default();
} }
} }
@ -222,7 +222,7 @@ class FreshRSS_stats_Controller extends FreshRSS_ActionController {
} }
$this->view->categories = $categoryDAO->listCategories(true) ?: []; $this->view->categories = $categoryDAO->listCategories(true) ?: [];
$this->view->feed = $id === null ? null : $feedDAO->searchById($id); $this->view->feed = $id === null ? FreshRSS_Feed::default() : ($feedDAO->searchById($id) ?? FreshRSS_Feed::default());
$this->view->days = $statsDAO->getDays(); $this->view->days = $statsDAO->getDays();
$this->view->months = $statsDAO->getMonths(); $this->view->months = $statsDAO->getMonths();

View File

@ -59,7 +59,7 @@ class FreshRSS_subscription_Controller extends FreshRSS_ActionController {
break; break;
default: default:
$feedDAO = FreshRSS_Factory::createFeedDao(); $feedDAO = FreshRSS_Factory::createFeedDao();
$this->view->feed = $feedDAO->searchById($id); $this->view->feed = $feedDAO->searchById($id) ?? FreshRSS_Feed::default();
break; break;
} }
} }

View File

@ -199,6 +199,6 @@ class FreshRSS_tag_Controller extends FreshRSS_ActionController {
Minz_Error::error(403); Minz_Error::error(403);
} }
$tagDAO = FreshRSS_Factory::createTagDao(); $tagDAO = FreshRSS_Factory::createTagDao();
$this->view->tags = $tagDAO->listTags() ?: []; $this->view->tags = $tagDAO->listTags(true) ?: [];
} }
} }

View File

@ -143,7 +143,7 @@ class FreshRSS extends Minz_FrontController {
} }
} }
//Use prepend to insert before extensions. Added in reverse order. //Use prepend to insert before extensions. Added in reverse order.
if (Minz_Request::controllerName() !== 'index') { if (!in_array(Minz_Request::controllerName(), ['index', ''], true)) {
FreshRSS_View::prependScript(Minz_Url::display('/scripts/extra.js?' . @filemtime(PUBLIC_PATH . '/scripts/extra.js'))); FreshRSS_View::prependScript(Minz_Url::display('/scripts/extra.js?' . @filemtime(PUBLIC_PATH . '/scripts/extra.js')));
} }
FreshRSS_View::prependScript(Minz_Url::display('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js'))); FreshRSS_View::prependScript(Minz_Url::display('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js')));

View File

@ -16,14 +16,12 @@ class FreshRSS_BooleanSearch {
private string $operator; private string $operator;
/** @param 'AND'|'OR'|'AND NOT' $operator */ /** @param 'AND'|'OR'|'AND NOT' $operator */
public function __construct(string $input, int $level = 0, string $operator = 'AND') { public function __construct(string $input, int $level = 0, string $operator = 'AND', bool $allowUserQueries = true) {
$this->operator = $operator; $this->operator = $operator;
$input = trim($input); $input = trim($input);
if ($input === '') { if ($input === '') {
return; return;
} }
$this->raw_input = $input;
if ($level === 0) { if ($level === 0) {
$input = preg_replace('/:&quot;(.*?)&quot;/', ':"\1"', $input); $input = preg_replace('/:&quot;(.*?)&quot;/', ':"\1"', $input);
if (!is_string($input)) { if (!is_string($input)) {
@ -34,9 +32,11 @@ class FreshRSS_BooleanSearch {
return; return;
} }
$input = $this->parseUserQueryNames($input); $input = $this->parseUserQueryNames($input, $allowUserQueries);
$input = $this->parseUserQueryIds($input); $input = $this->parseUserQueryIds($input, $allowUserQueries);
$input = trim($input);
} }
$this->raw_input = $input;
// Either parse everything as a series of BooleanSearchs combined by implicit AND // Either parse everything as a series of BooleanSearchs combined by implicit AND
// or parse everything as a series of Searchs combined by explicit OR // or parse everything as a series of Searchs combined by explicit OR
@ -46,7 +46,7 @@ class FreshRSS_BooleanSearch {
/** /**
* Parse the user queries (saved searches) by name and expand them in the input string. * Parse the user queries (saved searches) by name and expand them in the input string.
*/ */
private function parseUserQueryNames(string $input): string { private function parseUserQueryNames(string $input, bool $allowUserQueries = true): string {
$all_matches = []; $all_matches = [];
if (preg_match_all('/\bsearch:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matchesFound)) { if (preg_match_all('/\bsearch:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matchesFound)) {
$all_matches[] = $matchesFound; $all_matches[] = $matchesFound;
@ -60,7 +60,7 @@ class FreshRSS_BooleanSearch {
/** @var array<string,FreshRSS_UserQuery> */ /** @var array<string,FreshRSS_UserQuery> */
$queries = []; $queries = [];
foreach (FreshRSS_Context::userConf()->queries as $raw_query) { foreach (FreshRSS_Context::userConf()->queries as $raw_query) {
$query = new FreshRSS_UserQuery($raw_query); $query = new FreshRSS_UserQuery($raw_query, FreshRSS_Context::categories(), FreshRSS_Context::labels());
$queries[$query->getName()] = $query; $queries[$query->getName()] = $query;
} }
@ -74,7 +74,11 @@ class FreshRSS_BooleanSearch {
$name = trim($matches['search'][$i]); $name = trim($matches['search'][$i]);
if (!empty($queries[$name])) { if (!empty($queries[$name])) {
$fromS[] = $matches[0][$i]; $fromS[] = $matches[0][$i];
$toS[] = '(' . trim($queries[$name]->getSearch()->getRawInput()) . ')'; if ($allowUserQueries) {
$toS[] = '(' . trim($queries[$name]->getSearch()->getRawInput()) . ')';
} else {
$toS[] = '';
}
} }
} }
} }
@ -87,7 +91,7 @@ class FreshRSS_BooleanSearch {
/** /**
* Parse the user queries (saved searches) by ID and expand them in the input string. * Parse the user queries (saved searches) by ID and expand them in the input string.
*/ */
private function parseUserQueryIds(string $input): string { private function parseUserQueryIds(string $input, bool $allowUserQueries = true): string {
$all_matches = []; $all_matches = [];
if (preg_match_all('/\bS:(?P<search>\d+)/', $input, $matchesFound)) { if (preg_match_all('/\bS:(?P<search>\d+)/', $input, $matchesFound)) {
@ -95,14 +99,10 @@ class FreshRSS_BooleanSearch {
} }
if (!empty($all_matches)) { if (!empty($all_matches)) {
$category_dao = FreshRSS_Factory::createCategoryDao();
$feed_dao = FreshRSS_Factory::createFeedDao();
$tag_dao = FreshRSS_Factory::createTagDao();
/** @var array<string,FreshRSS_UserQuery> */ /** @var array<string,FreshRSS_UserQuery> */
$queries = []; $queries = [];
foreach (FreshRSS_Context::userConf()->queries as $raw_query) { foreach (FreshRSS_Context::userConf()->queries as $raw_query) {
$query = new FreshRSS_UserQuery($raw_query, $feed_dao, $category_dao, $tag_dao); $query = new FreshRSS_UserQuery($raw_query, FreshRSS_Context::categories(), FreshRSS_Context::labels());
$queries[] = $query; $queries[] = $query;
} }
@ -117,7 +117,11 @@ class FreshRSS_BooleanSearch {
$id = (int)(trim($matches['search'][$i])) - 1; $id = (int)(trim($matches['search'][$i])) - 1;
if (!empty($queries[$id])) { if (!empty($queries[$id])) {
$fromS[] = $matches[0][$i]; $fromS[] = $matches[0][$i];
$toS[] = '(' . trim($queries[$id]->getSearch()->getRawInput()) . ')'; if ($allowUserQueries) {
$toS[] = '(' . trim($queries[$id]->getSearch()->getRawInput()) . ')';
} else {
$toS[] = '';
}
} }
} }
} }

View File

@ -95,7 +95,7 @@ class FreshRSS_Category extends Minz_Model {
} }
/** /**
* @return array<FreshRSS_Feed> * @return array<int,FreshRSS_Feed>
* @throws Minz_ConfigurationNamespaceException * @throws Minz_ConfigurationNamespaceException
* @throws Minz_PDOConnectionException * @throws Minz_PDOConnectionException
*/ */
@ -110,10 +110,8 @@ class FreshRSS_Category extends Minz_Model {
$this->nbNotRead += $feed->nbNotRead(); $this->nbNotRead += $feed->nbNotRead();
$this->hasFeedsWithError |= ($feed->inError() && !$feed->mute()); $this->hasFeedsWithError |= ($feed->inError() && !$feed->mute());
} }
$this->sortFeeds(); $this->sortFeeds();
} }
return $this->feeds ?? []; return $this->feeds ?? [];
} }
@ -143,7 +141,6 @@ class FreshRSS_Category extends Minz_Model {
if (!is_array($values)) { if (!is_array($values)) {
$values = [$values]; $values = [$values];
} }
$this->feeds = $values; $this->feeds = $values;
$this->sortFeeds(); $this->sortFeeds();
} }
@ -157,7 +154,6 @@ class FreshRSS_Category extends Minz_Model {
} }
$feed->_category($this); $feed->_category($this);
$this->feeds[] = $feed; $this->feeds[] = $feed;
$this->sortFeeds(); $this->sortFeeds();
} }
@ -243,8 +239,54 @@ class FreshRSS_Category extends Minz_Model {
if ($this->feeds === null) { if ($this->feeds === null) {
return; return;
} }
usort($this->feeds, static function (FreshRSS_Feed $a, FreshRSS_Feed $b) { uasort($this->feeds, static function (FreshRSS_Feed $a, FreshRSS_Feed $b) {
return strnatcasecmp($a->name(), $b->name()); return strnatcasecmp($a->name(), $b->name());
}); });
} }
/**
* Access cached feed
* @param array<FreshRSS_Category> $categories
*/
public static function findFeed(array $categories, int $feed_id): ?FreshRSS_Feed {
foreach ($categories as $category) {
foreach ($category->feeds() as $feed) {
if ($feed->id() === $feed_id) {
$feed->_category($category); // Should already be done; just to be safe
return $feed;
}
}
}
return null;
}
/**
* Access cached feeds
* @param array<FreshRSS_Category> $categories
* @return array<int,FreshRSS_Feed>
*/
public static function findFeeds(array $categories): array {
$result = [];
foreach ($categories as $category) {
foreach ($category->feeds() as $feed) {
$result[$feed->id()] = $feed;
}
}
return $result;
}
/**
* @param array<FreshRSS_Category> $categories
*/
public static function countUnread(array $categories, int $minPriority = 0): int {
$n = 0;
foreach ($categories as $category) {
foreach ($category->feeds() as $feed) {
if ($feed->priority() >= $minPriority) {
$n += $feed->nbNotRead();
}
}
}
return $n;
}
} }

View File

@ -245,19 +245,19 @@ SQL;
$sql = 'SELECT * FROM `_category` WHERE id=:id'; $sql = 'SELECT * FROM `_category` WHERE id=:id';
$res = $this->fetchAssoc($sql, ['id' => $id]) ?? []; $res = $this->fetchAssoc($sql, ['id' => $id]) ?? [];
/** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate':int,'error':int|bool,'attributes':string}> $res */ /** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate':int,'error':int|bool,'attributes':string}> $res */
$cat = self::daoToCategory($res); $categories = self::daoToCategories($res);
return $cat[0] ?? null; return reset($categories) ?: null;
} }
public function searchByName(string $name): ?FreshRSS_Category { public function searchByName(string $name): ?FreshRSS_Category {
$sql = 'SELECT * FROM `_category` WHERE name=:name'; $sql = 'SELECT * FROM `_category` WHERE name=:name';
$res = $this->fetchAssoc($sql, ['name' => $name]) ?? []; $res = $this->fetchAssoc($sql, ['name' => $name]) ?? [];
/** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate':int,'error':int|bool,'attributes':string}> $res */ /** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate':int,'error':int|bool,'attributes':string}> $res */
$cat = self::daoToCategory($res); $categories = self::daoToCategories($res);
return $cat[0] ?? null; return reset($categories) ?: null;
} }
/** @return array<FreshRSS_Category> */ /** @return array<int,FreshRSS_Category> */
public function listSortedCategories(bool $prePopulateFeeds = true, bool $details = false): array { public function listSortedCategories(bool $prePopulateFeeds = true, bool $details = false): array {
$categories = $this->listCategories($prePopulateFeeds, $details); $categories = $this->listCategories($prePopulateFeeds, $details);
@ -277,7 +277,7 @@ SQL;
return $categories; return $categories;
} }
/** @return array<FreshRSS_Category> */ /** @return array<int,FreshRSS_Category> */
public function listCategories(bool $prePopulateFeeds = true, bool $details = false): array { public function listCategories(bool $prePopulateFeeds = true, bool $details = false): array {
if ($prePopulateFeeds) { if ($prePopulateFeeds) {
$sql = 'SELECT c.id AS c_id, c.name AS c_name, c.kind AS c_kind, c.`lastUpdate` AS c_last_update, c.error AS c_error, c.attributes AS c_attributes, ' $sql = 'SELECT c.id AS c_id, c.name AS c_name, c.kind AS c_kind, c.`lastUpdate` AS c_last_update, c.error AS c_error, c.attributes AS c_attributes, '
@ -293,7 +293,7 @@ SQL;
$res = $stm->fetchAll(PDO::FETCH_ASSOC) ?: []; $res = $stm->fetchAll(PDO::FETCH_ASSOC) ?: [];
/** @var array<array{'c_name':string,'c_id':int,'c_kind':int,'c_last_update':int,'c_error':int|bool,'c_attributes'?:string, /** @var array<array{'c_name':string,'c_id':int,'c_kind':int,'c_last_update':int,'c_error':int|bool,'c_attributes'?:string,
* 'id'?:int,'name'?:string,'url'?:string,'kind'?:int,'category'?:int,'website'?:string,'priority'?:int,'error'?:int|bool,'cache_nbEntries'?:int,'cache_nbUnreads'?:int,'ttl'?:int}> $res */ * 'id'?:int,'name'?:string,'url'?:string,'kind'?:int,'category'?:int,'website'?:string,'priority'?:int,'error'?:int|bool,'cache_nbEntries'?:int,'cache_nbUnreads'?:int,'ttl'?:int}> $res */
return self::daoToCategoryPrepopulated($res); return self::daoToCategoriesPrepopulated($res);
} else { } else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
if ($this->autoUpdateDb($info)) { if ($this->autoUpdateDb($info)) {
@ -305,11 +305,11 @@ SQL;
} else { } else {
$res = $this->fetchAssoc('SELECT * FROM `_category` ORDER BY name'); $res = $this->fetchAssoc('SELECT * FROM `_category` ORDER BY name');
/** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate'?:int,'error'?:int|bool,'attributes'?:string}> $res */ /** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate'?:int,'error'?:int|bool,'attributes'?:string}> $res */
return $res == null ? [] : self::daoToCategory($res); return empty($res) ? [] : self::daoToCategories($res);
} }
} }
/** @return array<FreshRSS_Category> */ /** @return array<int,FreshRSS_Category> */
public function listCategoriesOrderUpdate(int $defaultCacheDuration = 86400, int $limit = 0): array { public function listCategoriesOrderUpdate(int $defaultCacheDuration = 86400, int $limit = 0): array {
$sql = 'SELECT * FROM `_category` WHERE kind = :kind AND `lastUpdate` < :lu ORDER BY `lastUpdate`' $sql = 'SELECT * FROM `_category` WHERE kind = :kind AND `lastUpdate` < :lu ORDER BY `lastUpdate`'
. ($limit < 1 ? '' : ' LIMIT ' . $limit); . ($limit < 1 ? '' : ' LIMIT ' . $limit);
@ -318,7 +318,7 @@ SQL;
$stm->bindValue(':kind', FreshRSS_Category::KIND_DYNAMIC_OPML, PDO::PARAM_INT) && $stm->bindValue(':kind', FreshRSS_Category::KIND_DYNAMIC_OPML, PDO::PARAM_INT) &&
$stm->bindValue(':lu', time() - $defaultCacheDuration, PDO::PARAM_INT) && $stm->bindValue(':lu', time() - $defaultCacheDuration, PDO::PARAM_INT) &&
$stm->execute()) { $stm->execute()) {
return self::daoToCategory($stm->fetchAll(PDO::FETCH_ASSOC)); return self::daoToCategories($stm->fetchAll(PDO::FETCH_ASSOC));
} else { } else {
$info = $stm ? $stm->errorInfo() : $this->pdo->errorInfo(); $info = $stm ? $stm->errorInfo() : $this->pdo->errorInfo();
if ($this->autoUpdateDb($info)) { if ($this->autoUpdateDb($info)) {
@ -333,9 +333,9 @@ SQL;
$sql = 'SELECT * FROM `_category` WHERE id=:id'; $sql = 'SELECT * FROM `_category` WHERE id=:id';
$res = $this->fetchAssoc($sql, [':id' => self::DEFAULTCATEGORYID]) ?? []; $res = $this->fetchAssoc($sql, [':id' => self::DEFAULTCATEGORYID]) ?? [];
/** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate'?:int,'error'?:int|bool,'attributes'?:string}> $res */ /** @var array<array{'name':string,'id':int,'kind':int,'lastUpdate'?:int,'error'?:int|bool,'attributes'?:string}> $res */
$cat = self::daoToCategory($res); $categories = self::daoToCategories($res);
if (isset($cat[0])) { if (isset($categories[self::DEFAULTCATEGORYID])) {
return $cat[0]; return $categories[self::DEFAULTCATEGORYID];
} else { } else {
if (FreshRSS_Context::$isCli) { if (FreshRSS_Context::$isCli) {
fwrite(STDERR, 'FreshRSS database error: Default category not found!' . "\n"); fwrite(STDERR, 'FreshRSS database error: Default category not found!' . "\n");
@ -394,41 +394,13 @@ SQL;
return isset($res[0]) ? (int)$res[0] : -1; return isset($res[0]) ? (int)$res[0] : -1;
} }
/** @param array<FreshRSS_Category> $categories */
public static function findFeed(array $categories, int $feed_id): ?FreshRSS_Feed {
foreach ($categories as $category) {
foreach ($category->feeds() as $feed) {
if ($feed->id() === $feed_id) {
$feed->_category($category); // Should already be done; just to be safe
return $feed;
}
}
}
return null;
}
/**
* @param array<FreshRSS_Category> $categories
*/
public static function countUnread(array $categories, int $minPriority = 0): int {
$n = 0;
foreach ($categories as $category) {
foreach ($category->feeds() as $feed) {
if ($feed->priority() >= $minPriority) {
$n += $feed->nbNotRead();
}
}
}
return $n;
}
/** /**
* @param array<array{'c_name':string,'c_id':int,'c_kind':int,'c_last_update':int,'c_error':int|bool,'c_attributes'?:string, * @param array<array{'c_name':string,'c_id':int,'c_kind':int,'c_last_update':int,'c_error':int|bool,'c_attributes'?:string,
* 'id'?:int,'name'?:string,'url'?:string,'kind'?:int,'website'?:string,'priority'?:int, * 'id'?:int,'name'?:string,'url'?:string,'kind'?:int,'website'?:string,'priority'?:int,
* 'error'?:int|bool,'cache_nbEntries'?:int,'cache_nbUnreads'?:int,'ttl'?:int}> $listDAO * 'error'?:int|bool,'cache_nbEntries'?:int,'cache_nbUnreads'?:int,'ttl'?:int}> $listDAO
* @return array<int,FreshRSS_Category> * @return array<int,FreshRSS_Category>
*/ */
private static function daoToCategoryPrepopulated(array $listDAO): array { private static function daoToCategoriesPrepopulated(array $listDAO): array {
$list = []; $list = [];
$previousLine = []; $previousLine = [];
$feedsDao = []; $feedsDao = [];
@ -441,11 +413,11 @@ SQL;
$cat = new FreshRSS_Category( $cat = new FreshRSS_Category(
$previousLine['c_name'], $previousLine['c_name'],
$previousLine['c_id'], $previousLine['c_id'],
$feedDao::daoToFeed($feedsDao, $previousLine['c_id']) $feedDao::daoToFeeds($feedsDao, $previousLine['c_id'])
); );
$cat->_kind($previousLine['c_kind']); $cat->_kind($previousLine['c_kind']);
$cat->_attributes($previousLine['c_attributes'] ?? '[]'); $cat->_attributes($previousLine['c_attributes'] ?? '[]');
$list[(int)$previousLine['c_id']] = $cat; $list[$cat->id()] = $cat;
$feedsDao = []; //Prepare for next category $feedsDao = []; //Prepare for next category
} }
@ -459,13 +431,13 @@ SQL;
$cat = new FreshRSS_Category( $cat = new FreshRSS_Category(
$previousLine['c_name'], $previousLine['c_name'],
$previousLine['c_id'], $previousLine['c_id'],
$feedDao::daoToFeed($feedsDao, $previousLine['c_id']) $feedDao::daoToFeeds($feedsDao, $previousLine['c_id'])
); );
$cat->_kind($previousLine['c_kind']); $cat->_kind($previousLine['c_kind']);
$cat->_lastUpdate($previousLine['c_last_update'] ?? 0); $cat->_lastUpdate($previousLine['c_last_update'] ?? 0);
$cat->_error($previousLine['c_error'] ?? 0); $cat->_error($previousLine['c_error'] ?? 0);
$cat->_attributes($previousLine['c_attributes'] ?? []); $cat->_attributes($previousLine['c_attributes'] ?? []);
$list[(int)$previousLine['c_id']] = $cat; $list[$cat->id()] = $cat;
} }
return $list; return $list;
@ -473,11 +445,10 @@ SQL;
/** /**
* @param array<array{'name':string,'id':int,'kind':int,'lastUpdate'?:int,'error'?:int|bool,'attributes'?:string}> $listDAO * @param array<array{'name':string,'id':int,'kind':int,'lastUpdate'?:int,'error'?:int|bool,'attributes'?:string}> $listDAO
* @return array<FreshRSS_Category> * @return array<int,FreshRSS_Category>
*/ */
private static function daoToCategory(array $listDAO): array { private static function daoToCategories(array $listDAO): array {
$list = []; $list = [];
foreach ($listDAO as $dao) { foreach ($listDAO as $dao) {
FreshRSS_DatabaseDAO::pdoInt($dao, ['id', 'kind', 'lastUpdate', 'error']); FreshRSS_DatabaseDAO::pdoInt($dao, ['id', 'kind', 'lastUpdate', 'error']);
$cat = new FreshRSS_Category( $cat = new FreshRSS_Category(
@ -488,9 +459,8 @@ SQL;
$cat->_lastUpdate($dao['lastUpdate'] ?? 0); $cat->_lastUpdate($dao['lastUpdate'] ?? 0);
$cat->_error($dao['error'] ?? 0); $cat->_error($dao['error'] ?? 0);
$cat->_attributes($dao['attributes'] ?? ''); $cat->_attributes($dao['attributes'] ?? '');
$list[] = $cat; $list[$cat->id()] = $cat;
} }
return $list; return $list;
} }
} }

View File

@ -10,11 +10,11 @@ final class FreshRSS_Context {
/** /**
* @var array<int,FreshRSS_Category> * @var array<int,FreshRSS_Category>
*/ */
public static array $categories = []; private static array $categories = [];
/** /**
* @var array<int,FreshRSS_Tag> * @var array<int,FreshRSS_Tag>
*/ */
public static array $tags = []; private static array $tags = [];
public static string $name = ''; public static string $name = '';
public static string $description = ''; public static string $description = '';
public static int $total_unread = 0; public static int $total_unread = 0;
@ -47,6 +47,7 @@ final class FreshRSS_Context {
*/ */
public static string $order = 'DESC'; public static string $order = 'DESC';
public static int $number = 0; public static int $number = 0;
public static int $offset = 0;
public static FreshRSS_BooleanSearch $search; public static FreshRSS_BooleanSearch $search;
public static string $first_id = ''; public static string $first_id = '';
public static string $next_id = ''; public static string $next_id = '';
@ -173,10 +174,33 @@ final class FreshRSS_Context {
FreshRSS_Context::$user_conf = null; FreshRSS_Context::$user_conf = null;
} }
/** @return array<int,FreshRSS_Category> */
public static function categories(): array {
if (empty(self::$categories)) {
$catDAO = FreshRSS_Factory::createCategoryDao();
self::$categories = $catDAO->listSortedCategories(true, false);
}
return self::$categories;
}
/** @return array<int,FreshRSS_Feed> */
public static function feeds(): array {
return FreshRSS_Category::findFeeds(self::categories());
}
/** @return array<int,FreshRSS_Tag> */
public static function labels(bool $precounts = false): array {
if (empty(self::$tags) || $precounts) {
$tagDAO = FreshRSS_Factory::createTagDao();
self::$tags = $tagDAO->listTags($precounts) ?: [];
}
return self::$tags;
}
/** /**
* This action updates the Context object by using request parameters. * This action updates the Context object by using request parameters.
* *
* Parameters are: * HTTP GET request parameters are:
* - state (default: conf->default_view) * - state (default: conf->default_view)
* - search (default: empty string) * - search (default: empty string)
* - order (default: conf->sort_order) * - order (default: conf->sort_order)
@ -187,18 +211,15 @@ final class FreshRSS_Context {
* @throws Minz_ConfigurationNamespaceException * @throws Minz_ConfigurationNamespaceException
* @throws Minz_PDOConnectionException * @throws Minz_PDOConnectionException
*/ */
public static function updateUsingRequest(): void { public static function updateUsingRequest(bool $computeStatistics): void {
if (empty(self::$categories)) { if ($computeStatistics && self::$total_unread === 0) {
$catDAO = FreshRSS_Factory::createCategoryDao(); // Update number of read / unread variables.
self::$categories = $catDAO->listSortedCategories(); $entryDAO = FreshRSS_Factory::createEntryDao();
self::$total_starred = $entryDAO->countUnreadReadFavorites();
self::$total_unread = FreshRSS_Category::countUnread(self::categories(), FreshRSS_Feed::PRIORITY_MAIN_STREAM);
self::$total_important_unread = FreshRSS_Category::countUnread(self::categories(), FreshRSS_Feed::PRIORITY_IMPORTANT);
} }
// Update number of read / unread variables.
$entryDAO = FreshRSS_Factory::createEntryDao();
self::$total_starred = $entryDAO->countUnreadReadFavorites();
self::$total_unread = FreshRSS_CategoryDAO::countUnread(self::$categories, FreshRSS_Feed::PRIORITY_MAIN_STREAM);
self::$total_important_unread = FreshRSS_CategoryDAO::countUnread(self::$categories, FreshRSS_Feed::PRIORITY_IMPORTANT);
self::_get(Minz_Request::paramString('get') ?: 'a'); self::_get(Minz_Request::paramString('get') ?: 'a');
self::$state = Minz_Request::paramInt('state') ?: FreshRSS_Context::userConf()->default_state; self::$state = Minz_Request::paramInt('state') ?: FreshRSS_Context::userConf()->default_state;
@ -224,6 +245,7 @@ final class FreshRSS_Context {
FreshRSS_Context::userConf()->max_posts_per_rss, FreshRSS_Context::userConf()->max_posts_per_rss,
FreshRSS_Context::userConf()->posts_per_page); FreshRSS_Context::userConf()->posts_per_page);
} }
self::$offset = Minz_Request::paramInt('offset');
self::$first_id = Minz_Request::paramString('next'); self::$first_id = Minz_Request::paramString('next');
self::$sinceHours = Minz_Request::paramInt('hours'); self::$sinceHours = Minz_Request::paramInt('hours');
} }
@ -394,7 +416,7 @@ final class FreshRSS_Context {
break; break;
case 'f': case 'f':
// We try to find the corresponding feed. When allowing robots, always retrieve the full feed including description // We try to find the corresponding feed. When allowing robots, always retrieve the full feed including description
$feed = FreshRSS_Context::systemConf()->allow_robots ? null : FreshRSS_CategoryDAO::findFeed(self::$categories, $id); $feed = FreshRSS_Context::systemConf()->allow_robots ? null : FreshRSS_Category::findFeed(self::$categories, $id);
if ($feed === null) { if ($feed === null) {
$feedDAO = FreshRSS_Factory::createFeedDao(); $feedDAO = FreshRSS_Factory::createFeedDao();
$feed = $feedDAO->searchById($id); $feed = $feedDAO->searchById($id);
@ -417,7 +439,7 @@ final class FreshRSS_Context {
if ($cat === null) { if ($cat === null) {
throw new FreshRSS_Context_Exception('Invalid category: ' . $id); throw new FreshRSS_Context_Exception('Invalid category: ' . $id);
} }
//self::$categories[$id] = $cat; self::$categories[$id] = $cat;
} else { } else {
$cat = self::$categories[$id]; $cat = self::$categories[$id];
} }
@ -433,7 +455,7 @@ final class FreshRSS_Context {
if ($tag === null) { if ($tag === null) {
throw new FreshRSS_Context_Exception('Invalid tag: ' . $id); throw new FreshRSS_Context_Exception('Invalid tag: ' . $id);
} }
//self::$tags[$id] = $tag; self::$tags[$id] = $tag;
} else { } else {
$tag = self::$tags[$id]; $tag = self::$tags[$id];
} }

View File

@ -815,6 +815,28 @@ HTML;
]; ];
} }
/**
* @return array{array<string>,array<string>} Array of first tags to show, then array of remaining tags
*/
public function tagsFormattingHelper(): array {
$firstTags = [];
$remainingTags = [];
if (FreshRSS_Context::hasUserConf() && in_array(FreshRSS_Context::userConf()->show_tags, ['b', 'f', 'h'], true)) {
$maxTagsDisplayed = (int)FreshRSS_Context::userConf()->show_tags_max;
$tags = $this->tags();
if (!empty($tags)) {
if ($maxTagsDisplayed > 0) {
$firstTags = array_slice($tags, 0, $maxTagsDisplayed);
$remainingTags = array_slice($tags, $maxTagsDisplayed);
} else {
$firstTags = $tags;
}
}
}
return [$firstTags,$remainingTags];
}
/** /**
* Integer format conversion for Google Reader API format * Integer format conversion for Google Reader API format
* @param string|int $dec Decimal number * @param string|int $dec Decimal number

View File

@ -1063,7 +1063,7 @@ SQL;
* @throws FreshRSS_EntriesGetter_Exception * @throws FreshRSS_EntriesGetter_Exception
*/ */
private function sqlListWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL, private function sqlListWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL,
string $order = 'DESC', int $limit = 1, string $firstId = '', ?FreshRSS_BooleanSearch $filters = null, string $order = 'DESC', int $limit = 1, int $offset = 0, string $firstId = '', ?FreshRSS_BooleanSearch $filters = null,
int $date_min = 0): array { int $date_min = 0): array {
if (!$state) { if (!$state) {
$state = FreshRSS_Entry::STATE_ALL; $state = FreshRSS_Entry::STATE_ALL;
@ -1120,7 +1120,9 @@ SQL;
. 'WHERE ' . $where . 'WHERE ' . $where
. $search . $search
. 'ORDER BY e.id ' . $order . 'ORDER BY e.id ' . $order
. ($limit > 0 ? ' LIMIT ' . intval($limit) : '')]; //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ . ($limit > 0 ? ' LIMIT ' . $limit : '') // http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/
. ($offset > 0 ? ' OFFSET ' . $offset : '')
];
} }
/** /**
@ -1131,9 +1133,9 @@ SQL;
* @throws FreshRSS_EntriesGetter_Exception * @throws FreshRSS_EntriesGetter_Exception
*/ */
private function listWhereRaw(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL, private function listWhereRaw(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL,
string $order = 'DESC', int $limit = 1, string $firstId = '', ?FreshRSS_BooleanSearch $filters = null, string $order = 'DESC', int $limit = 1, int $offset = 0, string $firstId = '', ?FreshRSS_BooleanSearch $filters = null,
int $date_min = 0) { int $date_min = 0) {
[$values, $sql] = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filters, $date_min); [$values, $sql] = $this->sqlListWhere($type, $id, $state, $order, $limit, $offset, $firstId, $filters, $date_min);
if ($order !== 'DESC' && $order !== 'ASC') { if ($order !== 'DESC' && $order !== 'ASC') {
$order = 'DESC'; $order = 'DESC';
@ -1152,7 +1154,7 @@ SQL;
} else { } else {
$info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo(); $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
if ($this->autoUpdateDb($info)) { if ($this->autoUpdateDb($info)) {
return $this->listWhereRaw($type, $id, $state, $order, $limit, $firstId, $filters, $date_min); return $this->listWhereRaw($type, $id, $state, $order, $limit, $offset, $firstId, $filters, $date_min);
} }
Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info)); Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
return false; return false;
@ -1167,9 +1169,9 @@ SQL;
* @throws FreshRSS_EntriesGetter_Exception * @throws FreshRSS_EntriesGetter_Exception
*/ */
public function listWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL, public function listWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL,
string $order = 'DESC', int $limit = 1, string $firstId = '', string $order = 'DESC', int $limit = 1, int $offset = 0, string $firstId = '',
?FreshRSS_BooleanSearch $filters = null, int $date_min = 0): Traversable { ?FreshRSS_BooleanSearch $filters = null, int $date_min = 0): Traversable {
$stm = $this->listWhereRaw($type, $id, $state, $order, $limit, $firstId, $filters, $date_min); $stm = $this->listWhereRaw($type, $id, $state, $order, $limit, $offset, $firstId, $filters, $date_min);
if ($stm) { if ($stm) {
while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
if (is_array($row)) { if (is_array($row)) {
@ -1233,9 +1235,9 @@ SQL;
* @throws FreshRSS_EntriesGetter_Exception * @throws FreshRSS_EntriesGetter_Exception
*/ */
public function listIdsWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL, public function listIdsWhere(string $type = 'a', int $id = 0, int $state = FreshRSS_Entry::STATE_ALL,
string $order = 'DESC', int $limit = 1, string $firstId = '', ?FreshRSS_BooleanSearch $filters = null): ?array { string $order = 'DESC', int $limit = 1, int $offset = 0, string $firstId = '', ?FreshRSS_BooleanSearch $filters = null): ?array {
[$values, $sql] = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filters); [$values, $sql] = $this->sqlListWhere($type, $id, $state, $order, $limit, $offset, $firstId, $filters);
$stm = $this->pdo->prepare($sql); $stm = $this->pdo->prepare($sql);
if ($stm !== false && $stm->execute($values) && ($res = $stm->fetchAll(PDO::FETCH_COLUMN, 0)) !== false) { if ($stm !== false && $stm->execute($values) && ($res = $stm->fetchAll(PDO::FETCH_COLUMN, 0)) !== false) {
/** @var array<numeric-string> $res */ /** @var array<numeric-string> $res */

View File

@ -76,7 +76,7 @@ class FreshRSS_Feed extends Minz_Model {
} }
} }
public static function example(): FreshRSS_Feed { public static function default(): FreshRSS_Feed {
$f = new FreshRSS_Feed('http://example.net/', false); $f = new FreshRSS_Feed('http://example.net/', false);
$f->faviconPrepare(); $f->faviconPrepare();
return $f; return $f;
@ -708,7 +708,8 @@ class FreshRSS_Feed extends Minz_Model {
$view = new FreshRSS_View(); $view = new FreshRSS_View();
$view->_path('index/rss.phtml'); $view->_path('index/rss.phtml');
$view->internal_rendering = true; $view->internal_rendering = true;
$view->rss_url = $feedSourceUrl; $view->rss_url = htmlspecialchars($feedSourceUrl, ENT_COMPAT, 'UTF-8');
$view->html_url = $view->rss_url;
$view->entries = []; $view->entries = [];
try { try {

View File

@ -322,7 +322,7 @@ SQL;
} }
/** @var array<int,array{'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int, /** @var array<int,array{'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int,
* 'priority'?:int,'pathEntries'?:string,'httpAuth':string,'error':int,'ttl'?:int,'attributes'?:string}> $res */ * 'priority'?:int,'pathEntries'?:string,'httpAuth':string,'error':int,'ttl'?:int,'attributes'?:string}> $res */
$feeds = self::daoToFeed($res); $feeds = self::daoToFeeds($res);
return $feeds[$id] ?? null; return $feeds[$id] ?? null;
} }
@ -331,7 +331,7 @@ SQL;
$res = $this->fetchAssoc($sql, [':url' => $url]); $res = $this->fetchAssoc($sql, [':url' => $url]);
/** @var array<int,array{'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int, /** @var array<int,array{'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int,
* 'priority'?:int,'pathEntries'?:string,'httpAuth':string,'error':int,'ttl'?:int,'attributes'?:string}> $res */ * 'priority'?:int,'pathEntries'?:string,'httpAuth':string,'error':int,'ttl'?:int,'attributes'?:string}> $res */
return empty($res[0]) ? null : (current(self::daoToFeed($res)) ?: null); return empty($res[0]) ? null : (current(self::daoToFeeds($res)) ?: null);
} }
/** @return array<int> */ /** @return array<int> */
@ -343,14 +343,14 @@ SQL;
} }
/** /**
* @return array<FreshRSS_Feed> * @return array<int,FreshRSS_Feed>
*/ */
public function listFeeds(): array { public function listFeeds(): array {
$sql = 'SELECT * FROM `_feed` ORDER BY name'; $sql = 'SELECT * FROM `_feed` ORDER BY name';
$res = $this->fetchAssoc($sql); $res = $this->fetchAssoc($sql);
/** @var array<array{'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int, /** @var array<array{'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int,
* 'priority':int,'pathEntries':string,'httpAuth':string,'error':int,'ttl':int,'attributes':string}>|null $res */ * 'priority':int,'pathEntries':string,'httpAuth':string,'error':int,'ttl':int,'attributes':string}>|null $res */
return $res == null ? [] : self::daoToFeed($res); return $res == null ? [] : self::daoToFeeds($res);
} }
/** @return array<string,string> */ /** @return array<string,string> */
@ -375,7 +375,7 @@ SQL;
/** /**
* @param int $defaultCacheDuration Use -1 to return all feeds, without filtering them by TTL. * @param int $defaultCacheDuration Use -1 to return all feeds, without filtering them by TTL.
* @return array<FreshRSS_Feed> * @return array<int,FreshRSS_Feed>
*/ */
public function listFeedsOrderUpdate(int $defaultCacheDuration = 3600, int $limit = 0): array { public function listFeedsOrderUpdate(int $defaultCacheDuration = 3600, int $limit = 0): array {
$sql = 'SELECT id, url, kind, category, name, website, `lastUpdate`, `pathEntries`, `httpAuth`, ttl, attributes, `cache_nbEntries`, `cache_nbUnreads` ' $sql = 'SELECT id, url, kind, category, name, website, `lastUpdate`, `pathEntries`, `httpAuth`, ttl, attributes, `cache_nbEntries`, `cache_nbUnreads` '
@ -387,7 +387,7 @@ SQL;
. ($limit < 1 ? '' : 'LIMIT ' . intval($limit)); . ($limit < 1 ? '' : 'LIMIT ' . intval($limit));
$stm = $this->pdo->query($sql); $stm = $this->pdo->query($sql);
if ($stm !== false) { if ($stm !== false) {
return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC)); return self::daoToFeeds($stm->fetchAll(PDO::FETCH_ASSOC));
} else { } else {
$info = $this->pdo->errorInfo(); $info = $this->pdo->errorInfo();
if ($this->autoUpdateDb($info)) { if ($this->autoUpdateDb($info)) {
@ -409,7 +409,7 @@ SQL;
/** /**
* @param bool|null $muted to include only muted feeds * @param bool|null $muted to include only muted feeds
* @return array<FreshRSS_Feed> * @return array<int,FreshRSS_Feed>
*/ */
public function listByCategory(int $cat, ?bool $muted = null): array { public function listByCategory(int $cat, ?bool $muted = null): array {
$sql = 'SELECT * FROM `_feed` WHERE category=:category'; $sql = 'SELECT * FROM `_feed` WHERE category=:category';
@ -425,9 +425,9 @@ SQL;
* @var array<int,array{'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int, * @var array<int,array{'url':string,'kind':int,'category':int,'name':string,'website':string,'lastUpdate':int,
* 'priority'?:int,'pathEntries'?:string,'httpAuth':string,'error':int,'ttl'?:int,'attributes'?:string}> $res * 'priority'?:int,'pathEntries'?:string,'httpAuth':string,'error':int,'ttl'?:int,'attributes'?:string}> $res
*/ */
$feeds = self::daoToFeed($res); $feeds = self::daoToFeeds($res);
usort($feeds, static function (FreshRSS_Feed $a, FreshRSS_Feed $b) { uasort($feeds, static function (FreshRSS_Feed $a, FreshRSS_Feed $b) {
return strnatcasecmp($a->name(), $b->name()); return strnatcasecmp($a->name(), $b->name());
}); });
@ -585,7 +585,7 @@ SQL;
* 'pathEntries'?:string,'httpAuth'?:string,'error'?:int|bool,'ttl'?:int,'attributes'?:string,'cache_nbUnreads'?:int,'cache_nbEntries'?:int}> $listDAO * 'pathEntries'?:string,'httpAuth'?:string,'error'?:int|bool,'ttl'?:int,'attributes'?:string,'cache_nbUnreads'?:int,'cache_nbEntries'?:int}> $listDAO
* @return array<int,FreshRSS_Feed> * @return array<int,FreshRSS_Feed>
*/ */
public static function daoToFeed(array $listDAO, ?int $catID = null): array { public static function daoToFeeds(array $listDAO, ?int $catID = null): array {
$list = []; $list = [];
foreach ($listDAO as $key => $dao) { foreach ($listDAO as $key => $dao) {

View File

@ -184,16 +184,16 @@ SQL;
public function searchById(int $id): ?FreshRSS_Tag { public function searchById(int $id): ?FreshRSS_Tag {
$res = $this->fetchAssoc('SELECT * FROM `_tag` WHERE id=:id', [':id' => $id]); $res = $this->fetchAssoc('SELECT * FROM `_tag` WHERE id=:id', [':id' => $id]);
/** @var array<array{'id':int,'name':string,'attributes'?:string}>|null $res */ /** @var array<array{'id':int,'name':string,'attributes'?:string}>|null $res */
return $res === null ? null : self::daoToTag($res)[0] ?? null; return $res === null ? null : (current(self::daoToTags($res)) ?: null);
} }
public function searchByName(string $name): ?FreshRSS_Tag { public function searchByName(string $name): ?FreshRSS_Tag {
$res = $this->fetchAssoc('SELECT * FROM `_tag` WHERE name=:name', [':name' => $name]); $res = $this->fetchAssoc('SELECT * FROM `_tag` WHERE name=:name', [':name' => $name]);
/** @var array<array{'id':int,'name':string,'attributes'?:string}>|null $res */ /** @var array<array{'id':int,'name':string,'attributes'?:string}>|null $res */
return $res === null ? null : self::daoToTag($res)[0] ?? null; return $res === null ? null : (current(self::daoToTags($res)) ?: null);
} }
/** @return array<FreshRSS_Tag>|false */ /** @return array<int,FreshRSS_Tag>|false */
public function listTags(bool $precounts = false) { public function listTags(bool $precounts = false) {
if ($precounts) { if ($precounts) {
$sql = <<<'SQL' $sql = <<<'SQL'
@ -211,7 +211,7 @@ SQL;
$stm = $this->pdo->query($sql); $stm = $this->pdo->query($sql);
if ($stm !== false) { if ($stm !== false) {
$res = $stm->fetchAll(PDO::FETCH_ASSOC) ?: []; $res = $stm->fetchAll(PDO::FETCH_ASSOC) ?: [];
return self::daoToTag($res); return self::daoToTags($res);
} else { } else {
$info = $this->pdo->errorInfo(); $info = $this->pdo->errorInfo();
Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info)); Minz_Log::error('SQL error ' . __METHOD__ . json_encode($info));
@ -430,9 +430,9 @@ SQL;
/** /**
* @param iterable<array{'id':int,'name':string,'attributes'?:string}> $listDAO * @param iterable<array{'id':int,'name':string,'attributes'?:string}> $listDAO
* @return array<FreshRSS_Tag> * @return array<int,FreshRSS_Tag>
*/ */
private static function daoToTag(iterable $listDAO): array { private static function daoToTags(iterable $listDAO): array {
$list = []; $list = [];
foreach ($listDAO as $dao) { foreach ($listDAO as $dao) {
if (empty($dao['id']) || empty($dao['name'])) { if (empty($dao['id']) || empty($dao['name'])) {
@ -446,7 +446,7 @@ SQL;
if (isset($dao['unreads'])) { if (isset($dao['unreads'])) {
$tag->_nbUnread($dao['unreads']); $tag->_nbUnread($dao['unreads']);
} }
$list[] = $tag; $list[$tag->id()] = $tag;
} }
return $list; return $list;
} }

View File

@ -41,7 +41,7 @@ declare(strict_types=1);
* @property bool $onread_jump_next * @property bool $onread_jump_next
* @property string $passwordHash * @property string $passwordHash
* @property int $posts_per_page * @property int $posts_per_page
* @property array<array{'get'?:string,'name'?:string,'order'?:string,'search'?:string,'state'?:int,'url'?:string}> $queries * @property array<array{'get'?:string,'name'?:string,'order'?:string,'search'?:string,'state'?:int,'url'?:string,'token'?:string}> $queries
* @property bool $reading_confirm * @property bool $reading_confirm
* @property int $since_hours_posts_per_rss * @property int $since_hours_posts_per_rss
* @property bool $show_fav_unread * @property bool $show_fav_unread
@ -81,6 +81,20 @@ final class FreshRSS_UserConfiguration extends Minz_Configuration {
return parent::get('user'); return parent::get('user');
} }
/**
* Access the default configuration for users.
* @throws Minz_FileNotExistException
*/
public static function default(): FreshRSS_UserConfiguration {
static $default_user_conf = null;
if ($default_user_conf == null) {
$namespace = 'user_default';
FreshRSS_UserConfiguration::register($namespace, '_', FRESHRSS_PATH . '/config-user.default.php');
$default_user_conf = FreshRSS_UserConfiguration::get($namespace);
}
return $default_user_conf;
}
/** /**
* @param non-empty-string $key * @param non-empty-string $key
* @return array<int|string,mixed>|null * @return array<int|string,mixed>|null

View File

@ -18,17 +18,34 @@ class FreshRSS_UserQuery {
private FreshRSS_BooleanSearch $search; private FreshRSS_BooleanSearch $search;
private int $state = 0; private int $state = 0;
private string $url = ''; private string $url = '';
private ?FreshRSS_FeedDAO $feed_dao; private string $token = '';
private ?FreshRSS_CategoryDAO $category_dao; private bool $shareRss = false;
private ?FreshRSS_TagDAO $tag_dao; private bool $shareOpml = false;
/** @var array<int,FreshRSS_Category> $categories */
private array $categories;
/** @var array<int,FreshRSS_Tag> $labels */
private array $labels;
public static function generateToken(string $salt): string {
if (!FreshRSS_Context::hasSystemConf()) {
return '';
}
$hash = md5(FreshRSS_Context::systemConf()->salt . $salt . random_bytes(16));
if (function_exists('gmp_init')) {
// Shorten the hash if possible by converting from base 16 to base 62
$hash = gmp_strval(gmp_init($hash, 16), 62);
}
return $hash;
}
/** /**
* @param array{'get'?:string,'name'?:string,'order'?:string,'search'?:string,'state'?:int,'url'?:string} $query * @param array{get?:string,name?:string,order?:string,search?:string,state?:int,url?:string,token?:string,shareRss?:bool,shareOpml?:bool} $query
* @param array<int,FreshRSS_Category> $categories
* @param array<int,FreshRSS_Tag> $labels
*/ */
public function __construct(array $query, FreshRSS_FeedDAO $feed_dao = null, FreshRSS_CategoryDAO $category_dao = null, FreshRSS_TagDAO $tag_dao = null) { public function __construct(array $query, array $categories, array $labels) {
$this->category_dao = $category_dao; $this->categories = $categories;
$this->feed_dao = $feed_dao; $this->labels = $labels;
$this->tag_dao = $tag_dao;
if (isset($query['get'])) { if (isset($query['get'])) {
$this->parseGet($query['get']); $this->parseGet($query['get']);
} }
@ -49,8 +66,18 @@ class FreshRSS_UserQuery {
if (!isset($query['search'])) { if (!isset($query['search'])) {
$query['search'] = ''; $query['search'] = '';
} }
if (!empty($query['token'])) {
$this->token = $query['token'];
}
if (isset($query['shareRss'])) {
$this->shareRss = $query['shareRss'];
}
if (isset($query['shareOpml'])) {
$this->shareOpml = $query['shareOpml'];
}
// linked too deeply with the search object, need to use dependency injection // linked too deeply with the search object, need to use dependency injection
$this->search = new FreshRSS_BooleanSearch($query['search']); $this->search = new FreshRSS_BooleanSearch($query['search'], 0, 'AND', false);
if (!empty($query['state'])) { if (!empty($query['state'])) {
$this->state = intval($query['state']); $this->state = intval($query['state']);
} }
@ -59,16 +86,19 @@ class FreshRSS_UserQuery {
/** /**
* Convert the current object to an array. * Convert the current object to an array.
* *
* @return array{'get'?:string,'name'?:string,'order'?:string,'search'?:string,'state'?:int,'url'?:string} * @return array{'get'?:string,'name'?:string,'order'?:string,'search'?:string,'state'?:int,'url'?:string,'token'?:string}
*/ */
public function toArray(): array { public function toArray(): array {
return array_filter([ return array_filter([
'get' => $this->get, 'get' => $this->get,
'name' => $this->name, 'name' => $this->name,
'order' => $this->order, 'order' => $this->order,
'search' => $this->search->__toString(), 'search' => $this->search->getRawInput(),
'state' => $this->state, 'state' => $this->state,
'url' => $this->url, 'url' => $this->url,
'token' => $this->token,
'shareRss' => $this->shareRss,
'shareOpml' => $this->shareOpml,
]); ]);
} }
@ -77,92 +107,43 @@ class FreshRSS_UserQuery {
*/ */
private function parseGet(string $get): void { private function parseGet(string $get): void {
$this->get = $get; $this->get = $get;
if (preg_match('/(?P<type>[acfst])(_(?P<id>\d+))?/', $get, $matches)) { if (preg_match('/(?P<type>[acfistT])(_(?P<id>\d+))?/', $get, $matches)) {
$id = intval($matches['id'] ?? '0'); $id = intval($matches['id'] ?? '0');
switch ($matches['type']) { switch ($matches['type']) {
case 'a': case 'a':
$this->parseAll(); $this->get_type = 'all';
break; break;
case 'c': case 'c':
$this->parseCategory($id); $this->get_type = 'category';
$c = $this->categories[$id] ?? null;
$this->get_name = $c === null ? '' : $c->name();
break; break;
case 'f': case 'f':
$this->parseFeed($id); $this->get_type = 'feed';
$f = FreshRSS_Category::findFeed($this->categories, $id);
$this->get_name = $f === null ? '' : $f->name();
break;
case 'i':
$this->get_type = 'important';
break; break;
case 's': case 's':
$this->parseFavorite(); $this->get_type = 'favorite';
break; break;
case 't': case 't':
$this->parseTag($id); $this->get_type = 'label';
$l = $this->labels[$id] ?? null;
$this->get_name = $l === null ? '' : $l->name();
break;
case 'T':
$this->get_type = 'all_labels';
break; break;
} }
if ($this->get_name === '' && in_array($matches['type'], ['c', 'f', 't'], true)) {
$this->deprecated = true;
}
} }
} }
/**
* Parse the query string when it is an "all" query
*/
private function parseAll(): void {
$this->get_name = 'all';
$this->get_type = 'all';
}
/**
* Parse the query string when it is a "category" query
*/
private function parseCategory(int $id): void {
if ($this->category_dao === null) {
$this->category_dao = FreshRSS_Factory::createCategoryDao();
}
$category = $this->category_dao->searchById($id);
if ($category !== null) {
$this->get_name = $category->name();
} else {
$this->deprecated = true;
}
$this->get_type = 'category';
}
/**
* Parse the query string when it is a "feed" query
*/
private function parseFeed(int $id): void {
if ($this->feed_dao === null) {
$this->feed_dao = FreshRSS_Factory::createFeedDao();
}
$feed = $this->feed_dao->searchById($id);
if ($feed !== null) {
$this->get_name = $feed->name();
} else {
$this->deprecated = true;
}
$this->get_type = 'feed';
}
/**
* Parse the query string when it is a "tag" query
*/
private function parseTag(int $id): void {
if ($this->tag_dao === null) {
$this->tag_dao = FreshRSS_Factory::createTagDao();
}
$tag = $this->tag_dao->searchById($id);
if ($tag !== null) {
$this->get_name = $tag->name();
} else {
$this->deprecated = true;
}
$this->get_type = 'tag';
}
/**
* Parse the query string when it is a "favorite" query
*/
private function parseFavorite(): void {
$this->get_name = 'favorite';
$this->get_type = 'favorite';
}
/** /**
* Check if the current user query is deprecated. * Check if the current user query is deprecated.
* It is deprecated if the category or the feed used in the query are * It is deprecated if the category or the feed used in the query are
@ -219,7 +200,7 @@ class FreshRSS_UserQuery {
} }
public function getOrder(): string { public function getOrder(): string {
return $this->order; return $this->order ?: FreshRSS_Context::userConf()->sort_order;
} }
public function getSearch(): FreshRSS_BooleanSearch { public function getSearch(): FreshRSS_BooleanSearch {
@ -227,11 +208,74 @@ class FreshRSS_UserQuery {
} }
public function getState(): int { public function getState(): int {
return $this->state; $state = $this->state;
if (!($state & FreshRSS_Entry::STATE_READ) && !($state & FreshRSS_Entry::STATE_NOT_READ)) {
$state |= FreshRSS_Entry::STATE_READ | FreshRSS_Entry::STATE_NOT_READ;
}
if (!($state & FreshRSS_Entry::STATE_FAVORITE) && !($state & FreshRSS_Entry::STATE_NOT_FAVORITE)) {
$state |= FreshRSS_Entry::STATE_FAVORITE | FreshRSS_Entry::STATE_NOT_FAVORITE;
}
return $state;
} }
public function getUrl(): string { public function getUrl(): string {
return $this->url; return $this->url;
} }
public function getToken(): string {
return $this->token;
}
public function setToken(string $token): void {
$this->token = $token;
}
public function setShareRss(bool $shareRss): void {
$this->shareRss = $shareRss;
}
public function shareRss(): bool {
return $this->shareRss;
}
public function setShareOpml(bool $shareOpml): void {
$this->shareOpml = $shareOpml;
}
public function shareOpml(): bool {
return $this->shareOpml;
}
protected function sharedUrl(bool $xmlEscaped = true): string {
$currentUser = Minz_User::name() ?? '';
return Minz_Url::display("/api/query.php?user={$currentUser}&t={$this->token}", $xmlEscaped ? 'html' : '', true);
}
public function sharedUrlRss(bool $xmlEscaped = true): string {
if ($this->shareRss && $this->token !== '') {
return $this->sharedUrl($xmlEscaped) . ($xmlEscaped ? '&amp;' : '&') . 'f=rss';
}
return '';
}
public function sharedUrlHtml(bool $xmlEscaped = true): string {
if ($this->shareRss && $this->token !== '') {
return $this->sharedUrl($xmlEscaped) . ($xmlEscaped ? '&amp;' : '&') . 'f=html';
}
return '';
}
/**
* OPML is only safe for some query types, otherwise it risks leaking unwanted feed information.
*/
public function safeForOpml(): bool {
return in_array($this->get_type, ['all', 'category', 'feed'], true);
}
public function sharedUrlOpml(bool $xmlEscaped = true): string {
if ($this->shareOpml && $this->token !== '' && $this->safeForOpml()) {
return $this->sharedUrl($xmlEscaped) . ($xmlEscaped ? '&amp;' : '&') . 'f=opml';
}
return '';
}
} }

View File

@ -10,7 +10,7 @@ class FreshRSS_View extends Minz_View {
public $callbackBeforeFeeds; public $callbackBeforeFeeds;
/** @var callable */ /** @var callable */
public $callbackBeforePagination; public $callbackBeforePagination;
/** @var array<FreshRSS_Category> */ /** @var array<int,FreshRSS_Category> */
public array $categories; public array $categories;
public ?FreshRSS_Category $category; public ?FreshRSS_Category $category;
public ?FreshRSS_Tag $tag; public ?FreshRSS_Tag $tag;
@ -18,11 +18,11 @@ class FreshRSS_View extends Minz_View {
/** @var iterable<FreshRSS_Entry> */ /** @var iterable<FreshRSS_Entry> */
public $entries; public $entries;
public FreshRSS_Entry $entry; public FreshRSS_Entry $entry;
public ?FreshRSS_Feed $feed; public FreshRSS_Feed $feed;
/** @var array<FreshRSS_Feed> */ /** @var array<int,FreshRSS_Feed> */
public array $feeds; public array $feeds;
public int $nbUnreadTags; public int $nbUnreadTags;
/** @var array<FreshRSS_Tag> */ /** @var array<int,FreshRSS_Tag> */
public array $tags; public array $tags;
/** @var array<int,array{'id':int,'name':string,'id_entry':string,'checked':bool}> */ /** @var array<int,array{'id':int,'name':string,'id_entry':string,'checked':bool}> */
public array $tagsForEntry; public array $tagsForEntry;
@ -100,6 +100,8 @@ class FreshRSS_View extends Minz_View {
public int $nbPage; public int $nbPage;
// RSS view // RSS view
public FreshRSS_UserQuery $userQuery;
public string $html_url = '';
public string $rss_title = ''; public string $rss_title = '';
public string $rss_url = ''; public string $rss_url = '';
public string $rss_base = ''; public string $rss_base = '';

View File

@ -3,11 +3,11 @@ declare(strict_types=1);
final class FreshRSS_ViewJavascript extends FreshRSS_View { final class FreshRSS_ViewJavascript extends FreshRSS_View {
/** @var array<FreshRSS_Category> */ /** @var array<int,FreshRSS_Category> */
public array $categories; public array $categories;
/** @var array<FreshRSS_Feed> */ /** @var array<int,FreshRSS_Feed> */
public array $feeds; public array $feeds;
/** @var array<FreshRSS_Tag> */ /** @var array<int,FreshRSS_Tag> */
public array $tags; public array $tags;
public string $nonce; public string $nonce;

View File

@ -3,10 +3,10 @@ declare(strict_types=1);
final class FreshRSS_ViewStats extends FreshRSS_View { final class FreshRSS_ViewStats extends FreshRSS_View {
/** @var array<FreshRSS_Category> */ /** @var array<int,FreshRSS_Category> */
public array $categories; public array $categories;
public ?FreshRSS_Feed $feed; public FreshRSS_Feed $feed;
/** @var array<FreshRSS_Feed> */ /** @var array<int,FreshRSS_Feed> */
public array $feeds; public array $feeds;
public bool $displaySlider = false; public bool $displaySlider = false;

View File

@ -95,7 +95,7 @@ class FreshRSS_Export_Service {
$view = new FreshRSS_View(); $view = new FreshRSS_View();
$view->categories = $this->category_dao->listCategories(true) ?: []; $view->categories = $this->category_dao->listCategories(true) ?: [];
$feed = FreshRSS_CategoryDAO::findFeed($view->categories, $feed_id); $feed = FreshRSS_Category::findFeed($view->categories, $feed_id);
if ($feed === null) { if ($feed === null) {
return null; return null;
} }

View File

@ -107,7 +107,8 @@ final class FreshRSS_dotpath_Util
$view = new FreshRSS_View(); $view = new FreshRSS_View();
$view->_path('index/rss.phtml'); $view->_path('index/rss.phtml');
$view->internal_rendering = true; $view->internal_rendering = true;
$view->rss_url = $feedSourceUrl; $view->rss_url = htmlspecialchars($feedSourceUrl, ENT_COMPAT, 'UTF-8');
$view->html_url = $view->rss_url;
$view->entries = []; $view->entries = [];
$view->rss_title = isset($dotPaths['feedTitle']) $view->rss_title = isset($dotPaths['feedTitle'])

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (pro pokročilé uživatele s HTTPS)', 'http' => 'HTTP (pro pokročilé uživatele s HTTPS)',
'none' => 'Žádný (nebezpečné)', 'none' => 'Žádný (nebezpečné)',
'title' => 'Ověřování', 'title' => 'Ověřování',
'token' => 'Ověřovací token', 'token' => 'Master authentication token', // TODO
'token_help' => 'Umožňuje přístup k výstupu RSS výchozího uživatele bez ověřování:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Metoda ověřování', 'type' => 'Metoda ověřování',
'unsafe_autologin' => 'Povolit nebezpečné automatické přihlášení pomocí formátu: ', 'unsafe_autologin' => 'Povolit nebezpečné automatické přihlášení pomocí formátu: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Zobrazit podle kanálu', 'feeds' => 'Zobrazit podle kanálu',
'order' => 'Seřadit podle data', 'order' => 'Seřadit podle data',
'search' => 'Výraz', 'search' => 'Výraz',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'Stav', 'state' => 'Stav',
'tags' => 'Zobrazit podle štítku', 'tags' => 'Zobrazit podle štítku',
'type' => 'Typ', 'type' => 'Typ',
), ),
'get_all' => 'Zobrazit všechny články', 'get_all' => 'Zobrazit všechny články',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'Zobrazit kategorii „%s“', 'get_category' => 'Zobrazit kategorii „%s“',
'get_favorite' => 'Zobrazit oblíbené články', 'get_favorite' => 'Zobrazit oblíbené články',
'get_feed' => 'Zobrazit kanál „%s“', 'get_feed' => 'Zobrazit kanál „%s“',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Název', 'name' => 'Název',
'no_filter' => 'Žádný filtr', 'no_filter' => 'Žádný filtr',
'number' => 'Dotaz č. %d', 'number' => 'Dotaz č. %d',
'order_asc' => 'Zobrazit nejdříve nejstarší články', 'order_asc' => 'Zobrazit nejdříve nejstarší články',
'order_desc' => 'Zobrazit nejdříve nejnovější články', 'order_desc' => 'Zobrazit nejdříve nejnovější články',
'search' => 'Hledat „%s“', 'search' => 'Hledat „%s“',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Zobrazit všechny články', 'state_0' => 'Zobrazit všechny články',
'state_1' => 'Zobrazit přečtené články', 'state_1' => 'Zobrazit přečtené články',
'state_2' => 'Zobrazit nepřečtené články', 'state_2' => 'Zobrazit nepřečtené články',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (HTTPS für erfahrene Benutzer)', 'http' => 'HTTP (HTTPS für erfahrene Benutzer)',
'none' => 'Keine (gefährlich)', 'none' => 'Keine (gefährlich)',
'title' => 'Authentifizierung', 'title' => 'Authentifizierung',
'token' => 'Authentifizierungs-Token', 'token' => 'Master authentication token', // TODO
'token_help' => 'Erlaubt den Zugriff auf die RSS-Ausgabe des Standardbenutzers ohne Authentifizierung.', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Authentifizierungsmethode', 'type' => 'Authentifizierungsmethode',
'unsafe_autologin' => 'Erlaube unsicheres automatisches Anmelden mit folgendem Format: ', 'unsafe_autologin' => 'Erlaube unsicheres automatisches Anmelden mit folgendem Format: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Nach Feed filtern', 'feeds' => 'Nach Feed filtern',
'order' => 'Nach Datum sortieren', 'order' => 'Nach Datum sortieren',
'search' => 'Suchbegriff', 'search' => 'Suchbegriff',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'Eigenschaft', 'state' => 'Eigenschaft',
'tags' => 'Nach Labels filtern', 'tags' => 'Nach Labels filtern',
'type' => 'Filter-Typ', 'type' => 'Filter-Typ',
), ),
'get_all' => 'Alle Artikel anzeigen', 'get_all' => 'Alle Artikel anzeigen',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'Kategorie „%s“ anzeigen', 'get_category' => 'Kategorie „%s“ anzeigen',
'get_favorite' => 'Lieblingsartikel anzeigen', 'get_favorite' => 'Lieblingsartikel anzeigen',
'get_feed' => 'Feed „%s“ anzeigen', 'get_feed' => 'Feed „%s“ anzeigen',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Name', // IGNORE 'name' => 'Name', // IGNORE
'no_filter' => 'Kein Filter', 'no_filter' => 'Kein Filter',
'number' => 'Abfrage Nr. %d', 'number' => 'Abfrage Nr. %d',
'order_asc' => 'Älteste Artikel zuerst anzeigen', 'order_asc' => 'Älteste Artikel zuerst anzeigen',
'order_desc' => 'Neueste Artikel zuerst anzeigen', 'order_desc' => 'Neueste Artikel zuerst anzeigen',
'search' => 'Suche nach „%s“', 'search' => 'Suche nach „%s“',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Alle Artikel anzeigen', 'state_0' => 'Alle Artikel anzeigen',
'state_1' => 'Gelesene Artikel anzeigen', 'state_1' => 'Gelesene Artikel anzeigen',
'state_2' => 'Ungelesene Artikel anzeigen', 'state_2' => 'Ungelesene Artikel anzeigen',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (για έμπειρους χρήστες με )', 'http' => 'HTTP (για έμπειρους χρήστες με )',
'none' => 'Καμία (ριψοκίνδυνο)', 'none' => 'Καμία (ριψοκίνδυνο)',
'title' => 'Πιστοποίηση', 'title' => 'Πιστοποίηση',
'token' => 'Διακριτικό Πιστοποίησης (token)', 'token' => 'Master authentication token', // TODO
'token_help' => 'Επιτρέπει την πρόσβαση στα RSS αποτελέσματα του προεπιλεγμένου χρήστη χωρίς έλεγχο ταυτότητας:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Μέθοδος Πιστοποίησης', 'type' => 'Μέθοδος Πιστοποίησης',
'unsafe_autologin' => 'Επιτρέψτε την μη ασφαλή αυτόματη σύνδεση με την χρήση της μορφής: ', 'unsafe_autologin' => 'Επιτρέψτε την μη ασφαλή αυτόματη σύνδεση με την χρήση της μορφής: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Display by feed', // TODO 'feeds' => 'Display by feed', // TODO
'order' => 'Sort by date', // TODO 'order' => 'Sort by date', // TODO
'search' => 'Expression', // TODO 'search' => 'Expression', // TODO
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'State', // TODO 'state' => 'State', // TODO
'tags' => 'Display by label', // TODO 'tags' => 'Display by label', // TODO
'type' => 'Type', // TODO 'type' => 'Type', // TODO
), ),
'get_all' => 'Display all articles', // TODO 'get_all' => 'Display all articles', // TODO
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'Display “%s” category', // TODO 'get_category' => 'Display “%s” category', // TODO
'get_favorite' => 'Display favourite articles', // TODO 'get_favorite' => 'Display favourite articles', // TODO
'get_feed' => 'Display “%s” feed', // TODO 'get_feed' => 'Display “%s” feed', // TODO
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Name', // TODO 'name' => 'Name', // TODO
'no_filter' => 'No filter', // TODO 'no_filter' => 'No filter', // TODO
'number' => 'Query n°%d', // TODO 'number' => 'Query n°%d', // TODO
'order_asc' => 'Display oldest articles first', // TODO 'order_asc' => 'Display oldest articles first', // TODO
'order_desc' => 'Display newest articles first', // TODO 'order_desc' => 'Display newest articles first', // TODO
'search' => 'Search for “%s”', // TODO 'search' => 'Search for “%s”', // TODO
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Display all articles', // TODO 'state_0' => 'Display all articles', // TODO
'state_1' => 'Display read articles', // TODO 'state_1' => 'Display read articles', // TODO
'state_2' => 'Display unread articles', // TODO 'state_2' => 'Display unread articles', // TODO

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (for advanced users with HTTPS)', // IGNORE 'http' => 'HTTP (for advanced users with HTTPS)', // IGNORE
'none' => 'None (dangerous)', // IGNORE 'none' => 'None (dangerous)', // IGNORE
'title' => 'Authentication', // IGNORE 'title' => 'Authentication', // IGNORE
'token' => 'Authentication token', // IGNORE 'token' => 'Master authentication token', // IGNORE
'token_help' => 'Allows access to RSS output of the default user without authentication:', // IGNORE 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // IGNORE
'type' => 'Authentication method', // IGNORE 'type' => 'Authentication method', // IGNORE
'unsafe_autologin' => 'Allow unsafe automatic login using the format: ', // IGNORE 'unsafe_autologin' => 'Allow unsafe automatic login using the format: ', // IGNORE
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Display by feed', // IGNORE 'feeds' => 'Display by feed', // IGNORE
'order' => 'Sort by date', // IGNORE 'order' => 'Sort by date', // IGNORE
'search' => 'Expression', // IGNORE 'search' => 'Expression', // IGNORE
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // IGNORE
'shareRss' => 'Enable sharing by HTML &amp; RSS', // IGNORE
'state' => 'State', // IGNORE 'state' => 'State', // IGNORE
'tags' => 'Display by label', // IGNORE 'tags' => 'Display by label', // IGNORE
'type' => 'Type', // IGNORE 'type' => 'Type', // IGNORE
), ),
'get_all' => 'Display all articles', // IGNORE 'get_all' => 'Display all articles', // IGNORE
'get_all_labels' => 'Display articles with any label', // IGNORE
'get_category' => 'Display “%s” category', // IGNORE 'get_category' => 'Display “%s” category', // IGNORE
'get_favorite' => 'Display favorite articles', 'get_favorite' => 'Display favorite articles',
'get_feed' => 'Display “%s” feed', // IGNORE 'get_feed' => 'Display “%s” feed', // IGNORE
'get_important' => 'Display articles from important feeds', // IGNORE
'get_label' => 'Display articles with “%s” label', // IGNORE
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // IGNORE
'name' => 'Name', // IGNORE 'name' => 'Name', // IGNORE
'no_filter' => 'No filter', // IGNORE 'no_filter' => 'No filter', // IGNORE
'number' => 'Query n°%d', // IGNORE 'number' => 'Query n°%d', // IGNORE
'order_asc' => 'Display oldest articles first', // IGNORE 'order_asc' => 'Display oldest articles first', // IGNORE
'order_desc' => 'Display newest articles first', // IGNORE 'order_desc' => 'Display newest articles first', // IGNORE
'search' => 'Search for “%s”', // IGNORE 'search' => 'Search for “%s”', // IGNORE
'share' => array(
'_' => 'Share this query by link', // IGNORE
'help' => 'Give this link if you want to share this query with anyone', // IGNORE
'html' => 'Shareable link to the HTML page', // IGNORE
'opml' => 'Shareable link to the OPML list of feeds', // IGNORE
'rss' => 'Shareable link to the RSS feed', // IGNORE
),
'state_0' => 'Display all articles', // IGNORE 'state_0' => 'Display all articles', // IGNORE
'state_1' => 'Display read articles', // IGNORE 'state_1' => 'Display read articles', // IGNORE
'state_2' => 'Display unread articles', // IGNORE 'state_2' => 'Display unread articles', // IGNORE

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (for advanced users with HTTPS)', 'http' => 'HTTP (for advanced users with HTTPS)',
'none' => 'None (dangerous)', 'none' => 'None (dangerous)',
'title' => 'Authentication', 'title' => 'Authentication',
'token' => 'Authentication token', 'token' => 'Master authentication token',
'token_help' => 'Allows access to RSS output of the default user without authentication:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:',
'type' => 'Authentication method', 'type' => 'Authentication method',
'unsafe_autologin' => 'Allow unsafe automatic login using the format: ', 'unsafe_autologin' => 'Allow unsafe automatic login using the format: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Display by feed', 'feeds' => 'Display by feed',
'order' => 'Sort by date', 'order' => 'Sort by date',
'search' => 'Expression', 'search' => 'Expression',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds',
'shareRss' => 'Enable sharing by HTML &amp; RSS',
'state' => 'State', 'state' => 'State',
'tags' => 'Display by label', 'tags' => 'Display by label',
'type' => 'Type', 'type' => 'Type',
), ),
'get_all' => 'Display all articles', 'get_all' => 'Display all articles',
'get_all_labels' => 'Display articles with any label',
'get_category' => 'Display “%s” category', 'get_category' => 'Display “%s” category',
'get_favorite' => 'Display favourite articles', 'get_favorite' => 'Display favourite articles',
'get_feed' => 'Display “%s” feed', 'get_feed' => 'Display “%s” feed',
'get_important' => 'Display articles from important feeds',
'get_label' => 'Display articles with “%s” label',
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.',
'name' => 'Name', 'name' => 'Name',
'no_filter' => 'No filter', 'no_filter' => 'No filter',
'number' => 'Query n°%d', 'number' => 'Query n°%d',
'order_asc' => 'Display oldest articles first', 'order_asc' => 'Display oldest articles first',
'order_desc' => 'Display newest articles first', 'order_desc' => 'Display newest articles first',
'search' => 'Search for “%s”', 'search' => 'Search for “%s”',
'share' => array(
'_' => 'Share this query by link',
'help' => 'Give this link if you want to share this query with anyone',
'html' => 'Shareable link to the HTML page',
'opml' => 'Shareable link to the OPML list of feeds',
'rss' => 'Shareable link to the RSS feed',
),
'state_0' => 'Display all articles', 'state_0' => 'Display all articles',
'state_1' => 'Display read articles', 'state_1' => 'Display read articles',
'state_2' => 'Display unread articles', 'state_2' => 'Display unread articles',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (para usuarios avanzados con HTTPS)', 'http' => 'HTTP (para usuarios avanzados con HTTPS)',
'none' => 'Ninguno (peligroso)', 'none' => 'Ninguno (peligroso)',
'title' => 'Identificación', 'title' => 'Identificación',
'token' => 'Clave de identificación', 'token' => 'Master authentication token', // TODO
'token_help' => 'Permite el acceso a la salida RSS del usuario por defecto sin necesidad de identificación:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Método de identificación', 'type' => 'Método de identificación',
'unsafe_autologin' => 'Permite la identificación automática insegura usando el formato: ', 'unsafe_autologin' => 'Permite la identificación automática insegura usando el formato: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Mostrar por feed', 'feeds' => 'Mostrar por feed',
'order' => 'Ordenar por fecha', 'order' => 'Ordenar por fecha',
'search' => 'Expresión', 'search' => 'Expresión',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'Estado', 'state' => 'Estado',
'tags' => 'Mostrar por etiqueta', 'tags' => 'Mostrar por etiqueta',
'type' => 'Tipo', 'type' => 'Tipo',
), ),
'get_all' => 'Mostrar todos los artículos', 'get_all' => 'Mostrar todos los artículos',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'Mostrar la categoría “%s”', 'get_category' => 'Mostrar la categoría “%s”',
'get_favorite' => 'Mostrar artículos favoritos', 'get_favorite' => 'Mostrar artículos favoritos',
'get_feed' => 'Mostrar fuente “%s”', 'get_feed' => 'Mostrar fuente “%s”',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Nombre', 'name' => 'Nombre',
'no_filter' => 'Sin filtro', 'no_filter' => 'Sin filtro',
'number' => 'Consulta n° %d', 'number' => 'Consulta n° %d',
'order_asc' => 'Mostrar primero los artículos más antiguos', 'order_asc' => 'Mostrar primero los artículos más antiguos',
'order_desc' => 'Mostrar primero los artículos más recientes', 'order_desc' => 'Mostrar primero los artículos más recientes',
'search' => 'Buscar “%s”', 'search' => 'Buscar “%s”',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Mostrar todos los artículos', 'state_0' => 'Mostrar todos los artículos',
'state_1' => 'Mostrar artículos leídos', 'state_1' => 'Mostrar artículos leídos',
'state_2' => 'Mostrar artículos pendientes', 'state_2' => 'Mostrar artículos pendientes',

View File

@ -19,8 +19,8 @@ return array(
'http' => ' HTTP (برای کاربران پیشرفته با HTTPS)', 'http' => ' HTTP (برای کاربران پیشرفته با HTTPS)',
'none' => ' هیچ (خطرناک)', 'none' => ' هیچ (خطرناک)',
'title' => ' احراز هویت', 'title' => ' احراز هویت',
'token' => ' نشانه احراز هویت', 'token' => 'Master authentication token', // TODO
'token_help' => ' امکان دسترسی به خروجی RSS کاربر پیش فرض بدون احراز هویت را می دهد:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => ' روش احراز هویت', 'type' => ' روش احراز هویت',
'unsafe_autologin' => ' اجازه ورود خودکار ناامن را با استفاده از قالب:', 'unsafe_autologin' => ' اجازه ورود خودکار ناامن را با استفاده از قالب:',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => ' نمایش با فید', 'feeds' => ' نمایش با فید',
'order' => ' مرتب سازی بر اساس تاریخ', 'order' => ' مرتب سازی بر اساس تاریخ',
'search' => ' بیان', 'search' => ' بیان',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => ' ایالت', 'state' => ' ایالت',
'tags' => ' نمایش بر اساس برچسب', 'tags' => ' نمایش بر اساس برچسب',
'type' => ' نوع', 'type' => ' نوع',
), ),
'get_all' => ' نمایش همه مقالات', 'get_all' => ' نمایش همه مقالات',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => ' دسته «%s» را نمایش دهید', 'get_category' => ' دسته «%s» را نمایش دهید',
'get_favorite' => ' نمایش مقالات مورد علاقه', 'get_favorite' => ' نمایش مقالات مورد علاقه',
'get_feed' => ' فید "%s" را نمایش دهید', 'get_feed' => ' فید "%s" را نمایش دهید',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => ' نام', 'name' => ' نام',
'no_filter' => ' بدون فیلتر', 'no_filter' => ' بدون فیلتر',
'number' => ' پرس و جو n°%d', 'number' => ' پرس و جو n°%d',
'order_asc' => ' ابتدا قدیمی ترین مقالات را نمایش دهید', 'order_asc' => ' ابتدا قدیمی ترین مقالات را نمایش دهید',
'order_desc' => ' ابتدا جدیدترین مقالات را نمایش دهید', 'order_desc' => ' ابتدا جدیدترین مقالات را نمایش دهید',
'search' => ' «%s» را جستجو کنید', 'search' => ' «%s» را جستجو کنید',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'نمایش همه مقالات', 'state_0' => 'نمایش همه مقالات',
'state_1' => 'نمایش مقالات خوانده شده', 'state_1' => 'نمایش مقالات خوانده شده',
'state_2' => 'نمایش مقالات خوانده نشده', 'state_2' => 'نمایش مقالات خوانده نشده',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (pour utilisateurs avancés avec HTTPS)', 'http' => 'HTTP (pour utilisateurs avancés avec HTTPS)',
'none' => 'Aucune (dangereux)', 'none' => 'Aucune (dangereux)',
'title' => 'Authentification', 'title' => 'Authentification',
'token' => 'Jeton didentification', 'token' => 'Jeton didentification maître',
'token_help' => 'Permet daccéder à la sortie RSS de lutilisateur par défaut sans besoin de sauthentifier :', 'token_help' => 'Permet daccéder à toutes les sorties RSS de lutilisateur et au rafraîchissement des flux sans besoin de sauthentifier :',
'type' => 'Méthode dauthentification', 'type' => 'Méthode dauthentification',
'unsafe_autologin' => 'Autoriser les connexions automatiques non-sûres au format : ', 'unsafe_autologin' => 'Autoriser les connexions automatiques non-sûres au format : ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Afficher par flux', 'feeds' => 'Afficher par flux',
'order' => 'Tri par date', 'order' => 'Tri par date',
'search' => 'Expression', // IGNORE 'search' => 'Expression', // IGNORE
'shareOpml' => 'Active le partage par OPML des catégories et flux correspondants',
'shareRss' => 'Active le partage par HTML &amp; RSS',
'state' => 'État', 'state' => 'État',
'tags' => 'Afficher par étiquette', 'tags' => 'Afficher par étiquette',
'type' => 'Type', // IGNORE 'type' => 'Type', // IGNORE
), ),
'get_all' => 'Afficher tous les articles', 'get_all' => 'Afficher tous les articles',
'get_all_labels' => 'Afficher les articles avec une étiquette',
'get_category' => 'Afficher la catégorie <em>%s<em>', 'get_category' => 'Afficher la catégorie <em>%s<em>',
'get_favorite' => 'Afficher les articles favoris', 'get_favorite' => 'Afficher les articles favoris',
'get_feed' => 'Afficher le flux <em>%s</em>', 'get_feed' => 'Afficher le flux <em>%s</em>',
'get_important' => 'Afficher les articles des flux importants',
'get_label' => 'Afficher les articles avec létiquette “%s”',
'help' => 'Voir la <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation pour les filtres utilisateurs et repartage par HTML / RSS / OPML</a>.',
'name' => 'Nom', 'name' => 'Nom',
'no_filter' => 'Aucun filtre appliqué', 'no_filter' => 'Aucun filtre appliqué',
'number' => 'Filtre n°%d', 'number' => 'Filtre n°%d',
'order_asc' => 'Afficher les articles les plus anciens en premier', 'order_asc' => 'Afficher les articles les plus anciens en premier',
'order_desc' => 'Afficher les articles les plus récents en premier', 'order_desc' => 'Afficher les articles les plus récents en premier',
'search' => 'Recherche de « %s »', 'search' => 'Recherche de « %s »',
'share' => array(
'_' => 'Partager ce filtre par lien',
'help' => 'Donner ce lien pour partager le contenu du filtre avec dautres personnes',
'html' => 'Lien partageable de la page HTML',
'opml' => 'Lien partageable de la liste des flux au format OPML',
'rss' => 'Lien partageable du flux RSS',
),
'state_0' => 'Afficher tous les articles', 'state_0' => 'Afficher tous les articles',
'state_1' => 'Afficher les articles lus', 'state_1' => 'Afficher les articles lus',
'state_2' => 'Afficher les articles non lus', 'state_2' => 'Afficher les articles non lus',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (למשתמשים מתקדמים עם HTTPS)', 'http' => 'HTTP (למשתמשים מתקדמים עם HTTPS)',
'none' => 'ללא (מסוכן)', 'none' => 'ללא (מסוכן)',
'title' => 'Authentication', // TODO 'title' => 'Authentication', // TODO
'token' => 'מחרוזת אימות', 'token' => 'Master authentication token', // TODO
'token_help' => 'Allows to access RSS output of the default user without authentication:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'שיטת אימות', 'type' => 'שיטת אימות',
'unsafe_autologin' => 'הרשאה להתחברות אוטומטית בפורמט: ', 'unsafe_autologin' => 'הרשאה להתחברות אוטומטית בפורמט: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Display by feed', // TODO 'feeds' => 'Display by feed', // TODO
'order' => 'Sort by date', // TODO 'order' => 'Sort by date', // TODO
'search' => 'Expression', // TODO 'search' => 'Expression', // TODO
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'State', // TODO 'state' => 'State', // TODO
'tags' => 'Display by label', // TODO 'tags' => 'Display by label', // TODO
'type' => 'Type', // TODO 'type' => 'Type', // TODO
), ),
'get_all' => 'הצגת כל המאמרים', 'get_all' => 'הצגת כל המאמרים',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'הצגת קטגוריה “%s”', 'get_category' => 'הצגת קטגוריה “%s”',
'get_favorite' => 'הצגת מאמרים מועדפים', 'get_favorite' => 'הצגת מאמרים מועדפים',
'get_feed' => 'הצגת הזנה %s', 'get_feed' => 'הצגת הזנה %s',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Name', // TODO 'name' => 'Name', // TODO
'no_filter' => 'ללא סינון', 'no_filter' => 'ללא סינון',
'number' => 'שאילתה מספר °%d', 'number' => 'שאילתה מספר °%d',
'order_asc' => 'הצגת מאמרים ישנים בראש', 'order_asc' => 'הצגת מאמרים ישנים בראש',
'order_desc' => 'הצגת מאמרים חדשים בראש', 'order_desc' => 'הצגת מאמרים חדשים בראש',
'search' => 'חיפוש “%s”', 'search' => 'חיפוש “%s”',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'הצגת כל המאמרים', 'state_0' => 'הצגת כל המאמרים',
'state_1' => 'הצגת מאמרים שנקראו', 'state_1' => 'הצגת מאמרים שנקראו',
'state_2' => 'הצגת מאמרים שלא נקראו', 'state_2' => 'הצגת מאמרים שלא נקראו',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (haladó felhasználóknak HTTPS-el)', 'http' => 'HTTP (haladó felhasználóknak HTTPS-el)',
'none' => 'nincs (veszélyes)', 'none' => 'nincs (veszélyes)',
'title' => 'Hitelesítés', 'title' => 'Hitelesítés',
'token' => 'Hitelesítő token', 'token' => 'Master authentication token', // TODO
'token_help' => 'Engedélyezi az alapértelmezett felhasználó RSS-ének olvasását hitelesítés nélkül:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Hitelesítési módszer', 'type' => 'Hitelesítési módszer',
'unsafe_autologin' => 'Engedélyezze a nem biztonságos automata bejelentkezést a következő formátummal: ', 'unsafe_autologin' => 'Engedélyezze a nem biztonságos automata bejelentkezést a következő formátummal: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Rendezés hírforrás szerint', 'feeds' => 'Rendezés hírforrás szerint',
'order' => 'Rendezés dátum szerint', 'order' => 'Rendezés dátum szerint',
'search' => 'Kifejezés', 'search' => 'Kifejezés',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'Státusz', 'state' => 'Státusz',
'tags' => 'Rendezés címke szerint', 'tags' => 'Rendezés címke szerint',
'type' => 'Típus', 'type' => 'Típus',
), ),
'get_all' => 'Minden cikk megjelenítése', 'get_all' => 'Minden cikk megjelenítése',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'Listáz “%s” kategóriát', 'get_category' => 'Listáz “%s” kategóriát',
'get_favorite' => 'Kedvenc cikkek megjelenítése', 'get_favorite' => 'Kedvenc cikkek megjelenítése',
'get_feed' => 'Listáz “%s” hírforrást', 'get_feed' => 'Listáz “%s” hírforrást',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Név', 'name' => 'Név',
'no_filter' => 'Nincs szűrés', 'no_filter' => 'Nincs szűrés',
'number' => 'Lekérdezés %d', 'number' => 'Lekérdezés %d',
'order_asc' => 'Régebbi cikkek előre', 'order_asc' => 'Régebbi cikkek előre',
'order_desc' => 'Újabb cikkek előre', 'order_desc' => 'Újabb cikkek előre',
'search' => 'Keresse a “%s”', 'search' => 'Keresse a “%s”',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Minden cikk', 'state_0' => 'Minden cikk',
'state_1' => 'Olvasott cikkek', 'state_1' => 'Olvasott cikkek',
'state_2' => 'Olvasatlan cikkek', 'state_2' => 'Olvasatlan cikkek',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (untuk pengguna tingkat lanjut HTTPS)', 'http' => 'HTTP (untuk pengguna tingkat lanjut HTTPS)',
'none' => 'None (dangerous)', // TODO 'none' => 'None (dangerous)', // TODO
'title' => 'Authentication', // TODO 'title' => 'Authentication', // TODO
'token' => 'Authentication token', // TODO 'token' => 'Master authentication token', // TODO
'token_help' => 'Memungkinkan akses ke output RSS dari pengguna default tanpa otentikasi:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Authentication method', // TODO 'type' => 'Authentication method', // TODO
'unsafe_autologin' => 'Izinkan login otomatis yang tidak aman menggunakan format: ', 'unsafe_autologin' => 'Izinkan login otomatis yang tidak aman menggunakan format: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Display by feed', // TODO 'feeds' => 'Display by feed', // TODO
'order' => 'Sort by date', // TODO 'order' => 'Sort by date', // TODO
'search' => 'Expression', // TODO 'search' => 'Expression', // TODO
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'State', // TODO 'state' => 'State', // TODO
'tags' => 'Display by label', // TODO 'tags' => 'Display by label', // TODO
'type' => 'Type', // TODO 'type' => 'Type', // TODO
), ),
'get_all' => 'Display all articles', // TODO 'get_all' => 'Display all articles', // TODO
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'Display “%s” category', // TODO 'get_category' => 'Display “%s” category', // TODO
'get_favorite' => 'Display favorite articles', 'get_favorite' => 'Display favourite articles', // TODO
'get_feed' => 'Display “%s” feed', // TODO 'get_feed' => 'Display “%s” feed', // TODO
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Name', // TODO 'name' => 'Name', // TODO
'no_filter' => 'No filter', // TODO 'no_filter' => 'No filter', // TODO
'number' => 'Query n°%d', // TODO 'number' => 'Query n°%d', // TODO
'order_asc' => 'Display oldest articles first', // TODO 'order_asc' => 'Display oldest articles first', // TODO
'order_desc' => 'Display newest articles first', // TODO 'order_desc' => 'Display newest articles first', // TODO
'search' => 'Search for “%s”', // TODO 'search' => 'Search for “%s”', // TODO
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Display all articles', // TODO 'state_0' => 'Display all articles', // TODO
'state_1' => 'Display read articles', // TODO 'state_1' => 'Display read articles', // TODO
'state_2' => 'Display unread articles', // TODO 'state_2' => 'Display unread articles', // TODO

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (per gli utenti avanzati con HTTPS)', 'http' => 'HTTP (per gli utenti avanzati con HTTPS)',
'none' => 'Nessuno (pericoloso)', 'none' => 'Nessuno (pericoloso)',
'title' => 'Autenticazione', 'title' => 'Autenticazione',
'token' => 'Token di autenticazione', 'token' => 'Master authentication token', // TODO
'token_help' => 'Consenti accesso agli RSS dell utente predefinito senza autenticazione:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Metodo di autenticazione', 'type' => 'Metodo di autenticazione',
'unsafe_autologin' => 'Consenti accesso automatico non sicuro usando il formato: ', 'unsafe_autologin' => 'Consenti accesso automatico non sicuro usando il formato: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Mostra per feed', 'feeds' => 'Mostra per feed',
'order' => 'Ordina per data', 'order' => 'Ordina per data',
'search' => 'Espressione', 'search' => 'Espressione',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'Stato', 'state' => 'Stato',
'tags' => 'Mostra per tag', // DIRTY 'tags' => 'Mostra per tag', // DIRTY
'type' => 'Tipo', 'type' => 'Tipo',
), ),
'get_all' => 'Mostra tutti gli articoli', 'get_all' => 'Mostra tutti gli articoli',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'Mostra la categoria “%s” ', 'get_category' => 'Mostra la categoria “%s” ',
'get_favorite' => 'Mostra articoli preferiti', 'get_favorite' => 'Mostra articoli preferiti',
'get_feed' => 'Mostra feed “%s” ', 'get_feed' => 'Mostra feed “%s” ',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Nome', 'name' => 'Nome',
'no_filter' => 'Nessun filtro', 'no_filter' => 'Nessun filtro',
'number' => 'Ricerca n°%d', 'number' => 'Ricerca n°%d',
'order_asc' => 'Mostra prima gli articoli più vecchi', 'order_asc' => 'Mostra prima gli articoli più vecchi',
'order_desc' => 'Mostra prima gli articoli più nuovi', 'order_desc' => 'Mostra prima gli articoli più nuovi',
'search' => 'Cerca per “%s”', 'search' => 'Cerca per “%s”',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Mostra tutti gli articoli', 'state_0' => 'Mostra tutti gli articoli',
'state_1' => 'Mostra gli articoli letti', 'state_1' => 'Mostra gli articoli letti',
'state_2' => 'Mostra gli articoli non letti', 'state_2' => 'Mostra gli articoli non letti',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (上級者はHTTPSでも)', 'http' => 'HTTP (上級者はHTTPSでも)',
'none' => 'なし (危険)', 'none' => 'なし (危険)',
'title' => '認証', 'title' => '認証',
'token' => '認証トークン', 'token' => 'Master authentication token', // TODO
'token_help' => 'ユーザーが承認無しで、RSSを出力できるようにします。:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => '認証メソッド', 'type' => '認証メソッド',
'unsafe_autologin' => '危険な自動ログインを有効にします', 'unsafe_autologin' => '危険な自動ログインを有効にします',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'フィードごとに表示する', 'feeds' => 'フィードごとに表示する',
'order' => '日付ごとにソートする', 'order' => '日付ごとにソートする',
'search' => '式', 'search' => '式',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => '状態', 'state' => '状態',
'tags' => 'タグごとに表示する', 'tags' => 'タグごとに表示する',
'type' => 'タイプ', 'type' => 'タイプ',
), ),
'get_all' => 'すべての著者を表示する', 'get_all' => 'すべての著者を表示する',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => '“%s”カテゴリを表示する', 'get_category' => '“%s”カテゴリを表示する',
'get_favorite' => 'お気に入りの著者を表示する', 'get_favorite' => 'お気に入りの著者を表示する',
'get_feed' => '“%s”フィードを表示する', 'get_feed' => '“%s”フィードを表示する',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => '名前', 'name' => '名前',
'no_filter' => 'フィルターはありません', 'no_filter' => 'フィルターはありません',
'number' => 'クエリ n°%d', 'number' => 'クエリ n°%d',
'order_asc' => '古い著者を最初に表示する', 'order_asc' => '古い著者を最初に表示する',
'order_desc' => '新しい著者を最初に表示する', 'order_desc' => '新しい著者を最初に表示する',
'search' => '“%s”で検索する', 'search' => '“%s”で検索する',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'すべての記事を表示する', 'state_0' => 'すべての記事を表示する',
'state_1' => '既読の記事を表示する', 'state_1' => '既読の記事を表示する',
'state_2' => '未読の記事を表示する', 'state_2' => '未読の記事を表示する',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (HTTPS를 사용하는 고급 사용자용)', 'http' => 'HTTP (HTTPS를 사용하는 고급 사용자용)',
'none' => '사용하지 않음 (위험)', 'none' => '사용하지 않음 (위험)',
'title' => '인증', 'title' => '인증',
'token' => '인증 토큰', 'token' => 'Master authentication token', // TODO
'token_help' => '기본 사용자의 RSS에 인증 없이 접근할 수 있도록 합니다:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => '인증', 'type' => '인증',
'unsafe_autologin' => '다음과 같은 안전하지 않은 방식의 로그인을 허가합니다: ', 'unsafe_autologin' => '다음과 같은 안전하지 않은 방식의 로그인을 허가합니다: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => '피드별로 표시', 'feeds' => '피드별로 표시',
'order' => '날짜순으로 정렬', 'order' => '날짜순으로 정렬',
'search' => '정규 표현식', 'search' => '정규 표현식',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => '상태', 'state' => '상태',
'tags' => '태그별로 표시', 'tags' => '태그별로 표시',
'type' => '유형', 'type' => '유형',
), ),
'get_all' => '모든 글 표시', 'get_all' => '모든 글 표시',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => '“%s” 카테고리 표시', 'get_category' => '“%s” 카테고리 표시',
'get_favorite' => '즐겨찾기에 등록된 글 표시', 'get_favorite' => '즐겨찾기에 등록된 글 표시',
'get_feed' => '“%s” 피드 표시', 'get_feed' => '“%s” 피드 표시',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => '이름', 'name' => '이름',
'no_filter' => '필터가 없습니다', 'no_filter' => '필터가 없습니다',
'number' => '쿼리 #%d', 'number' => '쿼리 #%d',
'order_asc' => '오래된 글 먼저 표시', 'order_asc' => '오래된 글 먼저 표시',
'order_desc' => '최근 글 먼저 표시', 'order_desc' => '최근 글 먼저 표시',
'search' => '“%s”의 검색 결과', 'search' => '“%s”의 검색 결과',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => '모든 글 표시', 'state_0' => '모든 글 표시',
'state_1' => '읽은 글 표시', 'state_1' => '읽은 글 표시',
'state_2' => '읽지 않은 글 표시', 'state_2' => '읽지 않은 글 표시',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (pieredzējušiem lietotājiem ar HTTPS)', 'http' => 'HTTP (pieredzējušiem lietotājiem ar HTTPS)',
'none' => 'Nav (bīstami)', 'none' => 'Nav (bīstami)',
'title' => 'Autentifikācija', 'title' => 'Autentifikācija',
'token' => 'Autentifikācijas žetons', 'token' => 'Master authentication token', // TODO
'token_help' => 'Ļauj piekļūt noklusējuma lietotāja RSS izvadei bez autentifikācijas:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Autentifikācijas metode', 'type' => 'Autentifikācijas metode',
'unsafe_autologin' => 'Atļaut nedrošu automātisku pieteikšanos, izmantojot formātu: ', 'unsafe_autologin' => 'Atļaut nedrošu automātisku pieteikšanos, izmantojot formātu: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Rādīt pēc barotnes', 'feeds' => 'Rādīt pēc barotnes',
'order' => 'Kārtot pēc datuma', 'order' => 'Kārtot pēc datuma',
'search' => 'Izteiksme', 'search' => 'Izteiksme',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'Stāvoklis', 'state' => 'Stāvoklis',
'tags' => 'Rādīt pēc birkas', 'tags' => 'Rādīt pēc birkas',
'type' => 'Veids', 'type' => 'Veids',
), ),
'get_all' => 'Rādīt visus rakstus', 'get_all' => 'Rādīt visus rakstus',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'Rādīt kategoriju “%s”', 'get_category' => 'Rādīt kategoriju “%s”',
'get_favorite' => 'Rādīt mīļākos rakstus', 'get_favorite' => 'Rādīt mīļākos rakstus',
'get_feed' => 'Rādīt barotni “%s”', 'get_feed' => 'Rādīt barotni “%s”',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Vārds', 'name' => 'Vārds',
'no_filter' => 'Bez filtra', 'no_filter' => 'Bez filtra',
'number' => 'Pieprasījums nr. %d', 'number' => 'Pieprasījums nr. %d',
'order_asc' => 'Vispirms rādīt vecākos rakstus', 'order_asc' => 'Vispirms rādīt vecākos rakstus',
'order_desc' => 'Vispirms rādīt jaunākos rakstus', 'order_desc' => 'Vispirms rādīt jaunākos rakstus',
'search' => 'Meklēt “%s”', 'search' => 'Meklēt “%s”',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Rādīt visus rakstus', 'state_0' => 'Rādīt visus rakstus',
'state_1' => 'Rādīt lasītos rakstus', 'state_1' => 'Rādīt lasītos rakstus',
'state_2' => 'Rādīt nelasītos rakstus', 'state_2' => 'Rādīt nelasītos rakstus',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (voor gevorderde gebruikers met HTTPS)', 'http' => 'HTTP (voor gevorderde gebruikers met HTTPS)',
'none' => 'Geen (gevaarlijk)', 'none' => 'Geen (gevaarlijk)',
'title' => 'Authenticatie', 'title' => 'Authenticatie',
'token' => 'Authenticatie teken', 'token' => 'Master authentication token', // TODO
'token_help' => 'Sta toegang toe tot de RSS uitvoer van de standaard gebruiker zonder authenticatie:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Authenticatie methode', 'type' => 'Authenticatie methode',
'unsafe_autologin' => 'Sta onveilige automatische log in toe met het volgende formaat: ', 'unsafe_autologin' => 'Sta onveilige automatische log in toe met het volgende formaat: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Weergeven op feed', 'feeds' => 'Weergeven op feed',
'order' => 'Sorteren op datum', 'order' => 'Sorteren op datum',
'search' => 'Expressie', 'search' => 'Expressie',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'Status', 'state' => 'Status',
'tags' => 'Weergeven op label', 'tags' => 'Weergeven op label',
'type' => 'Type', // IGNORE 'type' => 'Type', // IGNORE
), ),
'get_all' => 'Toon alle artikelen', 'get_all' => 'Toon alle artikelen',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'Toon „%s” categorie', 'get_category' => 'Toon „%s” categorie',
'get_favorite' => 'Toon favoriete artikelen', 'get_favorite' => 'Toon favoriete artikelen',
'get_feed' => 'Toon „%s” feed', 'get_feed' => 'Toon „%s” feed',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Naam', 'name' => 'Naam',
'no_filter' => 'Geen filter', 'no_filter' => 'Geen filter',
'number' => 'Query n°%d', // IGNORE 'number' => 'Query n°%d', // IGNORE
'order_asc' => 'Toon oudste artikelen eerst', 'order_asc' => 'Toon oudste artikelen eerst',
'order_desc' => 'Toon nieuwste artikelen eerst', 'order_desc' => 'Toon nieuwste artikelen eerst',
'search' => 'Zoek naar „%s”', 'search' => 'Zoek naar „%s”',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Toon alle artikelen', 'state_0' => 'Toon alle artikelen',
'state_1' => 'Toon gelezen artikelen', 'state_1' => 'Toon gelezen artikelen',
'state_2' => 'Toon ongelezen artikelen', 'state_2' => 'Toon ongelezen artikelen',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (per utilizaires avançats amb HTTPS)', 'http' => 'HTTP (per utilizaires avançats amb HTTPS)',
'none' => 'Cap (perilhós)', 'none' => 'Cap (perilhós)',
'title' => 'Autentificacion', 'title' => 'Autentificacion',
'token' => 'Geton dautentificacion', 'token' => 'Master authentication token', // TODO
'token_help' => 'Permetre laccès a la sortida RSS de lutilizaire per defaut sens cap dautentificacion:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Mòde dautentification', 'type' => 'Mòde dautentification',
'unsafe_autologin' => 'Autorizar las connexions automaticas pas seguras al format: ', 'unsafe_autologin' => 'Autorizar las connexions automaticas pas seguras al format: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Afichatge per flux', 'feeds' => 'Afichatge per flux',
'order' => 'Triar per data', 'order' => 'Triar per data',
'search' => 'Expression', // IGNORE 'search' => 'Expression', // IGNORE
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'Estat', 'state' => 'Estat',
'tags' => 'Afichatge per etiqueta', 'tags' => 'Afichatge per etiqueta',
'type' => 'Tipe', 'type' => 'Tipe',
), ),
'get_all' => 'Mostrar totes los articles', 'get_all' => 'Mostrar totes los articles',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'Mostrar la categoria « %s »', 'get_category' => 'Mostrar la categoria « %s »',
'get_favorite' => 'Mostrar los articles favorits', 'get_favorite' => 'Mostrar los articles favorits',
'get_feed' => 'Mostrar lo flux « %s »', 'get_feed' => 'Mostrar lo flux « %s »',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Nom', 'name' => 'Nom',
'no_filter' => 'Cap de filtre aplicat', 'no_filter' => 'Cap de filtre aplicat',
'number' => 'Filtre n°%d', 'number' => 'Filtre n°%d',
'order_asc' => 'Mostrar los articles mai ancians en primièr', 'order_asc' => 'Mostrar los articles mai ancians en primièr',
'order_desc' => 'Mostrar los articles mai recents en primièr', 'order_desc' => 'Mostrar los articles mai recents en primièr',
'search' => 'Recèrca de « %s »', 'search' => 'Recèrca de « %s »',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Mostrar totes los articles', 'state_0' => 'Mostrar totes los articles',
'state_1' => 'Mostrar los articles pas legits', 'state_1' => 'Mostrar los articles pas legits',
'state_2' => 'Mostrar los articles pas legits', 'state_2' => 'Mostrar los articles pas legits',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (dla zaawansowanych użytkowników, z wykorzystaniem HTTPS)', 'http' => 'HTTP (dla zaawansowanych użytkowników, z wykorzystaniem HTTPS)',
'none' => 'Brak (niebezpieczna)', 'none' => 'Brak (niebezpieczna)',
'title' => 'Uwierzytelnianie', 'title' => 'Uwierzytelnianie',
'token' => 'Token uwierzytelniania', 'token' => 'Master authentication token', // TODO
'token_help' => 'Pozwala na dostęp do treści RSS domyślnego użytkownika bez uwierzytelnienia:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Metoda uwierzytelniania', 'type' => 'Metoda uwierzytelniania',
'unsafe_autologin' => 'Pozwól na niebezpieczne automatyczne logowanie następującym schematem: -> todo', 'unsafe_autologin' => 'Pozwól na niebezpieczne automatyczne logowanie następującym schematem: -> todo',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Według kanału', 'feeds' => 'Według kanału',
'order' => 'Sortowanie wg daty', 'order' => 'Sortowanie wg daty',
'search' => 'Wyrażenie', 'search' => 'Wyrażenie',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'Stan', 'state' => 'Stan',
'tags' => 'Według tagu', 'tags' => 'Według tagu',
'type' => 'Rodzaj', 'type' => 'Rodzaj',
), ),
'get_all' => 'Wyświetlenie wszystkich wiadomości', 'get_all' => 'Wyświetlenie wszystkich wiadomości',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'Wyświetlenie kategorii “%s”', 'get_category' => 'Wyświetlenie kategorii “%s”',
'get_favorite' => 'Wyświetlenie ulubionych wiadomości', 'get_favorite' => 'Wyświetlenie ulubionych wiadomości',
'get_feed' => 'Wyświetlenie kanału “%s”', 'get_feed' => 'Wyświetlenie kanału “%s”',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Nazwa', 'name' => 'Nazwa',
'no_filter' => 'Brak filtrów', 'no_filter' => 'Brak filtrów',
'number' => 'Zapytanie nr %d', 'number' => 'Zapytanie nr %d',
'order_asc' => 'Wyświetl najpierw najstarsze wiadomości', 'order_asc' => 'Wyświetl najpierw najstarsze wiadomości',
'order_desc' => 'Wyświetl najpierw najnowsze wiadomości', 'order_desc' => 'Wyświetl najpierw najnowsze wiadomości',
'search' => 'Szukaj “%s”', 'search' => 'Szukaj “%s”',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Wyświetl wszystkie wiadomości', 'state_0' => 'Wyświetl wszystkie wiadomości',
'state_1' => 'Wyświetl przeczytane wiadomości', 'state_1' => 'Wyświetl przeczytane wiadomości',
'state_2' => 'Wyświetl nieprzeczytane wiadomości', 'state_2' => 'Wyświetl nieprzeczytane wiadomości',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (Para usuários avançados com HTTPS)', 'http' => 'HTTP (Para usuários avançados com HTTPS)',
'none' => 'Nenhum (Perigoso)', 'none' => 'Nenhum (Perigoso)',
'title' => 'Autenticação', 'title' => 'Autenticação',
'token' => 'Token de autenticação ', 'token' => 'Master authentication token', // TODO
'token_help' => 'Permitir acesso a saída RSS para o usuário padrão sem autenticação', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Método de autenticação', 'type' => 'Método de autenticação',
'unsafe_autologin' => 'Permitir login automática insegura usando o seguinte formato: ', 'unsafe_autologin' => 'Permitir login automática insegura usando o seguinte formato: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Exibir por feed', 'feeds' => 'Exibir por feed',
'order' => 'Ordenar por data', 'order' => 'Ordenar por data',
'search' => 'Expressão', 'search' => 'Expressão',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'Estado', 'state' => 'Estado',
'tags' => 'Exibir por tag', // DIRTY 'tags' => 'Exibir por tag', // DIRTY
'type' => 'Tipo', 'type' => 'Tipo',
), ),
'get_all' => 'Mostrar todos os artigos', 'get_all' => 'Mostrar todos os artigos',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'Visualizar “%s” categoria', 'get_category' => 'Visualizar “%s” categoria',
'get_favorite' => 'Visualizar artigos favoritos', 'get_favorite' => 'Visualizar artigos favoritos',
'get_feed' => 'Visualizar “%s” feed', 'get_feed' => 'Visualizar “%s” feed',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Nome', 'name' => 'Nome',
'no_filter' => 'Sem filtro', 'no_filter' => 'Sem filtro',
'number' => 'Query n°%d', // IGNORE 'number' => 'Query n°%d', // IGNORE
'order_asc' => 'Exibir artigos mais antigos primeiro', 'order_asc' => 'Exibir artigos mais antigos primeiro',
'order_desc' => 'Exibir artigos mais novos primeiro', 'order_desc' => 'Exibir artigos mais novos primeiro',
'search' => 'Busca por “%s”', 'search' => 'Busca por “%s”',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Exibir todos os artigos', 'state_0' => 'Exibir todos os artigos',
'state_1' => 'Exibir artigos lidos', 'state_1' => 'Exibir artigos lidos',
'state_2' => 'Exibir artigos não lidos', 'state_2' => 'Exibir artigos não lidos',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (для опытных пользователей с HTTPS)', 'http' => 'HTTP (для опытных пользователей с HTTPS)',
'none' => 'Без аутентификации (небезопасно)', 'none' => 'Без аутентификации (небезопасно)',
'title' => 'Аутентификации', 'title' => 'Аутентификации',
'token' => 'Токен аутентификации', 'token' => 'Master authentication token', // TODO
'token_help' => 'Разрешает доступ к RSS-лентам пользователя по умолчанию без аутентификации:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Способ аутентификации', 'type' => 'Способ аутентификации',
'unsafe_autologin' => 'Разрешить небезопасный автоматический вход с использованием следующего формата: ', 'unsafe_autologin' => 'Разрешить небезопасный автоматический вход с использованием следующего формата: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Отображение по ленте', 'feeds' => 'Отображение по ленте',
'order' => 'Сортировать по дате', 'order' => 'Сортировать по дате',
'search' => 'Выражение', 'search' => 'Выражение',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'Состояние', 'state' => 'Состояние',
'tags' => 'Отображение по метке', 'tags' => 'Отображение по метке',
'type' => 'Тип', 'type' => 'Тип',
), ),
'get_all' => 'Показать все статьи', 'get_all' => 'Показать все статьи',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'Показать категорию “%s”', 'get_category' => 'Показать категорию “%s”',
'get_favorite' => 'Показать избранные статьи', 'get_favorite' => 'Показать избранные статьи',
'get_feed' => 'Показать ленту “%s”', 'get_feed' => 'Показать ленту “%s”',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Название', 'name' => 'Название',
'no_filter' => 'Нет фильтров', 'no_filter' => 'Нет фильтров',
'number' => 'Запрос №%d', 'number' => 'Запрос №%d',
'order_asc' => 'Показывать сначала старые статьи', 'order_asc' => 'Показывать сначала старые статьи',
'order_desc' => 'Показывать сначала новые статьи', 'order_desc' => 'Показывать сначала новые статьи',
'search' => 'Искать “%s”', 'search' => 'Искать “%s”',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Показать все статьи', 'state_0' => 'Показать все статьи',
'state_1' => 'Показать прочитанные статьи', 'state_1' => 'Показать прочитанные статьи',
'state_2' => 'Показать непрочитанные статьи', 'state_2' => 'Показать непрочитанные статьи',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (pre pokročilých používateľov s HTTPS)', 'http' => 'HTTP (pre pokročilých používateľov s HTTPS)',
'none' => 'Žiadny (nebezpečné)', 'none' => 'Žiadny (nebezpečné)',
'title' => 'Prihlásenie', 'title' => 'Prihlásenie',
'token' => 'Token prihlásenia', 'token' => 'Master authentication token', // TODO
'token_help' => 'Povoliť prístup k výstupu RSS prednastaveného používateľa bez prihlásenia:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Spôsob prihlásenia', 'type' => 'Spôsob prihlásenia',
'unsafe_autologin' => 'Povoliť nebezpečné automatické prihlásenie pomocou webového formulára: ', 'unsafe_autologin' => 'Povoliť nebezpečné automatické prihlásenie pomocou webového formulára: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Zobraziť podľa kanála', 'feeds' => 'Zobraziť podľa kanála',
'order' => 'Zobraziť podľa dátumu', 'order' => 'Zobraziť podľa dátumu',
'search' => 'Výraz', 'search' => 'Výraz',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'Štát', 'state' => 'Štát',
'tags' => 'Zobraziť podľa štítku', 'tags' => 'Zobraziť podľa štítku',
'type' => 'Typ', 'type' => 'Typ',
), ),
'get_all' => 'Zobraziť všetky články', 'get_all' => 'Zobraziť všetky články',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => 'Zobraziť kategóriu “%s”', 'get_category' => 'Zobraziť kategóriu “%s”',
'get_favorite' => 'Zobraziť obľúbené články', 'get_favorite' => 'Zobraziť obľúbené články',
'get_feed' => 'Zobraziť kanál “%s”', 'get_feed' => 'Zobraziť kanál “%s”',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'Meno', 'name' => 'Meno',
'no_filter' => 'Žiadny filter', 'no_filter' => 'Žiadny filter',
'number' => 'Dopyt číslo %d', 'number' => 'Dopyt číslo %d',
'order_asc' => 'Zobraziť staršie články hore', 'order_asc' => 'Zobraziť staršie články hore',
'order_desc' => 'Zobraziť novšie články hore', 'order_desc' => 'Zobraziť novšie články hore',
'search' => 'Vyhľadáva sa: “%s”', 'search' => 'Vyhľadáva sa: “%s”',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Zobraziť všetky články', 'state_0' => 'Zobraziť všetky články',
'state_1' => 'Zobraziť prečítané články', 'state_1' => 'Zobraziť prečítané články',
'state_2' => 'Zobraziť neprečítané články', 'state_2' => 'Zobraziť neprečítané články',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP (ileri kullanıcılar için, HTTPS)', 'http' => 'HTTP (ileri kullanıcılar için, HTTPS)',
'none' => 'Hiçbiri (tehlikeli)', 'none' => 'Hiçbiri (tehlikeli)',
'title' => 'Kimlik doğrulama', 'title' => 'Kimlik doğrulama',
'token' => 'Kimlik doğrulama işareti', 'token' => 'Master authentication token', // TODO
'token_help' => 'Kimlik doğrulama olmaksızın öntanımlı kullanıcının RSS çıktısına erişime izin ver:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => 'Kimlik doğrulama yöntemi', 'type' => 'Kimlik doğrulama yöntemi',
'unsafe_autologin' => 'Güvensiz otomatik girişe izin ver: ', 'unsafe_autologin' => 'Güvensiz otomatik girişe izin ver: ',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => 'Akışa göre göster', 'feeds' => 'Akışa göre göster',
'order' => 'Tarihe göre göster', 'order' => 'Tarihe göre göster',
'search' => 'İfade', 'search' => 'İfade',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => 'Durum', 'state' => 'Durum',
'tags' => 'Etikete göre göster', 'tags' => 'Etikete göre göster',
'type' => 'Tür', 'type' => 'Tür',
), ),
'get_all' => 'Tüm makaleleri göster', 'get_all' => 'Tüm makaleleri göster',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => '“%s” kategorisini göster', 'get_category' => '“%s” kategorisini göster',
'get_favorite' => 'Favori makaleleri göster', 'get_favorite' => 'Favori makaleleri göster',
'get_feed' => '“%s” akışını göster', 'get_feed' => '“%s” akışını göster',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => 'İsim', 'name' => 'İsim',
'no_filter' => 'Filtre yok', 'no_filter' => 'Filtre yok',
'number' => 'Sorgu n°%d', 'number' => 'Sorgu n°%d',
'order_asc' => 'Önce eski makaleleri göster', 'order_asc' => 'Önce eski makaleleri göster',
'order_desc' => 'Önce yeni makaleleri göster', 'order_desc' => 'Önce yeni makaleleri göster',
'search' => '“%s” için arama', 'search' => '“%s” için arama',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => 'Tüm makaleleri göster', 'state_0' => 'Tüm makaleleri göster',
'state_1' => 'Okunmuş makaleleri göster', 'state_1' => 'Okunmuş makaleleri göster',
'state_2' => 'Okunmamış makaleleri göster', 'state_2' => 'Okunmamış makaleleri göster',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP面向启用 HTTPS 的高级用户)', 'http' => 'HTTP面向启用 HTTPS 的高级用户)',
'none' => '无(危险)', 'none' => '无(危险)',
'title' => '认证', 'title' => '认证',
'token' => '认证口令', 'token' => 'Master authentication token', // TODO
'token_help' => '用于不经认证访问默认用户的 RSS 输出:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => '认证方式', 'type' => '认证方式',
'unsafe_autologin' => '允许不安全的自动登陆方式:', 'unsafe_autologin' => '允许不安全的自动登陆方式:',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => '按订阅源显示', 'feeds' => '按订阅源显示',
'order' => '按日期排序', 'order' => '按日期排序',
'search' => '表达式', 'search' => '表达式',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => '状态', 'state' => '状态',
'tags' => '按标签显示', 'tags' => '按标签显示',
'type' => '类型', 'type' => '类型',
), ),
'get_all' => '显示所有文章', 'get_all' => '显示所有文章',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => '显示分类 “%s”', 'get_category' => '显示分类 “%s”',
'get_favorite' => '显示收藏文章', 'get_favorite' => '显示收藏文章',
'get_feed' => '显示订阅源 “%s”', 'get_feed' => '显示订阅源 “%s”',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => '名称', 'name' => '名称',
'no_filter' => '无过滤器', 'no_filter' => '无过滤器',
'number' => '查询 n°%d', 'number' => '查询 n°%d',
'order_asc' => '由旧至新显示文章', 'order_asc' => '由旧至新显示文章',
'order_desc' => '由新至旧显示文章', 'order_desc' => '由新至旧显示文章',
'search' => '搜索 “%s”', 'search' => '搜索 “%s”',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => '显示所有文章', 'state_0' => '显示所有文章',
'state_1' => '显示已读文章', 'state_1' => '显示已读文章',
'state_2' => '显示未读文章', 'state_2' => '显示未读文章',

View File

@ -19,8 +19,8 @@ return array(
'http' => 'HTTP面向啟用 HTTPS 的高級用戶)', 'http' => 'HTTP面向啟用 HTTPS 的高級用戶)',
'none' => '無認證(危險)', 'none' => '無認證(危險)',
'title' => '認證', 'title' => '認證',
'token' => '認證口令', 'token' => 'Master authentication token', // TODO
'token_help' => '用於不經認證訪問預設使用者的 RSS 輸出:', 'token_help' => 'Allows access to all RSS outputs of the user as well as refreshing feeds without authentication:', // TODO
'type' => '認證方式', 'type' => '認證方式',
'unsafe_autologin' => '允許不安全的自動登入方式:', 'unsafe_autologin' => '允許不安全的自動登入方式:',
), ),

View File

@ -120,20 +120,33 @@ return array(
'feeds' => '按訂閱源顯示', 'feeds' => '按訂閱源顯示',
'order' => '按日期排序', 'order' => '按日期排序',
'search' => '表達式', 'search' => '表達式',
'shareOpml' => 'Enable sharing by OPML of corresponding categories and feeds', // TODO
'shareRss' => 'Enable sharing by HTML &amp; RSS', // TODO
'state' => '狀態', 'state' => '狀態',
'tags' => '按標簽顯示', 'tags' => '按標簽顯示',
'type' => '類型', 'type' => '類型',
), ),
'get_all' => '顯示所有文章', 'get_all' => '顯示所有文章',
'get_all_labels' => 'Display articles with any label', // TODO
'get_category' => '顯示分類 “%s”', 'get_category' => '顯示分類 “%s”',
'get_favorite' => '顯示收藏文章', 'get_favorite' => '顯示收藏文章',
'get_feed' => '顯示訂閱源 “%s”', 'get_feed' => '顯示訂閱源 “%s”',
'get_important' => 'Display articles from important feeds', // TODO
'get_label' => 'Display articles with “%s” label', // TODO
'help' => 'See the <a href="https://freshrss.github.io/FreshRSS/en/users/user_queries.html" target="_blank">documentation for user queries and resharing by HTML / RSS / OPML</a>.', // TODO
'name' => '名稱', 'name' => '名稱',
'no_filter' => '無過濾器', 'no_filter' => '無過濾器',
'number' => '查詢 n°%d', 'number' => '查詢 n°%d',
'order_asc' => '由舊至新顯示文章', 'order_asc' => '由舊至新顯示文章',
'order_desc' => '由新至舊顯示文章', 'order_desc' => '由新至舊顯示文章',
'search' => '搜尋 “%s”', 'search' => '搜尋 “%s”',
'share' => array(
'_' => 'Share this query by link', // TODO
'help' => 'Give this link if you want to share this query with anyone', // TODO
'html' => 'Shareable link to the HTML page', // TODO
'opml' => 'Shareable link to the OPML list of feeds', // TODO
'rss' => 'Shareable link to the RSS feed', // TODO
),
'state_0' => '顯示所有文章', 'state_0' => '顯示所有文章',
'state_1' => '顯示已讀文章', 'state_1' => '顯示已讀文章',
'state_2' => '顯示未讀文章', 'state_2' => '顯示未讀文章',

View File

@ -1,9 +1,10 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
/** @var FreshRSS_View $this */
?> ?>
<header class="header"> <header class="header">
<div class="item title"> <div class="item title">
<a href="<?= _url('index', 'index') ?>"> <a href="<?= Minz_Url::display(['c' => 'index', 'a' => 'index'], 'html', 'root') ?>">
<?php if (FreshRSS_Context::systemConf()->logo_html == '') { ?> <?php if (FreshRSS_Context::systemConf()->logo_html == '') { ?>
<img class="logo" src="<?= _i('FreshRSS-logo', FreshRSS_Themes::ICON_URL) ?>" alt="FreshRSS" loading="lazy" /> <img class="logo" src="<?= _i('FreshRSS-logo', FreshRSS_Themes::ICON_URL) ?>" alt="FreshRSS" loading="lazy" />
<?php <?php
@ -16,32 +17,29 @@
<div class="item search"> <div class="item search">
<?php if (FreshRSS_Auth::hasAccess() || FreshRSS_Context::systemConf()->allow_anonymous) { ?> <?php if (FreshRSS_Auth::hasAccess() || FreshRSS_Context::systemConf()->allow_anonymous) { ?>
<form action="<?= _url('index', 'index') ?>" method="get"> <form action="<?= $this->html_url ?>" method="get">
<div class="stick"> <div class="stick">
<?php if (Minz_Request::controllerName() === 'index'): ?>
<?php if (in_array(Minz_Request::actionName(), ['normal', 'global', 'reader'], true)) { ?>
<input type="hidden" name="a" value="<?= Minz_Request::actionName() ?>" />
<?php } if (Minz_Request::paramString('get') !== '') { ?>
<input type="hidden" name="get" value="<?= FreshRSS_Context::currentGet() ?>" />
<?php } if (Minz_Request::paramInt('state') !== 0) { ?>
<input type="hidden" name="state" value="<?= Minz_Request::paramInt('state') ?>" />
<?php } ?>
<?php endif; ?>
<?php if (Minz_Request::paramString('user') !== '') { ?>
<input type="hidden" name="user" value="<?= Minz_User::name() ?>" />
<?php } if (ctype_alnum(Minz_Request::paramString('t'))) { ?>
<input type="hidden" name="t" value="<?= Minz_Request::paramString('t') ?>" />
<?php } if (ctype_upper(Minz_Request::paramString('order'))) { ?>
<input type="hidden" name="order" value="<?= FreshRSS_Context::$order ?>" />
<?php } if (ctype_lower(Minz_Request::paramString('f'))) { ?>
<input type="hidden" name="f" value="<?= Minz_Request::paramString('f') ?>" />
<?php } ?>
<input type="search" name="search" id="search" <input type="search" name="search" id="search"
value="<?= htmlspecialchars(htmlspecialchars_decode(FreshRSS_Context::$search->getRawInput(), ENT_QUOTES), ENT_COMPAT, 'UTF-8') ?>" value="<?= htmlspecialchars(htmlspecialchars_decode(Minz_Request::paramString('search'), ENT_QUOTES), ENT_COMPAT, 'UTF-8') ?>"
placeholder="<?= _t('gen.menu.search') ?>" /> placeholder="<?= _t('gen.menu.search') ?>" />
<?php $param_a = Minz_Request::actionName(); ?>
<?php if (in_array($param_a, ['normal', 'global', 'reader'], true)) { ?>
<input type="hidden" name="a" value="<?= $param_a ?>" />
<?php } ?>
<?php $get = Minz_Request::paramString('get'); ?>
<?php if ($get !== '') { ?>
<input type="hidden" name="get" value="<?= $get ?>" />
<?php } ?>
<?php $order = Minz_Request::paramString('order'); ?>
<?php if ($order !== '') { ?>
<input type="hidden" name="order" value="<?= $order ?>" />
<?php } ?>
<?php $state = Minz_Request::paramString('state'); ?>
<?php if ($state !== '') { ?>
<input type="hidden" name="state" value="<?= $state ?>" />
<?php } ?>
<button class="btn" type="submit"><?= _i('search') ?></button> <button class="btn" type="submit"><?= _i('search') ?></button>
</div> </div>
</form> </form>
@ -120,7 +118,7 @@
</nav> </nav>
<?php } elseif (FreshRSS_Auth::accessNeedsAction()) { ?> <?php } elseif (FreshRSS_Auth::accessNeedsAction()) { ?>
<div class="item configure"> <div class="item configure">
<a class="signin" href="<?= _url('auth', 'login') ?>"><?= _i('login') ?><?= _t('gen.auth.login') ?></a> <a class="signin" href="<?= Minz_Url::display(['c' => 'auth', 'a' => 'login'], 'html', 'root') ?>"><?= _i('login') ?><?= _t('gen.auth.login') ?></a>
</div> </div>
<?php } ?> <?php } ?>
</header> </header>

View File

@ -2,15 +2,17 @@
declare(strict_types=1); declare(strict_types=1);
/** @var FreshRSS_View $this */ /** @var FreshRSS_View $this */
FreshRSS::preLayout(); FreshRSS::preLayout();
$class = '';
if (_t('gen.dir') === 'rtl') {
echo ' dir="rtl"';
$class = 'rtl ';
}
if (FreshRSS_Context::userConf()->darkMode !== 'no') {
$class .= 'darkMode_' . FreshRSS_Context::userConf()->darkMode;
}
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="<?= FreshRSS_Context::userConf()->language ?>" xml:lang="<?= FreshRSS_Context::userConf()->language ?>"<?php <html lang="<?= FreshRSS_Context::userConf()->language ?>" xml:lang="<?= FreshRSS_Context::userConf()->language ?>" class="<?= $class ?>">
$class = '';
if (_t('gen.dir') === 'rtl') {
echo ' dir="rtl"';
$class = 'rtl ';
}
?> class="<?= $class ?><?= (FreshRSS_Context::userConf()->darkMode === 'no') ? '' : 'darkMode_' . FreshRSS_Context::userConf()->darkMode ?>">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />

View File

@ -41,26 +41,15 @@
<li class="item"> <li class="item">
<span> <span>
<form action="<?= _url('index', 'index') ?>" method="get"> <form action="<?= _url('index', 'index') ?>" method="get">
<?php $param_a = Minz_Request::actionName(); ?> <?php if (in_array(Minz_Request::actionName(), ['normal', 'global', 'reader'], true)) { ?>
<?php if (in_array($param_a, ['normal', 'global', 'reader'], true)) { ?> <input type="hidden" name="a" value="<?= Minz_Request::actionName() ?>" />
<input type="hidden" name="a" value="<?= $param_a ?>" /> <?php } if (Minz_Request::paramString('get') !== '') { ?>
<input type="hidden" name="get" value="<?= FreshRSS_Context::currentGet() ?>" />
<?php } if (ctype_upper(Minz_Request::paramString('order'))) { ?>
<input type="hidden" name="order" value="<?= FreshRSS_Context::$order ?>" />
<?php } if (Minz_Request::paramInt('state') !== 0) { ?>
<input type="hidden" name="state" value="<?= FreshRSS_Context::$state ?>" />
<?php } ?> <?php } ?>
<?php $get = Minz_Request::paramString('get'); ?>
<?php if ($get !== '') { ?>
<input type="hidden" name="get" value="<?= $get ?>" />
<?php } ?>
<?php $order = Minz_Request::paramString('order'); ?>
<?php if ($order !== '') { ?>
<input type="hidden" name="order" value="<?= $order ?>" />
<?php } ?>
<?php $state = Minz_Request::paramString('state'); ?>
<?php if ($state !== '') { ?>
<input type="hidden" name="state" value="<?= $state ?>" />
<?php } ?>
<div class="stick search"> <div class="stick search">
<input type="search" name="search" <input type="search" name="search"
value="<?= htmlspecialchars(htmlspecialchars_decode(FreshRSS_Context::$search->getRawInput(), ENT_QUOTES), ENT_COMPAT, 'UTF-8'); ?>" value="<?= htmlspecialchars(htmlspecialchars_decode(FreshRSS_Context::$search->getRawInput(), ENT_QUOTES), ENT_COMPAT, 'UTF-8'); ?>"
@ -89,7 +78,7 @@
<?php if (!empty($raw_query['url'])): ?> <?php if (!empty($raw_query['url'])): ?>
<a href="<?= $raw_query['url'] ?>"><?= $raw_query['name'] ?? $raw_query['url'] ?></a> <a href="<?= $raw_query['url'] ?>"><?= $raw_query['name'] ?? $raw_query['url'] ?></a>
<?php else: ?> <?php else: ?>
<?php $query = new FreshRSS_UserQuery($raw_query); ?> <?php $query = new FreshRSS_UserQuery($raw_query, FreshRSS_Context::categories(), FreshRSS_Context::labels()); ?>
<a href="<?= $query->getUrl() ?>"><?= $query->getName() ?></a> <a href="<?= $query->getUrl() ?>"><?= $query->getName() ?></a>
<?php endif; ?> <?php endif; ?>
</li> </li>
@ -210,20 +199,6 @@
<?php <?php
} }
?> ?>
<?php
$url_output['a'] = 'rss';
if (FreshRSS_Context::userConf()->token) {
$url_output['params']['user'] = Minz_User::name();
$url_output['params']['token'] = FreshRSS_Context::userConf()->token;
}
if (FreshRSS_Context::userConf()->since_hours_posts_per_rss) {
$url_output['params']['hours'] = FreshRSS_Context::userConf()->since_hours_posts_per_rss;
}
?>
<a class="view-rss btn" target="_blank" rel="noreferrer" title="<?= _t('index.menu.rss_view') ?>" href="<?= Minz_Url::display($url_output) ?>">
<?= _i('rss') ?>
</a>
</div> </div>
<?php $nav_menu_hooks = Minz_ExtensionManager::callHookString('nav_menu'); ?> <?php $nav_menu_hooks = Minz_ExtensionManager::callHookString('nav_menu'); ?>

View File

@ -2,17 +2,27 @@
declare(strict_types=1); declare(strict_types=1);
/** @var FreshRSS_View $this */ /** @var FreshRSS_View $this */
FreshRSS::preLayout(); FreshRSS::preLayout();
$class = '';
if (_t('gen.dir') === 'rtl') {
echo ' dir="rtl"';
$class = 'rtl ';
}
if (FreshRSS_Context::userConf()->darkMode !== 'no') {
$class .= 'darkMode_' . FreshRSS_Context::userConf()->darkMode;
}
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="<?= FreshRSS_Context::userConf()->language ?>" xml:lang="<?= FreshRSS_Context::userConf()->language ?>"> <html lang="<?= FreshRSS_Context::userConf()->language ?>" xml:lang="<?= FreshRSS_Context::userConf()->language ?>" class="<?= $class ?>">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<?= FreshRSS_View::metaThemeColor() ?>
<?= FreshRSS_View::headStyle() ?> <?= FreshRSS_View::headStyle() ?>
<script id="jsonVars" type="application/json"> <script id="jsonVars" type="application/json">
<?php $this->renderHelper('javascript_vars'); ?> <?php $this->renderHelper('javascript_vars'); ?>
</script> </script>
<?= FreshRSS_View::headScript() ?> <?= FreshRSS_View::headScript() ?>
<link rel="manifest" href="<?= Minz_Url::display('/themes/manifest.json') ?>" />
<link rel="shortcut icon" id="favicon" type="image/x-icon" sizes="16x16 64x64" href="<?= Minz_Url::display('/favicon.ico') ?>" /> <link rel="shortcut icon" id="favicon" type="image/x-icon" sizes="16x16 64x64" href="<?= Minz_Url::display('/favicon.ico') ?>" />
<link rel="icon msapplication-TileImage apple-touch-icon" type="image/png" sizes="256x256" href="<?= Minz_Url::display('/themes/icons/favicon-256.png') ?>" /> <link rel="icon msapplication-TileImage apple-touch-icon" type="image/png" sizes="256x256" href="<?= Minz_Url::display('/themes/icons/favicon-256.png') ?>" />
<link rel="apple-touch-icon" href="<?= Minz_Url::display('/themes/icons/apple-touch-icon.png') ?>" /> <link rel="apple-touch-icon" href="<?= Minz_Url::display('/themes/icons/apple-touch-icon.png') ?>" />
@ -20,9 +30,15 @@
<meta name="apple-mobile-web-app-status-bar-style" content="black" /> <meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="<?= FreshRSS_Context::systemConf()->title ?>"> <meta name="apple-mobile-web-app-title" content="<?= FreshRSS_Context::systemConf()->title ?>">
<meta name="msapplication-TileColor" content="#FFF" /> <meta name="msapplication-TileColor" content="#FFF" />
<meta name="theme-color" content="#FFF" />
<?php if (!FreshRSS_Context::systemConf()->allow_referrer) { ?>
<meta name="referrer" content="never" /> <meta name="referrer" content="never" />
<meta name="robots" content="noindex,nofollow" /> <?php } ?>
<?= FreshRSS_View::headTitle() ?> <?= FreshRSS_View::headTitle() ?>
<?php if ($this->rss_url != ''): ?>
<link rel="alternate" type="application/rss+xml" title="<?= $this->rss_title ?>" href="<?= $this->rss_url ?>" />
<?php endif; ?>
<meta name="robots" content="noindex,nofollow" />
</head> </head>
<body> <body>
@ -30,7 +46,7 @@
<div class="app-layout app-layout-simple"> <div class="app-layout app-layout-simple">
<div class="header"> <div class="header">
<div class="item title"> <div class="item title">
<a href="<?= _url('index', 'index') ?>"> <a href="<?= Minz_Url::display(['c' => 'index', 'a' => 'index'], 'html', 'root') ?>">
<?php if (FreshRSS_Context::systemConf()->logo_html == '') { ?> <?php if (FreshRSS_Context::systemConf()->logo_html == '') { ?>
<img class="logo" src="<?= _i('FreshRSS-logo', FreshRSS_Themes::ICON_URL) ?>" alt="FreshRSS" loading="lazy" /> <img class="logo" src="<?= _i('FreshRSS-logo', FreshRSS_Themes::ICON_URL) ?>" alt="FreshRSS" loading="lazy" />
<?php <?php
@ -43,14 +59,20 @@
<div class="item"></div> <div class="item"></div>
<div class="item"> <?php if (FreshRSS_Auth::accessNeedsAction()): ?>
<?php if (FreshRSS_Auth::accessNeedsAction()) { ?> <div class="item configure">
<a class="signout" href="<?= _url('auth', 'logout') ?>"> <?php if (FreshRSS_Auth::hasAccess()): ?>
<?= _i('logout') . _t('gen.auth.logout') ?> <a class="signout" href="<?= Minz_Url::display(['c' => 'auth', 'a' => 'logout'], 'html', 'root') ?>">
<?= _i('logout') ?><?= _t('gen.auth.logout') ?>
(<?= htmlspecialchars(Minz_User::name() ?? '', ENT_NOQUOTES, 'UTF-8') ?>) (<?= htmlspecialchars(Minz_User::name() ?? '', ENT_NOQUOTES, 'UTF-8') ?>)
</a> </a>
<?php } ?> <?php else: ?>
</div> <a class="signin" href="<?= Minz_Url::display(['c' => 'auth', 'a' => 'login'], 'html', 'root') ?>">
<?= _i('login') ?><?= _t('gen.auth.login') ?>
</a>
<?php endif; ?>
</div>
<?php endif; ?>
</div> </div>
<?php $this->render(); ?> <?php $this->render(); ?>

View File

@ -18,6 +18,9 @@
<div class="box-title"> <div class="box-title">
<a class="configure open-slider" href="<?= _url('configure', 'query', 'id', '' . $key) ?>"><?= _i('configure') ?></a><h2><?= $query->getName() ?></h2> <a class="configure open-slider" href="<?= _url('configure', 'query', 'id', '' . $key) ?>"><?= _i('configure') ?></a><h2><?= $query->getName() ?></h2>
<input type="hidden" id="queries_<?= $key ?>_name" name="queries[<?= $key ?>][name]" value="<?= $query->getName() ?>"/> <input type="hidden" id="queries_<?= $key ?>_name" name="queries[<?= $key ?>][name]" value="<?= $query->getName() ?>"/>
<input type="hidden" id="queries_<?= $key ?>_token" name="queries[<?= $key ?>][token]" value="<?= $query->getToken() ?>"/>
<input type="hidden" id="queries_<?= $key ?>_shareRss" name="queries[<?= $key ?>][token]" value="<?= $query->shareRss() ?>"/>
<input type="hidden" id="queries_<?= $key ?>_shareOpml" name="queries[<?= $key ?>][token]" value="<?= $query->shareOpml() ?>"/>
<input type="hidden" id="queries_<?= $key ?>_url" name="queries[<?= $key ?>][url]" value="<?= $query->getUrl() ?>"/> <input type="hidden" id="queries_<?= $key ?>_url" name="queries[<?= $key ?>][url]" value="<?= $query->getUrl() ?>"/>
<input type="hidden" id="queries_<?= $key ?>_search" name="queries[<?= $key ?>][search]" value="<?= urlencode($query->getSearch()->getRawInput()) ?>"/> <input type="hidden" id="queries_<?= $key ?>_search" name="queries[<?= $key ?>][search]" value="<?= urlencode($query->getSearch()->getRawInput()) ?>"/>
<input type="hidden" id="queries_<?= $key ?>_state" name="queries[<?= $key ?>][state]" value="<?= $query->getState() ?>"/> <input type="hidden" id="queries_<?= $key ?>_state" name="queries[<?= $key ?>][state]" value="<?= $query->getState() ?>"/>

View File

@ -7,7 +7,6 @@
?> ?>
<div class="post"> <div class="post">
<h2><?= $this->query->getName() ?></h2> <h2><?= $this->query->getName() ?></h2>
<div> <div>
<a href="<?= $this->query->getUrl() ?>"><?= _i('link') ?> <?= _t('gen.action.filter') ?></a> <a href="<?= $this->query->getUrl() ?>"><?= _i('link') ?> <?= _t('gen.action.filter') ?></a>
</div> </div>
@ -18,15 +17,53 @@
<div class="form-group"> <div class="form-group">
<label class="group-name" for="name"><?= _t('conf.query.name') ?></label> <label class="group-name" for="name"><?= _t('conf.query.name') ?></label>
<div class="group-controls"> <div class="group-controls">
<input type="text" name="name" id="name" value="<?= $this->query->getName() ?>" /> <input type="text" name="name" id="name" value="<?= $this->query->getName() ?>" />
<input type="hidden" name="query[token]" id="query_token" value="<?= $this->query->getToken() ?>" />
</div> </div>
</div> </div>
<legend><?= _t('conf.query.filter') ?></legend>
<legend><?= _t('conf.query.share') ?></legend>
<div class="form-group">
<div class="group-controls">
<label class="checkbox" for="shareRss">
<input type="checkbox" name="query[shareRss]" id="shareRss" value="1" <?= $this->query->shareRss() ? 'checked="checked"' : ''?> />
<?= _t('conf.query.filter.shareRss') ?>
</label>
<?php if ($this->query->sharedUrlRss() !== ''): ?>
<ul>
<li><a href="<?= $this->query->sharedUrlHtml() ?>"><?= _i('link') ?> <?= _t('conf.query.share.html') ?></a></li>
<li><a href="<?= $this->query->sharedUrlRss() ?>"><?= _i('link') ?> <?= _t('conf.query.share.rss') ?></a></li>
</ul>
<?php endif; ?>
</div>
<div class="group-controls">
<label class="checkbox" for="shareOpml">
<input type="checkbox" name="query[shareOpml]" id="shareOpml" value="1" <?= $this->query->shareOpml() && $this->query->safeForOpml() ? 'checked="checked"' : '' ?>
<?= $this->query->safeForOpml() ? '' : 'disabled="disabled"' ?> />
<?= _t('conf.query.filter.shareOpml') ?>
</label>
<?php if ($this->query->sharedUrlOpml() !== ''): ?>
<ul>
<li><a href="<?= $this->query->sharedUrlOpml() ?>"><?= _i('link') ?> <?= _t('conf.query.share.opml') ?></a></li>
</ul>
<?php endif; ?>
</div>
<p class="help"><?= _i('help') ?> <?= _t('conf.query.share.help') ?></a></p>
<p class="help"><?= _i('help') ?> <?= _t('conf.query.help') ?></a></p>
</div>
<div class="form-group form-actions">
<div class="group-controls">
<button type="submit" class="btn btn-important"><?= _t('gen.action.submit') ?></button>
</div>
</div>
<legend><?= _t('conf.query.filter') ?></legend>
<div class="form-group"> <div class="form-group">
<label class="group-name" for=""><?= _t('conf.query.filter.search') ?></label> <label class="group-name" for=""><?= _t('conf.query.filter.search') ?></label>
<div class="group-controls"> <div class="group-controls">
<input type="text" id="query_search" name="query[search]" value="<?= htmlspecialchars($this->query->getSearch()->getRawInput(), ENT_COMPAT, 'UTF-8') ?>"/> <input type="text" id="query_search" name="query[search]" value="<?= htmlspecialchars($this->query->getSearch()->getRawInput(), ENT_COMPAT, 'UTF-8') ?>"/>
<p class="help"><?= _i('help') ?> <?= _t('gen.menu.search_help') ?></a></p>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -58,22 +95,24 @@
<label class="group-name" for="query_get"><?= _t('conf.query.filter.type') ?></label> <label class="group-name" for="query_get"><?= _t('conf.query.filter.type') ?></label>
<div class="group-controls"> <div class="group-controls">
<select name="query[get]" id="query_get" size="10"> <select name="query[get]" id="query_get" size="10">
<option value=""></option> <option value="a" <?= in_array($this->query->getGet(), ['', 'a'], true) ? 'selected="selected"' : '' ?>><?= _t('index.feed.title') ?></option>
<option value="s" <?= 's' === $this->query->getGet() ? 'selected="selected"' : '' ?>><?= _t('conf.query.get_favorite') ?></option> <option value="i" <?= 'i' === $this->query->getGet() ? 'selected="selected"' : '' ?>><?= _t('index.menu.important') ?></option>
<option value="s" <?= 's' === $this->query->getGet() ? 'selected="selected"' : '' ?>><?= _t('index.feed.title_fav') ?></option>
<option value="T" <?= 'T' === $this->query->getGet() ? 'selected="selected"' : '' ?>><?= _t('index.menu.tags') ?></option>
<optgroup label="<?= _t('conf.query.filter.tags') ?>">
<?php foreach ($this->tags as $tag): ?>
<option value="t_<?= $tag->id() ?>" <?= "t_{$tag->id()}" === $this->query->getGet() ? 'selected="selected"' : '' ?>><?= $tag->name() ?></option>
<?php endforeach?>
</optgroup>
<optgroup label="<?= _t('conf.query.filter.categories') ?>"> <optgroup label="<?= _t('conf.query.filter.categories') ?>">
<?php foreach ($this->categories as $category): ?> <?php foreach ($this->categories as $category): ?>
<option value="c_<?= $category->id() ?>" <?= "c_{$category->id()}" === $this->query->getGet() ? 'selected="selected"' : '' ?>><?= $category->name() ?></option> <option value="c_<?= $category->id() ?>" <?= "c_{$category->id()}" === $this->query->getGet() ? 'selected="selected"' : '' ?>><?= $category->name() ?></option>
<?php endforeach?> <?php endforeach?>
</optgroup> </optgroup>
<optgroup label="<?= _t('conf.query.filter.feeds') ?>"> <optgroup label="<?= _t('conf.query.filter.feeds') ?>">
<?php foreach ($this->feeds as $feed): ?> <?php foreach ($this->feeds as $feed): ?>
<option value="f_<?= $feed->id() ?>" <?= "f_{$feed->id()}" === $this->query->getGet() ? 'selected="selected"' : '' ?>><?= $feed->name() ?></option> <option value="f_<?= $feed->id() ?>" <?= "f_{$feed->id()}" === $this->query->getGet() ? 'selected="selected"' : '' ?>><?= $feed->name() ?></option>
<?php endforeach?> <?php endforeach?>
</optgroup>
<optgroup label="<?= _t('conf.query.filter.tags') ?>">
<?php foreach ($this->tags as $tag): ?>
<option value="t_<?= $tag->id() ?>" <?= "t_{$tag->id()}" === $this->query->getGet() ? 'selected="selected"' : '' ?>><?= $tag->name() ?></option>
<?php endforeach?>
</optgroup> </optgroup>
</select> </select>
</div> </div>
@ -83,8 +122,8 @@
<div class="group-controls"> <div class="group-controls">
<select name="query[order]" id="query_order"> <select name="query[order]" id="query_order">
<option value=""></option> <option value=""></option>
<option value="ASC" <?= 'ASC' === $this->query->getOrder() ? 'selected="selected"' : '' ?>><?= _t('conf.query.order_asc') ?></option>
<option value="DESC" <?= 'DESC' === $this->query->getOrder() ? 'selected="selected"' : '' ?>><?= _t('conf.query.order_desc') ?></option> <option value="DESC" <?= 'DESC' === $this->query->getOrder() ? 'selected="selected"' : '' ?>><?= _t('conf.query.order_desc') ?></option>
<option value="ASC" <?= 'ASC' === $this->query->getOrder() ? 'selected="selected"' : '' ?>><?= _t('conf.query.order_asc') ?></option>
</select> </select>
</div> </div>
</div> </div>

View File

@ -24,7 +24,7 @@ foreach ($this->entries as $entry) {
continue; continue;
} }
$feed = $this->feed ?? FreshRSS_CategoryDAO::findFeed($this->categories, $entry->feedId()); $feed = $this->feed ?? FreshRSS_Category::findFeed($this->categories, $entry->feedId());
$entry->_feed($feed); $entry->_feed($feed);
$article = $entry->toGReader('freshrss', $this->entryIdsTagNames['e_' . $entry->id()] ?? []); $article = $entry->toGReader('freshrss', $this->entryIdsTagNames['e_' . $entry->id()] ?? []);

View File

@ -1,9 +1,6 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
/** @var FreshRSS_View $this */ /** @var FreshRSS_View $this */
if ($this->feed === null) {
throw new FreshRSS_Context_Exception('Feed not initialised!');
}
?> ?>
<div class="post" id="feed_update"> <div class="post" id="feed_update">
<h1><?= $this->feed->name() ?></h1> <h1><?= $this->feed->name() ?></h1>

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/** @var FreshRSS_View $this */
?>
<nav class="nav-pagination nav-list">
<ul class="pagination">
<?php if (FreshRSS_Context::$offset > 0): ?>
<li class="item pager-first">
<a href="<?= $this->userQuery->sharedUrlHtml() . '&nb=' . FreshRSS_Context::$number ?>">« <?= _t('conf.logs.pagination.first') ?></a>
</li>
<li class="item pager-previous">
<a href="<?= $this->userQuery->sharedUrlHtml() . '&nb=' . FreshRSS_Context::$number .
'&offset=' . max(0, FreshRSS_Context::$offset - FreshRSS_Context::$number) ?>"> <?= _t('conf.logs.pagination.previous') ?></a>
</li>
<?php endif; ?>
<li class="item pager-next">
<a href="<?= $this->userQuery->sharedUrlHtml() . '&nb=' . FreshRSS_Context::$number .
'&offset=' . (FreshRSS_Context::$offset + FreshRSS_Context::$number) ?>"><?= _t('conf.logs.pagination.next') ?> </a>
</li>
</ul>
</nav>

View File

@ -0,0 +1,117 @@
<?php
declare(strict_types=1);
/** @var FreshRSS_View $this */
$entry = $this->entry;
$feed = $this->feed;
?>
<article class="flux_content" dir="auto">
<div class="content <?= FreshRSS_Context::userConf()->content_width ?>">
<header>
<?php
$favoriteUrl = ['c' => 'entry', 'a' => 'bookmark', 'params' => ['id' => $entry->id()]];
if ($entry->isFavorite()) {
$favoriteUrl['params']['is_favorite'] = 0;
}
$readUrl = ['c' => 'entry', 'a' => 'read', 'params' => ['id' => $entry->id()]];
if ($entry->isRead()) {
$readUrl['params']['is_read'] = 0;
}
?>
<div class="article-header-topline">
<?php if (FreshRSS_Auth::hasAccess()) { ?>
<a class="read" href="<?= Minz_Url::display($readUrl) ?>" title="<?= _t('conf.shortcut.mark_read') ?>"><?= _i($entry->isRead() ? 'read' : 'unread') ?></a>
<a class="bookmark" href="<?= Minz_Url::display($favoriteUrl) ?>" title="<?= _t('conf.shortcut.mark_favorite') ?>"><?= _i($entry->isFavorite() ? 'starred' : 'non-starred') ?></a>
<?php } ?>
<?php if (FreshRSS_Context::userConf()->show_feed_name === 't') { ?>
<a class="website" href="<?= _url('index', 'reader', 'get', 'f_' . $feed->id()) ?>" title="<?= _t('gen.action.filter') ?>">
<?php if (FreshRSS_Context::userConf()->show_favicons): ?>
<img class="favicon" src="<?= $feed->favicon() ?>" alt="✇" loading="lazy" /><?php
endif; ?><span><?= $feed->name() ?></span></a>
<?php } ?>
</div>
<?php
if (in_array(FreshRSS_Context::userConf()->show_tags, ['b', 'h'], true)) {
$this->renderHelper('index/tags');
}
?>
<h1 class="title"><a target="_blank" rel="noreferrer" class="go_website" href="<?= $entry->link() ?>"><?= $entry->title() ?></a></h1>
<?php if (FreshRSS_Context::userConf()->show_author_date === 'h' || FreshRSS_Context::userConf()->show_author_date === 'b') { ?>
<div class="subtitle">
<?php if (FreshRSS_Context::userConf()->show_feed_name === 'a') { ?>
<div class="website"><a href="<?= $this->internal_rendering ? $feed->website() : _url('index', 'reader', 'get', 'f_' . $feed->id()) ?>" title="<?= _t('gen.action.filter') ?>">
<?php if (FreshRSS_Context::userConf()->show_favicons): ?>
<img class="favicon" src="<?= $feed->favicon() ?>" alt="✇" loading="lazy" /><?php
endif; ?><span><?= $feed->name() ?></span></a></div>
<?php } ?>
<div class="author"><?php
$authors = $entry->authors();
if (is_array($authors)) {
if ($this->internal_rendering):
foreach ($authors as $author): ?>
<?= $author ?>
<?php endforeach;
else:
foreach ($authors as $author): ?>
<a href="<?= Minz_Url::display(Minz_Request::modifiedCurrentRequest(['search' => 'author:' . str_replace(' ', '+', htmlspecialchars_decode($author, ENT_QUOTES))])) ?>">
<?= $author ?>
</a>
<?php endforeach;
endif;
} ?>
</div>
<div class="date">
<time datetime="<?= $entry->machineReadableDate() ?>"><?= $entry->date() ?></time>
</div>
</div>
<?php } ?>
</header>
<div class="text">
<?= $entry->content(true) ?>
</div>
<?php
$display_authors_date = in_array(FreshRSS_Context::userConf()->show_author_date, ['b', 'f'], true);
$display_tags = in_array(FreshRSS_Context::userConf()->show_tags, ['b', 'f'], true);
if ($display_authors_date || $display_tags) {
?>
<footer>
<?php if ($display_authors_date) { ?>
<div class="subtitle">
<?php if (FreshRSS_Context::userConf()->show_feed_name === 'a') { ?>
<div class="website"><a href="<?= _url('index', 'reader', 'get', 'f_' . $feed->id()) ?>" title="<?= _t('gen.action.filter') ?>">
<?php if (FreshRSS_Context::userConf()->show_favicons): ?>
<img class="favicon" src="<?= $feed->favicon() ?>" alt="✇" loading="lazy" /><?php
endif; ?><span><?= $feed->name() ?></span></a></div>
<?php } ?>
<div class="author"><?php
$authors = $entry->authors();
if (is_array($authors)) {
foreach ($authors as $author) {
?>
<a href="<?= Minz_Url::display(Minz_Request::modifiedCurrentRequest(['search' => 'author:' . str_replace(' ', '+', htmlspecialchars_decode($author, ENT_QUOTES))])) ?>">
<?= $author ?>
</a>
<?php
}
}
?>
</div>
<div class="date">
<time datetime="<?= $entry->machineReadableDate() ?>"><?= $entry->date() ?></time>
</div>
</div>
<?php
}
if ($display_tags) {
$this->renderHelper('index/tags');
}
?>
</footer>
<?php
} ?>
</div>
</article>

View File

@ -1,9 +1,6 @@
<?php <?php
declare(strict_types=1); declare(strict_types=1);
/** @var FreshRSS_View $this */ /** @var FreshRSS_View $this */
if ($this->feed === null) {
throw new FreshRSS_Context_Exception('Feed not initialised!');
}
$topline_read = FreshRSS_Context::userConf()->topline_read; $topline_read = FreshRSS_Context::userConf()->topline_read;
$topline_favorite = FreshRSS_Context::userConf()->topline_favorite; $topline_favorite = FreshRSS_Context::userConf()->topline_favorite;
$topline_website = FreshRSS_Context::userConf()->topline_website; $topline_website = FreshRSS_Context::userConf()->topline_website;

View File

@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/** @var FreshRSS_View $this */
[$firstTags,$remainingTags] = $this->entry->tagsFormattingHelper();
?>
<div class="tags">
<?php if (!empty($firstTags)): ?>
<?= _i('tag') ?><ul class="list-tags">
<?php if (Minz_Request::controllerName() === 'index'): ?>
<?php foreach ($firstTags as $tag): ?>
<li class="item tag"><a class="link-tag" href="<?= _url('index', 'index', 'search', '#' . str_replace(' ', '+', htmlspecialchars_decode($tag, ENT_QUOTES))) ?>" title="<?= _t('gen.action.filter') ?>">#<?= $tag ?></a></li>
<?php endforeach; ?>
<?php else: // API public access ?>
<?php foreach ($firstTags as $tag): ?>
<li class="item tag"><a class="link-tag" href="<?= $this->html_url . '&search=%23' . str_replace(' ', '+', htmlspecialchars_decode($tag, ENT_QUOTES)) ?>" title="<?= _t('gen.action.filter') ?>">#<?= $tag ?></a></li>
<?php endforeach; ?>
<?php endif; ?>
<?php if (!empty($remainingTags)): // more than 7 tags: show dropdown menu ?>
<li class="item tag">
<div class="dropdown">
<div id="dropdown-tags2-<?= $this->entry->id() ?>" class="dropdown-target"></div>
<a class="dropdown-toggle" href="#dropdown-tags2-<?= $this->entry->id() ?>"><?= _i('down') ?></a>
<ul class="dropdown-menu">
<li class="dropdown-header"><?= _t('index.tag.related') ?></li>
<?php if (Minz_Request::controllerName() === 'index'): ?>
<?php foreach ($remainingTags as $tag): ?>
<li class="item"><a href="<?= _url('index', 'index', 'search', '#' . str_replace(' ', '+', htmlspecialchars_decode($tag, ENT_QUOTES))) ?>" title="<?= _t('gen.action.filter') ?>">#<?= $tag ?></a></li>
<?php endforeach; ?>
<?php else: ?>
<?php foreach ($remainingTags as $tag): ?>
<li class="item tag"><a class="link-tag" href="<?= $this->html_url . '&search=%23' . str_replace(' ', '+', htmlspecialchars_decode($tag, ENT_QUOTES)) ?>" title="<?= _t('gen.action.filter') ?>">#<?= $tag ?></a></li>
<?php endforeach; ?>
<?php endif; ?>
</ul>
<a class="dropdown-close" href="#close"></a>
</div>
</li>
<?php endif; ?>
</ul>
<?php endif; ?>
</div>

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
/** @var FreshRSS_View $this */
// Override some layout preferences for the public API output
FreshRSS_Context::userConf()->content_width = 'large';
FreshRSS_Context::userConf()->show_author_date = FreshRSS_UserConfiguration::default()->show_author_date;
FreshRSS_Context::userConf()->show_favicons = FreshRSS_UserConfiguration::default()->show_favicons;
FreshRSS_Context::userConf()->show_feed_name = FreshRSS_UserConfiguration::default()->show_feed_name;
FreshRSS_Context::userConf()->show_tags = FreshRSS_UserConfiguration::default()->show_tags;
FreshRSS_Context::userConf()->show_tags_max = FreshRSS_UserConfiguration::default()->show_tags_max;
?>
<?php $this->renderHelper('htmlPagination'); ?>
<main id="stream" class="reader api">
<h2>
<a href="<?= $this->html_url ?>"><?= FreshRSS_View::title() ?></a> ·
<a class="view-rss" href="<?= $this->rss_url ?>" title="<?= _t('index.menu.rss_view') ?>">
<?= _i('rss') ?>
</a>
</h2>
<?php
foreach ($this->entries as $entry):
$this->entry = $entry;
$this->feed = $this->feeds[$entry->feedId()] ??
FreshRSS_Category::findFeed($this->categories, $entry->feedId()) ??
FreshRSS_Feed::default();
?>
<div class="flux">
<?php $this->renderHelper('index/article'); ?>
</div>
<?php endforeach; ?>
</main>
<?php $this->renderHelper('htmlPagination'); ?>

View File

@ -11,23 +11,18 @@ call_user_func($this->callbackBeforeEntries, $this);
$display_today = true; $display_today = true;
$display_yesterday = true; $display_yesterday = true;
$display_others = true; $display_others = true;
$hidePosts = !FreshRSS_Context::userConf()->display_posts;
$lazyload = FreshRSS_Context::userConf()->lazyload;
$content_width = FreshRSS_Context::userConf()->content_width;
$MAX_TAGS_DISPLAYED = (int)FreshRSS_Context::userConf()->show_tags_max;
$useKeepUnreadImportant = !FreshRSS_Context::isImportant() && !FreshRSS_Context::isFeed(); $useKeepUnreadImportant = !FreshRSS_Context::isImportant() && !FreshRSS_Context::isFeed();
$today = @strtotime('today'); $today = @strtotime('today');
?> ?>
<main id="stream" class="normal<?= $hidePosts ? ' hide_posts' : '' ?>"> <main id="stream" class="normal<?= FreshRSS_Context::userConf()->display_posts ? '' : ' hide_posts' ?>">
<h1 class="title_hidden"><?= _t('conf.reading.view.normal') ?></h1> <h1 class="title_hidden"><?= _t('conf.reading.view.normal') ?></h1>
<div id="new-article"> <div id="new-article">
<a href="<?= Minz_Url::display(Minz_Request::currentRequest()) ?>"><?= _t('gen.js.new_article'); /* TODO: move string in JS*/ ?></a> <a href="<?= Minz_Url::display(Minz_Request::currentRequest()) ?>"><?= _t('gen.js.new_article'); /* TODO: move string in JS*/ ?></a>
</div><?php </div><?php
$lastEntry = null; $lastEntry = null;
$nbEntries = 0; $nbEntries = 0;
/** @var FreshRSS_Entry */
foreach ($this->entries as $item): foreach ($this->entries as $item):
$lastEntry = $item; $lastEntry = $item;
$nbEntries++; $nbEntries++;
@ -40,8 +35,8 @@ $today = @strtotime('today');
$this->entry = $item; $this->entry = $item;
// We most likely already have the feed object in cache, otherwise make a request // We most likely already have the feed object in cache, otherwise make a request
$this->feed = FreshRSS_CategoryDAO::findFeed($this->categories, $this->entry->feedId()) ?? $this->feed = FreshRSS_Category::findFeed($this->categories, $this->entry->feedId()) ??
$this->entry->feed() ?? FreshRSS_Feed::example(); $this->entry->feed() ?? FreshRSS_Feed::default();
if ($display_today && $this->entry->isDay(FreshRSS_Days::TODAY, $today)) { if ($display_today && $this->entry->isDay(FreshRSS_Days::TODAY, $today)) {
?><div class="day" id="day_today"><?php ?><div class="day" id="day_today"><?php
@ -74,27 +69,8 @@ $today = @strtotime('today');
?>" data-priority="<?= $this->feed->priority() ?>" data-priority="<?= $this->feed->priority()
?>"><?php ?>"><?php
$this->renderHelper('index/normal/entry_header'); $this->renderHelper('index/normal/entry_header');
if ($this->feed === null) {
throw new FreshRSS_Context_Exception('Feed not initialised!');
}
$tags = null;
$firstTags = array();
$remainingTags = array();
if (FreshRSS_Context::userConf()->show_tags === 'h' || FreshRSS_Context::userConf()->show_tags === 'f' || FreshRSS_Context::userConf()->show_tags === 'b') {
$tags = $this->entry->tags();
if (!empty($tags)) {
if ($MAX_TAGS_DISPLAYED > 0) {
$firstTags = array_slice($tags, 0, $MAX_TAGS_DISPLAYED);
$remainingTags = array_slice($tags, $MAX_TAGS_DISPLAYED);
} else {
$firstTags = $tags;
}
}
}
?><article class="flux_content" dir="auto"> ?><article class="flux_content" dir="auto">
<div class="content <?= $content_width ?>"> <div class="content <?= FreshRSS_Context::userConf()->content_width ?>">
<header> <header>
<?php if (FreshRSS_Context::userConf()->show_feed_name === 't') { ?> <?php if (FreshRSS_Context::userConf()->show_feed_name === 't') { ?>
<div class="website"><a href="<?= _url('index', 'index', 'get', 'f_' . $this->feed->id()) ?>" title="<?= _t('gen.action.filter') ?>"> <div class="website"><a href="<?= _url('index', 'index', 'get', 'f_' . $this->feed->id()) ?>" title="<?= _t('gen.action.filter') ?>">
@ -103,36 +79,8 @@ $today = @strtotime('today');
endif; ?><span><?= $this->feed->name() ?></span></a> endif; ?><span><?= $this->feed->name() ?></span></a>
</div> </div>
<?php } ?> <?php } ?>
<?php if (FreshRSS_Context::userConf()->show_tags === 'h' || FreshRSS_Context::userConf()->show_tags === 'b') { ?> <?php if (FreshRSS_Context::userConf()->show_tags === 'h' || FreshRSS_Context::userConf()->show_tags === 'b') {
<div class="tags"> $this->renderHelper('index/tags');
<?php
if (!empty($tags)) {
?><?= _i('tag') ?><ul class="list-tags"><?php
foreach ($firstTags as $tag) {
?><li class="item tag"><a class="link-tag" href="<?= _url('index', 'index', 'search', '#' . str_replace(' ', '+', htmlspecialchars_decode($tag, ENT_QUOTES))) ?>" title="<?= _t('gen.action.filter') ?>">#<?= $tag ?></a></li><?php
}
if (!empty($remainingTags)) { // more than 7 tags: show dropdown menu ?>
<li class="item tag">
<div class="dropdown">
<div id="dropdown-tags2-<?= $this->entry->id() ?>" class="dropdown-target"></div>
<a class="dropdown-toggle" href="#dropdown-tags2-<?= $this->entry->id() ?>"><?= _i('down') ?></a>
<ul class="dropdown-menu">
<li class="dropdown-header"><?= _t('index.tag.related') ?></li>
<?php
foreach ($remainingTags as $tag) {
?><li class="item"><a href="<?= _url('index', 'index', 'search', '#' . str_replace(' ', '+', htmlspecialchars_decode($tag, ENT_QUOTES))) ?>" title="<?= _t('gen.action.filter') ?>"><?= $tag ?></a></li><?php
} ?>
</ul>
<a class="dropdown-close" href="#close"></a>
</div>
</li>
<?php
} ?>
</ul><?php
} ?>
</div>
<?php
} ?> } ?>
<h1 class="title"><a target="_blank" rel="noreferrer" class="go_website" href="<?= $this->entry->link() ?>" title="<?= _t('conf.shortcut.see_on_website')?>"><?= $this->entry->title() ?></a></h1> <h1 class="title"><a target="_blank" rel="noreferrer" class="go_website" href="<?= $this->entry->link() ?>" title="<?= _t('conf.shortcut.see_on_website')?>"><?= $this->entry->title() ?></a></h1>
<?php if (FreshRSS_Context::userConf()->show_author_date === 'h' || FreshRSS_Context::userConf()->show_author_date === 'b') { ?> <?php if (FreshRSS_Context::userConf()->show_author_date === 'h' || FreshRSS_Context::userConf()->show_author_date === 'b') { ?>
@ -163,8 +111,8 @@ $today = @strtotime('today');
</div> </div>
<?php } ?> <?php } ?>
</header> </header>
<div class="text"><?php <div class="text"><?=
echo $lazyload && $hidePosts ? lazyimg($this->entry->content(true)) : $this->entry->content(true); FreshRSS_Context::userConf()->lazyload && !FreshRSS_Context::userConf()->display_posts ? lazyimg($this->entry->content(true)) : $this->entry->content(true)
?></div> ?></div>
<?php <?php
$display_authors_date = FreshRSS_Context::userConf()->show_author_date === 'f' || FreshRSS_Context::userConf()->show_author_date === 'b'; $display_authors_date = FreshRSS_Context::userConf()->show_author_date === 'f' || FreshRSS_Context::userConf()->show_author_date === 'b';
@ -201,36 +149,10 @@ $today = @strtotime('today');
</div> </div>
<?php <?php
} }
if ($display_tags) { ?> if ($display_tags) {
<div class="tags"> $this->renderHelper('index/tags');
<?php }
if (!empty($tags)) { ?>
?><?= _i('tag') ?><ul class="list-tags"><?php
foreach ($firstTags as $tag) {
?><li class="item tag"><a class="link-tag" href="<?= _url('index', 'index', 'search', '#' . str_replace(' ', '+', htmlspecialchars_decode($tag, ENT_QUOTES))) ?>" title="<?= _t('gen.action.filter') ?>">#<?= $tag ?></a></li><?php
}
if (!empty($remainingTags)) { ?>
<li class="item tag">
<div class="dropdown">
<div id="dropdown-tags3-<?= $this->entry->id() ?>" class="dropdown-target"></div>
<a class="dropdown-toggle" href="#dropdown-tags3-<?= $this->entry->id() ?>"><?= _i('down') ?></a>
<ul class="dropdown-menu">
<li class="dropdown-header"><?= _t('index.tag.related') ?></li>
<?php
foreach ($remainingTags as $tag) {
?><li class="item"><a href="<?= _url('index', 'index', 'search', '#' . str_replace(' ', '+', htmlspecialchars_decode($tag, ENT_QUOTES))) ?>" title="<?= _t('gen.action.filter') ?>"><?= $tag ?></a></li><?php
} ?>
</ul>
<a class="dropdown-close" href="#close"></a>
</div>
</li>
<?php
} ?>
</ul><?php
} ?>
</div>
<?php
} ?>
</footer> </footer>
<?php <?php
} ?> } ?>

View File

@ -9,8 +9,6 @@ if (!Minz_Request::paramBoolean('ajax')) {
call_user_func($this->callbackBeforeEntries, $this); call_user_func($this->callbackBeforeEntries, $this);
$lazyload = FreshRSS_Context::userConf()->lazyload; $lazyload = FreshRSS_Context::userConf()->lazyload;
$content_width = FreshRSS_Context::userConf()->content_width;
$MAX_TAGS_DISPLAYED = (int)FreshRSS_Context::userConf()->show_tags_max;
?> ?>
<main id="stream" class="reader"> <main id="stream" class="reader">
<h1 class="title_hidden"><?= _t('conf.reading.view.reader') ?></h1> <h1 class="title_hidden"><?= _t('conf.reading.view.reader') ?></h1>
@ -19,197 +17,21 @@ $MAX_TAGS_DISPLAYED = (int)FreshRSS_Context::userConf()->show_tags_max;
</div><?php </div><?php
$lastEntry = null; $lastEntry = null;
$nbEntries = 0; $nbEntries = 0;
/** @var FreshRSS_Entry */ foreach ($this->entries as $entry):
foreach ($this->entries as $item): $lastEntry = $entry;
$lastEntry = $item;
$nbEntries++; $nbEntries++;
ob_flush(); ob_flush();
/** @var FreshRSS_Entry */ /** @var FreshRSS_Entry */
$item = Minz_ExtensionManager::callHook('entry_before_display', $item); $entry = Minz_ExtensionManager::callHook('entry_before_display', $entry);
if ($item == null) { if ($entry == null) {
continue; continue;
} }
$this->entry = $item; $this->entry = $entry;
$tags = null;
$firstTags = array();
$remainingTags = array();
if (FreshRSS_Context::userConf()->show_tags == 'h' || FreshRSS_Context::userConf()->show_tags == 'f' || FreshRSS_Context::userConf()->show_tags == 'b') {
$tags = $this->entry->tags();
if (!empty($tags)) {
if ($MAX_TAGS_DISPLAYED > 0) {
$firstTags = array_slice($tags, 0, $MAX_TAGS_DISPLAYED);
$remainingTags = array_slice($tags, $MAX_TAGS_DISPLAYED);
} else {
$firstTags = $tags;
}
}
}
//We most likely already have the feed object in cache, otherwise make a request //We most likely already have the feed object in cache, otherwise make a request
$feed = FreshRSS_CategoryDAO::findFeed($this->categories, $item->feedId()) ?? $item->feed() ?? FreshRSS_Feed::example(); $this->feed = FreshRSS_Category::findFeed($this->categories, $entry->feedId()) ?? $entry->feed() ?? FreshRSS_Feed::default();
?><div class="flux<?= !$item->isRead() ? ' not_read' : '' ?><?= $item->isFavorite() ? ' favorite' : '' ?>" id="flux_<?= $item->id() ?>" data-priority="<?= $feed->priority() ?>"> ?><div class="flux<?= !$entry->isRead() ? ' not_read' : '' ?><?= $entry->isFavorite() ? ' favorite' : '' ?>" id="flux_<?= $entry->id() ?>" data-priority="<?= $this->feed->priority() ?>">
<article class="flux_content" dir="auto"> <?php $this->renderHelper('index/article'); ?>
<div class="content <?= $content_width ?>">
<header>
<?php
$favoriteUrl = array('c' => 'entry', 'a' => 'bookmark', 'params' => array('id' => $item->id()));
if ($item->isFavorite()) {
$favoriteUrl['params']['is_favorite'] = 0;
}
$readUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('id' => $item->id()));
if ($item->isRead()) {
$readUrl['params']['is_read'] = 0;
}
?>
<div class="article-header-topline">
<?php if (FreshRSS_Auth::hasAccess()) { ?>
<a class="read" href="<?= Minz_Url::display($readUrl) ?>" title="<?= _t('conf.shortcut.mark_read') ?>"><?= _i($item->isRead() ? 'read' : 'unread') ?></a>
<a class="bookmark" href="<?= Minz_Url::display($favoriteUrl) ?>" title="<?= _t('conf.shortcut.mark_favorite') ?>"><?= _i($item->isFavorite() ? 'starred' : 'non-starred') ?></a>
<?php } ?>
<?php if (FreshRSS_Context::userConf()->show_feed_name === 't') { ?>
<a class="website" href="<?= _url('index', 'reader', 'get', 'f_' . $feed->id()) ?>" title="<?= _t('gen.action.filter') ?>">
<?php if (FreshRSS_Context::userConf()->show_favicons): ?>
<img class="favicon" src="<?= $feed->favicon() ?>" alt="✇" loading="lazy" /><?php
endif; ?><span><?= $feed->name() ?></span></a>
<?php } ?>
</div>
<?php if (FreshRSS_Context::userConf()->show_tags === 'h' || FreshRSS_Context::userConf()->show_tags === 'b') { ?>
<div class="tags">
<?php
if (!empty($tags)) {
?><?= _i('tag') ?><ul class="list-tags"><?php
foreach ($firstTags as $tag) {
?><li class="item tag"><a class="link-tag" href="<?= _url('index', 'index', 'search', '#' . str_replace(' ', '+', htmlspecialchars_decode($tag, ENT_QUOTES))) ?>" title="<?= _t('gen.action.filter') ?>">#<?= $tag ?></a></li><?php
}
if (!empty($remainingTags)) { // more than 7 tags: show dropdown menu ?>
<li class="item tag">
<div class="dropdown">
<div id="dropdown-tags-<?= $this->entry->id() ?>" class="dropdown-target"></div>
<a class="dropdown-toggle" href="#dropdown-tags-<?= $this->entry->id() ?>"><?= _i('down') ?></a>
<ul class="dropdown-menu">
<li class="dropdown-header"><?= _t('index.tag.related') ?></li>
<?php
foreach ($remainingTags as $tag) {
?><li class="item"><a href="<?= _url('index', 'index', 'search', '#' . str_replace(' ', '+', htmlspecialchars_decode($tag, ENT_QUOTES))) ?>" title="<?= _t('gen.action.filter') ?>"><?= $tag ?></a></li><?php
} ?>
</ul>
<a class="dropdown-close" href="#close"></a>
</div>
</li>
<?php
} ?>
</ul><?php
} ?>
</div>
<?php } ?>
<h1 class="title"><a target="_blank" rel="noreferrer" class="go_website" href="<?= $item->link() ?>"><?= $item->title() ?></a></h1>
<?php if (FreshRSS_Context::userConf()->show_author_date === 'h' || FreshRSS_Context::userConf()->show_author_date === 'b') { ?>
<div class="subtitle">
<?php if (FreshRSS_Context::userConf()->show_feed_name === 'a') { ?>
<div class="website"><a href="<?= _url('index', 'reader', 'get', 'f_' . $feed->id()) ?>" title="<?= _t('gen.action.filter') ?>">
<?php if (FreshRSS_Context::userConf()->show_favicons): ?>
<img class="favicon" src="<?= $feed->favicon() ?>" alt="✇" loading="lazy" /><?php
endif; ?><span><?= $feed->name() ?></span></a></div>
<?php } ?>
<div class="author"><?php
$authors = $item->authors();
if (is_array($authors)) {
foreach ($authors as $author) {
?>
<a href="<?= Minz_Url::display(Minz_Request::modifiedCurrentRequest(['search' => 'author:' . str_replace(' ', '+', htmlspecialchars_decode($author, ENT_QUOTES))])) ?>">
<?= $author ?>
</a>
<?php
}
}
?>
</div>
<div class="date">
<time datetime="<?= $item->machineReadableDate() ?>"><?= $item->date() ?></time>
</div>
</div>
<?php } ?>
</header>
<div class="text">
<?= $item->content(true) ?>
</div>
<?php
$display_authors_date = FreshRSS_Context::userConf()->show_author_date === 'f' || FreshRSS_Context::userConf()->show_author_date === 'b';
$display_tags = FreshRSS_Context::userConf()->show_tags === 'f' || FreshRSS_Context::userConf()->show_tags === 'b';
if ($display_authors_date || $display_tags) {
?>
<footer>
<?php if ($display_authors_date) { ?>
<div class="subtitle">
<?php if (FreshRSS_Context::userConf()->show_feed_name === 'a') { ?>
<div class="website"><a href="<?= _url('index', 'reader', 'get', 'f_' . $feed->id()) ?>" title="<?= _t('gen.action.filter') ?>">
<?php if (FreshRSS_Context::userConf()->show_favicons): ?>
<img class="favicon" src="<?= $feed->favicon() ?>" alt="✇" loading="lazy" /><?php
endif; ?><span><?= $feed->name() ?></span></a></div>
<?php } ?>
<div class="author"><?php
$authors = $item->authors();
if (is_array($authors)) {
foreach ($authors as $author) {
?>
<a href="<?= Minz_Url::display(Minz_Request::modifiedCurrentRequest(['search' => 'author:' . str_replace(' ', '+', htmlspecialchars_decode($author, ENT_QUOTES))])) ?>">
<?= $author ?>
</a>
<?php
}
}
?>
</div>
<div class="date">
<time datetime="<?= $item->machineReadableDate() ?>"><?= $item->date() ?></time>
</div>
</div>
<?php
}
if ($display_tags) { ?>
<div class="tags">
<?php
if (!empty($tags)) {
?><?= _i('tag') ?><ul class="list-tags"><?php
foreach ($firstTags as $tag) {
?><li class="item tag"><a class="link-tag" href="<?= _url('index', 'index', 'search', '#' . str_replace(' ', '+', htmlspecialchars_decode($tag, ENT_QUOTES))) ?>" title="<?= _t('gen.action.filter') ?>">#<?= $tag ?></a></li><?php
}
if (!empty($remainingTags)) { // more than 7 tags: show dropdown menu ?>
<li class="item tag">
<div class="dropdown">
<div id="dropdown-tags2-<?= $this->entry->id() ?>" class="dropdown-target"></div>
<a class="dropdown-toggle" href="#dropdown-tags2-<?= $this->entry->id() ?>"><?= _i('down') ?></a>
<ul class="dropdown-menu">
<li class="dropdown-header"><?= _t('index.tag.related') ?></li>
<?php
foreach ($remainingTags as $tag) {
?><li class="item"><a href="<?= _url('index', 'index', 'search', '#' . str_replace(' ', '+', htmlspecialchars_decode($tag, ENT_QUOTES))) ?>" title="<?= _t('gen.action.filter') ?>"><?= $tag ?></a></li><?php
} ?>
</ul>
<a class="dropdown-close" href="#close"></a>
</div>
</li>
<?php
} ?>
</ul><?php
} ?>
</div>
<?php } ?>
</footer>
<?php
} ?>
</div>
</article>
</div><?php </div><?php
endforeach; endforeach;

View File

@ -8,14 +8,12 @@
> >
<channel> <channel>
<title><?= $this->rss_title ?></title> <title><?= $this->rss_title ?></title>
<link><?= $this->internal_rendering ? htmlspecialchars($this->rss_url, ENT_NOQUOTES, 'UTF-8') : Minz_Url::display('', 'html', true) ?></link> <link><?= $this->html_url ?></link>
<description><?= _t('index.feed.rss_of', $this->rss_title) ?></description> <description><?= _t('index.feed.rss_of', $this->rss_title) ?></description>
<pubDate><?= date('D, d M Y H:i:s O') ?></pubDate> <pubDate><?= date('D, d M Y H:i:s O') ?></pubDate>
<lastBuildDate><?= gmdate('D, d M Y H:i:s') ?> GMT</lastBuildDate> <lastBuildDate><?= gmdate('D, d M Y H:i:s') ?> GMT</lastBuildDate>
<atom:link href="<?= $this->internal_rendering ? htmlspecialchars($this->rss_url, ENT_COMPAT, 'UTF-8') : <atom:link href="<?= $this->rss_url ?>" rel="self" type="application/rss+xml" />
Minz_Url::display($this->rss_url, 'html', true) ?>" rel="self" type="application/rss+xml" />
<?php <?php
/** @var FreshRSS_Entry */
foreach ($this->entries as $item) { foreach ($this->entries as $item) {
if (!$this->internal_rendering) { if (!$this->internal_rendering) {
/** @var FreshRSS_Entry */ /** @var FreshRSS_Entry */

View File

@ -62,6 +62,7 @@
<p class="help"><?= _i('help') ?> <?= _t('admin.auth.token_help') ?></p> <p class="help"><?= _i('help') ?> <?= _t('admin.auth.token_help') ?></p>
<kbd><?= Minz_Url::display(array('a' => 'rss', 'params' => array('user' => Minz_User::name(), <kbd><?= Minz_Url::display(array('a' => 'rss', 'params' => array('user' => Minz_User::name(),
'token' => $token, 'hours' => FreshRSS_Context::userConf()->since_hours_posts_per_rss)), 'html', true) ?></kbd> 'token' => $token, 'hours' => FreshRSS_Context::userConf()->since_hours_posts_per_rss)), 'html', true) ?></kbd>
<p class="help"><?= _i('help') ?> <?= _t('conf.query.help') ?></a></p>
</div> </div>
</div> </div>
<?php } ?> <?php } ?>

View File

@ -36,10 +36,10 @@ return array (
'auto_load_more' => true, 'auto_load_more' => true,
'display_posts' => false, 'display_posts' => false,
'display_categories' => 'active', //{ active, remember, all, none } 'display_categories' => 'active', //{ active, remember, all, none }
'show_tags' => '0', 'show_tags' => 'f', // {0 => none, b => both, f => footer, h => header}
'show_tags_max' => 7, 'show_tags_max' => 7,
'show_author_date' => 'h', 'show_author_date' => 'h', // {0 => none, b => both, f => footer, h => header}
'show_feed_name' => 'a', 'show_feed_name' => 'a', // {0 => none, a => with authors, t => above title}
'hide_read_feeds' => true, 'hide_read_feeds' => true,
'onread_jump_next' => true, 'onread_jump_next' => true,
'lazyload' => true, 'lazyload' => true,

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

@ -38,3 +38,4 @@ Reader view will display a feed will all articles already open for reading. Feed
Read more: Read more:
* [Refreshing the feeds](./09_refreshing_feeds.md) * [Refreshing the feeds](./09_refreshing_feeds.md)
* [Filter the feeds and search](./10_filter.md) * [Filter the feeds and search](./10_filter.md)
* [User queries](./user_queries.md)

View File

@ -167,10 +167,7 @@ This means that if you assign a shortcut to more than one action, youll end u
# User queries # User queries
You can configure your [user queries](./03_Main_view.md) in that section. There is not much to say here as it is pretty straightforward. You can configure your [user queries](./user_queries.md) in that section.
You can only change user query titles or drop them.
At the moment, there is no helper to build a user query from here.
# Profile # Profile

View File

@ -119,34 +119,18 @@ Finally, parentheses may be used to express more complex queries, with basic neg
You can change the sort order by clicking the toggle button available in the header. You can change the sort order by clicking the toggle button available in the header.
## Store your filters ## Bookmark the current query
Once you came up with your perfect filter, it would be a shame if you need to recreate it every time you need to use it. Once you came up with your perfect filter, it would be a shame if you had to recreate it every time you need to use it.
Hopefully, there is a way to bookmark them for later use. Luckily, there is a way to bookmark them for later use.
We call them *user queries*. We call them [*user queries*](./user_queries.md).
You can create as many as you want, the only limit is how they will be displayed on your screen. You can create as many as you want, the only limit is how they will be displayed on your screen.
### Bookmark the current query Read more about [*user queries*](./user_queries.md) to learn how to create them, use them, and even reshare them via HTML / RSS / OPML.
Display the user queries drop-down by clicking the button next to the state buttons.
![User queries drop-down](../img/users/user.queries.drop-down.empty.png)
Then click on the bookmark action.
Congratulations, youre done!
### Using a bookmarked query
Display the user queries drop-down by clicking the button next to the state buttons.
![User queries drop-down](../img/users/user.queries.drop-down.not.empty.png)
Then click on the bookmarked query, the previously stored query will be applied.
> Note that only the query is stored, not the articles.
> The results you are seeing now could be different from the results on the day you've created the query.
--- ---
Read more: Read more:
* [Normal, Global and Reader view](./03_Main_view.md) * [Normal, Global and Reader view](./03_Main_view.md)
* [Refreshing the feeds](./09_refreshing_feeds.md) * [Refreshing the feeds](./09_refreshing_feeds.md)
* [User queries](./user_queries.md)

View File

@ -0,0 +1,63 @@
# User queries
*User queries* are a way to store any FreshRSS search query.
Read about [the filters](./10_filter.md) to learn the different ways to search and filter
articles in FreshRSS.
## Bookmark the current query
Once you have a search query with a filter, it can be saved.
To do so, display the user queries drop-down menu by clicking the button next to the state buttons:
![User queries drop-down](../img/users/user.queries.drop-down.empty.png)
Then click on the bookmark action.
## Using a bookmarked query
Display the user queries drop-down menu by clicking the button next to the state buttons:
![User queries drop-down](../img/users/user.queries.drop-down.not.empty.png)
Then click on the bookmarked query, the previously stored query will be applied.
> Note that only the search query is stored, not the articles.
> So the results you are seeing one day might be different another day.
## Share your user queries
A prerequisite is that the FreshRSS API(s) must be enabled in FreshRSS authentication settings.
From the configuration page of the user queries,
it is possible to share the output of the user queries with external users,
in the formats HTML, RSS, and OPML:
![Share user query](../img/users/user-query-share.png)
> Note that the sharing as OPML is only available for user queries based on all feeds, a category, or a feed.
> Sharing by OPML is **not** available for queries based on user labels or favourites or important feeds,
> to avoid leaking some feed details in an unintended manner.
### Additional parameters for shared user queries
Some parameters can be manually added to the URL:
* `f`: Format of output. Can be `html`, `rss` (`atom` is an alias), or `opml`.
* `hours`: Show only the articles newer than this number of hours.
* `nb`: Number of articles to return. Limited by `max_posts_per_rss` in the user configuration. Can be used in combination with `offset` for pagination.
* `offset`: Skip a number of articles. Used in particular by the HTML view for pagination.
* `order`: Show the newest articles at the top with `DESC`, or the oldest articles at the top with `ASC`. By default, will use the sort order defined by the user query.
## Sharing with a master token (deprecated)
Before FreshRSS 1.24, the only option to reshare an RSS output was by using a master token,
like `https://freshrss.example.net/?a=rss&user=alice&token1234`
This was mostly intended for sharing between systems controlled by the same user, and not for sharing publicly.
This method **is not advised anymore** as it is not safe to use the same master token for multiple outputs,
especially not when shared with other persons.
Now, sharing RSS outputs via user queries is the recommended approach for all scenarios.

View File

@ -162,11 +162,11 @@ class Minz_Request {
* Setteurs * Setteurs
*/ */
public static function _controllerName(string $controller_name): void { public static function _controllerName(string $controller_name): void {
self::$controller_name = $controller_name; self::$controller_name = ctype_alnum($controller_name) ? $controller_name : '';
} }
public static function _actionName(string $action_name): void { public static function _actionName(string $action_name): void {
self::$action_name = $action_name; self::$action_name = ctype_alnum($action_name) ? $action_name : '';
} }
/** @param array<string,string> $params */ /** @param array<string,string> $params */
@ -187,6 +187,7 @@ class Minz_Request {
* Initialise la Request * Initialise la Request
*/ */
public static function init(): void { public static function init(): void {
self::_params($_GET);
self::initJSON(); self::initJSON();
} }

View File

@ -572,7 +572,7 @@ final class GReaderAPI {
continue; continue;
} }
$feed = FreshRSS_CategoryDAO::findFeed($categories, $entry->feedId()); $feed = FreshRSS_Category::findFeed($categories, $entry->feedId());
if ($feed === null) { if ($feed === null) {
continue; continue;
} }
@ -694,7 +694,7 @@ final class GReaderAPI {
} }
$entryDAO = FreshRSS_Factory::createEntryDao(); $entryDAO = FreshRSS_Factory::createEntryDao();
$entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, $searches); $entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, 0, $continuation, $searches);
$entries = iterator_to_array($entries); //TODO: Improve $entries = iterator_to_array($entries); //TODO: Improve
$items = self::entriesToArray($entries); $items = self::entriesToArray($entries);
@ -746,7 +746,7 @@ final class GReaderAPI {
} }
$entryDAO = FreshRSS_Factory::createEntryDao(); $entryDAO = FreshRSS_Factory::createEntryDao();
$ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, $searches); $ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, 0, $continuation, $searches);
if ($ids === null) { if ($ids === null) {
self::internalServerError(); self::internalServerError();
} }

175
p/api/query.php Normal file
View File

@ -0,0 +1,175 @@
<?php
declare(strict_types=1);
require(__DIR__ . '/../../constants.php');
require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader
Minz_Request::init();
$token = Minz_Request::paramString('t');
if (!ctype_alnum($token)) {
header('HTTP/1.1 422 Unprocessable Entity');
header('Content-Type: text/plain; charset=UTF-8');
die('Invalid token `t`!' . $token);
}
$format = Minz_Request::paramString('f');
if (!in_array($format, ['atom', 'html', 'opml', 'rss'], true)) {
header('HTTP/1.1 422 Unprocessable Entity');
header('Content-Type: text/plain; charset=UTF-8');
die('Invalid format `f`!');
}
$user = Minz_Request::paramString('user');
if (!FreshRSS_user_Controller::checkUsername($user)) {
header('HTTP/1.1 422 Unprocessable Entity');
header('Content-Type: text/plain; charset=UTF-8');
die('Invalid user!');
}
Minz_Session::init('FreshRSS', true);
FreshRSS_Context::initSystem();
if (!FreshRSS_Context::hasSystemConf() || !FreshRSS_Context::systemConf()->api_enabled) {
header('HTTP/1.1 503 Service Unavailable');
header('Content-Type: text/plain; charset=UTF-8');
die('Service Unavailable!');
}
FreshRSS_Context::initUser($user);
if (!FreshRSS_Context::hasUserConf()) {
usleep(rand(100, 10000)); //Primitive mitigation of scanning for users
header('HTTP/1.1 404 Not Found');
header('Content-Type: text/plain; charset=UTF-8');
die('User not found!');
} else {
usleep(rand(20, 200));
}
if (!file_exists(DATA_PATH . '/no-cache.txt')) {
require(LIB_PATH . '/http-conditional.php');
// TODO: Consider taking advantage of $feedMode, only for monotonous queries {all, categories, feeds} and not dynamic ones {read/unread, favourites, user labels}
if (httpConditional(FreshRSS_UserDAO::mtime($user) ?: time(), 0, 0, false, PHP_COMPRESSION, false)) {
exit(); //No need to send anything
}
}
Minz_Translate::init(FreshRSS_Context::userConf()->language);
Minz_ExtensionManager::init();
Minz_ExtensionManager::enableByList(FreshRSS_Context::userConf()->extensions_enabled, 'user');
$query = null;
$userSearch = null;
foreach (FreshRSS_Context::userConf()->queries as $raw_query) {
if (!empty($raw_query['token']) && $raw_query['token'] === $token) {
switch ($format) {
case 'atom':
case 'html':
case 'rss':
if (empty($raw_query['shareRss'])) {
continue 2;
}
break;
case 'opml':
if (empty($raw_query['shareOpml'])) {
continue 2;
}
break;
default:
continue 2;
}
$query = new FreshRSS_UserQuery($raw_query, FreshRSS_Context::categories(), FreshRSS_Context::labels());
Minz_Request::_param('get', $query->getGet());
if (Minz_Request::paramString('order') === '') {
Minz_Request::_param('order', $query->getOrder());
}
Minz_Request::_param('state', $query->getState());
$search = $query->getSearch()->getRawInput();
// Note: we disallow references to user queries in public user search to avoid sniffing internal user queries
$userSearch = new FreshRSS_BooleanSearch(Minz_Request::paramString('search'), 0, 'AND', false);
if ($userSearch->getRawInput() !== '') {
if ($search === '') {
$search = $userSearch->getRawInput();
} else {
$search .= ' (' . $userSearch->getRawInput() . ')';
}
}
Minz_Request::_param('search', $search);
break;
}
}
if ($query === null || $userSearch === null) {
usleep(rand(100, 10000));
header('HTTP/1.1 404 Not Found');
header('Content-Type: text/plain; charset=UTF-8');
die('User query not found!');
}
$view = new FreshRSS_View();
try {
FreshRSS_Context::updateUsingRequest(false);
Minz_Request::_param('search', $userSearch->getRawInput()); // Restore user search
$view->entries = FreshRSS_index_Controller::listEntriesByContext();
} catch (Minz_Exception $e) {
Minz_Error::error(400, 'Bad user query!');
die();
}
$get = FreshRSS_Context::currentGet(true);
$type = (string)$get[0];
$id = (int)$get[1];
switch ($type) {
case 'c': // Category
$cat = FreshRSS_Context::categories()[$id] ?? null;
if ($cat === null) {
Minz_Error::error(404, "Category {$id} not found!");
die();
}
$view->categories = [ $cat->id() => $cat ];
break;
case 'f': // Feed
$feed = FreshRSS_Category::findFeed(FreshRSS_Context::categories(), $id);
if ($feed === null) {
Minz_Error::error(404, "Feed {$id} not found!");
die();
}
$view->feeds = [ $feed->id() => $feed ];
$view->categories = [];
break;
default:
$view->categories = FreshRSS_Context::categories();
break;
}
$view->disable_aside = true;
$view->excludeMutedFeeds = true;
$view->internal_rendering = true;
$view->userQuery = $query;
$view->html_url = $query->sharedUrlHtml();
$view->rss_url = $query->sharedUrlRss();
$view->rss_title = $query->getName();
if ($query->getName() != '') {
FreshRSS_View::_title($query->getName());
}
FreshRSS_Context::systemConf()->allow_anonymous = true;
if (in_array($format, ['rss', 'atom'], true)) {
header('Content-Type: application/rss+xml; charset=utf-8');
$view->_layout(null);
$view->_path('index/rss.phtml');
} elseif ($format === 'opml') {
if (!$query->safeForOpml()) {
Minz_Error::error(404, 'OPML not allowed for this user query!');
die();
}
header('Content-Type: application/xml; charset=utf-8');
$view->_layout(null);
$view->_path('index/opml.phtml');
} else {
$view->_layout('layout');
$view->_path('index/html.phtml');
}
$view->build();

Some files were not shown because too many files have changed in this diff Show More