]> git.mxchange.org Git - friendica.git/blobdiff - include/api.php
Merge pull request #7078 from nupplaphil/task/mod_fetch
[friendica.git] / include / api.php
index 2085c801b8bcc6933a3034aeb936e4f72d2e06bc..f360dc4116762b916dc0932120f21116ba331f9e 100644 (file)
@@ -31,6 +31,7 @@ use Friendica\Model\User;
 use Friendica\Network\FKOAuth1;
 use Friendica\Network\HTTPException;
 use Friendica\Network\HTTPException\BadRequestException;
+use Friendica\Network\HTTPException\ExpectationFailedException;
 use Friendica\Network\HTTPException\ForbiddenException;
 use Friendica\Network\HTTPException\InternalServerErrorException;
 use Friendica\Network\HTTPException\MethodNotAllowedException;
@@ -55,6 +56,8 @@ define('API_METHOD_GET', 'GET');
 define('API_METHOD_POST', 'POST,PUT');
 define('API_METHOD_DELETE', 'POST,DELETE');
 
+define('API_LOG_PREFIX', 'API {action} - ');
+
 $API = [];
 $called_api = [];
 
@@ -83,7 +86,8 @@ function api_user()
  * @brief Get source name from API client
  *
  * @return string
- *             Client source name, default to "api" if unset/unknown
+ *        Client source name, default to "api" if unset/unknown
+ * @throws Exception
  */
 function api_source()
 {
@@ -97,9 +101,9 @@ function api_source()
                        return "Twidere";
                }
 
-               Logger::log("Unrecognized user-agent ".$_SERVER['HTTP_USER_AGENT'], Logger::DEBUG);
+               Logger::info(API_LOG_PREFIX . 'Unrecognized user-agent', ['module' => 'api', 'action' => 'source', 'http_user_agent' => $_SERVER['HTTP_USER_AGENT']]);
        } else {
-               Logger::log("Empty user-agent", Logger::DEBUG);
+               Logger::info(API_LOG_PREFIX . 'Empty user-agent', ['module' => 'api', 'action' => 'source']);
        }
 
        return "api";
@@ -110,6 +114,7 @@ function api_source()
  *
  * @param string $str Source date, as UTC
  * @return string Date in UTC formatted as "D M d H:i:s +0000 Y"
