diff --git a/app/Http/Controllers/CollectionController.php b/app/Http/Controllers/CollectionController.php index 6cd4bda57..85f3f30cf 100644 --- a/app/Http/Controllers/CollectionController.php +++ b/app/Http/Controllers/CollectionController.php @@ -153,7 +153,7 @@ class CollectionController extends Controller abort(400, 'You can only add '.$max.' posts per collection'); } - $status = Status::whereScope('public') + $status = Status::whereIn('scope', ['public', 'unlisted']) ->whereProfileId($profileId) ->whereIn('type', ['photo', 'photo:album', 'video']) ->findOrFail($postId); @@ -166,17 +166,13 @@ class CollectionController extends Controller 'order' => $count, ]); - CollectionService::addItem( - $collection->id, - $status->id, - $count - ); + CollectionService::deleteCollection($collection->id); $collection->updated_at = now(); $collection->save(); CollectionService::setCollection($collection->id, $collection); - return StatusService::get($status->id); + return StatusService::get($status->id, false); } public function getCollection(Request $request, $id) @@ -226,10 +222,10 @@ class CollectionController extends Controller return collect($items) ->map(function($id) { - return StatusService::get($id); + return StatusService::get($id, false); }) ->filter(function($item) { - return $item && isset($item['account'], $item['media_attachments']); + return $item && ($item['visibility'] == 'public' || $item['visibility'] == 'unlisted') && isset($item['account'], $item['media_attachments']); }) ->values(); } @@ -298,7 +294,7 @@ class CollectionController extends Controller abort(400, 'You cannot delete the only post of a collection!'); } - $status = Status::whereScope('public') + $status = Status::whereIn('scope', ['public', 'unlisted']) ->whereIn('type', ['photo', 'photo:album', 'video']) ->findOrFail($postId); diff --git a/app/Http/Controllers/FederationController.php b/app/Http/Controllers/FederationController.php index c4b5e86bf..0cf33d43e 100644 --- a/app/Http/Controllers/FederationController.php +++ b/app/Http/Controllers/FederationController.php @@ -3,17 +3,17 @@ namespace App\Http\Controllers; use App\Jobs\InboxPipeline\{ - DeleteWorker, - InboxWorker, - InboxValidator + DeleteWorker, + InboxWorker, + InboxValidator }; use App\Jobs\RemoteFollowPipeline\RemoteFollowPipeline; use App\{ - AccountLog, - Like, - Profile, - Status, - User + AccountLog, + Like, + Profile, + Status, + User }; use App\Util\Lexer\Nickname; use App\Util\Webfinger\Webfinger; @@ -24,243 +24,248 @@ use Illuminate\Http\Request; use League\Fractal; use App\Util\Site\Nodeinfo; use App\Util\ActivityPub\{ - Helpers, - HttpSignature, - Outbox + Helpers, + HttpSignature, + Outbox }; use Zttp\Zttp; use App\Services\InstanceService; +use App\Services\AccountService; class FederationController extends Controller { - public function nodeinfoWellKnown() - { - abort_if(!config('federation.nodeinfo.enabled'), 404); - return response()->json(Nodeinfo::wellKnown(), 200, [], JSON_UNESCAPED_SLASHES) - ->header('Access-Control-Allow-Origin','*'); - } + public function nodeinfoWellKnown() + { + abort_if(!config('federation.nodeinfo.enabled'), 404); + return response()->json(Nodeinfo::wellKnown(), 200, [], JSON_UNESCAPED_SLASHES) + ->header('Access-Control-Allow-Origin','*'); + } - public function nodeinfo() - { - abort_if(!config('federation.nodeinfo.enabled'), 404); - return response()->json(Nodeinfo::get(), 200, [], JSON_UNESCAPED_SLASHES) - ->header('Access-Control-Allow-Origin','*'); - } + public function nodeinfo() + { + abort_if(!config('federation.nodeinfo.enabled'), 404); + return response()->json(Nodeinfo::get(), 200, [], JSON_UNESCAPED_SLASHES) + ->header('Access-Control-Allow-Origin','*'); + } - public function webfinger(Request $request) - { - if (!config('federation.webfinger.enabled') || - !$request->has('resource') || - !$request->filled('resource') - ) { - return response('', 400); - } + public function webfinger(Request $request) + { + if (!config('federation.webfinger.enabled') || + !$request->has('resource') || + !$request->filled('resource') + ) { + return response('', 400); + } - $resource = $request->input('resource'); - $domain = config('pixelfed.domain.app'); + $resource = $request->input('resource'); + $domain = config('pixelfed.domain.app'); - if(config('federation.activitypub.sharedInbox') && - $resource == 'acct:' . $domain . '@' . $domain) { - $res = [ - 'subject' => 'acct:' . $domain . '@' . $domain, - 'aliases' => [ - 'https://' . $domain . '/i/actor' - ], - 'links' => [ - [ - 'rel' => 'http://webfinger.net/rel/profile-page', - 'type' => 'text/html', - 'href' => 'https://' . $domain . '/site/kb/instance-actor' - ], - [ - 'rel' => 'self', - 'type' => 'application/activity+json', - 'href' => 'https://' . $domain . '/i/actor' - ] - ] - ]; - return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES); - } - $hash = hash('sha256', $resource); - $key = 'federation:webfinger:sha256:' . $hash; - if($cached = Cache::get($key)) { - return response()->json($cached, 200, [], JSON_UNESCAPED_SLASHES); - } - if(strpos($resource, $domain) == false) { - return response('', 400); - } - $parsed = Nickname::normalizeProfileUrl($resource); - if(empty($parsed) || $parsed['domain'] !== $domain) { - return response('', 400); - } - $username = $parsed['username']; - $profile = Profile::whereNull('domain')->whereUsername($username)->first(); - if(!$profile || $profile->status !== null) { - return response('', 400); - } - $webfinger = (new Webfinger($profile))->generate(); - Cache::put($key, $webfinger, 1209600); + if(config('federation.activitypub.sharedInbox') && + $resource == 'acct:' . $domain . '@' . $domain) { + $res = [ + 'subject' => 'acct:' . $domain . '@' . $domain, + 'aliases' => [ + 'https://' . $domain . '/i/actor' + ], + 'links' => [ + [ + 'rel' => 'http://webfinger.net/rel/profile-page', + 'type' => 'text/html', + 'href' => 'https://' . $domain . '/site/kb/instance-actor' + ], + [ + 'rel' => 'self', + 'type' => 'application/activity+json', + 'href' => 'https://' . $domain . '/i/actor' + ] + ] + ]; + return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES); + } + $hash = hash('sha256', $resource); + $key = 'federation:webfinger:sha256:' . $hash; + if($cached = Cache::get($key)) { + return response()->json($cached, 200, [], JSON_UNESCAPED_SLASHES); + } + if(strpos($resource, $domain) == false) { + return response('', 400); + } + $parsed = Nickname::normalizeProfileUrl($resource); + if(empty($parsed) || $parsed['domain'] !== $domain) { + return response('', 400); + } + $username = $parsed['username']; + $profile = Profile::whereNull('domain')->whereUsername($username)->first(); + if(!$profile || $profile->status !== null) { + return response('', 400); + } + $webfinger = (new Webfinger($profile))->generate(); + Cache::put($key, $webfinger, 1209600); - return response()->json($webfinger, 200, [], JSON_UNESCAPED_SLASHES) - ->header('Access-Control-Allow-Origin','*'); - } + return response()->json($webfinger, 200, [], JSON_UNESCAPED_SLASHES) + ->header('Access-Control-Allow-Origin','*'); + } - public function hostMeta(Request $request) - { - abort_if(!config('federation.webfinger.enabled'), 404); + public function hostMeta(Request $request) + { + abort_if(!config('federation.webfinger.enabled'), 404); - $path = route('well-known.webfinger'); - $xml = ''; + $path = route('well-known.webfinger'); + $xml = ''; - return response($xml)->header('Content-Type', 'application/xrd+xml'); - } + return response($xml)->header('Content-Type', 'application/xrd+xml'); + } - public function userOutbox(Request $request, $username) - { - abort_if(!config_cache('federation.activitypub.enabled'), 404); + public function userOutbox(Request $request, $username) + { + abort_if(!config_cache('federation.activitypub.enabled'), 404); - if(!$request->wantsJson()) { - return redirect('/' . $username); - } + if(!$request->wantsJson()) { + return redirect('/' . $username); + } - $res = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => 'https://' . config('pixelfed.domain.app') . '/users/' . $username . '/outbox', - 'type' => 'OrderedCollection', - 'totalItems' => 0, - 'orderedItems' => [] - ]; + $res = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => 'https://' . config('pixelfed.domain.app') . '/users/' . $username . '/outbox', + 'type' => 'OrderedCollection', + 'totalItems' => 0, + 'orderedItems' => [] + ]; - return response(json_encode($res, JSON_UNESCAPED_SLASHES))->header('Content-Type', 'application/activity+json'); - } + return response(json_encode($res, JSON_UNESCAPED_SLASHES))->header('Content-Type', 'application/activity+json'); + } - public function userInbox(Request $request, $username) - { - abort_if(!config_cache('federation.activitypub.enabled'), 404); - abort_if(!config('federation.activitypub.inbox'), 404); + public function userInbox(Request $request, $username) + { + abort_if(!config_cache('federation.activitypub.enabled'), 404); + abort_if(!config('federation.activitypub.inbox'), 404); - $headers = $request->headers->all(); - $payload = $request->getContent(); - if(!$payload || empty($payload)) { - return; - } - $obj = json_decode($payload, true, 8); - if(!isset($obj['id'])) { - return; - } - $domain = parse_url($obj['id'], PHP_URL_HOST); - if(in_array($domain, InstanceService::getBannedDomains())) { - return; - } + $headers = $request->headers->all(); + $payload = $request->getContent(); + if(!$payload || empty($payload)) { + return; + } + $obj = json_decode($payload, true, 8); + if(!isset($obj['id'])) { + return; + } + $domain = parse_url($obj['id'], PHP_URL_HOST); + if(in_array($domain, InstanceService::getBannedDomains())) { + return; + } - if(isset($obj['type']) && $obj['type'] === 'Delete') { - if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) { - if($obj['object']['type'] === 'Person') { - if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) { - dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox'); - return; - } - } + if(isset($obj['type']) && $obj['type'] === 'Delete') { + if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) { + if($obj['object']['type'] === 'Person') { + if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) { + dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox'); + return; + } + } - if($obj['object']['type'] === 'Tombstone') { - if(Status::whereObjectUrl($obj['object']['id'])->exists()) { - dispatch(new DeleteWorker($headers, $payload))->onQueue('delete'); - return; - } - } + if($obj['object']['type'] === 'Tombstone') { + if(Status::whereObjectUrl($obj['object']['id'])->exists()) { + dispatch(new DeleteWorker($headers, $payload))->onQueue('delete'); + return; + } + } - if($obj['object']['type'] === 'Story') { - dispatch(new DeleteWorker($headers, $payload))->onQueue('story'); - return; - } - } - return; - } else if( isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) { - dispatch(new InboxValidator($username, $headers, $payload))->onQueue('follow'); - } else { - dispatch(new InboxValidator($username, $headers, $payload))->onQueue('high'); - } - return; - } + if($obj['object']['type'] === 'Story') { + dispatch(new DeleteWorker($headers, $payload))->onQueue('story'); + return; + } + } + return; + } else if( isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) { + dispatch(new InboxValidator($username, $headers, $payload))->onQueue('follow'); + } else { + dispatch(new InboxValidator($username, $headers, $payload))->onQueue('high'); + } + return; + } - public function sharedInbox(Request $request) - { - abort_if(!config_cache('federation.activitypub.enabled'), 404); - abort_if(!config('federation.activitypub.sharedInbox'), 404); + public function sharedInbox(Request $request) + { + abort_if(!config_cache('federation.activitypub.enabled'), 404); + abort_if(!config('federation.activitypub.sharedInbox'), 404); - $headers = $request->headers->all(); - $payload = $request->getContent(); + $headers = $request->headers->all(); + $payload = $request->getContent(); - if(!$payload || empty($payload)) { - return; - } + if(!$payload || empty($payload)) { + return; + } - $obj = json_decode($payload, true, 8); - if(!isset($obj['id'])) { - return; - } + $obj = json_decode($payload, true, 8); + if(!isset($obj['id'])) { + return; + } - $domain = parse_url($obj['id'], PHP_URL_HOST); - if(in_array($domain, InstanceService::getBannedDomains())) { - return; - } + $domain = parse_url($obj['id'], PHP_URL_HOST); + if(in_array($domain, InstanceService::getBannedDomains())) { + return; + } - if(isset($obj['type']) && $obj['type'] === 'Delete') { - if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) { - if($obj['object']['type'] === 'Person') { - if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) { - dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox'); - return; - } - } + if(isset($obj['type']) && $obj['type'] === 'Delete') { + if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) { + if($obj['object']['type'] === 'Person') { + if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) { + dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox'); + return; + } + } - if($obj['object']['type'] === 'Tombstone') { - if(Status::whereObjectUrl($obj['object']['id'])->exists()) { - dispatch(new DeleteWorker($headers, $payload))->onQueue('delete'); - return; - } - } + if($obj['object']['type'] === 'Tombstone') { + if(Status::whereObjectUrl($obj['object']['id'])->exists()) { + dispatch(new DeleteWorker($headers, $payload))->onQueue('delete'); + return; + } + } - if($obj['object']['type'] === 'Story') { - dispatch(new DeleteWorker($headers, $payload))->onQueue('story'); - return; - } - } - return; - } else if( isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) { - dispatch(new InboxWorker($headers, $payload))->onQueue('follow'); - } else { - dispatch(new InboxWorker($headers, $payload))->onQueue('shared'); - } - return; - } + if($obj['object']['type'] === 'Story') { + dispatch(new DeleteWorker($headers, $payload))->onQueue('story'); + return; + } + } + return; + } else if( isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) { + dispatch(new InboxWorker($headers, $payload))->onQueue('follow'); + } else { + dispatch(new InboxWorker($headers, $payload))->onQueue('shared'); + } + return; + } - public function userFollowing(Request $request, $username) - { - abort_if(!config_cache('federation.activitypub.enabled'), 404); + public function userFollowing(Request $request, $username) + { + abort_if(!config_cache('federation.activitypub.enabled'), 404); - $obj = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $request->getUri(), - 'type' => 'OrderedCollectionPage', - 'totalItems' => 0, - 'orderedItems' => [] - ]; - return response()->json($obj); - } + $id = AccountService::usernameToId($username); + abort_if(!$id, 404); + $account = AccountService::get($id); + abort_if(!$account || !isset($account['following_count']), 404); + $obj = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $request->getUri(), + 'type' => 'OrderedCollection', + 'totalItems' => $account['following_count'] ?? 0, + ]; + return response()->json($obj); + } - public function userFollowers(Request $request, $username) - { - abort_if(!config_cache('federation.activitypub.enabled'), 404); - - $obj = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $request->getUri(), - 'type' => 'OrderedCollectionPage', - 'totalItems' => 0, - 'orderedItems' => [] - ]; - - return response()->json($obj); - } + public function userFollowers(Request $request, $username) + { + abort_if(!config_cache('federation.activitypub.enabled'), 404); + $id = AccountService::usernameToId($username); + abort_if(!$id, 404); + $account = AccountService::get($id); + abort_if(!$account || !isset($account['followers_count']), 404); + $obj = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $request->getUri(), + 'type' => 'OrderedCollection', + 'totalItems' => $account['followers_count'] ?? 0, + ]; + return response()->json($obj); + } } diff --git a/contrib/docker/Dockerfile.apache b/contrib/docker/Dockerfile.apache index 9c33aee17..a400f8797 100644 --- a/contrib/docker/Dockerfile.apache +++ b/contrib/docker/Dockerfile.apache @@ -33,7 +33,7 @@ RUN apt-get update \ # Required for GD libxpm4 \ libxpm-dev \ - libwebp6 \ + libwebp7 \ libwebp-dev \ ## Video Processing ffmpeg \ diff --git a/contrib/docker/Dockerfile.fpm b/contrib/docker/Dockerfile.fpm index 0b8e5c113..1bb0a15f7 100644 --- a/contrib/docker/Dockerfile.fpm +++ b/contrib/docker/Dockerfile.fpm @@ -33,7 +33,7 @@ RUN apt-get update \ # Required for GD libxpm4 \ libxpm-dev \ - libwebp6 \ + libwebp7 \ libwebp-dev \ ## Video Processing ffmpeg \ diff --git a/resources/assets/js/components/CollectionComponent.vue b/resources/assets/js/components/CollectionComponent.vue index 3f77cfc13..ea5e21525 100644 --- a/resources/assets/js/components/CollectionComponent.vue +++ b/resources/assets/js/components/CollectionComponent.vue @@ -460,7 +460,7 @@ export default { }) .then(res => { self.postsList = res.data.filter(l => { - return self.ids.indexOf(l.id) == -1; + return (l.visibility == 'public' || l.visibility == 'unlisted') && l.sensitive == false && self.ids.indexOf(l.id) == -1; }); self.loadingPostList = false; self.$refs.addPhotoModal.show(); @@ -619,6 +619,9 @@ export default { this.posts = this.posts.filter(post => { return post.id != id; }); + this.ids = this.ids.filter(post_id => { + return post_id != id; + }); }, addRecentId(post) { @@ -630,6 +633,7 @@ export default { // window.location.reload(); this.closeModals(); this.posts.push(res.data); + this.ids.push(post.id); this.collection.post_count++; }).catch(err => { swal('Oops!', 'An error occured, please try selecting another post.', 'error'); diff --git a/resources/assets/js/components/CollectionCompose.vue b/resources/assets/js/components/CollectionCompose.vue index 84444cf1d..2920e1f76 100644 --- a/resources/assets/js/components/CollectionCompose.vue +++ b/resources/assets/js/components/CollectionCompose.vue @@ -194,7 +194,6 @@ export default { swal('Invalid URL', 'You can only add posts from this instance', 'error'); this.id = ''; } - if(url.includes('/i/web/post/') || url.includes('/p/')) { let id = split[split.length - 1]; console.log('adding ' + id); @@ -228,7 +227,7 @@ export default { let ids = this.posts.map(s => { return s.id; }); - return s.visibility == 'public' && s.sensitive == false && ids.indexOf(s.id) == -1; + return (s.visibility == 'public' || s.visibility == 'unlisted') && s.sensitive == false && ids.indexOf(s.id) == -1; }); }); },