Add disable_embeds setting, and fix cache invalidation in other settings

This commit is contained in:
Daniel Supernault 2024-06-04 03:46:06 -06:00
parent e97b5b7a69
commit c5e7e91777
No known key found for this signature in database
GPG key ID: 23740873EE6F76A1
8 changed files with 475 additions and 383 deletions

View file

@ -194,19 +194,7 @@ class ApiV1Controller extends Controller
];
if ($request->has(self::PF_API_ENTITY_KEY)) {
$settings = $user->settings;
$other = array_merge(AccountService::defaultSettings()['other'], $settings->other ?? []);
$res['settings'] = [
'reduce_motion' => (bool) $settings->reduce_motion,
'high_contrast_mode' => (bool) $settings->high_contrast_mode,
'video_autoplay' => (bool) $settings->video_autoplay,
'media_descriptions' => (bool) $settings->media_descriptions,
'crawlable' => (bool) $settings->crawlable,
'show_profile_follower_count' => (bool) $settings->show_profile_follower_count,
'show_profile_following_count' => (bool) $settings->show_profile_following_count,
'public_dm' => (bool) $settings->public_dm,
'disable_embeds' => (bool) $other['disable_embeds'],
];
$res['settings'] = AccountService::getAccountSettings($user->profile_id);
}
return $this->json($res);
@ -466,6 +454,7 @@ class ApiV1Controller extends Controller
Cache::forget('pf:acct-trans:hideFollowing:'.$profile->id);
Cache::forget('pf:acct-trans:hideFollowers:'.$profile->id);
AccountService::del($user->profile_id);
AccountService::forgetAccountSettings($profile->id);
}
if ($syncLicenses && $licenseChanged) {

View file

@ -361,7 +361,7 @@ class ProfileController extends Controller
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
}
if (AccountService::canEmbed($profile->user_id) == false) {
if (AccountService::canEmbed($profile->id) == false) {
return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']);
}

View file

@ -4,21 +4,17 @@ namespace App\Http\Controllers\Settings;
use App\AccountLog;
use App\EmailVerification;
use App\Mail\PasswordChange;
use App\Media;
use App\Profile;
use App\User;
use App\UserFilter;
use App\Services\AccountService;
use App\Services\PronounService;
use App\Util\Lexer\Autolink;
use App\Util\Lexer\PrettyNumber;
use Auth;
use Cache;
use DB;
use Illuminate\Http\Request;
use Mail;
use Purify;
use App\Mail\PasswordChange;
use Illuminate\Http\Request;
use App\Services\AccountService;
use App\Services\PronounService;
trait HomeSettings
{
@ -44,7 +40,7 @@ trait HomeSettings
'bio' => 'nullable|string|max:'.config('pixelfed.max_bio_length'),
'website' => 'nullable|url',
'language' => 'nullable|string|min:2|max:5',
'pronouns' => 'nullable|array|max:4'
'pronouns' => 'nullable|array|max:4',
]);
$changes = false;
@ -102,7 +98,9 @@ trait HomeSettings
$user->save();
$profile->save();
Cache::forget('user:account:id:'.$user->id);
AccountService::forgetAccountSettings($profile->id);
AccountService::del($profile->id);
return redirect('/settings/home')->with('status', 'Profile successfully updated!');
}
@ -144,6 +142,7 @@ trait HomeSettings
$log->save();
Mail::to($request->user())->send(new PasswordChange($user));
return redirect('/settings/home')->with('status', 'Password successfully updated!');
} else {
return redirect()->back()->with('error', 'There was an error with your request! Please try again.');

View file

@ -4,6 +4,7 @@ namespace App\Http\Controllers\Settings;
use App\Follower;
use App\Profile;
use App\Services\AccountService;
use App\Services\RelationshipService;
use App\UserFilter;
use Auth;
@ -19,7 +20,13 @@ trait PrivacySettings
$settings = $user->settings;
$profile = $user->profile;
$is_private = $profile->is_private;
$cachedSettings = AccountService::getAccountSettings($profile->id);
$settings['is_private'] = (bool) $is_private;
if ($cachedSettings && isset($cachedSettings['disable_embeds'])) {
$settings['disable_embeds'] = (bool) $cachedSettings['disable_embeds'];
} else {
$settings['disable_embeds'] = false;
}
return view('settings.privacy', compact('settings', 'profile'));
}
@ -28,6 +35,7 @@ trait PrivacySettings
{
$settings = $request->user()->settings;
$profile = $request->user()->profile;
$other = $settings->other;
$fields = [
'is_private',
'crawlable',
@ -42,6 +50,16 @@ trait PrivacySettings
$profile->is_suggestable = $request->input('is_suggestable') == 'on';
$profile->save();
if ($request->has('disable_embeds')) {
$other['disable_embeds'] = true;
$settings->other = $other;
$settings->save();
} else {
$other['disable_embeds'] = false;
$settings->other = $other;
$settings->save();
}
foreach ($fields as $field) {
$form = $request->input($field);
if ($field == 'is_private') {
@ -91,6 +109,7 @@ trait PrivacySettings
Cache::forget('pf:acct-trans:hideFollowers:'.$pid);
Cache::forget('pfc:cached-user:wt:'.strtolower($profile->username));
Cache::forget('pfc:cached-user:wot:'.strtolower($profile->username));
AccountService::forgetAccountSettings($profile->id);
return redirect(route('settings.privacy'))->with('status', 'Settings successfully updated!');
}

View file

@ -2,34 +2,30 @@
namespace App\Http\Controllers;
use App\AccountLog;
use App\Following;
use App\ProfileSponsor;
use App\Report;
use App\UserFilter;
use App\UserSetting;
use Auth, Cookie, DB, Cache, Purify;
use Illuminate\Support\Facades\Redis;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use App\Http\Controllers\Settings\{
ExportSettings,
LabsSettings,
HomeSettings,
PrivacySettings,
RelationshipSettings,
SecuritySettings
};
use App\Http\Controllers\Settings\ExportSettings;
use App\Http\Controllers\Settings\HomeSettings;
use App\Http\Controllers\Settings\LabsSettings;
use App\Http\Controllers\Settings\PrivacySettings;
use App\Http\Controllers\Settings\RelationshipSettings;
use App\Http\Controllers\Settings\SecuritySettings;
use App\Jobs\DeletePipeline\DeleteAccountPipeline;
use App\Jobs\MediaPipeline\MediaSyncLicensePipeline;
use App\ProfileSponsor;
use App\Services\AccountService;
use App\UserSetting;
use Auth;
use Cache;
use Carbon\Carbon;
use Cookie;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redis;
use Illuminate\Support\Str;
class SettingsController extends Controller
{
use ExportSettings,
LabsSettings,
HomeSettings,
LabsSettings,
PrivacySettings,
RelationshipSettings,
SecuritySettings;
@ -48,7 +44,8 @@ class SettingsController extends Controller
public function accessibilityStore(Request $request)
{
$settings = Auth::user()->settings;
$user = $request->user();
$settings = $user->settings;
$fields = [
'compose_media_descriptions',
'reduce_motion',
@ -65,6 +62,7 @@ class SettingsController extends Controller
}
$settings->save();
}
AccountService::forgetAccountSettings($user->profile_id);
return redirect(route('settings.accessibility'))->with('status', 'Settings successfully updated!');
}
@ -115,6 +113,7 @@ class SettingsController extends Controller
$profile->save();
Auth::logout();
Cache::forget('profiles:private');
return redirect('/');
}
@ -122,6 +121,7 @@ class SettingsController extends Controller
{
$user = Auth::user();
abort_if($user->is_admin, 403);
return view('settings.remove.permanent');
}
@ -147,19 +147,21 @@ class SettingsController extends Controller
AccountService::del($profile->id);
Auth::logout();
DeleteAccountPipeline::dispatch($user)->onQueue('low');
return redirect('/');
}
public function requestFullExport(Request $request)
{
$user = Auth::user();
return view('settings.export.show');
}
public function metroDarkMode(Request $request)
{
$this->validate($request, [
'mode' => 'required|string|in:light,dark'
'mode' => 'required|string|in:light,dark',
]);
$mode = $request->input('mode');
@ -178,10 +180,11 @@ class SettingsController extends Controller
$default = [
'patreon' => null,
'liberapay' => null,
'opencollective' => null
'opencollective' => null,
];
$sponsors = ProfileSponsor::whereProfileId(Auth::user()->profile->id)->first();
$sponsors = $sponsors ? json_decode($sponsors->sponsors, true) : $default;
return view('settings.sponsor', compact('sponsors'));
}
@ -190,7 +193,7 @@ class SettingsController extends Controller
$this->validate($request, [
'patreon' => 'nullable|string',
'liberapay' => 'nullable|string',
'opencollective' => 'nullable|string'
'opencollective' => 'nullable|string',
]);
$patreon = Str::startsWith($request->input('patreon'), 'https://') ?
@ -216,15 +219,16 @@ class SettingsController extends Controller
$res = [
'patreon' => $patreon,
'liberapay' => $liberapay,
'opencollective' => $opencollective
'opencollective' => $opencollective,
];
$sponsors = ProfileSponsor::firstOrCreate([
'profile_id' => Auth::user()->profile_id ?? Auth::user()->profile->id
'profile_id' => Auth::user()->profile_id ?? Auth::user()->profile->id,
]);
$sponsors->sponsors = json_encode($res);
$sponsors->save();
$sponsors = $res;
return redirect(route('settings'))->with('status', 'Sponsor settings successfully updated!');
}
@ -235,20 +239,21 @@ class SettingsController extends Controller
$top = Redis::zscore('pf:tl:top', $pid) != false;
$replies = Redis::zscore('pf:tl:replies', $pid) != false;
$userSettings = UserSetting::firstOrCreate([
'user_id' => $uid
'user_id' => $uid,
]);
if (! $userSettings || ! $userSettings->other) {
$userSettings = [
'enable_reblogs' => false,
'photo_reblogs_only' => false
'photo_reblogs_only' => false,
];
} else {
$userSettings = array_merge([
'enable_reblogs' => false,
'photo_reblogs_only' => false
'photo_reblogs_only' => false,
],
$userSettings->other);
}
return view('settings.timeline', compact('top', 'replies', 'userSettings'));
}
@ -258,12 +263,12 @@ class SettingsController extends Controller
$uid = $request->user()->id;
$this->validate($request, [
'enable_reblogs' => 'sometimes',
'photo_reblogs_only' => 'sometimes'
'photo_reblogs_only' => 'sometimes',
]);
Redis::zrem('pf:tl:top', $pid);
Redis::zrem('pf:tl:replies', $pid);
$userSettings = UserSetting::firstOrCreate([
'user_id' => $uid
'user_id' => $uid,
]);
if ($userSettings->other) {
$other = $userSettings->other;
@ -275,6 +280,7 @@ class SettingsController extends Controller
}
$userSettings->other = $other;
$userSettings->save();
return redirect(route('settings'))->with('status', 'Timeline settings successfully updated!');
}
@ -285,8 +291,9 @@ class SettingsController extends Controller
is_string($setting->compose_settings) ? json_decode($setting->compose_settings, true) : $setting->compose_settings
) : [
'default_license' => null,
'media_descriptions' => false
'media_descriptions' => false,
];
return view('settings.media', compact('compose'));
}
@ -295,7 +302,7 @@ class SettingsController extends Controller
$this->validate($request, [
'default' => 'required|int|min:1|max:16',
'sync' => 'nullable',
'media_descriptions' => 'nullable'
'media_descriptions' => 'nullable',
]);
$license = $request->input('default');
@ -311,6 +318,7 @@ class SettingsController extends Controller
$key = 'pf:settings:mls_recently:'.$uid;
if (Cache::get($key) == 2) {
$msg = 'You can only sync licenses twice per 24 hours. Try again later.';
return redirect(route('settings'))
->with('error', $msg);
}
@ -336,11 +344,10 @@ class SettingsController extends Controller
$val = Cache::has($key) ? 2 : 1;
Cache::put($key, $val, 86400);
MediaSyncLicensePipeline::dispatch($uid, $license);
return redirect(route('settings'))->with('status', 'Media licenses successfully synced! It may take a few minutes to take effect for every post.');
}
return redirect(route('settings'))->with('status', 'Media settings successfully updated!');
}
}