+ * @throws Exception
  */
 function api_date($str)
 {
@@ -155,15 +160,17 @@ function api_register_func($path, $func, $auth = false, $method = API_METHOD_ANY
  *
  * @brief Login API user
  *
- * @param object $a App
- * @hook 'authenticate'
- *             array $addon_auth
- *                     'username' => username from login form
- *                     'password' => password from login form
- *                     'authenticated' => return status,
- *                     'user_record' => return authenticated user record
- * @hook 'logged_in'
- *             array $user     logged user record
+ * @param App $a App
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
+ * @hook  'authenticate'
+ *               array $addon_auth
+ *               'username' => username from login form
+ *               'password' => password from login form
+ *               'authenticated' => return status,
+ *               'user_record' => return authenticated user record
+ * @hook  'logged_in'
+ *               array $user    logged user record
  */
 function api_login(App $a)
 {
@@ -181,7 +188,7 @@ function api_login(App $a)
                var_dump($consumer, $token);
                die();
        } catch (Exception $e) {
-               $a->getLogger()->warning('API login: error', ['module' => 'api', 'action' => 'login', 'exception' => $e->getMessage()]);
+               Logger::warning(API_LOG_PREFIX . 'error', ['module' => 'api', 'action' => 'login', 'exception' => $e->getMessage()]);
        }
 
        // workaround for HTTP-auth in CGI mode
@@ -195,7 +202,7 @@ function api_login(App $a)
        }
 
        if (empty($_SERVER['PHP_AUTH_USER'])) {
-               $a->getLogger()->debug('API login: failed', ['module' => 'api', 'action' => 'login', 'parameters' => $_SERVER]);
+               Logger::debug(API_LOG_PREFIX . 'failed', ['module' => 'api', 'action' => 'login', 'parameters' => $_SERVER]);
                header('WWW-Authenticate: Basic realm="Friendica"');
                throw new UnauthorizedException("This API requires login");
        }
@@ -236,7 +243,7 @@ function api_login(App $a)
        }
 
        if (!DBA::isResult($record)) {
-               $a->getLogger()->debug('API login: failed', ['module' => 'api', 'action' => 'login', 'parameters' => $_SERVER]);
+               Logger::debug(API_LOG_PREFIX . 'failed', ['module' => 'api', 'action' => 'login', 'parameters' => $_SERVER]);
                header('WWW-Authenticate: Basic realm="Friendica"');
                //header('HTTP/1.0 401 Unauthorized');
                //die('This api requires login');
@@ -273,8 +280,9 @@ function api_check_method($method)
  *
  * @brief Main API entry point
  *
- * @param object $a App
+ * @param App $a App
  * @return string|array API call result
+ * @throws Exception
  */
 function api_call(App $a)
 {
@@ -309,78 +317,16 @@ function api_call(App $a)
                                        api_login($a);
                                }
 
-                               $a->getLogger()->info('API call', ['module' => 'api', 'action' => 'call', 'username' => $a->user['username']]);
-                               $a->getLogger()->debug('API parameters', ['module' => 'api', 'action' => 'call', 'parameters' => $_REQUEST]);
+                               Logger::info(API_LOG_PREFIX . 'username {username}', ['module' => 'api', 'action' => 'call', 'username' => $a->user['username']]);
+                               Logger::debug(API_LOG_PREFIX . 'parameters', ['module' => 'api', 'action' => 'call', 'parameters' => $_REQUEST]);
 
                                $stamp =  microtime(true);
                                $return = call_user_func($info['func'], $type);
                                $duration = (float) (microtime(true) - $stamp);
 
-                               $a->getLogger()->info('API call', ['module' => 'api', 'action' => 'call', 'username' => $a->user['username'], 'duration' => round($duration, 2)]);
-
-                               if (Config::get("system", "profiler")) {
-                                       $duration = microtime(true)-$a->performance["start"];
-
-                                       /// @TODO round() really everywhere?
-                                       $a->getLogger()->debug(
-                                               'API call performance',
-                                               [
-                                                       'module' => 'api',
-                                                       'action' => 'performance',
-                                                       'database_read' => round($a->performance["database"] - $a->performance["database_write"], 3),
-                                                       'database_write' => round($a->performance["database_write"], 3),
-                                                       'cache_read' => round($a->performance["cache"], 3),
-                                                       'cache_write' => round($a->performance["cache_write"], 3),
-                                                       'network_io' => round($a->performance["network"], 2),
-                                                       'file_io' => round($a->performance["file"], 2),
-                                                       'other_io' => round($duration - ($a->performance["database"]
-                                                                       + $a->performance["cache"] + $a->performance["cache_write"]
-                                                                       + $a->performance["network"] + $a->performance["file"]), 2),
-                                                       'total' => round($duration, 2)
-                                               ]
-                                       );
-
-                                       if (Config::get("rendertime", "callstack")) {
-                                               $o = "Database Read:\n";
-                                               foreach ($a->callstack["database"] as $func => $time) {
-                                                       $time = round($time, 3);
-                                                       if ($time > 0) {
-                                                               $o .= $func . ": " . $time . "\n";
-                                                       }
-                                               }
-                                               $o .= "\nDatabase Write:\n";
-                                               foreach ($a->callstack["database_write"] as $func => $time) {
-                                                       $time = round($time, 3);
-                                                       if ($time > 0) {
-                                                               $o .= $func . ": " . $time . "\n";
-                                                       }
-                                               }
+                               Logger::info(API_LOG_PREFIX . 'username {username}', ['module' => 'api', 'action' => 'call', 'username' => $a->user['username'], 'duration' => round($duration, 2)]);
 
-                                               $o = "Cache Read:\n";
-                                               foreach ($a->callstack["cache"] as $func => $time) {
-                                                       $time = round($time, 3);
-                                                       if ($time > 0) {
-                                                               $o .= $func . ": " . $time . "\n";
-                                                       }
-                                               }
-                                               $o .= "\nCache Write:\n";
-                                               foreach ($a->callstack["cache_write"] as $func => $time) {
-                                                       $time = round($time, 3);
-                                                       if ($time > 0) {
-                                                               $o .= $func . ": " . $time . "\n";
-                                                       }
-                                               }
-
-                                               $o .= "\nNetwork:\n";
-                                               foreach ($a->callstack["network"] as $func => $time) {
-                                                       $time = round($time, 3);
-                                                       if ($time > 0) {
-                                                               $o .= $func . ": " . $time . "\n";
-                                                       }
-                                               }
-                                               $a->getLogger()->debug($o, ['module' => 'api', 'action' => 'performance']);
-                                       }
-                               }
+                               $a->getProfiler()->saveLog($a->getLogger(), API_LOG_PREFIX . 'performance');
 
                                if (false === $return) {
                                        /*
@@ -415,10 +361,10 @@ function api_call(App $a)
                        }
                }
 
-               $a->getLogger()->warning('API call not implemented');
+               Logger::warning(API_LOG_PREFIX . 'not implemented', ['module' => 'api', 'action' => 'call']);
                throw new NotImplementedException();
        } catch (HTTPException $e) {
-               header("HTTP/1.1 {$e->httpcode} {$e->httpdesc}");
+               header("HTTP/1.1 {$e->getCode()} {$e->httpdesc}");
                return api_error($type, $e);
        }
 }
@@ -438,7 +384,7 @@ function api_error($type, $e)
        /// @TODO:  https://dev.twitter.com/overview/api/response-codes
 
        $error = ["error" => $error,
-                       "code" => $e->httpcode . " " . $e->httpdesc,
+                       "code" => $e->getCode() . " " . $e->httpdesc,
                        "request" => $a->query_string];
 
        $return = api_format_data('status', $type, ['status' => $error]);
@@ -465,11 +411,15 @@ function api_error($type, $e)
 /**
  * @brief Set values for RSS template
  *
- * @param App $a
+ * @param App   $a
  * @param array $arr       Array to be passed to template
  * @param array $user_info User info
  * @return array
- * @todo find proper type-hints
+ * @throws BadRequestException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
+ * @todo  find proper type-hints
  */
 function api_rss_extra(App $a, $arr, $user_info)
 {
@@ -497,7 +447,8 @@ function api_rss_extra(App $a, $arr, $user_info)
  *
  * @param int $id Contact id
  * @return bool|string
- *             Contact url or False if contact id is unknown
+ *                Contact url or False if contact id is unknown
+ * @throws Exception
  */
 function api_unique_id_to_nurl($id)
 {
@@ -513,8 +464,13 @@ function api_unique_id_to_nurl($id)
 /**
  * @brief Get user info array.
  *
- * @param object     $a          App
+ * @param App        $a          App
  * @param int|string $contact_id Contact ID or URL
+ * @return array|bool
+ * @throws BadRequestException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_get_user(App $a, $contact_id = null)
 {
@@ -524,7 +480,7 @@ function api_get_user(App $a, $contact_id = null)
        $extra_query = "";
        $url = "";
 
-       $a->getLogger()->info('api_get_user: Fetching user data', ['module' => 'api', 'action' => 'get_user', 'user' => $contact_id]);
+       Logger::info(API_LOG_PREFIX . 'Fetching data for user {user}', ['module' => 'api', 'action' => 'get_user', 'user' => $contact_id]);
 
        // Searching for contact URL
        if (!is_null($contact_id) && (intval($contact_id) == 0)) {
@@ -608,7 +564,7 @@ function api_get_user(App $a, $contact_id = null)
                }
        }
 
-       $a->getLogger()->info('api_get_user: getting user', ['module' => 'api', 'action' => 'get_user', 'user' => $user]);
+       Logger::info(API_LOG_PREFIX . 'getting user {user}', ['module' => 'api', 'action' => 'get_user', 'user' => $user]);
 
        if (!$user) {
                if (api_user() === false) {
@@ -620,7 +576,7 @@ function api_get_user(App $a, $contact_id = null)
                }
        }
 
-       $a->getLogger()->info('api_get_user: found user', ['module' => 'api', 'action' => 'get_user', 'user' => $user, 'extra_query' => $extra_query]);
+       Logger::info(API_LOG_PREFIX . 'found user {user}', ['module' => 'api', 'action' => 'get_user', 'user' => $user, 'extra_query' => $extra_query]);
 
        // user info
        $uinfo = q(
@@ -807,30 +763,36 @@ function api_get_user(App $a, $contact_id = null)
 /**
  * @brief return api-formatted array for item's author and owner
  *
- * @param object $a    App
- * @param array  $item item from db
+ * @param App   $a    App
+ * @param array $item item from db
  * @return array(array:author, array:owner)
+ * @throws BadRequestException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_item_get_user(App $a, $item)
 {
        $status_user = api_get_user($a, defaults($item, 'author-id', null));
 
+       $author_user = $status_user;
+
        $status_user["protected"] = defaults($item, 'private', 0);
 
        if (defaults($item, 'thr-parent', '') == defaults($item, 'uri', '')) {
                $owner_user = api_get_user($a, defaults($item, 'owner-id', null));
        } else {
-               $owner_user = $status_user;
+               $owner_user = $author_user;
        }
 
-       return ([$status_user, $owner_user]);
+       return ([$status_user, $author_user, $owner_user]);
 }
 
 /**
  * @brief walks recursively through an array with the possibility to change value and key
  *
- * @param array  $array    The array to walk through
- * @param string $callback The callback function
+ * @param array    $array    The array to walk through
+ * @param callable $callback The callback function
  *
  * @return array the transformed array
  */
@@ -932,7 +894,7 @@ function api_create_xml(array $data, $root_element)
  * @param string $type         Return type (atom, rss, xml, json)
  * @param array  $data         JSON style array
  *
- * @return (string|array) XML data or JSON data
+ * @return array|string (string|array) XML data or JSON data
  */
 function api_format_data($root_element, $type, $data)
 {
@@ -957,13 +919,19 @@ function api_format_data($root_element, $type, $data)
 /**
  * Returns an HTTP 200 OK response code and a representation of the requesting user if authentication was successful;
  * returns a 401 status code and an error message if not.
+ *
  * @see https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/get-account-verify_credentials
  *
  * @param string $type Return type (atom, rss, xml, json)
+ * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_account_verify_credentials($type)
 {
-
        $a = \get_app();
 
        if (api_user() === false) {
@@ -985,11 +953,9 @@ function api_account_verify_credentials($type)
 
        // - Adding last status
        if (!$skip_status) {
-               $user_info["status"] = api_status_show("raw");
-               if (!count($user_info["status"])) {
-                       unset($user_info["status"]);
-               } else {
-                       unset($user_info["status"]["user"]);
+               $item = api_get_last_status($user_info['pid'], $user_info['uid']);
+               if ($item) {
+                       $user_info['status'] = api_format_item($item, $type);
                }
        }
 
@@ -1007,6 +973,7 @@ api_register_func('api/account/verify_credentials', 'api_account_verify_credenti
  * Get data from $_POST or $_GET
  *
  * @param string $k
+ * @return null
  */
 function requestdata($k)
 {
@@ -1025,6 +992,11 @@ function requestdata($k)
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_statuses_mediap($type)
 {
@@ -1072,6 +1044,12 @@ api_register_func('api/statuses/mediap', 'api_statuses_mediap', true, API_METHOD
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws TooManyRequestsException
+ * @throws UnauthorizedException
  * @see https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-update
  */
 function api_statuses_update($type)
@@ -1214,6 +1192,11 @@ api_register_func('api/statuses/update_with_media', 'api_statuses_update', true,
  * Uploads an image to Friendica.
  *
  * @return array
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @see https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-upload
  */
 function api_media_upload()
@@ -1256,101 +1239,61 @@ function api_media_upload()
 api_register_func('api/media/upload', 'api_media_upload', true, API_METHOD_POST);
 
 /**
- *
- * @param string $type Return type (atom, rss, xml, json)
- *
- * @return array|string
+ * @param string $type    Return format (atom, rss, xml, json)
+ * @param int    $item_id
+ * @return string
+ * @throws Exception
  */
-function api_status_show($type, $item_id = 0)
+function api_status_show($type, $item_id)
 {
-       $a = \get_app();
-
-       $user_info = api_get_user($a);
-
-       Logger::log('api_status_show: user_info: '.print_r($user_info, true), Logger::DEBUG);
+       Logger::info(API_LOG_PREFIX . 'Start', ['action' => 'status_show', 'type' => $type, 'item_id' => $item_id]);
 
-       if ($type == "raw") {
-               $privacy_sql = "AND NOT `private`";
-       } else {
-               $privacy_sql = "";
-       }
+       $status_info = [];
 
-       if (!empty($item_id)) {
-               // Get the item with the given id
-               $condition = ['id' => $item_id];
-       } else {
-               // get last public wall message
-               $condition = ['owner-id' => $user_info['pid'], 'uid' => api_user(),
-                       'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]];
+       $item = api_get_item(['id' => $item_id]);
+       if ($item) {
+               $status_info = api_format_item($item, $type);
        }
-       $lastwall = Item::selectFirst(Item::ITEM_FIELDLIST, $condition, ['order' => ['id' => true]]);
-
-       if (DBA::isResult($lastwall)) {
-               $in_reply_to = api_in_reply_to($lastwall);
-
-               $converted = api_convert_item($lastwall);
-
-               if ($type == "xml") {
-                       $geo = "georss:point";
-               } else {
-                       $geo = "geo";
-               }
-
-               $status_info = [
-                       'created_at' => api_date($lastwall['created']),
-                       'id' => intval($lastwall['id']),
-                       'id_str' => (string) $lastwall['id'],
-                       'text' => $converted["text"],
-                       'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'),
-                       'truncated' => false,
-                       'in_reply_to_status_id' => $in_reply_to['status_id'],
-                       'in_reply_to_status_id_str' => $in_reply_to['status_id_str'],
-                       'in_reply_to_user_id' => $in_reply_to['user_id'],
-                       'in_reply_to_user_id_str' => $in_reply_to['user_id_str'],
-                       'in_reply_to_screen_name' => $in_reply_to['screen_name'],
-                       'user' => $user_info,
-                       $geo => null,
-                       'coordinates' => '',
-                       'place' => '',
-                       'contributors' => '',
-                       'is_quote_status' => false,
-                       'retweet_count' => 0,
-                       'favorite_count' => 0,
-                       'favorited' => $lastwall['starred'] ? true : false,
-                       'retweeted' => false,
-                       'possibly_sensitive' => false,
-                       'lang' => '',
-                       'statusnet_html' => $converted["html"],
-                       'statusnet_conversation_id' => $lastwall['parent'],
-                       'external_url' => System::baseUrl() . '/display/' . $lastwall['guid'],
-               ];
 
-               if (count($converted["attachments"]) > 0) {
-                       $status_info["attachments"] = $converted["attachments"];
-               }
+       Logger::info(API_LOG_PREFIX . 'End', ['action' => 'get_status', 'status_info' => $status_info]);
 
-               if (count($converted["entities"]) > 0) {
-                       $status_info["entities"] = $converted["entities"];
-               }
+       return api_format_data('statuses', $type, ['status' => $status_info]);
+}
 
-               if ($status_info["source"] == 'web') {
-                       $status_info["source"] = ContactSelector::networkToName($lastwall['network'], $lastwall['author-link']);
-               } elseif (ContactSelector::networkToName($lastwall['network'], $lastwall['author-link']) != $status_info["source"]) {
-                       $status_info["source"] = trim($status_info["source"].' ('.ContactSelector::networkToName($lastwall['network'], $lastwall['author-link']).')');
-               }
+/**
+ * Retrieves the last public status of the provided user info
+ *
+ * @param int    $ownerId Public contact Id
+ * @param int    $uid     User Id
+ * @return array
+ * @throws Exception
+ */
+function api_get_last_status($ownerId, $uid)
+{
+       $condition = [
+               'owner-id' => $ownerId,
+               'uid'      => $uid,
+               'gravity'  => [GRAVITY_PARENT, GRAVITY_COMMENT],
+               'private'  => false
+       ];
 
-               // "uid" and "self" are only needed for some internal stuff, so remove it from here
-               unset($status_info["user"]["uid"]);
-               unset($status_info["user"]["self"]);
+       $item = api_get_item($condition);
 
-               Logger::log('status_info: '.print_r($status_info, true), Logger::DEBUG);
+       return $item;
+}
 
-               if ($type == "raw") {
-                       return $status_info;
-               }
+/**
+ * Retrieves a single item record based on the provided condition and converts it for API use.
+ *
+ * @param array $condition Item table condition array
+ * @return array
+ * @throws Exception
+ */
+function api_get_item(array $condition)
+{
+       $item = Item::selectFirst(Item::DISPLAY_FIELDLIST, $condition, ['order' => ['id' => true]]);
 
-               return api_format_data("statuses", $type, ['status' => $status_info]);
-       }
+       return $item;
 }
 
 /**
@@ -1358,70 +1301,29 @@ function api_status_show($type, $item_id = 0)
  * The author's most recent status will be returned inline.
  *
  * @param string $type Return type (atom, rss, xml, json)
+ * @return array|string
+ * @throws BadRequestException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-show
  */
 function api_users_show($type)
 {
-       $a = \get_app();
+       $a = \Friendica\BaseObject::getApp();
 
        $user_info = api_get_user($a);
 
-       $condition = ['owner-id' => $user_info['pid'], 'uid' => api_user(),
-               'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'private' => false];
-       $lastwall = Item::selectFirst(Item::ITEM_FIELDLIST, $condition, ['order' => ['id' => true]]);
-
-       if (DBA::isResult($lastwall)) {
-               $in_reply_to = api_in_reply_to($lastwall);
-
-               $converted = api_convert_item($lastwall);
-
-               if ($type == "xml") {
-                       $geo = "georss:point";
-               } else {
-                       $geo = "geo";
-               }
-
-               $user_info['status'] = [
-                       'text' => $converted["text"],
-                       'truncated' => false,
-                       'created_at' => api_date($lastwall['created']),
-                       'in_reply_to_status_id' => $in_reply_to['status_id'],
-                       'in_reply_to_status_id_str' => $in_reply_to['status_id_str'],
-                       'source' => (($lastwall['app']) ? $lastwall['app'] : 'web'),
-                       'id' => intval($lastwall['contact-id']),
-                       'id_str' => (string) $lastwall['contact-id'],
-                       'in_reply_to_user_id' => $in_reply_to['user_id'],
-                       'in_reply_to_user_id_str' => $in_reply_to['user_id_str'],
-                       'in_reply_to_screen_name' => $in_reply_to['screen_name'],
-                       $geo => null,
-                       'favorited' => $lastwall['starred'] ? true : false,
-                       'statusnet_html' => $converted["html"],
-                       'statusnet_conversation_id' => $lastwall['parent'],
-                       'external_url' => System::baseUrl() . "/display/" . $lastwall['guid'],
-               ];
-
-               if (count($converted["attachments"]) > 0) {
-                       $user_info["status"]["attachments"] = $converted["attachments"];
-               }
-
-               if (count($converted["entities"]) > 0) {
-                       $user_info["status"]["entities"] = $converted["entities"];
-               }
-
-               if ($user_info["status"]["source"] == 'web') {
-                       $user_info["status"]["source"] = ContactSelector::networkToName($lastwall['network'], $lastwall['author-link']);
-               }
-
-               if (ContactSelector::networkToName($lastwall['network'], $user_info['url']) != $user_info["status"]["source"]) {
-                       $user_info["status"]["source"] = trim($user_info["status"]["source"] . ' (' . ContactSelector::networkToName($lastwall['network'], $lastwall['author-link']) . ')');
-               }
+       $item = api_get_last_status($user_info['pid'], $user_info['uid']);
+       if ($item) {
+               $user_info['status'] = api_format_item($item, $type);
        }
 
        // "uid" and "self" are only needed for some internal stuff, so remove it from here
-       unset($user_info["uid"]);
-       unset($user_info["self"]);
+       unset($user_info['uid']);
+       unset($user_info['self']);
 
-       return api_format_data("user", $type, ['user' => $user_info]);
+       return api_format_data('user', $type, ['user' => $user_info]);
 }
 
 /// @TODO move to top of file or somewhere better
@@ -1434,6 +1336,10 @@ api_register_func('api/externalprofile/show', 'api_users_show');
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-users-search
  */
 function api_users_search($type)
@@ -1482,7 +1388,11 @@ api_register_func('api/users/search', 'api_users_search');
  * @param string $type Return format: json or xml
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
  * @throws NotFoundException if the results are empty.
+ * @throws UnauthorizedException
  */
 function api_users_lookup($type)
 {
@@ -1515,6 +1425,10 @@ api_register_func('api/users/lookup', 'api_users_lookup', true);
  *
  * @return array|string
  * @throws BadRequestException if the "q" parameter is missing.
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_search($type)
 {
@@ -1523,8 +1437,10 @@ function api_search($type)
 
        if (api_user() === false || $user_info === false) { throw new ForbiddenException(); }
 
-       if (empty($_REQUEST['q'])) { throw new BadRequestException('q parameter is required.'); }
-       
+       if (empty($_REQUEST['q'])) {
+               throw new BadRequestException('q parameter is required.');
+       }
+
        $searchTerm = trim(rawurldecode($_REQUEST['q']));
 
        $data = [];
@@ -1597,10 +1513,16 @@ api_register_func('api/search', 'api_search', true);
 /**
  * Returns the most recent statuses posted by the user and the users they follow.
  *
- * @see https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline
+ * @see  https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-home_timeline
  *
  * @param string $type Return type (atom, rss, xml, json)
  *
+ * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @todo Optional parameters
  * @todo Add reply info
  */
@@ -1668,7 +1590,7 @@ function api_statuses_home_timeline($type)
                        Item::update(['unseen' => false], ['unseen' => true, 'id' => $idarray]);
                }
        }
-       
+
        bindComments($ret);
 
        $data = ['status' => $ret];
@@ -1694,6 +1616,11 @@ api_register_func('api/statuses/friends_timeline', 'api_statuses_home_timeline',
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_statuses_public_timeline($type)
 {
@@ -1720,7 +1647,7 @@ function api_statuses_public_timeline($type)
        $start = $page * $count;
 
        if ($exclude_replies && !$conversation_id) {
-               $condition = ["`gravity` IN (?, ?) AND `iid` > ? AND NOT `private` AND `wall` AND NOT `user`.`hidewall`",
+               $condition = ["`gravity` IN (?, ?) AND `iid` > ? AND NOT `private` AND `wall` AND NOT `user`.`hidewall` AND NOT `author`.`hidden`",
                        GRAVITY_PARENT, GRAVITY_COMMENT, $since_id];
 
                if ($max_id > 0) {
@@ -1733,7 +1660,7 @@ function api_statuses_public_timeline($type)
 
                $r = Item::inArray($statuses);
        } else {
-               $condition = ["`gravity` IN (?, ?) AND `id` > ? AND NOT `private` AND `wall` AND NOT `user`.`hidewall` AND `item`.`origin`",
+               $condition = ["`gravity` IN (?, ?) AND `id` > ? AND NOT `private` AND `wall` AND NOT `user`.`hidewall` AND `item`.`origin` AND NOT `author`.`hidden`",
                        GRAVITY_PARENT, GRAVITY_COMMENT, $since_id];
 
                if ($max_id > 0) {
@@ -1777,7 +1704,11 @@ api_register_func('api/statuses/public_timeline', 'api_statuses_public_timeline'
  *
  * @param string $type Return format: json, xml, atom, rss
  * @return array|string
+ * @throws BadRequestException
  * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_statuses_networkpublic_timeline($type)
 {
@@ -1834,6 +1765,12 @@ api_register_func('api/statuses/networkpublic_timeline', 'api_statuses_networkpu
  *
  * @param string $type Return type (atom, rss, xml, json)
  *
+ * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @see https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/get-statuses-show-id
  */
 function api_statuses_show($type)
@@ -1907,6 +1844,12 @@ api_register_func('api/statuses/show', 'api_statuses_show', true);
  *
  * @param string $type Return type (atom, rss, xml, json)
  *
+ * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @todo nothing to say?
  */
 function api_conversation_show($type)
@@ -1939,7 +1882,7 @@ function api_conversation_show($type)
                $id = intval(defaults($a->argv, 4, 0));
        }
 
-       $a->getLogger()->info('API: api_conversation_show', ['module' => 'api', 'action' => 'conversation', 'id' => $id]);
+       Logger::info(API_LOG_PREFIX . '{subaction}', ['module' => 'api', 'action' => 'conversation', 'subaction' => 'show', 'id' => $id]);
 
        // try to fetch the item for the local user - or the public item, if there is no local one
        $item = Item::selectFirst(['parent-uri'], ['id' => $id]);
@@ -1984,6 +1927,12 @@ api_register_func('api/statusnet/conversation', 'api_conversation_show', true);
  *
  * @param string $type Return type (atom, rss, xml, json)
  *
+ * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @see https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-retweet-id
  */
 function api_statuses_repeat($type)
@@ -2051,6 +2000,12 @@ api_register_func('api/statuses/retweet', 'api_statuses_repeat', true, API_METHO
  *
  * @param string $type Return type (atom, rss, xml, json)
  *
+ * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @see https://developer.twitter.com/en/docs/tweets/post-and-engage/api-reference/post-statuses-destroy-id
  */
 function api_statuses_destroy($type)
@@ -2092,6 +2047,12 @@ api_register_func('api/statuses/destroy', 'api_statuses_destroy', true, API_METH
  *
  * @param string $type Return type (atom, rss, xml, json)
  *
+ * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @see http://developer.twitter.com/doc/get/statuses/mentions
  */
 function api_statuses_mentions($type)
@@ -2159,8 +2120,12 @@ api_register_func('api/statuses/replies', 'api_statuses_mentions', true);
  *
  * @param string $type Either "json" or "xml"
  * @return string|array
+ * @throws BadRequestException
  * @throws ForbiddenException
- * @see https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-user_timeline
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
+ * @see   https://developer.twitter.com/en/docs/tweets/timelines/api-reference/get-statuses-user_timeline
  */
 function api_statuses_user_timeline($type)
 {
@@ -2240,6 +2205,12 @@ api_register_func('api/statuses/user_timeline', 'api_statuses_user_timeline', tr
  *
  * @param string $type Return type (atom, rss, xml, json)
  *
+ * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @see https://web.archive.org/web/20131019055350/https://dev.twitter.com/docs/api/1/post/favorites/create/%3Aid
  */
 function api_favorites_create_destroy($type)
@@ -2317,6 +2288,11 @@ api_register_func('api/favorites/destroy', 'api_favorites_create_destroy', true,
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return string|array
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_favorites($type)
 {
@@ -2333,7 +2309,7 @@ function api_favorites($type)
 
        // in friendica starred item are private
        // return favorites only for self
-       $a->getLogger()->info('API: api_favorites', ['module' => 'api', 'action' => 'favorites', 'self' => $user_info['self']]);
+       Logger::info(API_LOG_PREFIX . 'for {self}', ['module' => 'api', 'action' => 'favorites', 'self' => $user_info['self']]);
 
        if ($user_info['self'] == 0) {
                $ret = [];
@@ -2388,6 +2364,7 @@ api_register_func('api/favorites', 'api_favorites', true);
  * @param array $sender
  *
  * @return array
+ * @throws InternalServerErrorException
  */
 function api_format_messages($item, $recipient, $sender)
 {
@@ -2445,6 +2422,7 @@ function api_format_messages($item, $recipient, $sender)
  * @param array $item
  *
  * @return array
+ * @throws InternalServerErrorException
  */
 function api_convert_item($item)
 {
@@ -2520,6 +2498,7 @@ function api_convert_item($item)
  * @param string $body
  *
  * @return array
+ * @throws InternalServerErrorException
  */
 function api_get_attachments(&$body)
 {
@@ -2558,6 +2537,7 @@ function api_get_attachments(&$body)
  * @param string $bbcode
  *
  * @return array
+ * @throws InternalServerErrorException
  * @todo Links at the first character of the post
  */
 function api_get_entitities(&$text, $bbcode)
@@ -2702,7 +2682,7 @@ function api_get_entitities(&$text, $bbcode)
 
                                $entities["media"][] = [
                                                        "id" => $start+1,
-                                                       "id_str" => (string)$start+1,
+                                                       "id_str" => (string) ($start + 1),
                                                        "indices" => [$start, $start+strlen($url)],
                                                        "media_url" => Strings::normaliseLink($media_url),
                                                        "media_url_https" => $media_url,
@@ -2768,12 +2748,16 @@ function api_contactlink_to_array($txt)
 /**
  * @brief return likes, dislikes and attend status for item
  *
- * @param array $item array
+ * @param array  $item array
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array
- *                     likes => int count,
- *                     dislikes => int count
+ *            likes => int count,
+ *            dislikes => int count
+ * @throws BadRequestException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_format_items_activities($item, $type = "json")
 {
@@ -2840,8 +2824,9 @@ function api_format_items_activities($item, $type = "json")
 /**
  * @brief return data from profiles
  *
- * @param array  $profile_row array containing data from db table 'profile'
+ * @param array $profile_row array containing data from db table 'profile'
  * @return array
+ * @throws InternalServerErrorException
  */
 function api_format_items_profiles($profile_row)
 {
@@ -2892,116 +2877,203 @@ function api_format_items_profiles($profile_row)
 /**
  * @brief format items to be returned by api
  *
- * @param array  $r array of items
+ * @param array  $items       array of items
  * @param array  $user_info
  * @param bool   $filter_user filter items by $user_info
- * @param string $type Return type (atom, rss, xml, json)
+ * @param string $type        Return type (atom, rss, xml, json)
+ * @return array
+ * @throws BadRequestException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
-function api_format_items($r, $user_info, $filter_user = false, $type = "json")
+function api_format_items($items, $user_info, $filter_user = false, $type = "json")
 {
-       $a = \get_app();
+       $a = \Friendica\BaseObject::getApp();
 
        $ret = [];
 
-       foreach ((array)$r as $item) {
-               localize_item($item);
-               list($status_user, $owner_user) = api_item_get_user($a, $item);
+       foreach ((array)$items as $item) {
+               list($status_user, $author_user, $owner_user) = api_item_get_user($a, $item);
 
                // Look if the posts are matching if they should be filtered by user id
                if ($filter_user && ($status_user["id"] != $user_info["id"])) {
                        continue;
                }
 
-               $in_reply_to = api_in_reply_to($item);
+               $status = api_format_item($item, $type, $status_user, $author_user, $owner_user);
 
-               $converted = api_convert_item($item);
+               $ret[] = $status;
+       }
 
-               if ($type == "xml") {
-                       $geo = "georss:point";
-               } else {
-                       $geo = "geo";
-               }
-
-               $status = [
-                       'text'          => $converted["text"],
-                       'truncated' => false,
-                       'created_at'=> api_date($item['created']),
-                       'in_reply_to_status_id' => $in_reply_to['status_id'],
-                       'in_reply_to_status_id_str' => $in_reply_to['status_id_str'],
-                       'source'    => (($item['app']) ? $item['app'] : 'web'),
-                       'id'            => intval($item['id']),
-                       'id_str'        => (string) intval($item['id']),
-                       'in_reply_to_user_id' => $in_reply_to['user_id'],
-                       'in_reply_to_user_id_str' => $in_reply_to['user_id_str'],
-                       'in_reply_to_screen_name' => $in_reply_to['screen_name'],
-                       $geo => null,
-                       'favorited' => $item['starred'] ? true : false,
-                       'user' =>  $status_user,
-                       'friendica_owner' => $owner_user,
-                       'friendica_private' => $item['private'] == 1,
-                       //'entities' => NULL,
-                       'statusnet_html' => $converted["html"],
-                       'statusnet_conversation_id' => $item['parent'],
-                       'external_url' => System::baseUrl() . "/display/" . $item['guid'],
-                       'friendica_activities' => api_format_items_activities($item, $type),
-               ];
+       return $ret;
+}
 
-               if (count($converted["attachments"]) > 0) {
-                       $status["attachments"] = $converted["attachments"];
+/**
+ * @param array  $item       Item record
+ * @param string $type       Return format (atom, rss, xml, json)
+ * @param array $status_user User record of the item author, can be provided by api_item_get_user()
+ * @param array $author_user User record of the item author, can be provided by api_item_get_user()
+ * @param array $owner_user  User record of the item owner, can be provided by api_item_get_user()
+ * @return array API-formatted status
+ * @throws BadRequestException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
+ */
+function api_format_item($item, $type = "json", $status_user = null, $author_user = null, $owner_user = null)
+{
+       $a = \Friendica\BaseObject::getApp();
+
+       if (empty($status_user) || empty($author_user) || empty($owner_user)) {
+               list($status_user, $author_user, $owner_user) = api_item_get_user($a, $item);
+       }
+
+       localize_item($item);
+
+       $in_reply_to = api_in_reply_to($item);
+
+       $converted = api_convert_item($item);
+
+       if ($type == "xml") {
+               $geo = "georss:point";
+       } else {
+               $geo = "geo";
+       }
+
+       $status = [
+               'text'          => $converted["text"],
+               'truncated' => false,
+               'created_at'=> api_date($item['created']),
+               'in_reply_to_status_id' => $in_reply_to['status_id'],
+               'in_reply_to_status_id_str' => $in_reply_to['status_id_str'],
+               'source'    => (($item['app']) ? $item['app'] : 'web'),
+               'id'            => intval($item['id']),
+               'id_str'        => (string) intval($item['id']),
+               'in_reply_to_user_id' => $in_reply_to['user_id'],
+               'in_reply_to_user_id_str' => $in_reply_to['user_id_str'],
+               'in_reply_to_screen_name' => $in_reply_to['screen_name'],
+               $geo => null,
+               'favorited' => $item['starred'] ? true : false,
+               'user' =>  $status_user,
+               'friendica_author' => $author_user,
+               'friendica_owner' => $owner_user,
+               'friendica_private' => $item['private'] == 1,
+               //'entities' => NULL,
+               'statusnet_html' => $converted["html"],
+               'statusnet_conversation_id' => $item['parent'],
+               'external_url' => System::baseUrl() . "/display/" . $item['guid'],
+               'friendica_activities' => api_format_items_activities($item, $type),
+       ];
+
+       if (count($converted["attachments"]) > 0) {
+               $status["attachments"] = $converted["attachments"];
+       }
+
+       if (count($converted["entities"]) > 0) {
+               $status["entities"] = $converted["entities"];
+       }
+
+       if ($status["source"] == 'web') {
+               $status["source"] = ContactSelector::networkToName($item['network'], $item['author-link']);
+       } elseif (ContactSelector::networkToName($item['network'], $item['author-link']) != $status["source"]) {
+               $status["source"] = trim($status["source"].' ('.ContactSelector::networkToName($item['network'], $item['author-link']).')');
+       }
+
+       $retweeted_item = [];
+       $quoted_item = [];
+
+       if ($item["id"] == $item["parent"]) {
+               $body = $item['body'];
+               $retweeted_item = api_share_as_retweet($item);
+               if ($body != $item['body']) {
+                       $quoted_item = $retweeted_item;
+                       $retweeted_item = [];
                }
+       }
 
-               if (count($converted["entities"]) > 0) {
-                       $status["entities"] = $converted["entities"];
+       if (empty($retweeted_item) && ($item['owner-id'] == $item['author-id'])) {
+               $announce = api_get_announce($item);
+               if (!empty($announce)) {
+                       $retweeted_item = $item;
+                       $item = $announce;
+                       $status['friendica_owner'] = api_get_user($a, $announce['author-id']);
                }
+       }
 
-               if ($status["source"] == 'web') {
-                       $status["source"] = ContactSelector::networkToName($item['network'], $item['author-link']);
-               } elseif (ContactSelector::networkToName($item['network'], $item['author-link']) != $status["source"]) {
-                       $status["source"] = trim($status["source"].' ('.ContactSelector::networkToName($item['network'], $item['author-link']).')');
+       if (!empty($quoted_item)) {
+               $conv_quoted = api_convert_item($quoted_item);
+               $quoted_status = $status;
+               unset($quoted_status['friendica_author']);
+               unset($quoted_status['friendica_owner']);
+               unset($quoted_status['friendica_activities']);
+               unset($quoted_status['friendica_private']);
+               unset($quoted_status['statusnet_conversation_id']);
+               $quoted_status['text'] = $conv_quoted['text'];
+               $quoted_status['statusnet_html'] = $conv_quoted['html'];
+               try {
+                       $quoted_status["user"] = api_get_user($a, $quoted_item["author-id"]);
+               } catch (BadRequestException $e) {
+                       // user not found. should be found?
+                       /// @todo check if the user should be always found
+                       $quoted_status["user"] = [];
                }
+       }
 
-               if ($item["id"] == $item["parent"]) {
-                       $retweeted_item = api_share_as_retweet($item);
-                       if ($retweeted_item !== false) {
-                               $retweeted_status = $status;
-                               $status['user'] = $status['friendica_owner'];
-                               try {
-                                       $retweeted_status["user"] = api_get_user($a, $retweeted_item["author-id"]);
-                               } catch (BadRequestException $e) {
-                                       // user not found. should be found?
-                                       /// @todo check if the user should be always found
-                                       $retweeted_status["user"] = [];
-                               }
+       if (!empty($retweeted_item)) {
+               $retweeted_status = $status;
+               unset($retweeted_status['friendica_author']);
+               unset($retweeted_status['friendica_owner']);
+               unset($retweeted_status['friendica_activities']);
+               unset($retweeted_status['friendica_private']);
+               unset($retweeted_status['statusnet_conversation_id']);
+               $status['user'] = $status['friendica_owner'];
+               try {
+                       $retweeted_status["user"] = api_get_user($a, $retweeted_item["author-id"]);
+               } catch (BadRequestException $e) {
+                       // user not found. should be found?
+                       /// @todo check if the user should be always found
+                       $retweeted_status["user"] = [];
+               }
 
-                               $rt_converted = api_convert_item($retweeted_item);
+               $rt_converted = api_convert_item($retweeted_item);
 
-                               $retweeted_status['text'] = $rt_converted["text"];
-                               $retweeted_status['statusnet_html'] = $rt_converted["html"];
-                               $retweeted_status['friendica_activities'] = api_format_items_activities($retweeted_item, $type);
-                               $retweeted_status['created_at'] =  api_date($retweeted_item['created']);
-                               $status['retweeted_status'] = $retweeted_status;
-                       }
+               $retweeted_status['text'] = $rt_converted["text"];
+               $retweeted_status['statusnet_html'] = $rt_converted["html"];
+               $retweeted_status['created_at'] =  api_date($retweeted_item['created']);
+
+               if (!empty($quoted_status)) {
+                       $retweeted_status['quoted_status'] = $quoted_status;
                }
 
-               // "uid" and "self" are only needed for some internal stuff, so remove it from here
-               unset($status["user"]["uid"]);
-               unset($status["user"]["self"]);
-
-               if ($item["coord"] != "") {
-                       $coords = explode(' ', $item["coord"]);
-                       if (count($coords) == 2) {
-                               if ($type == "json") {
-                                       $status["geo"] = ['type' => 'Point',
-                                                       'coordinates' => [(float) $coords[0],
-                                                                               (float) $coords[1]]];
-                               } else {// Not sure if this is the official format - if someone founds a documentation we can check
-                                       $status["georss:point"] = $item["coord"];
-                               }
+               $status['friendica_author'] = $retweeted_status['user'];
+               $status['retweeted_status'] = $retweeted_status;
+       } elseif (!empty($quoted_status)) {
+               $root_status = api_convert_item($item);
+
+               $status['text'] = $root_status["text"];
+               $status['statusnet_html'] = $root_status["html"];
+               $status['quoted_status'] = $quoted_status;
+       }
+
+       // "uid" and "self" are only needed for some internal stuff, so remove it from here
+       unset($status["user"]["uid"]);
+       unset($status["user"]["self"]);
+
+       if ($item["coord"] != "") {
+               $coords = explode(' ', $item["coord"]);
+               if (count($coords) == 2) {
+                       if ($type == "json") {
+                               $status["geo"] = ['type' => 'Point',
+                                       'coordinates' => [(float) $coords[0],
+                                               (float) $coords[1]]];
+                       } else {// Not sure if this is the official format - if someone founds a documentation we can check
+                               $status["georss:point"] = $item["coord"];
                        }
                }
-               $ret[] = $status;
-       };
-       return $ret;
+       }
+
+       return $status;
 }
 
 /**
@@ -3010,6 +3082,7 @@ function api_format_items($r, $user_info, $filter_user = false, $type = "json")
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws Exception
  */
 function api_account_rate_limit_status($type)
 {
@@ -3085,6 +3158,11 @@ api_register_func('api/lists/subscriptions', 'api_lists_list', true);
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @see https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-ownerships
  */
 function api_lists_ownerships($type)
@@ -3129,6 +3207,11 @@ api_register_func('api/lists/ownerships', 'api_lists_ownerships', true);
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @see https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/get-lists-ownerships
  */
 function api_lists_statuses($type)
@@ -3206,7 +3289,11 @@ api_register_func('api/lists/statuses', 'api_lists_statuses', true);
  *
  * @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)
 {
@@ -3290,12 +3377,14 @@ function api_statuses_f($qtype)
 /**
  * Returns the user's friends.
  *
- * @brief Returns the list of friends of the provided user
+ * @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
+ * @throws BadRequestException
+ * @throws ForbiddenException
  */
 function api_statuses_friends($type)
 {
@@ -3309,12 +3398,14 @@ function api_statuses_friends($type)
 /**
  * Returns the user's followers.
  *
- * @brief Returns the list of followers of the provided user
+ * @brief      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)
 {
@@ -3337,6 +3428,8 @@ api_register_func('api/statuses/followers', 'api_statuses_followers', true);
  * @param string $type Either "json" or "xml"
  *
  * @return boolean|string|array
+ * @throws BadRequestException
+ * @throws ForbiddenException
  */
 function api_blocks_list($type)
 {
@@ -3358,6 +3451,8 @@ api_register_func('api/blocks/list', 'api_blocks_list', true);
  * @param string $type Either "json" or "xml"
  *
  * @return boolean|string|array
+ * @throws BadRequestException
+ * @throws ForbiddenException
  */
 function api_friendships_incoming($type)
 {
@@ -3383,6 +3478,7 @@ api_register_func('api/friendships/incoming', 'api_friendships_incoming', true);
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws InternalServerErrorException
  */
 function api_statusnet_config($type)
 {
@@ -3392,7 +3488,7 @@ function api_statusnet_config($type)
        $server    = $a->getHostName();
        $logo      = System::baseUrl() . '/images/friendica-64.png';
        $email     = Config::get('config', 'admin_email');
-       $closed    = intval(Config::get('config', 'register_policy')) === REGISTER_CLOSED ? 'true' : 'false';
+       $closed    = intval(Config::get('config', 'register_policy')) === \Friendica\Module\Register::CLOSED ? 'true' : 'false';
        $private   = Config::get('system', 'block_public') ? 'true' : 'false';
        $textlimit = (string) Config::get('config', 'api_import_size', Config::get('config', 'max_import_size', 200000));
        $ssl       = Config::get('system', 'have_ssl') ? 'true' : 'false';
@@ -3442,6 +3538,12 @@ api_register_func('api/statusnet/version', 'api_statusnet_version', false);
  *
  * @param string $type Return type (atom, rss, xml, json)
  *
+ * @return array|string|void
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @todo use api_format_data() to return data
  */
 function api_ff_ids($type)
@@ -3484,6 +3586,8 @@ function api_ff_ids($type)
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
  * @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids
  */
 function api_friends_ids($type)
@@ -3497,6 +3601,8 @@ function api_friends_ids($type)
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
  * @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids
  */
 function api_followers_ids($type)
@@ -3514,6 +3620,12 @@ api_register_func('api/followers/ids', 'api_followers_ids', true);
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws NotFoundException
+ * @throws UnauthorizedException
  * @see https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/new-message
  */
 function api_direct_messages_new($type)
@@ -3553,7 +3665,6 @@ function api_direct_messages_new($type)
        }
 
        $replyto = '';
-       $sub     = '';
        if (!empty($_REQUEST['replyto'])) {
                $r = q(
                        'SELECT `parent-uri`, `title` FROM `mail` WHERE `uid`=%d AND `id`=%d',
@@ -3602,7 +3713,12 @@ api_register_func('api/direct_messages/new', 'api_direct_messages_new', true, AP
  *
  * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
  * @return string|array
- * @see https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/delete-message
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
+ * @see   https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/delete-message
  */
 function api_direct_messages_destroy($type)
 {
@@ -3683,12 +3799,16 @@ api_register_func('api/direct_messages/destroy', 'api_direct_messages_destroy',
  *
  * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
  * @return string|array
- * @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-destroy.html
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws NotFoundException
+ * @see   https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-destroy.html
  */
 function api_friendships_destroy($type)
 {
        $uid = api_user();
-       $a = get_app();
 
        if ($uid === false) {
                throw new ForbiddenException();
@@ -3697,7 +3817,7 @@ function api_friendships_destroy($type)
        $contact_id = defaults($_REQUEST, 'user_id');
 
        if (empty($contact_id)) {
-               $a->getLogger()->notice('No user_id specified', ['module' => 'api', 'action' => 'friendships_destroy']);
+               Logger::notice(API_LOG_PREFIX . 'No user_id specified', ['module' => 'api', 'action' => 'friendships_destroy']);
                throw new BadRequestException("no user_id specified");
        }
 
@@ -3705,7 +3825,7 @@ function api_friendships_destroy($type)
        $contact = DBA::selectFirst('contact', ['url'], ['id' => $contact_id, 'uid' => 0, 'self' => false]);
 
        if(!DBA::isResult($contact)) {
-               $a->getLogger()->notice('No contact found for ID', ['module' => 'api', 'action' => 'friendships_destroy', 'contact' => $contact_id]);
+               Logger::notice(API_LOG_PREFIX . 'No contact found for ID {contact}', ['module' => 'api', 'action' => 'friendships_destroy', 'contact' => $contact_id]);
                throw new NotFoundException("no contact found to given ID");
        }
 
@@ -3717,12 +3837,12 @@ function api_friendships_destroy($type)
        $contact = DBA::selectFirst('contact', [], $condition);
 
        if (!DBA::isResult($contact)) {
-               $a->getLogger()->notice('Not following contact', ['module' => 'api', 'action' => 'friendships_destroy']);
+               Logger::notice(API_LOG_PREFIX . 'Not following contact', ['module' => 'api', 'action' => 'friendships_destroy']);
                throw new NotFoundException("Not following Contact");
        }
 
        if (!in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
-               $a->getLogger()->notice('Not supported', ['module' => 'api', 'action' => 'friendships_destroy', 'network' => $contact['network']]);
+               Logger::notice(API_LOG_PREFIX . 'Not supported for {network}', ['module' => 'api', 'action' => 'friendships_destroy', 'network' => $contact['network']]);
                throw new ExpectationFailedException("Not supported");
        }
 
@@ -3733,7 +3853,7 @@ function api_friendships_destroy($type)
                Contact::terminateFriendship($owner, $contact, $dissolve);
        }
        else {
-               $a->getLogger()->notice('No owner found', ['module' => 'api', 'action' => 'friendships_destroy', 'uid' => $uid]);
+               Logger::notice(API_LOG_PREFIX . 'No owner {uid} found', ['module' => 'api', 'action' => 'friendships_destroy', 'uid' => $uid]);
                throw new NotFoundException("Error Processing Request");
        }
 
@@ -3762,6 +3882,11 @@ api_register_func('api/friendships/destroy', 'api_friendships_destroy', true, AP
  * @param string $verbose
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_direct_messages_box($type, $box, $verbose)
 {
@@ -3867,6 +3992,8 @@ function api_direct_messages_box($type, $box, $verbose)
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
  * @see https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-sent-message
  */
 function api_direct_messages_sentbox($type)
@@ -3881,6 +4008,8 @@ function api_direct_messages_sentbox($type)
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
  * @see https://developer.twitter.com/en/docs/direct-messages/sending-and-receiving/api-reference/get-messages
  */
 function api_direct_messages_inbox($type)
@@ -3894,6 +4023,8 @@ function api_direct_messages_inbox($type)
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
  */
 function api_direct_messages_all($type)
 {
@@ -3906,6 +4037,8 @@ function api_direct_messages_all($type)
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
  */
 function api_direct_messages_conversation($type)
 {
@@ -3966,6 +4099,9 @@ api_register_func('api/oauth/access_token', 'api_oauth_access_token', false);
  *
  * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
  * @return string|array
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws InternalServerErrorException
  */
 function api_fr_photoalbum_delete($type)
 {
@@ -4018,6 +4154,9 @@ function api_fr_photoalbum_delete($type)
  *
  * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
  * @return string|array
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws InternalServerErrorException
  */
 function api_fr_photoalbum_update($type)
 {
@@ -4057,6 +4196,8 @@ function api_fr_photoalbum_update($type)
  *
  * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
  * @return string|array
+ * @throws ForbiddenException
+ * @throws InternalServerErrorException
  */
 function api_fr_photos_list($type)
 {
@@ -4103,6 +4244,11 @@ function api_fr_photos_list($type)
  *
  * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
  * @return string|array
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws NotFoundException
  */
 function api_fr_photo_create_update($type)
 {
@@ -4237,12 +4383,16 @@ function api_fr_photo_create_update($type)
  *
  * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
  * @return string|array
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws InternalServerErrorException
  */
 function api_fr_photo_delete($type)
 {
        if (api_user() === false) {
                throw new ForbiddenException();
        }
+
        // input params
        $photo_id = defaults($_REQUEST, 'photo_id', null);
 
@@ -4251,11 +4401,12 @@ function api_fr_photo_delete($type)
        if ($photo_id == null) {
                throw new BadRequestException("no photo_id specified");
        }
+
        // check if photo is existing in database
-       $r = Photo::exists(['resource-id' => $photo_id, 'uid' => api_user()]);
-       if (!$r) {
+       if (!Photo::exists(['resource-id' => $photo_id, 'uid' => api_user()])) {
                throw new BadRequestException("photo not available");
        }
+
        // now we can perform on the deletion of the photo
        $result = Photo::delete(['uid' => api_user(), 'resource-id' => $photo_id]);
 
@@ -4285,6 +4436,10 @@ function api_fr_photo_delete($type)
  *
  * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
  * @return string|array
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws InternalServerErrorException
+ * @throws NotFoundException
  */
 function api_fr_photo_detail($type)
 {
@@ -4313,7 +4468,12 @@ function api_fr_photo_detail($type)
  * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
  *
  * @return string|array
- * @see https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile_image
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws NotFoundException
+ * @see   https://developer.twitter.com/en/docs/accounts-and-users/manage-account-settings/api-reference/post-account-update_profile_image
  */
 function api_account_update_profile_image($type)
 {
@@ -4369,8 +4529,8 @@ function api_account_update_profile_image($type)
                $condition = ["`profile` AND `resource-id` != ? AND `uid` = ?", $data['photo']['id'], api_user()];
                Photo::update(['profile' => false], $condition);
        } else {
-               $fields = ['photo' => System::baseUrl() . '/photo/' . $data['photo']['id'] . '-4.' . $filetype,
-                       'thumb' => System::baseUrl() . '/photo/' . $data['photo']['id'] . '-5.' . $filetype];
+               $fields = ['photo' => System::baseUrl() . '/photo/' . $data['photo']['id'] . '-4.' . $fileext,
+                       'thumb' => System::baseUrl() . '/photo/' . $data['photo']['id'] . '-5.' . $fileext];
                DBA::update('profile', $fields, ['id' => $_REQUEST['profile'], 'uid' => api_user()]);
        }
 
@@ -4409,6 +4569,11 @@ api_register_func('api/account/update_profile_image', 'api_account_update_profil
  * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_account_update_profile($type)
 {
@@ -4443,6 +4608,8 @@ api_register_func('api/account/update_profile', 'api_account_update_profile', tr
 /**
  *
  * @param string $acl_string
+ * @return bool
+ * @throws Exception
  */
 function check_acl_input($acl_string)
 {
@@ -4479,6 +4646,12 @@ function check_acl_input($acl_string)
  * @param integer $profile
  * @param boolean $visibility
  * @param string  $photo_id
+ * @return array
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws NotFoundException
  */
 function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $desc, $profile = 0, $visibility = false, $photo_id = null)
 {
@@ -4637,6 +4810,7 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
  * @param string  $deny_gid
  * @param string  $filetype
  * @param boolean $visibility
+ * @throws InternalServerErrorException
  */
 function post_photo_item($hash, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $filetype, $visibility = false)
 {
@@ -4689,6 +4863,12 @@ function post_photo_item($hash, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $f
  * @param string $photo_id
  *
  * @return array
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws NotFoundException
+ * @throws UnauthorizedException
  */
 function prepare_photo_data($type, $scale, $photo_id)
 {
@@ -4840,8 +5020,7 @@ function api_friendica_remoteauth()
                'sec' => $sec, 'expire' => time() + 45];
        DBA::insert('profile_check', $fields);
 
-       $a = get_app();
-       $a->getLogger()->info('API: friendica_remoteauth', ['module' => 'api', 'action' => 'remoteauth', 'contact' => $contact['name'], 'hey' => $sec]);
+       Logger::info(API_LOG_PREFIX . 'for contact {contact}', ['module' => 'api', 'action' => 'friendica_remoteauth', 'contact' => $contact['name'], 'hey' => $sec]);
        $dest = ($url ? '&destination_url=' . $url : '');
 
        System::externalRedirect(
@@ -4852,11 +5031,47 @@ function api_friendica_remoteauth()
 }
 api_register_func('api/friendica/remoteauth', 'api_friendica_remoteauth', true);
 
+/**
+ * Return an item with announcer data if it had been announced
+ *
+ * @param array $item Item array
+ * @return array Item array with announce data
+ */
+function api_get_announce($item)
+{
+       // Quit if the item already has got a different owner and author
+       if ($item['owner-id'] != $item['author-id']) {
+               return [];
+       }
+
+       // Don't change original or Diaspora posts
+       if ($item['origin'] || in_array($item['network'], [Protocol::DIASPORA])) {
+               return [];
+       }
+
+       // Quit if we do now the original author and it had been a post from a native network
+       if (!empty($item['contact-uid']) && in_array($item['network'], Protocol::NATIVE_SUPPORT)) {
+               return [];
+       }
+
+       $fields = ['author-id', 'author-name', 'author-link', 'author-avatar'];
+       $activity = Item::activityToIndex(ACTIVITY2_ANNOUNCE);
+       $condition = ['parent-uri' => $item['uri'], 'gravity' => GRAVITY_ACTIVITY, 'uid' => [0, $item['uid']], 'activity' => $activity];
+       $announce = Item::selectFirstForUser($item['uid'], $fields, $condition, ['order' => ['created' => true]]);
+       if (!DBA::isResult($announce)) {
+               return [];
+       }
+
+       return array_merge($item, $announce);
+}
+
 /**
  * @brief Return the item shared, if the item contains only the [share] tag
  *
  * @param array $item Sharer item
  * @return array|false Shared item or false if not a reshare
+ * @throws ImagickException
+ * @throws InternalServerErrorException
  */
 function api_share_as_retweet(&$item)
 {
@@ -4945,7 +5160,12 @@ function api_share_as_retweet(&$item)
                $posted = $matches[1];
        }
 
-       $shared_body = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "$2", $body);
+       $pre_body = trim(preg_replace("/(.*?)\[share.*?\]\s?.*?\s?\[\/share\]\s?/ism", "$1", $body));
+       if ($pre_body != '') {
+               $item['body'] = $pre_body;
+       }
+
+       $shared_body = trim(preg_replace("/(.*?)\[share.*?\]\s?(.*?)\s?\[\/share\]\s?/ism", "$2", $body));
 
        if (($shared_body == "") || ($profile == "") || ($author == "") || ($avatar == "") || ($posted == "")) {
                return false;
@@ -4968,6 +5188,7 @@ function api_share_as_retweet(&$item)
  * @param string $profile
  *
  * @return string|false
+ * @throws InternalServerErrorException
  * @todo remove trailing junk from profile url
  * @todo pump.io check has to check the website
  */
@@ -5052,6 +5273,7 @@ function api_get_nick($profile)
  * @param array $item
  *
  * @return array
+ * @throws Exception
  */
 function api_in_reply_to($item)
 {
@@ -5090,8 +5312,7 @@ function api_in_reply_to($item)
                // https://github.com/friendica/friendica/issues/1010
                // This is a bugfix for that.
                if (intval($in_reply_to['status_id']) == intval($item['id'])) {
-                       $a = get_app();
-                       $a->getLogger()->warning('ID is similar to reply-to', ['module' => 'api', 'action' => 'in_reply_to', 'id' => $item['id'], 'reply-to' => $in_reply_to['status_id']]);
+                       Logger::warning(API_LOG_PREFIX . 'ID {id} is similar to reply-to {reply-to}', ['module' => 'api', 'action' => 'in_reply_to', 'id' => $item['id'], 'reply-to' => $in_reply_to['status_id']]);
                        $in_reply_to['status_id'] = null;
                        $in_reply_to['user_id'] = null;
                        $in_reply_to['status_id_str'] = null;
@@ -5108,6 +5329,7 @@ function api_in_reply_to($item)
  * @param string $text
  *
  * @return string
+ * @throws InternalServerErrorException
  */
 function api_clean_plain_items($text)
 {
@@ -5134,6 +5356,7 @@ function api_clean_plain_items($text)
  * @param string $body The original body
  *
  * @return string Cleaned body
+ * @throws InternalServerErrorException
  */
 function api_clean_attachments($body)
 {
@@ -5162,7 +5385,7 @@ function api_clean_attachments($body)
  *
  * @param array $contacts
  *
- * @return array
+ * @return void
  */
 function api_best_nickname(&$contacts)
 {
@@ -5232,6 +5455,11 @@ function api_best_nickname(&$contacts)
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_friendica_group_show($type)
 {
@@ -5297,6 +5525,11 @@ api_register_func('api/friendica/group_show', 'api_friendica_group_show', true);
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_friendica_group_delete($type)
 {
@@ -5358,6 +5591,11 @@ api_register_func('api/friendica/group_delete', 'api_friendica_group_delete', tr
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @see https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-destroy
  */
 function api_lists_destroy($type)
@@ -5402,10 +5640,11 @@ api_register_func('api/lists/destroy', 'api_lists_destroy', true, API_METHOD_DEL
  * Add a new group to the database.
  *
  * @param  string $name  Group name
- * @param  int   $uid   User ID
+ * @param  int    $uid   User ID
  * @param  array  $users List of users to add to the group
  *
  * @return array
+ * @throws BadRequestException
  */
 function group_create($name, $uid, $users = [])
 {
@@ -5475,6 +5714,11 @@ function group_create($name, $uid, $users = [])
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_friendica_group_create($type)
 {
@@ -5503,6 +5747,11 @@ api_register_func('api/friendica/group_create', 'api_friendica_group_create', tr
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @see https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-create
  */
 function api_lists_create($type)
@@ -5538,6 +5787,11 @@ api_register_func('api/lists/create', 'api_lists_create', true, API_METHOD_POST)
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_friendica_group_update($type)
 {
@@ -5611,6 +5865,11 @@ api_register_func('api/friendica/group_update', 'api_friendica_group_update', tr
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  * @see https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-update
  */
 function api_lists_update($type)
@@ -5658,6 +5917,10 @@ api_register_func('api/lists/update', 'api_lists_update', true, API_METHOD_POST)
  * @param string $type Return type (atom, rss, xml, json)
  *
  * @return array|string
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
  */
 function api_friendica_activity($type)
 {
@@ -5702,7 +5965,10 @@ api_register_func('api/friendica/activity/unattendmaybe', 'api_friendica_activit
  *
  * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
  * @return string|array
-*/
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws InternalServerErrorException
+ */
 function api_friendica_notification($type)
 {
        $a = \get_app();
@@ -5715,7 +5981,7 @@ function api_friendica_notification($type)
        }
        $nm = new NotificationsManager();
 
-       $notes = $nm->getAll([], "+seen -date", 50);
+       $notes = $nm->getAll([], ['seen' => 'ASC', 'date' => 'DESC'], 50);
 
        if ($type == "xml") {
                $xmlnotes = [];
@@ -5737,6 +6003,11 @@ function api_friendica_notification($type)
  *
  * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
  * @return string|array
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_friendica_notification_seen($type)
 {
@@ -5782,6 +6053,11 @@ api_register_func('api/friendica/notification', 'api_friendica_notification', tr
  *
  * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
  * @return string|array (success result=ok, error result=error with error message)
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_friendica_direct_messages_setseen($type)
 {
@@ -5826,11 +6102,16 @@ api_register_func('api/friendica/direct_messages_setseen', 'api_friendica_direct
 /**
  * @brief search for direct_messages containing a searchstring through api
  *
- * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
+ * @param string $type      Known types are 'atom', 'rss', 'xml' and 'json'
  * @param string $box
  * @return string|array (success: success=true if found and search_result contains found messages,
  *                          success=false if nothing was found, search_result='nothing found',
- *                error: result=error with error message)
+ *                          error: result=error with error message)
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_friendica_direct_messages_search($type, $box = "")
 {
@@ -5894,6 +6175,11 @@ api_register_func('api/friendica/direct_messages_search', 'api_friendica_direct_
  *
  * @param string $type Known types are 'atom', 'rss', 'xml' and 'json'
  * @return string|array
+ * @throws BadRequestException
+ * @throws ForbiddenException
+ * @throws ImagickException
+ * @throws InternalServerErrorException
+ * @throws UnauthorizedException
  */
 function api_friendica_profile_show($type)
 {
@@ -5975,6 +6261,7 @@ api_register_func('api/friendica/profile/show', 'api_friendica_profile_show', tr
  * @param  string $type Return format: json or xml
  *
  * @return string|array
+ * @throws Exception
  */
 function api_saved_searches_list($type)
 {