]> git.mxchange.org Git - friendica.git/blobdiff - include/api.php
Throw NotFoundException if results are empty in api_users_lookup
[friendica.git] / include / api.php
index 50aa18c7e0ab2e64197dbfacdfbf08ec4a45c3be..6201b460a3c162e6ce96d68ea9660740568a35ca 100644 (file)
@@ -5,25 +5,36 @@
  *
  * @todo Automatically detect if incoming data is HTML or BBCode
  */
-
 use Friendica\App;
+use Friendica\Content\Feature;
 use Friendica\Core\System;
 use Friendica\Core\Config;
 use Friendica\Core\NotificationsManager;
 use Friendica\Core\Worker;
 use Friendica\Database\DBM;
-use Friendica\Object\Contact;
+use Friendica\Model\Contact;
+use Friendica\Model\Group;
+use Friendica\Model\Photo;
+use Friendica\Model\User;
+use Friendica\Network\FKOAuth1;
+use Friendica\Network\HTTPException;
+use Friendica\Network\HTTPException\BadRequestException;
+use Friendica\Network\HTTPException\ForbiddenException;
+use Friendica\Network\HTTPException\InternalServerErrorException;
+use Friendica\Network\HTTPException\MethodNotAllowedException;
+use Friendica\Network\HTTPException\NotFoundException;
+use Friendica\Network\HTTPException\NotImplementedException;
+use Friendica\Network\HTTPException\UnauthorizedException;
+use Friendica\Network\HTTPException\TooManyRequestsException;
+use Friendica\Object\Image;
 use Friendica\Protocol\Diaspora;
 use Friendica\Util\XML;
 
-require_once 'include/HTTPExceptions.php';
 require_once 'include/bbcode.php';
 require_once 'include/datetime.php';
 require_once 'include/conversation.php';
-require_once 'include/oauth.php';
 require_once 'include/html2plain.php';
 require_once 'mod/share.php';
-require_once 'include/Photo.php';
 require_once 'mod/item.php';
 require_once 'include/security.php';
 require_once 'include/contact_selectors.php';
@@ -31,7 +42,6 @@ require_once 'include/html2bbcode.php';
 require_once 'mod/wall_upload.php';
 require_once 'mod/proxy.php';
 require_once 'include/message.php';
-require_once 'include/group.php';
 require_once 'include/like.php';
 require_once 'include/plaintext.php';
 
