From 2dcfc814958c9ebb623f876338bea35e0e689c5e Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Thu, 11 Jan 2024 04:40:25 -0700 Subject: [PATCH] Update ComposeController, add parental controls support --- app/Http/Controllers/Api/ApiV1Controller.php | 11 + app/Http/Controllers/ComposeController.php | 1280 +++++++++--------- 2 files changed, 661 insertions(+), 630 deletions(-) diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index 8f41b38fb..dde416064 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -1750,6 +1750,8 @@ class ApiV1Controller extends Controller ]); $user = $request->user(); + abort_if($user->has_roles && !UserRoleService::can('can-post', $user->id), 403, 'Invalid permissions for this action'); + AccountService::setLastActive($user->id); $media = Media::whereUserId($user->id) @@ -2983,6 +2985,15 @@ class ApiV1Controller extends Controller $in_reply_to_id = $request->input('in_reply_to_id'); $user = $request->user(); + + if($user->has_roles) { + if($in_reply_to_id != null) { + abort_if(!UserRoleService::can('can-comment', $user->id), 403, 'Invalid permissions for this action'); + } else { + abort_if(!UserRoleService::can('can-post', $user->id), 403, 'Invalid permissions for this action'); + } + } + $profile = $user->profile; $limitKey = 'compose:rate-limit:store:' . $user->id; diff --git a/app/Http/Controllers/ComposeController.php b/app/Http/Controllers/ComposeController.php index e79625861..e17a37fd7 100644 --- a/app/Http/Controllers/ComposeController.php +++ b/app/Http/Controllers/ComposeController.php @@ -6,26 +6,26 @@ 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 + 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 + MediaTransformer, + MediaDraftTransformer, + StatusTransformer, + StatusStatelessTransformer }; use League\Fractal; use App\Util\Media\Filter; @@ -36,9 +36,9 @@ use App\Jobs\ImageOptimizePipeline\ImageOptimize; use App\Jobs\ImageOptimizePipeline\ImageThumbnail; use App\Jobs\StatusPipeline\NewStatusPipeline; use App\Jobs\VideoPipeline\{ - VideoOptimize, - VideoPostProcess, - VideoThumbnail + VideoOptimize, + VideoPostProcess, + VideoThumbnail }; use App\Services\AccountService; use App\Services\CollectionService; @@ -58,230 +58,234 @@ use App\Services\UserRoleService; class ComposeController extends Controller { - protected $fractal; + protected $fractal; - public function __construct() - { - $this->middleware('auth'); - $this->fractal = new Fractal\Manager(); - $this->fractal->setSerializer(new ArraySerializer()); - } + public function __construct() + { + $this->middleware('auth'); + $this->fractal = new Fractal\Manager(); + $this->fractal->setSerializer(new ArraySerializer()); + } - public function show(Request $request) - { - return view('status.compose'); - } + public function show(Request $request) + { + return view('status.compose'); + } - public function mediaUpload(Request $request) - { - abort_if(!$request->user(), 403); + public function mediaUpload(Request $request) + { + 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'), - ], - 'file' => [ - 'required_without:file.*', - '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' - ]); + $this->validate($request, [ + 'file.*' => [ + 'required_without:file', + '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'), + ], + 'filter_name' => 'nullable|string|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'); + $user = Auth::user(); + $profile = $user->profile; + 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; - $limitTtl = now()->addMinutes(15); - $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { - $dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count(); + $limitKey = 'compose:rate-limit:media-upload:' . $user->id; + $limitTtl = now()->addMinutes(15); + $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { + $dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count(); - return $dailyLimit >= 1250; - }); + return $dailyLimit >= 1250; + }); - abort_if($limitReached == true, 429); + abort_if($limitReached == true, 429); - 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'); - if ($size >= $limit) { - abort(403, 'Account size limit reached.'); - } - } + 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'); + if ($size >= $limit) { + abort(403, 'Account size limit reached.'); + } + } - $filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null; - $filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null; + $filterClass = in_array($request->input('filter_class'), Filter::classes()) ? $request->input('filter_class') : null; + $filterName = in_array($request->input('filter_name'), Filter::names()) ? $request->input('filter_name') : null; - $photo = $request->file('file'); + $photo = $request->file('file'); - $mimes = explode(',', config_cache('pixelfed.media_types')); + $mimes = explode(',', config_cache('pixelfed.media_types')); - abort_if(in_array($photo->getMimeType(), $mimes) == false, 400, 'Invalid media format'); + abort_if(in_array($photo->getMimeType(), $mimes) == false, 400, 'Invalid media format'); - $storagePath = MediaPathService::get($user, 2); - $path = $photo->storePublicly($storagePath); - $hash = \hash_file('sha256', $photo); - $mime = $photo->getMimeType(); + $storagePath = MediaPathService::get($user, 2); + $path = $photo->storePublicly($storagePath); + $hash = \hash_file('sha256', $photo); + $mime = $photo->getMimeType(); - abort_if(MediaBlocklistService::exists($hash) == true, 451); + abort_if(MediaBlocklistService::exists($hash) == true, 451); - $media = new Media(); - $media->status_id = null; - $media->profile_id = $profile->id; - $media->user_id = $user->id; - $media->media_path = $path; - $media->original_sha256 = $hash; - $media->size = $photo->getSize(); - $media->mime = $mime; - $media->filter_class = $filterClass; - $media->filter_name = $filterName; - $media->version = 3; - $media->save(); + $media = new Media(); + $media->status_id = null; + $media->profile_id = $profile->id; + $media->user_id = $user->id; + $media->media_path = $path; + $media->original_sha256 = $hash; + $media->size = $photo->getSize(); + $media->mime = $mime; + $media->filter_class = $filterClass; + $media->filter_name = $filterName; + $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; + switch ($media->mime) { + case 'image/jpeg': + case 'image/png': + case 'image/webp': + 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; + case 'video/mp4': + VideoThumbnail::dispatch($media)->onQueue('mmo'); + $preview_url = '/storage/no-preview.png'; + $url = '/storage/no-preview.png'; + break; - default: - break; - } + default: + break; + } - Cache::forget($limitKey); - $resource = new Fractal\Resource\Item($media, new MediaTransformer()); - $res = $this->fractal->createData($resource)->toArray(); - $res['preview_url'] = $preview_url; - $res['url'] = $url; - return response()->json($res); - } + Cache::forget($limitKey); + $resource = new Fractal\Resource\Item($media, new MediaTransformer()); + $res = $this->fractal->createData($resource)->toArray(); + $res['preview_url'] = $preview_url; + $res['url'] = $url; + return response()->json($res); + } - public function mediaUpdate(Request $request) - { - $this->validate($request, [ - 'id' => 'required', - 'file' => function() { - return [ - 'required', - 'mimetypes:' . config_cache('pixelfed.media_types'), - 'max:' . config_cache('pixelfed.max_photo_size'), - ]; - }, - ]); + public function mediaUpdate(Request $request) + { + $this->validate($request, [ + 'id' => 'required', + 'file' => function() { + return [ + 'required', + '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'); + $user = Auth::user(); + 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; - $limitTtl = now()->addMinutes(15); - $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { - $dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count(); + $limitKey = 'compose:rate-limit:media-updates:' . $user->id; + $limitTtl = now()->addMinutes(15); + $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { + $dailyLimit = Media::whereUserId($user->id)->where('created_at', '>', now()->subDays(1))->count(); - return $dailyLimit >= 1500; - }); + return $dailyLimit >= 1500; + }); - abort_if($limitReached == true, 429); + abort_if($limitReached == true, 429); - $photo = $request->file('file'); - $id = $request->input('id'); + $photo = $request->file('file'); + $id = $request->input('id'); - $media = Media::whereUserId($user->id) - ->whereProfileId($user->profile_id) - ->whereNull('status_id') - ->findOrFail($id); + $media = Media::whereUserId($user->id) + ->whereProfileId($user->profile_id) + ->whereNull('status_id') + ->findOrFail($id); - $media->save(); + $media->save(); - $fragments = explode('/', $media->media_path); - $name = last($fragments); - array_pop($fragments); - $dir = implode('/', $fragments); - $path = $photo->storePubliclyAs($dir, $name); - $res = [ - 'url' => $media->url() . '?v=' . time() - ]; - ImageOptimize::dispatch($media)->onQueue('mmo'); - Cache::forget($limitKey); - return $res; - } + $fragments = explode('/', $media->media_path); + $name = last($fragments); + array_pop($fragments); + $dir = implode('/', $fragments); + $path = $photo->storePubliclyAs($dir, $name); + $res = [ + '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); + public function mediaDelete(Request $request) + { + abort_if(!$request->user(), 403); - $this->validate($request, [ - 'id' => 'required|integer|min:1|exists:media,id' - ]); + $this->validate($request, [ + 'id' => 'required|integer|min:1|exists:media,id' + ]); - $media = Media::whereNull('status_id') - ->whereUserId(Auth::id()) - ->findOrFail($request->input('id')); + abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action'); - MediaStorageService::delete($media, true); + $media = Media::whereNull('status_id') + ->whereUserId(Auth::id()) + ->findOrFail($request->input('id')); - return response()->json([ - 'msg' => 'Successfully deleted', - 'code' => 200 - ]); - } + MediaStorageService::delete($media, true); - public function searchTag(Request $request) - { - abort_if(!$request->user(), 403); + return response()->json([ + 'msg' => 'Successfully deleted', + 'code' => 200 + ]); + } - $this->validate($request, [ - 'q' => 'required|string|min:1|max:50' - ]); + public function searchTag(Request $request) + { + abort_if(!$request->user(), 403); - $q = $request->input('q'); + $this->validate($request, [ + 'q' => 'required|string|min:1|max:50' + ]); - if(Str::of($q)->startsWith('@')) { - if(strlen($q) < 3) { - return []; - } - $q = mb_substr($q, 1); - } + $q = $request->input('q'); - $blocked = UserFilter::whereFilterableType('App\Profile') - ->whereFilterType('block') - ->whereFilterableId($request->user()->profile_id) - ->pluck('user_id'); + if(Str::of($q)->startsWith('@')) { + if(strlen($q) < 3) { + return []; + } + $q = mb_substr($q, 1); + } - $blocked->push($request->user()->profile_id); + abort_if($user->has_roles && !UserRoleService::can('can-post', $user->id), 403, 'Invalid permissions for this action'); - $results = Profile::select('id','domain','username') - ->whereNotIn('id', $blocked) - ->whereNull('domain') - ->where('username','like','%'.$q.'%') - ->limit(15) - ->get() - ->map(function($r) { - return [ - 'id' => (string) $r->id, - 'name' => $r->username, - 'privacy' => true, - 'avatar' => $r->avatarUrl() - ]; - }); + $blocked = UserFilter::whereFilterableType('App\Profile') + ->whereFilterType('block') + ->whereFilterableId($request->user()->profile_id) + ->pluck('user_id'); - return $results; - } + $blocked->push($request->user()->profile_id); + + $results = Profile::select('id','domain','username') + ->whereNotIn('id', $blocked) + ->whereNull('domain') + ->where('username','like','%'.$q.'%') + ->limit(15) + ->get() + ->map(function($r) { + return [ + 'id' => (string) $r->id, + 'name' => $r->username, + 'privacy' => true, + 'avatar' => $r->avatarUrl() + ]; + }); + + return $results; + } public function searchUntag(Request $request) { @@ -292,6 +296,8 @@ class ComposeController extends Controller 'profile_id' => 'required' ]); + 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'); $profile_id = (int) $request->input('profile_id'); @@ -316,506 +322,520 @@ class ComposeController extends Controller return [200]; } - public function searchLocation(Request $request) - { - abort_if(!$request->user(), 403); - $this->validate($request, [ - 'q' => 'required|string|max:100' - ]); - $pid = $request->user()->profile_id; - abort_if(!$pid, 400); - $q = e($request->input('q')); + public function searchLocation(Request $request) + { + abort_if(!$request->user(), 403); + $this->validate($request, [ + 'q' => 'required|string|max:100' + ]); + 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); + $q = e($request->input('q')); - $popular = Cache::remember('pf:search:location:v1:popular', 1209600, function() { - $minId = SnowflakeService::byDate(now()->subDays(290)); - 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(); - } - return Status::selectRaw('id, place_id, count(place_id) as pc') - ->whereNotNull('place_id') - ->where('id', '>', $minId) - ->groupBy('place_id') - ->orderByDesc('pc') - ->limit(400) - ->get() - ->filter(function($post) { - return $post; - }) - ->map(function($place) { - return [ - 'id' => $place->place_id, - 'count' => $place->pc - ]; - }); - }); - $q = '%' . $q . '%'; - $wildcard = config('database.default') === 'pgsql' ? 'ilike' : 'like'; + $popular = Cache::remember('pf:search:location:v1:popular', 1209600, function() { + $minId = SnowflakeService::byDate(now()->subDays(290)); + 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(); + } + return Status::selectRaw('id, place_id, count(place_id) as pc') + ->whereNotNull('place_id') + ->where('id', '>', $minId) + ->groupBy('place_id') + ->orderByDesc('pc') + ->limit(400) + ->get() + ->filter(function($post) { + return $post; + }) + ->map(function($place) { + return [ + 'id' => $place->place_id, + 'count' => $place->pc + ]; + }); + }); + $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(); - return $places; - } + $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(); + return $places; + } - public function searchMentionAutocomplete(Request $request) - { - abort_if(!$request->user(), 403); + public function searchMentionAutocomplete(Request $request) + { + abort_if(!$request->user(), 403); - $this->validate($request, [ - 'q' => 'required|string|min:2|max:50' - ]); + $this->validate($request, [ + 'q' => 'required|string|min:2|max:50' + ]); - $q = $request->input('q'); + abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action'); - if(Str::of($q)->startsWith('@')) { - if(strlen($q) < 3) { - return []; - } - } + $q = $request->input('q'); - $blocked = UserFilter::whereFilterableType('App\Profile') - ->whereFilterType('block') - ->whereFilterableId($request->user()->profile_id) - ->pluck('user_id'); + if(Str::of($q)->startsWith('@')) { + if(strlen($q) < 3) { + return []; + } + } - $blocked->push($request->user()->profile_id); + $blocked = UserFilter::whereFilterableType('App\Profile') + ->whereFilterType('block') + ->whereFilterableId($request->user()->profile_id) + ->pluck('user_id'); - $results = Profile::select('id','domain','username') - ->whereNotIn('id', $blocked) - ->where('username','like','%'.$q.'%') - ->groupBy('id', 'domain') - ->limit(15) - ->get() - ->map(function($profile) { - $username = $profile->domain ? substr($profile->username, 1) : $profile->username; + $blocked->push($request->user()->profile_id); + + $results = Profile::select('id','domain','username') + ->whereNotIn('id', $blocked) + ->where('username','like','%'.$q.'%') + ->groupBy('id', 'domain') + ->limit(15) + ->get() + ->map(function($profile) { + $username = $profile->domain ? substr($profile->username, 1) : $profile->username; return [ 'key' => '@' . str_limit($username, 30), 'value' => $username, ]; - }); + }); - return $results; - } + return $results; + } - public function searchHashtagAutocomplete(Request $request) - { - abort_if(!$request->user(), 403); + public function searchHashtagAutocomplete(Request $request) + { + abort_if(!$request->user(), 403); - $this->validate($request, [ - 'q' => 'required|string|min:2|max:50' - ]); + $this->validate($request, [ + 'q' => 'required|string|min:2|max:50' + ]); - $q = $request->input('q'); + abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action'); - $results = Hashtag::select('slug') - ->where('slug', 'like', '%'.$q.'%') - ->whereIsNsfw(false) - ->whereIsBanned(false) - ->limit(5) - ->get() - ->map(function($tag) { - return [ - 'key' => '#' . $tag->slug, - 'value' => $tag->slug - ]; - }); + $q = $request->input('q'); - return $results; - } + $results = Hashtag::select('slug') + ->where('slug', 'like', '%'.$q.'%') + ->whereIsNsfw(false) + ->whereIsBanned(false) + ->limit(5) + ->get() + ->map(function($tag) { + return [ + 'key' => '#' . $tag->slug, + 'value' => $tag->slug + ]; + }); - public function store(Request $request) - { - $this->validate($request, [ - 'caption' => 'nullable|string|max:'.config('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', - 'media.*.alt' => 'nullable|string|max:'.config_cache('pixelfed.max_altext_length'), - 'cw' => 'nullable|boolean', - 'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10', - 'place' => 'nullable', - 'comments_disabled' => 'nullable', - 'tagged' => 'nullable', - 'license' => 'nullable|integer|min:1|max:16', - 'collections' => 'sometimes|array|min:1|max:5', - 'spoiler_text' => 'nullable|string|max:140', - // 'optimize_media' => 'nullable' - ]); + return $results; + } - if(config('costar.enabled') == true) { - $blockedKeywords = config('costar.keyword.block'); - if($blockedKeywords !== null && $request->caption) { - $keywords = config('costar.keyword.block'); - foreach($keywords as $kw) { - if(Str::contains($request->caption, $kw) == true) { - abort(400, 'Invalid object'); - } - } - } - } + public function store(Request $request) + { + $this->validate($request, [ + 'caption' => 'nullable|string|max:'.config('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', + 'media.*.alt' => 'nullable|string|max:'.config_cache('pixelfed.max_altext_length'), + 'cw' => 'nullable|boolean', + 'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10', + 'place' => 'nullable', + 'comments_disabled' => 'nullable', + 'tagged' => 'nullable', + 'license' => 'nullable|integer|min:1|max:16', + 'collections' => 'sometimes|array|min:1|max:5', + 'spoiler_text' => 'nullable|string|max:140', + // 'optimize_media' => 'nullable' + ]); - $user = Auth::user(); - $profile = $user->profile; + abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action'); - $limitKey = 'compose:rate-limit:store:' . $user->id; - $limitTtl = now()->addMinutes(15); - $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { - $dailyLimit = Status::whereProfileId($user->profile_id) - ->whereNull('in_reply_to_id') - ->whereNull('reblog_of_id') - ->where('created_at', '>', now()->subDays(1)) - ->count(); + if(config('costar.enabled') == true) { + $blockedKeywords = config('costar.keyword.block'); + if($blockedKeywords !== null && $request->caption) { + $keywords = config('costar.keyword.block'); + foreach($keywords as $kw) { + if(Str::contains($request->caption, $kw) == true) { + abort(400, 'Invalid object'); + } + } + } + } - return $dailyLimit >= 1000; - }); + $user = $request->user(); + $profile = $user->profile; - abort_if($limitReached == true, 429); + $limitKey = 'compose:rate-limit:store:' . $user->id; + $limitTtl = now()->addMinutes(15); + $limitReached = Cache::remember($limitKey, $limitTtl, function() use($user) { + $dailyLimit = Status::whereProfileId($user->profile_id) + ->whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->where('created_at', '>', now()->subDays(1)) + ->count(); - $license = in_array($request->input('license'), License::keys()) ? $request->input('license') : null; + return $dailyLimit >= 1000; + }); - $visibility = $request->input('visibility'); - $medias = $request->input('media'); - $attachments = []; - $status = new Status; - $mimes = []; - $place = $request->input('place'); - $cw = $request->input('cw'); - $tagged = $request->input('tagged'); - $optimize_media = (bool) $request->input('optimize_media'); + abort_if($limitReached == true, 429); - 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) { - abort(403, 'Invalid media id'); - } - $m->filter_class = in_array($media['filter_class'], Filter::classes()) ? $media['filter_class'] : null; - $m->license = $license; - $m->caption = isset($media['alt']) ? strip_tags($media['alt']) : null; - $m->order = isset($media['cursor']) && is_int($media['cursor']) ? (int) $media['cursor'] : $k; + $license = in_array($request->input('license'), License::keys()) ? $request->input('license') : null; - if($cw == true || $profile->cw == true) { - $m->is_nsfw = $cw; - $status->is_nsfw = $cw; - } - $m->save(); - $attachments[] = $m; - array_push($mimes, $m->mime); - } + $visibility = $request->input('visibility'); + $medias = $request->input('media'); + $attachments = []; + $status = new Status; + $mimes = []; + $place = $request->input('place'); + $cw = $request->input('cw'); + $tagged = $request->input('tagged'); + $optimize_media = (bool) $request->input('optimize_media'); - abort_if(empty($attachments), 422); + 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) { + abort(403, 'Invalid media id'); + } + $m->filter_class = in_array($media['filter_class'], Filter::classes()) ? $media['filter_class'] : null; + $m->license = $license; + $m->caption = isset($media['alt']) ? strip_tags($media['alt']) : null; + $m->order = isset($media['cursor']) && is_int($media['cursor']) ? (int) $media['cursor'] : $k; - $mediaType = StatusController::mimeTypeCheck($mimes); + if($cw == true || $profile->cw == true) { + $m->is_nsfw = $cw; + $status->is_nsfw = $cw; + } + $m->save(); + $attachments[] = $m; + array_push($mimes, $m->mime); + } - if(in_array($mediaType, ['photo', 'video', 'photo:album']) == false) { - abort(400, __('exception.compose.invalid.album')); - } + abort_if(empty($attachments), 422); - if($place && is_array($place)) { - $status->place_id = $place['id']; - } + $mediaType = StatusController::mimeTypeCheck($mimes); - if($request->filled('comments_disabled')) { - $status->comments_disabled = (bool) $request->input('comments_disabled'); - } + if(in_array($mediaType, ['photo', 'video', 'photo:album']) == false) { + abort(400, __('exception.compose.invalid.album')); + } - if($request->filled('spoiler_text') && $cw) { - $status->cw_summary = $request->input('spoiler_text'); - } + if($place && is_array($place)) { + $status->place_id = $place['id']; + } - $status->caption = strip_tags($request->caption); - $status->rendered = Autolink::create()->autolink($status->caption); - $status->scope = 'draft'; - $status->visibility = 'draft'; - $status->profile_id = $profile->id; - $status->save(); + if($request->filled('comments_disabled')) { + $status->comments_disabled = (bool) $request->input('comments_disabled'); + } - foreach($attachments as $media) { - $media->status_id = $status->id; - $media->save(); - } + if($request->filled('spoiler_text') && $cw) { + $status->cw_summary = $request->input('spoiler_text'); + } - $visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility; - $visibility = $profile->is_private ? 'private' : $visibility; - $cw = $profile->cw == true ? true : $cw; - $status->is_nsfw = $cw; - $status->visibility = $visibility; - $status->scope = $visibility; - $status->type = $mediaType; - $status->save(); + $status->caption = strip_tags($request->caption); + $status->rendered = Autolink::create()->autolink($status->caption); + $status->scope = 'draft'; + $status->visibility = 'draft'; + $status->profile_id = $profile->id; + $status->save(); - foreach($tagged as $tg) { - $mt = new MediaTag; - $mt->status_id = $status->id; - $mt->media_id = $status->media->first()->id; - $mt->profile_id = $tg['id']; - $mt->tagged_username = $tg['name']; - $mt->is_public = true; - $mt->metadata = json_encode([ - '_v' => 1, - ]); - $mt->save(); - MediaTagService::set($mt->status_id, $mt->profile_id); - MediaTagService::sendNotification($mt); - } + foreach($attachments as $media) { + $media->status_id = $status->id; + $media->save(); + } - if($request->filled('collections')) { - $collections = Collection::whereProfileId($profile->id) - ->find($request->input('collections')) - ->each(function($collection) use($status) { - $count = $collection->items()->count(); - CollectionItem::firstOrCreate([ - 'collection_id' => $collection->id, - 'object_type' => 'App\Status', - 'object_id' => $status->id - ], [ - 'order' => $count - ]); + $visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility; + $visibility = $profile->is_private ? 'private' : $visibility; + $cw = $profile->cw == true ? true : $cw; + $status->is_nsfw = $cw; + $status->visibility = $visibility; + $status->scope = $visibility; + $status->type = $mediaType; + $status->save(); - CollectionService::addItem( - $collection->id, - $status->id, - $count - ); + foreach($tagged as $tg) { + $mt = new MediaTag; + $mt->status_id = $status->id; + $mt->media_id = $status->media->first()->id; + $mt->profile_id = $tg['id']; + $mt->tagged_username = $tg['name']; + $mt->is_public = true; + $mt->metadata = json_encode([ + '_v' => 1, + ]); + $mt->save(); + MediaTagService::set($mt->status_id, $mt->profile_id); + MediaTagService::sendNotification($mt); + } - $collection->updated_at = now(); + if($request->filled('collections')) { + $collections = Collection::whereProfileId($profile->id) + ->find($request->input('collections')) + ->each(function($collection) use($status) { + $count = $collection->items()->count(); + CollectionItem::firstOrCreate([ + 'collection_id' => $collection->id, + 'object_type' => 'App\Status', + 'object_id' => $status->id + ], [ + 'order' => $count + ]); + + CollectionService::addItem( + $collection->id, + $status->id, + $count + ); + + $collection->updated_at = now(); $collection->save(); CollectionService::setCollection($collection->id, $collection); - }); - } + }); + } - NewStatusPipeline::dispatch($status); - Cache::forget('user:account:id:'.$profile->user_id); - Cache::forget('_api:statuses:recent_9:'.$profile->id); - 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($limitKey); + NewStatusPipeline::dispatch($status); + Cache::forget('user:account:id:'.$profile->user_id); + Cache::forget('_api:statuses:recent_9:'.$profile->id); + 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($limitKey); - return $status->url(); - } + return $status->url(); + } - public function storeText(Request $request) - { - abort_unless(config('exp.top'), 404); - $this->validate($request, [ - 'caption' => 'nullable|string|max:'.config('pixelfed.max_caption_length', 500), - 'cw' => 'nullable|boolean', - 'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10', - 'place' => 'nullable', - 'comments_disabled' => 'nullable', - 'tagged' => 'nullable', - ]); + public function storeText(Request $request) + { + abort_unless(config('exp.top'), 404); + $this->validate($request, [ + 'caption' => 'nullable|string|max:'.config('pixelfed.max_caption_length', 500), + 'cw' => 'nullable|boolean', + 'visibility' => 'required|string|in:public,private,unlisted|min:2|max:10', + 'place' => 'nullable', + 'comments_disabled' => 'nullable', + 'tagged' => 'nullable', + ]); - if(config('costar.enabled') == true) { - $blockedKeywords = config('costar.keyword.block'); - if($blockedKeywords !== null && $request->caption) { - $keywords = config('costar.keyword.block'); - foreach($keywords as $kw) { - if(Str::contains($request->caption, $kw) == true) { - abort(400, 'Invalid object'); - } - } - } - } + abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action'); - $user = Auth::user(); - $profile = $user->profile; - $visibility = $request->input('visibility'); - $status = new Status; - $place = $request->input('place'); - $cw = $request->input('cw'); - $tagged = $request->input('tagged'); + if(config('costar.enabled') == true) { + $blockedKeywords = config('costar.keyword.block'); + if($blockedKeywords !== null && $request->caption) { + $keywords = config('costar.keyword.block'); + foreach($keywords as $kw) { + if(Str::contains($request->caption, $kw) == true) { + abort(400, 'Invalid object'); + } + } + } + } - if($place && is_array($place)) { - $status->place_id = $place['id']; - } + $user = $request->user(); + $profile = $user->profile; + $visibility = $request->input('visibility'); + $status = new Status; + $place = $request->input('place'); + $cw = $request->input('cw'); + $tagged = $request->input('tagged'); - if($request->filled('comments_disabled')) { - $status->comments_disabled = (bool) $request->input('comments_disabled'); - } + if($place && is_array($place)) { + $status->place_id = $place['id']; + } - $status->caption = strip_tags($request->caption); - $status->profile_id = $profile->id; - $entities = []; - $visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility; - $cw = $profile->cw == true ? true : $cw; - $status->is_nsfw = $cw; - $status->visibility = $visibility; - $status->scope = $visibility; - $status->type = 'text'; - $status->rendered = Autolink::create()->autolink($status->caption); - $status->entities = json_encode(array_merge([ - 'timg' => [ - 'version' => 0, - 'bg_id' => 1, - 'font_size' => strlen($status->caption) <= 140 ? 'h1' : 'h3', - 'length' => strlen($status->caption), - ] - ], $entities), JSON_UNESCAPED_SLASHES); - $status->save(); + if($request->filled('comments_disabled')) { + $status->comments_disabled = (bool) $request->input('comments_disabled'); + } - foreach($tagged as $tg) { - $mt = new MediaTag; - $mt->status_id = $status->id; - $mt->media_id = $status->media->first()->id; - $mt->profile_id = $tg['id']; - $mt->tagged_username = $tg['name']; - $mt->is_public = true; - $mt->metadata = json_encode([ - '_v' => 1, - ]); - $mt->save(); - MediaTagService::set($mt->status_id, $mt->profile_id); - MediaTagService::sendNotification($mt); - } + $status->caption = strip_tags($request->caption); + $status->profile_id = $profile->id; + $entities = []; + $visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility; + $cw = $profile->cw == true ? true : $cw; + $status->is_nsfw = $cw; + $status->visibility = $visibility; + $status->scope = $visibility; + $status->type = 'text'; + $status->rendered = Autolink::create()->autolink($status->caption); + $status->entities = json_encode(array_merge([ + 'timg' => [ + 'version' => 0, + '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) { + $mt = new MediaTag; + $mt->status_id = $status->id; + $mt->media_id = $status->media->first()->id; + $mt->profile_id = $tg['id']; + $mt->tagged_username = $tg['name']; + $mt->is_public = true; + $mt->metadata = json_encode([ + '_v' => 1, + ]); + $mt->save(); + MediaTagService::set($mt->status_id, $mt->profile_id); + 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); + Cache::forget('user:account:id:'.$profile->user_id); + Cache::forget('_api:statuses:recent_9:'.$profile->id); + Cache::forget('profile:status_count:'.$profile->id); - return $status->url(); - } + return $status->url(); + } - public function mediaProcessingCheck(Request $request) - { - $this->validate($request, [ - 'id' => 'required|integer|min:1' - ]); + public function mediaProcessingCheck(Request $request) + { + $this->validate($request, [ + 'id' => 'required|integer|min:1' + ]); - $media = Media::whereUserId($request->user()->id) - ->whereNull('status_id') - ->findOrFail($request->input('id')); + abort_if($request->user()->has_roles && !UserRoleService::can('can-post', $request->user()->id), 403, 'Invalid permissions for this action'); - if(config('pixelfed.media_fast_process')) { - return [ - 'finished' => true - ]; - } + $media = Media::whereUserId($request->user()->id) + ->whereNull('status_id') + ->findOrFail($request->input('id')); - $finished = false; + if(config('pixelfed.media_fast_process')) { + return [ + 'finished' => true + ]; + } - switch ($media->mime) { - case 'image/jpeg': - case 'image/png': - case 'video/mp4': - $finished = config_cache('pixelfed.cloud_storage') ? (bool) $media->cdn_url : (bool) $media->processed_at; - break; + $finished = false; - default: - # code... - break; - } + switch ($media->mime) { + case 'image/jpeg': + case 'image/png': + case 'video/mp4': + $finished = config_cache('pixelfed.cloud_storage') ? (bool) $media->cdn_url : (bool) $media->processed_at; + break; - return [ - 'finished' => $finished - ]; - } + default: + # code... + break; + } - public function composeSettings(Request $request) - { - $uid = $request->user()->id; - $default = [ - 'default_license' => 1, - 'media_descriptions' => false, - 'max_altext_length' => config_cache('pixelfed.max_altext_length') - ]; - $settings = AccountService::settings($uid); - if(isset($settings['other']) && isset($settings['other']['scope'])) { - $s = $settings['compose_settings']; - $s['default_scope'] = $settings['other']['scope']; - $settings['compose_settings'] = $s; - } + return [ + 'finished' => $finished + ]; + } - return array_merge($default, $settings['compose_settings']); - } + 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'); - public function createPoll(Request $request) - { - $this->validate($request, [ - 'caption' => 'nullable|string|max:'.config('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' - ]); + $default = [ + 'default_license' => 1, + 'media_descriptions' => false, + 'max_altext_length' => config_cache('pixelfed.max_altext_length') + ]; + $settings = AccountService::settings($uid); + if(isset($settings['other']) && isset($settings['other']['scope'])) { + $s = $settings['compose_settings']; + $s['default_scope'] = $settings['other']['scope']; + $settings['compose_settings'] = $s; + } - abort_if(config('instance.polls.enabled') == false, 404, 'Polls not enabled'); + return array_merge($default, $settings['compose_settings']); + } - abort_if(Status::whereType('poll') - ->whereProfileId($request->user()->profile_id) - ->whereCaption($request->input('caption')) - ->where('created_at', '>', now()->subDays(2)) - ->exists() - , 422, 'Duplicate detected.'); + public function createPoll(Request $request) + { + $this->validate($request, [ + 'caption' => 'nullable|string|max:'.config('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' + ]); + 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'); - $status = new Status; - $status->profile_id = $request->user()->profile_id; - $status->caption = $request->input('caption'); - $status->rendered = Autolink::create()->autolink($status->caption); - $status->visibility = 'draft'; - $status->scope = 'draft'; - $status->type = 'poll'; - $status->local = true; - $status->save(); + abort_if(Status::whereType('poll') + ->whereProfileId($request->user()->profile_id) + ->whereCaption($request->input('caption')) + ->where('created_at', '>', now()->subDays(2)) + ->exists() + , 422, 'Duplicate detected.'); - $poll = new Poll; - $poll->status_id = $status->id; - $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) { - return 0; - })->toArray(); - $poll->save(); + $status = new Status; + $status->profile_id = $request->user()->profile_id; + $status->caption = $request->input('caption'); + $status->rendered = Autolink::create()->autolink($status->caption); + $status->visibility = 'draft'; + $status->scope = 'draft'; + $status->type = 'poll'; + $status->local = true; + $status->save(); - $status->visibility = $request->input('visibility'); - $status->scope = $request->input('visibility'); - $status->save(); + $poll = new Poll; + $poll->status_id = $status->id; + $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) { + return 0; + })->toArray(); + $poll->save(); - NewStatusPipeline::dispatch($status); + $status->visibility = $request->input('visibility'); + $status->scope = $request->input('visibility'); + $status->save(); - return ['url' => $status->url()]; - } + NewStatusPipeline::dispatch($status); + + return ['url' => $status->url()]; + } }