diff --git a/app/Http/Controllers/Admin/AdminSettingsController.php b/app/Http/Controllers/Admin/AdminSettingsController.php index 8b5a26796..525f6114c 100644 --- a/app/Http/Controllers/Admin/AdminSettingsController.php +++ b/app/Http/Controllers/Admin/AdminSettingsController.php @@ -2,18 +2,18 @@ namespace App\Http\Controllers\Admin; -use Artisan, Cache, DB; -use Illuminate\Http\Request; -use Carbon\Carbon; -use App\{Comment, Like, Media, Page, Profile, Report, Status, User}; -use App\Models\InstanceActor; -use App\Http\Controllers\Controller; -use App\Util\Lexer\PrettyNumber; use App\Models\ConfigCache; +use App\Models\InstanceActor; +use App\Page; +use App\Profile; use App\Services\AccountService; use App\Services\ConfigCacheService; +use App\User; use App\Util\Site\Config; -use Illuminate\Support\Str; +use Artisan; +use Cache; +use DB; +use Illuminate\Http\Request; trait AdminSettingsController { @@ -21,7 +21,7 @@ trait AdminSettingsController { $cloud_storage = ConfigCacheService::get('pixelfed.cloud_storage'); $cloud_disk = config('filesystems.cloud'); - $cloud_ready = !empty(config('filesystems.disks.' . $cloud_disk . '.key')) && !empty(config('filesystems.disks.' . $cloud_disk . '.secret')); + $cloud_ready = ! empty(config('filesystems.disks.'.$cloud_disk.'.key')) && ! empty(config('filesystems.disks.'.$cloud_disk.'.secret')); $types = explode(',', ConfigCacheService::get('pixelfed.media_types')); $rules = ConfigCacheService::get('app.rules') ? json_decode(ConfigCacheService::get('app.rules'), true) : null; $jpeg = in_array('image/jpg', $types) || in_array('image/jpeg', $types); @@ -35,6 +35,7 @@ trait AdminSettingsController $openReg = (bool) config_cache('pixelfed.open_registration'); $curOnboarding = (bool) config_cache('instance.curated_registration.enabled'); $regState = $openReg ? 'open' : ($curOnboarding ? 'filtered' : 'closed'); + $accountMigration = (bool) config_cache('federation.migration'); return view('admin.settings.home', compact( 'jpeg', @@ -48,7 +49,8 @@ trait AdminSettingsController 'cloud_ready', 'availableAdmins', 'currentAdmin', - 'regState' + 'regState', + 'accountMigration' )); } @@ -67,41 +69,42 @@ trait AdminSettingsController 'type_mp4' => 'nullable', 'type_webp' => 'nullable', 'admin_account_id' => 'nullable', - 'regs' => 'required|in:open,filtered,closed' + 'regs' => 'required|in:open,filtered,closed', + 'account_migration' => 'nullable', ]); $orb = false; $cob = false; - switch($request->input('regs')) { + switch ($request->input('regs')) { case 'open': $orb = true; $cob = false; - break; + break; case 'filtered': $orb = false; $cob = true; - break; + break; case 'closed': $orb = false; $cob = false; - break; + break; } ConfigCacheService::put('pixelfed.open_registration', (bool) $orb); ConfigCacheService::put('instance.curated_registration.enabled', (bool) $cob); - if($request->filled('admin_account_id')) { + if ($request->filled('admin_account_id')) { ConfigCacheService::put('instance.admin.pid', $request->admin_account_id); Cache::forget('api:v1:instance-data:contact'); Cache::forget('api:v1:instance-data-response-v1'); } - if($request->filled('rule_delete')) { + if ($request->filled('rule_delete')) { $index = (int) $request->input('rule_delete'); $rules = ConfigCacheService::get('app.rules'); $json = json_decode($rules, true); - if(!$rules || empty($json)) { + if (! $rules || empty($json)) { return; } unset($json[$index]); @@ -109,6 +112,7 @@ trait AdminSettingsController ConfigCacheService::put('app.rules', $json); Cache::forget('api:v1:instance-data:rules'); Cache::forget('api:v1:instance-data-response-v1'); + return 200; } @@ -124,8 +128,8 @@ trait AdminSettingsController ]; foreach ($mimes as $key => $value) { - if($request->input($key) == 'on') { - if(!in_array($value, $media_types)) { + if ($request->input($key) == 'on') { + if (! in_array($value, $media_types)) { array_push($media_types, $value); } } else { @@ -133,7 +137,7 @@ trait AdminSettingsController } } - if($media_types !== $media_types_original) { + if ($media_types !== $media_types_original) { ConfigCacheService::put('pixelfed.media_types', implode(',', array_unique($media_types))); } @@ -147,15 +151,15 @@ trait AdminSettingsController 'account_limit' => 'pixelfed.max_account_size', 'custom_css' => 'uikit.custom.css', 'custom_js' => 'uikit.custom.js', - 'about_title' => 'about.title' + 'about_title' => 'about.title', ]; foreach ($keys as $key => $value) { $cc = ConfigCache::whereK($value)->first(); $val = $request->input($key); - if($cc && $cc->v != $val) { + if ($cc && $cc->v != $val) { ConfigCacheService::put($value, $val); - } else if(!empty($val)) { + } elseif (! empty($val)) { ConfigCacheService::put($value, $val); } } @@ -175,33 +179,34 @@ trait AdminSettingsController 'account_autofollow' => 'account.autofollow', 'show_directory' => 'instance.landing.show_directory', 'show_explore_feed' => 'instance.landing.show_explore', + 'account_migration' => 'federation.migration', ]; foreach ($bools as $key => $value) { $active = $request->input($key) == 'on'; - if($key == 'activitypub' && $active && !InstanceActor::exists()) { + if ($key == 'activitypub' && $active && ! InstanceActor::exists()) { Artisan::call('instance:actor'); } - if( $key == 'mobile_apis' && + if ($key == 'mobile_apis' && $active && - !file_exists(storage_path('oauth-public.key')) && - !file_exists(storage_path('oauth-private.key')) + ! file_exists(storage_path('oauth-public.key')) && + ! file_exists(storage_path('oauth-private.key')) ) { Artisan::call('passport:keys'); Artisan::call('route:cache'); } - if(config_cache($value) !== $active) { + if (config_cache($value) !== $active) { ConfigCacheService::put($value, (bool) $active); } } - if($request->filled('new_rule')) { + if ($request->filled('new_rule')) { $rules = ConfigCacheService::get('app.rules'); $val = $request->input('new_rule'); - if(!$rules) { + if (! $rules) { ConfigCacheService::put('app.rules', json_encode([$val])); } else { $json = json_decode($rules, true); @@ -212,13 +217,13 @@ trait AdminSettingsController Cache::forget('api:v1:instance-data-response-v1'); } - if($request->filled('account_autofollow_usernames')) { + if ($request->filled('account_autofollow_usernames')) { $usernames = explode(',', $request->input('account_autofollow_usernames')); $names = []; - foreach($usernames as $n) { + foreach ($usernames as $n) { $p = Profile::whereUsername($n)->first(); - if(!$p) { + if (! $p) { continue; } array_push($names, $p->username); @@ -236,6 +241,7 @@ trait AdminSettingsController { $path = storage_path('app/'.config('app.name')); $files = is_dir($path) ? new \DirectoryIterator($path) : []; + return view('admin.settings.backups', compact('files')); } @@ -247,6 +253,7 @@ trait AdminSettingsController public function settingsStorage(Request $request) { $storage = []; + return view('admin.settings.storage', compact('storage')); } @@ -258,6 +265,7 @@ trait AdminSettingsController public function settingsPages(Request $request) { $pages = Page::orderByDesc('updated_at')->paginate(10); + return view('admin.pages.home', compact('pages')); } @@ -275,30 +283,31 @@ trait AdminSettingsController ]; switch (config('database.default')) { case 'pgsql': - $exp = DB::raw('select version();'); - $expQuery = $exp->getValue(DB::connection()->getQueryGrammar()); - $sys['database'] = [ - 'name' => 'Postgres', - 'version' => explode(' ', DB::select($expQuery)[0]->version)[1] - ]; - break; + $exp = DB::raw('select version();'); + $expQuery = $exp->getValue(DB::connection()->getQueryGrammar()); + $sys['database'] = [ + 'name' => 'Postgres', + 'version' => explode(' ', DB::select($expQuery)[0]->version)[1], + ]; + break; case 'mysql': - $exp = DB::raw('select version()'); - $expQuery = $exp->getValue(DB::connection()->getQueryGrammar()); - $sys['database'] = [ - 'name' => 'MySQL', - 'version' => DB::select($expQuery)[0]->{'version()'} - ]; - break; + $exp = DB::raw('select version()'); + $expQuery = $exp->getValue(DB::connection()->getQueryGrammar()); + $sys['database'] = [ + 'name' => 'MySQL', + 'version' => DB::select($expQuery)[0]->{'version()'}, + ]; + break; default: - $sys['database'] = [ - 'name' => 'Unknown', - 'version' => '?' - ]; - break; + $sys['database'] = [ + 'name' => 'Unknown', + 'version' => '?', + ]; + break; } + return view('admin.settings.system', compact('sys')); } } diff --git a/app/Http/Controllers/ProfileMigrationController.php b/app/Http/Controllers/ProfileMigrationController.php index ee2fae918..d62b96175 100644 --- a/app/Http/Controllers/ProfileMigrationController.php +++ b/app/Http/Controllers/ProfileMigrationController.php @@ -23,6 +23,9 @@ class ProfileMigrationController extends Controller public function index(Request $request) { abort_if((bool) config_cache('federation.activitypub.enabled') === false, 404); + if ((bool) config_cache('federation.migration') === false) { + return redirect(route('help.account-migration')); + } $hasExistingMigration = ProfileMigration::whereProfileId($request->user()->profile_id) ->where('created_at', '>', now()->subDays(30)) ->exists(); diff --git a/app/Http/Requests/ProfileMigrationStoreRequest.php b/app/Http/Requests/ProfileMigrationStoreRequest.php index de9e31b8a..9b071511a 100644 --- a/app/Http/Requests/ProfileMigrationStoreRequest.php +++ b/app/Http/Requests/ProfileMigrationStoreRequest.php @@ -15,6 +15,10 @@ class ProfileMigrationStoreRequest extends FormRequest */ public function authorize(): bool { + if ((bool) config_cache('federation.activitypub.enabled') === false || + (bool) config_cache('federation.migration') === false) { + return false; + } if (! $this->user() || $this->user()->status) { return false; } diff --git a/app/Services/ConfigCacheService.php b/app/Services/ConfigCacheService.php index 65d9a8337..bf34f5dcc 100644 --- a/app/Services/ConfigCacheService.php +++ b/app/Services/ConfigCacheService.php @@ -2,127 +2,129 @@ namespace App\Services; -use Cache; -use Config; use App\Models\ConfigCache as ConfigCacheModel; +use Cache; class ConfigCacheService { - const CACHE_KEY = 'config_cache:_v0-key:'; + const CACHE_KEY = 'config_cache:_v0-key:'; - public static function get($key) - { - $cacheKey = self::CACHE_KEY . $key; - $ttl = now()->addHours(12); - if(!config('instance.enable_cc')) { - return config($key); - } + public static function get($key) + { + $cacheKey = self::CACHE_KEY.$key; + $ttl = now()->addHours(12); + if (! config('instance.enable_cc')) { + return config($key); + } - return Cache::remember($cacheKey, $ttl, function() use($key) { + return Cache::remember($cacheKey, $ttl, function () use ($key) { - $allowed = [ - 'app.name', - 'app.short_description', - 'app.description', - 'app.rules', + $allowed = [ + 'app.name', + 'app.short_description', + 'app.description', + 'app.rules', - 'pixelfed.max_photo_size', - 'pixelfed.max_album_length', - 'pixelfed.image_quality', - 'pixelfed.media_types', + 'pixelfed.max_photo_size', + 'pixelfed.max_album_length', + 'pixelfed.image_quality', + 'pixelfed.media_types', - 'pixelfed.open_registration', - 'federation.activitypub.enabled', - 'instance.stories.enabled', - 'pixelfed.oauth_enabled', - 'pixelfed.import.instagram.enabled', - 'pixelfed.bouncer.enabled', + 'pixelfed.open_registration', + 'federation.activitypub.enabled', + 'instance.stories.enabled', + 'pixelfed.oauth_enabled', + 'pixelfed.import.instagram.enabled', + 'pixelfed.bouncer.enabled', - 'pixelfed.enforce_email_verification', - 'pixelfed.max_account_size', - 'pixelfed.enforce_account_limit', + 'pixelfed.enforce_email_verification', + 'pixelfed.max_account_size', + 'pixelfed.enforce_account_limit', - 'uikit.custom.css', - 'uikit.custom.js', - 'uikit.show_custom.css', - 'uikit.show_custom.js', - 'about.title', + 'uikit.custom.css', + 'uikit.custom.js', + 'uikit.show_custom.css', + 'uikit.show_custom.js', + 'about.title', - 'pixelfed.cloud_storage', + 'pixelfed.cloud_storage', - 'account.autofollow', - 'account.autofollow_usernames', - 'config.discover.features', + 'account.autofollow', + 'account.autofollow_usernames', + 'config.discover.features', - 'instance.has_legal_notice', - 'instance.avatar.local_to_cloud', + 'instance.has_legal_notice', + 'instance.avatar.local_to_cloud', - 'pixelfed.directory', - 'app.banner_image', - 'pixelfed.directory.submission-key', - 'pixelfed.directory.submission-ts', - 'pixelfed.directory.has_submitted', - 'pixelfed.directory.latest_response', - 'pixelfed.directory.is_synced', - 'pixelfed.directory.testimonials', + 'pixelfed.directory', + 'app.banner_image', + 'pixelfed.directory.submission-key', + 'pixelfed.directory.submission-ts', + 'pixelfed.directory.has_submitted', + 'pixelfed.directory.latest_response', + 'pixelfed.directory.is_synced', + 'pixelfed.directory.testimonials', - 'instance.landing.show_directory', - 'instance.landing.show_explore', - 'instance.admin.pid', - 'instance.banner.blurhash', + 'instance.landing.show_directory', + 'instance.landing.show_explore', + 'instance.admin.pid', + 'instance.banner.blurhash', - 'autospam.nlp.enabled', + 'autospam.nlp.enabled', - 'instance.curated_registration.enabled', - // 'system.user_mode' - ]; + 'instance.curated_registration.enabled', - if(!config('instance.enable_cc')) { - return config($key); - } + 'federation.migration', + // 'system.user_mode' + ]; - if(!in_array($key, $allowed)) { - return config($key); - } + if (! config('instance.enable_cc')) { + return config($key); + } - $v = config($key); - $c = ConfigCacheModel::where('k', $key)->first(); + if (! in_array($key, $allowed)) { + return config($key); + } - if($c) { - return $c->v ?? config($key); - } + $v = config($key); + $c = ConfigCacheModel::where('k', $key)->first(); - if(!$v) { - return; - } + if ($c) { + return $c->v ?? config($key); + } - $cc = new ConfigCacheModel; - $cc->k = $key; - $cc->v = $v; - $cc->save(); + if (! $v) { + return; + } - return $v; - }); - } + $cc = new ConfigCacheModel; + $cc->k = $key; + $cc->v = $v; + $cc->save(); - public static function put($key, $val) - { - $exists = ConfigCacheModel::whereK($key)->first(); + return $v; + }); + } - if($exists) { - $exists->v = $val; - $exists->save(); - Cache::put(self::CACHE_KEY . $key, $val, now()->addHours(12)); - return self::get($key); - } + public static function put($key, $val) + { + $exists = ConfigCacheModel::whereK($key)->first(); - $cc = new ConfigCacheModel; - $cc->k = $key; - $cc->v = $val; - $cc->save(); + if ($exists) { + $exists->v = $val; + $exists->save(); + Cache::put(self::CACHE_KEY.$key, $val, now()->addHours(12)); - Cache::put(self::CACHE_KEY . $key, $val, now()->addHours(12)); + return self::get($key); + } - return self::get($key); - } + $cc = new ConfigCacheModel; + $cc->k = $key; + $cc->v = $val; + $cc->save(); + + Cache::put(self::CACHE_KEY.$key, $val, now()->addHours(12)); + + return self::get($key); + } } diff --git a/config/federation.php b/config/federation.php index 21415dad3..3d7a7bb30 100644 --- a/config/federation.php +++ b/config/federation.php @@ -57,4 +57,6 @@ return [ // max size in bytes, default is 2mb 'max_size' => env('CUSTOM_EMOJI_MAX_SIZE', 2000000), ], + + 'migration' => env('PF_ACCT_MIGRATION_ENABLED', true), ]; diff --git a/resources/views/admin/settings/home.blade.php b/resources/views/admin/settings/home.blade.php index 6201872c8..c2254f700 100644 --- a/resources/views/admin/settings/home.blade.php +++ b/resources/views/admin/settings/home.blade.php @@ -103,6 +103,16 @@
ActivityPub federation, compatible with Pixelfed, Mastodon and other projects.
+Allow local accounts to migrate to other local or remote accounts.
+ @else +ActivityPub Required Allow local accounts to migrate to other local or remote accounts.
+ @endif + {{--To redirect this account to a different one (where supported).
Storage Usage
diff --git a/resources/views/settings/migration/index.blade.php b/resources/views/settings/migration/index.blade.php index c74f13219..3407e55bc 100644 --- a/resources/views/settings/migration/index.blade.php +++ b/resources/views/settings/migration/index.blade.php @@ -40,7 +40,8 @@ @if($hasExistingMigration)You have migrated your account already.
+You have migrated your account already.
+You can only migrate your account once per 30 days. If you want to migrate your followers back to this account, follow this process in reverse.
Account Migration is not available on this server.
+Account Migration is a feature that allows users to move their account followers from one Pixelfed instance (server) to another.
+This can be useful if a user wants to switch to a different instance due to preferences for its community, policies, or features.
+@endsection diff --git a/routes/web.php b/routes/web.php index 75c4a4adc..87ac4b564 100644 --- a/routes/web.php +++ b/routes/web.php @@ -314,6 +314,7 @@ Route::domain(config('pixelfed.domain.app'))->middleware(['validemail', 'twofact Route::view('parental-controls', 'site.help.parental-controls'); Route::view('email-confirmation-issues', 'site.help.email-confirmation-issues')->name('help.email-confirmation-issues'); Route::view('curated-onboarding', 'site.help.curated-onboarding')->name('help.curated-onboarding'); + Route::view('account-migration', 'site.help.account-migration')->name('help.account-migration'); }); Route::get('newsroom/{year}/{month}/{slug}', 'NewsroomController@show'); Route::get('newsroom/archive', 'NewsroomController@archive');