diff --git a/app/Http/Controllers/Admin/AdminSettingsController.php b/app/Http/Controllers/Admin/AdminSettingsController.php index b1f83440b..e141fc81b 100644 --- a/app/Http/Controllers/Admin/AdminSettingsController.php +++ b/app/Http/Controllers/Admin/AdminSettingsController.php @@ -9,14 +9,13 @@ use App\Profile; use App\Services\AccountService; use App\Services\AdminSettingsService; use App\Services\ConfigCacheService; +use App\Services\FilesystemService; use App\User; use App\Util\Site\Config; use Artisan; use Cache; use DB; use Illuminate\Http\Request; -use App\Services\Internal\BeagleService; -use App\Services\FilesystemService; trait AdminSettingsController { @@ -74,7 +73,7 @@ trait AdminSettingsController 'admin_account_id' => 'nullable', 'regs' => 'required|in:open,filtered,closed', 'account_migration' => 'nullable', - 'rule_delete' => 'sometimes' + 'rule_delete' => 'sometimes', ]); $orb = false; @@ -335,7 +334,7 @@ trait AdminSettingsController $regState = $openReg ? 'open' : ($curOnboarding ? 'filtered' : 'closed'); $accountMigration = (bool) config_cache('federation.migration'); $autoFollow = config_cache('account.autofollow_usernames'); - if(strlen($autoFollow) > 3) { + if (strlen($autoFollow) > 3) { $autoFollow = explode(',', $autoFollow); } @@ -347,7 +346,7 @@ trait AdminSettingsController public function settingsApiRulesAdd(Request $request) { $this->validate($request, [ - 'rule' => 'required|string|min:5|max:1000' + 'rule' => 'required|string|min:5|max:1000', ]); $rules = ConfigCacheService::get('app.rules'); @@ -357,7 +356,7 @@ trait AdminSettingsController } else { $json = json_decode($rules, true); $count = count($json); - if($count >= 30) { + if ($count >= 30) { return response()->json(['message' => 'Max rules limit reached, you can set up to 30 rules at a time.'], 400); } $json[] = $val; @@ -367,6 +366,7 @@ trait AdminSettingsController Cache::forget('api:v1:instance-data-response-v1'); Cache::forget('api:v2:instance-data-response-v2'); Config::refresh(); + return [$val]; } @@ -384,7 +384,7 @@ trait AdminSettingsController } else { $json = json_decode($rules, true); $idx = array_search($val, $json); - if($idx !== false) { + if ($idx !== false) { unset($json[$idx]); $json = array_values($json); } @@ -426,17 +426,18 @@ trait AdminSettingsController $username = $request->input('username'); $names = []; $existing = config_cache('account.autofollow_usernames'); - if($existing) { + if ($existing) { $names = explode(',', $existing); } - if(in_array($username, $names)) { + if (in_array($username, $names)) { $key = array_search($username, $names); - if($key !== false) { + if ($key !== false) { unset($names[$key]); } } ConfigCacheService::put('account.autofollow_usernames', implode(',', $names)); + return response()->json(['accounts' => array_values($names)]); } @@ -449,16 +450,26 @@ trait AdminSettingsController $username = $request->input('username'); $names = []; $existing = config_cache('account.autofollow_usernames'); - if($existing) { + if ($existing) { $names = explode(',', $existing); } - $p = Profile::whereUsername($username)->whereNotNull('user_id')->first(); + if ($existing && count($names)) { + if (count($names) >= 5) { + return response()->json(['message' => 'You can only add up to 5 accounts to be autofollowed.'], 400); + } + if (in_array(strtolower($username), array_map('strtolower', $names))) { + return response()->json(['message' => 'User already exists, please try again.'], 400); + } + } + + $p = User::whereUsername($username)->whereNull('status')->first(); if (! $p || in_array($p->username, $names)) { abort(404); } - array_push($names, strtolower($p->username)); + array_push($names, $p->username); ConfigCacheService::put('account.autofollow_usernames', implode(',', $names)); + return response()->json(['accounts' => array_values($names)]); } @@ -478,11 +489,11 @@ trait AdminSettingsController switch ($type) { case 'home': return $this->settingsApiUpdateHomeType($request); - break; + break; case 'landing': return $this->settingsApiUpdateLandingType($request); - break; + break; case 'posts': return $this->settingsApiUpdatePostsType($request); @@ -531,13 +542,13 @@ trait AdminSettingsController ConfigCacheService::put('pixelfed.open_registration', $regStatus === 'open'); ConfigCacheService::put('instance.curated_registration.enabled', $regStatus === 'filtered'); $cloudStorage = $request->boolean('cloud_storage'); - if($cloudStorage !== (bool) config_cache('pixelfed.cloud_storage')) { - if(!$cloudStorage) { + if ($cloudStorage !== (bool) config_cache('pixelfed.cloud_storage')) { + if (! $cloudStorage) { ConfigCacheService::put('pixelfed.cloud_storage', false); } else { $cloud_disk = config('filesystems.cloud'); $cloud_ready = ! empty(config('filesystems.disks.'.$cloud_disk.'.key')) && ! empty(config('filesystems.disks.'.$cloud_disk.'.secret')); - if(!$cloud_ready) { + if (! $cloud_ready) { return redirect()->back()->withErrors(['cloud_storage' => 'Must configure cloud storage before enabling!']); } else { ConfigCacheService::put('pixelfed.cloud_storage', true); @@ -555,6 +566,7 @@ trait AdminSettingsController Cache::forget('api:v2:instance-data-response-v2'); Cache::forget('api:v1:instance-data:contact'); Config::refresh(); + return $request->all(); } @@ -593,7 +605,7 @@ trait AdminSettingsController $mediaTypes = $request->input('media_types'); $mediaArray = explode(',', $mediaTypes); foreach ($mediaArray as $mediaType) { - if(!in_array($mediaType, ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'video/mp4'])) { + if (! in_array($mediaType, ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'video/mp4'])) { return redirect()->back()->withErrors(['media_types' => 'Invalid media type']); } } @@ -652,6 +664,7 @@ trait AdminSettingsController Cache::forget('api:v1:instance-data-response-v1'); Cache::forget('api:v2:instance-data-response-v2'); Config::refresh(); + return $res; } @@ -682,13 +695,13 @@ trait AdminSettingsController ConfigCacheService::put('instance.embed.profile', $request->boolean('allow_profile_embeds')); ConfigCacheService::put('federation.custom_emoji.enabled', $request->boolean('custom_emoji_enabled')); $captcha = $request->boolean('captcha_enabled'); - if($captcha) { + if ($captcha) { $secret = $request->input('captcha_secret'); $sitekey = $request->input('captcha_sitekey'); - if(config_cache('captcha.secret') != $secret && strpos($secret, '*') === false) { + if (config_cache('captcha.secret') != $secret && strpos($secret, '*') === false) { ConfigCacheService::put('captcha.secret', $secret); } - if(config_cache('captcha.sitekey') != $sitekey && strpos($sitekey, '*') === false) { + if (config_cache('captcha.sitekey') != $sitekey && strpos($sitekey, '*') === false) { ConfigCacheService::put('captcha.sitekey', $sitekey); } ConfigCacheService::put('captcha.active.login', $request->boolean('captcha_on_login')); @@ -717,6 +730,7 @@ trait AdminSettingsController Cache::forget('api:v1:instance-data-response-v1'); Cache::forget('api:v2:instance-data-response-v2'); Config::refresh(); + return $res; } @@ -726,11 +740,43 @@ trait AdminSettingsController 'require_email_verification' => 'required', 'enforce_account_limit' => 'required', 'admin_autofollow' => 'required', + 'admin_autofollow_accounts' => 'sometimes', 'max_user_blocks' => 'required', 'max_user_mutes' => 'required', 'max_domain_blocks' => 'required', ]); + $adminAutofollow = $request->boolean('admin_autofollow'); + $adminAutofollowAccounts = $request->input('admin_autofollow_accounts'); + if ($adminAutofollow) { + if ($request->filled('admin_autofollow_accounts')) { + $names = []; + $existing = config_cache('account.autofollow_usernames'); + if ($existing) { + $names = explode(',', $existing); + foreach (array_map('strtolower', $adminAutofollowAccounts) as $afc) { + if (in_array(strtolower($afc), array_map('strtolower', $names))) { + continue; + } + $names[] = $afc; + } + } else { + $names = $adminAutofollowAccounts; + } + if (! $names || count($names) == 0) { + return response()->json(['message' => 'You need to assign autofollow accounts before you can enable it.'], 400); + } + if (count($names) > 5) { + return response()->json(['message' => 'You can only add up to 5 accounts to be autofollowed.'.json_encode($names)], 400); + } + $autofollows = User::whereIn('username', $names)->whereNull('status')->pluck('username'); + $adminAutofollowAccounts = $autofollows->implode(','); + ConfigCacheService::put('account.autofollow_usernames', $adminAutofollowAccounts); + } else { + return response()->json(['message' => 'You need to assign autofollow accounts before you can enable it.'], 400); + } + } + ConfigCacheService::put('pixelfed.enforce_email_verification', $request->boolean('require_email_verification')); ConfigCacheService::put('pixelfed.enforce_account_limit', $request->boolean('enforce_account_limit')); ConfigCacheService::put('account.autofollow', $request->boolean('admin_autofollow')); @@ -741,6 +787,7 @@ trait AdminSettingsController 'require_email_verification' => $request->boolean('require_email_verification'), 'enforce_account_limit' => $request->boolean('enforce_account_limit'), 'admin_autofollow' => $request->boolean('admin_autofollow'), + 'admin_autofollow_accounts' => $adminAutofollowAccounts, 'max_user_blocks' => $request->input('max_user_blocks'), 'max_user_mutes' => $request->input('max_user_mutes'), 'max_domain_blocks' => $request->input('max_domain_blocks'), @@ -749,6 +796,7 @@ trait AdminSettingsController Cache::forget('api:v1:instance-data-response-v1'); Cache::forget('api:v2:instance-data-response-v2'); Config::refresh(); + return $res; } @@ -772,7 +820,7 @@ trait AdminSettingsController $res = [ 'primary_disk' => $request->input('primary_disk'), ]; - if($request->has('update_disk')) { + if ($request->has('update_disk')) { $res['disk_config'] = $request->input('disk_config'); $changes = []; $dkey = $request->input('disk_config.driver') === 's3' ? 'filesystems.disks.s3.' : 'filesystems.disks.spaces.'; @@ -785,33 +833,33 @@ trait AdminSettingsController $visibility = $request->input('disk_config.visibility'); $url = $request->input('disk_config.url'); $endpoint = $request->input('disk_config.endpoint'); - if(strpos($key, '*') === false && $key != config_cache($dkey . 'key')) { + if (strpos($key, '*') === false && $key != config_cache($dkey.'key')) { array_push($changes, 'key'); } else { - $ckey = config_cache($dkey . 'key'); + $ckey = config_cache($dkey.'key'); } - if(strpos($secret, '*') === false && $secret != config_cache($dkey . 'secret')) { + if (strpos($secret, '*') === false && $secret != config_cache($dkey.'secret')) { array_push($changes, 'secret'); } else { - $csecret = config_cache($dkey . 'secret'); + $csecret = config_cache($dkey.'secret'); } - if($region != config_cache($dkey . 'region')) { + if ($region != config_cache($dkey.'region')) { array_push($changes, 'region'); } - if($bucket != config_cache($dkey . 'bucket')) { + if ($bucket != config_cache($dkey.'bucket')) { array_push($changes, 'bucket'); } - if($visibility != config_cache($dkey . 'visibility')) { + if ($visibility != config_cache($dkey.'visibility')) { array_push($changes, 'visibility'); } - if($url != config_cache($dkey . 'url')) { + if ($url != config_cache($dkey.'url')) { array_push($changes, 'url'); } - if($endpoint != config_cache($dkey . 'endpoint')) { + if ($endpoint != config_cache($dkey.'endpoint')) { array_push($changes, 'endpoint'); } - if($changes && count($changes)) { + if ($changes && count($changes)) { $isValid = FilesystemService::getVerifyCredentials( $ckey ?? $key, $csecret ?? $secret, @@ -819,7 +867,7 @@ trait AdminSettingsController $bucket, $endpoint, ); - if(!$isValid) { + if (! $isValid) { return response()->json(['error' => true, 's3_vce' => true, 'message' => "
The S3/Spaces credentials you provided are invalid, or the bucket does not have the proper permissions.

Please check all fields and try again.

Any cloud storage configuration changes you made have NOT been saved due to invalid credentials."], 400); } } @@ -829,6 +877,7 @@ trait AdminSettingsController Cache::forget('api:v1:instance-data-response-v1'); Cache::forget('api:v2:instance-data-response-v2'); Config::refresh(); + return $res; } } diff --git a/app/Services/AdminSettingsService.php b/app/Services/AdminSettingsService.php new file mode 100644 index 000000000..57fb6e96f --- /dev/null +++ b/app/Services/AdminSettingsService.php @@ -0,0 +1,166 @@ + self::getFeatures(), + 'landing' => self::getLanding(), + 'branding' => self::getBranding(), + 'media' => self::getMedia(), + 'rules' => self::getRules(), + 'suggested_rules' => self::getSuggestedRules(), + 'users' => self::getUsers(), + 'posts' => self::getPosts(), + 'platform' => self::getPlatform(), + 'storage' => self::getStorage(), + ]; + } + + public static function getFeatures() + { + $cloud_storage = (bool) config_cache('pixelfed.cloud_storage'); + $cloud_disk = config('filesystems.cloud'); + $cloud_ready = ! empty(config('filesystems.disks.'.$cloud_disk.'.key')) && ! empty(config('filesystems.disks.'.$cloud_disk.'.secret')); + $openReg = (bool) config_cache('pixelfed.open_registration'); + $curOnboarding = (bool) config_cache('instance.curated_registration.enabled'); + $regState = $openReg ? 'open' : ($curOnboarding ? 'filtered' : 'closed'); + + return [ + 'registration_status' => $regState, + 'cloud_storage' => $cloud_ready && $cloud_storage, + 'activitypub_enabled' => (bool) config_cache('federation.activitypub.enabled'), + 'account_migration' => (bool) config_cache('federation.migration'), + 'mobile_apis' => (bool) config_cache('pixelfed.oauth_enabled'), + 'stories' => (bool) config_cache('instance.stories.enabled'), + 'instagram_import' => (bool) config_cache('pixelfed.import.instagram.enabled'), + 'autospam_enabled' => (bool) config_cache('pixelfed.bouncer.enabled'), + ]; + } + + public static function getLanding() + { + $availableAdmins = User::whereIsAdmin(true)->get(); + $currentAdmin = config_cache('instance.admin.pid'); + + return [ + 'admins' => $availableAdmins, + 'current_admin' => $currentAdmin, + 'show_directory' => (bool) config_cache('instance.landing.show_directory'), + 'show_explore' => (bool) config_cache('instance.landing.show_explore'), + ]; + } + + public static function getBranding() + { + return [ + 'name' => config_cache('app.name'), + 'short_description' => config_cache('app.short_description'), + 'long_description' => config_cache('app.description'), + ]; + } + + public static function getMedia() + { + return [ + 'max_photo_size' => config_cache('pixelfed.max_photo_size'), + 'max_album_length' => config_cache('pixelfed.max_album_length'), + 'image_quality' => config_cache('pixelfed.image_quality'), + 'media_types' => config_cache('pixelfed.media_types'), + 'optimize_image' => (bool) config_cache('pixelfed.optimize_image'), + 'optimize_video' => (bool) config_cache('pixelfed.optimize_video'), + ]; + } + + public static function getRules() + { + return config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : []; + } + + public static function getSuggestedRules() + { + return BeagleService::getDefaultRules(); + } + + public static function getUsers() + { + $autoFollow = config_cache('account.autofollow_usernames'); + if (strlen($autoFollow) >= 2) { + $autoFollow = explode(',', $autoFollow); + } else { + $autoFollow = []; + } + + return [ + 'require_email_verification' => (bool) config_cache('pixelfed.enforce_email_verification'), + 'enforce_account_limit' => (bool) config_cache('pixelfed.enforce_account_limit'), + 'max_account_size' => config_cache('pixelfed.max_account_size'), + 'admin_autofollow' => (bool) config_cache('account.autofollow'), + 'admin_autofollow_accounts' => $autoFollow, + 'max_user_blocks' => (int) config_cache('instance.user_filters.max_user_blocks'), + 'max_user_mutes' => (int) config_cache('instance.user_filters.max_user_mutes'), + 'max_domain_blocks' => (int) config_cache('instance.user_filters.max_domain_blocks'), + ]; + } + + public static function getPosts() + { + return [ + 'max_caption_length' => config_cache('pixelfed.max_caption_length'), + 'max_altext_length' => config_cache('pixelfed.max_altext_length'), + ]; + } + + public static function getPlatform() + { + return [ + 'allow_app_registration' => (bool) config_cache('pixelfed.allow_app_registration'), + 'app_registration_rate_limit_attempts' => config_cache('pixelfed.app_registration_rate_limit_attempts'), + 'app_registration_rate_limit_decay' => config_cache('pixelfed.app_registration_rate_limit_decay'), + 'app_registration_confirm_rate_limit_attempts' => config_cache('pixelfed.app_registration_confirm_rate_limit_attempts'), + 'app_registration_confirm_rate_limit_decay' => config_cache('pixelfed.app_registration_confirm_rate_limit_decay'), + 'allow_post_embeds' => (bool) config_cache('instance.embed.post'), + 'allow_profile_embeds' => (bool) config_cache('instance.embed.profile'), + 'captcha_enabled' => (bool) config_cache('captcha.enabled'), + 'captcha_on_login' => (bool) config_cache('captcha.active.login'), + 'captcha_on_register' => (bool) config_cache('captcha.active.register'), + 'captcha_secret' => Str::of(config_cache('captcha.secret'))->mask('*', 4, -4), + 'captcha_sitekey' => Str::of(config_cache('captcha.sitekey'))->mask('*', 4, -4), + 'custom_emoji_enabled' => (bool) config_cache('federation.custom_emoji.enabled'), + ]; + } + + public static function getStorage() + { + $cloud_storage = (bool) config_cache('pixelfed.cloud_storage'); + $cloud_disk = config('filesystems.cloud'); + $cloud_ready = ! empty(config('filesystems.disks.'.$cloud_disk.'.key')) && ! empty(config('filesystems.disks.'.$cloud_disk.'.secret')); + $primaryDisk = (bool) $cloud_ready && $cloud_storage; + $pkey = 'filesystems.disks.'.$cloud_disk.'.'; + $disk = [ + 'driver' => $cloud_disk, + 'key' => Str::of(config_cache($pkey.'key'))->mask('*', 0, -2), + 'secret' => Str::of(config_cache($pkey.'secret'))->mask('*', 0, -2), + 'region' => config_cache($pkey.'region'), + 'bucket' => config_cache($pkey.'bucket'), + 'visibility' => config_cache($pkey.'visibility'), + 'endpoint' => config_cache($pkey.'endpoint'), + 'url' => config_cache($pkey.'url'), + 'use_path_style_endpoint' => config_cache($pkey.'use_path_style_endpoint'), + ]; + + return [ + 'primary_disk' => $primaryDisk ? 'cloud' : 'local', + 'cloud_ready' => (bool) $cloud_ready, + 'cloud_disk' => $cloud_disk, + 'disk_config' => $disk, + ]; + } +}