]> git.mxchange.org Git - friendica.git/blobdiff - include/api.php
Merge pull request #9757 from MrPetovan/bug/9716-show-edited-date-non-frio
[friendica.git] / include / api.php
index 90d0a511b7d772149e43fff732482d10670bc72e..5131c9574189e5d622b155fc3979e363505873cc 100644 (file)
@@ -44,7 +44,7 @@ use Friendica\Model\Photo;
 use Friendica\Model\User;
 use Friendica\Model\UserItem;
 use Friendica\Model\Verb;
-use Friendica\Network\FKOAuth1;
+use Friendica\Security\FKOAuth1;
 use Friendica\Network\HTTPException;
 use Friendica\Network\HTTPException\BadRequestException;
 use Friendica\Network\HTTPException\ExpectationFailedException;
@@ -58,6 +58,8 @@ use Friendica\Network\HTTPException\UnauthorizedException;
 use Friendica\Object\Image;
 use Friendica\Protocol\Activity;
 use Friendica\Protocol\Diaspora;
+use Friendica\Security\OAuth1\OAuthRequest;
+use Friendica\Security\OAuth1\OAuthUtil;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Images;
 use Friendica\Util\Network;
@@ -65,7 +67,6 @@ use Friendica\Util\Proxy as ProxyUtils;
 use Friendica\Util\Strings;
 use Friendica\Util\XML;
 
-require_once __DIR__ . '/../mod/share.php';
 require_once __DIR__ . '/../mod/item.php';
 require_once __DIR__ . '/../mod/wall_upload.php';
 
@@ -264,7 +265,10 @@ function api_login(App $a)
                throw new UnauthorizedException("This API requires login");
        }
 
-       DI::auth()->setForUser($a, $record);
+       // Don't refresh the login date more often than twice a day to spare database writes
+       $login_refresh = strcmp(DateTimeFormat::utc('now - 12 hours'), $record['login_date']) > 0;
+
+       DI::auth()->setForUser($a, $record, false, false, $login_refresh);
 
        $_SESSION["allow_api"] = true;
 
@@ -308,22 +312,22 @@ function api_call(App $a, App\Arguments $args = null)
        }
 
        $type = "json";
