From cf899d8d25c57b05dff89b89e2c7e56808f83c50 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 5 Nov 2018 18:10:38 +0100 Subject: TT-RSS import (#2099) * TT-RSS import Import of Tiny Tiny RSS favourites https://github.com/FreshRSS/FreshRSS/issues/2018#issuecomment-432710462 * Fallback feed_url * Simpler JSON * TT-RSS import custom labels * Fix syntax --- app/Models/EntryDAO.php | 9 +++++++++ app/Models/FeedDAO.php | 8 +++++++- app/Models/TagDAO.php | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) (limited to 'app/Models') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index a01c2227b..708d01a69 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -671,6 +671,15 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return isset($entries[0]) ? $entries[0] : null; } + public function searchIdByGuid($id_feed, $guid) { + $sql = 'SELECT id FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid=?'; + $stm = $this->bd->prepare($sql); + $values = array($id_feed, $guid); + $stm->execute($values); + $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); + return isset($res[0]) ? $res[0] : null; + } + protected function sqlConcat($s1, $s2) { return 'CONCAT(' . $s1 . ',' . $s2 . ')'; //MySQL } diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index e579f5881..7f00642f4 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -465,9 +465,15 @@ UPDATE `{$this->prefix}feed` SQL; $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute(array(':new_value' => FreshRSS_Feed::TTL_DEFAULT, ':old_value' => -2)))) { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::error('SQL warning updateTTL 1: ' . $info[2] . ' ' . $sql); + $sql2 = 'ALTER TABLE `' . $this->prefix . 'feed` ADD COLUMN ttl INT NOT NULL DEFAULT ' . FreshRSS_Feed::TTL_DEFAULT; //v0.7.3 $stm = $this->bd->prepare($sql2); - $stm->execute(); + if (!($stm && $stm->execute())) { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::error('SQL error updateTTL 2: ' . $info[2] . ' ' . $sql2); + } } else { $stm->execute(array(':new_value' => -3600, ':old_value' => -1)); } diff --git a/app/Models/TagDAO.php b/app/Models/TagDAO.php index 1b59c8971..b55d2b35d 100644 --- a/app/Models/TagDAO.php +++ b/app/Models/TagDAO.php @@ -266,7 +266,7 @@ class FreshRSS_TagDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if (is_array($entries) && count($entries) > 0) { $sql .= ' AND et.id_entry IN (' . str_repeat('?,', count($entries) - 1). '?)'; foreach ($entries as $entry) { - $values[] = $entry->id(); + $values[] = is_array($entry) ? $entry['id'] : $entry->id(); } } $stm = $this->bd->prepare($sql); -- cgit v1.2.3 From b672fc190d7df163449e91400c6d6a08a3775835 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 11 Nov 2018 17:31:50 +0100 Subject: Tweaks for Vienna RSS (#2093) * Tweaks for Vienna RSS https://github.com/FreshRSS/FreshRSS/issues/2091 https://github.com/ViennaRSS/vienna-rss/issues/1197 * Fix get feed by URL * Fix get item ids returning starred elements * API add item ids by feed URL * Add API filter `it` https://feedhq.readthedocs.io/en/latest/api/reference.html#stream-items-ids * API add `nt=` filter + refactoring * No ; prefix for author https://github.com/FreshRSS/FreshRSS/issues/2091#issuecomment-435562495 * Add id long form prefix and accept short id form https://github.com/FreshRSS/FreshRSS/issues/2091#issuecomment-435631259 * Fix quote problem https://github.com/FreshRSS/FreshRSS/issues/2091#issuecomment-435683930 * Isolate bug fix for News+ https://github.com/FreshRSS/FreshRSS/issues/2091#issuecomment-435687041 * Rework encoding conventions https://github.com/FreshRSS/FreshRSS/issues/2091#issuecomment-437441834 * Unicode escaping alternative Alternative approach to encode XML special characters and other problematic characters into their Unicode fullwidth version when we cannot use HTML-encoding because clients disagree wether they should HTML-decode or not. https://github.com/FreshRSS/FreshRSS/issues/2091#issuecomment-436059559 --- app/Models/BooleanSearch.php | 8 ++ app/Models/EntryDAO.php | 4 +- app/Models/Feed.php | 2 +- app/Models/Search.php | 8 ++ lib/lib_rss.php | 12 +++ p/api/greader.php | 226 +++++++++++++++++++++++++++---------------- 6 files changed, 171 insertions(+), 89 deletions(-) (limited to 'app/Models') diff --git a/app/Models/BooleanSearch.php b/app/Models/BooleanSearch.php index 6e016f7e9..88eeea73c 100644 --- a/app/Models/BooleanSearch.php +++ b/app/Models/BooleanSearch.php @@ -45,6 +45,14 @@ class FreshRSS_BooleanSearch { return $this->searches; } + public function add($search) { + if ($search instanceof FreshRSS_Search) { + $this->searches[] = $search; + return $search; + } + return null; + } + public function __toString() { return $this->getRawInput(); } diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 708d01a69..6d77a33cd 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -921,8 +921,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return self::daoToEntries($stm->fetchAll(PDO::FETCH_ASSOC)); } - public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filters = null, $date_min = 0) { //For API - list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filters, $date_min); + public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filters = null) { //For API + list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filters); $stm = $this->bd->prepare($sql); $stm->execute($values); diff --git a/app/Models/Feed.php b/app/Models/Feed.php index e1dd2990d..a5ef33d6b 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -424,7 +424,7 @@ class FreshRSS_Feed extends Minz_Model { $author_names = ''; if (is_array($authors)) { foreach ($authors as $author) { - $author_names .= html_only_entity_decode(strip_tags($author->name == '' ? $author->email : $author->name)) . '; '; + $author_names .= escapeToUnicodeAlternative(strip_tags($author->name == '' ? $author->email : $author->name)) . '; '; } } $author_names = substr($author_names, 0, -2); diff --git a/app/Models/Search.php b/app/Models/Search.php index c52e391fa..f9cda7354 100644 --- a/app/Models/Search.php +++ b/app/Models/Search.php @@ -73,10 +73,18 @@ class FreshRSS_Search { return $this->min_date; } + public function setMinDate($value) { + return $this->min_date = $value; + } + public function getMaxDate() { return $this->max_date; } + public function setMaxDate($value) { + return $this->max_date = $value; + } + public function getMinPubdate() { return $this->min_pubdate; } diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 4087f6faf..52e4408d2 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -102,6 +102,18 @@ function safe_ascii($text) { return filter_var($text, FILTER_DEFAULT, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH); } +function escapeToUnicodeAlternative($text) { + $text = htmlspecialchars_decode($text, ENT_QUOTES); + // https://raw.githubusercontent.com/mihaip/google-reader-api/master/wiki/StreamId.wiki + return trim(str_replace( + //Problematic characters + array("'", '"', '^', '<', '>', '?', '&', '\\', '/', ',', ';'), + //Use their fullwidth Unicode form instead: + array("’", '"', '^', '<', '>', '?', '&', '\', '/', ',', ';'), + $text + )); +} + /** * Test if a given server address is publicly accessible. * diff --git a/p/api/greader.php b/p/api/greader.php index c6701096c..7c5c54951 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -19,6 +19,7 @@ Server-side API compatible with Google Reader API layer 2 * https://github.com/devongovett/reader * https://github.com/theoldreader/api * https://www.inoreader.com/developers/ +* https://feedhq.readthedocs.io/en/latest/api/index.html */ require(__DIR__ . '/../../constants.php'); @@ -198,6 +199,7 @@ function clientLogin($email, $pass) { //http://web.archive.org/web/2013060409104 header('Content-Type: text/plain; charset=UTF-8'); $auth = $email . '/' . sha1(FreshRSS_Context::$system_conf->salt . $email . FreshRSS_Context::$user_conf->apiPasswordHash); echo 'SID=', $auth, "\n", + 'LSID=null', "\n", //Vienna RSS 'Auth=', $auth, "\n"; exit(); } else { @@ -258,7 +260,7 @@ function tagList() { foreach ($res as $cName) { $tags[] = array( - 'id' => 'user/-/label/' . $cName, + 'id' => 'user/-/label/' . htmlspecialchars_decode($cName, ENT_QUOTES), //'sortid' => $cName, 'type' => 'folder', //Inoreader ); @@ -270,7 +272,7 @@ function tagList() { $labels = $tagDAO->listTags(true); foreach ($labels as $label) { $tags[] = array( - 'id' => 'user/-/label/' . $label->name(), + 'id' => 'user/-/label/' . htmlspecialchars_decode($label->name(), ENT_QUOTES), //'sortid' => $cName, 'type' => 'tag', //Inoreader 'unread_count' => $label->nbUnread(), //Inoreader @@ -298,17 +300,17 @@ function subscriptionList() { foreach ($res as $line) { $subscriptions[] = array( 'id' => 'feed/' . $line['id'], - 'title' => $line['name'], + 'title' => escapeToUnicodeAlternative($line['name']), 'categories' => array( array( - 'id' => 'user/-/label/' . $line['c_name'], - 'label' => $line['c_name'], + 'id' => 'user/-/label/' . htmlspecialchars_decode($line['c_name'], ENT_QUOTES), + 'label' => htmlspecialchars_decode($line['c_name'], ENT_QUOTES), ), ), //'sortid' => $line['name'], //'firstitemmsec' => 0, - 'url' => $line['url'], - 'htmlUrl' => $line['website'], + 'url' => htmlspecialchars_decode($line['url'], ENT_QUOTES), + 'htmlUrl' => htmlspecialchars_decode($line['website'], ENT_QUOTES), 'iconUrl' => $faviconsUrl . hash('crc32b', $salt . $line['url']), ); } @@ -345,6 +347,7 @@ function subscriptionEdit($streamNames, $titles, $action, $add = '', $remove = ' $c_name = ''; } } + $c_name = htmlspecialchars($c_name, ENT_COMPAT, 'UTF-8'); $cat = $categoryDAO->searchByName($c_name); $addCatId = $cat == null ? 0 : $cat->id(); } else if ($remove != '' && strpos($remove, 'user/-/label/')) { @@ -355,26 +358,28 @@ function subscriptionEdit($streamNames, $titles, $action, $add = '', $remove = ' badRequest(); } for ($i = count($streamNames) - 1; $i >= 0; $i--) { - $streamName = $streamNames[$i]; //feed/http://example.net/sample.xml ; feed/338 - if (strpos($streamName, 'feed/') === 0) { - $streamName = substr($streamName, 5); + $streamUrl = $streamNames[$i]; //feed/http://example.net/sample.xml ; feed/338 + if (strpos($streamUrl, 'feed/') === 0) { + $streamUrl = substr($streamUrl, 5); $feedId = 0; - if (ctype_digit($streamName)) { + if (ctype_digit($streamUrl)) { if ($action === 'subscribe') { continue; } - $feedId = $streamName; + $feedId = $streamUrl; } else { - $feed = $feedDAO->searchByUrl($streamName); + $streamUrl = htmlspecialchars($streamUrl, ENT_COMPAT, 'UTF-8'); + $feed = $feedDAO->searchByUrl($streamUrl); $feedId = $feed == null ? -1 : $feed->id(); } $title = isset($titles[$i]) ? $titles[$i] : ''; + $title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8'); switch ($action) { case 'subscribe': if ($feedId <= 0) { - $http_auth = ''; //TODO + $http_auth = ''; try { - $feed = FreshRSS_feed_Controller::addFeed($streamName, $title, $addCatId, $c_name, $http_auth); + $feed = FreshRSS_feed_Controller::addFeed($streamUrl, $title, $addCatId, $c_name, $http_auth); continue; } catch (Exception $e) { Minz_Log::error('subscriptionEdit error subscribe: ' . $e->getMessage(), API_LOG); @@ -407,6 +412,7 @@ function subscriptionEdit($streamNames, $titles, $action, $add = '', $remove = ' function quickadd($url) { try { + $url = htmlspecialchars($url, ENT_COMPAT, 'UTF-8'); $feed = FreshRSS_feed_Controller::addFeed($url); exit(json_encode(array( 'numResults' => 1, @@ -442,7 +448,7 @@ function unreadCount() { //http://blog.martindoms.com/2009/10/16/using-the-googl } } $unreadcounts[] = array( - 'id' => 'user/-/label/' . $cat->name(), + 'id' => 'user/-/label/' . htmlspecialchars_decode($cat->name(), ENT_QUOTES), 'count' => $cat->nbNotRead(), 'newestItemTimestampUsec' => $catLastUpdate . '000000', ); @@ -455,7 +461,7 @@ function unreadCount() { //http://blog.martindoms.com/2009/10/16/using-the-googl $tagDAO = FreshRSS_Factory::createTagDao(); foreach ($tagDAO->listTags(true) as $label) { $unreadcounts[] = array( - 'id' => 'user/-/label/' . $label->name(), + 'id' => 'user/-/label/' . htmlspecialchars_decode($label->name(), ENT_QUOTES), 'count' => $label->nbUnread(), ); } @@ -496,28 +502,29 @@ function entriesToArray($entries) { $f_name = '_'; } $item = array( - 'id' => /*'tag:google.com,2005:reader/item/' .*/ dec2hex($entry->id()), //64-bit hexa http://code.google.com/p/google-reader-api/wiki/ItemId + 'id' => 'tag:google.com,2005:reader/item/' . dec2hex($entry->id()), //64-bit hexa http://code.google.com/p/google-reader-api/wiki/ItemId 'crawlTimeMsec' => substr($entry->id(), 0, -3), 'timestampUsec' => '' . $entry->id(), //EasyRSS 'published' => $entry->date(true), - 'title' => $entry->title(), + 'title' => escapeToUnicodeAlternative($entry->title()), 'summary' => array('content' => $entry->content()), 'alternate' => array( array('href' => htmlspecialchars_decode($entry->link(), ENT_QUOTES)), ), 'categories' => array( 'user/-/state/com.google/reading-list', - 'user/-/label/' . $c_name, + 'user/-/label/' . htmlspecialchars_decode($c_name, ENT_QUOTES), ), 'origin' => array( 'streamId' => 'feed/' . $f_id, - 'title' => $f_name, //EasyRSS + 'title' => escapeToUnicodeAlternative($f_name), //EasyRSS //'htmlUrl' => $line['f_website'], ), ); $author = $entry->authors(true); + $author = trim($author, '; '); if ($author != '') { - $item['author'] = $author; + $item['author'] = escapeToUnicodeAlternative($author); } if ($entry->isRead()) { $item['categories'][] = 'user/-/state/com.google/read'; @@ -527,69 +534,117 @@ function entriesToArray($entries) { } $tagNames = isset($entryIdsTagNames['e_' . $entry->id()]) ? $entryIdsTagNames['e_' . $entry->id()] : array(); foreach ($tagNames as $tagName) { - $item['categories'][] = 'user/-/label/' . $tagName; + $item['categories'][] = 'user/-/label/' . htmlspecialchars_decode($tagName, ENT_QUOTES); } $items[] = $item; } return $items; } -function streamContents($path, $include_target, $start_time, $count, $order, $exclude_target, $continuation) { -//http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI -//http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed - header('Content-Type: application/json; charset=UTF-8'); - - switch ($path) { - case 'reading-list': - $type = 'A'; - break; - case 'starred': - $type = 's'; - break; - case 'feed': - $type = 'f'; +function streamContentsFilters($type, $streamId, $filter_target, $exclude_target, $start_time, $stop_time) { + switch ($type) { + case 'f': //feed + if ($streamId != '' && !ctype_digit($streamId)) { + $feedDAO = FreshRSS_Factory::createFeedDao(); + $streamId = htmlspecialchars($streamId, ENT_COMPAT, 'UTF-8'); + $feed = $feedDAO->searchByUrl($streamId); + $streamId = $feed == null ? -1 : $feed->id(); + } break; - case 'label': + case 'c': //category or label $categoryDAO = FreshRSS_Factory::createCategoryDao(); - $cat = $categoryDAO->searchByName($include_target); + $streamId = htmlspecialchars($streamId, ENT_COMPAT, 'UTF-8'); + $cat = $categoryDAO->searchByName($streamId); if ($cat != null) { $type = 'c'; - $include_target = $cat->id(); + $streamId = $cat->id(); } else { $tagDAO = FreshRSS_Factory::createTagDao(); - $tag = $tagDAO->searchByName($include_target); + $tag = $tagDAO->searchByName($streamId); if ($tag != null) { $type = 't'; - $include_target = $tag->id(); + $streamId = $tag->id(); } else { $type = 'A'; - $include_target = -1; + $streamId = -1; } } break; + } + + switch ($filter_target) { + case 'user/-/state/com.google/read': + $state = FreshRSS_Entry::STATE_READ; + break; + case 'user/-/state/com.google/unread': + $state = FreshRSS_Entry::STATE_NOT_READ; + break; + case 'user/-/state/com.google/starred': + $state = FreshRSS_Entry::STATE_FAVORITE; + break; default: - $type = 'A'; + $state = FreshRSS_Entry::STATE_ALL; break; } switch ($exclude_target) { case 'user/-/state/com.google/read': - $state = FreshRSS_Entry::STATE_NOT_READ; + $state &= FreshRSS_Entry::STATE_NOT_READ; break; case 'user/-/state/com.google/unread': - $state = FreshRSS_Entry::STATE_READ; + $state &= FreshRSS_Entry::STATE_READ; + break; + case 'user/-/state/com.google/starred': + $state &= FreshRSS_Entry::STATE_NOT_FAVORITE; + break; + } + + $searches = new FreshRSS_BooleanSearch(''); + if ($start_time != '') { + $search = new FreshRSS_Search(''); + $search->setMinDate($start_time); + $searches->add($search); + } + if ($stop_time != '') { + $search = new FreshRSS_Search(''); + $search->setMaxDate($stop_time); + $searches->add($search); + } + + return array($type, $streamId, $state, $searches); +} + +function streamContents($path, $include_target, $start_time, $stop_time, $count, $order, $filter_target, $exclude_target, $continuation) { +//http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI +//http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed + header('Content-Type: application/json; charset=UTF-8'); + + switch ($path) { + case 'reading-list': + $type = 'A'; + break; + case 'starred': + $type = 's'; + break; + case 'feed': + $type = 'f'; + break; + case 'label': + $type = 'c'; break; default: - $state = FreshRSS_Entry::STATE_ALL; + $type = 'A'; break; } + list($type, $include_target, $state, $searches) = streamContentsFilters($type, $include_target, $filter_target, $exclude_target, $start_time, $stop_time); + if ($continuation != '') { $count++; //Shift by one element } $entryDAO = FreshRSS_Factory::createEntryDao(); - $entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, new FreshRSS_BooleanSearch(''), $start_time); + $entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, $searches); $items = entriesToArray($entries); @@ -614,7 +669,7 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex exit(); } -function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target, $continuation) { +function streamContentsItemsIds($streamId, $start_time, $stop_time, $count, $order, $filter_target, $exclude_target, $continuation) { //http://code.google.com/p/google-reader-api/wiki/ApiStreamItemsIds //http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI //http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed @@ -622,55 +677,32 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude $id = ''; if ($streamId === 'user/-/state/com.google/reading-list') { $type = 'A'; - } elseif ('user/-/state/com.google/starred') { + } elseif ($streamId === 'user/-/state/com.google/starred') { $type = 's'; } elseif (strpos($streamId, 'feed/') === 0) { $type = 'f'; - $id = basename($streamId); + $streamId = substr($streamId, 5); } elseif (strpos($streamId, 'user/-/label/') === 0) { $type = 'c'; - $c_name = substr($streamId, 13); - $categoryDAO = FreshRSS_Factory::createCategoryDao(); - $cat = $categoryDAO->searchByName($c_name); - if ($cat != null) { - $type = 'c'; - $id = $cat->id(); - } else { - $tagDAO = FreshRSS_Factory::createTagDao(); - $tag = $tagDAO->searchByName($c_name); - if ($tag != null) { - $type = 't'; - $id = $tag->id(); - } else { - $type = 'A'; - $id = -1; - } - } + $streamId = substr($streamId, 13); } - switch ($exclude_target) { - case 'user/-/state/com.google/read': - $state = FreshRSS_Entry::STATE_NOT_READ; - break; - default: - $state = FreshRSS_Entry::STATE_ALL; - break; - } + list($type, $id, $state, $searches) = streamContentsFilters($type, $streamId, $filter_target, $exclude_target, $start_time, $stop_time); if ($continuation != '') { $count++; //Shift by one element } $entryDAO = FreshRSS_Factory::createEntryDao(); - $ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, new FreshRSS_BooleanSearch(''), $start_time); + $ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, $searches); if ($continuation != '') { array_shift($ids); //Discard first element that was already sent in the previous response $count--; } - if (empty($ids)) { //For News+ bug https://github.com/noinnion/newsplus/issues/84#issuecomment-57834632 - $ids[] = 0; + if (empty($ids) && isset($_GET['client']) && $_GET['client'] === 'newsplus') { + $ids[] = 0; //For News+ bug https://github.com/noinnion/newsplus/issues/84#issuecomment-57834632 } $itemRefs = array(); foreach ($ids as $id) { @@ -697,7 +729,10 @@ function streamContentsItems($e_ids, $order) { header('Content-Type: application/json; charset=UTF-8'); foreach ($e_ids as $i => $e_id) { - $e_ids[$i] = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/' + if (strpos($e_id, '/') !== null) { + $e_id = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/' + } + $e_ids[$i] = $e_id; } $entryDAO = FreshRSS_Factory::createEntryDao(); @@ -717,7 +752,10 @@ function streamContentsItems($e_ids, $order) { function editTag($e_ids, $a, $r) { foreach ($e_ids as $i => $e_id) { - $e_ids[$i] = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/' + if (strpos($e_id, '/') !== null) { + $e_id = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/' + } + $e_ids[$i] = $e_id; } $entryDAO = FreshRSS_Factory::createEntryDao(); @@ -748,6 +786,7 @@ function editTag($e_ids, $a, $r) { } } if ($tagName != '') { + $tagName = htmlspecialchars($tagName, ENT_COMPAT, 'UTF-8'); $tag = $tagDAO->searchByName($tagName); if ($tag == null) { $tagDAO->addTag(array('name' => $tagName)); @@ -771,6 +810,7 @@ function editTag($e_ids, $a, $r) { default: if (strpos($r, 'user/-/label/') === 0) { $tagName = substr($r, 13); + $tagName = htmlspecialchars($tagName, ENT_COMPAT, 'UTF-8'); $tag = $tagDAO->searchByName($tagName); if ($tag != null) { foreach ($e_ids as $e_id) { @@ -788,7 +828,9 @@ function renameTag($s, $dest) { if ($s != '' && strpos($s, 'user/-/label/') === 0 && $dest != '' && strpos($dest, 'user/-/label/') === 0) { $s = substr($s, 13); + $s = htmlspecialchars($s, ENT_COMPAT, 'UTF-8'); $dest = substr($dest, 13); + $dest = htmlspecialchars($dest, ENT_COMPAT, 'UTF-8'); $categoryDAO = FreshRSS_Factory::createCategoryDao(); $cat = $categoryDAO->searchByName($s); @@ -810,6 +852,7 @@ function renameTag($s, $dest) { function disableTag($s) { if ($s != '' && strpos($s, 'user/-/label/') === 0) { $s = substr($s, 13); + $s = htmlspecialchars($s, ENT_COMPAT, 'UTF-8'); $categoryDAO = FreshRSS_Factory::createCategoryDao(); $cat = $categoryDAO->searchByName($s); if ($cat != null) { @@ -838,6 +881,7 @@ function markAllAsRead($streamId, $olderThanId) { $entryDAO->markReadFeed($f_id, $olderThanId); } elseif (strpos($streamId, 'user/-/label/') === 0) { $c_name = substr($streamId, 13); + $c_name = htmlspecialchars($c_name, ENT_COMPAT, 'UTF-8'); $categoryDAO = FreshRSS_Factory::createCategoryDao(); $cat = $categoryDAO->searchByName($c_name); if ($cat != null) { @@ -902,12 +946,14 @@ if (count($pathInfos) < 3) { * exclude items from a particular feed (obviously not useful in this * request, but xt appears in other listing requests). */ $exclude_target = isset($_GET['xt']) ? $_GET['xt'] : ''; + $filter_target = isset($_GET['it']) ? $_GET['it'] : ''; $count = isset($_GET['n']) ? intval($_GET['n']) : 20; //n=[integer] : The maximum number of results to return. $order = isset($_GET['r']) ? $_GET['r'] : 'd'; //r=[d|n|o] : Sort order of item results. d or n gives items in descending date order, o in ascending order. /* ot=[unix timestamp] : The time from which you want to retrieve * items. Only items that have been crawled by Google Reader after * this time will be returned. */ $start_time = isset($_GET['ot']) ? intval($_GET['ot']) : 0; + $stop_time = isset($_GET['nt']) ? intval($_GET['nt']) : 0; /* Continuation token. If a StreamContents response does not represent * all items in a timestamp range, it will have a continuation attribute. * The same request can be re-issued with the value of that attribute put @@ -920,23 +966,31 @@ if (count($pathInfos) < 3) { if (isset($pathInfos[7])) { if ($pathInfos[6] === 'feed') { $include_target = $pathInfos[7]; - StreamContents($pathInfos[6], $include_target, $start_time, $count, $order, $exclude_target, $continuation); + if ($include_target != '' && !ctype_digit($include_target)) { + $include_target = empty($_SERVER['REQUEST_URI']) ? '' : $_SERVER['REQUEST_URI']; + if (preg_match('#/reader/api/0/stream/contents/feed/([A-Za-z0-9\'!*()%$_.~+-]+)#', $include_target, $matches) && isset($matches[1])) { + $include_target = urldecode($matches[1]); + } else { + $include_target = ''; + } + } + streamContents($pathInfos[6], $include_target, $start_time, $stop_time, $count, $order, $filter_target, $exclude_target, $continuation); } elseif ($pathInfos[6] === 'user' && isset($pathInfos[8]) && isset($pathInfos[9])) { if ($pathInfos[8] === 'state') { if ($pathInfos[9] === 'com.google' && isset($pathInfos[10])) { if ($pathInfos[10] === 'reading-list' || $pathInfos[10] === 'starred') { $include_target = ''; - streamContents($pathInfos[10], $include_target, $start_time, $count, $order, $exclude_target, $continuation); + streamContents($pathInfos[10], $include_target, $start_time, $stop_time, $count, $order, $filter_target, $exclude_target, $continuation); } } } elseif ($pathInfos[8] === 'label') { $include_target = $pathInfos[9]; - streamContents($pathInfos[8], $include_target, $start_time, $count, $order, $exclude_target, $continuation); + streamContents($pathInfos[8], $include_target, $start_time, $stop_time, $count, $order, $filter_target, $exclude_target, $continuation); } } } else { //EasyRSS $include_target = ''; - streamContents('reading-list', $include_target, $start_time, $count, $order, $exclude_target, $continuation); + streamContents('reading-list', $include_target, $start_time, $stop_time, $count, $order, $filter_target, $exclude_target, $continuation); } } elseif ($pathInfos[5] === 'items') { if ($pathInfos[6] === 'ids' && isset($_GET['s'])) { @@ -944,7 +998,7 @@ if (count($pathInfos) < 3) { * be repeated to fetch the item IDs from multiple streams at once * (more efficient from a backend perspective than multiple requests). */ $streamId = $_GET['s']; - streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target, $continuation); + streamContentsItemsIds($streamId, $start_time, $stop_time, $count, $order, $filter_target, $exclude_target, $continuation); } else if ($pathInfos[6] === 'contents' && isset($_POST['i'])) { //FeedMe $e_ids = multiplePosts('i'); //item IDs streamContentsItems($e_ids, $order); -- cgit v1.2.3 From 0fce9892ff2b03083706b4f78495539861db98aa Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 12 Nov 2018 09:03:20 +0100 Subject: API encoding tuning (#2120) Use only minimal XML->Unicode encoding for articles title. Follow-up of https://github.com/FreshRSS/FreshRSS/pull/2093 --- app/Models/Feed.php | 2 +- lib/lib_rss.php | 21 +++++++++++++-------- p/api/greader.php | 8 ++++---- 3 files changed, 18 insertions(+), 13 deletions(-) (limited to 'app/Models') diff --git a/app/Models/Feed.php b/app/Models/Feed.php index a5ef33d6b..acf3bd981 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -424,7 +424,7 @@ class FreshRSS_Feed extends Minz_Model { $author_names = ''; if (is_array($authors)) { foreach ($authors as $author) { - $author_names .= escapeToUnicodeAlternative(strip_tags($author->name == '' ? $author->email : $author->name)) . '; '; + $author_names .= escapeToUnicodeAlternative(strip_tags($author->name == '' ? $author->email : $author->name), true) . '; '; } } $author_names = substr($author_names, 0, -2); diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 52e4408d2..c445874c8 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -102,16 +102,21 @@ function safe_ascii($text) { return filter_var($text, FILTER_DEFAULT, FILTER_FLAG_STRIP_LOW | FILTER_FLAG_STRIP_HIGH); } -function escapeToUnicodeAlternative($text) { +function escapeToUnicodeAlternative($text, $extended = true) { $text = htmlspecialchars_decode($text, ENT_QUOTES); + + //Problematic characters + $problem = array('&', '<', '>'); + //Use their fullwidth Unicode form instead: + $replace = array('&', '<', '>'); + // https://raw.githubusercontent.com/mihaip/google-reader-api/master/wiki/StreamId.wiki - return trim(str_replace( - //Problematic characters - array("'", '"', '^', '<', '>', '?', '&', '\\', '/', ',', ';'), - //Use their fullwidth Unicode form instead: - array("’", '"', '^', '<', '>', '?', '&', '\', '/', ',', ';'), - $text - )); + if ($extended) { + $problem += array("'", '"', '^', '?', '\\', '/', ',', ';'); + $replace += array("’", '"', '^', '?', '\', '/', ',', ';'); + } + + return trim(str_replace($problem, $replace, $text)); } /** diff --git a/p/api/greader.php b/p/api/greader.php index 7c5c54951..7cd312f2c 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -300,7 +300,7 @@ function subscriptionList() { foreach ($res as $line) { $subscriptions[] = array( 'id' => 'feed/' . $line['id'], - 'title' => escapeToUnicodeAlternative($line['name']), + 'title' => escapeToUnicodeAlternative($line['name'], true), 'categories' => array( array( 'id' => 'user/-/label/' . htmlspecialchars_decode($line['c_name'], ENT_QUOTES), @@ -506,7 +506,7 @@ function entriesToArray($entries) { 'crawlTimeMsec' => substr($entry->id(), 0, -3), 'timestampUsec' => '' . $entry->id(), //EasyRSS 'published' => $entry->date(true), - 'title' => escapeToUnicodeAlternative($entry->title()), + 'title' => escapeToUnicodeAlternative($entry->title(), false), 'summary' => array('content' => $entry->content()), 'alternate' => array( array('href' => htmlspecialchars_decode($entry->link(), ENT_QUOTES)), @@ -517,14 +517,14 @@ function entriesToArray($entries) { ), 'origin' => array( 'streamId' => 'feed/' . $f_id, - 'title' => escapeToUnicodeAlternative($f_name), //EasyRSS + 'title' => escapeToUnicodeAlternative($f_name, true), //EasyRSS //'htmlUrl' => $line['f_website'], ), ); $author = $entry->authors(true); $author = trim($author, '; '); if ($author != '') { - $item['author'] = escapeToUnicodeAlternative($author); + $item['author'] = escapeToUnicodeAlternative($author, false); } if ($entry->isRead()) { $item['categories'][] = 'user/-/state/com.google/read'; -- cgit v1.2.3 From 6122a45d9a60fb52cbb83cb649dbf928c6446149 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Thu, 15 Nov 2018 17:07:05 +0100 Subject: Minor additional WebSub debug --- app/Models/Feed.php | 1 + 1 file changed, 1 insertion(+) (limited to 'app/Models') diff --git a/app/Models/Feed.php b/app/Models/Feed.php index acf3bd981..fc7ed8c68 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -619,6 +619,7 @@ class FreshRSS_Feed extends Minz_Model { $info = curl_getinfo($ch); Minz_Log::warning('PubSubHubbub ' . ($state ? 'subscribe' : 'unsubscribe') . ' to ' . $url . + ' via hub ' . $hubJson['hub'] . ' with callback ' . $callbackUrl . ': ' . $info['http_code'] . ' ' . $response, PSHB_LOG); if (substr($info['http_code'], 0, 1) == '2') { -- cgit v1.2.3 From ebb9ee0873333c111546412ad08bf5207529f24a Mon Sep 17 00:00:00 2001 From: Patrick Crandol Date: Sun, 18 Nov 2018 13:34:41 -0500 Subject: Edit cookie_duration from GUI (#2137) * Use cookie_duration correctly * WIP allow cookie_duration to be modified from GUI * Allow cookie_duration to actually be updated * Update view to properly display cookie_duration * Add new strings in Translation Files * Fix typo * Fix trailing whitespace * I18n: French translation * I18n fr: Forgot todo --- app/Controllers/configureController.php | 2 ++ app/Models/Auth.php | 11 ++++++----- app/Models/ConfigurationSetter.php | 3 +++ app/i18n/cz/admin.php | 14 +++++++++----- app/i18n/de/admin.php | 4 ++++ app/i18n/en/admin.php | 4 ++++ app/i18n/es/admin.php | 4 ++++ app/i18n/fr/admin.php | 4 ++++ app/i18n/he/admin.php | 14 +++++++++----- app/i18n/it/admin.php | 4 ++++ app/i18n/kr/admin.php | 4 ++++ app/i18n/nl/admin.php | 4 ++++ app/i18n/oc/admin.php | 4 ++++ app/i18n/pt-br/admin.php | 4 ++++ app/i18n/ru/admin.php | 4 ++++ app/i18n/tr/admin.php | 4 ++++ app/i18n/zh-cn/admin.php | 4 ++++ app/views/configure/system.phtml | 8 ++++++++ 18 files changed, 85 insertions(+), 15 deletions(-) (limited to 'app/Models') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 20bcd2e76..9c3900f39 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -308,6 +308,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * - user limit (default: 1) * - user category limit (default: 16384) * - user feed limit (default: 16384) + * - user login duration for form auth (default: 2592000) */ public function systemAction() { if (!FreshRSS_Auth::hasAccess('admin')) { @@ -318,6 +319,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $limits['max_registrations'] = Minz_Request::param('max-registrations', 1); $limits['max_feeds'] = Minz_Request::param('max-feeds', 16384); $limits['max_categories'] = Minz_Request::param('max-categories', 16384); + $limits['cookie_duration'] = Minz_Request::param('cookie-duration', 2592000); FreshRSS_Context::$system_conf->limits = $limits; FreshRSS_Context::$system_conf->title = Minz_Request::param('instance-name', 'FreshRSS'); FreshRSS_Context::$system_conf->auto_update_url = Minz_Request::param('auto-update-url', false); diff --git a/app/Models/Auth.php b/app/Models/Auth.php index 8c711308c..d1e26b8e8 100644 --- a/app/Models/Auth.php +++ b/app/Models/Auth.php @@ -233,9 +233,10 @@ class FreshRSS_FormAuth { $token_file = DATA_PATH . '/tokens/' . $token . '.txt'; $mtime = @filemtime($token_file); - if ($mtime + 2629744 < time()) { - // Token has expired (> 1 month) or does not exist. - // TODO: 1 month -> use a configuration instead + $limits = $conf->limits; + $cookie_duration = empty($limits['cookie_duration']) ? 2592000 : $limits['cookie_duration']; + if ($mtime + $cookie_duration < time()) { + // Token has expired (> cookie_duration) or does not exist. @unlink($token_file); return array(); } @@ -256,7 +257,7 @@ class FreshRSS_FormAuth { } $limits = $conf->limits; - $cookie_duration = empty($limits['cookie_duration']) ? 2629744 : $limits['cookie_duration']; + $cookie_duration = empty($limits['cookie_duration']) ? 2592000 : $limits['cookie_duration']; $expire = time() + $cookie_duration; Minz_Session::setLongTermCookie('FreshRSS_login', $token, $expire); return $token; @@ -277,7 +278,7 @@ class FreshRSS_FormAuth { public static function purgeTokens() { $conf = Minz_Configuration::get('system'); $limits = $conf->limits; - $cookie_duration = empty($limits['cookie_duration']) ? 2629744 : $limits['cookie_duration']; + $cookie_duration = empty($limits['cookie_duration']) ? 2592000 : $limits['cookie_duration']; $oldest = time() - $cookie_duration; foreach (new DirectoryIterator(DATA_PATH . '/tokens/') as $file_info) { // $extension = $file_info->getExtension(); doesn't work in PHP < 5.3.7 diff --git a/app/Models/ConfigurationSetter.php b/app/Models/ConfigurationSetter.php index ad703dfc5..ec6380df4 100644 --- a/app/Models/ConfigurationSetter.php +++ b/app/Models/ConfigurationSetter.php @@ -335,6 +335,9 @@ class FreshRSS_ConfigurationSetter { private function _limits(&$data, $values) { $max_small_int = 16384; $limits_keys = array( + 'cookie_duration' => array( + 'min' => 0, + ), 'cache_duration' => array( 'min' => 0, ), diff --git a/app/i18n/cz/admin.php b/app/i18n/cz/admin.php index 271a9a253..68127f571 100644 --- a/app/i18n/cz/admin.php +++ b/app/i18n/cz/admin.php @@ -157,11 +157,15 @@ return array( 'top_feed' => 'Top ten kanálů', ), 'system' => array( - '_' => 'System configuration', //TODO - Translation - 'auto-update-url' => 'Auto-update server URL', //TODO - Translation - 'instance-name' => 'Instance name', //TODO - Translation - 'max-categories' => 'Categories per user limit', //TODO - Translation - 'max-feeds' => 'Feeds per user limit', //TODO - Translation + '_' => 'System configuration', //TODO - Translation + 'auto-update-url' => 'Auto-update server URL', //TODO - Translation + 'cookie-duration' => array( + 'help' => 'in seconds', //TODO - Translation + 'number' => 'Duration to keep logged in', //TODO - Translation + ), + 'instance-name' => 'Instance name', //TODO - Translation + 'max-categories' => 'Categories per user limit', //TODO - Translation + 'max-feeds' => 'Feeds per user limit', //TODO - Translation 'registration' => array( 'help' => '0 znamená žádná omezení účtu', 'number' => 'Maximální počet účtů', diff --git a/app/i18n/de/admin.php b/app/i18n/de/admin.php index fe626bb70..f0307dcab 100644 --- a/app/i18n/de/admin.php +++ b/app/i18n/de/admin.php @@ -162,6 +162,10 @@ return array( 'instance-name' => 'Dein Reader Name', 'max-categories' => 'Anzahl erlaubter Kategorien pro Benutzer', 'max-feeds' => 'Anzahl erlaubter Feeds pro Benutzer', + 'cookie-duration' => array( + 'help' => 'in Sekunden', + 'number' => 'Duration to keep logged in', // @todo translate + ), 'registration' => array( 'help' => '0 meint, dass es kein Account Limit gibt', 'number' => 'Maximale Anzahl von Accounts', diff --git a/app/i18n/en/admin.php b/app/i18n/en/admin.php index b1252d672..004089ffc 100644 --- a/app/i18n/en/admin.php +++ b/app/i18n/en/admin.php @@ -162,6 +162,10 @@ return array( 'instance-name' => 'Instance name', 'max-categories' => 'Categories per user limit', 'max-feeds' => 'Feeds per user limit', + 'cookie-duration' => array( + 'help' => 'in seconds', + 'number' => 'Duration to keep logged in', + ), 'registration' => array( 'help' => '0 means that there is no account limit', 'number' => 'Max number of accounts', diff --git a/app/i18n/es/admin.php b/app/i18n/es/admin.php index bd4446ce5..0ec8549bd 100755 --- a/app/i18n/es/admin.php +++ b/app/i18n/es/admin.php @@ -162,6 +162,10 @@ return array( 'instance-name' => 'Nombre de la fuente', 'max-categories' => 'Límite de categorías por usuario', 'max-feeds' => 'Límite de fuentes por usuario', + 'cookie-duration' => array( + 'help' => 'in seconds', // @todo translate + 'number' => 'Duration to keep logged in', // @todo translate + ), 'registration' => array( 'help' => '0 significa que no hay límite en la cuenta', 'number' => 'Número máximo de cuentas', diff --git a/app/i18n/fr/admin.php b/app/i18n/fr/admin.php index 1175c9c65..74605b5ee 100644 --- a/app/i18n/fr/admin.php +++ b/app/i18n/fr/admin.php @@ -162,6 +162,10 @@ return array( 'instance-name' => 'Nom de l’instance', 'max-categories' => 'Limite de catégories par utilisateur', 'max-feeds' => 'Limite de flux par utilisateur', + 'cookie-duration' => array( + 'help' => 'en secondes', + 'number' => 'Durée avant expiration de la session', + ), 'registration' => array( 'help' => 'Un chiffre de 0 signifie que l’on peut créer un nombre infini de comptes', 'number' => 'Nombre max de comptes', diff --git a/app/i18n/he/admin.php b/app/i18n/he/admin.php index 415816d34..e0dfc405d 100644 --- a/app/i18n/he/admin.php +++ b/app/i18n/he/admin.php @@ -157,11 +157,15 @@ return array( 'top_feed' => 'עשרת ההזנות המובילות', ), 'system' => array( - '_' => 'System configuration', //TODO - Translation - 'auto-update-url' => 'Auto-update server URL', //TODO - Translation - 'instance-name' => 'Instance name', //TODO - Translation - 'max-categories' => 'Categories per user limit', //TODO - Translation - 'max-feeds' => 'Feeds per user limit', //TODO - Translation + '_' => 'System configuration', //TODO - Translation + 'auto-update-url' => 'Auto-update server URL', //TODO - Translation + 'cookie-duration' => array( + 'help' => 'in seconds', //TODO - Translation + 'number' => 'Duration to keep logged in', //TODO - Translation + ), + 'instance-name' => 'Instance name', //TODO - Translation + 'max-categories' => 'Categories per user limit', //TODO - Translation + 'max-feeds' => 'Feeds per user limit', //TODO - Translation 'registration' => array( 'help' => '0 means that there is no account limit', //TODO - Translation 'number' => 'Max number of accounts', //TODO - Translation diff --git a/app/i18n/it/admin.php b/app/i18n/it/admin.php index 88972aa8f..d4253e9ba 100644 --- a/app/i18n/it/admin.php +++ b/app/i18n/it/admin.php @@ -162,6 +162,10 @@ return array( 'instance-name' => 'Nome istanza', 'max-categories' => 'Limite categorie per utente', 'max-feeds' => 'Limite feeds per utente', + 'cookie-duration' => array( + 'help' => 'in seconds', // @todo translate + 'number' => 'Duration to keep logged in', // @todo translate + ), 'registration' => array( 'help' => '0 significa che non esiste limite sui profili', 'number' => 'Numero massimo di profili', diff --git a/app/i18n/kr/admin.php b/app/i18n/kr/admin.php index ac4a808e7..532fe30a5 100644 --- a/app/i18n/kr/admin.php +++ b/app/i18n/kr/admin.php @@ -162,6 +162,10 @@ return array( 'instance-name' => '인스턴스 이름', 'max-categories' => '사용자별 카테고리 개수 제한', 'max-feeds' => '사용자별 피드 개수 제한', + 'cookie-duration' => array( + 'help' => 'in seconds', // @todo translate + 'number' => 'Duration to keep logged in', // @todo translate + ), 'registration' => array( 'help' => '0: 제한 없음', 'number' => '계정 최대 개수', diff --git a/app/i18n/nl/admin.php b/app/i18n/nl/admin.php index 1fd31e27a..8a63b885b 100644 --- a/app/i18n/nl/admin.php +++ b/app/i18n/nl/admin.php @@ -162,6 +162,10 @@ return array( 'instance-name' => 'Voorbeeld naam', 'max-categories' => 'Categoriën limiet per gebruiker', 'max-feeds' => 'Feed limiet per gebruiker', + 'cookie-duration' => array( + 'help' => 'in seconds', // @todo translate + 'number' => 'Duration to keep logged in', // @todo translate + ), 'registration' => array( 'help' => '0 betekent geen account limiet', 'number' => 'Maximum aantal accounts', diff --git a/app/i18n/oc/admin.php b/app/i18n/oc/admin.php index 9eab3b4c8..3df36c7c5 100644 --- a/app/i18n/oc/admin.php +++ b/app/i18n/oc/admin.php @@ -159,6 +159,10 @@ return array( 'system' => array( '_' => 'Configuracion sistèma', 'auto-update-url' => 'URL del servici de mesa a jorn', + 'cookie-duration' => array( + 'help' => 'in seconds', //TODO - Translation + 'number' => 'Duration to keep logged in', //TODO - Translation + ), 'instance-name' => 'Nom de l’instància', 'max-categories' => 'Limita de categoria per utilizaire', 'max-feeds' => 'Limita de fluxes per utilizaire', diff --git a/app/i18n/pt-br/admin.php b/app/i18n/pt-br/admin.php index ca88e2039..82559c67b 100644 --- a/app/i18n/pt-br/admin.php +++ b/app/i18n/pt-br/admin.php @@ -162,6 +162,10 @@ return array( 'instance-name' => 'Nome da instância', 'max-categories' => 'Limite de categorias por usuário', 'max-feeds' => 'Limite de Feeds por usuário', + 'cookie-duration' => array( + 'help' => 'in seconds', // @todo translate + 'number' => 'Duration to keep logged in', // @todo translate + ), 'registration' => array( 'help' => '0 significa que não há limite para a conta', 'number' => 'Máximo número de contas', diff --git a/app/i18n/ru/admin.php b/app/i18n/ru/admin.php index efbd8fde0..c9a7d6683 100644 --- a/app/i18n/ru/admin.php +++ b/app/i18n/ru/admin.php @@ -162,6 +162,10 @@ return array( 'instance-name' => 'Название этого сервера', 'max-categories' => 'Количество категорий на пользователя', 'max-feeds' => 'Количество статей на пользователя', + 'cookie-duration' => array( + 'help' => 'in seconds', // @todo translate + 'number' => 'Duration to keep logged in', // @todo translate + ), 'registration' => array( 'help' => '0 означает неограниченное количество пользователей', 'number' => 'Максимальное количество пользователей', diff --git a/app/i18n/tr/admin.php b/app/i18n/tr/admin.php index 81e5d14a2..b1d6671ca 100644 --- a/app/i18n/tr/admin.php +++ b/app/i18n/tr/admin.php @@ -162,6 +162,10 @@ return array( 'instance-name' => 'Örnek isim', 'max-categories' => 'Kullanıcı başına kategori limiti', 'max-feeds' => 'Kullanıcı başına akış limiti', + 'cookie-duration' => array( + 'help' => 'in seconds', // @todo translate + 'number' => 'Duration to keep logged in', // @todo translate + ), 'registration' => array( 'help' => '0 sınır yok anlamındadır', 'number' => 'En fazla hesap sayısı', diff --git a/app/i18n/zh-cn/admin.php b/app/i18n/zh-cn/admin.php index e6ca552ab..e34070526 100644 --- a/app/i18n/zh-cn/admin.php +++ b/app/i18n/zh-cn/admin.php @@ -162,6 +162,10 @@ return array( 'instance-name' => '实例名称', 'max-categories' => '每用户分类限制', 'max-feeds' => '每用户 RSS 源限制', + 'cookie-duration' => array( + 'help' => 'in seconds', // @todo translate + 'number' => 'Duration to keep logged in', // @todo translate + ), 'registration' => array( 'help' => '0 表示无账户数限制', 'number' => '最大账户数', diff --git a/app/views/configure/system.phtml b/app/views/configure/system.phtml index 37b68c991..9af4cc2c9 100644 --- a/app/views/configure/system.phtml +++ b/app/views/configure/system.phtml @@ -51,6 +51,14 @@ + +
+ +
+ + +
+
-- cgit v1.2.3 From 2f70da06e6c19bef383e5011e6300eef7a8257cb Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Tue, 4 Dec 2018 21:26:10 +0100 Subject: Fix undefined conf (#2163) Small fix for https://github.com/FreshRSS/FreshRSS/pull/2137 --- app/Models/Auth.php | 1 + 1 file changed, 1 insertion(+) (limited to 'app/Models') diff --git a/app/Models/Auth.php b/app/Models/Auth.php index d1e26b8e8..9c3e31952 100644 --- a/app/Models/Auth.php +++ b/app/Models/Auth.php @@ -233,6 +233,7 @@ class FreshRSS_FormAuth { $token_file = DATA_PATH . '/tokens/' . $token . '.txt'; $mtime = @filemtime($token_file); + $conf = Minz_Configuration::get('system'); $limits = $conf->limits; $cookie_duration = empty($limits['cookie_duration']) ? 2592000 : $limits['cookie_duration']; if ($mtime + $cookie_duration < time()) { -- cgit v1.2.3 From 512d047f02b601dcf21f8c807117ea154967e58f Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 16 Dec 2018 17:02:03 +0100 Subject: Update naming to WebSub (#2184) Instead of PubSubHubbub / PuSH --- README.fr.md | 7 +++++-- README.md | 7 +++++-- app/Controllers/feedController.php | 12 ++++++------ app/Models/Feed.php | 18 +++++++++--------- app/i18n/cz/sub.php | 2 +- app/i18n/de/sub.php | 2 +- app/i18n/en/sub.php | 2 +- app/i18n/es/sub.php | 2 +- app/i18n/fr/sub.php | 2 +- app/i18n/he/sub.php | 2 +- app/i18n/it/sub.php | 2 +- app/i18n/kr/sub.php | 2 +- app/i18n/nl/sub.php | 2 +- app/i18n/oc/sub.php | 2 +- app/i18n/pt-br/sub.php | 2 +- app/i18n/ru/sub.php | 2 +- app/i18n/tr/sub.php | 2 +- app/i18n/zh-cn/sub.php | 2 +- app/views/helpers/feed/update.phtml | 2 +- cli/README.md | 2 +- config.default.php | 2 +- docs/en/users/05_Configuration.md | 2 +- docs/fr/contributing.md | 2 +- docs/fr/users/08_PubSubHubbub.md | 23 ++++++++++++++++------- p/api/pshb.php | 4 ++-- 25 files changed, 62 insertions(+), 47 deletions(-) (limited to 'app/Models') diff --git a/README.fr.md b/README.fr.md index 9db5907a0..14d780b18 100644 --- a/README.fr.md +++ b/README.fr.md @@ -8,9 +8,12 @@ FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de [Leed] Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable. -Il permet de gérer plusieurs utilisateurs, et dispose d’un mode de lecture anonyme. -Il supporte les étiquettes personnalisées, et [PubSubHubbub](https://github.com/pubsubhubbub/PubSubHubbub) pour des notifications instantanées depuis les sites compatibles. +Il permet de gérer plusieurs utilisateurs, dispose d’un mode de lecture anonyme, et supporte les étiquettes personnalisées. Il y a une API pour les clients (mobiles), ainsi qu’une [interface en ligne de commande](cli/README.md). + +Grâce au standard [WebSub](https://www.w3.org/TR/websub/) (anciennement [PubSubHubbub](https://github.com/pubsubhubbub/PubSubHubbub)), +FreshRSS est capable de recevoir des notifications push instantanées depuis les sources compatibles, telles [Mastodon](https://joinmastodon.org), [Friendica](https://friendi.ca), [WordPress](https://wordpress.org/plugins/pubsubhubbub/), Blogger, FeedBurner, etc. + Enfin, il permet l’ajout d’[extensions](#extensions) pour encore plus de personnalisation. Les demandes de fonctionnalités, rapports de bugs, et autres contributions sont les bienvenues. Privilégiez pour cela des [demandes sur GitHub](https://github.com/FreshRSS/FreshRSS/issues). diff --git a/README.md b/README.md index 1904dad2c..4cf598172 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,12 @@ FreshRSS is a self-hosted RSS feed aggregator like [Leed](http://leed.idleman.fr It is lightweight, easy to work with, powerful, and customizable. -It is a multi-user application with an anonymous reading mode. -It supports custom tags, and [PubSubHubbub](https://github.com/pubsubhubbub/PubSubHubbub) for instant notifications from compatible Web sites. +It is a multi-user application with an anonymous reading mode. It supports custom tags. There is an API for (mobile) clients, and a [Command-Line Interface](cli/README.md). + +Thanks to the [WebSub](https://www.w3.org/TR/websub/) standard (formerly [PubSubHubbub](https://github.com/pubsubhubbub/PubSubHubbub)), +FreshRSS is able to receive instant push notifications from compatible sources, such as [Mastodon](https://joinmastodon.org), [Friendica](https://friendi.ca), [WordPress](https://wordpress.org/plugins/pubsubhubbub/), Blogger, FeedBurner, etc. + Finally, it supports [extensions](#extensions) for further tuning. Feature requests, bug reports, and other contributions are welcome. The best way to contribute is to [open an issue on GitHub](https://github.com/FreshRSS/FreshRSS/issues). diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index f2b1b8960..74c9eacfa 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -266,7 +266,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $nb_month_old = max(FreshRSS_Context::$user_conf->old_entries, 1); $date_min = time() - (3600 * 24 * 30 * $nb_month_old); - // PubSubHubbub support + // WebSub (PubSubHubbub) support $pubsubhubbubEnabledGeneral = FreshRSS_Context::$system_conf->pubsubhubbub_enabled; $pshbMinAge = time() - (3600 * 24); //TODO: Make a configuration. @@ -437,13 +437,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $entryDAO->commit(); } - if ($feed->hubUrl() && $feed->selfUrl()) { //selfUrl has priority for PubSubHubbub + if ($feed->hubUrl() && $feed->selfUrl()) { //selfUrl has priority for WebSub if ($feed->selfUrl() !== $url) { //https://code.google.com/p/pubsubhubbub/wiki/MovingFeedsOrChangingHubs $selfUrl = checkUrl($feed->selfUrl()); if ($selfUrl) { - Minz_Log::debug('PubSubHubbub unsubscribe ' . $feed->url(false)); + Minz_Log::debug('WebSub unsubscribe ' . $feed->url(false)); if (!$feed->pubSubHubbubSubscribe(false)) { //Unsubscribe - Minz_Log::warning('Error while PubSubHubbub unsubscribing from ' . $feed->url(false)); + Minz_Log::warning('Error while WebSub unsubscribing from ' . $feed->url(false)); } $feed->_url($selfUrl, false); Minz_Log::notice('Feed ' . $url . ' canonical address moved to ' . $feed->url(false)); @@ -457,9 +457,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feed->faviconPrepare(); if ($pubsubhubbubEnabledGeneral && $feed->pubSubHubbubPrepare()) { - Minz_Log::notice('PubSubHubbub subscribe ' . $feed->url(false)); + Minz_Log::notice('WebSub subscribe ' . $feed->url(false)); if (!$feed->pubSubHubbubSubscribe(true)) { //Subscribe - Minz_Log::warning('Error while PubSubHubbub subscribing to ' . $feed->url(false)); + Minz_Log::warning('Error while WebSub subscribing to ' . $feed->url(false)); } } $feed->unlock(); diff --git a/app/Models/Feed.php b/app/Models/Feed.php index fc7ed8c68..b21a8bbbe 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -498,7 +498,7 @@ class FreshRSS_Feed extends Minz_Model { @unlink($this->lockPath); } - // + // public function pubSubHubbubEnabled() { $url = $this->selfUrl ? $this->selfUrl : $this->url; @@ -534,13 +534,13 @@ class FreshRSS_Feed extends Minz_Model { if ($hubFile = @file_get_contents($hubFilename)) { $hubJson = json_decode($hubFile, true); if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key'])) { - $text = 'Invalid JSON for PubSubHubbub: ' . $this->url; + $text = 'Invalid JSON for WebSub: ' . $this->url; Minz_Log::warning($text); Minz_Log::warning($text, PSHB_LOG); return false; } if ((!empty($hubJson['lease_end'])) && ($hubJson['lease_end'] < (time() + (3600 * 23)))) { //TODO: Make a better policy - $text = 'PubSubHubbub lease ends at ' + $text = 'WebSub lease ends at ' . date('c', empty($hubJson['lease_end']) ? time() : $hubJson['lease_end']) . ' and needs renewal: ' . $this->url; Minz_Log::warning($text); @@ -560,7 +560,7 @@ class FreshRSS_Feed extends Minz_Model { file_put_contents($hubFilename, json_encode($hubJson)); @mkdir(PSHB_PATH . '/keys/'); file_put_contents(PSHB_PATH . '/keys/' . $key . '.txt', base64url_encode($this->selfUrl)); - $text = 'PubSubHubbub prepared for ' . $this->url; + $text = 'WebSub prepared for ' . $this->url; Minz_Log::debug($text); Minz_Log::debug($text, PSHB_LOG); } @@ -579,17 +579,17 @@ class FreshRSS_Feed extends Minz_Model { $hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($url) . '/!hub.json'; $hubFile = @file_get_contents($hubFilename); if ($hubFile === false) { - Minz_Log::warning('JSON not found for PubSubHubbub: ' . $this->url); + Minz_Log::warning('JSON not found for WebSub: ' . $this->url); return false; } $hubJson = json_decode($hubFile, true); if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key']) || empty($hubJson['hub'])) { - Minz_Log::warning('Invalid JSON for PubSubHubbub: ' . $this->url); + Minz_Log::warning('Invalid JSON for WebSub: ' . $this->url); return false; } $callbackUrl = checkUrl(Minz_Request::getBaseUrl() . '/api/pshb.php?k=' . $hubJson['key']); if ($callbackUrl == '') { - Minz_Log::warning('Invalid callback for PubSubHubbub: ' . $this->url); + Minz_Log::warning('Invalid callback for WebSub: ' . $this->url); return false; } if (!$state) { //unsubscribe @@ -618,7 +618,7 @@ class FreshRSS_Feed extends Minz_Model { $response = curl_exec($ch); $info = curl_getinfo($ch); - Minz_Log::warning('PubSubHubbub ' . ($state ? 'subscribe' : 'unsubscribe') . ' to ' . $url . + Minz_Log::warning('WebSub ' . ($state ? 'subscribe' : 'unsubscribe') . ' to ' . $url . ' via hub ' . $hubJson['hub'] . ' with callback ' . $callbackUrl . ': ' . $info['http_code'] . ' ' . $response, PSHB_LOG); @@ -634,5 +634,5 @@ class FreshRSS_Feed extends Minz_Model { return false; } - // + // } diff --git a/app/i18n/cz/sub.php b/app/i18n/cz/sub.php index ec4c6f374..ad02f6f49 100644 --- a/app/i18n/cz/sub.php +++ b/app/i18n/cz/sub.php @@ -45,7 +45,7 @@ return array( 'main_stream' => 'Zobrazit ve “Všechny kanály”', 'normal' => 'Show in its category', //TODO - Translation ), - 'pubsubhubbub' => 'Okamžité oznámení s PubSubHubbub', + 'websub' => 'Okamžité oznámení s WebSub', 'show' => array( 'all' => 'Show all feeds', //TODO - Translation 'error' => 'Show only feeds with error', //TODO - Translation diff --git a/app/i18n/de/sub.php b/app/i18n/de/sub.php index c4455c4be..aa408e8c7 100644 --- a/app/i18n/de/sub.php +++ b/app/i18n/de/sub.php @@ -45,7 +45,7 @@ return array( 'main_stream' => 'In Haupt-Feeds zeigen', 'normal' => 'Zeige in eigener Kategorie', ), - 'pubsubhubbub' => 'Sofortbenachrichtigung mit PubSubHubbub', + 'websub' => 'Sofortbenachrichtigung mit WebSub', 'show' => array( 'all' => 'Show all feeds', //TODO - Translation 'error' => 'Show only feeds with error', //TODO - Translation diff --git a/app/i18n/en/sub.php b/app/i18n/en/sub.php index 1dac808b0..9acbcbf33 100644 --- a/app/i18n/en/sub.php +++ b/app/i18n/en/sub.php @@ -45,7 +45,7 @@ return array( 'main_stream' => 'Show in main stream', 'normal' => 'Show in its category', ), - 'pubsubhubbub' => 'Instant notification with PubSubHubbub', + 'websub' => 'Instant notification with WebSub', 'show' => array( 'all' => 'Show all feeds', 'error' => 'Show only feeds with error', diff --git a/app/i18n/es/sub.php b/app/i18n/es/sub.php index b6a7eb568..64e420dc1 100755 --- a/app/i18n/es/sub.php +++ b/app/i18n/es/sub.php @@ -45,7 +45,7 @@ return array( 'main_stream' => 'Mostrar en salida principal', 'normal' => 'Show in its category', //TODO - Translation ), - 'pubsubhubbub' => 'Notificación inmedaiata con PubSubHubbub', + 'websub' => 'Notificación inmedaiata con WebSub', 'show' => array( 'all' => 'Show all feeds', //TODO - Translation 'error' => 'Show only feeds with error', //TODO - Translation diff --git a/app/i18n/fr/sub.php b/app/i18n/fr/sub.php index b5eaccef4..6cb31414d 100644 --- a/app/i18n/fr/sub.php +++ b/app/i18n/fr/sub.php @@ -45,7 +45,7 @@ return array( 'main_stream' => 'Afficher dans le flux principal', 'normal' => 'Afficher dans sa catégorie', ), - 'pubsubhubbub' => 'Notification instantanée par PubSubHubbub', + 'websub' => 'Notification instantanée par WebSub', 'show' => array( 'all' => 'Montrer tous les flux', 'error' => 'Montrer seulement les flux en erreur', diff --git a/app/i18n/he/sub.php b/app/i18n/he/sub.php index f467df28c..e4c487b84 100644 --- a/app/i18n/he/sub.php +++ b/app/i18n/he/sub.php @@ -45,7 +45,7 @@ return array( 'main_stream' => 'הצגה בזרם המרכזי', 'normal' => 'Show in its category', //TODO - Translation ), - 'pubsubhubbub' => 'Instant notification with PubSubHubbub', //TODO - Translation + 'websub' => 'Instant notification with WebSub', //TODO - Translation 'show' => array( 'all' => 'Show all feeds', //TODO - Translation 'error' => 'Show only feeds with error', //TODO - Translation diff --git a/app/i18n/it/sub.php b/app/i18n/it/sub.php index cbb488f3e..6faa48d63 100644 --- a/app/i18n/it/sub.php +++ b/app/i18n/it/sub.php @@ -45,7 +45,7 @@ return array( 'main_stream' => 'Mostra in homepage', //TODO - Translation 'normal' => 'Show in its category', //TODO - Translation ), - 'pubsubhubbub' => 'Notifica istantanea con PubSubHubbub', + 'websub' => 'Notifica istantanea con WebSub', 'show' => array( 'all' => 'Show all feeds', //TODO - Translation 'error' => 'Show only feeds with error', //TODO - Translation diff --git a/app/i18n/kr/sub.php b/app/i18n/kr/sub.php index 785a3ea96..463496c57 100644 --- a/app/i18n/kr/sub.php +++ b/app/i18n/kr/sub.php @@ -45,7 +45,7 @@ return array( 'main_stream' => '메인 스트림에 표시하기', 'normal' => '피드가 속한 카테고리에만 표시하기', ), - 'pubsubhubbub' => 'PubSubHubbub을 사용한 즉시 알림', + 'websub' => 'WebSub을 사용한 즉시 알림', 'show' => array( 'all' => 'Show all feeds', //TODO - Translation 'error' => 'Show only feeds with error', //TODO - Translation diff --git a/app/i18n/nl/sub.php b/app/i18n/nl/sub.php index 53954ad3f..36c96b53f 100644 --- a/app/i18n/nl/sub.php +++ b/app/i18n/nl/sub.php @@ -45,7 +45,7 @@ return array( 'main_stream' => 'Zichtbaar in het overzicht', 'normal' => 'Toon in categorie', ), - 'pubsubhubbub' => 'Directe notificaties met PubSubHubbub', + 'websub' => 'Directe notificaties met WebSub', 'show' => array( 'all' => 'Show all feeds', //TODO - Translation 'error' => 'Show only feeds with error', //TODO - Translation diff --git a/app/i18n/oc/sub.php b/app/i18n/oc/sub.php index 1d2e0007b..f9ddf339a 100644 --- a/app/i18n/oc/sub.php +++ b/app/i18n/oc/sub.php @@ -44,7 +44,7 @@ return array( 'main_stream' => 'Mostar al flux màger', 'normal' => 'Mostar dins sa categoria', ), - 'pubsubhubbub' => 'Notificaciones instantáneas amb PubSubHubbub', + 'websub' => 'Notificaciones instantáneas amb WebSub', 'show' => array( 'all' => 'Show all feeds', //TODO - Translation 'error' => 'Show only feeds with error', //TODO - Translation diff --git a/app/i18n/pt-br/sub.php b/app/i18n/pt-br/sub.php index 03ae9d014..78684c14c 100644 --- a/app/i18n/pt-br/sub.php +++ b/app/i18n/pt-br/sub.php @@ -45,7 +45,7 @@ return array( 'main_stream' => 'Mostrar na tela principal', 'normal' => 'Show in its category', //TODO - Translation ), - 'pubsubhubbub' => 'Notificação instantânea com PubSubHubbub', + 'websub' => 'Notificação instantânea com WebSub', 'show' => array( 'all' => 'Show all feeds', //TODO - Translation 'error' => 'Show only feeds with error', //TODO - Translation diff --git a/app/i18n/ru/sub.php b/app/i18n/ru/sub.php index ccd3b0020..7de80586b 100644 --- a/app/i18n/ru/sub.php +++ b/app/i18n/ru/sub.php @@ -45,7 +45,7 @@ return array( 'main_stream' => 'Show in main stream', //TODO - Translation 'normal' => 'Show in its category', //TODO - Translation ), - 'pubsubhubbub' => 'Instant notification with PubSubHubbub', //TODO - Translation + 'websub' => 'Instant notification with WebSub', //TODO - Translation 'show' => array( 'all' => 'Show all feeds', //TODO - Translation 'error' => 'Show only feeds with error', //TODO - Translation diff --git a/app/i18n/tr/sub.php b/app/i18n/tr/sub.php index 4d71e3dbf..b5b56f4b8 100644 --- a/app/i18n/tr/sub.php +++ b/app/i18n/tr/sub.php @@ -45,7 +45,7 @@ return array( 'main_stream' => 'Ana akışda göster', 'normal' => 'Show in its category', //TODO - Translation ), - 'pubsubhubbub' => 'PubSubHubbub ile anlık bildirim', + 'websub' => 'WebSub ile anlık bildirim', 'show' => array( 'all' => 'Show all feeds', //TODO - Translation 'error' => 'Show only feeds with error', //TODO - Translation diff --git a/app/i18n/zh-cn/sub.php b/app/i18n/zh-cn/sub.php index 49b6e4304..e1c176bc6 100644 --- a/app/i18n/zh-cn/sub.php +++ b/app/i18n/zh-cn/sub.php @@ -45,7 +45,7 @@ return array( 'main_stream' => '在首页中显示', 'normal' => '在分类中显示', ), - 'pubsubhubbub' => 'PubSubHubbub 即时通知', + 'websub' => 'WebSub 即时通知', 'show' => array( 'all' => 'Show all feeds', //TODO - Translation 'error' => 'Show only feeds with error', //TODO - Translation diff --git a/app/views/helpers/feed/update.phtml b/app/views/helpers/feed/update.phtml index 4dbaacd04..bc90ba456 100644 --- a/app/views/helpers/feed/update.phtml +++ b/app/views/helpers/feed/update.phtml @@ -133,7 +133,7 @@
- +