View file

@ -135,6 +135,14 @@ class StatusController extends Controller
return response($content)->header('X-Frame-Options', 'ALLOWALL');
}
$embedCheck = AccountService::canEmbed($profile['id']);
if (! $embedCheck) {
$content = view('status.embed-removed');
return response($content)->header('X-Frame-Options', 'ALLOWALL');
}
$aiCheck = Cache::remember('profile:ai-check:spam-login:'.$profile['id'], 3600, function () use ($profile) {
$user = Profile::find($profile['id']);
if (! $user) {

View file

@ -2,23 +2,25 @@
namespace App\Services;
use Cache;
use App\Models\UserDomainBlock;
use App\Profile;
use App\Status;
use App\Transformer\Api\AccountTransformer;
use App\User;
use App\UserSetting;
use App\Models\UserDomainBlock;
use App\Transformer\Api\AccountTransformer;
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
use Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use \NumberFormatter;
use League\Fractal;
use League\Fractal\Serializer\ArraySerializer;
use NumberFormatter;
class AccountService
{
const CACHE_KEY = 'pf:services:account:';
const CACHE_PF_ACCT_SETTINGS_KEY = 'pf:services:account-settings:';
public static function get($id, $softFail = false)
{
$res = Cache::remember(self::CACHE_KEY.$id, 43200, function () use ($id) {
@ -29,12 +31,14 @@ class AccountService
return null;
}
$resource = new Fractal\Resource\Item($profile, new AccountTransformer());
return $fractal->createData($resource)->toArray();
});
if (! $res) {
return $softFail ? null : abort(404);
}
return $res;
}
@ -74,6 +78,7 @@ class AccountService
public static function del($id)
{
Cache::forget('pf:activitypub:user-object:by-id:'.$id);
return Cache::forget(self::CACHE_KEY.$id);
}
@ -84,6 +89,7 @@ class AccountService
if (! $settings) {
return self::defaultSettings();
}
return collect($settings)
->filter(function ($item, $key) {
return in_array($key, array_keys(self::defaultSettings())) == true;
@ -92,22 +98,65 @@ class AccountService
if ($key == 'compose_settings') {
$cs = self::defaultSettings()['compose_settings'];
$ms = is_array($item) ? $item : [];
return array_merge($cs, $ms);
}
if ($key == 'other') {
$other = self::defaultSettings()['other'];
$mo = is_array($item) ? $item : [];
return array_merge($other, $mo);
}
return $item;
});
});
}
public static function getAccountSettings($pid)
{
$key = self::CACHE_PF_ACCT_SETTINGS_KEY.$pid;
return Cache::remember($key, 14400, function () use ($pid) {
$user = User::with('profile')->whereProfileId($pid)->whereNull('status')->first();
if (! $user) {
return [];
}
$settings = $user->settings;
$other = array_merge(self::defaultSettings()['other'], $settings->other ?? []);
return [
'reduce_motion' => (bool) $settings->reduce_motion,
'high_contrast_mode' => (bool) $settings->high_contrast_mode,
'video_autoplay' => (bool) $settings->video_autoplay,
'media_descriptions' => (bool) $settings->media_descriptions,
'crawlable' => (bool) $settings->crawlable,
'show_profile_follower_count' => (bool) $settings->show_profile_follower_count,
'show_profile_following_count' => (bool) $settings->show_profile_following_count,
'public_dm' => (bool) $settings->public_dm,
'disable_embeds' => (bool) $other['disable_embeds'],
'show_atom' => (bool) $settings->show_atom,
'is_suggestable' => (bool) $user->profile->is_suggestable,
'indexable' => (bool) $user->profile->indexable,
];
});
}
public static function forgetAccountSettings($pid)
{
return Cache::forget(self::CACHE_PF_ACCT_SETTINGS_KEY.$pid);
}
public static function canEmbed($id)
{
return self::settings($id)['other']['disable_embeds'] == false;
$res = self::getAccountSettings($id);
if (! $res || ! isset($res['disable_embeds'])) {
return false;
}
return ! $res['disable_embeds'];
}
public static function defaultSettings()
@ -123,7 +172,7 @@ class AccountService
'compose_settings' => [
'default_scope' => 'public',
'default_license' => 1,
'media_descriptions' => false
'media_descriptions' => false,
],
'other' => [
'advanced_atom' => false,
@ -134,7 +183,7 @@ class AccountService
'hide_groups' => false,
'hide_stories' => false,
'disable_cw' => false,
]
],
];
}
@ -162,12 +211,14 @@ class AccountService
$profile->save();
Cache::put($key, 1, 900);
return true;
}
public static function usernameToId($username)
{
$key = self::CACHE_KEY.'u2id:'.hash('sha256', $username);
return Cache::remember($key, 14400, function () use ($username) {
$s = Str::of($username);
if ($s->contains('@') && ! $s->startsWith('@')) {
@ -179,6 +230,7 @@ class AccountService
if (! $profile) {
return null;
}
return (string) $profile->id;
});
}
@ -199,6 +251,7 @@ class AccountService
if ($settings) {
return $settings->show_profile_follower_count == false;
}
return false;
});
}
@ -219,22 +272,27 @@ class AccountService
if ($settings) {
return $settings->show_profile_following_count == false;
}
return false;
});
}
public static function setLastActive($id = false)
{
if(!$id) { return; }
if (! $id) {
return;
}
$key = 'user:last_active_at:id:'.$id;
if (! Cache::has($key)) {
$user = User::find($id);
if(!$user) { return; }
if (! $user) {
return;
}
$user->last_active_at = now();
$user->save();
Cache::put($key, 1, 14400);
}
return;
}
public static function blocksDomain($pid, $domain = false)
@ -246,9 +304,10 @@ class AccountService
return UserDomainBlock::whereProfileId($pid)->whereDomain($domain)->exists();
}
public static function formatNumber($num) {
public static function formatNumber($num)
{
if (! $num || $num < 1) {
return "0";
return '0';
}
$num = intval($num);
$formatter = new NumberFormatter('en_US', NumberFormatter::DECIMAL);
@ -269,7 +328,9 @@ class AccountService
{
$account = self::get($id, true);
if(!$account) return "";
if (! $account) {
return '';
}
$posts = self::formatNumber($account['statuses_count']).' Posts, ';
$following = self::formatNumber($account['following_count']).' Following, ';
@ -277,6 +338,7 @@ class AccountService
$note = $account['note'] && strlen($account['note']) ?
' · '.\Purify::clean(strip_tags(str_replace("\n", '', str_replace("\r", '', $account['note'])))) :
'';
return $posts.$following.$followers.$note;
}
}

View file

@ -97,6 +97,14 @@
<p class="text-muted small help-text">Display following count on profile</p>
</div>
<div class="form-check pb-3">
<input class="form-check-input" type="checkbox" name="disable_embeds" id="disable_embeds" {{$settings->disable_embeds ? 'checked=""':''}}>
<label class="form-check-label font-weight-bold" for="disable_embeds">
{{__('Disable Embeds')}}
</label>
<p class="text-muted small help-text">Disable post and profile embeds</p>
</div>
@if(!$settings->is_private)
<div class="form-check pb-3">
<input class="form-check-input" type="checkbox" name="show_atom" id="show_atom" {{$settings->show_atom ? 'checked=""':''}}>