-       if (strpos($args->getQueryString(), ".xml") > 0) {
+       if (strpos($args->getCommand(), ".xml") > 0) {
                $type = "xml";
        }
-       if (strpos($args->getQueryString(), ".json") > 0) {
+       if (strpos($args->getCommand(), ".json") > 0) {
                $type = "json";
        }
-       if (strpos($args->getQueryString(), ".rss") > 0) {
+       if (strpos($args->getCommand(), ".rss") > 0) {
                $type = "rss";
        }
-       if (strpos($args->getQueryString(), ".atom") > 0) {
+       if (strpos($args->getCommand(), ".atom") > 0) {
                $type = "atom";
        }
 
        try {
                foreach ($API as $p => $info) {
-                       if (strpos($args->getQueryString(), $p) === 0) {
+                       if (strpos($args->getCommand(), $p) === 0) {
                                if (!api_check_method($info['method'])) {
                                        throw new MethodNotAllowedException();
                                }
@@ -332,16 +336,16 @@ function api_call(App $a, App\Arguments $args = null)
 
                                if (!empty($info['auth']) && api_user() === false) {
                                        api_login($a);
+                                       Logger::info(API_LOG_PREFIX . 'username {username}', ['module' => 'api', 'action' => 'call', 'username' => $a->user['username']]);
                                }
 
-                               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 = floatval(microtime(true) - $stamp);
 
-                               Logger::info(API_LOG_PREFIX . 'username {username}', ['module' => 'api', 'action' => 'call', 'username' => $a->user['username'], 'duration' => round($duration, 2)]);
+                               Logger::info(API_LOG_PREFIX . 'duration {duration}', ['module' => 'api', 'action' => 'call', 'duration' => round($duration, 2)]);
 
                                DI::profiler()->saveLog(DI::logger(), API_LOG_PREFIX . 'performance');
 
@@ -381,7 +385,7 @@ function api_call(App $a, App\Arguments $args = null)
                }
 
                Logger::warning(API_LOG_PREFIX . 'not implemented', ['module' => 'api', 'action' => 'call', 'query' => DI::args()->getQueryString()]);
-               throw new NotImplementedException();
+               throw new NotFoundException();
        } catch (HTTPException $e) {
                header("HTTP/1.1 {$e->getCode()} {$e->httpdesc}");
                return api_error($type, $e, $args);
@@ -624,7 +628,7 @@ function api_get_user(App $a, $contact_id = null)
                                'name' => $contact["name"],
                                'screen_name' => (($contact['nick']) ? $contact['nick'] : $contact['name']),
                                'location' => ($contact["location"] != "") ? $contact["location"] : ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol']),
-                               'description' => BBCode::toPlaintext($contact["about"]),
+                               'description' => BBCode::toPlaintext($contact["about"] ?? ''),
                                'profile_image_url' => $contact["micro"],
                                'profile_image_url_https' => $contact["micro"],
                                'profile_image_url_profile_size' => $contact["thumb"],
@@ -651,8 +655,8 @@ function api_get_user(App $a, $contact_id = null)
                                'notifications' => false,
                                'statusnet_profile_url' => $contact["url"],
                                'uid' => 0,
-                               'cid' => Contact::getIdForURL($contact["url"], api_user(), true),
-                               'pid' => Contact::getIdForURL($contact["url"], 0, true),
+                               'cid' => Contact::getIdForURL($contact["url"], api_user(), false),
+                               'pid' => Contact::getIdForURL($contact["url"], 0, false),
                                'self' => 0,
                                'network' => $contact["network"],
                        ];
@@ -676,7 +680,7 @@ function api_get_user(App $a, $contact_id = null)
        $countfollowers = 0;
        $starred = 0;
 
-       $pcontact_id  = Contact::getIdForURL($uinfo[0]['url'], 0, true);
+       $pcontact_id  = Contact::getIdForURL($uinfo[0]['url'], 0, false);
 
        if (!empty($profile['about'])) {
                $description = $profile['about'];
@@ -698,7 +702,7 @@ function api_get_user(App $a, $contact_id = null)
                'name' => (($uinfo[0]['name']) ? $uinfo[0]['name'] : $uinfo[0]['nick']),
                'screen_name' => (($uinfo[0]['nick']) ? $uinfo[0]['nick'] : $uinfo[0]['name']),
                'location' => $location,
-               'description' => BBCode::toPlaintext($description),
+               'description' => BBCode::toPlaintext($description ?? ''),
                'profile_image_url' => $uinfo[0]['micro'],
                'profile_image_url_https' => $uinfo[0]['micro'],
                'profile_image_url_profile_size' => $uinfo[0]["thumb"],
@@ -728,7 +732,7 @@ function api_get_user(App $a, $contact_id = null)
                'statusnet_profile_url' => $uinfo[0]['url'],
                'uid' => intval($uinfo[0]['uid']),
                'cid' => intval($uinfo[0]['cid']),
-               'pid' => Contact::getIdForURL($uinfo[0]["url"], 0, true),
+               'pid' => Contact::getIdForURL($uinfo[0]["url"], 0, false),
                'self' => $uinfo[0]['self'],
                'network' => $uinfo[0]['network'],
        ];
@@ -1241,7 +1245,7 @@ function api_media_upload()
                                "image_type" => $media["type"],
                                "friendica_preview_url" => $media["preview"]];
 
-       Logger::log("Media uploaded: " . print_r($returndata, true), Logger::DEBUG);
+       Logger::info('Media uploaded', ['return' => $returndata]);
 
        return ["media" => $returndata];
 }
@@ -2035,35 +2039,40 @@ function api_statuses_repeat($type)
 
        Logger::log('API: api_statuses_repeat: '.$id);
 
-       $fields = ['uri-id', 'body', 'title', 'attach', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink'];
+       $fields = ['uri-id', 'network', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink'];
        $item = Item::selectFirst($fields, ['id' => $id, 'private' => [Item::PUBLIC, Item::UNLISTED]]);
 
        if (DBA::isResult($item) && $item['body'] != "") {
-               if (strpos($item['body'], "[/share]") !== false) {
-                       $pos = strpos($item['body'], "[share");
-                       $post = substr($item['body'], $pos);
+               if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::TWITTER])) {
+                       if (!Item::performActivity($id, 'announce', local_user())) {
+                               throw new InternalServerErrorException();
+                       }
+               
+                       $item_id = $id;
                } else {
-                       $post = share_header($item['author-name'], $item['author-link'], $item['author-avatar'], $item['guid'], $item['created'], $item['plink']);
+                       if (strpos($item['body'], "[/share]") !== false) {
+                               $pos = strpos($item['body'], "[share");
+                               $post = substr($item['body'], $pos);
+                       } else {
+                               $post = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'], $item['plink'], $item['created'], $item['guid']);
+
+                               if (!empty($item['title'])) {
+                                       $post .= '[h3]' . $item['title'] . "[/h3]\n";
+                               }
 
-                       if (!empty($item['title'])) {
-                               $post .= '[h3]' . $item['title'] . "[/h3]\n";
+                               $post .= $item['body'];
+                               $post .= "[/share]";
                        }
+                       $_REQUEST['body'] = $post;
+                       $_REQUEST['profile_uid'] = api_user();
+                       $_REQUEST['api_source'] = true;
 
-                       $post .= $item['body'];
-                       $post .= "[/share]";
-               }
-               $_REQUEST['body'] = $post;
-               $_REQUEST['attach'] = $item['attach'];
-               $_REQUEST['profile_uid'] = api_user();
-               $_REQUEST['api_source'] = true;
+                       if (empty($_REQUEST['source'])) {
+                               $_REQUEST["source"] = api_source();
+                       }
 
-               if (empty($_REQUEST['source'])) {
-                       $_REQUEST["source"] = api_source();
+                       $item_id = item_post($a);
                }
-
-               $item_id = item_post($a);
-
-               /// @todo Copy tags from the original post to the new one
        } else {
                throw new ForbiddenException();
        }
@@ -2154,10 +2163,10 @@ function api_statuses_mentions($type)
        // get last network messages
 
        // params
-       $since_id = $_REQUEST['since_id'] ?? 0;
-       $max_id   = $_REQUEST['max_id']   ?? 0;
-       $count    = $_REQUEST['count']    ?? 20;
-       $page     = $_REQUEST['page']     ?? 1;
+       $since_id = intval($_REQUEST['since_id'] ?? 0);
+       $max_id   = intval($_REQUEST['max_id']   ?? 0);
+       $count    = intval($_REQUEST['count']    ?? 20);
+       $page     = intval($_REQUEST['page']     ?? 1);
 
        $start = max(0, ($page - 1) * $count);
 
@@ -2230,12 +2239,7 @@ function api_statuses_user_timeline($type)
                throw new ForbiddenException();
        }
 
-       Logger::log(
-               "api_statuses_user_timeline: api_user: ". api_user() .
-                       "\nuser_info: ".print_r($user_info, true) .
-                       "\n_REQUEST:  ".print_r($_REQUEST, true),
-               Logger::DEBUG
-       );
+       Logger::info('api_statuses_user_timeline', ['api_user' => api_user(), 'user_info' => $user_info, '_REQUEST' => $_REQUEST]);
 
        $since_id        = $_REQUEST['since_id'] ?? 0;
        $max_id          = $_REQUEST['max_id'] ?? 0;
@@ -3580,96 +3584,6 @@ function api_statusnet_version($type)
 api_register_func('api/gnusocial/version', 'api_statusnet_version', false);
 api_register_func('api/statusnet/version', 'api_statusnet_version', false);
 
-/**
- *
- * @param string $type Return type (atom, rss, xml, json)
- *
- * @param int $rel A contact relationship constant
- * @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, int $rel)
-{
-       if (!api_user()) {
-               throw new ForbiddenException();
-       }
-
-       $a = DI::app();
-
-       api_get_user($a);
-
-       $stringify_ids = $_REQUEST['stringify_ids'] ?? false;
-
-       $contacts = DBA::p("SELECT `pcontact`.`id`
-               FROM `contact`
-               INNER JOIN `contact` AS `pcontact`
-                   ON `contact`.`nurl` = `pcontact`.`nurl`
-                   AND `pcontact`.`uid` = 0
-               WHERE `contact`.`uid` = ?
-               AND NOT `contact`.`self`
-               AND `contact`.`rel` IN (?, ?)",
-               api_user(),
-               $rel,
-               Contact::FRIEND
-       );
-
-       $ids = [];
-       foreach (DBA::toArray($contacts) as $contact) {
-               if ($stringify_ids) {
-                       $ids[] = $contact['id'];
-               } else {
-                       $ids[] = intval($contact['id']);
-               }
-       }
-
-       return api_format_data('ids', $type, ['id' => $ids]);
-}
-
-/**
- * Returns the ID of every user the user is following.
- *
- * @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/follow-search-get-users/api-reference/get-friends-ids
- */
-function api_friends_ids($type)
-{
-       return api_ff_ids($type, Contact::SHARING);
-}
-
-/**
- * Returns the ID of every user following the user.
- *
- * @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/follow-search-get-users/api-reference/get-followers-ids
- */
-function api_followers_ids($type)
-{
-       return api_ff_ids($type, Contact::FOLLOWER);
-}
-
-/// @TODO move to top of file or somewhere better
-api_register_func('api/friends/ids', 'api_friends_ids', true);
-api_register_func('api/followers/ids', 'api_followers_ids', true);
-
 /**
  * Sends a new direct message.
  *
@@ -4165,26 +4079,18 @@ function api_fr_photoalbum_delete($type)
                throw new BadRequestException("no albumname specified");
        }
        // check if album is existing
-       $r = q(
-               "SELECT DISTINCT `resource-id` FROM `photo` WHERE `uid` = %d AND `album` = '%s'",
-               intval(api_user()),
-               DBA::escape($album)
-       );
-       if (!DBA::isResult($r)) {
+
+       $photos = DBA::selectToArray('photo', ['resource-id'], ['uid' => api_user(), 'album' => $album], ['group_by' => ['resource-id']]);
+       if (!DBA::isResult($photos)) {
                throw new BadRequestException("album not available");
        }
 
+       $resourceIds = array_column($photos, 'resource-id');
+
        // function for setting the items to "deleted = 1" which ensures that comments, likes etc. are not shown anymore
        // to the user and the contacts of the users (drop_items() performs the federation of the deletion to other networks
-       foreach ($r as $rr) {
-               $condition = ['uid' => local_user(), 'resource-id' => $rr['resource-id'], 'type' => 'photo'];
-               $photo_item = Item::selectFirstForUser(local_user(), ['id'], $condition);
-
-               if (!DBA::isResult($photo_item)) {
-                       throw new InternalServerErrorException("problem with deleting items occured");
-               }
-               Item::deleteForUser(['id' => $photo_item['id']], api_user());
-       }
+       $condition = ['uid' => api_user(), 'resource-id' => $resourceIds, 'type' => 'photo'];
+       Item::deleteForUser($condition, api_user());
 
        // now let's delete all photos from the album
        $result = Photo::delete(['uid' => api_user(), 'album' => $album]);
@@ -4313,7 +4219,7 @@ function api_fr_photo_create_update($type)
        $deny_cid  = $_REQUEST['deny_cid' ] ?? null;
        $allow_gid = $_REQUEST['allow_gid'] ?? null;
        $deny_gid  = $_REQUEST['deny_gid' ] ?? null;
-       $visibility = !empty($_REQUEST['visibility']) && $_REQUEST['visibility'] !== "false";
+       $visibility = !$allow_cid && !$deny_cid && !$allow_gid && !$deny_gid;
 
        // do several checks on input parameters
        // we do not allow calls without album string
@@ -4461,19 +4367,13 @@ function api_fr_photo_delete($type)
 
        // return success of deletion or error message
        if ($result) {
-               // retrieve the id of the parent element (the photo element)
-               $condition = ['uid' => local_user(), 'resource-id' => $photo_id, 'type' => 'photo'];
-               $photo_item = Item::selectFirstForUser(local_user(), ['id'], $condition);
-
-               if (!DBA::isResult($photo_item)) {
-                       throw new InternalServerErrorException("problem with deleting items occured");
-               }
                // function for setting the items to "deleted = 1" which ensures that comments, likes etc. are not shown anymore
                // to the user and the contacts of the users (drop_items() do all the necessary magic to avoid orphans in database and federate deletion)
-               Item::deleteForUser(['id' => $photo_item['id']], api_user());
+               $condition = ['uid' => api_user(), 'resource-id' => $photo_id, 'type' => 'photo'];
+               Item::deleteForUser($condition, api_user());
 
-               $answer = ['result' => 'deleted', 'message' => 'photo with id `' . $photo_id . '` has been deleted from server.'];
-               return api_format_data("photo_delete", $type, ['$result' => $answer]);
+               $result = ['result' => 'deleted', 'message' => 'photo with id `' . $photo_id . '` has been deleted from server.'];
+               return api_format_data("photo_delete", $type, ['$result' => $result]);
        } else {
                throw new InternalServerErrorException("unknown error on deleting photo from database table");
        }
@@ -4832,7 +4732,7 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
                Logger::log("photo upload: new profile image upload ended", Logger::DEBUG);
        }
 
-       if (isset($r) && $r) {
+       if (!empty($r)) {
                // create entry in 'item'-table on new uploads to enable users to comment/like/dislike the photo
                if ($photo_id == null && $mediatype == "photo") {
                        post_photo_item($resource_id, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $filetype, $visibility);
@@ -4865,7 +4765,6 @@ function post_photo_item($hash, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $f
        $arr['guid']          = System::createUUID();
        $arr['uid']           = intval(api_user());
        $arr['uri']           = $uri;
-       $arr['parent-uri']    = $uri;
        $arr['type']          = 'photo';
        $arr['wall']          = 1;
        $arr['resource-id']   = $hash;
@@ -4979,8 +4878,8 @@ function prepare_photo_data($type, $scale, $photo_id)
        }
 
        // retrieve item element for getting activities (like, dislike etc.) related to photo
-       $condition = ['uid' => local_user(), 'resource-id' => $photo_id, 'type' => 'photo'];
-       $item = Item::selectFirstForUser(local_user(), ['id'], $condition);
+       $condition = ['uid' => api_user(), 'resource-id' => $photo_id, 'type' => 'photo'];
+       $item = Item::selectFirst(['id', 'uid', 'uri', 'parent', 'allow_cid', 'deny_cid', 'allow_gid', 'deny_gid'], $condition);
        if (!DBA::isResult($item)) {
                throw new NotFoundException('Photo-related item not found.');
        }
@@ -4989,7 +4888,7 @@ function prepare_photo_data($type, $scale, $photo_id)
 
        // retrieve comments on photo
        $condition = ["`parent` = ? AND `uid` = ? AND (`gravity` IN (?, ?) OR `type`='photo')",
-               $item[0]['parent'], api_user(), GRAVITY_PARENT, GRAVITY_COMMENT];
+               $item['parent'], api_user(), GRAVITY_PARENT, GRAVITY_COMMENT];
 
        $statuses = Item::selectForUser(api_user(), [], $condition);
 
@@ -5009,10 +4908,10 @@ function prepare_photo_data($type, $scale, $photo_id)
        $data['photo']['friendica_comments'] = $comments;
 
        // include info if rights on photo and rights on item are mismatching
-       $rights_mismatch = $data['photo']['allow_cid'] != $item[0]['allow_cid'] ||
-               $data['photo']['deny_cid'] != $item[0]['deny_cid'] ||
-               $data['photo']['allow_gid'] != $item[0]['allow_gid'] ||
-               $data['photo']['deny_cid'] != $item[0]['deny_cid'];
+       $rights_mismatch = $data['photo']['allow_cid'] != $item['allow_cid'] ||
+               $data['photo']['deny_cid'] != $item['deny_cid'] ||
+               $data['photo']['allow_gid'] != $item['allow_gid'] ||
+               $data['photo']['deny_gid'] != $item['deny_gid'];
        $data['photo']['rights_mismatch'] = $rights_mismatch;
 
        return $data;
@@ -5158,7 +5057,7 @@ function api_share_as_retweet(&$item)
 
        $reshared_item["share-pre-body"] = $reshared['comment'];
        $reshared_item["body"] = $reshared['shared'];
-       $reshared_item["author-id"] = Contact::getIdForURL($reshared['profile'], 0, true);
+       $reshared_item["author-id"] = Contact::getIdForURL($reshared['profile'], 0, false);
        $reshared_item["author-name"] = $reshared['author'];
        $reshared_item["author-link"] = $reshared['profile'];
        $reshared_item["author-avatar"] = $reshared['avatar'];
@@ -5377,7 +5276,7 @@ function api_friendica_group_show($type)
        // loop through all groups and retrieve all members for adding data in the user array
        $grps = [];
        foreach ($r as $rr) {
-               $members = Contact::getByGroupId($rr['id']);
+               $members = Contact\Group::getById($rr['id']);
                $users = [];
 
                if ($type == "xml") {
@@ -5702,7 +5601,7 @@ function api_friendica_group_update($type)
        }
 
        // remove members
-       $members = Contact::getByGroupId($gid);
+       $members = Contact\Group::getById($gid);
        foreach ($members as $member) {
                $cid = $member['id'];
                foreach ($users as $user) {
@@ -5816,7 +5715,7 @@ function api_friendica_activity($type)
 
        $id = $_REQUEST['id'] ?? 0;
 
-       $res = Item::performActivity($id, $verb);
+       $res = Item::performActivity($id, $verb, api_user());
 
        if ($res) {
                if ($type == "xml") {