Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API /reader/api/0/stream/items/contents #1774

Merged
merged 10 commits into from Feb 8, 2018
@@ -781,6 +781,23 @@ public function listWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_
return self::daoToEntries($stm->fetchAll(PDO::FETCH_ASSOC));
}
public function listByIds($ids, $order = 'DESC') {
if (count($ids) < 1) {
return array();
}
$sql = 'SELECT id, guid, title, author, '
. ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content')
. ', link, date, is_read, is_favorite, id_feed, tags '
. 'FROM `' . $this->prefix . 'entry` '
. 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?) '
. 'ORDER BY id ' . $order;
$stm = $this->bd->prepare($sql);
$stm->execute($ids);
return self::daoToEntries($stm->fetchAll(PDO::FETCH_ASSOC));
}
public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) { //For API
list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min);
@@ -216,9 +216,13 @@ function token($conf) {
function checkToken($conf, $token) {
//http://code.google.com/p/google-reader-api/wiki/ActionToken
$user = Minz_Session::param('currentUser', '_');
if ($user !== '_' && $token == '') {

This comment has been minimized.

Copy link
@Frenzie

Frenzie Feb 7, 2018

Member

== or ===?

This comment has been minimized.

Copy link
@Alkarex

Alkarex Feb 7, 2018

Author Member

Strict for the first comparison, loose for the second (match empty string and 0 in particular)

return true; //FeedMe //TODO: Check security consequences
}
if ($token === str_pad(sha1(FreshRSS_Context::$system_conf->salt . $user . $conf->apiPasswordHash), 57, 'Z')) {
return true;
}
Minz_Log::warning('Invalid POST token: ' . $token, API_LOG);
unauthorized();
}
@@ -324,6 +328,9 @@ function subscriptionEdit($streamNames, $titles, $action, $add = '', $remove = '
$addCatId = 1; //Default category
}
$feedDAO = FreshRSS_Factory::createFeedDao();
if (!is_array($streamNames) || count($streamNames) < 1) {
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) {
@@ -435,6 +442,51 @@ function unreadCount() { //http://blog.martindoms.com/2009/10/16/using-the-googl
exit();
}
function entriesToArray($entries) {
$items = array();
foreach ($entries as $entry) {
$f_id = $entry->feed();
if (isset($arrayFeedCategoryNames[$f_id])) {
$c_name = $arrayFeedCategoryNames[$f_id]['c_name'];
$f_name = $arrayFeedCategoryNames[$f_id]['name'];
} else {
$c_name = '_';
$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
'crawlTimeMsec' => substr($entry->id(), 0, -3),
'timestampUsec' => '' . $entry->id(), //EasyRSS
'published' => $entry->date(true),
'title' => $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,
),
'origin' => array(
'streamId' => 'feed/' . $f_id,
'title' => $f_name, //EasyRSS
//'htmlUrl' => $line['f_website'],
),
);
if ($entry->author() != '') {
$item['author'] = $entry->author();
}
if ($entry->isRead()) {
$item['categories'][] = 'user/-/state/com.google/read';
}
if ($entry->isFavorite()) {
$item['categories'][] = 'user/-/state/com.google/starred';
}
$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
@@ -476,73 +528,37 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex
break;
}
if (!empty($continuation)) {
if ($continuation != '') {

This comment has been minimized.

Copy link
@Frenzie

Frenzie Feb 7, 2018

Member

So we're fine if it's FALSE or 0? Also, is $continuation trimmed anywhere or could you end up with something empty-adjacent like a space?

Also, like above, != or !==?

This comment has been minimized.

Copy link
@Alkarex

Alkarex Feb 7, 2018

Author Member

Yes, loose comparison there. The test should reject empty string, false, 0, null, etc. and only keep substantial strings.
But good point with sanitising those parameters. Will do so.

This comment has been minimized.

Copy link
@Frenzie

Frenzie Feb 7, 2018

Member

Ah, so != something falsy (loose) basically means exactly the same thing as empty()? I know that PHP plays a bit fast and loose with things like 1 and 0 vs true and false, but I didn't realize empty strings were included in being falsey. I thought any string was truthy. Apparently the string '0' is also falsey.

Confusingly, on that last note, 0.0 (float) is falsey and '0.0' (string) is truthy.

This comment has been minimized.

Copy link
@Alkarex

Alkarex Feb 7, 2018

Author Member

Yes indeed. empty() is mostly relevant when working with potentially non-existing entries such as in a array, and it is similar to doing !isset($t[1]) || $t[1] == ''. In the case where we have a variable and not an index, there is little point of using empty() and it used to be slower (I am not sure whether there is any difference nowadays).

This comment has been minimized.

Copy link
@Frenzie

Frenzie Feb 7, 2018

Member

I find empty() slightly clearer in the sense that I don't necessarily think of an empty string as falsey while I do think of it as empty, but that's just my problem I suppose. :-P

Python is much more consistent in counting empty arrays, empty strings, etc. as falsey than the likes of JavaScript or PHP. But in Ruby, for example, empty strings are truthy, and in a sense they are in Lua too. In POSIX and related shells you also have to be well aware to use checks like -z (unset or empty) and -n (set and non-empty).

Incidentally, can we get this kind of fun out of PHP?

bla = '0'; if (bla && bla == false) console.log('/sigh')
/sigh

It's truthy (non-empty string) but at the same time '0' == false. Perhaps even more confusingly, you can get the same behavior out of new Boolean(false) (the existence of any object is inherently truthy in JS, but its value == false).

This comment has been minimized.

Copy link
@Alkarex

Alkarex Feb 7, 2018

Author Member

I do not think there is anything in PHP that can evaluate positively $bla && $bla == false

This comment has been minimized.

Copy link
@Frenzie

Frenzie Feb 7, 2018

Member

I didn't mean that specific scenario, just if there's something similarly crazy lurking in PHP that I don't know about. ;-)

This comment has been minimized.

This comment has been minimized.

Copy link
@Frenzie

Frenzie Feb 7, 2018

Member

Fun, but no PHP. Well, at least I got the last one right after the hint. I didn't really dare think not a number at first because that just seemed too logical. xD

This comment has been minimized.

Copy link
@Alkarex

Alkarex Feb 7, 2018

Author Member

All right, I have one for you. Guess the output :-P

<?php
echo ctype_digit(1234) ? 'true' : 'false', "\n";
echo ctype_digit(123) ? 'true' : 'false', "\n";
echo ctype_digit('123') ? 'true' : 'false', "\n";

This comment has been minimized.

Copy link
@Frenzie

Frenzie Feb 7, 2018

Member

My intuitive guess would be false false true but I'm guessing there's a catch. Now to check… ;-)

Edit: hm, true false true. Well then…

If an integer between -128 and 255 inclusive is provided, it is interpreted as the ASCII value of a single character (negative values have 256 added in order to allow characters in the Extended ASCII range). Any other integer is interpreted as a string containing the decimal digits of the integer.

This comment has been minimized.

Copy link
@Alkarex

Alkarex Feb 7, 2018

Author Member

Close enough: true false true. :-)

If an integer between -128 and 255 inclusive is provided, it is interpreted as the ASCII value of a single character (negative values have 256 added in order to allow characters in the Extended ASCII range). Any other integer is interpreted as a string containing the decimal digits of the integer.

$count++; //Shift by one element
}
$entryDAO = FreshRSS_Factory::createEntryDao();
$entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, new FreshRSS_Search(''), $start_time);
$items = array();
foreach ($entries as $entry) {
$f_id = $entry->feed();
if (isset($arrayFeedCategoryNames[$f_id])) {
$c_name = $arrayFeedCategoryNames[$f_id]['c_name'];
$f_name = $arrayFeedCategoryNames[$f_id]['name'];
} else {
$c_name = '_';
$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
'crawlTimeMsec' => substr($entry->id(), 0, -3),
'timestampUsec' => '' . $entry->id(), //EasyRSS
'published' => $entry->date(true),
'title' => $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,
),
'origin' => array(
'streamId' => 'feed/' . $f_id,
'title' => $f_name, //EasyRSS
//'htmlUrl' => $line['f_website'],
),
);
if ($entry->author() != '') {
$item['author'] = $entry->author();
}
if ($entry->isRead()) {
$item['categories'][] = 'user/-/state/com.google/read';
}
if ($entry->isFavorite()) {
$item['categories'][] = 'user/-/state/com.google/starred';
}
$items[] = $item;
}
$items = entriesToArray($entries);
if (!empty($continuation)) {
if ($continuation != '') {
array_shift($items); //Discard first element that was already sent in the previous response
$count--;
}
$response = array(
'id' => 'user/-/state/com.google/reading-list',
'updated' => time(),
'items' => $items,
);
if ((count($entries) >= $count) && (!empty($entry))) {
$response['continuation'] = $entry->id();
if (count($entries) >= $count) {
$entry = end($entries);
if ($entry != false) {
$response['continuation'] = $entry->id();
}
}
echo json_encode($response), "\n";
exit();
}
function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target) {
function streamContentsItemsIds($streamId, $start_time, $count, $order, $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
@@ -572,8 +588,17 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude
break;
}
if ($continuation != '') {
$count++; //Shift by one element
}
$entryDAO = FreshRSS_Factory::createEntryDao();
$ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, '', new FreshRSS_Search(''), $start_time);
$ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, new FreshRSS_Search(''), $start_time);
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;
@@ -585,9 +610,39 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude
);
}
echo json_encode(array(
$response = array(
'itemRefs' => $itemRefs,
)), "\n";
);
if (count($ids) >= $count) {
$id = end($ids);
if ($id != false) {
$response['continuation'] = $id;
}
}
echo json_encode($response), "\n";
exit();
}
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/'
}
$entryDAO = FreshRSS_Factory::createEntryDao();
$entries = $entryDAO->listByIds($e_ids, $order === 'o' ? 'ASC' : 'DESC');
$items = entriesToArray($entries);
$response = array(
'id' => 'user/-/state/com.google/reading-list',
'updated' => time(),
'items' => $items,
);
echo json_encode($response), "\n";
exit();
}
@@ -755,7 +810,10 @@ function markAllAsRead($streamId, $olderThanId) {
* 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);
streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude_target, $continuation);
} else if ($pathInfos[6] === 'contents' && isset($_POST['i'])) { //FeedMe
$e_ids = multiplePosts('i'); //item IDs
streamContentsItems($e_ids, $order);
}
}
break;
@@ -775,16 +833,16 @@ function markAllAsRead($streamId, $olderThanId) {
subscriptionList($_GET['output']);
break;
case 'edit':
if (isset($_POST['s']) && isset($_POST['ac'])) {
if (isset($_REQUEST['s']) && isset($_REQUEST['ac'])) {
//StreamId to operate on. The parameter may be repeated to edit multiple subscriptions at once
$streamNames = multiplePosts('s');
$streamNames = empty($_POST['s']) && isset($_GET['s']) ? array($_GET['s']) : multiplePosts('s');
/* Title to use for the subscription. For the `subscribe` action,
* if not specified then the feed's current title will be used. Can
* be used with the `edit` action to rename a subscription */
$titles = multiplePosts('t');
$action = $_POST['ac']; //Action to perform on the given StreamId. Possible values are `subscribe`, `unsubscribe` and `edit`
$add = isset($_POST['a']) ? $_POST['a'] : ''; //StreamId to add the subscription to (generally a user label)
$remove = isset($_POST['r']) ? $_POST['r'] : ''; //StreamId to remove the subscription from (generally a user label)
$titles = empty($_POST['t']) && isset($_GET['t']) ? array($_GET['t']) : multiplePosts('t');
$action = $_REQUEST['ac']; //Action to perform on the given StreamId. Possible values are `subscribe`, `unsubscribe` and `edit`
$add = isset($_REQUEST['a']) ? $_REQUEST['a'] : ''; //StreamId to add the subscription to (generally a user label)
$remove = isset($_REQUEST['r']) ? $_REQUEST['r'] : ''; //StreamId to remove the subscription from (generally a user label)
subscriptionEdit($streamNames, $titles, $action, $add, $remove);
}
break;
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.