From e037394a50297cbcffc5dc3a2d3adad7f26fcd4d Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Wed, 14 Sep 2022 12:37:18 +0200 Subject: [PATCH] Make uploading files work Signed-off-by: Carl Schwan --- lib/Controller/LocalController.php | 26 +++++---- lib/Service/CacheDocumentService.php | 18 +++++++ lib/Service/PostService.php | 81 +++++++++++++++++----------- src/components/Composer/Composer.vue | 27 +++++----- src/store/timeline.js | 22 ++++---- 5 files changed, 110 insertions(+), 64 deletions(-) diff --git a/lib/Controller/LocalController.php b/lib/Controller/LocalController.php index 291d6faa..eec19f0c 100644 --- a/lib/Controller/LocalController.php +++ b/lib/Controller/LocalController.php @@ -124,18 +124,27 @@ class LocalController extends Controller { * * @NoAdminRequired */ - public function postCreate(array $data): DataResponse { + public function postCreate(string $content = '', $to = null, string $type = null, ?string $replyTo = null, $attachments = null, ?string $hashtags = null): DataResponse { + + $content = $content ?? ''; + $to = is_string($to) ? [$to] : $to; + $to = $to ?? []; + $replyTo = $replyTo ?? ''; + $type = $type ?? Stream::TYPE_PUBLIC; + $hashtags = $hashtags === '' ? [] : $hashtags; + $hashtags = $hashtags ?? []; + $attachments = $attachments ?? []; + try { $actor = $this->accountService->getActorFromUserId($this->userId); $post = new Post($actor); - $post->setContent($this->get('content', $data, '')); - $post->setReplyTo($this->get('replyTo', $data, '')); - $post->setTo($this->getArray('to', $data, [])); - $post->addTo($this->get('to', $data, '')); - $post->setType($this->get('type', $data, Stream::TYPE_PUBLIC)); - $post->setHashtags($this->getArray('hashtags', $data, [])); - $post->setAttachments($this->getArray('attachments', $data, [])); + $post->setContent($content); + $post->setReplyTo($replyTo); + $post->setTo($to); + $post->setType($type); + $post->setHashtags($hashtags); + $post->setAttachments($attachments); $token = ''; $activity = $this->postService->createPost($post, $token); @@ -151,7 +160,6 @@ class LocalController extends Controller { } } - /** * Get info about a post (limited to viewer rights). * diff --git a/lib/Service/CacheDocumentService.php b/lib/Service/CacheDocumentService.php index c3b5e130..80412c81 100644 --- a/lib/Service/CacheDocumentService.php +++ b/lib/Service/CacheDocumentService.php @@ -137,6 +137,24 @@ class CacheDocumentService { $document->setResizedCopy($resized); } + public function saveFromTempToCache(Document $document, string $tmpPath) { + $mime = mime_content_type($tmpPath); + + $this->filterMimeTypes($mime); + + $document->setMediaType($mime); + $document->setMimeType($mime); + + $file = fopen($tmpPath, 'r'); + $content = fread($file, filesize($tmpPath)); + + $filename = $this->generateFileFromContent($content); + $document->setLocalCopy($filename); + $this->resizeImage($content); + $resized = $this->generateFileFromContent($content); + $document->setResizedCopy($resized); + } + /** * @param string $content diff --git a/lib/Service/PostService.php b/lib/Service/PostService.php index c10145d6..beaa7386 100644 --- a/lib/Service/PostService.php +++ b/lib/Service/PostService.php @@ -53,34 +53,20 @@ use OCA\Social\Model\ActivityPub\Object\Note; use OCA\Social\Model\Post; use OCP\Files\NotFoundException; use OCP\Files\NotPermittedException; +use Psr\Log\LoggerInterface; class PostService { private StreamService $streamService; - private AccountService $accountService; - private ActivityService $activityService; - private CacheDocumentService $cacheDocumentService; - private ConfigService $configService; - private MiscService $miscService; + private LoggerInterface $logger; - - /** - * PostService constructor. - * - * @param StreamService $streamService - * @param AccountService $accountService - * @param ActivityService $activityService - * @param CacheDocumentService $cacheDocumentService - * @param ConfigService $configService - * @param MiscService $miscService - */ public function __construct( StreamService $streamService, AccountService $accountService, ActivityService $activityService, - CacheDocumentService $cacheDocumentService, ConfigService $configService, MiscService $miscService + CacheDocumentService $cacheDocumentService, ConfigService $configService, MiscService $miscService, LoggerInterface $logger ) { $this->streamService = $streamService; $this->accountService = $accountService; @@ -88,6 +74,7 @@ class PostService { $this->cacheDocumentService = $cacheDocumentService; $this->configService = $configService; $this->miscService = $miscService; + $this->logger = $logger; } @@ -142,15 +129,45 @@ class PostService { */ private function generateDocumentsFromAttachments(Note $note, Post $post) { $documents = []; - foreach ($post->getAttachments() as $attachment) { + \OC::$server->getLogger()->error(var_export($_FILES["attachments"], true)); + if (is_array($_FILES["attachments"]["error"])) { + foreach ($_FILES["attachments"]["error"] as $key => $error) { + if ($error == UPLOAD_ERR_OK) { + try { + $document = $this->generateDocumentFromAttachment($note, $key); + + $service = AP::$activityPub->getInterfaceForItem($document); + $service->save($document); + + $documents[] = $document; + } catch (Exception $e) { + } + } + } + } else { try { - $document = $this->generateDocumentFromAttachment($note, $attachment); + $tmp_name = $_FILES["attachments"]["tmp_name"]; + $name = basename($_FILES["attachments"]["name"]); + $tmpFile = tmpfile(); + $tmpPath = stream_get_meta_data($tmpFile)['uri']; + if (move_uploaded_file($tmp_name, $tmpPath)) { + $document = new Document(); + $document->setUrlCloud($this->configService->getCloudUrl()); + $document->generateUniqueId('/documents/local'); + $document->setParentId($note->getId()); + $document->setPublic(true); + + $this->cacheDocumentService->saveFromTempToCache($document, $tmpPath); + } $service = AP::$activityPub->getInterfaceForItem($document); $service->save($document); $documents[] = $document; } catch (Exception $e) { + $this->logger->error($e->getMessage(), [ + 'exception' => $e, + ]); } } $post->setDocuments($documents); @@ -168,21 +185,21 @@ class PostService { * @throws SocialAppConfigException * @throws UrlCloudException */ - private function generateDocumentFromAttachment(Note $note, string $attachment): Document { - list(, $data) = explode(';', $attachment); - list(, $data) = explode(',', $data); - $content = base64_decode($data); + private function generateDocumentFromAttachment(Note $note, int $key): Document { + $tmp_name = $_FILES["attachments"]["tmp_name"][$key]; + $name = basename($_FILES["attachments"]["name"][$key]); + $tmpFile = tmpfile(); + $tmpPath = stream_get_meta_data($tmpFile)['uri']; + if (move_uploaded_file($tmp_name, $tmpPath)) { + $document = new Document(); + $document->setUrlCloud($this->configService->getCloudUrl()); + $document->generateUniqueId('/documents/local'); + $document->setParentId($note->getId()); + $document->setPublic(true); - $document = new Document(); - $document->setUrlCloud($this->configService->getCloudUrl()); - $document->generateUniqueId('/documents/local'); - $document->setParentId($note->getId()); - $document->setPublic(true); + $this->cacheDocumentService->saveFromTempToCache($document, $tmpPath); + } - $mime = ''; - $this->cacheDocumentService->saveLocalUploadToCache($document, $content, $mime); - $document->setMediaType($mime); - $document->setMimeType($mime); return $document; } diff --git a/src/components/Composer/Composer.vue b/src/components/Composer/Composer.vue index 6134591a..5def1bc7 100644 --- a/src/components/Composer/Composer.vue +++ b/src/components/Composer/Composer.vue @@ -458,19 +458,20 @@ export default { let content = contentHtml.replace(/<(?!\/div)[^>]+>/gi, '').replace(/<\/div>/gi, '\n').trim() content = he.decode(content) - let data = { - content: content, - to: to, - hashtags: hashtags, - type: this.type, - attachments: this.previewUrls.map(preview => preview.result), // TODO send the summary and other props too + let formData = new FormData() + formData.append('content', content) + formData.append('to', to) + formData.append('hashtags', hashtags) + formData.append('type', this.type) + for (const preview of this.previewUrls) { + // TODO send the summary and other props too + formData.append('attachments', preview.result) } - if (this.replyTo) { - data.replyTo = this.replyTo.id + formData.append('replyTo', this.replyTo.id) } - return data + return formData }, keyup(event) { if (event.shiftKey || event.ctrlKey) { @@ -487,7 +488,7 @@ export default { // Trick to validate last mention when the user directly clicks on the "post" button without validating it. let regex = /@([-\w]+)$/ - let lastMention = postData.content.match(regex) + let lastMention = postData.get('content').match(regex) if (lastMention) { // Ask the server for matching accounts, and wait for the results @@ -495,13 +496,13 @@ export default { // Validate the last mention only when it matches a single account if (result.data.result.accounts.length === 1) { - postData.content = postData.content.replace(regex, '@' + result.data.result.accounts[0].account) - postData.to.push(result.data.result.accounts[0].account) + postData.set('content', postData.get('content').replace(regex, '@' + result.data.result.accounts[0].account)) + postData.set('to', postData.get('to').push(result.data.result.accounts[0].account)) } } // Abort if the post is a direct message and no valid mentions were found - // if (this.type === 'direct' && postData.to.length === 0) { + // if (this.type === 'direct' && postData.get('to').length === 0) { // OC.Notification.showTemporary(t('social', 'Error while trying to post your message: Could not find any valid recipients.'), { type: 'error' }) // return // } diff --git a/src/store/timeline.js b/src/store/timeline.js index 031f3306..f80b72b0 100644 --- a/src/store/timeline.js +++ b/src/store/timeline.js @@ -144,17 +144,19 @@ const actions = { context.commit('setTimelineType', 'account') context.commit('setAccount', account) }, - post(context, post) { - return new Promise((resolve, reject) => { - axios.post(generateUrl('apps/social/api/v1/post'), { data: post }).then((response) => { - Logger.info('Post created with token ' + response.data.result.token) - resolve(response) - }).catch((error) => { - OC.Notification.showTemporary('Failed to create a post') - Logger.error('Failed to create a post', { 'error': error.response }) - reject(error) + async post(context, post) { + try { + const { data } = axios.post(generateUrl('apps/social/api/v1/post'), post, { + headers: { + 'Content-Type': 'multipart/form-data' + } }) - }) + Logger.info('Post created with token ' + data.result.token) + } catch (error) { + OC.Notification.showTemporary('Failed to create a post') + console.error(error) + Logger.error('Failed to create a post', { 'error': error.response }) + } }, postDelete(context, post) { return axios.delete(generateUrl(`apps/social/api/v1/post?id=${post.id}`)).then((response) => {