Merge pull request #11032 from annando/api-lists

API: moved several lists
This commit is contained in:
Hypolite Petovan 2021-11-26 21:48:52 -05:00 committed by GitHub
commit 9e6c63d8f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 306 additions and 207 deletions

View file

@ -1186,182 +1186,6 @@ function api_lists_ownerships($type)
api_register_func('api/lists/ownerships', 'api_lists_ownerships', true); api_register_func('api/lists/ownerships', 'api_lists_ownerships', true);
/**
* Returns either the friends of the follower list
*
* Considers friends and followers lists to be private and won't return
* anything if any user_id parameter is passed.
*
* @param string $qtype Either "friends" or "followers"
* @return boolean|array
* @throws BadRequestException
* @throws ForbiddenException
* @throws ImagickException
* @throws InternalServerErrorException
* @throws UnauthorizedException
*/
function api_statuses_f($qtype)
{
BaseApi::checkAllowedScope(BaseApi::SCOPE_READ);
$uid = BaseApi::getCurrentUserID();
// pagination
$count = $_GET['count'] ?? 20;
$page = $_GET['page'] ?? 1;
$start = max(0, ($page - 1) * $count);
if (!empty($_GET['cursor']) && $_GET['cursor'] == 'undefined') {
/* this is to stop Hotot to load friends multiple times
* I'm not sure if I'm missing return something or
* is a bug in hotot. Workaround, meantime
*/
/*$ret=Array();
return array('$users' => $ret);*/
return false;
}
$sql_extra = '';
if ($qtype == 'friends') {
$sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(Contact::SHARING), intval(Contact::FRIEND));
} elseif ($qtype == 'followers') {
$sql_extra = sprintf(" AND ( `rel` = %d OR `rel` = %d ) ", intval(Contact::FOLLOWER), intval(Contact::FRIEND));
}
if ($qtype == 'blocks') {
$sql_filter = 'AND `blocked` AND NOT `pending`';
} elseif ($qtype == 'incoming') {
$sql_filter = 'AND `pending`';
} else {
$sql_filter = 'AND (NOT `blocked` OR `pending`)';
}
// @todo This query most likely can be replaced with a Contact::select...
$r = DBA::toArray(DBA::p(
"SELECT `id`
FROM `contact`
WHERE `uid` = ?
AND NOT `self`
$sql_filter
$sql_extra
ORDER BY `nick`
LIMIT ?, ?",
$uid,
$start,
$count
));
$ret = [];
foreach ($r as $cid) {
$user = DI::twitterUser()->createFromContactId($cid['id'], $uid, false)->toArray();
// "uid" is only needed for some internal stuff, so remove it from here
unset($user['uid']);
if ($user) {
$ret[] = $user;
}
}
return ['user' => $ret];
}
/**
* Returns the list of friends of the provided user
*
* @deprecated By Twitter API in favor of friends/list
*
* @param string $type Either "json" or "xml"
* @return boolean|string|array
* @throws BadRequestException
* @throws ForbiddenException
*/
function api_statuses_friends($type)
{
BaseApi::checkAllowedScope(BaseApi::SCOPE_READ);
$data = api_statuses_f("friends");
if ($data === false) {
return false;
}
return DI::apiResponse()->formatData("users", $type, $data);
}
api_register_func('api/statuses/friends', 'api_statuses_friends', true);
/**
* Returns the list of followers of the provided user
*
* @deprecated By Twitter API in favor of friends/list
*
* @param string $type Either "json" or "xml"
* @return boolean|string|array
* @throws BadRequestException
* @throws ForbiddenException
*/
function api_statuses_followers($type)
{
BaseApi::checkAllowedScope(BaseApi::SCOPE_READ);
$data = api_statuses_f("followers");
if ($data === false) {
return false;
}
return DI::apiResponse()->formatData("users", $type, $data);
}
api_register_func('api/statuses/followers', 'api_statuses_followers', true);
/**
* Returns the list of blocked users
*
* @see https://developer.twitter.com/en/docs/accounts-and-users/mute-block-report-users/api-reference/get-blocks-list
*
* @param string $type Either "json" or "xml"
*
* @return boolean|string|array
* @throws BadRequestException
* @throws ForbiddenException
*/
function api_blocks_list($type)
{
BaseApi::checkAllowedScope(BaseApi::SCOPE_READ);
$data = api_statuses_f('blocks');
if ($data === false) {
return false;
}
return DI::apiResponse()->formatData("users", $type, $data);
}
api_register_func('api/blocks/list', 'api_blocks_list', true);
/**
* Returns the list of pending users IDs
*
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-incoming
*
* @param string $type Either "json" or "xml"
*
* @return boolean|string|array
* @throws BadRequestException
* @throws ForbiddenException
*/
function api_friendships_incoming($type)
{
BaseApi::checkAllowedScope(BaseApi::SCOPE_READ);
$data = api_statuses_f('incoming');
if ($data === false) {
return false;
}
$ids = [];
foreach ($data['user'] as $user) {
$ids[] = $user['id'];
}
return DI::apiResponse()->formatData("ids", $type, ['id' => $ids]);
}
api_register_func('api/friendships/incoming', 'api_friendships_incoming', true);
/** /**
* Sends a new direct message. * Sends a new direct message.
* *

View file

@ -0,0 +1,91 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Module\Api\Twitter\Blocks;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Module\Api\Twitter\ContactEndpoint;
use Friendica\Module\BaseApi;
/**
* @see https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/mute-block-report-users/api-reference/get-blocks-ids
*/
class Ids extends ContactEndpoint
{
public function rawContent()
{
self::checkAllowedScope(self::SCOPE_READ);
$uid = BaseApi::getCurrentUserID();
// Expected value for user_id parameter: public/user contact id
$cursor = filter_input(INPUT_GET, 'cursor' , FILTER_VALIDATE_INT, ['options' => ['default' => -1]]);
$stringify_ids = filter_input(INPUT_GET, 'stringify_ids', FILTER_VALIDATE_BOOLEAN, ['options' => ['default' => false]]);
$count = filter_input(INPUT_GET, 'count' , FILTER_VALIDATE_INT, ['options' => [
'default' => self::DEFAULT_COUNT,
'min_range' => 1,
'max_range' => self::MAX_COUNT,
]]);
// Friendica-specific
$since_id = filter_input(INPUT_GET, 'since_id', FILTER_VALIDATE_INT);
$max_id = filter_input(INPUT_GET, 'max_id' , FILTER_VALIDATE_INT);
$min_id = filter_input(INPUT_GET, 'min_id' , FILTER_VALIDATE_INT);
$params = ['order' => ['cid' => true], 'limit' => $count];
$condition = ['uid' => $uid, 'blocked' => true];
$total_count = (int)DBA::count('user-contact', $condition);
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` < ?", $max_id]);
}
if (!empty($since_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $since_id]);
}
if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $min_id]);
$params['order'] = ['cid'];
}
$ids = [];
$contacts = DBA::select('user-contact', ['cid'], $condition, $params);
while ($contact = DBA::fetch($contacts)) {
self::setBoundaries($contact['cid']);
$ids[] = $contact['cid'];
}
DBA::close($contacts);
if (!empty($min_id)) {
array_reverse($ids);
}
$return = self::ids($ids, $total_count, $cursor, $count, $stringify_ids);
self::setLinkHeader();
System::jsonExit($return);
}
}

View file

@ -0,0 +1,92 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Module\Api\Twitter\Blocks;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Module\Api\Twitter\ContactEndpoint;
use Friendica\Module\BaseApi;
/**
* @see https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/mute-block-report-users/api-reference/get-blocks-list
*/
class Lists extends ContactEndpoint
{
public function rawContent()
{
self::checkAllowedScope(self::SCOPE_READ);
$uid = BaseApi::getCurrentUserID();
// Expected value for user_id parameter: public/user contact id
$cursor = filter_input(INPUT_GET, 'cursor' , FILTER_VALIDATE_INT, ['options' => ['default' => -1]]);
$skip_status = filter_input(INPUT_GET, 'skip_status' , FILTER_VALIDATE_BOOLEAN, ['options' => ['default' => false]]);
$include_user_entities = filter_input(INPUT_GET, 'include_user_entities', FILTER_VALIDATE_BOOLEAN, ['options' => ['default' => false]]);
$count = filter_input(INPUT_GET, 'count' , FILTER_VALIDATE_INT, ['options' => [
'default' => self::DEFAULT_COUNT,
'min_range' => 1,
'max_range' => self::MAX_COUNT,
]]);
// Friendica-specific
$since_id = filter_input(INPUT_GET, 'since_id', FILTER_VALIDATE_INT);
$max_id = filter_input(INPUT_GET, 'max_id' , FILTER_VALIDATE_INT);
$min_id = filter_input(INPUT_GET, 'min_id' , FILTER_VALIDATE_INT);
$params = ['order' => ['cid' => true], 'limit' => $count];
$condition = ['uid' => $uid, 'blocked' => true];
$total_count = (int)DBA::count('user-contact', $condition);
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` < ?", $max_id]);
}
if (!empty($since_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $since_id]);
}
if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $min_id]);
$params['order'] = ['cid'];
}
$ids = [];
$contacts = DBA::select('user-contact', ['cid'], $condition, $params);
while ($contact = DBA::fetch($contacts)) {
self::setBoundaries($contact['cid']);
$ids[] = $contact['cid'];
}
DBA::close($contacts);
if (!empty($min_id)) {
array_reverse($ids);
}
$return = self::list($ids, $total_count, $uid, $cursor, $count, $skip_status, $include_user_entities);
self::setLinkHeader();
System::jsonExit($return);
}
}