@@ -148,12 +158,12 @@ function api_register_func($path, $func, $auth = false, $method = API_METHOD_ANY
  */
 function api_login(App $a)
 {
+       $oauth1 = new FKOAuth1();
        // login with oauth
        try {
-               $oauth = new FKOAuth1();
-               list($consumer,$token) = $oauth->verify_request(OAuthRequest::from_request());
+               list($consumer, $token) = $oauth1->verify_request(OAuthRequest::from_request());
                if (!is_null($token)) {
-                       $oauth->loginUser($token->uid);
+                       $oauth1->loginUser($token->uid);
                        call_hooks('logged_in', $a->user);
                        return;
                }
@@ -182,7 +192,6 @@ function api_login(App $a)
 
        $user = $_SERVER['PHP_AUTH_USER'];
        $password = $_SERVER['PHP_AUTH_PW'];
-       $encrypted = hash('whirlpool', trim($password));
 
        // allow "user@server" login (but ignore 'server' part)
        $at = strstr($user, "@", true);
@@ -210,16 +219,9 @@ function api_login(App $a)
        if (($addon_auth['authenticated']) && (count($addon_auth['user_record']))) {
                $record = $addon_auth['user_record'];
        } else {
-               // process normal login request
-               $r = q(
-                       "SELECT * FROM `user` WHERE (`email` = '%s' OR `nickname` = '%s')
-                       AND `password` = '%s' AND NOT `blocked` AND NOT `account_expired` AND NOT `account_removed` AND `verified` LIMIT 1",
-                       dbesc(trim($user)),
-                       dbesc(trim($user)),
-                       dbesc($encrypted)
-               );
-               if (DBM::is_result($r)) {
-                       $record = $r[0];
+               $user_id = User::authenticate(trim($user), trim($password));
+               if ($user_id) {
+                       $record = dba::select('user', [], ['uid' => $user_id], ['limit' => 1]);
                }
        }
 
@@ -468,12 +470,12 @@ function api_rss_extra(App $a, $arr, $user_info)
  * @return bool|string
  *             Contact url or False if contact id is unknown
  */
-function api_unique_id_to_url($id)
+function api_unique_id_to_nurl($id)
 {
-       $r = dba::select('contact', array('url'), array('uid' => 0, 'id' => $id), array('limit' => 1));
+       $r = dba::select('contact', array('nurl'), array('uid' => 0, 'id' => $id), array('limit' => 1));
 
        if (DBM::is_result($r)) {
-               return $r["url"];
+               return $r["nurl"];
        } else {
                return false;
        }
@@ -484,9 +486,8 @@ function api_unique_id_to_url($id)
  *
  * @param object     $a          App
  * @param int|string $contact_id Contact ID or URL
- * @param string     $type       Return type (for errors)
  */
-function api_get_user(App $a, $contact_id = null, $type = "json")
+function api_get_user(App $a, $contact_id = null)
 {
        global $called_api;
 
@@ -509,7 +510,7 @@ function api_get_user(App $a, $contact_id = null, $type = "json")
 
        // Searching for contact id with uid = 0
        if (!is_null($contact_id) && (intval($contact_id) != 0)) {
-               $user = dbesc(api_unique_id_to_url($contact_id));
+               $user = dbesc(api_unique_id_to_nurl($contact_id));
 
                if ($user == "") {
                        throw new BadRequestException("User not found.");
@@ -523,7 +524,7 @@ function api_get_user(App $a, $contact_id = null, $type = "json")
        }
 
        if (is_null($user) && x($_GET, 'user_id')) {
-               $user = dbesc(api_unique_id_to_url($_GET['user_id']));
+               $user = dbesc(api_unique_id_to_nurl($_GET['user_id']));
 
                if ($user == "") {
                        throw new BadRequestException("User not found.");
@@ -557,7 +558,7 @@ function api_get_user(App $a, $contact_id = null, $type = "json")
                $argid = count($called_api);
                list($user, $null) = explode(".", $a->argv[$argid]);
                if (is_numeric($user)) {
-                       $user = dbesc(api_unique_id_to_url($user));
+                       $user = dbesc(api_unique_id_to_nurl($user));
 
                        if ($user == "") {
                                return false;
@@ -1191,7 +1192,7 @@ function api_statuses_update($type)
                        api_user()
                );
                if (DBM::is_result($r)) {
-                       $phototypes = Photo::supportedTypes();
+                       $phototypes = Image::supportedTypes();
                        $ext = $phototypes[$r[0]['type']];
                        $_REQUEST['body'] .= "\n\n" . '[url=' . System::baseUrl() . '/photos/' . $r[0]['nickname'] . '/image/' . $r[0]['resource-id'] . ']';
                        $_REQUEST['body'] .= '[img]' . System::baseUrl() . '/photo/' . $r[0]['resource-id'] . '-' . $r[0]['scale'] . '.' . $ext . '[/img][/url]';
@@ -1323,8 +1324,9 @@ function api_status_show($type)
                        'retweeted' => false,
                        'possibly_sensitive' => false,
                        'lang' => "",
-                       'statusnet_html'                => $converted["html"],
-                       'statusnet_conversation_id'     => $lastwall['parent'],
+                       'statusnet_html' => $converted["html"],
+                       'statusnet_conversation_id' => $lastwall['parent'],
+                       'external_url' => System::baseUrl() . "/display/" . $lastwall['guid'],
                );
 
                if (count($converted["attachments"]) > 0) {
@@ -1412,7 +1414,8 @@ function api_users_show($type)
                        $geo => null,
                        'favorited' => $lastwall['starred'] ? true : false,
                        'statusnet_html' => $converted["html"],
-                       'statusnet_conversation_id'     => $lastwall['parent'],
+                       'statusnet_conversation_id' => $lastwall['parent'],
+                       'external_url' => System::baseUrl() . "/display/" . $lastwall['guid'],
                );
 
                if (count($converted["attachments"]) > 0) {
@@ -1482,6 +1485,98 @@ function api_users_search($type)
 /// @TODO move to top of file or somewhere better
 api_register_func('api/users/search', 'api_users_search');
 
+/**
+ * Return user objects
+ *
+ * @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-lookup
+ *
+ * @param string $type Return format: json or xml
+ *
+ * @return array|string
+ * @throws UnauthorizedException
+ * @throws NotFoundException
+ */
+function api_users_lookup($type)
+{
+       $users = array();
+
+       if (x($_REQUEST['user_id'])) {
+               foreach (explode(',', $_REQUEST['user_id']) as $id) {
+                       if (!empty($id)) {
+                               $users[] = api_get_user(get_app(), $id);
+                       }
+               }
+       }
+
+       if (empty($users)) {
+               throw new NotFoundException;
+       }
+
+       return api_format_data("users", $type, array('users' => $users));
+}
+
+/// @TODO move to top of file or somewhere better
+api_register_func('api/users/lookup', 'api_users_lookup', true);
+
+/**
+ * Returns statuses that match a specified query.
+ *
+ * @see https://developer.twitter.com/en/docs/tweets/search/api-reference/get-search-tweets
+ *
+ * @param string $type Return format: json, xml, atom, rss
+ *
+ * @return array|string
+ * @throws UnauthorizedException
+ * @throws BadRequestException
+ */
+function api_search($type)
+{
+       $data = array();
+
+       if (!x($_REQUEST, 'q')) {
+               throw new BadRequestException("q parameter is required.");
+       }
+
+       if (x($_REQUEST, 'rpp')) {
+               $count = $_REQUEST['rpp'];
+       } elseif (x($_REQUEST, 'count')) {
+               $count = $_REQUEST['count'];
+       } else {
+               $count = 15;
+       }
+
+       $since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0);
+       $max_id = (x($_REQUEST, 'max_id') ? $_REQUEST['max_id'] : 0);
+       $page = (x($_REQUEST, 'page') ? $_REQUEST['page'] - 1 : 0);
+
+       $start = $page * $count;
+
+       if ($max_id > 0) {
+               $sql_extra .= ' AND `item`.`id` <= ' . intval($max_id);
+       }
+
+       $r = dba::p(
+               "SELECT ".item_fieldlists()."
+               FROM `item` ".item_joins()."
+               WHERE ".item_condition()." AND (`item`.`uid` = 0 OR (`item`.`uid` = ? AND NOT `item`.`global`))
+               AND `item`.`body` LIKE CONCAT('%',?,'%')
+               $sql_extra
+               AND `item`.`id`>?
+               ORDER BY `item`.`id` DESC LIMIT ".intval($start)." ,".intval($count)." ",
+               api_user(),
+               $_REQUEST['q'],
+               $since_id
+       );
+
+       $data['status'] = api_format_items(dba::inArray($r), api_get_user(get_app()));
+
+       return api_format_data("statuses", $type, $data);
+}
+
+/// @TODO move to top of file or somewhere better
+api_register_func('api/search/tweets', 'api_search', true);
+api_register_func('api/search', 'api_search', true);
+
 /**
  *
  * http://developer.twitter.com/doc/get/statuses/home_timeline
@@ -1609,40 +1704,131 @@ function api_statuses_public_timeline($type)
 
        $start = $page * $count;
 
-       if ($max_id > 0) {
-               $sql_extra = 'AND `item`.`id` <= ' . intval($max_id);
+       if ($exclude_replies && !$conversation_id) {
+               if ($max_id > 0) {
+                       $sql_extra = 'AND `thread`.`iid` <= ' . intval($max_id);
+               }
+
+               $r = dba::p("SELECT " . item_fieldlists() . "
+                       FROM `thread`
+                       STRAIGHT_JOIN `item` ON `item`.`id` = `thread`.`iid`
+                       " . item_joins() . "
+                       STRAIGHT_JOIN `user` ON `user`.`uid` = `thread`.`uid`
+                               AND NOT `user`.`hidewall`
+                       AND `verb` = ?
+                       AND NOT `thread`.`private`
+                       AND `thread`.`wall`
+                       AND `thread`.`visible`
+                       AND NOT `thread`.`deleted`
+                       AND NOT `thread`.`moderated`
+                       AND `thread`.`iid` > ?
+                       $sql_extra
+                       ORDER BY `thread`.`iid` DESC
+                       LIMIT " . intval($start) . ", " . intval($count),
+                       ACTIVITY_POST,
+                       $since_id
+               );
+
+               $r = dba::inArray($r);
+       } else {
+               if ($max_id > 0) {
+                       $sql_extra = 'AND `item`.`id` <= ' . intval($max_id);
+               }
+               if ($conversation_id > 0) {
+                       $sql_extra .= ' AND `item`.`parent` = ' . intval($conversation_id);
+               }
+
+               $r = dba::p("SELECT " . item_fieldlists() . "
+                       FROM `item`
+                       " . item_joins() . "
+                       STRAIGHT_JOIN `user` ON `user`.`uid` = `item`.`uid`
+                               AND NOT `user`.`hidewall`
+                       AND `verb` = ?
+                       AND NOT `item`.`private`
+                       AND `item`.`wall`
+                       AND `item`.`visible`
+                       AND NOT `item`.`deleted`
+                       AND NOT `item`.`moderated`
+                       AND `item`.`id` > ?
+                       $sql_extra
+                       ORDER BY `item`.`id` DESC
+                       LIMIT " . intval($start) . ", " . intval($count),
+                       ACTIVITY_POST,
+                       $since_id
+               );
+
+               $r = dba::inArray($r);
        }
-       if ($exclude_replies > 0) {
-               $sql_extra .= ' AND `item`.`parent` = `item`.`id`';
+
+       $ret = api_format_items($r, $user_info, false, $type);
+
+       $data = array('status' => $ret);
+       switch ($type) {
+               case "atom":
+               case "rss":
+                       $data = api_rss_extra($a, $data, $user_info);
+                       break;
        }
-       if ($conversation_id > 0) {
-               $sql_extra .= ' AND `item`.`parent` = ' . intval($conversation_id);
+
+       return api_format_data("statuses", $type, $data);
+}
+
+/// @TODO move to top of file or somewhere better
+api_register_func('api/statuses/public_timeline', 'api_statuses_public_timeline', true);
+
+/**
+ * @brief Returns the list of public federated posts this node knows about
+ *
+ * @param string $type Return format: json, xml, atom, rss
+ * @return array|string
+ * @throws ForbiddenException
+ */
+function api_statuses_networkpublic_timeline($type)
+{
+       $a = get_app();
+
+       if (api_user() === false) {
+               throw new ForbiddenException();
        }
 
-       $r = q(
-               "SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`,
-               `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
-               `contact`.`network`, `contact`.`thumb`, `contact`.`self`, `contact`.`writable`,
-               `contact`.`id` AS `cid`,
-               `user`.`nickname`, `user`.`hidewall`
-               FROM `item`
-               STRAIGHT_JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND `contact`.`uid` = `item`.`uid`
-                       AND (NOT `contact`.`blocked` OR `contact`.`pending`)
-               STRAIGHT_JOIN `user` ON `user`.`uid` = `item`.`uid`
-                       AND NOT `user`.`hidewall`
-               WHERE `verb` = '%s' AND `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated`
-               AND `item`.`allow_cid` = ''  AND `item`.`allow_gid` = ''
-               AND `item`.`deny_cid`  = '' AND `item`.`deny_gid`  = ''
-               AND NOT `item`.`private` AND `item`.`wall`
+       $user_info = api_get_user($a);
+
+       $since_id        = x($_REQUEST, 'since_id')        ? $_REQUEST['since_id']        : 0;
+       $max_id          = x($_REQUEST, 'max_id')          ? $_REQUEST['max_id']          : 0;
+
+       // pagination
+       $count = x($_REQUEST, 'count') ? $_REQUEST['count']   : 20;
+       $page  = x($_REQUEST, 'page')  ? $_REQUEST['page']    : 1;
+       if ($page < 1) {
+               $page = 1;
+       }
+       $start = ($page - 1) * $count;
+
+       $sql_extra = '';
+       if ($max_id > 0) {
+               $sql_extra = 'AND `thread`.`iid` <= ' . intval($max_id);
+       }
+
+       $r = dba::p("SELECT " . item_fieldlists() . "
+               FROM `thread`
+               STRAIGHT_JOIN `item` ON `item`.`id` = `thread`.`iid`
+               " . item_joins() . "
+               WHERE `thread`.`uid` = 0
+               AND `verb` = ?
+               AND NOT `thread`.`private`
+               AND `thread`.`visible`
+               AND NOT `thread`.`deleted`
+               AND NOT `thread`.`moderated`
+               AND `thread`.`iid` > ?
                $sql_extra
-               AND `item`.`id`>%d
-               ORDER BY `item`.`id` DESC LIMIT %d, %d ",
-               dbesc(ACTIVITY_POST),
-               intval($since_id),
-               intval($start),
-               intval($count)
+               ORDER BY `thread`.`iid` DESC
+               LIMIT " . intval($start) . ", " . intval($count),
+               ACTIVITY_POST,
+               $since_id
        );
 
+       $r = dba::inArray($r);
+
        $ret = api_format_items($r, $user_info, false, $type);
 
        $data = array('status' => $ret);
@@ -1657,7 +1843,7 @@ function api_statuses_public_timeline($type)
 }
 
 /// @TODO move to top of file or somewhere better
-api_register_func('api/statuses/public_timeline', 'api_statuses_public_timeline', true);
+api_register_func('api/statuses/networkpublic_timeline', 'api_statuses_networkpublic_timeline', true);
 
 /**
  * @TODO nothing to say?
@@ -2017,6 +2203,13 @@ function api_statuses_mentions($type)
 api_register_func('api/statuses/mentions', 'api_statuses_mentions', true);
 api_register_func('api/statuses/replies', 'api_statuses_mentions', true);
 
+/**
+ * @brief Returns a user's public timeline
+ *
+ * @param string $type Either "json" or "xml"
+ * @return string|array
+ * @throws ForbiddenException
+ */
 function api_statuses_user_timeline($type)
 {
        $a = get_app();
@@ -2026,7 +2219,6 @@ function api_statuses_user_timeline($type)
        }
 
        $user_info = api_get_user($a);
-       // get last network messages
 
        logger(
                "api_statuses_user_timeline: api_user: ". api_user() .
@@ -2035,18 +2227,18 @@ function api_statuses_user_timeline($type)
                LOGGER_DEBUG
        );
 
-       // params
-       $count = (x($_REQUEST, 'count') ? $_REQUEST['count'] : 20);
-       $page = (x($_REQUEST, 'page') ? $_REQUEST['page'] -1 : 0);
-       if ($page < 0) {
-               $page = 0;
-       }
-       $since_id = (x($_REQUEST, 'since_id') ? $_REQUEST['since_id'] : 0);
-       //$since_id = 0;//$since_id = (x($_REQUEST, 'since_id')?$_REQUEST['since_id'] : 0);
-       $exclude_replies = (x($_REQUEST, 'exclude_replies') ? 1 : 0);
-       $conversation_id = (x($_REQUEST, 'conversation_id') ? $_REQUEST['conversation_id'] : 0);
+       $since_id        = x($_REQUEST, 'since_id')        ? $_REQUEST['since_id']        : 0;
+       $max_id          = x($_REQUEST, 'max_id')          ? $_REQUEST['max_id']          : 0;
+       $exclude_replies = x($_REQUEST, 'exclude_replies') ? 1                            : 0;
+       $conversation_id = x($_REQUEST, 'conversation_id') ? $_REQUEST['conversation_id'] : 0;
 
-       $start = $page * $count;
+       // pagination
+       $count = x($_REQUEST, 'count') ? $_REQUEST['count'] : 20;
+       $page  = x($_REQUEST, 'page')  ? $_REQUEST['page']  : 1;
+       if ($page < 1) {
+               $page = 1;
+       }
+       $start = ($page - 1) * $count;
 
        $sql_extra = '';
        if ($user_info['self'] == 1) {
@@ -2056,10 +2248,15 @@ function api_statuses_user_timeline($type)
        if ($exclude_replies > 0) {
                $sql_extra .= ' AND `item`.`parent` = `item`.`id`';
        }
+
        if ($conversation_id > 0) {
                $sql_extra .= ' AND `item`.`parent` = ' . intval($conversation_id);
        }
 
+       if ($max_id > 0) {
+               $sql_extra .= ' AND `item`.`id` <= ' . intval($max_id);
+       }
+
        $r = q(
                "SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`,
                `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`,
@@ -2072,7 +2269,7 @@ function api_statuses_user_timeline($type)
                AND `item`.`contact-id` = %d
                AND `item`.`visible` AND NOT `item`.`moderated` AND NOT `item`.`deleted`
                $sql_extra
-               AND `item`.`id`>%d
+               AND `item`.`id` > %d
                ORDER BY `item`.`id` DESC LIMIT %d ,%d ",
                intval(api_user()),
                dbesc(ACTIVITY_POST),
@@ -2376,7 +2573,7 @@ function api_get_attachments(&$body)
        $attachments = array();
 
        foreach ($images[1] as $image) {
-               $imagedata = get_photo_info($image);
+               $imagedata = Image::getInfoFromURL($image);
 
                if ($imagedata) {
                        $attachments[] = array("url" => $image, "mimetype" => $imagedata["mime"], "size" => $imagedata["size"]);
@@ -2508,7 +2705,7 @@ function api_get_entitities(&$text, $bbcode)
 
                $start = iconv_strpos($text, $url, $offset, "UTF-8");
                if (!($start === false)) {
-                       $image = get_photo_info($url);
+                       $image = Image::getInfoFromURL($url);
                        if ($image) {
                                // If image cache is activated, then use the following sizes:
                                // thumb  (150), small (340), medium (600) and large (1024)
@@ -2516,19 +2713,19 @@ function api_get_entitities(&$text, $bbcode)
                                        $media_url = proxy_url($url);
 
                                        $sizes = array();
-                                       $scale = scale_image($image[0], $image[1], 150);
+                                       $scale = Image::getScalingDimensions($image[0], $image[1], 150);
                                        $sizes["thumb"] = array("w" => $scale["width"], "h" => $scale["height"], "resize" => "fit");
 
                                        if (($image[0] > 150) || ($image[1] > 150)) {
-                                               $scale = scale_image($image[0], $image[1], 340);
+                                               $scale = Image::getScalingDimensions($image[0], $image[1], 340);
                                                $sizes["small"] = array("w" => $scale["width"], "h" => $scale["height"], "resize" => "fit");
                                        }
 
-                                       $scale = scale_image($image[0], $image[1], 600);
+                                       $scale = Image::getScalingDimensions($image[0], $image[1], 600);
                                        $sizes["medium"] = array("w" => $scale["width"], "h" => $scale["height"], "resize" => "fit");
 
                                        if (($image[0] > 600) || ($image[1] > 600)) {
-                                               $scale = scale_image($image[0], $image[1], 1024);
+                                               $scale = Image::getScalingDimensions($image[0], $image[1], 1024);
                                                $sizes["large"] = array("w" => $scale["width"], "h" => $scale["height"], "resize" => "fit");
                                        }
                                } else {
@@ -2767,8 +2964,9 @@ function api_format_items($r, $user_info, $filter_user = false, $type = "json")
                        'user' =>  $status_user ,
                        'friendica_owner' => $owner_user,
                        //'entities' => NULL,
-                       'statusnet_html'                => $converted["html"],
-                       'statusnet_conversation_id'     => $item['parent'],
+                       'statusnet_html' => $converted["html"],
+                       'statusnet_conversation_id' => $item['parent'],
+                       'external_url' => System::baseUrl() . "/display/" . $item['guid'],
                        'friendica_activities' => api_format_items_activities($item, $type),
                );
 
@@ -2899,11 +3097,16 @@ function api_lists_list($type)
 api_register_func('api/lists/list', 'api_lists_list', true);
 
 /**
- * https://dev.twitter.com/docs/api/1/get/statuses/friends
- * This function is deprecated by Twitter
- * returns: json, xml
+ * @brief Returns either the friends of the follower list
+ *
+ * Note: 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 ForbiddenException
  */
-function api_statuses_f($type, $qtype)
+function api_statuses_f($qtype)
 {
        $a = get_app();
 
@@ -2911,9 +3114,17 @@ function api_statuses_f($type, $qtype)
                throw new ForbiddenException();
        }
 
+       // pagination
+       $count = x($_GET, 'count') ? $_GET['count'] : 20;
+       $page = x($_GET, 'page') ? $_GET['page'] : 1;
+       if ($page < 1) {
+               $page = 1;
+       }
+       $start = ($page - 1) * $count;
+
        $user_info = api_get_user($a);
 
-       if (x($_GET, 'cursor') && $_GET['cursor']=='undefined') {
+       if (x($_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
@@ -2936,9 +3147,26 @@ function api_statuses_f($type, $qtype)
                $sql_extra = " AND false ";
        }
 
+       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`)';
+       }
+
        $r = q(
-               "SELECT `nurl` FROM `contact` WHERE `uid` = %d AND NOT `self` AND (NOT `blocked` OR `pending`) $sql_extra ORDER BY `nick`",
-               intval(api_user())
+               "SELECT `nurl`
+               FROM `contact`
+               WHERE `uid` = %d
+               AND NOT `self`
+               $sql_filter
+               $sql_extra
+               ORDER BY `nick`
+               LIMIT %d, %d",
+               intval(api_user()),
+               intval($start),
+               intval($count)
        );
 
        $ret = array();
@@ -2954,21 +3182,37 @@ function api_statuses_f($type, $qtype)
        }
 
        return array('user' => $ret);
-
 }
 
+
+/**
+ * @brief 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
+ */
 function api_statuses_friends($type)
 {
-       $data =  api_statuses_f($type, "friends");
+       $data =  api_statuses_f("friends");
        if ($data === false) {
                return false;
        }
        return api_format_data("users", $type, $data);
 }
 
+/**
+ * @brief 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
+ */
 function api_statuses_followers($type)
 {
-       $data = api_statuses_f($type, "followers");
+       $data = api_statuses_f("followers");
        if ($data === false) {
                return false;
        }
@@ -2979,6 +3223,56 @@ function api_statuses_followers($type)
 api_register_func('api/statuses/friends', 'api_statuses_friends', true);
 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 UnauthorizedException
+ */
+function api_blocks_list($type)
+{
+       $data =  api_statuses_f('blocks');
+       if ($data === false) {
+               return false;
+       }
+       return api_format_data("users", $type, $data);
+}
+
+/// @TODO move to top of file or somewhere better
+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 UnauthorizedException
+ */
+function api_friendships_incoming($type)
+{
+       $data =  api_statuses_f('incoming');
+       if ($data === false) {
+               return false;
+       }
+
+       $ids = array();
+       foreach ($data['user'] as $user) {
+               $ids[] = $user['id'];
+       }
+
+       return api_format_data("ids", $type, array('id' => $ids));
+}
+
+/// @TODO move to top of file or somewhere better
+api_register_func('api/friendships/incoming', 'api_friendships_incoming', true);
+
 function api_statusnet_config($type)
 {
        $a = get_app();
@@ -3363,9 +3657,9 @@ api_register_func('api/direct_messages', 'api_direct_messages_inbox', true);
 
 function api_oauth_request_token($type)
 {
+       $oauth1 = new FKOAuth1();
        try {
-               $oauth = new FKOAuth1();
-               $r = $oauth->fetch_request_token(OAuthRequest::from_request());
+               $r = $oauth1->fetch_request_token(OAuthRequest::from_request());
        } catch (Exception $e) {
                echo "error=" . OAuthUtil::urlencode_rfc3986($e->getMessage());
                killme();
@@ -3376,9 +3670,9 @@ function api_oauth_request_token($type)
 
 function api_oauth_access_token($type)
 {
+       $oauth1 = new FKOAuth1();
        try {
-               $oauth = new FKOAuth1();
-               $r = $oauth->fetch_access_token(OAuthRequest::from_request());
+               $r = $oauth1->fetch_access_token(OAuthRequest::from_request());
        } catch (Exception $e) {
                echo "error=". OAuthUtil::urlencode_rfc3986($e->getMessage());
                killme();
@@ -3945,7 +4239,7 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
        }
 
        if ($filetype == "") {
-               $filetype=guess_image_type($filename);
+               $filetype=Image::guessType($filename);
        }
        $imagedata = getimagesize($src);
        if ($imagedata) {
@@ -3969,13 +4263,13 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
 
        // create Photo instance with the data of the image
        $imagedata = @file_get_contents($src);
-       $ph = new Photo($imagedata, $filetype);
-       if (! $ph->is_valid()) {
+       $Image = new Image($imagedata, $filetype);
+       if (! $Image->isValid()) {
                throw new InternalServerErrorException("unable to process image data");
        }
 
        // check orientation of image
-       $ph->orient($src);
+       $Image->orient($src);
        @unlink($src);
 
        // check max length of images on server
@@ -3984,11 +4278,11 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
                $max_length = MAX_IMAGE_LENGTH;
        }
        if ($max_length > 0) {
-               $ph->scaleImage($max_length);
+               $Image->scaleDown($max_length);
                logger("File upload: Scaling picture to new size " . $max_length, LOGGER_DEBUG);
        }
-       $width = $ph->getWidth();
-       $height = $ph->getHeight();
+       $width = $Image->getWidth();
+       $height = $Image->getHeight();
 
        // create a new resource-id if not already provided
        $hash = ($photo_id == null) ? photo_new_resource() : $photo_id;
@@ -3997,21 +4291,21 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
                // upload normal image (scales 0, 1, 2)
                logger("photo upload: starting new photo upload", LOGGER_DEBUG);
 
-               $r =$ph->store(local_user(), $visitor, $hash, $filename, $album, 0, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
+               $r = Photo::store($Image, local_user(), $visitor, $hash, $filename, $album, 0, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
                if (! $r) {
                        logger("photo upload: image upload with scale 0 (original size) failed");
                }
                if ($width > 640 || $height > 640) {
-                       $ph->scaleImage(640);
-                       $r = $ph->store(local_user(), $visitor, $hash, $filename, $album, 1, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
+                       $Image->scaleDown(640);
+                       $r = Photo::store($Image, local_user(), $visitor, $hash, $filename, $album, 1, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
                        if (! $r) {
                                logger("photo upload: image upload with scale 1 (640x640) failed");
                        }
                }
 
                if ($width > 320 || $height > 320) {
-                       $ph->scaleImage(320);
-                       $r = $ph->store(local_user(), $visitor, $hash, $filename, $album, 2, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
+                       $Image->scaleDown(320);
+                       $r = Photo::store($Image, local_user(), $visitor, $hash, $filename, $album, 2, 0, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
                        if (! $r) {
                                logger("photo upload: image upload with scale 2 (320x320) failed");
                        }
@@ -4022,29 +4316,29 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
                logger("photo upload: starting new profile image upload", LOGGER_DEBUG);
 
                if ($width > 175 || $height > 175) {
-                       $ph->scaleImage(175);
-                       $r = $ph->store(local_user(), $visitor, $hash, $filename, $album, 4, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
+                       $Image->scaleDown(175);
+                       $r = Photo::store($Image, local_user(), $visitor, $hash, $filename, $album, 4, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
                        if (! $r) {
                                logger("photo upload: profile image upload with scale 4 (175x175) failed");
                        }
                }
 
                if ($width > 80 || $height > 80) {
-                       $ph->scaleImage(80);
-                       $r = $ph->store(local_user(), $visitor, $hash, $filename, $album, 5, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
+                       $Image->scaleDown(80);
+                       $r = Photo::store($Image, local_user(), $visitor, $hash, $filename, $album, 5, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
                        if (! $r) {
                                logger("photo upload: profile image upload with scale 5 (80x80) failed");
                        }
                }
 
                if ($width > 48 || $height > 48) {
-                       $ph->scaleImage(48);
-                       $r = $ph->store(local_user(), $visitor, $hash, $filename, $album, 6, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
+                       $Image->scaleDown(48);
+                       $r = Photo::store($Image, local_user(), $visitor, $hash, $filename, $album, 6, $profile, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $desc);
                        if (! $r) {
                                logger("photo upload: profile image upload with scale 6 (48x48) failed");
                        }
                }
-               $ph->__destruct();
+               $Image->__destruct();
                logger("photo upload: new profile image upload ended", LOGGER_DEBUG);
        }
 
@@ -4643,7 +4937,7 @@ function api_friendica_group_show($type)
 
        // loop through all groups and retrieve all members for adding data in the user array
        foreach ($r as $rr) {
-               $members = group_get_members($rr['id']);
+               $members = Contact::getByGroupId($rr['id']);
                $users = array();
 
                if ($type == "xml") {
@@ -4672,7 +4966,9 @@ function api_friendica_group_delete($type)
 {
        $a = get_app();
 
-       if (api_user() === false) throw new ForbiddenException();
+       if (api_user() === false) {
+               throw new ForbiddenException();
+       }
 
        // params
        $user_info = api_get_user($a);
@@ -4681,8 +4977,9 @@ function api_friendica_group_delete($type)
        $uid = $user_info['uid'];
 
        // error if no gid specified
-       if ($gid == 0 || $name == "")
+       if ($gid == 0 || $name == "") {
                throw new BadRequestException('gid or name not specified');
+       }
 
        // get data of the specified group id
        $r = q(
@@ -4691,8 +4988,9 @@ function api_friendica_group_delete($type)
                intval($gid)
        );
        // error message if specified gid is not in database
-       if (!DBM::is_result($r))
+       if (!DBM::is_result($r)) {
                throw new BadRequestException('gid not available');
+       }
 
        // get data of the specified group id and group name
        $rname = q(
@@ -4702,11 +5000,12 @@ function api_friendica_group_delete($type)
                dbesc($name)
        );
        // error message if specified gid is not in database
-       if (!DBM::is_result($rname))
+       if (!DBM::is_result($rname)) {
                throw new BadRequestException('wrong group name');
+       }
 
        // delete group
-       $ret = group_rmv($uid, $name);
+       $ret = Group::removeByName($uid, $name);
        if ($ret) {
                // return success
                $success = array('success' => $ret, 'gid' => $gid, 'name' => $name, 'status' => 'deleted', 'wrong users' => array());
@@ -4757,9 +5056,9 @@ function api_friendica_group_create($type)
                $reactivate_group = true;
 
        // create group
-       $ret = group_add($uid, $name);
+       $ret = Group::create($uid, $name);
        if ($ret) {
-               $gid = group_byname($uid, $name);
+               $gid = Group::getIdByName($uid, $name);
        } else {
                throw new BadRequestException('other API error');
        }
@@ -4776,7 +5075,7 @@ function api_friendica_group_create($type)
                        intval($uid)
                );
                if (count($contact))
-                       $result = group_add_member($uid, $name, $cid, $gid);
+                       $result = Group::addMember($gid, $cid);
                else {
                        $erroraddinguser = true;
                        $errorusers[] = $cid;
@@ -4815,14 +5114,14 @@ function api_friendica_group_update($type)
                throw new BadRequestException('gid not specified');
 
        // remove members
-       $members = group_get_members($gid);
+       $members = Contact::getByGroupId($gid);
        foreach ($members as $member) {
                $cid = $member['id'];
                foreach ($users as $user) {
                        $found = ($user['cid'] == $cid ? true : false);
                }
                if (!$found) {
-                       $ret = group_rmv_member($uid, $name, $cid);
+                       $ret = Group::removeMemberByName($uid, $name, $cid);
                }
        }
 
@@ -4839,7 +5138,7 @@ function api_friendica_group_update($type)
                );
 
                if (count($contact)) {
-                       $result = group_add_member($uid, $name, $cid, $gid);
+                       $result = Group::addMember($gid, $cid);
                } else {
                        $erroraddinguser = true;
                        $errorusers[] = $cid;
@@ -5100,7 +5399,7 @@ function api_friendica_profile_show($type)
        $profileid = (x($_REQUEST, 'profile_id') ? $_REQUEST['profile_id'] : 0);
 
        // retrieve general information about profiles for user
-       $multi_profiles = feature_enabled(api_user(), 'multi_profiles');
+       $multi_profiles = Feature::isEnabled(api_user(), 'multi_profiles');
        $directory = Config::get('system', 'directory');
 
        // get data of the specified profile id or all profiles of the user if not specified