diff --git a/src/Factory/Api/Mastodon/StatusSource.php b/src/Factory/Api/Mastodon/StatusSource.php index 87d836cd62..13d6145ca6 100644 --- a/src/Factory/Api/Mastodon/StatusSource.php +++ b/src/Factory/Api/Mastodon/StatusSource.php @@ -41,7 +41,7 @@ class StatusSource extends BaseFactory $post = Post::selectFirst(['uri-id', 'raw-body', 'body', 'title'], ['uri-id' => $uriId, 'uid' => [0, $uid]]); $spoiler_text = $post['title'] ?: BBCode::toPlaintext(BBCode::getAbstract($post['body'], Protocol::ACTIVITYPUB)); - $body = BBCode::toMarkdown($post['body']); + $body = BBCode::toMarkdown(Post\Media::removeFromEndOfBody($post['body'])); return new \Friendica\Object\Api\Mastodon\StatusSource($post['uri-id'], $body, $spoiler_text); } diff --git a/src/Model/Post/Media.php b/src/Model/Post/Media.php index 653bdba98a..6454cefda1 100644 --- a/src/Model/Post/Media.php +++ b/src/Model/Post/Media.php @@ -721,7 +721,7 @@ class Media * * @param int $uri_id URI id * @param array $types Media types - * @return bool Whether media attachment exists + * @return bool result of deletion * @throws \Exception */ public static function deleteByURIId(int $uri_id, array $types = []): bool @@ -735,6 +735,18 @@ class Media return DBA::delete('post-media', $condition); } + /** + * Delete media by id + * + * @param int $id media id + * @return bool result of deletion + * @throws \Exception + */ + public static function deleteById(int $id): bool + { + return DBA::delete('post-media', ['id' => $id]); + } + /** * Split the attachment media in the three segments "visual", "link" and "additional" * diff --git a/src/Module/Api/Mastodon/Statuses.php b/src/Module/Api/Mastodon/Statuses.php index 01856b2d8b..943fc7514f 100644 --- a/src/Module/Api/Mastodon/Statuses.php +++ b/src/Module/Api/Mastodon/Statuses.php @@ -50,6 +50,7 @@ class Statuses extends BaseApi $request = $this->getRequest([ 'status' => '', // Text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided. + 'media_ids' => [], // Array of Attachment ids to be attached as media. If provided, status becomes optional, and poll cannot be used. 'in_reply_to_id' => 0, // ID of the status being replied to, if status is a reply 'spoiler_text' => '', // Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field. 'language' => '', // ISO 639 language code for this status. @@ -65,7 +66,7 @@ class Statuses extends BaseApi 'origin' => true, ]; - $post = Post::selectFirst(['uri-id', 'id'], $condition); + $post = Post::selectFirst(['uri-id', 'id', 'uid', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid'], $condition); if (empty($post['id'])) { throw new HTTPException\NotFoundException('Item with URI ID ' . $this->parameters['id'] . ' not found for user ' . $uid . '.'); } @@ -85,6 +86,44 @@ class Statuses extends BaseApi } } + if (!empty($request['media_ids'])) { + /* + The provided ids in the request value consists of these two sources: + - The id in the "photo" table for newly uploaded media + - The id in the "post-media" table for already attached media + + Because of this we have to add all media that isn't already attached. + Also we have to delete all media that isn't provided anymore. + + There is a possible situation where the newly uploaded media + could have the same id as an existing, but deleted media. + + We can't do anything about this, but the probability for this is extremely low. + */ + $media_ids = []; + $existing_media = array_column(Post\Media::getByURIId($post['uri-id'], [Post\Media::AUDIO, Post\Media::VIDEO, Post\Media::IMAGE]), 'id'); + + foreach ($request['media_ids'] as $media) { + if (!in_array($media, $existing_media)) { + $media_ids[] = $media; + } + } + + foreach ($existing_media as $media) { + if (!in_array($media, $request['media_ids'])) { + Post\Media::deleteById($media); + } + } + + $item = $this->storeMediaIds($media_ids, array_merge($post, $item)); + + foreach ($item['attachments'] as $attachment) { + $attachment['uri-id'] = $post['uri-id']; + Post\Media::insert($attachment); + } + unset($item['attachments']); + } + Item::update($item, ['id' => $post['id']]); Item::updateDisplayCache($post['uri-id']); @@ -222,40 +261,7 @@ class Statuses extends BaseApi $item = DI::contentItem()->expandTags($item, $request['visibility'] == 'direct'); if (!empty($request['media_ids'])) { - $item['object-type'] = Activity\ObjectType::IMAGE; - $item['post-type'] = Item::PT_IMAGE; - $item['attachments'] = []; - - foreach ($request['media_ids'] as $id) { - $media = DBA::toArray(DBA::p("SELECT `resource-id`, `scale`, `type`, `desc`, `filename`, `datasize`, `width`, `height` FROM `photo` - WHERE `resource-id` IN (SELECT `resource-id` FROM `photo` WHERE `id` = ?) AND `photo`.`uid` = ? - ORDER BY `photo`.`width` DESC LIMIT 2", $id, $uid)); - - if (empty($media)) { - continue; - } - - Photo::setPermissionForRessource($media[0]['resource-id'], $uid, $item['allow_cid'], $item['allow_gid'], $item['deny_cid'], $item['deny_gid']); - - $ressources[] = $media[0]['resource-id']; - $phototypes = Images::supportedTypes(); - $ext = $phototypes[$media[0]['type']]; - - $attachment = ['type' => Post\Media::IMAGE, 'mimetype' => $media[0]['type'], - 'url' => DI::baseUrl() . '/photo/' . $media[0]['resource-id'] . '-' . $media[0]['scale'] . '.' . $ext, - 'size' => $media[0]['datasize'], - 'name' => $media[0]['filename'] ?: $media[0]['resource-id'], - 'description' => $media[0]['desc'] ?? '', - 'width' => $media[0]['width'], - 'height' => $media[0]['height']]; - - if (count($media) > 1) { - $attachment['preview'] = DI::baseUrl() . '/photo/' . $media[1]['resource-id'] . '-' . $media[1]['scale'] . '.' . $ext; - $attachment['preview-width'] = $media[1]['width']; - $attachment['preview-height'] = $media[1]['height']; - } - $item['attachments'][] = $attachment; - } + $item = $this->storeMediaIds($request['media_ids'], $item); } if (!empty($request['scheduled_at'])) { @@ -322,4 +328,50 @@ class Statuses extends BaseApi return 'API'; } } + + /** + * Store provided media ids in the item array and adjust permissions + * + * @param array $media_ids + * @param array $item + * @return array + */ + private function storeMediaIds(array $media_ids, array $item): array + { + $item['object-type'] = Activity\ObjectType::IMAGE; + $item['post-type'] = Item::PT_IMAGE; + $item['attachments'] = []; + + foreach ($media_ids as $id) { + $media = DBA::toArray(DBA::p("SELECT `resource-id`, `scale`, `type`, `desc`, `filename`, `datasize`, `width`, `height` FROM `photo` + WHERE `resource-id` IN (SELECT `resource-id` FROM `photo` WHERE `id` = ?) AND `photo`.`uid` = ? + ORDER BY `photo`.`width` DESC LIMIT 2", $id, $item['uid'])); + + if (empty($media)) { + continue; + } + + Photo::setPermissionForRessource($media[0]['resource-id'], $item['uid'], $item['allow_cid'], $item['allow_gid'], $item['deny_cid'], $item['deny_gid']); + + $ressources[] = $media[0]['resource-id']; + $phototypes = Images::supportedTypes(); + $ext = $phototypes[$media[0]['type']]; + + $attachment = ['type' => Post\Media::IMAGE, 'mimetype' => $media[0]['type'], + 'url' => DI::baseUrl() . '/photo/' . $media[0]['resource-id'] . '-' . $media[0]['scale'] . '.' . $ext, + 'size' => $media[0]['datasize'], + 'name' => $media[0]['filename'] ?: $media[0]['resource-id'], + 'description' => $media[0]['desc'] ?? '', + 'width' => $media[0]['width'], + 'height' => $media[0]['height']]; + + if (count($media) > 1) { + $attachment['preview'] = DI::baseUrl() . '/photo/' . $media[1]['resource-id'] . '-' . $media[1]['scale'] . '.' . $ext; + $attachment['preview-width'] = $media[1]['width']; + $attachment['preview-height'] = $media[1]['height']; + } + $item['attachments'][] = $attachment; + } + return $item; + } }