From 15ad69f76e8d56213f1a95f3e60aa34ce3213a94 Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Fri, 8 Mar 2024 02:35:44 -0700
Subject: [PATCH 01/17] Update Curated Onboarding view, fix concierge form
---
resources/views/auth/curated-register/concierge_form.blade.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/resources/views/auth/curated-register/concierge_form.blade.php b/resources/views/auth/curated-register/concierge_form.blade.php
index b8a1f922c..472003fd5 100644
--- a/resources/views/auth/curated-register/concierge_form.blade.php
+++ b/resources/views/auth/curated-register/concierge_form.blade.php
@@ -25,7 +25,7 @@
From our Admins:
-
{{ $activity->message }}
+
{{ $activity->message }}
If you don't understand this request, or need additional context you should request clarification from the admin team.
{{--
--}}
From 25f3fa06aff9d731779ae77b36c2b4b6a83024b3 Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Fri, 8 Mar 2024 03:49:47 -0700
Subject: [PATCH 02/17] Update AP Profile Transformer, add `suspended`
attribute
---
.../ActivityPub/ProfileTransformer.php | 107 +++++++++---------
1 file changed, 56 insertions(+), 51 deletions(-)
diff --git a/app/Transformer/ActivityPub/ProfileTransformer.php b/app/Transformer/ActivityPub/ProfileTransformer.php
index 45d22cd11..a8173c4d0 100644
--- a/app/Transformer/ActivityPub/ProfileTransformer.php
+++ b/app/Transformer/ActivityPub/ProfileTransformer.php
@@ -3,67 +3,72 @@
namespace App\Transformer\ActivityPub;
use App\Profile;
-use League\Fractal;
use App\Services\AccountService;
+use League\Fractal;
class ProfileTransformer extends Fractal\TransformerAbstract
{
public function transform(Profile $profile)
{
$res = [
- '@context' => [
- 'https://w3id.org/security/v1',
- 'https://www.w3.org/ns/activitystreams',
- [
- 'toot' => 'http://joinmastodon.org/ns#',
- 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
- 'alsoKnownAs' => [
- '@id' => 'as:alsoKnownAs',
- '@type' => '@id'
- ],
- 'movedTo' => [
- '@id' => 'as:movedTo',
- '@type' => '@id'
- ],
- 'indexable' => 'toot:indexable',
+ '@context' => [
+ 'https://w3id.org/security/v1',
+ 'https://www.w3.org/ns/activitystreams',
+ [
+ 'toot' => 'http://joinmastodon.org/ns#',
+ 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
+ 'alsoKnownAs' => [
+ '@id' => 'as:alsoKnownAs',
+ '@type' => '@id',
+ ],
+ 'movedTo' => [
+ '@id' => 'as:movedTo',
+ '@type' => '@id',
+ ],
+ 'indexable' => 'toot:indexable',
+ 'suspended' => 'toot:suspended',
+ ],
],
- ],
- 'id' => $profile->permalink(),
- 'type' => 'Person',
- 'following' => $profile->permalink('/following'),
- 'followers' => $profile->permalink('/followers'),
- 'inbox' => $profile->permalink('/inbox'),
- 'outbox' => $profile->permalink('/outbox'),
- 'preferredUsername' => $profile->username,
- 'name' => $profile->name,
- 'summary' => $profile->bio,
- 'url' => $profile->url(),
- 'manuallyApprovesFollowers' => (bool) $profile->is_private,
- 'indexable' => (bool) $profile->indexable,
- 'published' => $profile->created_at->format('Y-m-d') . 'T00:00:00Z',
- 'publicKey' => [
- 'id' => $profile->permalink().'#main-key',
- 'owner' => $profile->permalink(),
- 'publicKeyPem' => $profile->public_key,
- ],
- 'icon' => [
- 'type' => 'Image',
- 'mediaType' => 'image/jpeg',
- 'url' => $profile->avatarUrl(),
- ],
- 'endpoints' => [
- 'sharedInbox' => config('app.url') . '/f/inbox'
- ]
- ];
+ 'id' => $profile->permalink(),
+ 'type' => 'Person',
+ 'following' => $profile->permalink('/following'),
+ 'followers' => $profile->permalink('/followers'),
+ 'inbox' => $profile->permalink('/inbox'),
+ 'outbox' => $profile->permalink('/outbox'),
+ 'preferredUsername' => $profile->username,
+ 'name' => $profile->name,
+ 'summary' => $profile->bio,
+ 'url' => $profile->url(),
+ 'manuallyApprovesFollowers' => (bool) $profile->is_private,
+ 'indexable' => (bool) $profile->indexable,
+ 'published' => $profile->created_at->format('Y-m-d').'T00:00:00Z',
+ 'publicKey' => [
+ 'id' => $profile->permalink().'#main-key',
+ 'owner' => $profile->permalink(),
+ 'publicKeyPem' => $profile->public_key,
+ ],
+ 'icon' => [
+ 'type' => 'Image',
+ 'mediaType' => 'image/jpeg',
+ 'url' => $profile->avatarUrl(),
+ ],
+ 'endpoints' => [
+ 'sharedInbox' => config('app.url').'/f/inbox',
+ ],
+ ];
- if($profile->aliases->count()) {
- $res['alsoKnownAs'] = $profile->aliases->map(fn($alias) => $alias->uri);
- }
+ if ($profile->status === 'delete' || $profile->deleted_at != null) {
+ $res['suspended'] = true;
+ } else {
+ if ($profile->aliases->count()) {
+ $res['alsoKnownAs'] = $profile->aliases->map(fn ($alias) => $alias->uri);
+ }
- if($profile->moved_to_profile_id) {
- $res['movedTo'] = AccountService::get($profile->moved_to_profile_id)['url'];
- }
+ if ($profile->moved_to_profile_id) {
+ $res['movedTo'] = AccountService::get($profile->moved_to_profile_id)['url'];
+ }
+ }
- return $res;
+ return $res;
}
}
From 63100fe9502ea3a7bad0ebaaa25765fcc3291bc3 Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Fri, 8 Mar 2024 03:56:53 -0700
Subject: [PATCH 03/17] Update AP Profile Transformer, fix movedTo attribute
---
app/Transformer/ActivityPub/ProfileTransformer.php | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/app/Transformer/ActivityPub/ProfileTransformer.php b/app/Transformer/ActivityPub/ProfileTransformer.php
index a8173c4d0..7c9ce3c9c 100644
--- a/app/Transformer/ActivityPub/ProfileTransformer.php
+++ b/app/Transformer/ActivityPub/ProfileTransformer.php
@@ -65,7 +65,10 @@ class ProfileTransformer extends Fractal\TransformerAbstract
}
if ($profile->moved_to_profile_id) {
- $res['movedTo'] = AccountService::get($profile->moved_to_profile_id)['url'];
+ $movedTo = AccountService::get($profile->moved_to_profile_id);
+ if ($movedTo && isset($movedTo['url'], $movedTo['id'])) {
+ $res['movedTo'] = $movedTo['url'];
+ }
}
}
From 2e5e68e4477a5bf5c30a81031d4d35ad1856e7ac Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Fri, 8 Mar 2024 04:24:13 -0700
Subject: [PATCH 04/17] Update AP Profile Transformer, fix suspended attributes
---
app/Transformer/ActivityPub/ProfileTransformer.php | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/app/Transformer/ActivityPub/ProfileTransformer.php b/app/Transformer/ActivityPub/ProfileTransformer.php
index 7c9ce3c9c..96d129bf7 100644
--- a/app/Transformer/ActivityPub/ProfileTransformer.php
+++ b/app/Transformer/ActivityPub/ProfileTransformer.php
@@ -59,6 +59,11 @@ class ProfileTransformer extends Fractal\TransformerAbstract
if ($profile->status === 'delete' || $profile->deleted_at != null) {
$res['suspended'] = true;
+ $res['name'] = '';
+ unset($res['icon']);
+ $res['summary'] = '';
+ $res['indexable'] = false;
+ $res['manuallyApprovesFollowers'] = false;
} else {
if ($profile->aliases->count()) {
$res['alsoKnownAs'] = $profile->aliases->map(fn ($alias) => $alias->uri);
From e742d595a62de72ca3c1d95af12db01f3345031c Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Fri, 8 Mar 2024 04:43:57 -0700
Subject: [PATCH 05/17] Update PrivacySettings controller, add cache
invalidation
---
app/Http/Controllers/Settings/PrivacySettings.php | 2 ++
1 file changed, 2 insertions(+)
diff --git a/app/Http/Controllers/Settings/PrivacySettings.php b/app/Http/Controllers/Settings/PrivacySettings.php
index 6697bf3c8..6509071e0 100644
--- a/app/Http/Controllers/Settings/PrivacySettings.php
+++ b/app/Http/Controllers/Settings/PrivacySettings.php
@@ -95,6 +95,8 @@ trait PrivacySettings
Cache::forget('pf:acct:settings:hidden-following:' . $pid);
Cache::forget('pf:acct-trans:hideFollowing:' . $pid);
Cache::forget('pf:acct-trans:hideFollowers:' . $pid);
+ Cache::forget('pfc:cached-user:wt:' . strtolower($profile->username));
+ Cache::forget('pfc:cached-user:wot:' . strtolower($profile->username));
return redirect(route('settings.privacy'))->with('status', 'Settings successfully updated!');
}
From 853a729f762b2209d438c0d2bd5f5bf94b88572a Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Fri, 8 Mar 2024 05:00:56 -0700
Subject: [PATCH 06/17] Update ProfileController, preserve deleted actor
objects for federated account deletion and use more efficient account cache
lookup
---
app/Http/Controllers/ProfileController.php | 605 +++++++++++----------
1 file changed, 317 insertions(+), 288 deletions(-)
diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php
index 26e9e5398..5354475e3 100644
--- a/app/Http/Controllers/ProfileController.php
+++ b/app/Http/Controllers/ProfileController.php
@@ -2,356 +2,385 @@
namespace App\Http\Controllers;
-use Illuminate\Http\Request;
-use Auth;
-use Cache;
-use DB;
-use View;
use App\AccountInterstitial;
use App\Follower;
use App\FollowRequest;
use App\Profile;
-use App\Story;
-use App\Status;
-use App\User;
-use App\UserSetting;
-use App\UserFilter;
-use League\Fractal;
use App\Services\AccountService;
use App\Services\FollowerService;
use App\Services\StatusService;
-use App\Util\Lexer\Nickname;
-use App\Util\Webfinger\Webfinger;
-use App\Transformer\ActivityPub\ProfileOutbox;
+use App\Status;
+use App\Story;
use App\Transformer\ActivityPub\ProfileTransformer;
+use App\User;
+use App\UserFilter;
+use App\UserSetting;
+use Auth;
+use Cache;
+use Illuminate\Http\Request;
+use League\Fractal;
+use View;
class ProfileController extends Controller
{
- public function show(Request $request, $username)
- {
- // redirect authed users to Metro 2.0
- if($request->user()) {
- // unless they force static view
- if(!$request->has('fs') || $request->input('fs') != '1') {
- $pid = AccountService::usernameToId($username);
- if($pid) {
- return redirect('/i/web/profile/' . $pid);
- }
- }
- }
+ public function show(Request $request, $username)
+ {
+ if ($request->wantsJson() && (bool) config_cache('federation.activitypub.enabled')) {
+ $user = $this->getCachedUser($username, true);
+ abort_if(! $user, 404, 'Not found');
- $user = Profile::whereNull('domain')
- ->whereNull('status')
- ->whereUsername($username)
- ->firstOrFail();
+ return $this->showActivityPub($request, $user);
+ }
+ // redirect authed users to Metro 2.0
+ if ($request->user()) {
+ // unless they force static view
+ if (! $request->has('fs') || $request->input('fs') != '1') {
+ $pid = AccountService::usernameToId($username);
+ if ($pid) {
+ return redirect('/i/web/profile/'.$pid);
+ }
+ }
+ }
- if($request->wantsJson() && config_cache('federation.activitypub.enabled')) {
- return $this->showActivityPub($request, $user);
- }
+ $user = $this->getCachedUser($username);
- $aiCheck = Cache::remember('profile:ai-check:spam-login:' . $user->id, 86400, function() use($user) {
- $exists = AccountInterstitial::whereUserId($user->user_id)->where('is_spam', 1)->count();
- if($exists) {
- return true;
- }
+ abort_unless($user, 404);
- return false;
- });
- if($aiCheck) {
- return redirect('/login');
- }
- return $this->buildProfile($request, $user);
- }
+ $aiCheck = Cache::remember('profile:ai-check:spam-login:'.$user->id, 3600, function () use ($user) {
+ $exists = AccountInterstitial::whereUserId($user->user_id)->where('is_spam', 1)->count();
+ if ($exists) {
+ return true;
+ }
- protected function buildProfile(Request $request, $user)
- {
- $username = $user->username;
- $loggedIn = Auth::check();
- $isPrivate = false;
- $isBlocked = false;
- if(!$loggedIn) {
- $key = 'profile:settings:' . $user->id;
- $ttl = now()->addHours(6);
- $settings = Cache::remember($key, $ttl, function() use($user) {
- return $user->user->settings;
- });
+ return false;
+ });
+ if ($aiCheck) {
+ return redirect('/login');
+ }
- if ($user->is_private == true) {
- $profile = null;
- return view('profile.private', compact('user'));
- }
+ return $this->buildProfile($request, $user);
+ }
- $owner = false;
- $is_following = false;
+ protected function buildProfile(Request $request, $user)
+ {
+ $username = $user->username;
+ $loggedIn = Auth::check();
+ $isPrivate = false;
+ $isBlocked = false;
+ if (! $loggedIn) {
+ $key = 'profile:settings:'.$user->id;
+ $ttl = now()->addHours(6);
+ $settings = Cache::remember($key, $ttl, function () use ($user) {
+ return $user->user->settings;
+ });
- $profile = $user;
- $settings = [
- 'crawlable' => $settings->crawlable,
- 'following' => [
- 'count' => $settings->show_profile_following_count,
- 'list' => $settings->show_profile_following
- ],
- 'followers' => [
- 'count' => $settings->show_profile_follower_count,
- 'list' => $settings->show_profile_followers
- ]
- ];
- return view('profile.show', compact('profile', 'settings'));
- } else {
- $key = 'profile:settings:' . $user->id;
- $ttl = now()->addHours(6);
- $settings = Cache::remember($key, $ttl, function() use($user) {
- return $user->user->settings;
- });
+ if ($user->is_private == true) {
+ $profile = null;
- if ($user->is_private == true) {
- $isPrivate = $this->privateProfileCheck($user, $loggedIn);
- }
+ return view('profile.private', compact('user'));
+ }
- $isBlocked = $this->blockedProfileCheck($user);
+ $owner = false;
+ $is_following = false;
- $owner = $loggedIn && Auth::id() === $user->user_id;
- $is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
+ $profile = $user;
+ $settings = [
+ 'crawlable' => $settings->crawlable,
+ 'following' => [
+ 'count' => $settings->show_profile_following_count,
+ 'list' => $settings->show_profile_following,
+ ],
+ 'followers' => [
+ 'count' => $settings->show_profile_follower_count,
+ 'list' => $settings->show_profile_followers,
+ ],
+ ];
- if ($isPrivate == true || $isBlocked == true) {
- $requested = Auth::check() ? FollowRequest::whereFollowerId(Auth::user()->profile_id)
- ->whereFollowingId($user->id)
- ->exists() : false;
- return view('profile.private', compact('user', 'is_following', 'requested'));
- }
+ return view('profile.show', compact('profile', 'settings'));
+ } else {
+ $key = 'profile:settings:'.$user->id;
+ $ttl = now()->addHours(6);
+ $settings = Cache::remember($key, $ttl, function () use ($user) {
+ return $user->user->settings;
+ });
- $is_admin = is_null($user->domain) ? $user->user->is_admin : false;
- $profile = $user;
- $settings = [
- 'crawlable' => $settings->crawlable,
- 'following' => [
- 'count' => $settings->show_profile_following_count,
- 'list' => $settings->show_profile_following
- ],
- 'followers' => [
- 'count' => $settings->show_profile_follower_count,
- 'list' => $settings->show_profile_followers
- ]
- ];
- return view('profile.show', compact('profile', 'settings'));
- }
- }
+ if ($user->is_private == true) {
+ $isPrivate = $this->privateProfileCheck($user, $loggedIn);
+ }
- public function permalinkRedirect(Request $request, $username)
- {
- $user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
+ $isBlocked = $this->blockedProfileCheck($user);
- if ($request->wantsJson() && config_cache('federation.activitypub.enabled')) {
- return $this->showActivityPub($request, $user);
- }
+ $owner = $loggedIn && Auth::id() === $user->user_id;
+ $is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false;
- return redirect($user->url());
- }
+ if ($isPrivate == true || $isBlocked == true) {
+ $requested = Auth::check() ? FollowRequest::whereFollowerId(Auth::user()->profile_id)
+ ->whereFollowingId($user->id)
+ ->exists() : false;
- protected function privateProfileCheck(Profile $profile, $loggedIn)
- {
- if (!Auth::check()) {
- return true;
- }
+ return view('profile.private', compact('user', 'is_following', 'requested'));
+ }
- $user = Auth::user()->profile;
- if($user->id == $profile->id || !$profile->is_private) {
- return false;
- }
+ $is_admin = is_null($user->domain) ? $user->user->is_admin : false;
+ $profile = $user;
+ $settings = [
+ 'crawlable' => $settings->crawlable,
+ 'following' => [
+ 'count' => $settings->show_profile_following_count,
+ 'list' => $settings->show_profile_following,
+ ],
+ 'followers' => [
+ 'count' => $settings->show_profile_follower_count,
+ 'list' => $settings->show_profile_followers,
+ ],
+ ];
- $follows = Follower::whereProfileId($user->id)->whereFollowingId($profile->id)->exists();
- if ($follows == false) {
- return true;
- }
+ return view('profile.show', compact('profile', 'settings'));
+ }
+ }
- return false;
- }
+ protected function getCachedUser($username, $withTrashed = false)
+ {
+ $val = str_replace(['_', '.', '-'], '', $username);
+ if (! ctype_alnum($val)) {
+ return;
+ }
+ $hash = ($withTrashed ? 'wt:' : 'wot:').strtolower($username);
- public static function accountCheck(Profile $profile)
- {
- switch ($profile->status) {
- case 'disabled':
- case 'suspended':
- case 'delete':
- return view('profile.disabled');
- break;
+ return Cache::remember('pfc:cached-user:'.$hash, ($withTrashed ? 14400 : 900), function () use ($username, $withTrashed) {
+ if (! $withTrashed) {
+ return Profile::whereNull(['domain', 'status'])
+ ->whereUsername($username)
+ ->first();
+ } else {
+ return Profile::withTrashed()
+ ->whereNull('domain')
+ ->whereUsername($username)
+ ->first();
+ }
+ });
+ }
- default:
- break;
- }
- return abort(404);
- }
+ public function permalinkRedirect(Request $request, $username)
+ {
+ if ($request->wantsJson() && (bool) config_cache('federation.activitypub.enabled')) {
+ $user = $this->getCachedUser($username, true);
- protected function blockedProfileCheck(Profile $profile)
- {
- $pid = Auth::user()->profile->id;
- $blocks = UserFilter::whereUserId($profile->id)
- ->whereFilterType('block')
- ->whereFilterableType('App\Profile')
- ->pluck('filterable_id')
- ->toArray();
- if (in_array($pid, $blocks)) {
- return true;
- }
+ return $this->showActivityPub($request, $user);
+ }
- return false;
- }
+ $user = $this->getCachedUser($username);
- public function showActivityPub(Request $request, $user)
- {
- abort_if(!config_cache('federation.activitypub.enabled'), 404);
- abort_if($user->domain, 404);
+ return redirect($user->url());
+ }
- return Cache::remember('pf:activitypub:user-object:by-id:' . $user->id, 3600, function() use($user) {
- $fractal = new Fractal\Manager();
- $resource = new Fractal\Resource\Item($user, new ProfileTransformer);
- $res = $fractal->createData($resource)->toArray();
- return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
- });
- }
+ protected function privateProfileCheck(Profile $profile, $loggedIn)
+ {
+ if (! Auth::check()) {
+ return true;
+ }
- public function showAtomFeed(Request $request, $user)
- {
- abort_if(!config('federation.atom.enabled'), 404);
+ $user = Auth::user()->profile;
+ if ($user->id == $profile->id || ! $profile->is_private) {
+ return false;
+ }
- $pid = AccountService::usernameToId($user);
+ $follows = Follower::whereProfileId($user->id)->whereFollowingId($profile->id)->exists();
+ if ($follows == false) {
+ return true;
+ }
- abort_if(!$pid, 404);
+ return false;
+ }
- $profile = AccountService::get($pid, true);
+ public static function accountCheck(Profile $profile)
+ {
+ switch ($profile->status) {
+ case 'disabled':
+ case 'suspended':
+ case 'delete':
+ return view('profile.disabled');
+ break;
- abort_if(!$profile || $profile['locked'] || !$profile['local'], 404);
+ default:
+ break;
+ }
- $aiCheck = Cache::remember('profile:ai-check:spam-login:' . $profile['id'], 86400, function() use($profile) {
- $uid = User::whereProfileId($profile['id'])->first();
- if(!$uid) {
- return true;
- }
- $exists = AccountInterstitial::whereUserId($uid->id)->where('is_spam', 1)->count();
- if($exists) {
- return true;
- }
+ return abort(404);
+ }
- return false;
- });
+ protected function blockedProfileCheck(Profile $profile)
+ {
+ $pid = Auth::user()->profile->id;
+ $blocks = UserFilter::whereUserId($profile->id)
+ ->whereFilterType('block')
+ ->whereFilterableType('App\Profile')
+ ->pluck('filterable_id')
+ ->toArray();
+ if (in_array($pid, $blocks)) {
+ return true;
+ }
- abort_if($aiCheck, 404);
+ return false;
+ }
- $enabled = Cache::remember('profile:atom:enabled:' . $profile['id'], 84600, function() use ($profile) {
- $uid = User::whereProfileId($profile['id'])->first();
- if(!$uid) {
- return false;
- }
- $settings = UserSetting::whereUserId($uid->id)->first();
- if(!$settings) {
- return false;
- }
+ public function showActivityPub(Request $request, $user)
+ {
+ abort_if(! config_cache('federation.activitypub.enabled'), 404);
+ abort_if(! $user, 404, 'Not found');
+ abort_if($user->domain, 404);
- return $settings->show_atom;
- });
+ return Cache::remember('pf:activitypub:user-object:by-id:'.$user->id, 1800, function () use ($user) {
+ $fractal = new Fractal\Manager();
+ $resource = new Fractal\Resource\Item($user, new ProfileTransformer);
+ $res = $fractal->createData($resource)->toArray();
- abort_if(!$enabled, 404);
+ return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json');
+ });
+ }
- $data = Cache::remember('pf:atom:user-feed:by-id:' . $profile['id'], 900, function() use($pid, $profile) {
- $items = Status::whereProfileId($pid)
- ->whereScope('public')
- ->whereIn('type', ['photo', 'photo:album'])
- ->orderByDesc('id')
- ->take(10)
- ->get()
- ->map(function($status) {
- return StatusService::get($status->id, true);
- })
- ->filter(function($status) {
- return $status &&
- isset($status['account']) &&
- isset($status['media_attachments']) &&
- count($status['media_attachments']);
- })
- ->values();
- $permalink = config('app.url') . "/users/{$profile['username']}.atom";
- $headers = ['Content-Type' => 'application/atom+xml'];
+ public function showAtomFeed(Request $request, $user)
+ {
+ abort_if(! config('federation.atom.enabled'), 404);
- if($items && $items->count()) {
- $headers['Last-Modified'] = now()->parse($items->first()['created_at'])->toRfc7231String();
- }
+ $pid = AccountService::usernameToId($user);
- return compact('items', 'permalink', 'headers');
- });
- abort_if(!$data || !isset($data['items']) || !isset($data['permalink']), 404);
- return response()
- ->view('atom.user',
- [
- 'profile' => $profile,
- 'items' => $data['items'],
- 'permalink' => $data['permalink']
- ]
- )
- ->withHeaders($data['headers']);
- }
+ abort_if(! $pid, 404);
- public function meRedirect()
- {
- abort_if(!Auth::check(), 404);
- return redirect(Auth::user()->url());
- }
+ $profile = AccountService::get($pid, true);
- public function embed(Request $request, $username)
- {
- $res = view('profile.embed-removed');
+ abort_if(! $profile || $profile['locked'] || ! $profile['local'], 404);
- if(!config('instance.embed.profile')) {
- return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
- }
+ $aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile['id'], 86400, function () use ($profile) {
+ $uid = User::whereProfileId($profile['id'])->first();
+ if (! $uid) {
+ return true;
+ }
+ $exists = AccountInterstitial::whereUserId($uid->id)->where('is_spam', 1)->count();
+ if ($exists) {
+ return true;
+ }
- if(strlen($username) > 15 || strlen($username) < 2) {
- return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
- }
+ return false;
+ });
- $profile = Profile::whereUsername($username)
- ->whereIsPrivate(false)
- ->whereNull('status')
- ->whereNull('domain')
- ->first();
+ abort_if($aiCheck, 404);
- if(!$profile) {
- return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
- }
+ $enabled = Cache::remember('profile:atom:enabled:'.$profile['id'], 84600, function () use ($profile) {
+ $uid = User::whereProfileId($profile['id'])->first();
+ if (! $uid) {
+ return false;
+ }
+ $settings = UserSetting::whereUserId($uid->id)->first();
+ if (! $settings) {
+ return false;
+ }
- $aiCheck = Cache::remember('profile:ai-check:spam-login:' . $profile->id, 86400, function() use($profile) {
- $exists = AccountInterstitial::whereUserId($profile->user_id)->where('is_spam', 1)->count();
- if($exists) {
- return true;
- }
+ return $settings->show_atom;
+ });
- return false;
- });
+ abort_if(! $enabled, 404);
- if($aiCheck) {
- return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
- }
+ $data = Cache::remember('pf:atom:user-feed:by-id:'.$profile['id'], 14400, function () use ($pid, $profile) {
+ $items = Status::whereProfileId($pid)
+ ->whereScope('public')
+ ->whereIn('type', ['photo', 'photo:album'])
+ ->orderByDesc('id')
+ ->take(10)
+ ->get()
+ ->map(function ($status) {
+ return StatusService::get($status->id, true);
+ })
+ ->filter(function ($status) {
+ return $status &&
+ isset($status['account']) &&
+ isset($status['media_attachments']) &&
+ count($status['media_attachments']);
+ })
+ ->values();
+ $permalink = config('app.url')."/users/{$profile['username']}.atom";
+ $headers = ['Content-Type' => 'application/atom+xml'];
- if(AccountService::canEmbed($profile->user_id) == false) {
- return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
- }
+ if ($items && $items->count()) {
+ $headers['Last-Modified'] = now()->parse($items->first()['created_at'])->toRfc7231String();
+ }
- $profile = AccountService::get($profile->id);
- $res = view('profile.embed', compact('profile'));
- return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
- }
+ return compact('items', 'permalink', 'headers');
+ });
+ abort_if(! $data || ! isset($data['items']) || ! isset($data['permalink']), 404);
- public function stories(Request $request, $username)
- {
- abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
- $profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
- $pid = $profile->id;
- $authed = Auth::user()->profile_id;
- abort_if($pid != $authed && !FollowerService::follows($authed, $pid), 404);
- $exists = Story::whereProfileId($pid)
- ->whereActive(true)
- ->exists();
- abort_unless($exists, 404);
- return view('profile.story', compact('pid', 'profile'));
- }
+ return response()
+ ->view('atom.user',
+ [
+ 'profile' => $profile,
+ 'items' => $data['items'],
+ 'permalink' => $data['permalink'],
+ ]
+ )
+ ->withHeaders($data['headers']);
+ }
+
+ public function meRedirect()
+ {
+ abort_if(! Auth::check(), 404);
+
+ return redirect(Auth::user()->url());
+ }
+
+ public function embed(Request $request, $username)
+ {
+ $res = view('profile.embed-removed');
+
+ if (! config('instance.embed.profile')) {
+ return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+ }
+
+ if (strlen($username) > 15 || strlen($username) < 2) {
+ return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+ }
+
+ $profile = $this->getCachedUser($username);
+
+ if (! $profile || $profile->is_private) {
+ return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+ }
+
+ $aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile->id, 86400, function () use ($profile) {
+ $exists = AccountInterstitial::whereUserId($profile->user_id)->where('is_spam', 1)->count();
+ if ($exists) {
+ return true;
+ }
+
+ return false;
+ });
+
+ if ($aiCheck) {
+ return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+ }
+
+ if (AccountService::canEmbed($profile->user_id) == false) {
+ return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+ }
+
+ $profile = AccountService::get($profile->id);
+ $res = view('profile.embed', compact('profile'));
+
+ return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+ }
+
+ public function stories(Request $request, $username)
+ {
+ abort_if(! config_cache('instance.stories.enabled') || ! $request->user(), 404);
+ $profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
+ $pid = $profile->id;
+ $authed = Auth::user()->profile_id;
+ abort_if($pid != $authed && ! FollowerService::follows($authed, $pid), 404);
+ $exists = Story::whereProfileId($pid)
+ ->whereActive(true)
+ ->exists();
+ abort_unless($exists, 404);
+
+ return view('profile.story', compact('pid', 'profile'));
+ }
}
From 95199843e30430b52c14e1427fc39e97355b9aae Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Fri, 8 Mar 2024 05:03:29 -0700
Subject: [PATCH 07/17] Update SiteController, add curatedOnboarding method
that gracefully falls back to open registration when applicable
---
app/Http/Controllers/SiteController.php | 310 +++++++++++++-----------
1 file changed, 173 insertions(+), 137 deletions(-)
diff --git a/app/Http/Controllers/SiteController.php b/app/Http/Controllers/SiteController.php
index 379b24505..5e205d64d 100644
--- a/app/Http/Controllers/SiteController.php
+++ b/app/Http/Controllers/SiteController.php
@@ -2,166 +2,202 @@
namespace App\Http\Controllers;
+use App\Page;
+use App\Profile;
+use App\Services\FollowerService;
+use App\Status;
+use App\User;
+use App\Util\ActivityPub\Helpers;
+use App\Util\Localization\Localization;
+use Auth;
+use Cache;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
-use App, Auth, Cache, View;
-use App\Util\Lexer\PrettyNumber;
-use App\{Follower, Page, Profile, Status, User, UserFilter};
-use App\Util\Localization\Localization;
-use App\Services\FollowerService;
-use App\Util\ActivityPub\Helpers;
+use View;
class SiteController extends Controller
{
- public function home(Request $request)
- {
- if (Auth::check()) {
- return $this->homeTimeline($request);
- } else {
- return $this->homeGuest();
- }
- }
+ public function home(Request $request)
+ {
+ if (Auth::check()) {
+ return $this->homeTimeline($request);
+ } else {
+ return $this->homeGuest();
+ }
+ }
- public function homeGuest()
- {
- return view('site.index');
- }
+ public function homeGuest()
+ {
+ return view('site.index');
+ }
- public function homeTimeline(Request $request)
- {
- if($request->has('force_old_ui')) {
- return view('timeline.home', ['layout' => 'feed']);
- }
+ public function homeTimeline(Request $request)
+ {
+ if ($request->has('force_old_ui')) {
+ return view('timeline.home', ['layout' => 'feed']);
+ }
- return redirect('/i/web');
- }
+ return redirect('/i/web');
+ }
- public function changeLocale(Request $request, $locale)
- {
- // todo: add other locales after pushing new l10n strings
- $locales = Localization::languages();
- if(in_array($locale, $locales)) {
- if($request->user()) {
- $user = $request->user();
- $user->language = $locale;
- $user->save();
- }
- session()->put('locale', $locale);
- }
+ public function changeLocale(Request $request, $locale)
+ {
+ // todo: add other locales after pushing new l10n strings
+ $locales = Localization::languages();
+ if (in_array($locale, $locales)) {
+ if ($request->user()) {
+ $user = $request->user();
+ $user->language = $locale;
+ $user->save();
+ }
+ session()->put('locale', $locale);
+ }
- return redirect(route('site.language'));
- }
+ return redirect(route('site.language'));
+ }
- public function about()
- {
- return Cache::remember('site.about_v2', now()->addMinutes(15), function() {
- $user_count = number_format(User::count());
- $post_count = number_format(Status::count());
- $rules = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : null;
- return view('site.about', compact('rules', 'user_count', 'post_count'))->render();
- });
- }
+ public function about()
+ {
+ return Cache::remember('site.about_v2', now()->addMinutes(15), function () {
+ $user_count = number_format(User::count());
+ $post_count = number_format(Status::count());
+ $rules = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : null;
- public function language()
- {
- return view('site.language');
- }
+ return view('site.about', compact('rules', 'user_count', 'post_count'))->render();
+ });
+ }
- public function communityGuidelines(Request $request)
- {
- return Cache::remember('site:help:community-guidelines', now()->addDays(120), function() {
- $slug = '/site/kb/community-guidelines';
- $page = Page::whereSlug($slug)->whereActive(true)->first();
- return View::make('site.help.community-guidelines')->with(compact('page'))->render();
- });
- }
+ public function language()
+ {
+ return view('site.language');
+ }
- public function privacy(Request $request)
- {
- $page = Cache::remember('site:privacy', now()->addDays(120), function() {
- $slug = '/site/privacy';
- return Page::whereSlug($slug)->whereActive(true)->first();
- });
- return View::make('site.privacy')->with(compact('page'))->render();
- }
+ public function communityGuidelines(Request $request)
+ {
+ return Cache::remember('site:help:community-guidelines', now()->addDays(120), function () {
+ $slug = '/site/kb/community-guidelines';
+ $page = Page::whereSlug($slug)->whereActive(true)->first();
- public function terms(Request $request)
- {
- $page = Cache::remember('site:terms', now()->addDays(120), function() {
- $slug = '/site/terms';
- return Page::whereSlug($slug)->whereActive(true)->first();
- });
- return View::make('site.terms')->with(compact('page'))->render();
- }
+ return View::make('site.help.community-guidelines')->with(compact('page'))->render();
+ });
+ }
- public function redirectUrl(Request $request)
- {
- abort_if(!$request->user(), 404);
- $this->validate($request, [
- 'url' => 'required|url'
- ]);
- $url = request()->input('url');
- abort_if(Helpers::validateUrl($url) == false, 404);
- return view('site.redirect', compact('url'));
- }
+ public function privacy(Request $request)
+ {
+ $page = Cache::remember('site:privacy', now()->addDays(120), function () {
+ $slug = '/site/privacy';
- public function followIntent(Request $request)
- {
- $this->validate($request, [
- 'user' => 'string|min:1|max:15|exists:users,username',
- ]);
- $profile = Profile::whereUsername($request->input('user'))->firstOrFail();
- $user = $request->user();
- abort_if($user && $profile->id == $user->profile_id, 404);
- $following = $user != null ? FollowerService::follows($user->profile_id, $profile->id) : false;
- return view('site.intents.follow', compact('profile', 'user', 'following'));
- }
+ return Page::whereSlug($slug)->whereActive(true)->first();
+ });
- public function legacyProfileRedirect(Request $request, $username)
- {
- $username = Str::contains($username, '@') ? '@' . $username : $username;
- if(str_contains($username, '@')) {
- $profile = Profile::whereUsername($username)
- ->firstOrFail();
+ return View::make('site.privacy')->with(compact('page'))->render();
+ }
- if($profile->domain == null) {
- $url = "/$profile->username";
- } else {
- $url = "/i/web/profile/_/{$profile->id}";
- }
+ public function terms(Request $request)
+ {
+ $page = Cache::remember('site:terms', now()->addDays(120), function () {
+ $slug = '/site/terms';
- } else {
- $profile = Profile::whereUsername($username)
- ->whereNull('domain')
- ->firstOrFail();
- $url = "/$profile->username";
- }
+ return Page::whereSlug($slug)->whereActive(true)->first();
+ });
- return redirect($url);
- }
+ return View::make('site.terms')->with(compact('page'))->render();
+ }
- public function legacyWebfingerRedirect(Request $request, $username, $domain)
- {
- $un = '@'.$username.'@'.$domain;
- $profile = Profile::whereUsername($un)
- ->firstOrFail();
+ public function redirectUrl(Request $request)
+ {
+ abort_if(! $request->user(), 404);
+ $this->validate($request, [
+ 'url' => 'required|url',
+ ]);
+ $url = request()->input('url');
+ abort_if(Helpers::validateUrl($url) == false, 404);
- if($profile->domain == null) {
- $url = "/$profile->username";
- } else {
- $url = $request->user() ? "/i/web/profile/_/{$profile->id}" : $profile->url();
- }
+ return view('site.redirect', compact('url'));
+ }
- return redirect($url);
- }
+ public function followIntent(Request $request)
+ {
+ $this->validate($request, [
+ 'user' => 'string|min:1|max:15|exists:users,username',
+ ]);
+ $profile = Profile::whereUsername($request->input('user'))->firstOrFail();
+ $user = $request->user();
+ abort_if($user && $profile->id == $user->profile_id, 404);
+ $following = $user != null ? FollowerService::follows($user->profile_id, $profile->id) : false;
- public function legalNotice(Request $request)
- {
- $page = Cache::remember('site:legal-notice', now()->addDays(120), function() {
- $slug = '/site/legal-notice';
- return Page::whereSlug($slug)->whereActive(true)->first();
- });
- abort_if(!$page, 404);
- return View::make('site.legal-notice')->with(compact('page'))->render();
- }
+ return view('site.intents.follow', compact('profile', 'user', 'following'));
+ }
+
+ public function legacyProfileRedirect(Request $request, $username)
+ {
+ $username = Str::contains($username, '@') ? '@'.$username : $username;
+ if (str_contains($username, '@')) {
+ $profile = Profile::whereUsername($username)
+ ->firstOrFail();
+
+ if ($profile->domain == null) {
+ $url = "/$profile->username";
+ } else {
+ $url = "/i/web/profile/_/{$profile->id}";
+ }
+
+ } else {
+ $profile = Profile::whereUsername($username)
+ ->whereNull('domain')
+ ->firstOrFail();
+ $url = "/$profile->username";
+ }
+
+ return redirect($url);
+ }
+
+ public function legacyWebfingerRedirect(Request $request, $username, $domain)
+ {
+ $un = '@'.$username.'@'.$domain;
+ $profile = Profile::whereUsername($un)
+ ->firstOrFail();
+
+ if ($profile->domain == null) {
+ $url = "/$profile->username";
+ } else {
+ $url = $request->user() ? "/i/web/profile/_/{$profile->id}" : $profile->url();
+ }
+
+ return redirect($url);
+ }
+
+ public function legalNotice(Request $request)
+ {
+ $page = Cache::remember('site:legal-notice', now()->addDays(120), function () {
+ $slug = '/site/legal-notice';
+
+ return Page::whereSlug($slug)->whereActive(true)->first();
+ });
+ abort_if(! $page, 404);
+
+ return View::make('site.legal-notice')->with(compact('page'))->render();
+ }
+
+ public function curatedOnboarding(Request $request)
+ {
+ if ($request->user()) {
+ return redirect('/i/web');
+ }
+
+ $regOpen = (bool) config_cache('pixelfed.open_registration');
+ $curOnboarding = (bool) config_cache('instance.curated_registration.enabled');
+ $curOnlyClosed = (bool) config('instance.curated_registration.state.only_enabled_on_closed_reg');
+ if ($regOpen) {
+ if ($curOnlyClosed) {
+ return redirect('/register');
+ }
+ } else {
+ if (! $curOnboarding) {
+ return redirect('/');
+ }
+ }
+
+ return view('auth.curated-register.index', ['step' => 1]);
+ }
}
From 36c518fe2c40f76bf2392a4bf9dbc2fa11bc07f7 Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Fri, 8 Mar 2024 05:04:27 -0700
Subject: [PATCH 08/17] Update web routes
---
routes/web.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/routes/web.php b/routes/web.php
index 87ac4b564..6768ddb1b 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -29,7 +29,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::get('auth/pci/{id}/{code}', 'ParentalControlsController@inviteRegister');
Route::post('auth/pci/{id}/{code}', 'ParentalControlsController@inviteRegisterStore');
- Route::get('auth/sign_up', 'CuratedRegisterController@index')->name('auth.curated-onboarding');
+ Route::get('auth/sign_up', 'SiteController@curatedOnboarding')->name('auth.curated-onboarding');
Route::post('auth/sign_up', 'CuratedRegisterController@proceed');
Route::get('auth/sign_up/concierge/response-sent', 'CuratedRegisterController@conciergeResponseSent');
Route::get('auth/sign_up/concierge', 'CuratedRegisterController@concierge');
From a969ca502f5d26073f56863be98f33e165b24d9b Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Fri, 8 Mar 2024 06:01:35 -0700
Subject: [PATCH 09/17] Add migrations
---
...red_inbox_attribute_to_instances_table.php | 28 ++++++++++++++++
..._add_shared_inboxes_to_instances_table.php | 32 +++++++++++++++++++
2 files changed, 60 insertions(+)
create mode 100644 database/migrations/2024_03_08_122947_add_shared_inbox_attribute_to_instances_table.php
create mode 100644 database/migrations/2024_03_08_123356_add_shared_inboxes_to_instances_table.php
diff --git a/database/migrations/2024_03_08_122947_add_shared_inbox_attribute_to_instances_table.php b/database/migrations/2024_03_08_122947_add_shared_inbox_attribute_to_instances_table.php
new file mode 100644
index 000000000..a3d69f271
--- /dev/null
+++ b/database/migrations/2024_03_08_122947_add_shared_inbox_attribute_to_instances_table.php
@@ -0,0 +1,28 @@
+string('shared_inbox')->nullable()->index();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('instances', function (Blueprint $table) {
+ $table->dropColumn('shared_inbox');
+ });
+ }
+};
diff --git a/database/migrations/2024_03_08_123356_add_shared_inboxes_to_instances_table.php b/database/migrations/2024_03_08_123356_add_shared_inboxes_to_instances_table.php
new file mode 100644
index 000000000..ff7eebc69
--- /dev/null
+++ b/database/migrations/2024_03_08_123356_add_shared_inboxes_to_instances_table.php
@@ -0,0 +1,32 @@
+domain)->whereNotNull('sharedInbox')->first();
+ if($si && $si->sharedInbox) {
+ $instance->shared_inbox = $si->sharedInbox;
+ $instance->save();
+ }
+ }
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+
+ }
+};
From bcce1df6fc1d3a3ba4b9d75a91b48e3266aad07f Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Fri, 8 Mar 2024 06:02:11 -0700
Subject: [PATCH 10/17] Update AP transformers, add DeleteActor activity
---
.../ActivityPub/Verb/DeleteActor.php | 24 +++++++++++++++++++
1 file changed, 24 insertions(+)
create mode 100644 app/Transformer/ActivityPub/Verb/DeleteActor.php
diff --git a/app/Transformer/ActivityPub/Verb/DeleteActor.php b/app/Transformer/ActivityPub/Verb/DeleteActor.php
new file mode 100644
index 000000000..5d3fdbc07
--- /dev/null
+++ b/app/Transformer/ActivityPub/Verb/DeleteActor.php
@@ -0,0 +1,24 @@
+ 'https://www.w3.org/ns/activitystreams',
+ 'id' => $profile->permalink('#delete'),
+ 'type' => 'Delete',
+ 'actor' => $profile->permalink(),
+ 'to' => [
+ 'https://www.w3.org/ns/activitystreams#Public'
+ ],
+ 'object' => $profile->permalink()
+ ];
+ }
+
+}
From 4aa0e25f4c5c394d49727e6aaba89c0b3405c927 Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Fri, 8 Mar 2024 06:44:02 -0700
Subject: [PATCH 11/17] Update commands, add user account delete cli command to
federate account deletion
---
app/Console/Commands/UserAccountDelete.php | 123 +++++++++++++++++++++
1 file changed, 123 insertions(+)
create mode 100644 app/Console/Commands/UserAccountDelete.php
diff --git a/app/Console/Commands/UserAccountDelete.php b/app/Console/Commands/UserAccountDelete.php
new file mode 100644
index 000000000..68fad1e92
--- /dev/null
+++ b/app/Console/Commands/UserAccountDelete.php
@@ -0,0 +1,123 @@
+ strlen($value) > 0
+ ? User::withTrashed()->whereStatus('deleted')->where('username', 'like', "%{$value}%")->pluck('username', 'id')->all()
+ : [],
+ );
+
+ $user = User::withTrashed()->find($id);
+
+ table(
+ ['Username', 'Name', 'Email', 'Created'],
+ [[$user->username, $user->name, $user->email, $user->created_at]]
+ );
+
+ $confirmed = confirm(
+ label: 'Do you want to federate this account deletion?',
+ default: false,
+ yes: 'Proceed',
+ no: 'Cancel',
+ hint: 'This action is irreversible'
+ );
+
+ if (! $confirmed) {
+ $this->error('Aborting...');
+ exit;
+ }
+
+ $profile = Profile::withTrashed()->find($user->profile_id);
+
+ $fractal = new Fractal\Manager();
+ $fractal->setSerializer(new ArraySerializer());
+ $resource = new Fractal\Resource\Item($profile, new DeleteActor());
+ $activity = $fractal->createData($resource)->toArray();
+
+ $audience = Instance::whereNotNull(['shared_inbox', 'nodeinfo_last_fetched'])
+ ->where('nodeinfo_last_fetched', '>', now()->subHours(12))
+ ->distinct()
+ ->pluck('shared_inbox');
+
+ $payload = json_encode($activity);
+
+ $client = new Client([
+ 'timeout' => 10,
+ ]);
+
+ $version = config('pixelfed.version');
+ $appUrl = config('app.url');
+ $userAgent = "(Pixelfed/{$version}; +{$appUrl})";
+
+ $requests = function ($audience) use ($client, $activity, $profile, $payload, $userAgent) {
+ foreach ($audience as $url) {
+ $headers = HttpSignature::sign($profile, $url, $activity, [
+ 'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
+ 'User-Agent' => $userAgent,
+ ]);
+ yield function () use ($client, $url, $headers, $payload) {
+ return $client->postAsync($url, [
+ 'curl' => [
+ CURLOPT_HTTPHEADER => $headers,
+ CURLOPT_POSTFIELDS => $payload,
+ CURLOPT_HEADER => true,
+ CURLOPT_SSL_VERIFYPEER => false,
+ CURLOPT_SSL_VERIFYHOST => false,
+ ],
+ ]);
+ };
+ }
+ };
+
+ $pool = new Pool($client, $requests($audience), [
+ 'concurrency' => 50,
+ 'fulfilled' => function ($response, $index) {
+ },
+ 'rejected' => function ($reason, $index) {
+ },
+ ]);
+
+ $promise = $pool->promise();
+
+ $promise->wait();
+ }
+}
From 37a82cfb902d55551ae55c327629bde84c4da259 Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Fri, 8 Mar 2024 06:44:29 -0700
Subject: [PATCH 12/17] Update changelog
---
CHANGELOG.md | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c787faaee..2b7670c75 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,15 @@
- Update DiscoverController, handle discover hashtag redirects ([18382e8a](https://github.com/pixelfed/pixelfed/commit/18382e8a))
- Update ApiV1Controller, use admin filter service ([94503a1c](https://github.com/pixelfed/pixelfed/commit/94503a1c))
- Update SearchApiV2Service, use more efficient query ([cee618e8](https://github.com/pixelfed/pixelfed/commit/cee618e8))
+- Update Curated Onboarding view, fix concierge form ([15ad69f7](https://github.com/pixelfed/pixelfed/commit/15ad69f7))
+- Update AP Profile Transformer, add `suspended` attribute ([25f3fa06](https://github.com/pixelfed/pixelfed/commit/25f3fa06))
+- Update AP Profile Transformer, fix movedTo attribute ([63100fe9](https://github.com/pixelfed/pixelfed/commit/63100fe9))
+- Update AP Profile Transformer, fix suspended attributes ([2e5e68e4](https://github.com/pixelfed/pixelfed/commit/2e5e68e4))
+- Update PrivacySettings controller, add cache invalidation ([e742d595](https://github.com/pixelfed/pixelfed/commit/e742d595))
+- Update ProfileController, preserve deleted actor objects for federated account deletion and use more efficient account cache lookup ([853a729f](https://github.com/pixelfed/pixelfed/commit/853a729f))
+- Update SiteController, add curatedOnboarding method that gracefully falls back to open registration when applicable ([95199843](https://github.com/pixelfed/pixelfed/commit/95199843))
+- Update AP transformers, add DeleteActor activity ([bcce1df6](https://github.com/pixelfed/pixelfed/commit/bcce1df6))
+- Update commands, add user account delete cli command to federate account deletion ([4aa0e25f](https://github.com/pixelfed/pixelfed/commit/4aa0e25f))
- ([](https://github.com/pixelfed/pixelfed/commit/))
## [v0.11.13 (2024-03-05)](https://github.com/pixelfed/pixelfed/compare/v0.11.12...v0.11.13)
From a4bc5ce3d030be6021dbab8824f80246bd296b83 Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Sat, 9 Mar 2024 23:25:28 -0700
Subject: [PATCH 13/17] Update web-api popular accounts route to its own method
to remove the breaking oauth scope bug
---
app/Http/Controllers/Api/ApiV1Controller.php | 6 ++-
app/Http/Controllers/DiscoverController.php | 43 ++++++++++++++++++++
routes/web-api.php | 2 +-
3 files changed, 49 insertions(+), 2 deletions(-)
diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php
index 8253e1373..5ab69dfc9 100644
--- a/app/Http/Controllers/Api/ApiV1Controller.php
+++ b/app/Http/Controllers/Api/ApiV1Controller.php
@@ -4066,7 +4066,7 @@ class ApiV1Controller extends Controller
$pid = $request->user()->profile_id;
- $ids = Cache::remember('api:v1.1:discover:accounts:popular', 3600, function () {
+ $ids = Cache::remember('api:v1.1:discover:accounts:popular', 14400, function () {
return DB::table('profiles')
->where('is_private', false)
->whereNull('status')
@@ -4075,6 +4075,7 @@ class ApiV1Controller extends Controller
->get();
});
$filters = UserFilterService::filters($pid);
+ $asf = AdminShadowFilterService::getHideFromPublicFeedsList();
$ids = $ids->map(function ($profile) {
return AccountService::get($profile->id, true);
})
@@ -4087,6 +4088,9 @@ class ApiV1Controller extends Controller
->filter(function ($profile) use ($pid) {
return ! FollowerService::follows($pid, $profile['id'], true);
})
+ ->filter(function ($profile) use ($asf) {
+ return ! in_array($profile['id'], $asf);
+ })
->filter(function ($profile) use ($filters) {
return ! in_array($profile['id'], $filters);
})
diff --git a/app/Http/Controllers/DiscoverController.php b/app/Http/Controllers/DiscoverController.php
index 862a16ad0..c9e93eecf 100644
--- a/app/Http/Controllers/DiscoverController.php
+++ b/app/Http/Controllers/DiscoverController.php
@@ -5,8 +5,11 @@ namespace App\Http\Controllers;
use App\Hashtag;
use App\Instance;
use App\Like;
+use App\Services\AccountService;
+use App\Services\AdminShadowFilterService;
use App\Services\BookmarkService;
use App\Services\ConfigCacheService;
+use App\Services\FollowerService;
use App\Services\HashtagService;
use App\Services\LikeService;
use App\Services\ReblogService;
@@ -377,4 +380,44 @@ class DiscoverController extends Controller
return $res;
}
+
+ public function discoverAccountsPopular(Request $request)
+ {
+ abort_if(! $request->user(), 403);
+
+ $pid = $request->user()->profile_id;
+
+ $ids = Cache::remember('api:v1.1:discover:accounts:popular', 14400, function () {
+ return DB::table('profiles')
+ ->where('is_private', false)
+ ->whereNull('status')
+ ->orderByDesc('profiles.followers_count')
+ ->limit(30)
+ ->get();
+ });
+ $filters = UserFilterService::filters($pid);
+ $asf = AdminShadowFilterService::getHideFromPublicFeedsList();
+ $ids = $ids->map(function ($profile) {
+ return AccountService::get($profile->id, true);
+ })
+ ->filter(function ($profile) {
+ return $profile && isset($profile['id'], $profile['locked']) && ! $profile['locked'];
+ })
+ ->filter(function ($profile) use ($pid) {
+ return $profile['id'] != $pid;
+ })
+ ->filter(function ($profile) use ($pid) {
+ return ! FollowerService::follows($pid, $profile['id'], true);
+ })
+ ->filter(function ($profile) use ($asf) {
+ return ! in_array($profile['id'], $asf);
+ })
+ ->filter(function ($profile) use ($filters) {
+ return ! in_array($profile['id'], $filters);
+ })
+ ->take(16)
+ ->values();
+
+ return response()->json($ids, 200, [], JSON_UNESCAPED_SLASHES);
+ }
}
diff --git a/routes/web-api.php b/routes/web-api.php
index e19c36b6c..4b0595c66 100644
--- a/routes/web-api.php
+++ b/routes/web-api.php
@@ -115,7 +115,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact
Route::post('discover/admin/features', 'DiscoverController@updateFeatures');
});
- Route::get('discover/accounts/popular', 'Api\ApiV1Controller@discoverAccountsPopular');
+ Route::get('discover/accounts/popular', 'DiscoverController@discoverAccountsPopular');
Route::post('web/change-language.json', 'SpaController@updateLanguage');
});
From 5e4d4eff9dbaa24cdcf0b853ab72d145cd1a3998 Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Sun, 10 Mar 2024 04:19:44 -0600
Subject: [PATCH 14/17] Update config cache
---
app/Http/Controllers/Api/ApiV1Controller.php | 4 +-
app/Http/Controllers/Api/ApiV2Controller.php | 2 +-
app/Http/Controllers/CommentController.php | 35 +-
app/Http/Controllers/ComposeController.php | 398 +++++++++---------
app/Services/ConfigCacheService.php | 12 +
app/Services/LandingService.php | 173 ++++----
app/Util/Site/Config.php | 41 +-
.../views/site/help/sharing-media.blade.php | 2 +-
8 files changed, 329 insertions(+), 338 deletions(-)
diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php
index 5ab69dfc9..0463e681e 100644
--- a/app/Http/Controllers/Api/ApiV1Controller.php
+++ b/app/Http/Controllers/Api/ApiV1Controller.php
@@ -1664,7 +1664,7 @@ class ApiV1Controller extends Controller
],
'statuses' => [
'characters_reserved_per_url' => 23,
- 'max_characters' => (int) config('pixelfed.max_caption_length'),
+ 'max_characters' => (int) config_cache('pixelfed.max_caption_length'),
'max_media_attachments' => (int) config('pixelfed.max_album_length'),
],
],
@@ -3308,7 +3308,7 @@ class ApiV1Controller extends Controller
abort_unless($request->user()->tokenCan('write'), 403);
$this->validate($request, [
- 'status' => 'nullable|string',
+ 'status' => 'nullable|string|max:' . config_cache('pixelfed.max_caption_length'),
'in_reply_to_id' => 'nullable',
'media_ids' => 'sometimes|array|max:'.config_cache('pixelfed.max_album_length'),
'sensitive' => 'nullable',
diff --git a/app/Http/Controllers/Api/ApiV2Controller.php b/app/Http/Controllers/Api/ApiV2Controller.php
index c585b3b04..ebdc851b8 100644
--- a/app/Http/Controllers/Api/ApiV2Controller.php
+++ b/app/Http/Controllers/Api/ApiV2Controller.php
@@ -104,7 +104,7 @@ class ApiV2Controller extends Controller
'max_featured_tags' => 0,
],
'statuses' => [
- 'max_characters' => (int) config('pixelfed.max_caption_length'),
+ 'max_characters' => (int) config_cache('pixelfed.max_caption_length'),
'max_media_attachments' => (int) config_cache('pixelfed.max_album_length'),
'characters_reserved_per_url' => 23
],
diff --git a/app/Http/Controllers/CommentController.php b/app/Http/Controllers/CommentController.php
index 42a5490d0..1dd985723 100644
--- a/app/Http/Controllers/CommentController.php
+++ b/app/Http/Controllers/CommentController.php
@@ -2,23 +2,18 @@
namespace App\Http\Controllers;
-use Illuminate\Http\Request;
-use Auth;
-use DB;
-use Cache;
-
-use App\Comment;
use App\Jobs\CommentPipeline\CommentPipeline;
use App\Jobs\StatusPipeline\NewStatusPipeline;
-use App\Util\Lexer\Autolink;
-use App\Profile;
-use App\Status;
-use App\UserFilter;
-use League\Fractal;
-use App\Transformer\Api\StatusTransformer;
-use League\Fractal\Serializer\ArraySerializer;
-use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use App\Services\StatusService;
+use App\Status;
+use App\Transformer\Api\StatusTransformer;
+use App\UserFilter;
+use App\Util\Lexer\Autolink;
+use Auth;
+use DB;
+use Illuminate\Http\Request;
+use League\Fractal;
+use League\Fractal\Serializer\ArraySerializer;
class CommentController extends Controller
{
@@ -33,9 +28,9 @@ class CommentController extends Controller
abort(403);
}
$this->validate($request, [
- 'item' => 'required|integer|min:1',
- 'comment' => 'required|string|max:'.(int) config('pixelfed.max_caption_length'),
- 'sensitive' => 'nullable|boolean'
+ 'item' => 'required|integer|min:1',
+ 'comment' => 'required|string|max:'.config_cache('pixelfed.max_caption_length'),
+ 'sensitive' => 'nullable|boolean',
]);
$comment = $request->input('comment');
$statusId = $request->input('item');
@@ -45,7 +40,7 @@ class CommentController extends Controller
$profile = $user->profile;
$status = Status::findOrFail($statusId);
- if($status->comments_disabled == true) {
+ if ($status->comments_disabled == true) {
return;
}
@@ -55,11 +50,11 @@ class CommentController extends Controller
->whereFilterableId($profile->id)
->exists();
- if($filtered == true) {
+ if ($filtered == true) {
return;
}
- $reply = DB::transaction(function() use($comment, $status, $profile, $nsfw) {
+ $reply = DB::transaction(function () use ($comment, $status, $profile, $nsfw) {
$scope = $profile->is_private == true ? 'private' : 'public';
$autolink = Autolink::create()->autolink($comment);
$reply = new Status();
diff --git a/app/Http/Controllers/ComposeController.php b/app/Http/Controllers/ComposeController.php
index 36bd5a66c..341d56ea8 100644
--- a/app/Http/Controllers/ComposeController.php
+++ b/app/Http/Controllers/ComposeController.php
@@ -2,59 +2,38 @@
namespace App\Http\Controllers;
-use Illuminate\Http\Request;
-use Auth, Cache, DB, Storage, URL;
-use Carbon\Carbon;
-use App\{
- Avatar,
- Collection,
- CollectionItem,
- Hashtag,
- Like,
- Media,
- MediaTag,
- Notification,
- Profile,
- Place,
- Status,
- UserFilter,
- UserSetting
-};
-use App\Models\Poll;
-use App\Transformer\Api\{
- MediaTransformer,
- MediaDraftTransformer,
- StatusTransformer,
- StatusStatelessTransformer
-};
-use League\Fractal;
-use App\Util\Media\Filter;
-use League\Fractal\Serializer\ArraySerializer;
-use League\Fractal\Pagination\IlluminatePaginatorAdapter;
-use App\Jobs\AvatarPipeline\AvatarOptimize;
+use App\Collection;
+use App\CollectionItem;
+use App\Hashtag;
use App\Jobs\ImageOptimizePipeline\ImageOptimize;
-use App\Jobs\ImageOptimizePipeline\ImageThumbnail;
use App\Jobs\StatusPipeline\NewStatusPipeline;
-use App\Jobs\VideoPipeline\{
- VideoOptimize,
- VideoPostProcess,
- VideoThumbnail
-};
+use App\Jobs\VideoPipeline\VideoThumbnail;
+use App\Media;
+use App\MediaTag;
+use App\Models\Poll;
+use App\Notification;
+use App\Profile;
use App\Services\AccountService;
use App\Services\CollectionService;
-use App\Services\NotificationService;
-use App\Services\MediaPathService;
use App\Services\MediaBlocklistService;
+use App\Services\MediaPathService;
use App\Services\MediaStorageService;
use App\Services\MediaTagService;
-use App\Services\StatusService;
use App\Services\SnowflakeService;
-use Illuminate\Support\Str;
-use App\Util\Lexer\Autolink;
-use App\Util\Lexer\Extractor;
-use App\Util\Media\License;
-use Image;
use App\Services\UserRoleService;
+use App\Status;
+use App\Transformer\Api\MediaTransformer;
+use App\UserFilter;
+use App\Util\Lexer\Autolink;
+use App\Util\Media\Filter;
+use App\Util\Media\License;
+use Auth;
+use Cache;
+use DB;
+use Illuminate\Http\Request;
+use Illuminate\Support\Str;
+use League\Fractal;
+use League\Fractal\Serializer\ArraySerializer;
class ComposeController extends Controller
{
@@ -74,30 +53,30 @@ class ComposeController extends Controller
public function mediaUpload(Request $request)
{
- abort_if(!$request->user(), 403);
+ abort_if(! $request->user(), 403);
$this->validate($request, [
'file.*' => [
'required_without:file',
- 'mimetypes:' . config_cache('pixelfed.media_types'),
- 'max:' . config_cache('pixelfed.max_photo_size'),
+ 'mimetypes:'.config_cache('pixelfed.media_types'),
+ 'max:'.config_cache('pixelfed.max_photo_size'),
],
'file' => [
'required_without:file.*',
- 'mimetypes:' . config_cache('pixelfed.media_types'),
- 'max:' . config_cache('pixelfed.max_photo_size'),
+ 'mimetypes:'.config_cache('pixelfed.media_types'),
+ 'max:'.config_cache('pixelfed.max_photo_size'),
],
'filter_name' => 'nullable|string|max:24',
- 'filter_class' => 'nullable|alpha_dash|max:24'
+ 'filter_class' => 'nullable|alpha_dash|max:24',
]);
$user = Auth::user();
$profile = $user->profile;
- abort_if($user->has_roles && !UserRoleService::can('can-post', $user->id), 403, 'Invalid permissions for this action');
+ abort_if($user->has_roles && ! UserRoleService::can('can-post', $user->id), 403, 'Invalid permissions for this action');
- $limitKey = 'compose:rate-limit:media-upload:' . $user->id;
+ $limitKey = 'compose:rate-limit:media-upload:'.$user->id;
$limitTtl = now()->addMinutes(15);
- $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
+ $limitReached = Cache::remember($limitKey, $limitTtl, function () use ($user) {
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
return $dailyLimit >= 1250;
@@ -105,8 +84,8 @@ class ComposeController extends Controller
abort_if($limitReached == true, 429);
- if(config_cache('pixelfed.enforce_account_limit') == true) {
- $size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function() use($user) {
+ if (config_cache('pixelfed.enforce_account_limit') == true) {
+ $size = Cache::remember($user->storageUsedKey(), now()->addDays(3), function () use ($user) {
return Media::whereUserId($user->id)->sum('size') / 1000;
});
$limit = (int) config_cache('pixelfed.max_account_size');
@@ -144,24 +123,24 @@ class ComposeController extends Controller
$media->version = 3;
$media->save();
- $preview_url = $media->url() . '?v=' . time();
- $url = $media->url() . '?v=' . time();
+ $preview_url = $media->url().'?v='.time();
+ $url = $media->url().'?v='.time();
switch ($media->mime) {
case 'image/jpeg':
case 'image/png':
case 'image/webp':
- ImageOptimize::dispatch($media)->onQueue('mmo');
- break;
+ ImageOptimize::dispatch($media)->onQueue('mmo');
+ break;
case 'video/mp4':
- VideoThumbnail::dispatch($media)->onQueue('mmo');
- $preview_url = '/storage/no-preview.png';
- $url = '/storage/no-preview.png';
- break;
+ VideoThumbnail::dispatch($media)->onQueue('mmo');
+ $preview_url = '/storage/no-preview.png';
+ $url = '/storage/no-preview.png';
+ break;
default:
- break;
+ break;
}
Cache::forget($limitKey);
@@ -169,6 +148,7 @@ class ComposeController extends Controller
$res = $this->fractal->createData($resource)->toArray();
$res['preview_url'] = $preview_url;
$res['url'] = $url;
+
return response()->json($res);
}
@@ -176,21 +156,21 @@ class ComposeController extends Controller
{
$this->validate($request, [
'id' => 'required',
- 'file' => function() {
+ 'file' => function () {
return [
'required',
- 'mimetypes:' . config_cache('pixelfed.media_types'),
- 'max:' . config_cache('pixelfed.max_photo_size'),
+ 'mimetypes:'.config_cache('pixelfed.media_types'),
+ 'max:'.config_cache('pixelfed.max_photo_size'),
];
},
]);
$user = Auth::user();
- abort_if($user->has_roles && !UserRoleService::can('can-post', $user->id), 403, 'Invalid permissions for this action');
+ abort_if($user->has_roles && ! UserRoleService::can('can-post', $user->id), 403, 'Invalid permissions for this action');
- $limitKey = 'compose:rate-limit:media-updates:' . $user->id;
+ $limitKey = 'compose:rate-limit:media-updates:'.$user->id;
$limitTtl = now()->addMinutes(15);
- $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
+ $limitReached = Cache::remember($limitKey, $limitTtl, function () use ($user) {
$dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count();
return $dailyLimit >= 1500;
@@ -202,9 +182,9 @@ class ComposeController extends Controller
$id = $request->input('id');
$media = Media::whereUserId($user->id)
- ->whereProfileId($user->profile_id)
- ->whereNull('status_id')
- ->findOrFail($id);
+ ->whereProfileId($user->profile_id)
+ ->whereNull('status_id')
+ ->findOrFail($id);
$media->save();
@@ -214,47 +194,48 @@ class ComposeController extends Controller
$dir = implode('/', $fragments);
$path = $photo->storePubliclyAs($dir, $name);
$res = [
- 'url' => $media->url() . '?v=' . time()
+ 'url' => $media->url().'?v='.time(),
];
ImageOptimize::dispatch($media)->onQueue('mmo');
Cache::forget($limitKey);
+
return $res;
}
public function mediaDelete(Request $request)
{
- abort_if(!$request->user(), 403);
+ abort_if(! $request->user(), 403);
$this->validate($request, [
- 'id' => 'required|integer|min:1|exists:media,id'
+ 'id' => 'required|integer|min:1|exists:media,id',
]);
- abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
+ abort_if($request->user()->has_roles && ! UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
$media = Media::whereNull('status_id')
- ->whereUserId(Auth::id())
- ->findOrFail($request->input('id'));
+ ->whereUserId(Auth::id())
+ ->findOrFail($request->input('id'));
MediaStorageService::delete($media, true);
return response()->json([
'msg' => 'Successfully deleted',
- 'code' => 200
+ 'code' => 200,
]);
}
public function searchTag(Request $request)
{
- abort_if(!$request->user(), 403);
+ abort_if(! $request->user(), 403);
$this->validate($request, [
- 'q' => 'required|string|min:1|max:50'
+ 'q' => 'required|string|min:1|max:50',
]);
$q = $request->input('q');
- if(Str::of($q)->startsWith('@')) {
- if(strlen($q) < 3) {
+ if (Str::of($q)->startsWith('@')) {
+ if (strlen($q) < 3) {
return [];
}
$q = mb_substr($q, 1);
@@ -262,7 +243,7 @@ class ComposeController extends Controller
$user = $request->user();
- abort_if($user->has_roles && !UserRoleService::can('can-post', $user->id), 403, 'Invalid permissions for this action');
+ abort_if($user->has_roles && ! UserRoleService::can('can-post', $user->id), 403, 'Invalid permissions for this action');
$blocked = UserFilter::whereFilterableType('App\Profile')
->whereFilterType('block')
@@ -271,34 +252,34 @@ class ComposeController extends Controller
$blocked->push($request->user()->profile_id);
- $results = Profile::select('id','domain','username')
+ $results = Profile::select('id', 'domain', 'username')
->whereNotIn('id', $blocked)
->whereNull('domain')
- ->where('username','like','%'.$q.'%')
+ ->where('username', 'like', '%'.$q.'%')
->limit(15)
->get()
- ->map(function($r) {
+ ->map(function ($r) {
return [
'id' => (string) $r->id,
'name' => $r->username,
'privacy' => true,
- 'avatar' => $r->avatarUrl()
+ 'avatar' => $r->avatarUrl(),
];
- });
+ });
return $results;
}
public function searchUntag(Request $request)
{
- abort_if(!$request->user(), 403);
+ abort_if(! $request->user(), 403);
$this->validate($request, [
'status_id' => 'required',
- 'profile_id' => 'required'
+ 'profile_id' => 'required',
]);
- abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
+ abort_if($request->user()->has_roles && ! UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
$user = $request->user();
$status_id = $request->input('status_id');
@@ -310,7 +291,7 @@ class ComposeController extends Controller
->whereProfileId($profile_id)
->first();
- if(!$tag) {
+ if (! $tag) {
return [];
}
Notification::whereItemType('App\MediaTag')
@@ -326,37 +307,38 @@ class ComposeController extends Controller
public function searchLocation(Request $request)
{
- abort_if(!$request->user(), 403);
+ abort_if(! $request->user(), 403);
$this->validate($request, [
- 'q' => 'required|string|max:100'
+ 'q' => 'required|string|max:100',
]);
- abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
+ abort_if($request->user()->has_roles && ! UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
$pid = $request->user()->profile_id;
- abort_if(!$pid, 400);
+ abort_if(! $pid, 400);
$q = e($request->input('q'));
- $popular = Cache::remember('pf:search:location:v1:popular', 1209600, function() {
+ $popular = Cache::remember('pf:search:location:v1:popular', 1209600, function () {
$minId = SnowflakeService::byDate(now()->subDays(290));
- if(config('database.default') == 'pgsql') {
+ if (config('database.default') == 'pgsql') {
return Status::selectRaw('id, place_id, count(place_id) as pc')
- ->whereNotNull('place_id')
- ->where('id', '>', $minId)
- ->orderByDesc('pc')
- ->groupBy(['place_id', 'id'])
- ->limit(400)
- ->get()
- ->filter(function($post) {
- return $post;
- })
- ->map(function($place) {
- return [
- 'id' => $place->place_id,
- 'count' => $place->pc
- ];
- })
- ->unique('id')
- ->values();
+ ->whereNotNull('place_id')
+ ->where('id', '>', $minId)
+ ->orderByDesc('pc')
+ ->groupBy(['place_id', 'id'])
+ ->limit(400)
+ ->get()
+ ->filter(function ($post) {
+ return $post;
+ })
+ ->map(function ($place) {
+ return [
+ 'id' => $place->place_id,
+ 'count' => $place->pc,
+ ];
+ })
+ ->unique('id')
+ ->values();
}
+
return Status::selectRaw('id, place_id, count(place_id) as pc')
->whereNotNull('place_id')
->where('id', '>', $minId)
@@ -364,57 +346,58 @@ class ComposeController extends Controller
->orderByDesc('pc')
->limit(400)
->get()
- ->filter(function($post) {
+ ->filter(function ($post) {
return $post;
})
- ->map(function($place) {
+ ->map(function ($place) {
return [
'id' => $place->place_id,
- 'count' => $place->pc
+ 'count' => $place->pc,
];
});
});
- $q = '%' . $q . '%';
+ $q = '%'.$q.'%';
$wildcard = config('database.default') === 'pgsql' ? 'ilike' : 'like';
$places = DB::table('places')
- ->where('name', $wildcard, $q)
- ->limit((strlen($q) > 5 ? 360 : 30))
- ->get()
- ->sortByDesc(function($place, $key) use($popular) {
- return $popular->filter(function($p) use($place) {
- return $p['id'] == $place->id;
- })->map(function($p) use($place) {
- return in_array($place->country, ['Canada', 'USA', 'France', 'Germany', 'United Kingdom']) ? $p['count'] : 1;
- })->values();
- })
- ->map(function($r) {
- return [
- 'id' => $r->id,
- 'name' => $r->name,
- 'country' => $r->country,
- 'url' => url('/discover/places/' . $r->id . '/' . $r->slug)
- ];
- })
- ->values()
- ->all();
+ ->where('name', $wildcard, $q)
+ ->limit((strlen($q) > 5 ? 360 : 30))
+ ->get()
+ ->sortByDesc(function ($place, $key) use ($popular) {
+ return $popular->filter(function ($p) use ($place) {
+ return $p['id'] == $place->id;
+ })->map(function ($p) use ($place) {
+ return in_array($place->country, ['Canada', 'USA', 'France', 'Germany', 'United Kingdom']) ? $p['count'] : 1;
+ })->values();
+ })
+ ->map(function ($r) {
+ return [
+ 'id' => $r->id,
+ 'name' => $r->name,
+ 'country' => $r->country,
+ 'url' => url('/discover/places/'.$r->id.'/'.$r->slug),
+ ];
+ })
+ ->values()
+ ->all();
+
return $places;
}
public function searchMentionAutocomplete(Request $request)
{
- abort_if(!$request->user(), 403);
+ abort_if(! $request->user(), 403);
$this->validate($request, [
- 'q' => 'required|string|min:2|max:50'
+ 'q' => 'required|string|min:2|max:50',
]);
- abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
+ abort_if($request->user()->has_roles && ! UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
$q = $request->input('q');
- if(Str::of($q)->startsWith('@')) {
- if(strlen($q) < 3) {
+ if (Str::of($q)->startsWith('@')) {
+ if (strlen($q) < 3) {
return [];
}
}
@@ -426,32 +409,33 @@ class ComposeController extends Controller
$blocked->push($request->user()->profile_id);
- $results = Profile::select('id','domain','username')
+ $results = Profile::select('id', 'domain', 'username')
->whereNotIn('id', $blocked)
- ->where('username','like','%'.$q.'%')
+ ->where('username', 'like', '%'.$q.'%')
->groupBy('id', 'domain')
->limit(15)
->get()
- ->map(function($profile) {
+ ->map(function ($profile) {
$username = $profile->domain ? substr($profile->username, 1) : $profile->username;
+
return [
- 'key' => '@' . str_limit($username, 30),
+ 'key' => '@'.str_limit($username, 30),
'value' => $username,
];
- });
+ });
return $results;
}
public function searchHashtagAutocomplete(Request $request)
{
- abort_if(!$request->user(), 403);
+ abort_if(! $request->user(), 403);
$this->validate($request, [
- 'q' => 'required|string|min:2|max:50'
+ 'q' => 'required|string|min:2|max:50',
]);
- abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
+ abort_if($request->user()->has_roles && ! UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
$q = $request->input('q');
@@ -461,12 +445,12 @@ class ComposeController extends Controller
->whereIsBanned(false)
->limit(5)
->get()
- ->map(function($tag) {
+ ->map(function ($tag) {
return [
- 'key' => '#' . $tag->slug,
- 'value' => $tag->slug
+ 'key' => '#'.$tag->slug,
+ 'value' => $tag->slug,
];
- });
+ });
return $results;
}
@@ -474,8 +458,8 @@ class ComposeController extends Controller
public function store(Request $request)
{
$this->validate($request, [
- 'caption' => 'nullable|string|max:'.config('pixelfed.max_caption_length', 500),
- 'media.*' => 'required',
+ 'caption' => 'nullable|string|max:'.config_cache('pixelfed.max_caption_length', 500),
+ 'media.*' => 'required',
'media.*.id' => 'required|integer|min:1',
'media.*.filter_class' => 'nullable|alpha_dash|max:30',
'media.*.license' => 'nullable|string|max:140',
@@ -491,14 +475,14 @@ class ComposeController extends Controller
// 'optimize_media' => 'nullable'
]);
- abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
+ abort_if($request->user()->has_roles && ! UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
- if(config('costar.enabled') == true) {
+ if (config('costar.enabled') == true) {
$blockedKeywords = config('costar.keyword.block');
- if($blockedKeywords !== null && $request->caption) {
+ if ($blockedKeywords !== null && $request->caption) {
$keywords = config('costar.keyword.block');
- foreach($keywords as $kw) {
- if(Str::contains($request->caption, $kw) == true) {
+ foreach ($keywords as $kw) {
+ if (Str::contains($request->caption, $kw) == true) {
abort(400, 'Invalid object');
}
}
@@ -508,9 +492,9 @@ class ComposeController extends Controller
$user = $request->user();
$profile = $user->profile;
- $limitKey = 'compose:rate-limit:store:' . $user->id;
+ $limitKey = 'compose:rate-limit:store:'.$user->id;
$limitTtl = now()->addMinutes(15);
- $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) {
+ $limitReached = Cache::remember($limitKey, $limitTtl, function () use ($user) {
$dailyLimit = Status::whereProfileId($user->profile_id)
->whereNull('in_reply_to_id')
->whereNull('reblog_of_id')
@@ -534,12 +518,12 @@ class ComposeController extends Controller
$tagged = $request->input('tagged');
$optimize_media = (bool) $request->input('optimize_media');
- foreach($medias as $k => $media) {
- if($k + 1 > config_cache('pixelfed.max_album_length')) {
+ foreach ($medias as $k => $media) {
+ if ($k + 1 > config_cache('pixelfed.max_album_length')) {
continue;
}
$m = Media::findOrFail($media['id']);
- if($m->profile_id !== $profile->id || $m->status_id) {
+ if ($m->profile_id !== $profile->id || $m->status_id) {
abort(403, 'Invalid media id');
}
$m->filter_class = in_array($media['filter_class'], Filter::classes()) ? $media['filter_class'] : null;
@@ -547,7 +531,7 @@ class ComposeController extends Controller
$m->caption = isset($media['alt']) ? strip_tags($media['alt']) : null;
$m->order = isset($media['cursor']) && is_int($media['cursor']) ? (int) $media['cursor'] : $k;
- if($cw == true || $profile->cw == true) {
+ if ($cw == true || $profile->cw == true) {
$m->is_nsfw = $cw;
$status->is_nsfw = $cw;
}
@@ -560,19 +544,19 @@ class ComposeController extends Controller
$mediaType = StatusController::mimeTypeCheck($mimes);
- if(in_array($mediaType, ['photo', 'video', 'photo:album']) == false) {
+ if (in_array($mediaType, ['photo', 'video', 'photo:album']) == false) {
abort(400, __('exception.compose.invalid.album'));
}
- if($place && is_array($place)) {
+ if ($place && is_array($place)) {
$status->place_id = $place['id'];
}
- if($request->filled('comments_disabled')) {
+ if ($request->filled('comments_disabled')) {
$status->comments_disabled = (bool) $request->input('comments_disabled');
}
- if($request->filled('spoiler_text') && $cw) {
+ if ($request->filled('spoiler_text') && $cw) {
$status->cw_summary = $request->input('spoiler_text');
}
@@ -583,7 +567,7 @@ class ComposeController extends Controller
$status->profile_id = $profile->id;
$status->save();
- foreach($attachments as $media) {
+ foreach ($attachments as $media) {
$media->status_id = $status->id;
$media->save();
}
@@ -597,7 +581,7 @@ class ComposeController extends Controller
$status->type = $mediaType;
$status->save();
- foreach($tagged as $tg) {
+ foreach ($tagged as $tg) {
$mt = new MediaTag;
$mt->status_id = $status->id;
$mt->media_id = $status->media->first()->id;
@@ -612,17 +596,17 @@ class ComposeController extends Controller
MediaTagService::sendNotification($mt);
}
- if($request->filled('collections')) {
+ if ($request->filled('collections')) {
$collections = Collection::whereProfileId($profile->id)
->find($request->input('collections'))
- ->each(function($collection) use($status) {
+ ->each(function ($collection) use ($status) {
$count = $collection->items()->count();
CollectionItem::firstOrCreate([
'collection_id' => $collection->id,
'object_type' => 'App\Status',
- 'object_id' => $status->id
+ 'object_id' => $status->id,
], [
- 'order' => $count
+ 'order' => $count,
]);
CollectionService::addItem(
@@ -643,7 +627,7 @@ class ComposeController extends Controller
Cache::forget('profile:status_count:'.$profile->id);
Cache::forget('status:transformer:media:attachments:'.$status->id);
Cache::forget($user->storageUsedKey());
- Cache::forget('profile:embed:' . $status->profile_id);
+ Cache::forget('profile:embed:'.$status->profile_id);
Cache::forget($limitKey);
return $status->url();
@@ -653,7 +637,7 @@ class ComposeController extends Controller
{
abort_unless(config('exp.top'), 404);
$this->validate($request, [
- 'caption' => 'nullable|string|max:'.config('pixelfed.max_caption_length', 500),
+ 'caption' => 'nullable|string|max:'.config_cache('pixelfed.max_caption_length', 500),
'cw' => 'nullable|boolean',
'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10',
'place' => 'nullable',
@@ -661,14 +645,14 @@ class ComposeController extends Controller
'tagged' => 'nullable',
]);
- abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
+ abort_if($request->user()->has_roles && ! UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
- if(config('costar.enabled') == true) {
+ if (config('costar.enabled') == true) {
$blockedKeywords = config('costar.keyword.block');
- if($blockedKeywords !== null && $request->caption) {
+ if ($blockedKeywords !== null && $request->caption) {
$keywords = config('costar.keyword.block');
- foreach($keywords as $kw) {
- if(Str::contains($request->caption, $kw) == true) {
+ foreach ($keywords as $kw) {
+ if (Str::contains($request->caption, $kw) == true) {
abort(400, 'Invalid object');
}
}
@@ -683,11 +667,11 @@ class ComposeController extends Controller
$cw = $request->input('cw');
$tagged = $request->input('tagged');
- if($place && is_array($place)) {
+ if ($place && is_array($place)) {
$status->place_id = $place['id'];
}
- if($request->filled('comments_disabled')) {
+ if ($request->filled('comments_disabled')) {
$status->comments_disabled = (bool) $request->input('comments_disabled');
}
@@ -707,11 +691,11 @@ class ComposeController extends Controller
'bg_id' => 1,
'font_size' => strlen($status->caption) <= 140 ? 'h1' : 'h3',
'length' => strlen($status->caption),
- ]
+ ],
], $entities), JSON_UNESCAPED_SLASHES);
$status->save();
- foreach($tagged as $tg) {
+ foreach ($tagged as $tg) {
$mt = new MediaTag;
$mt->status_id = $status->id;
$mt->media_id = $status->media->first()->id;
@@ -726,7 +710,6 @@ class ComposeController extends Controller
MediaTagService::sendNotification($mt);
}
-
Cache::forget('user:account:id:'.$profile->user_id);
Cache::forget('_api:statuses:recent_9:'.$profile->id);
Cache::forget('profile:status_count:'.$profile->id);
@@ -737,18 +720,18 @@ class ComposeController extends Controller
public function mediaProcessingCheck(Request $request)
{
$this->validate($request, [
- 'id' => 'required|integer|min:1'
+ 'id' => 'required|integer|min:1',
]);
- abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
+ abort_if($request->user()->has_roles && ! UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
$media = Media::whereUserId($request->user()->id)
->whereNull('status_id')
->findOrFail($request->input('id'));
- if(config('pixelfed.media_fast_process')) {
+ if (config('pixelfed.media_fast_process')) {
return [
- 'finished' => true
+ 'finished' => true,
];
}
@@ -762,27 +745,27 @@ class ComposeController extends Controller
break;
default:
- # code...
+ // code...
break;
}
return [
- 'finished' => $finished
+ 'finished' => $finished,
];
}
public function composeSettings(Request $request)
{
$uid = $request->user()->id;
- abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
+ abort_if($request->user()->has_roles && ! UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
$default = [
'default_license' => 1,
'media_descriptions' => false,
- 'max_altext_length' => config_cache('pixelfed.max_altext_length')
+ 'max_altext_length' => config_cache('pixelfed.max_altext_length'),
];
$settings = AccountService::settings($uid);
- if(isset($settings['other']) && isset($settings['other']['scope'])) {
+ if (isset($settings['other']) && isset($settings['other']['scope'])) {
$s = $settings['compose_settings'];
$s['default_scope'] = $settings['other']['scope'];
$settings['compose_settings'] = $s;
@@ -794,23 +777,22 @@ class ComposeController extends Controller
public function createPoll(Request $request)
{
$this->validate($request, [
- 'caption' => 'nullable|string|max:'.config('pixelfed.max_caption_length', 500),
+ 'caption' => 'nullable|string|max:'.config_cache('pixelfed.max_caption_length', 500),
'cw' => 'nullable|boolean',
'visibility' => 'required|string|in:public,private',
'comments_disabled' => 'nullable',
'expiry' => 'required|in:60,360,1440,10080',
- 'pollOptions' => 'required|array|min:1|max:4'
+ 'pollOptions' => 'required|array|min:1|max:4',
]);
abort(404);
abort_if(config('instance.polls.enabled') == false, 404, 'Polls not enabled');
- abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
+ abort_if($request->user()->has_roles && ! UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action');
abort_if(Status::whereType('poll')
->whereProfileId($request->user()->profile_id)
->whereCaption($request->input('caption'))
->where('created_at', '>', now()->subDays(2))
- ->exists()
- , 422, 'Duplicate detected.');
+ ->exists(), 422, 'Duplicate detected.');
$status = new Status;
$status->profile_id = $request->user()->profile_id;
@@ -827,7 +809,7 @@ class ComposeController extends Controller
$poll->profile_id = $status->profile_id;
$poll->poll_options = $request->input('pollOptions');
$poll->expires_at = now()->addMinutes($request->input('expiry'));
- $poll->cached_tallies = collect($poll->poll_options)->map(function($o) {
+ $poll->cached_tallies = collect($poll->poll_options)->map(function ($o) {
return 0;
})->toArray();
$poll->save();
diff --git a/app/Services/ConfigCacheService.php b/app/Services/ConfigCacheService.php
index bf34f5dcc..af2d1ef6a 100644
--- a/app/Services/ConfigCacheService.php
+++ b/app/Services/ConfigCacheService.php
@@ -75,6 +75,18 @@ class ConfigCacheService
'instance.curated_registration.enabled',
'federation.migration',
+
+ 'pixelfed.max_caption_length',
+ 'pixelfed.max_bio_length',
+ 'pixelfed.max_name_length',
+ 'pixelfed.min_password_length',
+ 'pixelfed.max_avatar_size',
+ 'pixelfed.max_altext_length',
+ 'pixelfed.allow_app_registration',
+ 'pixelfed.app_registration_rate_limit_attempts',
+ 'pixelfed.app_registration_rate_limit_decay',
+ 'pixelfed.app_registration_confirm_rate_limit_attempts',
+ 'pixelfed.app_registration_confirm_rate_limit_decay',
// 'system.user_mode'
];
diff --git a/app/Services/LandingService.php b/app/Services/LandingService.php
index 199768b86..20759ecf4 100644
--- a/app/Services/LandingService.php
+++ b/app/Services/LandingService.php
@@ -2,105 +2,104 @@
namespace App\Services;
-use App\Util\ActivityPub\Helpers;
-use Illuminate\Support\Str;
-use Illuminate\Support\Facades\Cache;
-use Illuminate\Support\Facades\Redis;
use App\Status;
use App\User;
-use App\Services\AccountService;
use App\Util\Site\Nodeinfo;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Str;
class LandingService
{
- public static function get($json = true)
- {
- $activeMonth = Nodeinfo::activeUsersMonthly();
+ public static function get($json = true)
+ {
+ $activeMonth = Nodeinfo::activeUsersMonthly();
- $totalUsers = Cache::remember('api:nodeinfo:users', 43200, function() {
- return User::count();
- });
+ $totalUsers = Cache::remember('api:nodeinfo:users', 43200, function () {
+ return User::count();
+ });
- $postCount = Cache::remember('api:nodeinfo:statuses', 21600, function() {
- return Status::whereLocal(true)->count();
- });
+ $postCount = Cache::remember('api:nodeinfo:statuses', 21600, function () {
+ return Status::whereLocal(true)->count();
+ });
- $contactAccount = Cache::remember('api:v1:instance-data:contact', 604800, function () {
- if(config_cache('instance.admin.pid')) {
- return AccountService::getMastodon(config_cache('instance.admin.pid'), true);
- }
- $admin = User::whereIsAdmin(true)->first();
- return $admin && isset($admin->profile_id) ?
- AccountService::getMastodon($admin->profile_id, true) :
- null;
- });
+ $contactAccount = Cache::remember('api:v1:instance-data:contact', 604800, function () {
+ if (config_cache('instance.admin.pid')) {
+ return AccountService::getMastodon(config_cache('instance.admin.pid'), true);
+ }
+ $admin = User::whereIsAdmin(true)->first();
- $rules = Cache::remember('api:v1:instance-data:rules', 604800, function () {
- return config_cache('app.rules') ?
- collect(json_decode(config_cache('app.rules'), true))
- ->map(function($rule, $key) {
- $id = $key + 1;
- return [
- 'id' => "{$id}",
- 'text' => $rule
- ];
- })
- ->toArray() : [];
- });
+ return $admin && isset($admin->profile_id) ?
+ AccountService::getMastodon($admin->profile_id, true) :
+ null;
+ });
- $openReg = (bool) config_cache('pixelfed.open_registration');
+ $rules = Cache::remember('api:v1:instance-data:rules', 604800, function () {
+ return config_cache('app.rules') ?
+ collect(json_decode(config_cache('app.rules'), true))
+ ->map(function ($rule, $key) {
+ $id = $key + 1;
- $res = [
- 'name' => config_cache('app.name'),
- 'url' => config_cache('app.url'),
- 'domain' => config('pixelfed.domain.app'),
- 'show_directory' => config_cache('instance.landing.show_directory'),
- 'show_explore_feed' => config_cache('instance.landing.show_explore'),
- 'open_registration' => (bool) $openReg,
- 'curated_onboarding' => (bool) config_cache('instance.curated_registration.enabled'),
- 'version' => config('pixelfed.version'),
- 'about' => [
- 'banner_image' => config_cache('app.banner_image') ?? url('/storage/headers/default.jpg'),
- 'short_description' => config_cache('app.short_description'),
- 'description' => config_cache('app.description'),
- ],
- 'stats' => [
- 'active_users' => (int) $activeMonth,
- 'posts_count' => (int) $postCount,
- 'total_users' => (int) $totalUsers
- ],
- 'contact' => [
- 'account' => $contactAccount,
- 'email' => config('instance.email')
- ],
- 'rules' => $rules,
- 'uploader' => [
- 'max_photo_size' => (int) (config('pixelfed.max_photo_size') * 1024),
- 'max_caption_length' => (int) config('pixelfed.max_caption_length'),
- 'max_altext_length' => (int) config('pixelfed.max_altext_length', 150),
- 'album_limit' => (int) config_cache('pixelfed.max_album_length'),
- 'image_quality' => (int) config_cache('pixelfed.image_quality'),
- 'max_collection_length' => (int) config('pixelfed.max_collection_length', 18),
- 'optimize_image' => (bool) config('pixelfed.optimize_image'),
- 'optimize_video' => (bool) config('pixelfed.optimize_video'),
- 'media_types' => config_cache('pixelfed.media_types'),
- ],
- 'features' => [
- 'federation' => config_cache('federation.activitypub.enabled'),
- 'timelines' => [
- 'local' => true,
- 'network' => (bool) config('federation.network_timeline'),
- ],
- 'mobile_apis' => (bool) config_cache('pixelfed.oauth_enabled'),
- 'stories' => (bool) config_cache('instance.stories.enabled'),
- 'video' => Str::contains(config_cache('pixelfed.media_types'), 'video/mp4'),
- ]
- ];
+ return [
+ 'id' => "{$id}",
+ 'text' => $rule,
+ ];
+ })
+ ->toArray() : [];
+ });
- if($json) {
- return json_encode($res);
- }
+ $openReg = (bool) config_cache('pixelfed.open_registration');
- return $res;
- }
+ $res = [
+ 'name' => config_cache('app.name'),
+ 'url' => config_cache('app.url'),
+ 'domain' => config('pixelfed.domain.app'),
+ 'show_directory' => config_cache('instance.landing.show_directory'),
+ 'show_explore_feed' => config_cache('instance.landing.show_explore'),
+ 'open_registration' => (bool) $openReg,
+ 'curated_onboarding' => (bool) config_cache('instance.curated_registration.enabled'),
+ 'version' => config('pixelfed.version'),
+ 'about' => [
+ 'banner_image' => config_cache('app.banner_image') ?? url('/storage/headers/default.jpg'),
+ 'short_description' => config_cache('app.short_description'),
+ 'description' => config_cache('app.description'),
+ ],
+ 'stats' => [
+ 'active_users' => (int) $activeMonth,
+ 'posts_count' => (int) $postCount,
+ 'total_users' => (int) $totalUsers,
+ ],
+ 'contact' => [
+ 'account' => $contactAccount,
+ 'email' => config('instance.email'),
+ ],
+ 'rules' => $rules,
+ 'uploader' => [
+ 'max_photo_size' => (int) (config_cache('pixelfed.max_photo_size') * 1024),
+ 'max_caption_length' => (int) config_cache('pixelfed.max_caption_length'),
+ 'max_altext_length' => (int) config_cache('pixelfed.max_altext_length', 150),
+ 'album_limit' => (int) config_cache('pixelfed.max_album_length'),
+ 'image_quality' => (int) config_cache('pixelfed.image_quality'),
+ 'max_collection_length' => (int) config('pixelfed.max_collection_length', 18),
+ 'optimize_image' => (bool) config_cache('pixelfed.optimize_image'),
+ 'optimize_video' => (bool) config_cache('pixelfed.optimize_video'),
+ 'media_types' => config_cache('pixelfed.media_types'),
+ ],
+ 'features' => [
+ 'federation' => config_cache('federation.activitypub.enabled'),
+ 'timelines' => [
+ 'local' => true,
+ 'network' => (bool) config_cache('federation.network_timeline'),
+ ],
+ 'mobile_apis' => (bool) config_cache('pixelfed.oauth_enabled'),
+ 'stories' => (bool) config_cache('instance.stories.enabled'),
+ 'video' => Str::contains(config_cache('pixelfed.media_types'), 'video/mp4'),
+ ],
+ ];
+
+ if ($json) {
+ return json_encode($res);
+ }
+
+ return $res;
+ }
}
diff --git a/app/Util/Site/Config.php b/app/Util/Site/Config.php
index e0916591d..46af16a42 100644
--- a/app/Util/Site/Config.php
+++ b/app/Util/Site/Config.php
@@ -5,31 +5,33 @@ namespace App\Util\Site;
use Cache;
use Illuminate\Support\Str;
-class Config {
-
+class Config
+{
const CACHE_KEY = 'api:site:configuration:_v0.8';
- public static function get() {
- return Cache::remember(self::CACHE_KEY, 900, function() {
+ public static function get()
+ {
+ return Cache::remember(self::CACHE_KEY, 900, function () {
$hls = [
'enabled' => config('media.hls.enabled'),
];
- if(config('media.hls.enabled')) {
+ if (config('media.hls.enabled')) {
$hls = [
'enabled' => true,
'debug' => (bool) config('media.hls.debug'),
'p2p' => (bool) config('media.hls.p2p'),
'p2p_debug' => (bool) config('media.hls.p2p_debug'),
'tracker' => config('media.hls.tracker'),
- 'ice' => config('media.hls.ice')
+ 'ice' => config('media.hls.ice'),
];
}
+
return [
'version' => config('pixelfed.version'),
'open_registration' => (bool) config_cache('pixelfed.open_registration'),
'uploader' => [
'max_photo_size' => (int) config('pixelfed.max_photo_size'),
- 'max_caption_length' => (int) config('pixelfed.max_caption_length'),
+ 'max_caption_length' => (int) config_cache('pixelfed.max_caption_length'),
'max_altext_length' => (int) config('pixelfed.max_altext_length', 150),
'album_limit' => (int) config_cache('pixelfed.max_album_length'),
'image_quality' => (int) config_cache('pixelfed.image_quality'),
@@ -41,12 +43,12 @@ class Config {
'media_types' => config_cache('pixelfed.media_types'),
'mime_types' => config_cache('pixelfed.media_types') ? explode(',', config_cache('pixelfed.media_types')) : [],
- 'enforce_account_limit' => (bool) config_cache('pixelfed.enforce_account_limit')
+ 'enforce_account_limit' => (bool) config_cache('pixelfed.enforce_account_limit'),
],
'activitypub' => [
'enabled' => (bool) config_cache('federation.activitypub.enabled'),
- 'remote_follow' => config('federation.activitypub.remoteFollow')
+ 'remote_follow' => config('federation.activitypub.remoteFollow'),
],
'ab' => config('exp'),
@@ -54,8 +56,8 @@ class Config {
'site' => [
'name' => config_cache('app.name'),
'domain' => config('pixelfed.domain.app'),
- 'url' => config('app.url'),
- 'description' => config_cache('app.short_description')
+ 'url' => config('app.url'),
+ 'description' => config_cache('app.short_description'),
],
'account' => [
@@ -63,15 +65,15 @@ class Config {
'max_bio_length' => config('pixelfed.max_bio_length'),
'max_name_length' => config('pixelfed.max_name_length'),
'min_password_length' => config('pixelfed.min_password_length'),
- 'max_account_size' => config('pixelfed.max_account_size')
+ 'max_account_size' => config('pixelfed.max_account_size'),
],
'username' => [
'remote' => [
'formats' => config('instance.username.remote.formats'),
'format' => config('instance.username.remote.format'),
- 'custom' => config('instance.username.remote.custom')
- ]
+ 'custom' => config('instance.username.remote.custom'),
+ ],
],
'features' => [
@@ -85,22 +87,23 @@ class Config {
'import' => [
'instagram' => (bool) config_cache('pixelfed.import.instagram.enabled'),
'mastodon' => false,
- 'pixelfed' => false
+ 'pixelfed' => false,
],
'label' => [
'covid' => [
'enabled' => (bool) config('instance.label.covid.enabled'),
'org' => config('instance.label.covid.org'),
'url' => config('instance.label.covid.url'),
- ]
+ ],
],
- 'hls' => $hls
- ]
+ 'hls' => $hls,
+ ],
];
});
}
- public static function json() {
+ public static function json()
+ {
return json_encode(self::get(), JSON_FORCE_OBJECT);
}
}
diff --git a/resources/views/site/help/sharing-media.blade.php b/resources/views/site/help/sharing-media.blade.php
index d429f7406..3fe275d39 100644
--- a/resources/views/site/help/sharing-media.blade.php
+++ b/resources/views/site/help/sharing-media.blade.php
@@ -50,7 +50,7 @@
- During the compose process, you will see the Caption input. Captions are optional and limited to {{config('pixelfed.max_caption_length')}} characters.
+ During the compose process, you will see the Caption input. Captions are optional and limited to {{config_cache('pixelfed.max_caption_length')}} characters.
From 7785a2dae43b01e284deeafb036c7fca89164a86 Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Sun, 10 Mar 2024 04:37:22 -0600
Subject: [PATCH 15/17] Update Config, use config_cache
---
app/Util/Site/Config.php | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/app/Util/Site/Config.php b/app/Util/Site/Config.php
index 46af16a42..02944defe 100644
--- a/app/Util/Site/Config.php
+++ b/app/Util/Site/Config.php
@@ -32,7 +32,7 @@ class Config
'uploader' => [
'max_photo_size' => (int) config('pixelfed.max_photo_size'),
'max_caption_length' => (int) config_cache('pixelfed.max_caption_length'),
- 'max_altext_length' => (int) config('pixelfed.max_altext_length', 150),
+ 'max_altext_length' => (int) config_cache('pixelfed.max_altext_length', 150),
'album_limit' => (int) config_cache('pixelfed.max_album_length'),
'image_quality' => (int) config_cache('pixelfed.image_quality'),
@@ -102,6 +102,12 @@ class Config
});
}
+ public static function refresh()
+ {
+ Cache::forget(self::CACHE_KEY);
+ return self::get();
+ }
+
public static function json()
{
return json_encode(self::get(), JSON_FORCE_OBJECT);
From b0cb4456a95fa4be24c18184314935b01510b919 Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Sun, 10 Mar 2024 05:06:52 -0600
Subject: [PATCH 16/17] Update ApiV1Dot1Controller, use config_cache for in-app
registration
---
app/Http/Controllers/Api/ApiV1Dot1Controller.php | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/app/Http/Controllers/Api/ApiV1Dot1Controller.php b/app/Http/Controllers/Api/ApiV1Dot1Controller.php
index c34b9d968..6d051866b 100644
--- a/app/Http/Controllers/Api/ApiV1Dot1Controller.php
+++ b/app/Http/Controllers/Api/ApiV1Dot1Controller.php
@@ -473,15 +473,15 @@ class ApiV1Dot1Controller extends Controller
{
return [
'open' => (bool) config_cache('pixelfed.open_registration'),
- 'iara' => config('pixelfed.allow_app_registration')
+ 'iara' => (bool) config_cache('pixelfed.allow_app_registration'),
];
}
public function inAppRegistration(Request $request)
{
abort_if($request->user(), 404);
- abort_unless(config_cache('pixelfed.open_registration'), 404);
- abort_unless(config('pixelfed.allow_app_registration'), 404);
+ abort_unless((bool) config_cache('pixelfed.open_registration'), 404);
+ abort_unless((bool) config_cache('pixelfed.allow_app_registration'), 404);
abort_unless($request->hasHeader('X-PIXELFED-APP'), 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
abort_if(BouncerService::checkIp($request->ip()), 404);
@@ -609,8 +609,8 @@ class ApiV1Dot1Controller extends Controller
public function inAppRegistrationConfirm(Request $request)
{
abort_if($request->user(), 404);
- abort_unless(config_cache('pixelfed.open_registration'), 404);
- abort_unless(config('pixelfed.allow_app_registration'), 404);
+ abort_unless((bool) config_cache('pixelfed.open_registration'), 404);
+ abort_unless((bool) config_cache('pixelfed.allow_app_registration'), 404);
abort_unless($request->hasHeader('X-PIXELFED-APP'), 403);
if(config('pixelfed.bouncer.cloud_ips.ban_signups')) {
abort_if(BouncerService::checkIp($request->ip()), 404);
From bf46f6f5f4badaf856f36da4c3319f3ef82a1e39 Mon Sep 17 00:00:00 2001
From: Daniel Supernault
Date: Sun, 10 Mar 2024 05:42:25 -0600
Subject: [PATCH 17/17] Update config_cache
---
app/Http/Controllers/ProfileController.php | 2 +-
app/Http/Controllers/StatusController.php | 892 +++++++++++----------
app/Services/ConfigCacheService.php | 2 +
app/Services/HashidService.php | 74 +-
4 files changed, 482 insertions(+), 488 deletions(-)
diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php
index 5354475e3..6471ed760 100644
--- a/app/Http/Controllers/ProfileController.php
+++ b/app/Http/Controllers/ProfileController.php
@@ -332,7 +332,7 @@ class ProfileController extends Controller
{
$res = view('profile.embed-removed');
- if (! config('instance.embed.profile')) {
+ if (! (bool) config_cache('instance.embed.profile')) {
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
}
diff --git a/app/Http/Controllers/StatusController.php b/app/Http/Controllers/StatusController.php
index 873f5eace..4a3b3552d 100644
--- a/app/Http/Controllers/StatusController.php
+++ b/app/Http/Controllers/StatusController.php
@@ -2,458 +2,466 @@
namespace App\Http\Controllers;
-use App\Jobs\ImageOptimizePipeline\ImageOptimize;
-use App\Jobs\StatusPipeline\NewStatusPipeline;
-use App\Jobs\StatusPipeline\StatusDelete;
-use App\Jobs\StatusPipeline\RemoteStatusDelete;
+use App\AccountInterstitial;
use App\Jobs\SharePipeline\SharePipeline;
use App\Jobs\SharePipeline\UndoSharePipeline;
-use App\AccountInterstitial;
-use App\Media;
+use App\Jobs\StatusPipeline\RemoteStatusDelete;
+use App\Jobs\StatusPipeline\StatusDelete;
use App\Profile;
+use App\Services\HashidService;
+use App\Services\ReblogService;
+use App\Services\StatusService;
use App\Status;
-use App\StatusArchived;
use App\StatusView;
-use App\Transformer\ActivityPub\StatusTransformer;
use App\Transformer\ActivityPub\Verb\Note;
use App\Transformer\ActivityPub\Verb\Question;
-use App\User;
-use Auth, DB, Cache;
+use App\Util\Media\License;
+use Auth;
+use Cache;
+use DB;
use Illuminate\Http\Request;
use League\Fractal;
-use App\Util\Media\Filter;
-use Illuminate\Support\Str;
-use App\Services\HashidService;
-use App\Services\StatusService;
-use App\Util\Media\License;
-use App\Services\ReblogService;
class StatusController extends Controller
{
- public function show(Request $request, $username, $id)
- {
- // redirect authed users to Metro 2.0
- if($request->user()) {
- // unless they force static view
- if(!$request->has('fs') || $request->input('fs') != '1') {
- return redirect('/i/web/post/' . $id);
- }
- }
-
- $user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
-
- if($user->status != null) {
- return ProfileController::accountCheck($user);
- }
-
- $status = Status::whereProfileId($user->id)
- ->whereNull('reblog_of_id')
- ->whereIn('scope', ['public','unlisted', 'private'])
- ->findOrFail($id);
-
- if($status->uri || $status->url) {
- $url = $status->uri ?? $status->url;
- if(ends_with($url, '/activity')) {
- $url = str_replace('/activity', '', $url);
- }
- return redirect($url);
- }
-
- if($status->visibility == 'private' || $user->is_private) {
- if(!Auth::check()) {
- abort(404);
- }
- $pid = Auth::user()->profile;
- if($user->followedBy($pid) == false && $user->id !== $pid->id && Auth::user()->is_admin == false) {
- abort(404);
- }
- }
-
- if($status->type == 'archived') {
- if(Auth::user()->profile_id !== $status->profile_id) {
- abort(404);
- }
- }
-
- if($request->user() && $request->user()->profile_id != $status->profile_id) {
- StatusView::firstOrCreate([
- 'status_id' => $status->id,
- 'status_profile_id' => $status->profile_id,
- 'profile_id' => $request->user()->profile_id
- ]);
- }
-
- if ($request->wantsJson() && config_cache('federation.activitypub.enabled')) {
- return $this->showActivityPub($request, $status);
- }
-
- $template = $status->in_reply_to_id ? 'status.reply' : 'status.show';
- return view($template, compact('user', 'status'));
- }
-
- public function shortcodeRedirect(Request $request, $id)
- {
- abort(404);
- }
-
- public function showId(int $id)
- {
- abort(404);
- $status = Status::whereNull('reblog_of_id')
- ->whereIn('scope', ['public', 'unlisted'])
- ->findOrFail($id);
- return redirect($status->url());
- }
-
- public function showEmbed(Request $request, $username, int $id)
- {
- if(!config('instance.embed.post')) {
- $res = view('status.embed-removed');
- return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
- }
-
- $profile = Profile::whereNull(['domain','status'])
- ->whereIsPrivate(false)
- ->whereUsername($username)
- ->first();
-
- if(!$profile) {
- $content = view('status.embed-removed');
- return response($content)->header('X-Frame-Options', 'ALLOWALL');
- }
-
- $aiCheck = Cache::remember('profile:ai-check:spam-login:' . $profile->id, 86400, function() use($profile) {
- $exists = AccountInterstitial::whereUserId($profile->user_id)->where('is_spam', 1)->count();
- if($exists) {
- return true;
- }
-
- return false;
- });
-
- if($aiCheck) {
- $res = view('status.embed-removed');
- return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
- }
- $status = Status::whereProfileId($profile->id)
- ->whereNull('uri')
- ->whereScope('public')
- ->whereIsNsfw(false)
- ->whereIn('type', ['photo', 'video','photo:album'])
- ->find($id);
- if(!$status) {
- $content = view('status.embed-removed');
- return response($content)->header('X-Frame-Options', 'ALLOWALL');
- }
- $showLikes = $request->filled('likes') && $request->likes == true;
- $showCaption = $request->filled('caption') && $request->caption !== false;
- $layout = $request->filled('layout') && $request->layout == 'compact' ? 'compact' : 'full';
- $content = view('status.embed', compact('status', 'showLikes', 'showCaption', 'layout'));
- return response($content)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
- }
-
- public function showObject(Request $request, $username, int $id)
- {
- $user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
-
- if($user->status != null) {
- return ProfileController::accountCheck($user);
- }
-
- $status = Status::whereProfileId($user->id)
- ->whereNotIn('visibility',['draft','direct'])
- ->findOrFail($id);
-
- abort_if($status->uri, 404);
-
- if($status->visibility == 'private' || $user->is_private) {
- if(!Auth::check()) {
- abort(403);
- }
- $pid = Auth::user()->profile;
- if($user->followedBy($pid) == false && $user->id !== $pid->id) {
- abort(403);
- }
- }
-
- return $this->showActivityPub($request, $status);
- }
-
- public function compose()
- {
- $this->authCheck();
-
- return view('status.compose');
- }
-
- public function store(Request $request)
- {
- return;
- }
-
- public function delete(Request $request)
- {
- $this->authCheck();
-
- $this->validate($request, [
- 'item' => 'required|integer|min:1',
- ]);
-
- $status = Status::findOrFail($request->input('item'));
-
- $user = Auth::user();
-
- if($status->profile_id != $user->profile->id &&
- $user->is_admin == true &&
- $status->uri == null
- ) {
- $media = $status->media;
-
- $ai = new AccountInterstitial;
- $ai->user_id = $status->profile->user_id;
- $ai->type = 'post.removed';
- $ai->view = 'account.moderation.post.removed';
- $ai->item_type = 'App\Status';
- $ai->item_id = $status->id;
- $ai->has_media = (bool) $media->count();
- $ai->blurhash = $media->count() ? $media->first()->blurhash : null;
- $ai->meta = json_encode([
- 'caption' => $status->caption,
- 'created_at' => $status->created_at,
- 'type' => $status->type,
- 'url' => $status->url(),
- 'is_nsfw' => $status->is_nsfw,
- 'scope' => $status->scope,
- 'reblog' => $status->reblog_of_id,
- 'likes_count' => $status->likes_count,
- 'reblogs_count' => $status->reblogs_count,
- ]);
- $ai->save();
-
- $u = $status->profile->user;
- $u->has_interstitial = true;
- $u->save();
- }
-
- if($status->in_reply_to_id) {
- $parent = Status::find($status->in_reply_to_id);
- if($parent && ($parent->profile_id == $user->profile_id) || ($status->profile_id == $user->profile_id) || $user->is_admin) {
- Cache::forget('_api:statuses:recent_9:' . $status->profile_id);
- Cache::forget('profile:status_count:' . $status->profile_id);
- Cache::forget('profile:embed:' . $status->profile_id);
- StatusService::del($status->id, true);
- Cache::forget('profile:status_count:'.$status->profile_id);
- $status->uri ? RemoteStatusDelete::dispatch($status) : StatusDelete::dispatch($status);
- }
- } else if ($status->profile_id == $user->profile_id || $user->is_admin == true) {
- Cache::forget('_api:statuses:recent_9:' . $status->profile_id);
- Cache::forget('profile:status_count:' . $status->profile_id);
- Cache::forget('profile:embed:' . $status->profile_id);
- StatusService::del($status->id, true);
- Cache::forget('profile:status_count:'.$status->profile_id);
- $status->uri ? RemoteStatusDelete::dispatch($status) : StatusDelete::dispatch($status);
- }
-
- if($request->wantsJson()) {
- return response()->json(['Status successfully deleted.']);
- } else {
- return redirect($user->url());
- }
- }
-
- public function storeShare(Request $request)
- {
- $this->authCheck();
-
- $this->validate($request, [
- 'item' => 'required|integer|min:1',
- ]);
-
- $user = Auth::user();
- $profile = $user->profile;
- $status = Status::whereScope('public')
- ->findOrFail($request->input('item'));
-
- $count = $status->reblogs_count;
-
- $exists = Status::whereProfileId(Auth::user()->profile->id)
- ->whereReblogOfId($status->id)
- ->exists();
- if ($exists == true) {
- $shares = Status::whereProfileId(Auth::user()->profile->id)
- ->whereReblogOfId($status->id)
- ->get();
- foreach ($shares as $share) {
- UndoSharePipeline::dispatch($share);
- ReblogService::del($profile->id, $status->id);
- $count--;
- }
- } else {
- $share = new Status();
- $share->profile_id = $profile->id;
- $share->reblog_of_id = $status->id;
- $share->in_reply_to_profile_id = $status->profile_id;
- $share->type = 'share';
- $share->save();
- $count++;
- SharePipeline::dispatch($share);
- ReblogService::add($profile->id, $status->id);
- }
-
- Cache::forget('status:'.$status->id.':sharedby:userid:'.$user->id);
- StatusService::del($status->id);
-
- if ($request->ajax()) {
- $response = ['code' => 200, 'msg' => 'Share saved', 'count' => $count];
- } else {
- $response = redirect($status->url());
- }
-
- return $response;
- }
-
- public function showActivityPub(Request $request, $status)
- {
- $object = $status->type == 'poll' ? new Question() : new Note();
- $fractal = new Fractal\Manager();
- $resource = new Fractal\Resource\Item($status, $object);
- $res = $fractal->createData($resource)->toArray();
-
- return response()->json($res['data'], 200, ['Content-Type' => 'application/activity+json'], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
- }
-
- public function edit(Request $request, $username, $id)
- {
- $this->authCheck();
- $user = Auth::user()->profile;
- $status = Status::whereProfileId($user->id)
- ->with(['media'])
- ->findOrFail($id);
- $licenses = License::get();
- return view('status.edit', compact('user', 'status', 'licenses'));
- }
-
- public function editStore(Request $request, $username, $id)
- {
- $this->authCheck();
- $user = Auth::user()->profile;
- $status = Status::whereProfileId($user->id)
- ->with(['media'])
- ->findOrFail($id);
-
- $this->validate($request, [
- 'license' => 'nullable|integer|min:1|max:16',
- ]);
-
- $licenseId = $request->input('license');
-
- $status->media->each(function($media) use($licenseId) {
- $media->license = $licenseId;
- $media->save();
- Cache::forget('status:transformer:media:attachments:'.$media->status_id);
- });
-
- return redirect($status->url());
- }
-
- protected function authCheck()
- {
- if (Auth::check() == false) {
- abort(403);
- }
- }
-
- protected function validateVisibility($visibility)
- {
- $allowed = ['public', 'unlisted', 'private'];
- return in_array($visibility, $allowed) ? $visibility : 'public';
- }
-
- public static function mimeTypeCheck($mimes)
- {
- $allowed = explode(',', config_cache('pixelfed.media_types'));
- $count = count($mimes);
- $photos = 0;
- $videos = 0;
- foreach($mimes as $mime) {
- if(in_array($mime, $allowed) == false && $mime !== 'video/mp4') {
- continue;
- }
- if(str_contains($mime, 'image/')) {
- $photos++;
- }
- if(str_contains($mime, 'video/')) {
- $videos++;
- }
- }
- if($photos == 1 && $videos == 0) {
- return 'photo';
- }
- if($videos == 1 && $photos == 0) {
- return 'video';
- }
- if($photos > 1 && $videos == 0) {
- return 'photo:album';
- }
- if($videos > 1 && $photos == 0) {
- return 'video:album';
- }
- if($photos >= 1 && $videos >= 1) {
- return 'photo:video:album';
- }
-
- return 'text';
- }
-
- public function toggleVisibility(Request $request) {
- $this->authCheck();
- $this->validate($request, [
- 'item' => 'required|string|min:1|max:20',
- 'disableComments' => 'required|boolean'
- ]);
-
- $user = Auth::user();
- $id = $request->input('item');
- $state = $request->input('disableComments');
-
- $status = Status::findOrFail($id);
-
- if($status->profile_id != $user->profile->id && $user->is_admin == false) {
- abort(403);
- }
-
- $status->comments_disabled = $status->comments_disabled == true ? false : true;
- $status->save();
-
- return response()->json([200]);
- }
-
- public function storeView(Request $request)
- {
- abort_if(!$request->user(), 403);
-
- $views = $request->input('_v');
- $uid = $request->user()->profile_id;
-
- if(empty($views) || !is_array($views)) {
- return response()->json(0);
- }
-
- Cache::forget('profile:home-timeline-cursor:' . $request->user()->id);
-
- foreach($views as $view) {
- if(!isset($view['sid']) || !isset($view['pid'])) {
- continue;
- }
- DB::transaction(function () use($view, $uid) {
- StatusView::firstOrCreate([
- 'status_id' => $view['sid'],
- 'status_profile_id' => $view['pid'],
- 'profile_id' => $uid
- ]);
- });
- }
-
- return response()->json(1);
- }
+ public function show(Request $request, $username, $id)
+ {
+ // redirect authed users to Metro 2.0
+ if ($request->user()) {
+ // unless they force static view
+ if (! $request->has('fs') || $request->input('fs') != '1') {
+ return redirect('/i/web/post/'.$id);
+ }
+ }
+
+ $user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
+
+ if ($user->status != null) {
+ return ProfileController::accountCheck($user);
+ }
+
+ $status = Status::whereProfileId($user->id)
+ ->whereNull('reblog_of_id')
+ ->whereIn('scope', ['public', 'unlisted', 'private'])
+ ->findOrFail($id);
+
+ if ($status->uri || $status->url) {
+ $url = $status->uri ?? $status->url;
+ if (ends_with($url, '/activity')) {
+ $url = str_replace('/activity', '', $url);
+ }
+
+ return redirect($url);
+ }
+
+ if ($status->visibility == 'private' || $user->is_private) {
+ if (! Auth::check()) {
+ abort(404);
+ }
+ $pid = Auth::user()->profile;
+ if ($user->followedBy($pid) == false && $user->id !== $pid->id && Auth::user()->is_admin == false) {
+ abort(404);
+ }
+ }
+
+ if ($status->type == 'archived') {
+ if (Auth::user()->profile_id !== $status->profile_id) {
+ abort(404);
+ }
+ }
+
+ if ($request->user() && $request->user()->profile_id != $status->profile_id) {
+ StatusView::firstOrCreate([
+ 'status_id' => $status->id,
+ 'status_profile_id' => $status->profile_id,
+ 'profile_id' => $request->user()->profile_id,
+ ]);
+ }
+
+ if ($request->wantsJson() && config_cache('federation.activitypub.enabled')) {
+ return $this->showActivityPub($request, $status);
+ }
+
+ $template = $status->in_reply_to_id ? 'status.reply' : 'status.show';
+
+ return view($template, compact('user', 'status'));
+ }
+
+ public function shortcodeRedirect(Request $request, $id)
+ {
+ $hid = HashidService::decode($id);
+ abort_if(! $hid, 404);
+
+ return redirect('/i/web/post/'.$hid);
+ }
+
+ public function showId(int $id)
+ {
+ abort(404);
+ $status = Status::whereNull('reblog_of_id')
+ ->whereIn('scope', ['public', 'unlisted'])
+ ->findOrFail($id);
+
+ return redirect($status->url());
+ }
+
+ public function showEmbed(Request $request, $username, int $id)
+ {
+ if (! (bool) config_cache('instance.embed.post')) {
+ $res = view('status.embed-removed');
+
+ return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+ }
+
+ $profile = Profile::whereNull(['domain', 'status'])
+ ->whereIsPrivate(false)
+ ->whereUsername($username)
+ ->first();
+
+ if (! $profile) {
+ $content = view('status.embed-removed');
+
+ return response($content)->header('X-Frame-Options', 'ALLOWALL');
+ }
+
+ $aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile->id, 86400, function () use ($profile) {
+ $exists = AccountInterstitial::whereUserId($profile->user_id)->where('is_spam', 1)->count();
+ if ($exists) {
+ return true;
+ }
+
+ return false;
+ });
+
+ if ($aiCheck) {
+ $res = view('status.embed-removed');
+
+ return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+ }
+ $status = Status::whereProfileId($profile->id)
+ ->whereNull('uri')
+ ->whereScope('public')
+ ->whereIsNsfw(false)
+ ->whereIn('type', ['photo', 'video', 'photo:album'])
+ ->find($id);
+ if (! $status) {
+ $content = view('status.embed-removed');
+
+ return response($content)->header('X-Frame-Options', 'ALLOWALL');
+ }
+ $showLikes = $request->filled('likes') && $request->likes == true;
+ $showCaption = $request->filled('caption') && $request->caption !== false;
+ $layout = $request->filled('layout') && $request->layout == 'compact' ? 'compact' : 'full';
+ $content = view('status.embed', compact('status', 'showLikes', 'showCaption', 'layout'));
+
+ return response($content)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
+ }
+
+ public function showObject(Request $request, $username, int $id)
+ {
+ $user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
+
+ if ($user->status != null) {
+ return ProfileController::accountCheck($user);
+ }
+
+ $status = Status::whereProfileId($user->id)
+ ->whereNotIn('visibility', ['draft', 'direct'])
+ ->findOrFail($id);
+
+ abort_if($status->uri, 404);
+
+ if ($status->visibility == 'private' || $user->is_private) {
+ if (! Auth::check()) {
+ abort(403);
+ }
+ $pid = Auth::user()->profile;
+ if ($user->followedBy($pid) == false && $user->id !== $pid->id) {
+ abort(403);
+ }
+ }
+
+ return $this->showActivityPub($request, $status);
+ }
+
+ public function compose()
+ {
+ $this->authCheck();
+
+ return view('status.compose');
+ }
+
+ public function store(Request $request)
+ {
+
+ }
+
+ public function delete(Request $request)
+ {
+ $this->authCheck();
+
+ $this->validate($request, [
+ 'item' => 'required|integer|min:1',
+ ]);
+
+ $status = Status::findOrFail($request->input('item'));
+
+ $user = Auth::user();
+
+ if ($status->profile_id != $user->profile->id &&
+ $user->is_admin == true &&
+ $status->uri == null
+ ) {
+ $media = $status->media;
+
+ $ai = new AccountInterstitial;
+ $ai->user_id = $status->profile->user_id;
+ $ai->type = 'post.removed';
+ $ai->view = 'account.moderation.post.removed';
+ $ai->item_type = 'App\Status';
+ $ai->item_id = $status->id;
+ $ai->has_media = (bool) $media->count();
+ $ai->blurhash = $media->count() ? $media->first()->blurhash : null;
+ $ai->meta = json_encode([
+ 'caption' => $status->caption,
+ 'created_at' => $status->created_at,
+ 'type' => $status->type,
+ 'url' => $status->url(),
+ 'is_nsfw' => $status->is_nsfw,
+ 'scope' => $status->scope,
+ 'reblog' => $status->reblog_of_id,
+ 'likes_count' => $status->likes_count,
+ 'reblogs_count' => $status->reblogs_count,
+ ]);
+ $ai->save();
+
+ $u = $status->profile->user;
+ $u->has_interstitial = true;
+ $u->save();
+ }
+
+ if ($status->in_reply_to_id) {
+ $parent = Status::find($status->in_reply_to_id);
+ if ($parent && ($parent->profile_id == $user->profile_id) || ($status->profile_id == $user->profile_id) || $user->is_admin) {
+ Cache::forget('_api:statuses:recent_9:'.$status->profile_id);
+ Cache::forget('profile:status_count:'.$status->profile_id);
+ Cache::forget('profile:embed:'.$status->profile_id);
+ StatusService::del($status->id, true);
+ Cache::forget('profile:status_count:'.$status->profile_id);
+ $status->uri ? RemoteStatusDelete::dispatch($status) : StatusDelete::dispatch($status);
+ }
+ } elseif ($status->profile_id == $user->profile_id || $user->is_admin == true) {
+ Cache::forget('_api:statuses:recent_9:'.$status->profile_id);
+ Cache::forget('profile:status_count:'.$status->profile_id);
+ Cache::forget('profile:embed:'.$status->profile_id);
+ StatusService::del($status->id, true);
+ Cache::forget('profile:status_count:'.$status->profile_id);
+ $status->uri ? RemoteStatusDelete::dispatch($status) : StatusDelete::dispatch($status);
+ }
+
+ if ($request->wantsJson()) {
+ return response()->json(['Status successfully deleted.']);
+ } else {
+ return redirect($user->url());
+ }
+ }
+
+ public function storeShare(Request $request)
+ {
+ $this->authCheck();
+
+ $this->validate($request, [
+ 'item' => 'required|integer|min:1',
+ ]);
+
+ $user = Auth::user();
+ $profile = $user->profile;
+ $status = Status::whereScope('public')
+ ->findOrFail($request->input('item'));
+
+ $count = $status->reblogs_count;
+
+ $exists = Status::whereProfileId(Auth::user()->profile->id)
+ ->whereReblogOfId($status->id)
+ ->exists();
+ if ($exists == true) {
+ $shares = Status::whereProfileId(Auth::user()->profile->id)
+ ->whereReblogOfId($status->id)
+ ->get();
+ foreach ($shares as $share) {
+ UndoSharePipeline::dispatch($share);
+ ReblogService::del($profile->id, $status->id);
+ $count--;
+ }
+ } else {
+ $share = new Status();
+ $share->profile_id = $profile->id;
+ $share->reblog_of_id = $status->id;
+ $share->in_reply_to_profile_id = $status->profile_id;
+ $share->type = 'share';
+ $share->save();
+ $count++;
+ SharePipeline::dispatch($share);
+ ReblogService::add($profile->id, $status->id);
+ }
+
+ Cache::forget('status:'.$status->id.':sharedby:userid:'.$user->id);
+ StatusService::del($status->id);
+
+ if ($request->ajax()) {
+ $response = ['code' => 200, 'msg' => 'Share saved', 'count' => $count];
+ } else {
+ $response = redirect($status->url());
+ }
+
+ return $response;
+ }
+
+ public function showActivityPub(Request $request, $status)
+ {
+ $object = $status->type == 'poll' ? new Question() : new Note();
+ $fractal = new Fractal\Manager();
+ $resource = new Fractal\Resource\Item($status, $object);
+ $res = $fractal->createData($resource)->toArray();
+
+ return response()->json($res['data'], 200, ['Content-Type' => 'application/activity+json'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
+ }
+
+ public function edit(Request $request, $username, $id)
+ {
+ $this->authCheck();
+ $user = Auth::user()->profile;
+ $status = Status::whereProfileId($user->id)
+ ->with(['media'])
+ ->findOrFail($id);
+ $licenses = License::get();
+
+ return view('status.edit', compact('user', 'status', 'licenses'));
+ }
+
+ public function editStore(Request $request, $username, $id)
+ {
+ $this->authCheck();
+ $user = Auth::user()->profile;
+ $status = Status::whereProfileId($user->id)
+ ->with(['media'])
+ ->findOrFail($id);
+
+ $this->validate($request, [
+ 'license' => 'nullable|integer|min:1|max:16',
+ ]);
+
+ $licenseId = $request->input('license');
+
+ $status->media->each(function ($media) use ($licenseId) {
+ $media->license = $licenseId;
+ $media->save();
+ Cache::forget('status:transformer:media:attachments:'.$media->status_id);
+ });
+
+ return redirect($status->url());
+ }
+
+ protected function authCheck()
+ {
+ if (Auth::check() == false) {
+ abort(403);
+ }
+ }
+
+ protected function validateVisibility($visibility)
+ {
+ $allowed = ['public', 'unlisted', 'private'];
+
+ return in_array($visibility, $allowed) ? $visibility : 'public';
+ }
+
+ public static function mimeTypeCheck($mimes)
+ {
+ $allowed = explode(',', config_cache('pixelfed.media_types'));
+ $count = count($mimes);
+ $photos = 0;
+ $videos = 0;
+ foreach ($mimes as $mime) {
+ if (in_array($mime, $allowed) == false && $mime !== 'video/mp4') {
+ continue;
+ }
+ if (str_contains($mime, 'image/')) {
+ $photos++;
+ }
+ if (str_contains($mime, 'video/')) {
+ $videos++;
+ }
+ }
+ if ($photos == 1 && $videos == 0) {
+ return 'photo';
+ }
+ if ($videos == 1 && $photos == 0) {
+ return 'video';
+ }
+ if ($photos > 1 && $videos == 0) {
+ return 'photo:album';
+ }
+ if ($videos > 1 && $photos == 0) {
+ return 'video:album';
+ }
+ if ($photos >= 1 && $videos >= 1) {
+ return 'photo:video:album';
+ }
+
+ return 'text';
+ }
+
+ public function toggleVisibility(Request $request)
+ {
+ $this->authCheck();
+ $this->validate($request, [
+ 'item' => 'required|string|min:1|max:20',
+ 'disableComments' => 'required|boolean',
+ ]);
+
+ $user = Auth::user();
+ $id = $request->input('item');
+ $state = $request->input('disableComments');
+
+ $status = Status::findOrFail($id);
+
+ if ($status->profile_id != $user->profile->id && $user->is_admin == false) {
+ abort(403);
+ }
+
+ $status->comments_disabled = $status->comments_disabled == true ? false : true;
+ $status->save();
+
+ return response()->json([200]);
+ }
+
+ public function storeView(Request $request)
+ {
+ abort_if(! $request->user(), 403);
+
+ $views = $request->input('_v');
+ $uid = $request->user()->profile_id;
+
+ if (empty($views) || ! is_array($views)) {
+ return response()->json(0);
+ }
+
+ Cache::forget('profile:home-timeline-cursor:'.$request->user()->id);
+
+ foreach ($views as $view) {
+ if (! isset($view['sid']) || ! isset($view['pid'])) {
+ continue;
+ }
+ DB::transaction(function () use ($view, $uid) {
+ StatusView::firstOrCreate([
+ 'status_id' => $view['sid'],
+ 'status_profile_id' => $view['pid'],
+ 'profile_id' => $uid,
+ ]);
+ });
+ }
+
+ return response()->json(1);
+ }
}
diff --git a/app/Services/ConfigCacheService.php b/app/Services/ConfigCacheService.php
index af2d1ef6a..7537830fc 100644
--- a/app/Services/ConfigCacheService.php
+++ b/app/Services/ConfigCacheService.php
@@ -87,6 +87,8 @@ class ConfigCacheService
'pixelfed.app_registration_rate_limit_decay',
'pixelfed.app_registration_confirm_rate_limit_attempts',
'pixelfed.app_registration_confirm_rate_limit_decay',
+ 'instance.embed.profile',
+ 'instance.embed.post',
// 'system.user_mode'
];
diff --git a/app/Services/HashidService.php b/app/Services/HashidService.php
index 914d24321..e12c10599 100644
--- a/app/Services/HashidService.php
+++ b/app/Services/HashidService.php
@@ -2,54 +2,38 @@
namespace App\Services;
-use Cache;
+class HashidService
+{
+ public const CMAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
-class HashidService {
+ public static function encode($id, $minLimit = true)
+ {
+ if (! is_numeric($id) || $id > PHP_INT_MAX) {
+ return null;
+ }
- public const MIN_LIMIT = 15;
- public const CMAP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
+ $cmap = self::CMAP;
+ $base = strlen($cmap);
+ $shortcode = '';
+ while ($id) {
+ $id = ($id - ($r = $id % $base)) / $base;
+ $shortcode = $cmap[$r].$shortcode;
+ }
- public static function encode($id, $minLimit = true)
- {
- if(!is_numeric($id) || $id > PHP_INT_MAX) {
- return null;
- }
+ return $shortcode;
+ }
- if($minLimit && strlen($id) < self::MIN_LIMIT) {
- return null;
- }
-
- $key = "hashids:{$id}";
- return Cache::remember($key, now()->hours(48), function() use($id) {
- $cmap = self::CMAP;
- $base = strlen($cmap);
- $shortcode = '';
- while($id) {
- $id = ($id - ($r = $id % $base)) / $base;
- $shortcode = $cmap[$r] . $shortcode;
- }
- return $shortcode;
- });
- }
-
- public static function decode($short)
- {
- $len = strlen($short);
- if($len < 3 || $len > 11) {
- return null;
- }
- $id = 0;
- foreach(str_split($short) as $needle) {
- $pos = strpos(self::CMAP, $needle);
- // if(!$pos) {
- // return null;
- // }
- $id = ($id*64) + $pos;
- }
- if(strlen($id) < self::MIN_LIMIT) {
- return null;
- }
- return $id;
- }
+ public static function decode($short = false)
+ {
+ if (! $short) {
+ return;
+ }
+ $id = 0;
+ foreach (str_split($short) as $needle) {
+ $pos = strpos(self::CMAP, $needle);
+ $id = ($id * 64) + $pos;
+ }
+ return $id;
+ }
}