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.

+
+ + +
+ @if((bool) config_cache('federation.activitypub.enabled')) +

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 + {{--
diff --git a/resources/views/settings/home.blade.php b/resources/views/settings/home.blade.php index 5cad17f3b..1ecd6bca8 100644 --- a/resources/views/settings/home.blade.php +++ b/resources/views/settings/home.blade.php @@ -88,6 +88,7 @@
+ @if((bool) config_cache('federation.activitypub.enabled'))
@@ -96,6 +97,7 @@
+ @if((bool) config_cache('federation.migration'))
@@ -103,6 +105,8 @@

To redirect this account to a different one (where supported).

+ @endif + @endif @if(config_cache('pixelfed.enforce_account_limit'))

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.

@else diff --git a/resources/views/site/help/account-migration.blade.php b/resources/views/site/help/account-migration.blade.php new file mode 100644 index 000000000..eda63742c --- /dev/null +++ b/resources/views/site/help/account-migration.blade.php @@ -0,0 +1,19 @@ +@extends('site.help.partial.template', ['breadcrumb'=>'Account Migration']) + +@section('section') + +
+

Account Migration

+
+ +
+ + @if((bool) config_cache('federation.migration') === false) +
+

Account Migration is not available on this server.

+
+ @endif + +

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');