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')); + } } 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!'); } 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]); + } } diff --git a/app/Transformer/ActivityPub/ProfileTransformer.php b/app/Transformer/ActivityPub/ProfileTransformer.php index 45d22cd11..96d129bf7 100644 --- a/app/Transformer/ActivityPub/ProfileTransformer.php +++ b/app/Transformer/ActivityPub/ProfileTransformer.php @@ -3,67 +3,80 @@ 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; + $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); + } - if($profile->moved_to_profile_id) { - $res['movedTo'] = AccountService::get($profile->moved_to_profile_id)['url']; - } + if ($profile->moved_to_profile_id) { + $movedTo = AccountService::get($profile->moved_to_profile_id); + if ($movedTo && isset($movedTo['url'], $movedTo['id'])) { + $res['movedTo'] = $movedTo['url']; + } + } + } - return $res; + return $res; } } 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() + ]; + } + +} 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 + { + + } +}; 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');