From 7785a2dae43b01e284deeafb036c7fca89164a86 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Sun, 10 Mar 2024 04:37:22 -0600 Subject: [PATCH 1/3] 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 2/3] 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 3/3] 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; + } }