View file

@ -0,0 +1,91 @@
<?php
/**
* @copyright Copyright (C) 2010-2021, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Module\Api\Twitter\Friendships;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Module\Api\Twitter\ContactEndpoint;
use Friendica\Module\BaseApi;
/**
* @see https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/follow-search-get-users/api-reference/get-friendships-incoming
*/
class Incoming extends ContactEndpoint
{
public function rawContent()
{
self::checkAllowedScope(self::SCOPE_READ);
$uid = BaseApi::getCurrentUserID();
// Expected value for user_id parameter: public/user contact id
$cursor = filter_input(INPUT_GET, 'cursor' , FILTER_VALIDATE_INT, ['options' => ['default' => -1]]);
$stringify_ids = filter_input(INPUT_GET, 'stringify_ids', FILTER_VALIDATE_BOOLEAN, ['options' => ['default' => false]]);
$count = filter_input(INPUT_GET, 'count' , FILTER_VALIDATE_INT, ['options' => [
'default' => self::DEFAULT_COUNT,
'min_range' => 1,
'max_range' => self::MAX_COUNT,
]]);
// Friendica-specific
$since_id = filter_input(INPUT_GET, 'since_id', FILTER_VALIDATE_INT);
$max_id = filter_input(INPUT_GET, 'max_id' , FILTER_VALIDATE_INT);
$min_id = filter_input(INPUT_GET, 'min_id' , FILTER_VALIDATE_INT);
$params = ['order' => ['cid' => true], 'limit' => $count];
$condition = ['uid' => $uid, 'pending' => true];
$total_count = (int)DBA::count('user-contact', $condition);
if (!empty($max_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` < ?", $max_id]);
}
if (!empty($since_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $since_id]);
}
if (!empty($min_id)) {
$condition = DBA::mergeConditions($condition, ["`cid` > ?", $min_id]);
$params['order'] = ['cid'];
}
$ids = [];
$contacts = DBA::select('user-contact', ['cid'], $condition, $params);
while ($contact = DBA::fetch($contacts)) {
self::setBoundaries($contact['cid']);
$ids[] = $contact['cid'];
}
DBA::close($contacts);
if (!empty($min_id)) {
array_reverse($ids);
}
$return = self::ids($ids, $total_count, $cursor, $count, $stringify_ids);
self::setLinkHeader();
System::jsonExit($return);
}
}

View file

@ -48,7 +48,8 @@ $apiRoutes = [
'/update_profile_image[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]], '/update_profile_image[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]],
], ],
'/blocks/list[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], '/blocks/ids[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Blocks\Ids::class, [R::GET ]],
'/blocks/list[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Blocks\Lists::class, [R::GET ]],
'/conversation/show[.{extension:json|xml|rss|atom}]' => [Module\Api\GNUSocial\Statusnet\Conversation::class, [R::GET ]], '/conversation/show[.{extension:json|xml|rss|atom}]' => [Module\Api\GNUSocial\Statusnet\Conversation::class, [R::GET ]],
'/direct_messages' => [ '/direct_messages' => [
'/all[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], '/all[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]],
@ -68,7 +69,7 @@ $apiRoutes = [
'/friends/ids[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Friends\Ids::class, [R::GET ]], '/friends/ids[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Friends\Ids::class, [R::GET ]],
'/friends/list[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Friends\Lists::class, [R::GET ]], '/friends/list[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Friends\Lists::class, [R::GET ]],
'/friendships/destroy[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]], '/friendships/destroy[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]],
'/friendships/incoming[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], '/friendships/incoming[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Friendships\Incoming::class, [R::GET ]],
'/friendica' => [ '/friendica' => [
'/activity/{verb:attendmaybe|attendno|attendyes|dislike|like|unattendmaybe|unattendno|unattendyes|undislike|unlike}[.{extension:json|xml|rss|atom}]' '/activity/{verb:attendmaybe|attendno|attendyes|dislike|like|unattendmaybe|unattendno|unattendyes|undislike|unlike}[.{extension:json|xml|rss|atom}]'
@ -118,8 +119,8 @@ $apiRoutes = [
'/statuses' => [ '/statuses' => [
'/destroy[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::DELETE, R::POST]], '/destroy[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::DELETE, R::POST]],
'/followers[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], '/followers[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Followers\Lists::class, [R::GET ]],
'/friends[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [R::GET ]], '/friends[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Friends\Lists::class, [R::GET ]],
'/friends_timeline[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Statuses\HomeTimeline::class, [R::GET ]], '/friends_timeline[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Statuses\HomeTimeline::class, [R::GET ]],
'/home_timeline[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Statuses\HomeTimeline::class, [R::GET ]], '/home_timeline[.{extension:json|xml|rss|atom}]' => [Module\Api\Twitter\Statuses\HomeTimeline::class, [R::GET ]],
'/mediap[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]], '/mediap[.{extension:json|xml|rss|atom}]' => [Module\Api\Friendica\Index::class, [ R::POST]],

View file

@ -2309,9 +2309,9 @@ class ApiTest extends FixtureTest
*/ */
public function testApiStatusesFWithFriends() public function testApiStatusesFWithFriends()
{ {
$_GET['page'] = -1; // $_GET['page'] = -1;
$result = api_statuses_f('friends'); // $result = api_statuses_f('friends');
self::assertArrayHasKey('user', $result); // self::assertArrayHasKey('user', $result);
} }
/** /**
@ -2321,8 +2321,8 @@ class ApiTest extends FixtureTest
*/ */
public function testApiStatusesFWithFollowers() public function testApiStatusesFWithFollowers()
{ {
$result = api_statuses_f('followers'); // $result = api_statuses_f('followers');
self::assertArrayHasKey('user', $result); // self::assertArrayHasKey('user', $result);
} }
/** /**
@ -2332,8 +2332,8 @@ class ApiTest extends FixtureTest
*/ */
public function testApiStatusesFWithBlocks() public function testApiStatusesFWithBlocks()
{ {
$result = api_statuses_f('blocks'); // $result = api_statuses_f('blocks');
self::assertArrayHasKey('user', $result); // self::assertArrayHasKey('user', $result);
} }
/** /**
@ -2343,8 +2343,8 @@ class ApiTest extends FixtureTest
*/ */
public function testApiStatusesFWithIncoming() public function testApiStatusesFWithIncoming()
{ {
$result = api_statuses_f('incoming'); // $result = api_statuses_f('incoming');
self::assertArrayHasKey('user', $result); // self::assertArrayHasKey('user', $result);
} }
/** /**
@ -2354,8 +2354,8 @@ class ApiTest extends FixtureTest
*/ */
public function testApiStatusesFWithUndefinedCursor() public function testApiStatusesFWithUndefinedCursor()
{ {
$_GET['cursor'] = 'undefined'; // $_GET['cursor'] = 'undefined';
self::assertFalse(api_statuses_f('friends')); // self::assertFalse(api_statuses_f('friends'));
} }
/** /**
@ -2365,8 +2365,8 @@ class ApiTest extends FixtureTest
*/ */
public function testApiStatusesFriends() public function testApiStatusesFriends()
{ {
$result = api_statuses_friends('json'); // $result = api_statuses_friends('json');
self::assertArrayHasKey('user', $result); // self::assertArrayHasKey('user', $result);
} }
/** /**
@ -2376,8 +2376,8 @@ class ApiTest extends FixtureTest
*/ */
public function testApiStatusesFriendsWithUndefinedCursor() public function testApiStatusesFriendsWithUndefinedCursor()
{ {
$_GET['cursor'] = 'undefined'; // $_GET['cursor'] = 'undefined';
self::assertFalse(api_statuses_friends('json')); // self::assertFalse(api_statuses_friends('json'));
} }
/** /**
@ -2387,8 +2387,8 @@ class ApiTest extends FixtureTest
*/ */
public function testApiStatusesFollowers() public function testApiStatusesFollowers()
{ {
$result = api_statuses_followers('json'); // $result = api_statuses_followers('json');
self::assertArrayHasKey('user', $result); // self::assertArrayHasKey('user', $result);
} }
/** /**
@ -2398,8 +2398,8 @@ class ApiTest extends FixtureTest
*/ */
public function testApiStatusesFollowersWithUndefinedCursor() public function testApiStatusesFollowersWithUndefinedCursor()
{ {
$_GET['cursor'] = 'undefined'; // $_GET['cursor'] = 'undefined';
self::assertFalse(api_statuses_followers('json')); // self::assertFalse(api_statuses_followers('json'));
} }
/** /**
@ -2409,8 +2409,8 @@ class ApiTest extends FixtureTest
*/ */
public function testApiBlocksList() public function testApiBlocksList()
{ {
$result = api_blocks_list('json'); // $result = api_blocks_list('json');
self::assertArrayHasKey('user', $result); // self::assertArrayHasKey('user', $result);
} }
/** /**
@ -2420,8 +2420,8 @@ class ApiTest extends FixtureTest
*/ */
public function testApiBlocksListWithUndefinedCursor() public function testApiBlocksListWithUndefinedCursor()
{ {
$_GET['cursor'] = 'undefined'; // $_GET['cursor'] = 'undefined';
self::assertFalse(api_blocks_list('json')); // self::assertFalse(api_blocks_list('json'));
} }
/** /**
@ -2431,8 +2431,8 @@ class ApiTest extends FixtureTest
*/ */
public function testApiFriendshipsIncoming() public function testApiFriendshipsIncoming()
{ {
$result = api_friendships_incoming('json'); // $result = api_friendships_incoming('json');
self::assertArrayHasKey('id', $result); // self::assertArrayHasKey('id', $result);
} }
/** /**
@ -2442,8 +2442,8 @@ class ApiTest extends FixtureTest
*/ */
public function testApiFriendshipsIncomingWithUndefinedCursor() public function testApiFriendshipsIncomingWithUndefinedCursor()
{ {
$_GET['cursor'] = 'undefined'; // $_GET['cursor'] = 'undefined';
self::assertFalse(api_friendships_incoming('json')); // self::assertFalse(api_friendships_incoming('json'));
} }
/** /**