X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=include%2Fapi.php;h=9de054e0a0122cffcfaa2a8b9ad073de4b97485f;hb=4c1125562472ac509a478af7a40256687d3afa91;hp=6ca47c01dd5e28acb93b7fac309a8cb76d9df7d8;hpb=5a3991d4f7bc929c1087d9275716fc1c8cc299a6;p=friendica.git diff --git a/include/api.php b/include/api.php index 6ca47c01dd..9de054e0a0 100644 --- a/include/api.php +++ b/include/api.php @@ -11,9 +11,9 @@ use Friendica\Content\ContactSelector; use Friendica\Content\Feature; use Friendica\Content\Text\BBCode; use Friendica\Content\Text\HTML; -use Friendica\Core\Addon; use Friendica\Core\Authentication; use Friendica\Core\Config; +use Friendica\Core\Hook; use Friendica\Core\L10n; use Friendica\Core\Logger; use Friendica\Core\NotificationsManager; @@ -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; @@ -46,7 +47,6 @@ use Friendica\Util\Proxy as ProxyUtils; use Friendica\Util\Strings; use Friendica\Util\XML; -require_once 'include/conversation.php'; require_once 'mod/share.php'; require_once 'mod/item.php'; require_once 'mod/wall_upload.php'; @@ -56,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 = []; @@ -84,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() { @@ -98,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"; @@ -111,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) { @@ -156,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) { @@ -175,14 +181,14 @@ function api_login(App $a) list($consumer, $token) = $oauth1->verify_request($request); if (!is_null($token)) { $oauth1->loginUser($token->uid); - Addon::callHooks('logged_in', $a->user); + Hook::callAll('logged_in', $a->user); return; } echo __FILE__.__LINE__.__FUNCTION__ . "
"; var_dump($consumer, $token); die(); } catch (Exception $e) { - Logger::log($e); + Logger::warning(API_LOG_PREFIX . 'error', ['module' => 'api', 'action' => 'login', 'exception' => $e->getMessage()]); } // workaround for HTTP-auth in CGI mode @@ -196,7 +202,7 @@ function api_login(App $a) } if (empty($_SERVER['PHP_AUTH_USER'])) { - Logger::log('API_login: ' . print_r($_SERVER, true), Logger::DEBUG); + 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"); } @@ -225,7 +231,7 @@ function api_login(App $a) * Addons should never set 'authenticated' except to indicate success - as hooks may be chained * and later addons should not interfere with an earlier one that succeeded. */ - Addon::callHooks('authenticate', $addon_auth); + Hook::callAll('authenticate', $addon_auth); if ($addon_auth['authenticated'] && count($addon_auth['user_record'])) { $record = $addon_auth['user_record']; @@ -237,7 +243,7 @@ function api_login(App $a) } if (!DBA::isResult($record)) { - Logger::log('API_login failure: ' . print_r($_SERVER, true), Logger::DEBUG); + 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'); @@ -248,7 +254,7 @@ function api_login(App $a) $_SESSION["allow_api"] = true; - Addon::callHooks('logged_in', $a->user); + Hook::callAll('logged_in', $a->user); } /** @@ -274,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) { @@ -310,76 +317,16 @@ function api_call(App $a) api_login($a); } - Logger::log('API call for ' . $a->user['username'] . ': ' . $a->query_string); - Logger::log('API parameters: ' . print_r($_REQUEST, true)); + 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); - Logger::log("API call duration: " . round($duration, 2) . "\t" . $a->query_string, Logger::DEBUG); - - if (Config::get("system", "profiler")) { - $duration = microtime(true)-$a->performance["start"]; - - /// @TODO round() really everywhere? - Logger::log( - parse_url($a->query_string, PHP_URL_PATH) . ": " . sprintf( - "Database: %s/%s, Cache %s/%s, Network: %s, I/O: %s, Other: %s, Total: %s", - round($a->performance["database"] - $a->performance["database_write"], 3), - round($a->performance["database_write"], 3), - round($a->performance["cache"], 3), - round($a->performance["cache_write"], 3), - round($a->performance["network"], 2), - round($a->performance["file"], 2), - round($duration - ($a->performance["database"] - + $a->performance["cache"] + $a->performance["cache_write"] - + $a->performance["network"] + $a->performance["file"]), 2), - round($duration, 2) - ), - Logger::DEBUG - ); - - 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"; - } - } - $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"; - } - } + Logger::info(API_LOG_PREFIX . 'username {username}', ['module' => 'api', 'action' => 'call', 'username' => $a->user['username'], 'duration' => round($duration, 2)]); - $o .= "\nNetwork:\n"; - foreach ($a->callstack["network"] as $func => $time) { - $time = round($time, 3); - if ($time > 0) { - $o .= $func . ": " . $time . "\n"; - } - } - Logger::log($o, Logger::DEBUG); - } - } + $a->getProfiler()->saveLog($a->getLogger(), API_LOG_PREFIX . 'performance'); if (false === $return) { /* @@ -414,7 +361,7 @@ function api_call(App $a) } } - Logger::log('API call not implemented: ' . $a->query_string); + 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}"); @@ -431,7 +378,7 @@ function api_call(App $a) */ function api_error($type, $e) { - $a = get_app(); + $a = \get_app(); $error = ($e->getMessage() !== "" ? $e->getMessage() : $e->httpdesc); /// @TODO: https://dev.twitter.com/overview/api/response-codes @@ -464,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) { @@ -496,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) { @@ -512,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) { @@ -523,7 +480,7 @@ function api_get_user(App $a, $contact_id = null) $extra_query = ""; $url = ""; - Logger::log("api_get_user: Fetching user data for user ".$contact_id, Logger::DEBUG); + 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)) { @@ -607,7 +564,7 @@ function api_get_user(App $a, $contact_id = null) } } - Logger::log("api_get_user: user ".$user, Logger::DEBUG); + Logger::info(API_LOG_PREFIX . 'getting user {user}', ['module' => 'api', 'action' => 'get_user', 'user' => $user]); if (!$user) { if (api_user() === false) { @@ -619,7 +576,7 @@ function api_get_user(App $a, $contact_id = null) } } - Logger::log('api_user: ' . $extra_query . ', user: ' . $user); + Logger::info(API_LOG_PREFIX . 'found user {user}', ['module' => 'api', 'action' => 'get_user', 'user' => $user, 'extra_query' => $extra_query]); // user info $uinfo = q( @@ -643,8 +600,6 @@ function api_get_user(App $a, $contact_id = null) $contact = DBA::selectFirst('contact', [], ['uid' => 0, 'nurl' => Strings::normaliseLink($url)]); if (DBA::isResult($contact)) { - $network_name = ContactSelector::networkToName($contact['network'], $contact['url']); - // If no nick where given, extract it from the address if (($contact['nick'] == "") || ($contact['name'] == $contact['nick'])) { $contact['nick'] = api_get_nick($contact["url"]); @@ -655,7 +610,7 @@ function api_get_user(App $a, $contact_id = null) 'id_str' => (string) $contact["id"], 'name' => $contact["name"], 'screen_name' => (($contact['nick']) ? $contact['nick'] : $contact['name']), - 'location' => ($contact["location"] != "") ? $contact["location"] : $network_name, + 'location' => ($contact["location"] != "") ? $contact["location"] : ContactSelector::networkToName($contact['network'], $contact['url']), 'description' => $contact["about"], 'profile_image_url' => $contact["micro"], 'profile_image_url_https' => $contact["micro"], @@ -713,8 +668,6 @@ function api_get_user(App $a, $contact_id = null) $uinfo[0]['nick'] = api_get_nick($uinfo[0]["url"]); } - $network_name = ContactSelector::networkToName($uinfo[0]['network'], $uinfo[0]['url']); - $pcontact_id = Contact::getIdForURL($uinfo[0]['url'], 0, true); if (!empty($profile['about'])) { @@ -728,7 +681,7 @@ function api_get_user(App $a, $contact_id = null) } elseif (!empty($uinfo[0]["location"])) { $location = $uinfo[0]["location"]; } else { - $location = $network_name; + $location = ContactSelector::networkToName($uinfo[0]['network'], $uinfo[0]['url']); } $ret = [ @@ -810,9 +763,13 @@ 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) { @@ -832,8 +789,8 @@ function api_item_get_user(App $a, $item) /** * @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 */ @@ -935,7 +892,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) { @@ -950,7 +907,6 @@ function api_format_data($root_element, $type, $data) $ret = $data; break; } - return $ret; } @@ -961,14 +917,21 @@ 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(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -1011,6 +974,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) { @@ -1029,10 +993,15 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { Logger::log('api_statuses_update: no user'); @@ -1076,11 +1045,17 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { Logger::log('api_statuses_update: no user'); @@ -1218,11 +1193,16 @@ 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() { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { Logger::log('no user'); @@ -1247,8 +1227,9 @@ function api_media_upload() $returndata["media_id_string"] = (string)$media["id"]; $returndata["size"] = $media["size"]; $returndata["image"] = ["w" => $media["width"], - "h" => $media["height"], - "image_type" => $media["type"]]; + "h" => $media["height"], + "image_type" => $media["type"], + "friendica_preview_url" => $media["preview"]]; Logger::log("Media uploaded: " . print_r($returndata, true), Logger::DEBUG); @@ -1262,22 +1243,21 @@ api_register_func('api/media/upload', 'api_media_upload', true, API_METHOD_POST) * * @param string $type Return type (atom, rss, xml, json) * + * @param int $item_id * @return array|string + * @throws BadRequestException + * @throws ImagickException + * @throws InternalServerErrorException + * @throws UnauthorizedException */ function api_status_show($type, $item_id = 0) { - $a = get_app(); + $a = \get_app(); $user_info = api_get_user($a); Logger::log('api_status_show: user_info: '.print_r($user_info, true), Logger::DEBUG); - if ($type == "raw") { - $privacy_sql = "AND NOT `private`"; - } else { - $privacy_sql = ""; - } - if (!empty($item_id)) { // Get the item with the given id $condition = ['id' => $item_id]; @@ -1286,6 +1266,11 @@ function api_status_show($type, $item_id = 0) $condition = ['owner-id' => $user_info['pid'], 'uid' => api_user(), 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]]; } + + if ($type == "raw") { + $condition['private'] = false; + } + $lastwall = Item::selectFirst(Item::ITEM_FIELDLIST, $condition, ['order' => ['id' => true]]); if (DBA::isResult($lastwall)) { @@ -1361,11 +1346,16 @@ 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 = \get_app(); $user_info = api_get_user($a); @@ -1437,11 +1427,15 @@ 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) { - $a = get_app(); + $a = \get_app(); $userlist = []; @@ -1485,7 +1479,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) { @@ -1518,52 +1516,85 @@ 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) { - $a = get_app(); + $a = \get_app(); $user_info = api_get_user($a); - if (api_user() === false || $user_info === false) { - throw new ForbiddenException(); - } - - $data = []; + if (api_user() === false || $user_info === false) { throw new ForbiddenException(); } if (empty($_REQUEST['q'])) { - throw new BadRequestException("q parameter is required."); + throw new BadRequestException('q parameter is required.'); } + $searchTerm = trim(rawurldecode($_REQUEST['q'])); + + $data = []; + $data['status'] = []; + $count = 15; + $exclude_replies = !empty($_REQUEST['exclude_replies']); if (!empty($_REQUEST['rpp'])) { $count = $_REQUEST['rpp']; } elseif (!empty($_REQUEST['count'])) { $count = $_REQUEST['count']; - } else { - $count = 15; } - + $since_id = defaults($_REQUEST, 'since_id', 0); $max_id = defaults($_REQUEST, 'max_id', 0); $page = (!empty($_REQUEST['page']) ? $_REQUEST['page'] - 1 : 0); - $start = $page * $count; + $params = ['order' => ['id' => true], 'limit' => [$start, $count]]; + if (preg_match('/^#(\w+)$/', $searchTerm, $matches) === 1 && isset($matches[1])) { + $searchTerm = $matches[1]; + $condition = ["`oid` > ? + AND (`uid` = 0 OR (`uid` = ? AND NOT `global`)) + AND `otype` = ? AND `type` = ? AND `term` = ?", + $since_id, local_user(), TERM_OBJ_POST, TERM_HASHTAG, $searchTerm]; + if ($max_id > 0) { + $condition[0] .= ' AND `oid` <= ?'; + $condition[] = $max_id; + } + $terms = DBA::select('term', ['oid'], $condition, []); + $itemIds = []; + while ($term = DBA::fetch($terms)) { + $itemIds[] = $term['oid']; + } + DBA::close($terms); + + if (empty($itemIds)) { + return api_format_data('statuses', $type, $data); + } - $condition = ["`gravity` IN (?, ?) AND `item`.`id` > ? - AND (`item`.`uid` = 0 OR (`item`.`uid` = ? AND NOT `item`.`global`)) - AND `item`.`body` LIKE CONCAT('%',?,'%')", - GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, api_user(), $_REQUEST['q']]; + $preCondition = ['`id` IN (' . implode(', ', $itemIds) . ')']; + if ($exclude_replies) { + $preCondition[] = '`id` = `parent`'; + } - if ($max_id > 0) { - $condition[0] .= " AND `item`.`id` <= ?"; - $condition[] = $max_id; + $condition = [implode(' AND ', $preCondition)]; + } else { + $condition = ["`id` > ? + " . ($exclude_replies ? " AND `id` = `parent` " : ' ') . " + AND (`uid` = 0 OR (`uid` = ? AND NOT `global`)) + AND `body` LIKE CONCAT('%',?,'%')", + $since_id, api_user(), $_REQUEST['q']]; + if ($max_id > 0) { + $condition[0] .= ' AND `id` <= ?'; + $condition[] = $max_id; + } } - $params = ['order' => ['id' => true], 'limit' => [$start, $count]]; $statuses = Item::selectForUser(api_user(), [], $condition, $params); $data['status'] = api_format_items(Item::inArray($statuses), $user_info); - return api_format_data("statuses", $type, $data); + bindComments($data['status']); + + return api_format_data('statuses', $type, $data); } /// @TODO move to top of file or somewhere better @@ -1573,16 +1604,22 @@ 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 */ function api_statuses_home_timeline($type) { - $a = get_app(); + $a = \get_app(); $user_info = api_get_user($a); if (api_user() === false || $user_info === false) { @@ -1644,6 +1681,8 @@ function api_statuses_home_timeline($type) Item::update(['unseen' => false], ['unseen' => true, 'id' => $idarray]); } } + + bindComments($ret); $data = ['status' => $ret]; switch ($type) { @@ -1657,6 +1696,7 @@ function api_statuses_home_timeline($type) return api_format_data("statuses", $type, $data); } + /// @TODO move to top of file or somewhere better api_register_func('api/statuses/home_timeline', 'api_statuses_home_timeline', true); api_register_func('api/statuses/friends_timeline', 'api_statuses_home_timeline', true); @@ -1667,10 +1707,15 @@ 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) { - $a = get_app(); + $a = \get_app(); $user_info = api_get_user($a); if (api_user() === false || $user_info === false) { @@ -1693,7 +1738,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) { @@ -1706,7 +1751,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) { @@ -1726,6 +1771,8 @@ function api_statuses_public_timeline($type) $ret = api_format_items($r, $user_info, false, $type); + bindComments($ret); + $data = ['status' => $ret]; switch ($type) { case "atom": @@ -1748,11 +1795,15 @@ 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) { - $a = get_app(); + $a = \get_app(); $user_info = api_get_user($a); if (api_user() === false || $user_info === false) { @@ -1783,6 +1834,8 @@ function api_statuses_networkpublic_timeline($type) $ret = api_format_items(Item::inArray($statuses), $user_info, false, $type); + bindComments($ret); + $data = ['status' => $ret]; switch ($type) { case "atom": @@ -1803,11 +1856,17 @@ 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) { - $a = get_app(); + $a = \get_app(); $user_info = api_get_user($a); if (api_user() === false || $user_info === false) { @@ -1876,11 +1935,17 @@ 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) { - $a = get_app(); + $a = \get_app(); $user_info = api_get_user($a); if (api_user() === false || $user_info === false) { @@ -1908,7 +1973,7 @@ function api_conversation_show($type) $id = intval(defaults($a->argv, 4, 0)); } - Logger::log('API: api_conversation_show: '.$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]); @@ -1953,13 +2018,19 @@ 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) { global $called_api; - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -2020,11 +2091,17 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -2061,11 +2138,17 @@ 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) { - $a = get_app(); + $a = \get_app(); $user_info = api_get_user($a); if (api_user() === false || $user_info === false) { @@ -2128,12 +2211,16 @@ 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) { - $a = get_app(); + $a = \get_app(); $user_info = api_get_user($a); if (api_user() === false || $user_info === false) { @@ -2186,6 +2273,8 @@ function api_statuses_user_timeline($type) $ret = api_format_items(Item::inArray($statuses), $user_info, true, $type); + bindComments($ret); + $data = ['status' => $ret]; switch ($type) { case "atom": @@ -2207,11 +2296,17 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -2284,12 +2379,17 @@ 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) { global $called_api; - $a = get_app(); + $a = \get_app(); $user_info = api_get_user($a); if (api_user() === false || $user_info === false) { @@ -2300,7 +2400,7 @@ function api_favorites($type) // in friendica starred item are private // return favorites only for self - Logger::log('api_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 = []; @@ -2331,6 +2431,8 @@ function api_favorites($type) $ret = api_format_items(Item::inArray($statuses), $user_info, false, $type); } + bindComments($ret); + $data = ['status' => $ret]; switch ($type) { case "atom": @@ -2353,6 +2455,7 @@ api_register_func('api/favorites', 'api_favorites', true); * @param array $sender * * @return array + * @throws InternalServerErrorException */ function api_format_messages($item, $recipient, $sender) { @@ -2410,6 +2513,7 @@ function api_format_messages($item, $recipient, $sender) * @param array $item * * @return array + * @throws InternalServerErrorException */ function api_convert_item($item) { @@ -2485,6 +2589,7 @@ function api_convert_item($item) * @param string $body * * @return array + * @throws InternalServerErrorException */ function api_get_attachments(&$body) { @@ -2523,6 +2628,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) @@ -2667,7 +2773,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, @@ -2733,16 +2839,20 @@ 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") { - $a = get_app(); + $a = \get_app(); $activities = [ 'like' => [], @@ -2805,8 +2915,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) { @@ -2857,18 +2968,23 @@ function api_format_items_profiles($profile_row) /** * @brief format items to be returned by api * - * @param array $r array of items + * @param array $r 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") { - $a = get_app(); + $a = \get_app(); $ret = []; - foreach ($r as $item) { + foreach ((array)$r as $item) { localize_item($item); list($status_user, $owner_user) = api_item_get_user($a, $item); @@ -2975,6 +3091,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) { @@ -3050,11 +3167,16 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -3094,11 +3216,16 @@ 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) { - $a = get_app(); + $a = \get_app(); $user_info = api_get_user($a); if (api_user() === false || $user_info === false) { @@ -3171,11 +3298,15 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -3255,12 +3386,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) { @@ -3274,12 +3407,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) { @@ -3302,6 +3437,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) { @@ -3323,6 +3460,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) { @@ -3348,16 +3487,17 @@ 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) { - $a = get_app(); + $a = \get_app(); $name = Config::get('config', 'sitename'); $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'; @@ -3407,6 +3547,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) @@ -3415,7 +3561,7 @@ function api_ff_ids($type) throw new ForbiddenException(); } - $a = get_app(); + $a = \get_app(); api_get_user($a); @@ -3449,6 +3595,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) @@ -3462,6 +3610,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) @@ -3479,11 +3629,17 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -3518,7 +3674,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', @@ -3567,11 +3722,16 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -3648,7 +3808,12 @@ 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) { @@ -3661,7 +3826,7 @@ function api_friendships_destroy($type) $contact_id = defaults($_REQUEST, 'user_id'); if (empty($contact_id)) { - Logger::log("No user_id specified", Logger::DEBUG); + Logger::notice(API_LOG_PREFIX . 'No user_id specified', ['module' => 'api', 'action' => 'friendships_destroy']); throw new BadRequestException("no user_id specified"); } @@ -3669,7 +3834,7 @@ function api_friendships_destroy($type) $contact = DBA::selectFirst('contact', ['url'], ['id' => $contact_id, 'uid' => 0, 'self' => false]); if(!DBA::isResult($contact)) { - Logger::log("No contact found for ID" . $contact_id, Logger::DEBUG); + 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"); } @@ -3681,12 +3846,12 @@ function api_friendships_destroy($type) $contact = DBA::selectFirst('contact', [], $condition); if (!DBA::isResult($contact)) { - Logger::log("Not following Contact", Logger::DEBUG); + 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)) { - Logger::log("Not supported", Logger::DEBUG); + Logger::notice(API_LOG_PREFIX . 'Not supported for {network}', ['module' => 'api', 'action' => 'friendships_destroy', 'network' => $contact['network']]); throw new ExpectationFailedException("Not supported"); } @@ -3697,7 +3862,7 @@ function api_friendships_destroy($type) Contact::terminateFriendship($owner, $contact, $dissolve); } else { - Logger::log("No owner found", Logger::DEBUG); + Logger::notice(API_LOG_PREFIX . 'No owner {uid} found', ['module' => 'api', 'action' => 'friendships_destroy', 'uid' => $uid]); throw new NotFoundException("Error Processing Request"); } @@ -3726,10 +3891,15 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); } @@ -3831,6 +4001,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) @@ -3845,6 +4017,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) @@ -3858,6 +4032,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) { @@ -3870,6 +4046,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) { @@ -3895,10 +4073,10 @@ function api_oauth_request_token() $r = $oauth1->fetch_request_token(OAuthRequest::from_request()); } catch (Exception $e) { echo "error=" . OAuthUtil::urlencode_rfc3986($e->getMessage()); - killme(); + exit(); } echo $r; - killme(); + exit(); } /** @@ -3914,10 +4092,10 @@ function api_oauth_access_token() $r = $oauth1->fetch_access_token(OAuthRequest::from_request()); } catch (Exception $e) { echo "error=". OAuthUtil::urlencode_rfc3986($e->getMessage()); - killme(); + exit(); } echo $r; - killme(); + exit(); } /// @TODO move to top of file or somewhere better @@ -3930,6 +4108,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) { @@ -3966,7 +4147,7 @@ function api_fr_photoalbum_delete($type) } // now let's delete all photos from the album - $result = DBA::delete('photo', ['uid' => api_user(), 'album' => $album]); + $result = Photo::delete(['uid' => api_user(), 'album' => $album]); // return success of deletion or error message if ($result) { @@ -3982,6 +4163,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) { @@ -4000,11 +4184,11 @@ function api_fr_photoalbum_update($type) throw new BadRequestException("no new albumname specified"); } // check if album is existing - if (!DBA::exists('photo', ['uid' => api_user(), 'album' => $album])) { + if (!Photo::exists(['uid' => api_user(), 'album' => $album])) { throw new BadRequestException("album not available"); } // now let's update all photos to the albumname - $result = DBA::update('photo', ['album' => $album_new], ['uid' => api_user(), 'album' => $album]); + $result = Photo::update(['album' => $album_new], ['uid' => api_user(), 'album' => $album]); // return success of updating or error message if ($result) { @@ -4021,6 +4205,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) { @@ -4067,6 +4253,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) { @@ -4104,14 +4295,8 @@ function api_fr_photo_create_update($type) } else { $mode = "update"; - // check if photo is existing in database - $r = q( - "SELECT `id` FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s' AND `album` = '%s'", - intval(api_user()), - DBA::escape($photo_id), - DBA::escape($album) - ); - if (!DBA::isResult($r)) { + // check if photo is existing in databasei + if (!Photo::exists(['resource-id' => $photo_id, 'uid' => api_user(), 'album' => $album])) { throw new BadRequestException("photo not available"); } } @@ -4140,47 +4325,40 @@ function api_fr_photo_create_update($type) // now let's do the changes in update-mode if ($mode == "update") { - $sql_extra = ""; + $updated_fields = []; if (!is_null($desc)) { - $sql_extra .= (($sql_extra != "") ? " ," : "") . "`desc` = '$desc'"; + $updated_fields['desc'] = $desc; } if (!is_null($album_new)) { - $sql_extra .= (($sql_extra != "") ? " ," : "") . "`album` = '$album_new'"; + $updated_fields['album'] = $album_new; } if (!is_null($allow_cid)) { $allow_cid = trim($allow_cid); - $sql_extra .= (($sql_extra != "") ? " ," : "") . "`allow_cid` = '$allow_cid'"; + $updated_fields['allow_cid'] = $allow_cid; } if (!is_null($deny_cid)) { $deny_cid = trim($deny_cid); - $sql_extra .= (($sql_extra != "") ? " ," : "") . "`deny_cid` = '$deny_cid'"; + $updated_fields['deny_cid'] = $deny_cid; } if (!is_null($allow_gid)) { $allow_gid = trim($allow_gid); - $sql_extra .= (($sql_extra != "") ? " ," : "") . "`allow_gid` = '$allow_gid'"; + $updated_fields['allow_gid'] = $allow_gid; } if (!is_null($deny_gid)) { $deny_gid = trim($deny_gid); - $sql_extra .= (($sql_extra != "") ? " ," : "") . "`deny_gid` = '$deny_gid'"; + $updated_fields['deny_gid'] = $deny_gid; } $result = false; - if ($sql_extra != "") { + if (count($updated_fields) > 0) { $nothingtodo = false; - $result = q( - "UPDATE `photo` SET %s, `edited`='%s' WHERE `uid` = %d AND `resource-id` = '%s' AND `album` = '%s'", - $sql_extra, - DateTimeFormat::utcNow(), // update edited timestamp - intval(api_user()), - DBA::escape($photo_id), - DBA::escape($album) - ); + $result = Photo::update($updated_fields, ['uid' => api_user(), 'resource-id' => $photo_id, 'album' => $album]); } else { $nothingtodo = true; } @@ -4214,12 +4392,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); @@ -4228,17 +4410,14 @@ 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 = q( - "SELECT `id` FROM `photo` WHERE `uid` = %d AND `resource-id` = '%s'", - intval(api_user()), - DBA::escape($photo_id) - ); - if (!DBA::isResult($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 = DBA::delete('photo', ['uid' => api_user(), 'resource-id' => $photo_id]); + $result = Photo::delete(['uid' => api_user(), 'resource-id' => $photo_id]); // return success of deletion or error message if ($result) { @@ -4266,6 +4445,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) { @@ -4294,7 +4477,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) { @@ -4348,17 +4536,17 @@ function api_account_update_profile_image($type) // change specified profile or all profiles to the new resource-id if ($is_default_profile) { $condition = ["`profile` AND `resource-id` != ? AND `uid` = ?", $data['photo']['id'], api_user()]; - DBA::update('photo', ['profile' => false], $condition); + 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()]); } Contact::updateSelfFromUserID(api_user(), true); // Update global directory in background - $url = System::baseUrl() . '/profile/' . get_app()->user['nickname']; + $url = System::baseUrl() . '/profile/' . \get_app()->user['nickname']; if ($url && strlen(Config::get('system', 'directory'))) { Worker::add(PRIORITY_LOW, "Directory", $url); } @@ -4390,6 +4578,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) { @@ -4424,6 +4617,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) { @@ -4460,6 +4655,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) { @@ -4618,6 +4819,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) { @@ -4670,10 +4872,16 @@ 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) { - $a = get_app(); + $a = \get_app(); $user_info = api_get_user($a); if ($user_info === false) { @@ -4821,7 +5029,7 @@ function api_friendica_remoteauth() 'sec' => $sec, 'expire' => time() + 45]; DBA::insert('profile_check', $fields); - Logger::log($contact['name'] . ' ' . $sec, Logger::DEBUG); + 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( @@ -4837,6 +5045,8 @@ api_register_func('api/friendica/remoteauth', 'api_friendica_remoteauth', true); * * @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) { @@ -4932,6 +5142,7 @@ function api_share_as_retweet(&$item) } $reshared_item["body"] = $shared_body; + $reshared_item["author-id"] = Contact::getIdForURL($profile, 0, true); $reshared_item["author-name"] = $author; $reshared_item["author-link"] = $profile; $reshared_item["author-avatar"] = $avatar; @@ -4947,6 +5158,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 */ @@ -5031,6 +5243,7 @@ function api_get_nick($profile) * @param array $item * * @return array + * @throws Exception */ function api_in_reply_to($item) { @@ -5069,7 +5282,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'])) { - Logger::log('this message should never appear: id: '.$item['id'].' similar to reply-to: '.$in_reply_to['status_id'], Logger::DEBUG); + 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; @@ -5086,6 +5299,7 @@ function api_in_reply_to($item) * @param string $text * * @return string + * @throws InternalServerErrorException */ function api_clean_plain_items($text) { @@ -5112,6 +5326,7 @@ function api_clean_plain_items($text) * @param string $body The original body * * @return string Cleaned body + * @throws InternalServerErrorException */ function api_clean_attachments($body) { @@ -5140,7 +5355,7 @@ function api_clean_attachments($body) * * @param array $contacts * - * @return array + * @return void */ function api_best_nickname(&$contacts) { @@ -5210,10 +5425,15 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -5275,10 +5495,15 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -5336,11 +5561,16 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -5380,10 +5610,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 = []) { @@ -5453,10 +5684,15 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -5481,11 +5717,16 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -5516,10 +5757,15 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -5589,11 +5835,16 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -5636,10 +5887,14 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -5680,10 +5935,13 @@ 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(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -5705,7 +5963,6 @@ function api_friendica_notification($type) $notes = $xmlnotes; } - return api_format_data("notes", $type, ['note' => $notes]); } @@ -5716,10 +5973,15 @@ 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) { - $a = get_app(); + $a = \get_app(); $user_info = api_get_user($a); if (api_user() === false || $user_info === false) { @@ -5761,10 +6023,15 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); } @@ -5805,15 +6072,20 @@ 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 = "") { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -5873,10 +6145,15 @@ 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) { - $a = get_app(); + $a = \get_app(); if (api_user() === false) { throw new ForbiddenException(); @@ -5954,6 +6231,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) { @@ -5979,6 +6257,42 @@ function api_saved_searches_list($type) /// @TODO move to top of file or somewhere better api_register_func('api/saved_searches/list', 'api_saved_searches_list', true); +/* + * Bind comment numbers(friendica_comments: Int) on each statuses page of *_timeline / favorites / search + * + * @brief Number of comments + * + * @param object $data [Status, Status] + * + * @return void + */ +function bindComments(&$data) +{ + if (count($data) == 0) { + return; + } + + $ids = []; + $comments = []; + foreach ($data as $item) { + $ids[] = $item['id']; + } + + $idStr = DBA::escape(implode(', ', $ids)); + $sql = "SELECT `parent`, COUNT(*) as comments FROM `item` WHERE `parent` IN ($idStr) AND `deleted` = ? AND `gravity`= ? GROUP BY `parent`"; + $items = DBA::p($sql, 0, GRAVITY_COMMENT); + $itemsData = DBA::toArray($items); + + foreach ($itemsData as $item) { + $comments[$item['parent']] = $item['comments']; + } + + foreach ($data as $idx => $item) { + $id = $item['id']; + $data[$idx]['friendica_comments'] = isset($comments[$id]) ? $comments[$id] : 0; + } +} + /* @TODO Maybe open to implement? To.Do: