diff --git a/CHANGELOG.md b/CHANGELOG.md index 5864089c3..0528e5938 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,9 @@ ### Added - Autocomplete Support (hashtags + mentions) ([de514f7d](https://github.com/pixelfed/pixelfed/commit/de514f7d)) - Creative Commons Licenses ([552e950](https://github.com/pixelfed/pixelfed/commit/552e950)) -- Add Network Timeline ([af7face4](https://github.com/pixelfed/pixelfed/commit/af7face4)) +- Network Timeline ([af7face4](https://github.com/pixelfed/pixelfed/commit/af7face4)) +- Admin config settings ([f2066b74](https://github.com/pixelfed/pixelfed/commit/f2066b74)) +- Profile pronouns ([fabb57a9](https://github.com/pixelfed/pixelfed/commit/fabb57a9)) ### Updated - Updated AdminController, fix variable name in updateSpam method. ([6edaf940](https://github.com/pixelfed/pixelfed/commit/6edaf940)) @@ -83,6 +85,7 @@ - Updated PostComponent, change like logic. ([0a35f5d6](https://github.com/pixelfed/pixelfed/commit/0a35f5d6)) - Updated Timeline component, change like logic. ([7bcbf96b](https://github.com/pixelfed/pixelfed/commit/7bcbf96b)) - Updated LikeService, fix likedBy method. ([a5e64da6](https://github.com/pixelfed/pixelfed/commit/a5e64da6)) +- Updated PublicApiController, increase public timeline to 6 months from 3. ([8a736432](https://github.com/pixelfed/pixelfed/commit/8a736432)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.10.10 (2021-01-28)](https://github.com/pixelfed/pixelfed/compare/v0.10.9...v0.10.10) diff --git a/app/Http/Controllers/Admin/AdminSettingsController.php b/app/Http/Controllers/Admin/AdminSettingsController.php index 9e1a685cd..0dbf02e83 100644 --- a/app/Http/Controllers/Admin/AdminSettingsController.php +++ b/app/Http/Controllers/Admin/AdminSettingsController.php @@ -8,12 +8,116 @@ use Carbon\Carbon; use App\{Comment, Like, Media, Page, Profile, Report, Status, User}; use App\Http\Controllers\Controller; use App\Util\Lexer\PrettyNumber; +use App\Models\ConfigCache; +use App\Services\ConfigCacheService; trait AdminSettingsController { public function settings(Request $request) { - return view('admin.settings.home'); + $name = ConfigCacheService::get('app.name'); + $short_description = ConfigCacheService::get('app.short_description'); + $description = ConfigCacheService::get('app.description'); + $types = explode(',', ConfigCacheService::get('pixelfed.media_types')); + $jpeg = in_array('image/jpg', $types) ? true : in_array('image/jpeg', $types); + $png = in_array('image/png', $types); + $gif = in_array('image/gif', $types); + $mp4 = in_array('video/mp4', $types); + + return view('admin.settings.home', compact( + 'name', + 'short_description', + 'description', + 'jpeg', + 'png', + 'gif', + 'mp4' + )); + } + + public function settingsHomeStore(Request $request) + { + $this->validate($request, [ + 'name' => 'nullable|string', + 'short_description' => 'nullable', + 'long_description' => 'nullable', + 'max_photo_size' => 'nullable|integer|min:1', + 'max_album_length' => 'nullable|integer|min:1|max:100', + 'image_quality' => 'nullable|integer|min:1|max:100', + 'type_jpeg' => 'nullable', + 'type_png' => 'nullable', + 'type_gif' => 'nullable', + 'type_mp4' => 'nullable', + ]); + + $media_types = explode(',', config_cache('pixelfed.media_types')); + $media_types_original = $media_types; + + $mimes = [ + 'type_jpeg' => 'image/jpeg', + 'type_png' => 'image/png', + 'type_gif' => 'image/gif', + 'type_mp4' => 'video/mp4', + ]; + + foreach ($mimes as $key => $value) { + if($request->input($key) == 'on') { + if(!in_array($value, $media_types)) { + array_push($media_types, $value); + } + } else { + $media_types = array_diff($media_types, [$value]); + } + } + + if($media_types !== $media_types_original) { + ConfigCacheService::put('pixelfed.media_types', implode(',', array_unique($media_types))); + } + + $keys = [ + 'name' => 'app.name', + 'short_description' => 'app.short_description', + 'long_description' => 'app.description', + 'max_photo_size' => 'pixelfed.max_photo_size', + 'max_album_length' => 'pixelfed.max_album_length', + 'image_quality' => 'pixelfed.image_quality', + 'account_limit' => 'pixelfed.max_account_size', + 'custom_css' => 'uikit.custom.css', + 'custom_js' => 'uikit.custom.js' + ]; + + foreach ($keys as $key => $value) { + $cc = ConfigCache::whereK($value)->first(); + $val = $request->input($key); + if($cc && $cc->v != $val) { + ConfigCacheService::put($value, $val); + } + } + + $bools = [ + 'activitypub' => 'federation.activitypub.enabled', + 'open_registration' => 'pixelfed.open_registration', + 'mobile_apis' => 'pixelfed.oauth_enabled', + 'stories' => 'instance.stories.enabled', + 'ig_import' => 'pixelfed.import.instagram.enabled', + 'spam_detection' => 'pixelfed.bouncer.enabled', + 'require_email_verification' => 'pixelfed.enforce_email_verification', + 'enforce_account_limit' => 'pixelfed.enforce_account_limit', + 'show_custom_css' => 'uikit.show_custom.css', + 'show_custom_js' => 'uikit.show_custom.js', + ]; + + foreach ($bools as $key => $value) { + $active = $request->input($key) == 'on'; + + if(config_cache($value) !== $active) { + ConfigCacheService::put($value, (bool) $active); + } + } + + Cache::forget('api:site:configuration:_v0.2'); + + return redirect('/i/admin/settings'); } public function settingsBackups(Request $request) @@ -84,15 +188,6 @@ trait AdminSettingsController return view('admin.settings.features'); } - public function settingsHomeStore(Request $request) - { - $this->validate($request, [ - 'APP_NAME' => 'required|string', - ]); - // Artisan::call('config:clear'); - return redirect()->back(); - } - public function settingsPages(Request $request) { $pages = Page::orderByDesc('updated_at')->paginate(10); @@ -135,4 +230,4 @@ trait AdminSettingsController } return view('admin.settings.system', compact('sys')); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index ebfaf1b70..ddc02cdc4 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -66,7 +66,7 @@ class ApiV1Controller extends Controller public function apps(Request $request) { - abort_if(!config('pixelfed.oauth_enabled'), 404); + abort_if(!config_cache('pixelfed.oauth_enabled'), 404); $this->validate($request, [ 'client_name' => 'required', @@ -960,31 +960,31 @@ class ApiV1Controller extends Controller $res = [ 'approval_required' => false, 'contact_account' => null, - 'description' => config('instance.description'), + 'description' => config_cache('app.description'), 'email' => config('instance.email'), 'invites_enabled' => false, 'rules' => [], 'short_description' => 'Pixelfed - Photo sharing for everyone', 'languages' => ['en'], 'max_toot_chars' => (int) config('pixelfed.max_caption_length'), - 'registrations' => config('pixelfed.open_registration'), + 'registrations' => config_cache('pixelfed.open_registration'), 'stats' => [ 'user_count' => 0, 'status_count' => 0, 'domain_count' => 0 ], 'thumbnail' => config('app.url') . '/img/pixelfed-icon-color.png', - 'title' => config('app.name'), + 'title' => config_cache('app.name'), 'uri' => config('pixelfed.domain.app'), 'urls' => [], 'version' => '2.7.2 (compatible; Pixelfed ' . config('pixelfed.version') . ')', 'environment' => [ - 'max_photo_size' => (int) config('pixelfed.max_photo_size'), + 'max_photo_size' => (int) config_cache('pixelfed.max_photo_size'), 'max_avatar_size' => (int) config('pixelfed.max_avatar_size'), 'max_caption_length' => (int) config('pixelfed.max_caption_length'), 'max_bio_length' => (int) config('pixelfed.max_bio_length'), - 'max_album_length' => (int) config('pixelfed.max_album_length'), - 'mobile_apis' => config('pixelfed.oauth_enabled') + 'max_album_length' => (int) config_cache('pixelfed.max_album_length'), + 'mobile_apis' => config_cache('pixelfed.oauth_enabled') ] ]; @@ -1033,8 +1033,8 @@ class ApiV1Controller extends Controller 'file.*' => function() { return [ 'required', - 'mimes:' . config('pixelfed.media_types'), - 'max:' . config('pixelfed.max_photo_size'), + 'mimes:' . config_cache('pixelfed.media_types'), + 'max:' . config_cache('pixelfed.max_photo_size'), ]; }, 'filter_name' => 'nullable|string|max:24', @@ -1059,11 +1059,11 @@ class ApiV1Controller extends Controller $profile = $user->profile; - if(config('pixelfed.enforce_account_limit') == true) { + 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('pixelfed.max_account_size'); + $limit = (int) config_cache('pixelfed.max_account_size'); if ($size >= $limit) { abort(403, 'Account size limit reached.'); } @@ -1074,7 +1074,7 @@ class ApiV1Controller extends Controller $photo = $request->file('file'); - $mimes = explode(',', config('pixelfed.media_types')); + $mimes = explode(',', config_cache('pixelfed.media_types')); if(in_array($photo->getMimeType(), $mimes) == false) { abort(403, 'Invalid or unsupported mime type.'); } @@ -1742,7 +1742,7 @@ class ApiV1Controller extends Controller $this->validate($request, [ 'status' => 'nullable|string', 'in_reply_to_id' => 'nullable|integer', - 'media_ids' => 'array|max:' . config('pixelfed.max_album_length'), + 'media_ids' => 'array|max:' . config_cache('pixelfed.max_album_length'), 'media_ids.*' => 'integer|min:1', 'sensitive' => 'nullable|boolean', 'visibility' => 'string|in:private,unlisted,public', @@ -1824,7 +1824,7 @@ class ApiV1Controller extends Controller $mimes = []; foreach($ids as $k => $v) { - if($k + 1 > config('pixelfed.max_album_length')) { + if($k + 1 > config_cache('pixelfed.max_album_length')) { continue; } $m = Media::whereUserId($user->id)->whereNull('status_id')->findOrFail($v); diff --git a/app/Http/Controllers/Api/InstanceApiController.php b/app/Http/Controllers/Api/InstanceApiController.php index b7847b4f1..80ca2169d 100644 --- a/app/Http/Controllers/Api/InstanceApiController.php +++ b/app/Http/Controllers/Api/InstanceApiController.php @@ -34,7 +34,7 @@ class InstanceApiController extends Controller { $res = [ 'uri' => config('pixelfed.domain.app'), - 'title' => config('app.name'), + 'title' => config_cache('app.name'), 'description' => '', 'version' => config('pixelfed.version'), 'urls' => [], diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php index c8adf28d6..ab1db6625 100644 --- a/app/Http/Controllers/Auth/RegisterController.php +++ b/app/Http/Controllers/Auth/RegisterController.php @@ -14,183 +14,183 @@ use App\Services\EmailService; class RegisterController extends Controller { - /* - |-------------------------------------------------------------------------- - | Register Controller - |-------------------------------------------------------------------------- - | - | This controller handles the registration of new users as well as their - | validation and creation. By default this controller uses a trait to - | provide this functionality without requiring any additional code. - | - */ - - use RegistersUsers; - - /** - * Where to redirect users after registration. - * - * @var string - */ - protected $redirectTo = '/'; - - /** - * Create a new controller instance. - * - * @return void - */ - public function __construct() - { - $this->middleware('guest'); - } - - /** - * Get a validator for an incoming registration request. - * - * @param array $data - * - * @return \Illuminate\Contracts\Validation\Validator - */ - protected function validator(array $data) - { - if(config('database.default') == 'pgsql') { - $data['username'] = strtolower($data['username']); - $data['email'] = strtolower($data['email']); - } - - $usernameRules = [ - 'required', - 'min:2', - 'max:15', - 'unique:users', - function ($attribute, $value, $fail) { - $dash = substr_count($value, '-'); - $underscore = substr_count($value, '_'); - $period = substr_count($value, '.'); - - if(ends_with($value, ['.php', '.js', '.css'])) { - return $fail('Username is invalid.'); - } - - if(($dash + $underscore + $period) > 1) { - return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).'); - } - - if (!ctype_alpha($value[0])) { - return $fail('Username is invalid. Must start with a letter or number.'); - } - - if (!ctype_alnum($value[strlen($value) - 1])) { - return $fail('Username is invalid. Must end with a letter or number.'); - } - - $val = str_replace(['_', '.', '-'], '', $value); - if(!ctype_alnum($val)) { - return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).'); - } - - $restricted = RestrictedNames::get(); - if (in_array(strtolower($value), array_map('strtolower', $restricted))) { - return $fail('Username cannot be used.'); - } - }, - ]; - - $emailRules = [ - 'required', - 'string', - 'email', - 'max:255', - 'unique:users', - function ($attribute, $value, $fail) { - $banned = EmailService::isBanned($value); - if($banned) { - return $fail('Email is invalid.'); - } - }, - ]; - - $rules = [ - 'agecheck' => 'required|accepted', - 'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'), - 'username' => $usernameRules, - 'email' => $emailRules, - 'password' => 'required|string|min:'.config('pixelfed.min_password_length').'|confirmed', - ]; - - if(config('captcha.enabled')) { - $rules['h-captcha-response'] = 'required|captcha'; - } - - return Validator::make($data, $rules); - } - - /** - * Create a new user instance after a valid registration. - * - * @param array $data - * - * @return \App\User - */ - protected function create(array $data) - { - if(config('database.default') == 'pgsql') { - $data['username'] = strtolower($data['username']); - $data['email'] = strtolower($data['email']); - } - - return User::create([ - 'name' => $data['name'], - 'username' => $data['username'], - 'email' => $data['email'], - 'password' => Hash::make($data['password']), - ]); - } - - /** - * Show the application registration form. - * - * @return \Illuminate\Http\Response - */ - public function showRegistrationForm() - { - if(config('pixelfed.open_registration')) { - $limit = config('pixelfed.max_users'); - if($limit) { - abort_if($limit <= User::count(), 404); - return view('auth.register'); - } else { - return view('auth.register'); - } - } else { - abort(404); - } - } - - /** - * Handle a registration request for the application. - * - * @param \Illuminate\Http\Request $request - * @return \Illuminate\Http\Response - */ - public function register(Request $request) - { - abort_if(config('pixelfed.open_registration') == false, 400); - - $count = User::count(); - $limit = config('pixelfed.max_users'); - - if(false == config('pixelfed.open_registration') || $limit && $limit <= $count) { - return abort(403); - } - - $this->validator($request->all())->validate(); - - event(new Registered($user = $this->create($request->all()))); - - $this->guard()->login($user); - - return $this->registered($request, $user) - ?: redirect($this->redirectPath()); - } + /* + |-------------------------------------------------------------------------- + | Register Controller + |-------------------------------------------------------------------------- + | + | This controller handles the registration of new users as well as their + | validation and creation. By default this controller uses a trait to + | provide this functionality without requiring any additional code. + | + */ + + use RegistersUsers; + + /** + * Where to redirect users after registration. + * + * @var string + */ + protected $redirectTo = '/'; + + /** + * Create a new controller instance. + * + * @return void + */ + public function __construct() + { + $this->middleware('guest'); + } + + /** + * Get a validator for an incoming registration request. + * + * @param array $data + * + * @return \Illuminate\Contracts\Validation\Validator + */ + protected function validator(array $data) + { + if(config('database.default') == 'pgsql') { + $data['username'] = strtolower($data['username']); + $data['email'] = strtolower($data['email']); + } + + $usernameRules = [ + 'required', + 'min:2', + 'max:15', + 'unique:users', + function ($attribute, $value, $fail) { + $dash = substr_count($value, '-'); + $underscore = substr_count($value, '_'); + $period = substr_count($value, '.'); + + if(ends_with($value, ['.php', '.js', '.css'])) { + return $fail('Username is invalid.'); + } + + if(($dash + $underscore + $period) > 1) { + return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).'); + } + + if (!ctype_alpha($value[0])) { + return $fail('Username is invalid. Must start with a letter or number.'); + } + + if (!ctype_alnum($value[strlen($value) - 1])) { + return $fail('Username is invalid. Must end with a letter or number.'); + } + + $val = str_replace(['_', '.', '-'], '', $value); + if(!ctype_alnum($val)) { + return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).'); + } + + $restricted = RestrictedNames::get(); + if (in_array(strtolower($value), array_map('strtolower', $restricted))) { + return $fail('Username cannot be used.'); + } + }, + ]; + + $emailRules = [ + 'required', + 'string', + 'email', + 'max:255', + 'unique:users', + function ($attribute, $value, $fail) { + $banned = EmailService::isBanned($value); + if($banned) { + return $fail('Email is invalid.'); + } + }, + ]; + + $rules = [ + 'agecheck' => 'required|accepted', + 'name' => 'nullable|string|max:'.config('pixelfed.max_name_length'), + 'username' => $usernameRules, + 'email' => $emailRules, + 'password' => 'required|string|min:'.config('pixelfed.min_password_length').'|confirmed', + ]; + + if(config('captcha.enabled')) { + $rules['h-captcha-response'] = 'required|captcha'; + } + + return Validator::make($data, $rules); + } + + /** + * Create a new user instance after a valid registration. + * + * @param array $data + * + * @return \App\User + */ + protected function create(array $data) + { + if(config('database.default') == 'pgsql') { + $data['username'] = strtolower($data['username']); + $data['email'] = strtolower($data['email']); + } + + return User::create([ + 'name' => $data['name'], + 'username' => $data['username'], + 'email' => $data['email'], + 'password' => Hash::make($data['password']), + ]); + } + + /** + * Show the application registration form. + * + * @return \Illuminate\Http\Response + */ + public function showRegistrationForm() + { + if(config_cache('pixelfed.open_registration')) { + $limit = config('pixelfed.max_users'); + if($limit) { + abort_if($limit <= User::count(), 404); + return view('auth.register'); + } else { + return view('auth.register'); + } + } else { + abort(404); + } + } + + /** + * Handle a registration request for the application. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response + */ + public function register(Request $request) + { + abort_if(config_cache('pixelfed.open_registration') == false, 400); + + $count = User::count(); + $limit = config('pixelfed.max_users'); + + if(false == config_cache('pixelfed.open_registration') || $limit && $limit <= $count) { + return abort(403); + } + + $this->validator($request->all())->validate(); + + event(new Registered($user = $this->create($request->all()))); + + $this->guard()->login($user); + + return $this->registered($request, $user) + ?: redirect($this->redirectPath()); + } } diff --git a/app/Http/Controllers/ComposeController.php b/app/Http/Controllers/ComposeController.php index 6d62ab07d..ee317ad2c 100644 --- a/app/Http/Controllers/ComposeController.php +++ b/app/Http/Controllers/ComposeController.php @@ -71,8 +71,8 @@ class ComposeController extends Controller 'file.*' => function() { return [ 'required', - 'mimes:' . config('pixelfed.media_types'), - 'max:' . config('pixelfed.max_photo_size'), + 'mimes:' . config_cache('pixelfed.media_types'), + 'max:' . config_cache('pixelfed.max_photo_size'), ]; }, 'filter_name' => 'nullable|string|max:24', @@ -92,11 +92,11 @@ class ComposeController extends Controller abort_if($limitReached == true, 429); - if(config('pixelfed.enforce_account_limit') == true) { + 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('pixelfed.max_account_size'); + }); + $limit = (int) config_cache('pixelfed.max_account_size'); if ($size >= $limit) { abort(403, 'Account size limit reached.'); } @@ -107,7 +107,7 @@ class ComposeController extends Controller $photo = $request->file('file'); - $mimes = explode(',', config('pixelfed.media_types')); + $mimes = explode(',', config_cache('pixelfed.media_types')); abort_if(in_array($photo->getMimeType(), $mimes) == false, 400, 'Invalid media format'); @@ -132,7 +132,7 @@ class ComposeController extends Controller $preview_url = $media->url() . '?v=' . time(); $url = $media->url() . '?v=' . time(); - + switch ($media->mime) { case 'image/jpeg': case 'image/png': @@ -164,8 +164,8 @@ class ComposeController extends Controller 'file' => function() { return [ 'required', - 'mimes:' . config('pixelfed.media_types'), - 'max:' . config('pixelfed.max_photo_size'), + 'mimes:' . config_cache('pixelfed.media_types'), + 'max:' . config_cache('pixelfed.max_photo_size'), ]; }, ]); @@ -454,7 +454,7 @@ class ComposeController extends Controller $optimize_media = (bool) $request->input('optimize_media'); foreach($medias as $k => $media) { - if($k + 1 > config('pixelfed.max_album_length')) { + if($k + 1 > config_cache('pixelfed.max_album_length')) { continue; } $m = Media::findOrFail($media['id']); @@ -648,7 +648,7 @@ class ComposeController extends Controller case 'video/mp4': $finished = config('pixelfed.cloud_storage') ? (bool) $media->cdn_url : (bool) $media->processed_at; break; - + default: # code... break; diff --git a/app/Http/Controllers/DirectMessageController.php b/app/Http/Controllers/DirectMessageController.php index fe2753518..e01a0ef90 100644 --- a/app/Http/Controllers/DirectMessageController.php +++ b/app/Http/Controllers/DirectMessageController.php @@ -160,7 +160,7 @@ class DirectMessageController extends Controller 'messages' => [] ]; }); - } + } } elseif(config('database.default') == 'mysql') { if($action == 'inbox') { $dms = DirectMessage::selectRaw('*, max(created_at) as createdAt') @@ -334,7 +334,7 @@ class DirectMessageController extends Controller $dm->type = 'link'; $dm->meta = [ 'domain' => parse_url($msg, PHP_URL_HOST), - 'local' => parse_url($msg, PHP_URL_HOST) == + 'local' => parse_url($msg, PHP_URL_HOST) == parse_url(config('app.url'), PHP_URL_HOST) ]; $dm->save(); @@ -500,8 +500,8 @@ class DirectMessageController extends Controller 'file' => function() { return [ 'required', - 'mimes:' . config('pixelfed.media_types'), - 'max:' . config('pixelfed.max_photo_size'), + 'mimes:' . config_cache('pixelfed.media_types'), + 'max:' . config_cache('pixelfed.max_photo_size'), ]; }, 'to_id' => 'required' @@ -522,18 +522,18 @@ class DirectMessageController extends Controller $hidden = false; } - if(config('pixelfed.enforce_account_limit') == true) { + 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('pixelfed.max_account_size'); + }); + $limit = (int) config_cache('pixelfed.max_account_size'); if ($size >= $limit) { abort(403, 'Account size limit reached.'); } } $photo = $request->file('file'); - $mimes = explode(',', config('pixelfed.media_types')); + $mimes = explode(',', config_cache('pixelfed.media_types')); if(in_array($photo->getMimeType(), $mimes) == false) { abort(403, 'Invalid or unsupported mime type.'); } diff --git a/app/Http/Controllers/FederationController.php b/app/Http/Controllers/FederationController.php index 3c5b93950..c17019f1b 100644 --- a/app/Http/Controllers/FederationController.php +++ b/app/Http/Controllers/FederationController.php @@ -79,7 +79,7 @@ class FederationController extends Controller public function userOutbox(Request $request, $username) { - abort_if(!config('federation.activitypub.enabled'), 404); + abort_if(!config_cache('federation.activitypub.enabled'), 404); abort_if(!config('federation.activitypub.outbox'), 404); $profile = Profile::whereNull('domain') @@ -99,7 +99,7 @@ class FederationController extends Controller public function userInbox(Request $request, $username) { - abort_if(!config('federation.activitypub.enabled'), 404); + abort_if(!config_cache('federation.activitypub.enabled'), 404); abort_if(!config('federation.activitypub.inbox'), 404); $headers = $request->headers->all(); @@ -110,7 +110,7 @@ class FederationController extends Controller public function sharedInbox(Request $request) { - abort_if(!config('federation.activitypub.enabled'), 404); + abort_if(!config_cache('federation.activitypub.enabled'), 404); abort_if(!config('federation.activitypub.sharedInbox'), 404); $headers = $request->headers->all(); @@ -121,13 +121,13 @@ class FederationController extends Controller public function userFollowing(Request $request, $username) { - abort_if(!config('federation.activitypub.enabled'), 404); + abort_if(!config_cache('federation.activitypub.enabled'), 404); $profile = Profile::whereNull('remote_url') ->whereUsername($username) ->whereIsPrivate(false) ->firstOrFail(); - + if($profile->status != null) { abort(404); } @@ -139,12 +139,12 @@ class FederationController extends Controller 'totalItems' => 0, 'orderedItems' => [] ]; - return response()->json($obj); + return response()->json($obj); } public function userFollowers(Request $request, $username) { - abort_if(!config('federation.activitypub.enabled'), 404); + abort_if(!config_cache('federation.activitypub.enabled'), 404); $profile = Profile::whereNull('remote_url') ->whereUsername($username) @@ -163,6 +163,6 @@ class FederationController extends Controller 'orderedItems' => [] ]; - return response()->json($obj); + return response()->json($obj); } } diff --git a/app/Http/Controllers/ImportController.php b/app/Http/Controllers/ImportController.php index 37a5ddd7e..9885455e9 100644 --- a/app/Http/Controllers/ImportController.php +++ b/app/Http/Controllers/ImportController.php @@ -12,7 +12,7 @@ class ImportController extends Controller { $this->middleware('auth'); - if(config('pixelfed.import.instagram.enabled') != true) { + if(config_cache('pixelfed.import.instagram.enabled') != true) { abort(404, 'Feature not enabled'); } } diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 6bd42d177..4c38a2319 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -20,229 +20,229 @@ use App\Transformer\ActivityPub\ProfileTransformer; class ProfileController extends Controller { - public function show(Request $request, $username) - { - $user = Profile::whereNull('domain') - ->whereNull('status') - ->whereUsername($username) - ->firstOrFail(); - - if($request->wantsJson() && config('federation.activitypub.enabled')) { - return $this->showActivityPub($request, $user); - } - return $this->buildProfile($request, $user); - } - - protected function buildProfile(Request $request, $user) - { - $username = $user->username; - $loggedIn = Auth::check(); - $isPrivate = false; - $isBlocked = false; - if(!$loggedIn) { - $key = 'profile:settings:' . $user->id; - $ttl = now()->addHours(6); - $settings = Cache::remember($key, $ttl, function() use($user) { - return $user->user->settings; - }); - - if ($user->is_private == true) { - abort(404); - } - - $owner = false; - $is_following = false; - - $is_admin = $user->user->is_admin; - $profile = $user; - $settings = [ - 'crawlable' => $settings->crawlable, - 'following' => [ - 'count' => $settings->show_profile_following_count, - 'list' => $settings->show_profile_following - ], - 'followers' => [ - 'count' => $settings->show_profile_follower_count, - 'list' => $settings->show_profile_followers - ] - ]; - $ui = $request->has('ui') && $request->input('ui') == 'memory' ? 'profile.memory' : 'profile.show'; - - return view($ui, compact('profile', 'settings')); - } else { - $key = 'profile:settings:' . $user->id; - $ttl = now()->addHours(6); - $settings = Cache::remember($key, $ttl, function() use($user) { - return $user->user->settings; - }); - - if ($user->is_private == true) { - $isPrivate = $this->privateProfileCheck($user, $loggedIn); - } - - $isBlocked = $this->blockedProfileCheck($user); - - $owner = $loggedIn && Auth::id() === $user->user_id; - $is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false; - - if ($isPrivate == true || $isBlocked == true) { - $requested = Auth::check() ? FollowRequest::whereFollowerId(Auth::user()->profile_id) - ->whereFollowingId($user->id) - ->exists() : false; - return view('profile.private', compact('user', 'is_following', 'requested')); - } - - $is_admin = is_null($user->domain) ? $user->user->is_admin : false; - $profile = $user; - $settings = [ - 'crawlable' => $settings->crawlable, - 'following' => [ - 'count' => $settings->show_profile_following_count, - 'list' => $settings->show_profile_following - ], - 'followers' => [ - 'count' => $settings->show_profile_follower_count, - 'list' => $settings->show_profile_followers - ] - ]; - $ui = $request->has('ui') && $request->input('ui') == 'memory' ? 'profile.memory' : 'profile.show'; - return view($ui, compact('profile', 'settings')); - } - } - - public function permalinkRedirect(Request $request, $username) - { - $user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail(); - - if ($request->wantsJson() && config('federation.activitypub.enabled')) { - return $this->showActivityPub($request, $user); - } - - return redirect($user->url()); - } - - protected function privateProfileCheck(Profile $profile, $loggedIn) - { - if (!Auth::check()) { - return true; - } - - $user = Auth::user()->profile; - if($user->id == $profile->id || !$profile->is_private) { - return false; - } - - $follows = Follower::whereProfileId($user->id)->whereFollowingId($profile->id)->exists(); - if ($follows == false) { - return true; - } - - return false; - } - - public static function accountCheck(Profile $profile) - { - switch ($profile->status) { - case 'disabled': - case 'suspended': - case 'delete': - return view('profile.disabled'); - break; - - default: - break; - } - return abort(404); - } - - protected function blockedProfileCheck(Profile $profile) - { - $pid = Auth::user()->profile->id; - $blocks = UserFilter::whereUserId($profile->id) - ->whereFilterType('block') - ->whereFilterableType('App\Profile') - ->pluck('filterable_id') - ->toArray(); - if (in_array($pid, $blocks)) { - return true; - } - - return false; - } - - public function showActivityPub(Request $request, $user) - { - abort_if(!config('federation.activitypub.enabled'), 404); - abort_if($user->domain, 404); - - $fractal = new Fractal\Manager(); - $resource = new Fractal\Resource\Item($user, new ProfileTransformer); - $res = $fractal->createData($resource)->toArray(); - return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json'); - } - - public function showAtomFeed(Request $request, $user) - { - abort_if(!config('federation.atom.enabled'), 404); - - $profile = $user = Profile::whereNull('status')->whereNull('domain')->whereUsername($user)->whereIsPrivate(false)->firstOrFail(); - if($profile->status != null) { - return $this->accountCheck($profile); - } - if($profile->is_private || Auth::check()) { - $blocked = $this->blockedProfileCheck($profile); - $check = $this->privateProfileCheck($profile, null); - if($check || $blocked) { - return redirect($profile->url()); - } - } - $items = $profile->statuses()->whereHas('media')->whereIn('visibility',['public', 'unlisted'])->orderBy('created_at', 'desc')->take(10)->get(); - return response()->view('atom.user', compact('profile', 'items')) - ->header('Content-Type', 'application/atom+xml'); - } - - public function meRedirect() - { - abort_if(!Auth::check(), 404); - return redirect(Auth::user()->url()); - } - - public function embed(Request $request, $username) - { - $res = view('profile.embed-removed'); - - if(strlen($username) > 15 || strlen($username) < 2) { - return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']); - } - - $profile = Profile::whereUsername($username) - ->whereIsPrivate(false) - ->whereNull('status') - ->whereNull('domain') - ->first(); - - if(!$profile) { - return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']); - } - - $content = Cache::remember('profile:embed:'.$profile->id, now()->addHours(12), function() use($profile) { - return View::make('profile.embed')->with(compact('profile'))->render(); - }); - - return response($content)->withHeaders(['X-Frame-Options' => 'ALLOWALL']); - } - - public function stories(Request $request, $username) - { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); - $profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail(); - $pid = $profile->id; - $authed = Auth::user()->profile; - abort_if($pid != $authed->id && $profile->followedBy($authed) == false, 404); - $exists = Story::whereProfileId($pid) - ->where('expires_at', '>', now()) - ->count(); - abort_unless($exists > 0, 404); - return view('profile.story', compact('pid', 'profile')); - } + public function show(Request $request, $username) + { + $user = Profile::whereNull('domain') + ->whereNull('status') + ->whereUsername($username) + ->firstOrFail(); + + if($request->wantsJson() && config_cache('federation.activitypub.enabled')) { + return $this->showActivityPub($request, $user); + } + return $this->buildProfile($request, $user); + } + + protected function buildProfile(Request $request, $user) + { + $username = $user->username; + $loggedIn = Auth::check(); + $isPrivate = false; + $isBlocked = false; + if(!$loggedIn) { + $key = 'profile:settings:' . $user->id; + $ttl = now()->addHours(6); + $settings = Cache::remember($key, $ttl, function() use($user) { + return $user->user->settings; + }); + + if ($user->is_private == true) { + abort(404); + } + + $owner = false; + $is_following = false; + + $is_admin = $user->user->is_admin; + $profile = $user; + $settings = [ + 'crawlable' => $settings->crawlable, + 'following' => [ + 'count' => $settings->show_profile_following_count, + 'list' => $settings->show_profile_following + ], + 'followers' => [ + 'count' => $settings->show_profile_follower_count, + 'list' => $settings->show_profile_followers + ] + ]; + $ui = $request->has('ui') && $request->input('ui') == 'memory' ? 'profile.memory' : 'profile.show'; + + return view($ui, compact('profile', 'settings')); + } else { + $key = 'profile:settings:' . $user->id; + $ttl = now()->addHours(6); + $settings = Cache::remember($key, $ttl, function() use($user) { + return $user->user->settings; + }); + + if ($user->is_private == true) { + $isPrivate = $this->privateProfileCheck($user, $loggedIn); + } + + $isBlocked = $this->blockedProfileCheck($user); + + $owner = $loggedIn && Auth::id() === $user->user_id; + $is_following = ($owner == false && Auth::check()) ? $user->followedBy(Auth::user()->profile) : false; + + if ($isPrivate == true || $isBlocked == true) { + $requested = Auth::check() ? FollowRequest::whereFollowerId(Auth::user()->profile_id) + ->whereFollowingId($user->id) + ->exists() : false; + return view('profile.private', compact('user', 'is_following', 'requested')); + } + + $is_admin = is_null($user->domain) ? $user->user->is_admin : false; + $profile = $user; + $settings = [ + 'crawlable' => $settings->crawlable, + 'following' => [ + 'count' => $settings->show_profile_following_count, + 'list' => $settings->show_profile_following + ], + 'followers' => [ + 'count' => $settings->show_profile_follower_count, + 'list' => $settings->show_profile_followers + ] + ]; + $ui = $request->has('ui') && $request->input('ui') == 'memory' ? 'profile.memory' : 'profile.show'; + return view($ui, compact('profile', 'settings')); + } + } + + public function permalinkRedirect(Request $request, $username) + { + $user = Profile::whereNull('domain')->whereUsername($username)->firstOrFail(); + + if ($request->wantsJson() && config_cache('federation.activitypub.enabled')) { + return $this->showActivityPub($request, $user); + } + + return redirect($user->url()); + } + + protected function privateProfileCheck(Profile $profile, $loggedIn) + { + if (!Auth::check()) { + return true; + } + + $user = Auth::user()->profile; + if($user->id == $profile->id || !$profile->is_private) { + return false; + } + + $follows = Follower::whereProfileId($user->id)->whereFollowingId($profile->id)->exists(); + if ($follows == false) { + return true; + } + + return false; + } + + public static function accountCheck(Profile $profile) + { + switch ($profile->status) { + case 'disabled': + case 'suspended': + case 'delete': + return view('profile.disabled'); + break; + + default: + break; + } + return abort(404); + } + + protected function blockedProfileCheck(Profile $profile) + { + $pid = Auth::user()->profile->id; + $blocks = UserFilter::whereUserId($profile->id) + ->whereFilterType('block') + ->whereFilterableType('App\Profile') + ->pluck('filterable_id') + ->toArray(); + if (in_array($pid, $blocks)) { + return true; + } + + return false; + } + + public function showActivityPub(Request $request, $user) + { + abort_if(!config_cache('federation.activitypub.enabled'), 404); + abort_if($user->domain, 404); + + $fractal = new Fractal\Manager(); + $resource = new Fractal\Resource\Item($user, new ProfileTransformer); + $res = $fractal->createData($resource)->toArray(); + return response(json_encode($res['data']))->header('Content-Type', 'application/activity+json'); + } + + public function showAtomFeed(Request $request, $user) + { + abort_if(!config('federation.atom.enabled'), 404); + + $profile = $user = Profile::whereNull('status')->whereNull('domain')->whereUsername($user)->whereIsPrivate(false)->firstOrFail(); + if($profile->status != null) { + return $this->accountCheck($profile); + } + if($profile->is_private || Auth::check()) { + $blocked = $this->blockedProfileCheck($profile); + $check = $this->privateProfileCheck($profile, null); + if($check || $blocked) { + return redirect($profile->url()); + } + } + $items = $profile->statuses()->whereHas('media')->whereIn('visibility',['public', 'unlisted'])->orderBy('created_at', 'desc')->take(10)->get(); + return response()->view('atom.user', compact('profile', 'items')) + ->header('Content-Type', 'application/atom+xml'); + } + + public function meRedirect() + { + abort_if(!Auth::check(), 404); + return redirect(Auth::user()->url()); + } + + public function embed(Request $request, $username) + { + $res = view('profile.embed-removed'); + + if(strlen($username) > 15 || strlen($username) < 2) { + return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']); + } + + $profile = Profile::whereUsername($username) + ->whereIsPrivate(false) + ->whereNull('status') + ->whereNull('domain') + ->first(); + + if(!$profile) { + return response($res)->withHeaders(['X-Frame-Options' => 'ALLOWALL']); + } + + $content = Cache::remember('profile:embed:'.$profile->id, now()->addHours(12), function() use($profile) { + return View::make('profile.embed')->with(compact('profile'))->render(); + }); + + return response($content)->withHeaders(['X-Frame-Options' => 'ALLOWALL']); + } + + public function stories(Request $request, $username) + { + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); + $profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail(); + $pid = $profile->id; + $authed = Auth::user()->profile; + abort_if($pid != $authed->id && $profile->followedBy($authed) == false, 404); + $exists = Story::whereProfileId($pid) + ->where('expires_at', '>', now()) + ->count(); + abort_unless($exists > 0, 404); + return view('profile.story', compact('pid', 'profile')); + } } diff --git a/app/Http/Controllers/PublicApiController.php b/app/Http/Controllers/PublicApiController.php index dabe5a6d9..1f52f7267 100644 --- a/app/Http/Controllers/PublicApiController.php +++ b/app/Http/Controllers/PublicApiController.php @@ -314,7 +314,7 @@ class PublicApiController extends Controller ->whereNotIn('profile_id', $filtered) ->whereLocal(true) ->whereScope('public') - ->where('created_at', '>', now()->subMonths(3)) + ->where('created_at', '>', now()->subMonths(6)) ->orderBy('created_at', 'desc') ->limit($limit) ->get(); @@ -343,7 +343,7 @@ class PublicApiController extends Controller ->with('profile', 'hashtags', 'mentions') ->whereLocal(true) ->whereScope('public') - ->where('created_at', '>', now()->subMonths(3)) + ->where('created_at', '>', now()->subMonths(6)) ->orderBy('created_at', 'desc') ->simplePaginate($limit); } diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 3f5accd35..c273e95d9 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -12,348 +12,348 @@ use App\Util\ActivityPub\Helpers; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Str; use App\Transformer\Api\{ - AccountTransformer, - HashtagTransformer, - StatusTransformer, + AccountTransformer, + HashtagTransformer, + StatusTransformer, }; use App\Services\WebfingerService; class SearchController extends Controller { - public $tokens = []; - public $term = ''; - public $hash = ''; - public $cacheKey = 'api:search:tag:'; - - public function __construct() - { - $this->middleware('auth'); - } - - public function searchAPI(Request $request) - { - $this->validate($request, [ - 'q' => 'required|string|min:3|max:120', - 'src' => 'required|string|in:metro', - 'v' => 'required|integer|in:2', - 'scope' => 'required|in:all,hashtag,profile,remote,webfinger' - ]); - - $scope = $request->input('scope') ?? 'all'; - $this->term = e(urldecode($request->input('q'))); - $this->hash = hash('sha256', $this->term); - - switch ($scope) { - case 'all': - $this->getHashtags(); - $this->getPosts(); - $this->getProfiles(); - // $this->getPlaces(); - break; - - case 'hashtag': - $this->getHashtags(); - break; - - case 'profile': - $this->getProfiles(); - break; - - case 'webfinger': - $this->webfingerSearch(); - break; - - case 'remote': - $this->remoteLookupSearch(); - break; - - case 'place': - $this->getPlaces(); - break; - - default: - break; - } - - return response()->json($this->tokens, 200, [], JSON_PRETTY_PRINT); - } - - protected function getPosts() - { - $tag = $this->term; - $hash = hash('sha256', $tag); - if( Helpers::validateUrl($tag) != false && - Helpers::validateLocalUrl($tag) != true && - config('federation.activitypub.enabled') == true && - config('federation.activitypub.remoteFollow') == true - ) { - $remote = Helpers::fetchFromUrl($tag); - if( isset($remote['type']) && - $remote['type'] == 'Note') { - $item = Helpers::statusFetch($tag); - $this->tokens['posts'] = [[ - 'count' => 0, - 'url' => $item->url(), - 'type' => 'status', - 'value' => "by {$item->profile->username} {$item->created_at->diffForHumans(null, true, true)}", - 'tokens' => [$item->caption], - 'name' => $item->caption, - 'thumb' => $item->thumb(), - ]]; - } - } else { - $posts = Status::select('id', 'profile_id', 'caption', 'created_at') - ->whereHas('media') - ->whereNull('in_reply_to_id') - ->whereNull('reblog_of_id') - ->whereProfileId(Auth::user()->profile_id) - ->where('caption', 'like', '%'.$tag.'%') - ->latest() - ->limit(10) - ->get(); - - if($posts->count() > 0) { - $posts = $posts->map(function($item, $key) { - return [ - 'count' => 0, - 'url' => $item->url(), - 'type' => 'status', - 'value' => "by {$item->profile->username} {$item->created_at->diffForHumans(null, true, true)}", - 'tokens' => [$item->caption], - 'name' => $item->caption, - 'thumb' => $item->thumb(), - 'filter' => $item->firstMedia()->filter_class - ]; - }); - $this->tokens['posts'] = $posts; - } - } - } - - protected function getHashtags() - { - $tag = $this->term; - $key = $this->cacheKey . 'hashtags:' . $this->hash; - $ttl = now()->addMinutes(1); - $tokens = Cache::remember($key, $ttl, function() use($tag) { - $htag = Str::startsWith($tag, '#') == true ? mb_substr($tag, 1) : $tag; - $hashtags = Hashtag::select('id', 'name', 'slug') - ->where('slug', 'like', '%'.$htag.'%') - ->whereHas('posts') - ->limit(20) - ->get(); - if($hashtags->count() > 0) { - $tags = $hashtags->map(function ($item, $key) { - return [ - 'count' => $item->posts()->count(), - 'url' => $item->url(), - 'type' => 'hashtag', - 'value' => $item->name, - 'tokens' => '', - 'name' => null, - ]; - }); - return $tags; - } - }); - $this->tokens['hashtags'] = $tokens; - } - - protected function getPlaces() - { - $tag = $this->term; - // $key = $this->cacheKey . 'places:' . $this->hash; - // $ttl = now()->addHours(12); - // $tokens = Cache::remember($key, $ttl, function() use($tag) { - $htag = Str::contains($tag, ',') == true ? explode(',', $tag) : [$tag]; - $hashtags = Place::select('id', 'name', 'slug', 'country') - ->where('name', 'like', '%'.$htag[0].'%') - ->paginate(20); - $tags = []; - if($hashtags->count() > 0) { - $tags = $hashtags->map(function ($item, $key) { - return [ - 'count' => null, - 'url' => $item->url(), - 'type' => 'place', - 'value' => $item->name . ', ' . $item->country, - 'tokens' => '', - 'name' => null, - 'city' => $item->name, - 'country' => $item->country - ]; - }); - // return $tags; - } - // }); - $this->tokens['places'] = $tags; - $this->tokens['placesPagination'] = [ - 'total' => $hashtags->total(), - 'current_page' => $hashtags->currentPage(), - 'last_page' => $hashtags->lastPage() - ]; - } - - protected function getProfiles() - { - $tag = $this->term; - $remoteKey = $this->cacheKey . 'profiles:remote:' . $this->hash; - $key = $this->cacheKey . 'profiles:' . $this->hash; - $remoteTtl = now()->addMinutes(15); - $ttl = now()->addHours(2); - if( Helpers::validateUrl($tag) != false && - Helpers::validateLocalUrl($tag) != true && - config('federation.activitypub.enabled') == true && - config('federation.activitypub.remoteFollow') == true - ) { - $remote = Helpers::fetchFromUrl($tag); - if( isset($remote['type']) && - $remote['type'] == 'Person' - ) { - $this->tokens['profiles'] = Cache::remember($remoteKey, $remoteTtl, function() use($tag) { - $item = Helpers::profileFirstOrNew($tag); - $tokens = [[ - 'count' => 1, - 'url' => $item->url(), - 'type' => 'profile', - 'value' => $item->username, - 'tokens' => [$item->username], - 'name' => $item->name, - 'entity' => [ - 'id' => (string) $item->id, - 'following' => $item->followedBy(Auth::user()->profile), - 'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id), - 'thumb' => $item->avatarUrl(), - 'local' => (bool) !$item->domain, - 'post_count' => $item->statuses()->count() - ] - ]]; - return $tokens; - }); - } - } - - else { - $this->tokens['profiles'] = Cache::remember($key, $ttl, function() use($tag) { - if(Str::startsWith($tag, '@')) { - $tag = substr($tag, 1); - } - $users = Profile::select('status', 'domain', 'username', 'name', 'id') - ->whereNull('status') - ->where('username', 'like', '%'.$tag.'%') - ->limit(20) - ->orderBy('domain') - ->get(); - - if($users->count() > 0) { - return $users->map(function ($item, $key) { - return [ - 'count' => 0, - 'url' => $item->url(), - 'type' => 'profile', - 'value' => $item->username, - 'tokens' => [$item->username], - 'name' => $item->name, - 'avatar' => $item->avatarUrl(), - 'id' => (string) $item->id, - 'entity' => [ - 'id' => (string) $item->id, - 'following' => $item->followedBy(Auth::user()->profile), - 'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id), - 'thumb' => $item->avatarUrl(), - 'local' => (bool) !$item->domain, - 'post_count' => $item->statuses()->count() - ] - ]; - }); - } - }); - } - } - - public function results(Request $request) - { - $this->validate($request, [ - 'q' => 'required|string|min:1', - ]); - - return view('search.results'); - } - - protected function webfingerSearch() - { - $wfs = WebfingerService::lookup($this->term); - - if(empty($wfs)) { - return; - } - - $this->tokens['profiles'] = [ - [ - 'count' => 1, - 'url' => $wfs['url'], - 'type' => 'profile', - 'value' => $wfs['username'], - 'tokens' => [$wfs['username']], - 'name' => $wfs['display_name'], - 'entity' => [ - 'id' => (string) $wfs['id'], - 'following' => null, - 'follow_request' => null, - 'thumb' => $wfs['avatar'], - 'local' => (bool) $wfs['local'] - ] - ] - ]; - return; - } - - protected function remotePostLookup() - { - $tag = $this->term; - $hash = hash('sha256', $tag); - $local = Helpers::validateLocalUrl($tag); - $valid = Helpers::validateUrl($tag); - - if($valid == false || $local == true) { - return; - } - - if(Status::whereUri($tag)->whereLocal(false)->exists()) { - $item = Status::whereUri($tag)->first(); - $this->tokens['posts'] = [[ - 'count' => 0, - 'url' => "/i/web/post/_/$item->profile_id/$item->id", - 'type' => 'status', - 'username' => $item->profile->username, - 'caption' => $item->rendered ?? $item->caption, - 'thumb' => $item->firstMedia()->remote_url, - 'timestamp' => $item->created_at->diffForHumans() - ]]; - } - - $remote = Helpers::fetchFromUrl($tag); - - if(isset($remote['type']) && $remote['type'] == 'Note') { - $item = Helpers::statusFetch($tag); - $this->tokens['posts'] = [[ - 'count' => 0, - 'url' => "/i/web/post/_/$item->profile_id/$item->id", - 'type' => 'status', - 'username' => $item->profile->username, - 'caption' => $item->rendered ?? $item->caption, - 'thumb' => $item->firstMedia()->remote_url, - 'timestamp' => $item->created_at->diffForHumans() - ]]; - } - } - - protected function remoteLookupSearch() - { - if(!Helpers::validateUrl($this->term)) { - return; - } - $this->getProfiles(); - $this->remotePostLookup(); - } -} \ No newline at end of file + public $tokens = []; + public $term = ''; + public $hash = ''; + public $cacheKey = 'api:search:tag:'; + + public function __construct() + { + $this->middleware('auth'); + } + + public function searchAPI(Request $request) + { + $this->validate($request, [ + 'q' => 'required|string|min:3|max:120', + 'src' => 'required|string|in:metro', + 'v' => 'required|integer|in:2', + 'scope' => 'required|in:all,hashtag,profile,remote,webfinger' + ]); + + $scope = $request->input('scope') ?? 'all'; + $this->term = e(urldecode($request->input('q'))); + $this->hash = hash('sha256', $this->term); + + switch ($scope) { + case 'all': + $this->getHashtags(); + $this->getPosts(); + $this->getProfiles(); + // $this->getPlaces(); + break; + + case 'hashtag': + $this->getHashtags(); + break; + + case 'profile': + $this->getProfiles(); + break; + + case 'webfinger': + $this->webfingerSearch(); + break; + + case 'remote': + $this->remoteLookupSearch(); + break; + + case 'place': + $this->getPlaces(); + break; + + default: + break; + } + + return response()->json($this->tokens, 200, [], JSON_PRETTY_PRINT); + } + + protected function getPosts() + { + $tag = $this->term; + $hash = hash('sha256', $tag); + if( Helpers::validateUrl($tag) != false && + Helpers::validateLocalUrl($tag) != true && + config_cache('federation.activitypub.enabled') == true && + config('federation.activitypub.remoteFollow') == true + ) { + $remote = Helpers::fetchFromUrl($tag); + if( isset($remote['type']) && + $remote['type'] == 'Note') { + $item = Helpers::statusFetch($tag); + $this->tokens['posts'] = [[ + 'count' => 0, + 'url' => $item->url(), + 'type' => 'status', + 'value' => "by {$item->profile->username} {$item->created_at->diffForHumans(null, true, true)}", + 'tokens' => [$item->caption], + 'name' => $item->caption, + 'thumb' => $item->thumb(), + ]]; + } + } else { + $posts = Status::select('id', 'profile_id', 'caption', 'created_at') + ->whereHas('media') + ->whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->whereProfileId(Auth::user()->profile_id) + ->where('caption', 'like', '%'.$tag.'%') + ->latest() + ->limit(10) + ->get(); + + if($posts->count() > 0) { + $posts = $posts->map(function($item, $key) { + return [ + 'count' => 0, + 'url' => $item->url(), + 'type' => 'status', + 'value' => "by {$item->profile->username} {$item->created_at->diffForHumans(null, true, true)}", + 'tokens' => [$item->caption], + 'name' => $item->caption, + 'thumb' => $item->thumb(), + 'filter' => $item->firstMedia()->filter_class + ]; + }); + $this->tokens['posts'] = $posts; + } + } + } + + protected function getHashtags() + { + $tag = $this->term; + $key = $this->cacheKey . 'hashtags:' . $this->hash; + $ttl = now()->addMinutes(1); + $tokens = Cache::remember($key, $ttl, function() use($tag) { + $htag = Str::startsWith($tag, '#') == true ? mb_substr($tag, 1) : $tag; + $hashtags = Hashtag::select('id', 'name', 'slug') + ->where('slug', 'like', '%'.$htag.'%') + ->whereHas('posts') + ->limit(20) + ->get(); + if($hashtags->count() > 0) { + $tags = $hashtags->map(function ($item, $key) { + return [ + 'count' => $item->posts()->count(), + 'url' => $item->url(), + 'type' => 'hashtag', + 'value' => $item->name, + 'tokens' => '', + 'name' => null, + ]; + }); + return $tags; + } + }); + $this->tokens['hashtags'] = $tokens; + } + + protected function getPlaces() + { + $tag = $this->term; + // $key = $this->cacheKey . 'places:' . $this->hash; + // $ttl = now()->addHours(12); + // $tokens = Cache::remember($key, $ttl, function() use($tag) { + $htag = Str::contains($tag, ',') == true ? explode(',', $tag) : [$tag]; + $hashtags = Place::select('id', 'name', 'slug', 'country') + ->where('name', 'like', '%'.$htag[0].'%') + ->paginate(20); + $tags = []; + if($hashtags->count() > 0) { + $tags = $hashtags->map(function ($item, $key) { + return [ + 'count' => null, + 'url' => $item->url(), + 'type' => 'place', + 'value' => $item->name . ', ' . $item->country, + 'tokens' => '', + 'name' => null, + 'city' => $item->name, + 'country' => $item->country + ]; + }); + // return $tags; + } + // }); + $this->tokens['places'] = $tags; + $this->tokens['placesPagination'] = [ + 'total' => $hashtags->total(), + 'current_page' => $hashtags->currentPage(), + 'last_page' => $hashtags->lastPage() + ]; + } + + protected function getProfiles() + { + $tag = $this->term; + $remoteKey = $this->cacheKey . 'profiles:remote:' . $this->hash; + $key = $this->cacheKey . 'profiles:' . $this->hash; + $remoteTtl = now()->addMinutes(15); + $ttl = now()->addHours(2); + if( Helpers::validateUrl($tag) != false && + Helpers::validateLocalUrl($tag) != true && + config_cache('federation.activitypub.enabled') == true && + config('federation.activitypub.remoteFollow') == true + ) { + $remote = Helpers::fetchFromUrl($tag); + if( isset($remote['type']) && + $remote['type'] == 'Person' + ) { + $this->tokens['profiles'] = Cache::remember($remoteKey, $remoteTtl, function() use($tag) { + $item = Helpers::profileFirstOrNew($tag); + $tokens = [[ + 'count' => 1, + 'url' => $item->url(), + 'type' => 'profile', + 'value' => $item->username, + 'tokens' => [$item->username], + 'name' => $item->name, + 'entity' => [ + 'id' => (string) $item->id, + 'following' => $item->followedBy(Auth::user()->profile), + 'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id), + 'thumb' => $item->avatarUrl(), + 'local' => (bool) !$item->domain, + 'post_count' => $item->statuses()->count() + ] + ]]; + return $tokens; + }); + } + } + + else { + $this->tokens['profiles'] = Cache::remember($key, $ttl, function() use($tag) { + if(Str::startsWith($tag, '@')) { + $tag = substr($tag, 1); + } + $users = Profile::select('status', 'domain', 'username', 'name', 'id') + ->whereNull('status') + ->where('username', 'like', '%'.$tag.'%') + ->limit(20) + ->orderBy('domain') + ->get(); + + if($users->count() > 0) { + return $users->map(function ($item, $key) { + return [ + 'count' => 0, + 'url' => $item->url(), + 'type' => 'profile', + 'value' => $item->username, + 'tokens' => [$item->username], + 'name' => $item->name, + 'avatar' => $item->avatarUrl(), + 'id' => (string) $item->id, + 'entity' => [ + 'id' => (string) $item->id, + 'following' => $item->followedBy(Auth::user()->profile), + 'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id), + 'thumb' => $item->avatarUrl(), + 'local' => (bool) !$item->domain, + 'post_count' => $item->statuses()->count() + ] + ]; + }); + } + }); + } + } + + public function results(Request $request) + { + $this->validate($request, [ + 'q' => 'required|string|min:1', + ]); + + return view('search.results'); + } + + protected function webfingerSearch() + { + $wfs = WebfingerService::lookup($this->term); + + if(empty($wfs)) { + return; + } + + $this->tokens['profiles'] = [ + [ + 'count' => 1, + 'url' => $wfs['url'], + 'type' => 'profile', + 'value' => $wfs['username'], + 'tokens' => [$wfs['username']], + 'name' => $wfs['display_name'], + 'entity' => [ + 'id' => (string) $wfs['id'], + 'following' => null, + 'follow_request' => null, + 'thumb' => $wfs['avatar'], + 'local' => (bool) $wfs['local'] + ] + ] + ]; + return; + } + + protected function remotePostLookup() + { + $tag = $this->term; + $hash = hash('sha256', $tag); + $local = Helpers::validateLocalUrl($tag); + $valid = Helpers::validateUrl($tag); + + if($valid == false || $local == true) { + return; + } + + if(Status::whereUri($tag)->whereLocal(false)->exists()) { + $item = Status::whereUri($tag)->first(); + $this->tokens['posts'] = [[ + 'count' => 0, + 'url' => "/i/web/post/_/$item->profile_id/$item->id", + 'type' => 'status', + 'username' => $item->profile->username, + 'caption' => $item->rendered ?? $item->caption, + 'thumb' => $item->firstMedia()->remote_url, + 'timestamp' => $item->created_at->diffForHumans() + ]]; + } + + $remote = Helpers::fetchFromUrl($tag); + + if(isset($remote['type']) && $remote['type'] == 'Note') { + $item = Helpers::statusFetch($tag); + $this->tokens['posts'] = [[ + 'count' => 0, + 'url' => "/i/web/post/_/$item->profile_id/$item->id", + 'type' => 'status', + 'username' => $item->profile->username, + 'caption' => $item->rendered ?? $item->caption, + 'thumb' => $item->firstMedia()->remote_url, + 'timestamp' => $item->created_at->diffForHumans() + ]]; + } + } + + protected function remoteLookupSearch() + { + if(!Helpers::validateUrl($this->term)) { + return; + } + $this->getProfiles(); + $this->remotePostLookup(); + } +} diff --git a/app/Http/Controllers/Settings/HomeSettings.php b/app/Http/Controllers/Settings/HomeSettings.php index 5279aa5ea..39e3edd87 100644 --- a/app/Http/Controllers/Settings/HomeSettings.php +++ b/app/Http/Controllers/Settings/HomeSettings.php @@ -16,6 +16,7 @@ use Mail; use Purify; use App\Mail\PasswordChange; use Illuminate\Http\Request; +use App\Services\PronounService; trait HomeSettings { @@ -25,23 +26,25 @@ trait HomeSettings $id = Auth::user()->profile->id; $storage = []; $used = Media::whereProfileId($id)->sum('size'); - $storage['limit'] = config('pixelfed.max_account_size') * 1024; + $storage['limit'] = config_cache('pixelfed.max_account_size') * 1024; $storage['used'] = $used; $storage['percentUsed'] = ceil($storage['used'] / $storage['limit'] * 100); $storage['limitPretty'] = PrettyNumber::size($storage['limit']); $storage['usedPretty'] = PrettyNumber::size($storage['used']); + $pronouns = PronounService::get($id); - return view('settings.home', compact('storage')); + return view('settings.home', compact('storage', 'pronouns')); } public function homeUpdate(Request $request) { - $this->validate($request, [ - 'name' => 'required|string|max:'.config('pixelfed.max_name_length'), - 'bio' => 'nullable|string|max:'.config('pixelfed.max_bio_length'), - 'website' => 'nullable|url', - 'language' => 'nullable|string|min:2|max:5' - ]); + $this->validate($request, [ + 'name' => 'required|string|max:'.config('pixelfed.max_name_length'), + 'bio' => 'nullable|string|max:'.config('pixelfed.max_bio_length'), + 'website' => 'nullable|url', + 'language' => 'nullable|string|min:2|max:5', + 'pronouns' => 'nullable|array|max:4' + ]); $changes = false; $name = strip_tags(Purify::clean($request->input('name'))); @@ -50,12 +53,14 @@ trait HomeSettings $language = $request->input('language'); $user = Auth::user(); $profile = $user->profile; + $pronouns = $request->input('pronouns'); + $existingPronouns = PronounService::get($profile->id); $layout = $request->input('profile_layout'); if($layout) { $layout = !in_array($layout, ['metro', 'moment']) ? 'metro' : $layout; } - $enforceEmailVerification = config('pixelfed.enforce_email_verification'); + $enforceEmailVerification = config_cache('pixelfed.enforce_email_verification'); // Only allow email to be updated if not yet verified if (!$enforceEmailVerification || !$changes && $user->email_verified_at) { @@ -82,6 +87,14 @@ trait HomeSettings $user->language = $language; session()->put('locale', $language); } + + if($existingPronouns != $pronouns) { + if($pronouns && in_array('Select Pronoun(s)', $pronouns)) { + PronounService::clear($profile->id); + } else { + PronounService::put($profile->id, $pronouns); + } + } } if ($changes === true) { @@ -152,7 +165,7 @@ trait HomeSettings $user = Auth::user(); $profile = $user->profile; - $validate = config('pixelfed.enforce_email_verification'); + $validate = config_cache('pixelfed.enforce_email_verification'); if ($user->email != $email) { $changes = true; @@ -193,4 +206,4 @@ trait HomeSettings return view('settings.avatar'); } -} \ No newline at end of file +} diff --git a/app/Http/Controllers/SettingsController.php b/app/Http/Controllers/SettingsController.php index 92cddec4a..e84ae0987 100644 --- a/app/Http/Controllers/SettingsController.php +++ b/app/Http/Controllers/SettingsController.php @@ -77,13 +77,13 @@ class SettingsController extends Controller public function dataImport() { - abort_if(!config('pixelfed.import.instagram.enabled'), 404); + abort_if(!config_cache('pixelfed.import.instagram.enabled'), 404); return view('settings.import.home'); } public function dataImportInstagram() { - abort_if(!config('pixelfed.import.instagram.enabled'), 404); + abort_if(!config_cache('pixelfed.import.instagram.enabled'), 404); return view('settings.import.instagram.home'); } diff --git a/app/Http/Controllers/StatusController.php b/app/Http/Controllers/StatusController.php index 4e2d1a16a..985bcfe12 100644 --- a/app/Http/Controllers/StatusController.php +++ b/app/Http/Controllers/StatusController.php @@ -70,11 +70,16 @@ class StatusController extends Controller ]); } - if ($request->wantsJson() && config('federation.activitypub.enabled')) { + if ($request->wantsJson() && config_cache('federation.activitypub.enabled')) { return $this->showActivityPub($request, $status); } $template = $status->in_reply_to_id ? 'status.reply' : 'status.show'; + // $template = $status->type === 'video' && + // $request->has('video_beta') && + // $request->video_beta == 1 && + // $request->user() ? + // 'status.show_video' : 'status.show'; return view($template, compact('user', 'status')); } @@ -340,7 +345,7 @@ class StatusController extends Controller public static function mimeTypeCheck($mimes) { - $allowed = explode(',', config('pixelfed.media_types')); + $allowed = explode(',', config_cache('pixelfed.media_types')); $count = count($mimes); $photos = 0; $videos = 0; diff --git a/app/Http/Controllers/StoryController.php b/app/Http/Controllers/StoryController.php index 7398f7f10..4bcf2d690 100644 --- a/app/Http/Controllers/StoryController.php +++ b/app/Http/Controllers/StoryController.php @@ -21,14 +21,14 @@ class StoryController extends Controller { public function apiV1Add(Request $request) { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); $this->validate($request, [ 'file' => function() { return [ 'required', 'mimes:image/jpeg,image/png,video/mp4', - 'max:' . config('pixelfed.max_photo_size'), + 'max:' . config_cache('pixelfed.max_photo_size'), ]; }, ]); @@ -78,7 +78,7 @@ class StoryController extends Controller protected function storePhoto($photo, $user) { - $mimes = explode(',', config('pixelfed.media_types')); + $mimes = explode(',', config_cache('pixelfed.media_types')); if(in_array($photo->getMimeType(), [ 'image/jpeg', 'image/png', @@ -94,7 +94,7 @@ class StoryController extends Controller $fpath = storage_path('app/' . $path); $img = Intervention::make($fpath); $img->orientate(); - $img->save($fpath, config('pixelfed.image_quality')); + $img->save($fpath, config_cache('pixelfed.image_quality')); $img->destroy(); } return $path; @@ -102,7 +102,7 @@ class StoryController extends Controller public function cropPhoto(Request $request) { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); $this->validate($request, [ 'media_id' => 'required|integer|min:1', @@ -133,7 +133,7 @@ class StoryController extends Controller $img->resize(1080, 1920, function ($constraint) { $constraint->aspectRatio(); }); - $img->save($path, config('pixelfed.image_quality')); + $img->save($path, config_cache('pixelfed.image_quality')); } return [ @@ -144,7 +144,7 @@ class StoryController extends Controller public function publishStory(Request $request) { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); $this->validate($request, [ 'media_id' => 'required', @@ -169,7 +169,7 @@ class StoryController extends Controller public function apiV1Delete(Request $request, $id) { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); $user = $request->user(); @@ -190,7 +190,7 @@ class StoryController extends Controller public function apiV1Recent(Request $request) { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); $profile = $request->user()->profile; $following = $profile->following->pluck('id')->toArray(); @@ -232,7 +232,7 @@ class StoryController extends Controller public function apiV1Fetch(Request $request, $id) { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); $authed = $request->user()->profile; $profile = Profile::findOrFail($id); @@ -270,7 +270,7 @@ class StoryController extends Controller public function apiV1Item(Request $request, $id) { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); $authed = $request->user()->profile; $story = Story::with('profile') @@ -304,7 +304,7 @@ class StoryController extends Controller public function apiV1Profile(Request $request, $id) { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); $authed = $request->user()->profile; $profile = Profile::findOrFail($id); @@ -355,7 +355,7 @@ class StoryController extends Controller public function apiV1Viewed(Request $request) { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); $this->validate($request, [ 'id' => 'required|integer|min:1|exists:stories', @@ -391,7 +391,7 @@ class StoryController extends Controller public function apiV1Exists(Request $request, $id) { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); $res = (bool) Story::whereProfileId($id) ->whereActive(true) @@ -403,7 +403,7 @@ class StoryController extends Controller public function apiV1Me(Request $request) { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); $profile = $request->user()->profile; $stories = Story::whereProfileId($profile->id) @@ -441,14 +441,14 @@ class StoryController extends Controller public function compose(Request $request) { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); return view('stories.compose'); } public function iRedirect(Request $request) { - abort_if(!config('instance.stories.enabled') || !$request->user(), 404); + abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404); $user = $request->user(); abort_if(!$user, 404); diff --git a/app/Http/Middleware/EmailVerificationCheck.php b/app/Http/Middleware/EmailVerificationCheck.php index 50e3da9c7..fab7a0bfb 100644 --- a/app/Http/Middleware/EmailVerificationCheck.php +++ b/app/Http/Middleware/EmailVerificationCheck.php @@ -6,31 +6,31 @@ use Closure; class EmailVerificationCheck { - /** - * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * - * @return mixed - */ - public function handle($request, Closure $next) - { - if ($request->user() && - config('pixelfed.enforce_email_verification') && - is_null($request->user()->email_verified_at) && - !$request->is( - 'i/auth/*', - 'i/verify-email', - 'log*', - 'i/confirm-email/*', - 'settings/home', - 'settings/email' - ) - ) { - return redirect('/i/verify-email'); - } + /** + * Handle an incoming request. + * + * @param \Illuminate\Http\Request $request + * @param \Closure $next + * + * @return mixed + */ + public function handle($request, Closure $next) + { + if ($request->user() && + config_cache('pixelfed.enforce_email_verification') && + is_null($request->user()->email_verified_at) && + !$request->is( + 'i/auth/*', + 'i/verify-email', + 'log*', + 'i/confirm-email/*', + 'settings/home', + 'settings/email' + ) + ) { + return redirect('/i/verify-email'); + } - return $next($request); - } + return $next($request); + } } diff --git a/app/Jobs/AvatarPipeline/AvatarOptimize.php b/app/Jobs/AvatarPipeline/AvatarOptimize.php index b88901f8f..8d5d2446d 100644 --- a/app/Jobs/AvatarPipeline/AvatarOptimize.php +++ b/app/Jobs/AvatarPipeline/AvatarOptimize.php @@ -16,67 +16,67 @@ use Image as Intervention; class AvatarOptimize implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - protected $profile; - protected $current; + protected $profile; + protected $current; - /** - * Delete the job if its models no longer exist. - * - * @var bool - */ - public $deleteWhenMissingModels = true; + /** + * Delete the job if its models no longer exist. + * + * @var bool + */ + public $deleteWhenMissingModels = true; - /** - * Create a new job instance. - * - * @return void - */ - public function __construct(Profile $profile, $current) - { - $this->profile = $profile; - $this->current = $current; - } + /** + * Create a new job instance. + * + * @return void + */ + public function __construct(Profile $profile, $current) + { + $this->profile = $profile; + $this->current = $current; + } - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - $avatar = $this->profile->avatar; - $file = storage_path("app/$avatar->media_path"); + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $avatar = $this->profile->avatar; + $file = storage_path("app/$avatar->media_path"); - try { - $img = Intervention::make($file)->orientate(); - $img->fit(200, 200, function ($constraint) { - $constraint->upsize(); - }); - $quality = config('pixelfed.image_quality'); - $img->save($file, $quality); + try { + $img = Intervention::make($file)->orientate(); + $img->fit(200, 200, function ($constraint) { + $constraint->upsize(); + }); + $quality = config_cache('pixelfed.image_quality'); + $img->save($file, $quality); - $avatar = Avatar::whereProfileId($this->profile->id)->firstOrFail(); - $avatar->change_count = ++$avatar->change_count; - $avatar->last_processed_at = Carbon::now(); - $avatar->save(); - Cache::forget('avatar:' . $avatar->profile_id); - $this->deleteOldAvatar($avatar->media_path, $this->current); - } catch (Exception $e) { - } - } + $avatar = Avatar::whereProfileId($this->profile->id)->firstOrFail(); + $avatar->change_count = ++$avatar->change_count; + $avatar->last_processed_at = Carbon::now(); + $avatar->save(); + Cache::forget('avatar:' . $avatar->profile_id); + $this->deleteOldAvatar($avatar->media_path, $this->current); + } catch (Exception $e) { + } + } - protected function deleteOldAvatar($new, $current) - { - if ( storage_path('app/'.$new) == $current || - Str::endsWith($current, 'avatars/default.png') || - Str::endsWith($current, 'avatars/default.jpg')) - { - return; - } - if (is_file($current)) { - @unlink($current); - } - } + protected function deleteOldAvatar($new, $current) + { + if ( storage_path('app/'.$new) == $current || + Str::endsWith($current, 'avatars/default.png') || + Str::endsWith($current, 'avatars/default.jpg')) + { + return; + } + if (is_file($current)) { + @unlink($current); + } + } } diff --git a/app/Jobs/ImportPipeline/ImportInstagram.php b/app/Jobs/ImportPipeline/ImportInstagram.php index ea7258289..baf5728b7 100644 --- a/app/Jobs/ImportPipeline/ImportInstagram.php +++ b/app/Jobs/ImportPipeline/ImportInstagram.php @@ -49,7 +49,7 @@ class ImportInstagram implements ShouldQueue */ public function handle() { - if(config('pixelfed.import.instagram.enabled') != true) { + if(config_cache('pixelfed.import.instagram.enabled') != true) { return; } diff --git a/app/Jobs/SharePipeline/SharePipeline.php b/app/Jobs/SharePipeline/SharePipeline.php index a3cda16fe..46ed8bf25 100644 --- a/app/Jobs/SharePipeline/SharePipeline.php +++ b/app/Jobs/SharePipeline/SharePipeline.php @@ -18,132 +18,132 @@ use App\Util\ActivityPub\HttpSignature; class SharePipeline implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - - protected $status; - - /** - * Delete the job if its models no longer exist. - * - * @var bool - */ - public $deleteWhenMissingModels = true; - - /** - * Create a new job instance. - * - * @return void - */ - public function __construct(Status $status) - { - $this->status = $status; - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - $status = $this->status; - $actor = $status->profile; - $target = $status->parent()->profile; - - if ($status->uri !== null) { - // Ignore notifications to remote statuses - return; - } - - $exists = Notification::whereProfileId($target->id) - ->whereActorId($status->profile_id) - ->whereAction('share') - ->whereItemId($status->reblog_of_id) - ->whereItemType('App\Status') - ->count(); - - if ($target->id === $status->profile_id) { - $this->remoteAnnounceDeliver(); - return true; - } - - if( $exists !== 0) { - return true; - } - - $this->remoteAnnounceDeliver(); - - try { - $notification = new Notification; - $notification->profile_id = $target->id; - $notification->actor_id = $actor->id; - $notification->action = 'share'; - $notification->message = $status->shareToText(); - $notification->rendered = $status->shareToHtml(); - $notification->item_id = $status->reblog_of_id ?? $status->id; - $notification->item_type = "App\Status"; - $notification->save(); - - $redis = Redis::connection(); - $key = config('cache.prefix').':user.'.$status->profile_id.'.notifications'; - $redis->lpush($key, $notification->id); - } catch (Exception $e) { - Log::error($e); - } - } - - public function remoteAnnounceDeliver() - { - if(config('federation.activitypub.enabled') == false) { - return true; - } - $status = $this->status; - $profile = $status->profile; - - $fractal = new Fractal\Manager(); - $fractal->setSerializer(new ArraySerializer()); - $resource = new Fractal\Resource\Item($status, new Announce()); - $activity = $fractal->createData($resource)->toArray(); - - $audience = $status->profile->getAudienceInbox(); - - if(empty($audience) || $status->scope != 'public') { - // Return on profiles with no remote followers - return; - } - - $payload = json_encode($activity); - - $client = new Client([ - 'timeout' => config('federation.activitypub.delivery.timeout') - ]); - - $requests = function($audience) use ($client, $activity, $profile, $payload) { - foreach($audience as $url) { - $headers = HttpSignature::sign($profile, $url, $activity); - yield function() use ($client, $url, $headers, $payload) { - return $client->postAsync($url, [ - 'curl' => [ - CURLOPT_HTTPHEADER => $headers, - CURLOPT_POSTFIELDS => $payload, - CURLOPT_HEADER => true - ] - ]); - }; - } - }; - - $pool = new Pool($client, $requests($audience), [ - 'concurrency' => config('federation.activitypub.delivery.concurrency'), - 'fulfilled' => function ($response, $index) { - }, - 'rejected' => function ($reason, $index) { - } - ]); - - $promise = $pool->promise(); - - $promise->wait(); - - } + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + + protected $status; + + /** + * Delete the job if its models no longer exist. + * + * @var bool + */ + public $deleteWhenMissingModels = true; + + /** + * Create a new job instance. + * + * @return void + */ + public function __construct(Status $status) + { + $this->status = $status; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $status = $this->status; + $actor = $status->profile; + $target = $status->parent()->profile; + + if ($status->uri !== null) { + // Ignore notifications to remote statuses + return; + } + + $exists = Notification::whereProfileId($target->id) + ->whereActorId($status->profile_id) + ->whereAction('share') + ->whereItemId($status->reblog_of_id) + ->whereItemType('App\Status') + ->count(); + + if ($target->id === $status->profile_id) { + $this->remoteAnnounceDeliver(); + return true; + } + + if( $exists !== 0) { + return true; + } + + $this->remoteAnnounceDeliver(); + + try { + $notification = new Notification; + $notification->profile_id = $target->id; + $notification->actor_id = $actor->id; + $notification->action = 'share'; + $notification->message = $status->shareToText(); + $notification->rendered = $status->shareToHtml(); + $notification->item_id = $status->reblog_of_id ?? $status->id; + $notification->item_type = "App\Status"; + $notification->save(); + + $redis = Redis::connection(); + $key = config('cache.prefix').':user.'.$status->profile_id.'.notifications'; + $redis->lpush($key, $notification->id); + } catch (Exception $e) { + Log::error($e); + } + } + + public function remoteAnnounceDeliver() + { + if(config_cache('federation.activitypub.enabled') == false) { + return true; + } + $status = $this->status; + $profile = $status->profile; + + $fractal = new Fractal\Manager(); + $fractal->setSerializer(new ArraySerializer()); + $resource = new Fractal\Resource\Item($status, new Announce()); + $activity = $fractal->createData($resource)->toArray(); + + $audience = $status->profile->getAudienceInbox(); + + if(empty($audience) || $status->scope != 'public') { + // Return on profiles with no remote followers + return; + } + + $payload = json_encode($activity); + + $client = new Client([ + 'timeout' => config('federation.activitypub.delivery.timeout') + ]); + + $requests = function($audience) use ($client, $activity, $profile, $payload) { + foreach($audience as $url) { + $headers = HttpSignature::sign($profile, $url, $activity); + yield function() use ($client, $url, $headers, $payload) { + return $client->postAsync($url, [ + 'curl' => [ + CURLOPT_HTTPHEADER => $headers, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_HEADER => true + ] + ]); + }; + } + }; + + $pool = new Pool($client, $requests($audience), [ + 'concurrency' => config('federation.activitypub.delivery.concurrency'), + 'fulfilled' => function ($response, $index) { + }, + 'rejected' => function ($reason, $index) { + } + ]); + + $promise = $pool->promise(); + + $promise->wait(); + + } } diff --git a/app/Jobs/StatusPipeline/StatusDelete.php b/app/Jobs/StatusPipeline/StatusDelete.php index 4f29d6b15..5202d79cf 100644 --- a/app/Jobs/StatusPipeline/StatusDelete.php +++ b/app/Jobs/StatusPipeline/StatusDelete.php @@ -4,13 +4,14 @@ namespace App\Jobs\StatusPipeline; use DB, Storage; use App\{ - AccountInterstitial, - MediaTag, - Notification, - Report, - Status, - StatusHashtag, + AccountInterstitial, + MediaTag, + Notification, + Report, + Status, + StatusHashtag, }; +use App\Models\StatusVideo; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -30,150 +31,149 @@ use App\Services\MediaStorageService; class StatusDelete implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - - protected $status; - - /** - * Delete the job if its models no longer exist. - * - * @var bool - */ - public $deleteWhenMissingModels = true; - - /** - * Create a new job instance. - * - * @return void - */ - public function __construct(Status $status) - { - $this->status = $status; - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - $status = $this->status; - $profile = $this->status->profile; - - StatusService::del($status->id); - $count = $profile->statuses() - ->getQuery() - ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) - ->whereNull('in_reply_to_id') - ->whereNull('reblog_of_id') - ->count(); - - $profile->status_count = ($count - 1); - $profile->save(); - - if(config('federation.activitypub.enabled') == true) { - $this->fanoutDelete($status); - } else { - $this->unlinkRemoveMedia($status); - } - - } - - public function unlinkRemoveMedia($status) - { - foreach ($status->media as $media) { - MediaStorageService::delete($media, true); - } - - if($status->in_reply_to_id) { - DB::transaction(function() use($status) { - $parent = Status::findOrFail($status->in_reply_to_id); - --$parent->reply_count; - $parent->save(); - }); - } - DB::transaction(function() use($status) { - $comments = Status::where('in_reply_to_id', $status->id)->get(); - foreach ($comments as $comment) { - $comment->in_reply_to_id = null; - $comment->save(); - Notification::whereItemType('App\Status') - ->whereItemId($comment->id) - ->delete(); - } - $status->likes()->delete(); - Notification::whereItemType('App\Status') - ->whereItemId($status->id) - ->delete(); - StatusHashtag::whereStatusId($status->id)->delete(); - Report::whereObjectType('App\Status') - ->whereObjectId($status->id) - ->delete(); - - MediaTag::where('status_id', $status->id) - ->cursor() - ->each(function($tag) { - Notification::where('item_type', 'App\MediaTag') - ->where('item_id', $tag->id) - ->forceDelete(); - $tag->delete(); - }); - - AccountInterstitial::where('item_type', 'App\Status') - ->where('item_id', $status->id) - ->delete(); - - $status->forceDelete(); - }); - - return true; - } - - protected function fanoutDelete($status) - { - $audience = $status->profile->getAudienceInbox(); - $profile = $status->profile; - - $fractal = new Fractal\Manager(); - $fractal->setSerializer(new ArraySerializer()); - $resource = new Fractal\Resource\Item($status, new DeleteNote()); - $activity = $fractal->createData($resource)->toArray(); - - $this->unlinkRemoveMedia($status); - - $payload = json_encode($activity); - - $client = new Client([ - 'timeout' => config('federation.activitypub.delivery.timeout') - ]); - - $requests = function($audience) use ($client, $activity, $profile, $payload) { - foreach($audience as $url) { - $headers = HttpSignature::sign($profile, $url, $activity); - yield function() use ($client, $url, $headers, $payload) { - return $client->postAsync($url, [ - 'curl' => [ - CURLOPT_HTTPHEADER => $headers, - CURLOPT_POSTFIELDS => $payload, - CURLOPT_HEADER => true - ] - ]); - }; - } - }; - - $pool = new Pool($client, $requests($audience), [ - 'concurrency' => config('federation.activitypub.delivery.concurrency'), - 'fulfilled' => function ($response, $index) { - }, - 'rejected' => function ($reason, $index) { - } - ]); - - $promise = $pool->promise(); - - $promise->wait(); - - } + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + + protected $status; + + /** + * Delete the job if its models no longer exist. + * + * @var bool + */ + public $deleteWhenMissingModels = true; + + /** + * Create a new job instance. + * + * @return void + */ + public function __construct(Status $status) + { + $this->status = $status; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $status = $this->status; + $profile = $this->status->profile; + + StatusService::del($status->id); + $count = $profile->statuses() + ->getQuery() + ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) + ->whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->count(); + + $profile->status_count = ($count - 1); + $profile->save(); + + if(config_cache('federation.activitypub.enabled') == true) { + $this->fanoutDelete($status); + } else { + $this->unlinkRemoveMedia($status); + } + + } + + public function unlinkRemoveMedia($status) + { + foreach ($status->media as $media) { + MediaStorageService::delete($media, true); + } + + if($status->in_reply_to_id) { + DB::transaction(function() use($status) { + $parent = Status::findOrFail($status->in_reply_to_id); + --$parent->reply_count; + $parent->save(); + }); + } + DB::transaction(function() use($status) { + $comments = Status::where('in_reply_to_id', $status->id)->get(); + foreach ($comments as $comment) { + $comment->in_reply_to_id = null; + $comment->save(); + Notification::whereItemType('App\Status') + ->whereItemId($comment->id) + ->delete(); + } + $status->likes()->delete(); + Notification::whereItemType('App\Status') + ->whereItemId($status->id) + ->delete(); + StatusHashtag::whereStatusId($status->id)->delete(); + Report::whereObjectType('App\Status') + ->whereObjectId($status->id) + ->delete(); + MediaTag::where('status_id', $status->id) + ->cursor() + ->each(function($tag) { + Notification::where('item_type', 'App\MediaTag') + ->where('item_id', $tag->id) + ->forceDelete(); + $tag->delete(); + }); + StatusVideo::whereStatusId($status->id)->delete(); + AccountInterstitial::where('item_type', 'App\Status') + ->where('item_id', $status->id) + ->delete(); + + $status->forceDelete(); + }); + + return true; + } + + protected function fanoutDelete($status) + { + $audience = $status->profile->getAudienceInbox(); + $profile = $status->profile; + + $fractal = new Fractal\Manager(); + $fractal->setSerializer(new ArraySerializer()); + $resource = new Fractal\Resource\Item($status, new DeleteNote()); + $activity = $fractal->createData($resource)->toArray(); + + $this->unlinkRemoveMedia($status); + + $payload = json_encode($activity); + + $client = new Client([ + 'timeout' => config('federation.activitypub.delivery.timeout') + ]); + + $requests = function($audience) use ($client, $activity, $profile, $payload) { + foreach($audience as $url) { + $headers = HttpSignature::sign($profile, $url, $activity); + yield function() use ($client, $url, $headers, $payload) { + return $client->postAsync($url, [ + 'curl' => [ + CURLOPT_HTTPHEADER => $headers, + CURLOPT_POSTFIELDS => $payload, + CURLOPT_HEADER => true + ] + ]); + }; + } + }; + + $pool = new Pool($client, $requests($audience), [ + 'concurrency' => config('federation.activitypub.delivery.concurrency'), + 'fulfilled' => function ($response, $index) { + }, + 'rejected' => function ($reason, $index) { + } + ]); + + $promise = $pool->promise(); + + $promise->wait(); + + } } diff --git a/app/Jobs/StatusPipeline/StatusEntityLexer.php b/app/Jobs/StatusPipeline/StatusEntityLexer.php index e173c6394..a078e0d0f 100644 --- a/app/Jobs/StatusPipeline/StatusEntityLexer.php +++ b/app/Jobs/StatusPipeline/StatusEntityLexer.php @@ -21,146 +21,146 @@ use Illuminate\Queue\SerializesModels; class StatusEntityLexer implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - - protected $status; - protected $entities; - protected $autolink; - - /** - * Delete the job if its models no longer exist. - * - * @var bool - */ - public $deleteWhenMissingModels = true; - - /** - * Create a new job instance. - * - * @return void - */ - public function __construct(Status $status) - { - $this->status = $status; - } - - /** - * Execute the job. - * - * @return void - */ - public function handle() - { - $profile = $this->status->profile; - - $count = $profile->statuses() - ->getQuery() - ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) - ->whereNull('in_reply_to_id') - ->whereNull('reblog_of_id') - ->count(); - - $profile->status_count = $count; - $profile->save(); - - if($profile->no_autolink == false) { - $this->parseEntities(); - } - } - - public function parseEntities() - { - $this->extractEntities(); - } - - public function extractEntities() - { - $this->entities = Extractor::create()->extract($this->status->caption); - $this->autolinkStatus(); - } - - public function autolinkStatus() - { - $this->autolink = Autolink::create()->autolink($this->status->caption); - $this->storeEntities(); - } - - public function storeEntities() - { - $this->storeHashtags(); - DB::transaction(function () { - $status = $this->status; - $status->rendered = nl2br($this->autolink); - $status->entities = json_encode($this->entities); - $status->save(); - }); - } - - public function storeHashtags() - { - $tags = array_unique($this->entities['hashtags']); - $status = $this->status; - - foreach ($tags as $tag) { - if(mb_strlen($tag) > 124) { - continue; - } - DB::transaction(function () use ($status, $tag) { - $slug = str_slug($tag, '-', false); - $hashtag = Hashtag::firstOrCreate( - ['name' => $tag, 'slug' => $slug] - ); - StatusHashtag::firstOrCreate( - [ - 'status_id' => $status->id, - 'hashtag_id' => $hashtag->id, - 'profile_id' => $status->profile_id, - 'status_visibility' => $status->visibility, - ] - ); - }); - } - $this->storeMentions(); - } - - public function storeMentions() - { - $mentions = array_unique($this->entities['mentions']); - $status = $this->status; - - foreach ($mentions as $mention) { - $mentioned = Profile::whereUsername($mention)->first(); - - if (empty($mentioned) || !isset($mentioned->id)) { - continue; - } - - DB::transaction(function () use ($status, $mentioned) { - $m = new Mention(); - $m->status_id = $status->id; - $m->profile_id = $mentioned->id; - $m->save(); - - MentionPipeline::dispatch($status, $m); - }); - } - $this->deliver(); - } - - public function deliver() - { - $status = $this->status; - - if(config('pixelfed.bouncer.enabled')) { - Bouncer::get($status); - } - - if($status->uri == null && $status->scope == 'public') { - PublicTimelineService::add($status->id); - } - - if(config('federation.activitypub.enabled') == true && config('app.env') == 'production') { - StatusActivityPubDeliver::dispatch($this->status); - } - } + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + + protected $status; + protected $entities; + protected $autolink; + + /** + * Delete the job if its models no longer exist. + * + * @var bool + */ + public $deleteWhenMissingModels = true; + + /** + * Create a new job instance. + * + * @return void + */ + public function __construct(Status $status) + { + $this->status = $status; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $profile = $this->status->profile; + + $count = $profile->statuses() + ->getQuery() + ->whereIn('type', ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) + ->whereNull('in_reply_to_id') + ->whereNull('reblog_of_id') + ->count(); + + $profile->status_count = $count; + $profile->save(); + + if($profile->no_autolink == false) { + $this->parseEntities(); + } + } + + public function parseEntities() + { + $this->extractEntities(); + } + + public function extractEntities() + { + $this->entities = Extractor::create()->extract($this->status->caption); + $this->autolinkStatus(); + } + + public function autolinkStatus() + { + $this->autolink = Autolink::create()->autolink($this->status->caption); + $this->storeEntities(); + } + + public function storeEntities() + { + $this->storeHashtags(); + DB::transaction(function () { + $status = $this->status; + $status->rendered = nl2br($this->autolink); + $status->entities = json_encode($this->entities); + $status->save(); + }); + } + + public function storeHashtags() + { + $tags = array_unique($this->entities['hashtags']); + $status = $this->status; + + foreach ($tags as $tag) { + if(mb_strlen($tag) > 124) { + continue; + } + DB::transaction(function () use ($status, $tag) { + $slug = str_slug($tag, '-', false); + $hashtag = Hashtag::firstOrCreate( + ['name' => $tag, 'slug' => $slug] + ); + StatusHashtag::firstOrCreate( + [ + 'status_id' => $status->id, + 'hashtag_id' => $hashtag->id, + 'profile_id' => $status->profile_id, + 'status_visibility' => $status->visibility, + ] + ); + }); + } + $this->storeMentions(); + } + + public function storeMentions() + { + $mentions = array_unique($this->entities['mentions']); + $status = $this->status; + + foreach ($mentions as $mention) { + $mentioned = Profile::whereUsername($mention)->first(); + + if (empty($mentioned) || !isset($mentioned->id)) { + continue; + } + + DB::transaction(function () use ($status, $mentioned) { + $m = new Mention(); + $m->status_id = $status->id; + $m->profile_id = $mentioned->id; + $m->save(); + + MentionPipeline::dispatch($status, $m); + }); + } + $this->deliver(); + } + + public function deliver() + { + $status = $this->status; + + if(config_cache('pixelfed.bouncer.enabled')) { + Bouncer::get($status); + } + + if($status->uri == null && $status->scope == 'public') { + PublicTimelineService::add($status->id); + } + + if(config_cache('federation.activitypub.enabled') == true && config('app.env') == 'production') { + StatusActivityPubDeliver::dispatch($this->status); + } + } } diff --git a/app/Models/ConfigCache.php b/app/Models/ConfigCache.php new file mode 100644 index 000000000..4698b1c6b --- /dev/null +++ b/app/Models/ConfigCache.php @@ -0,0 +1,14 @@ +registerPolicies(); - if(config('pixelfed.oauth_enabled')) { + if(config_cache('pixelfed.oauth_enabled')) { Passport::routes(null, ['middleware' => ['twofactor', \Fruitcake\Cors\HandleCors::class]]); Passport::tokensExpireIn(now()->addDays(config('instance.oauth.token_expiration', 15))); Passport::refreshTokensExpireIn(now()->addDays(config('instance.oauth.refresh_expiration', 30))); diff --git a/app/Services/ConfigCacheService.php b/app/Services/ConfigCacheService.php new file mode 100644 index 000000000..7c1f0c9ab --- /dev/null +++ b/app/Services/ConfigCacheService.php @@ -0,0 +1,95 @@ +addHours(12); + return Cache::remember($cacheKey, $ttl, function() use($key) { + + $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.open_registration', + 'federation.activitypub.enabled', + 'pixelfed.oauth_enabled', + 'instance.stories.enabled', + 'pixelfed.import.instagram.enabled', + 'pixelfed.bouncer.enabled', + + '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' + ]; + + if(!config('instance.enable_cc')) { + return config($key); + } + + if(!in_array($key, $allowed)) { + return config($key); + } + + $v = config($key); + $c = ConfigCacheModel::where('k', $key)->first(); + + if($c) { + return $c->v ?? config($key); + } + + if(!$v) { + return; + } + + $cc = new ConfigCacheModel; + $cc->k = $key; + $cc->v = $v; + $cc->save(); + + return $v; + }); + } + + public static function put($key, $val) + { + $exists = ConfigCacheModel::whereK($key)->first(); + + if($exists) { + $exists->v = $val; + $exists->save(); + Cache::forget(self::CACHE_KEY . $key); + return self::get($key); + } + + $cc = new ConfigCacheModel; + $cc->k = $key; + $cc->v = $val; + $cc->save(); + + Cache::forget(self::CACHE_KEY . $key); + + return self::get($key); + } +} diff --git a/app/Services/MediaStorageService.php b/app/Services/MediaStorageService.php index 1618af1a6..17e866da6 100644 --- a/app/Services/MediaStorageService.php +++ b/app/Services/MediaStorageService.php @@ -43,11 +43,11 @@ class MediaStorageService { $h = $r->getHeaders(); - if (isset($h['Content-Length'], $h['Content-Type']) == false || + if (isset($h['Content-Length'], $h['Content-Type']) == false || empty($h['Content-Length']) || - empty($h['Content-Type']) || + empty($h['Content-Type']) || $h['Content-Length'] < 10 || - $h['Content-Length'] > (config('pixelfed.max_photo_size') * 1000) + $h['Content-Length'] > (config_cache('pixelfed.max_photo_size') * 1000) ) { return false; } @@ -77,7 +77,7 @@ class MediaStorageService { $pt = explode('/', $media->thumbnail_path); $thumbname = array_pop($pt); $storagePath = implode('/', $p); - + $disk = Storage::disk(config('filesystems.cloud')); $file = $disk->putFileAs($storagePath, new File($path), $name, 'public'); $url = $disk->url($file); @@ -102,11 +102,11 @@ class MediaStorageService { } $head = $this->head($media->remote_url); - + if(!$head) { return; } - + $mimes = [ 'image/jpeg', 'image/png', @@ -114,7 +114,7 @@ class MediaStorageService { ]; $mime = $head['mime']; - $max_size = (int) config('pixelfed.max_photo_size') * 1000; + $max_size = (int) config_cache('pixelfed.max_photo_size') * 1000; $media->size = $head['length']; $media->remote_media = true; $media->save(); @@ -247,4 +247,4 @@ class MediaStorageService { } MediaDeletePipeline::dispatch($media); } -} \ No newline at end of file +} diff --git a/app/Services/PronounService.php b/app/Services/PronounService.php new file mode 100644 index 000000000..f8185b97b --- /dev/null +++ b/app/Services/PronounService.php @@ -0,0 +1,102 @@ +addHours(12); + + return Cache::remember($key, $ttl, function() use($id) { + $res = UserPronoun::whereProfileId($id)->first(); + return $res ? json_decode($res->pronouns, true) : []; + }); + } + + public static function put($id, $pronouns) + { + $res = UserPronoun::whereProfileId($id)->first(); + $key = 'user:pronouns:' . $id; + + if($res) { + $res->pronouns = json_encode($pronouns); + $res->save(); + Cache::forget($key); + AccountService::del($id); + return $res->pronouns; + } + + $res = new UserPronoun; + $res->profile_id = $id; + $res->pronouns = json_encode($pronouns); + $res->save(); + Cache::forget($key); + AccountService::del($id); + return $res->pronouns; + } + + public static function clear($id) + { + $res = UserPronoun::whereProfileId($id)->first(); + if($res) { + $res->pronouns = null; + $res->save(); + } + $key = 'user:pronouns:' . $id; + Cache::forget($key); + AccountService::del($id); + } + + public static function pronouns() + { + return [ + 'co', + 'cos', + 'e', + 'ey', + 'em', + 'eir', + 'fae', + 'faer', + 'he', + 'him', + 'his', + 'her', + 'hers', + 'hir', + 'mer', + 'mers', + 'ne', + 'nir', + 'nirs', + 'nee', + 'ner', + 'ners', + 'per', + 'pers', + 'she', + 'they', + 'them', + 'theirs', + 'thon', + 'thons', + 've', + 'ver', + 'vis', + 'vi', + 'vir', + 'xe', + 'xem', + 'xyr', + 'ze', + 'zir', + 'zie' + ]; + } +} diff --git a/app/Transformer/Api/AccountTransformer.php b/app/Transformer/Api/AccountTransformer.php index 16a45b97c..4b8e16976 100644 --- a/app/Transformer/Api/AccountTransformer.php +++ b/app/Transformer/Api/AccountTransformer.php @@ -5,6 +5,7 @@ namespace App\Transformer\Api; use Auth; use App\Profile; use League\Fractal; +use App\Services\PronounService; class AccountTransformer extends Fractal\TransformerAbstract { @@ -35,7 +36,8 @@ class AccountTransformer extends Fractal\TransformerAbstract 'is_admin' => (bool) $is_admin, 'created_at' => $profile->created_at->toJSON(), 'header_bg' => $profile->header_bg, - 'last_fetched_at' => optional($profile->last_fetched_at)->toJSON() + 'last_fetched_at' => optional($profile->last_fetched_at)->toJSON(), + 'pronouns' => PronounService::get($profile->id) ]; } diff --git a/app/Util/ActivityPub/Helpers.php b/app/Util/ActivityPub/Helpers.php index 512ca4268..c5c176fc3 100644 --- a/app/Util/ActivityPub/Helpers.php +++ b/app/Util/ActivityPub/Helpers.php @@ -63,7 +63,7 @@ class Helpers { $activity = $data['object']; - $mimeTypes = explode(',', config('pixelfed.media_types')); + $mimeTypes = explode(',', config_cache('pixelfed.media_types')); $mediaTypes = in_array('video/mp4', $mimeTypes) ? ['Document', 'Image', 'Video'] : ['Document', 'Image']; if(!isset($activity['attachment']) || empty($activity['attachment'])) { @@ -418,7 +418,7 @@ class Helpers { $attachments = isset($data['object']) ? $data['object']['attachment'] : $data['attachment']; $user = $status->profile; $storagePath = MediaPathService::get($user, 2); - $allowed = explode(',', config('pixelfed.media_types')); + $allowed = explode(',', config_cache('pixelfed.media_types')); foreach($attachments as $media) { $type = $media['mediaType']; diff --git a/app/Util/ActivityPub/Inbox.php b/app/Util/ActivityPub/Inbox.php index 58ac45d64..524edc21f 100644 --- a/app/Util/ActivityPub/Inbox.php +++ b/app/Util/ActivityPub/Inbox.php @@ -115,8 +115,8 @@ class Inbox { $activity = $this->payload['object']; - if(isset($activity['inReplyTo']) && - !empty($activity['inReplyTo']) && + if(isset($activity['inReplyTo']) && + !empty($activity['inReplyTo']) && Helpers::validateUrl($activity['inReplyTo']) ) { // reply detected, skip attachment check @@ -147,8 +147,8 @@ class Inbox } $to = $activity['to']; $cc = isset($activity['cc']) ? $activity['cc'] : []; - if(count($to) == 1 && - count($cc) == 0 && + if(count($to) == 1 && + count($cc) == 0 && parse_url($to[0], PHP_URL_HOST) == config('pixelfed.domain.app') ) { $this->handleDirectMessage(); @@ -175,7 +175,7 @@ class Inbox $inReplyTo = $activity['inReplyTo']; $url = isset($activity['url']) ? $activity['url'] : $activity['id']; - + Helpers::statusFirstOrFetch($url, true); return; } @@ -251,8 +251,8 @@ class Inbox if(count($activity['attachment'])) { $photos = 0; $videos = 0; - $allowed = explode(',', config('pixelfed.media_types')); - $activity['attachment'] = array_slice($activity['attachment'], 0, config('pixelfed.max_album_length')); + $allowed = explode(',', config_cache('pixelfed.media_types')); + $activity['attachment'] = array_slice($activity['attachment'], 0, config_cache('pixelfed.max_album_length')); foreach($activity['attachment'] as $a) { $type = $a['mediaType']; $url = $a['url']; @@ -293,7 +293,7 @@ class Inbox $dm->type = 'link'; $dm->meta = [ 'domain' => parse_url($msgText, PHP_URL_HOST), - 'local' => parse_url($msgText, PHP_URL_HOST) == + 'local' => parse_url($msgText, PHP_URL_HOST) == parse_url(config('app.url'), PHP_URL_HOST) ]; $dm->save(); @@ -459,8 +459,8 @@ class Inbox public function handleDeleteActivity() { if(!isset( - $this->payload['actor'], - $this->payload['object'] + $this->payload['actor'], + $this->payload['object'] )) { return; } @@ -510,7 +510,7 @@ class Inbox $status->delete(); return; break; - + default: return; break; @@ -564,7 +564,7 @@ class Inbox switch ($obj['type']) { case 'Accept': break; - + case 'Announce': $obj = $obj['object']; if(!Helpers::validateLocalUrl($obj)) { @@ -603,7 +603,7 @@ class Inbox ->whereItemType('App\Profile') ->forceDelete(); break; - + case 'Like': $status = Helpers::statusFirstOrFetch($obj['object']); if(!$status) { diff --git a/app/Util/ActivityPub/Outbox.php b/app/Util/ActivityPub/Outbox.php index 3e5f78a74..43adb36e3 100644 --- a/app/Util/ActivityPub/Outbox.php +++ b/app/Util/ActivityPub/Outbox.php @@ -13,7 +13,7 @@ class Outbox { public static function get($profile) { - abort_if(!config('federation.activitypub.enabled'), 404); + abort_if(!config_cache('federation.activitypub.enabled'), 404); abort_if(!config('federation.activitypub.outbox'), 404); if($profile->status != null) { @@ -48,4 +48,4 @@ class Outbox { return $outbox; } -} \ No newline at end of file +} diff --git a/app/Util/Media/Image.php b/app/Util/Media/Image.php index 7e41344f4..2f4414de3 100644 --- a/app/Util/Media/Image.php +++ b/app/Util/Media/Image.php @@ -154,7 +154,7 @@ class Image } } $media->metadata = json_encode($meta); - } + } $img->resize($aspect['width'], $aspect['height'], function ($constraint) { $constraint->aspectRatio(); @@ -163,7 +163,7 @@ class Image $converted = $this->setBaseName($path, $thumbnail, $img->extension); $newPath = storage_path('app/'.$converted['path']); - $quality = config('pixelfed.image_quality'); + $quality = config_cache('pixelfed.image_quality'); $img->save($newPath, $quality); if ($thumbnail == true) { diff --git a/app/Util/Site/Config.php b/app/Util/Site/Config.php index a68a03cf8..d9836ca70 100644 --- a/app/Util/Site/Config.php +++ b/app/Util/Site/Config.php @@ -8,26 +8,26 @@ use Illuminate\Support\Str; class Config { public static function get() { - return Cache::remember('api:site:configuration:_v0.2', now()->addHours(30), function() { + return Cache::remember('api:site:configuration:_v0.2', now()->addMinutes(5), function() { return [ - 'open_registration' => config('pixelfed.open_registration'), + 'open_registration' => (bool) config_cache('pixelfed.open_registration'), 'uploader' => [ 'max_photo_size' => config('pixelfed.max_photo_size'), 'max_caption_length' => config('pixelfed.max_caption_length'), - 'album_limit' => config('pixelfed.max_album_length'), - 'image_quality' => config('pixelfed.image_quality'), + 'album_limit' => config_cache('pixelfed.max_album_length'), + 'image_quality' => config_cache('pixelfed.image_quality'), 'max_collection_length' => config('pixelfed.max_collection_length', 18), 'optimize_image' => config('pixelfed.optimize_image'), 'optimize_video' => config('pixelfed.optimize_video'), - 'media_types' => config('pixelfed.media_types'), - 'enforce_account_limit' => config('pixelfed.enforce_account_limit') + 'media_types' => config_cache('pixelfed.media_types'), + 'enforce_account_limit' => config_cache('pixelfed.enforce_account_limit') ], 'activitypub' => [ - 'enabled' => config('federation.activitypub.enabled'), + 'enabled' => config_cache('federation.activitypub.enabled'), 'remote_follow' => config('federation.activitypub.remoteFollow') ], @@ -39,10 +39,10 @@ class Config { ], 'site' => [ - 'name' => config('app.name', 'pixelfed'), + 'name' => config_cache('app.name'), 'domain' => config('pixelfed.domain.app'), 'url' => config('app.url'), - 'description' => config('instance.description') + 'description' => config_cache('app.short_description') ], 'username' => [ @@ -54,12 +54,12 @@ class Config { ], 'features' => [ - 'mobile_apis' => config('pixelfed.oauth_enabled'), + 'mobile_apis' => config_cache('pixelfed.oauth_enabled'), 'circles' => false, - 'stories' => config('instance.stories.enabled'), - 'video' => Str::contains(config('pixelfed.media_types'), 'video/mp4'), + 'stories' => config_cache('instance.stories.enabled'), + 'video' => Str::contains(config_cache('pixelfed.media_types'), 'video/mp4'), 'import' => [ - 'instagram' => config('pixelfed.import.instagram.enabled'), + 'instagram' => config_cache('pixelfed.import.instagram.enabled'), 'mastodon' => false, 'pixelfed' => false ], diff --git a/app/Util/Site/Nodeinfo.php b/app/Util/Site/Nodeinfo.php index 022615e37..36ac72729 100644 --- a/app/Util/Site/Nodeinfo.php +++ b/app/Util/Site/Nodeinfo.php @@ -10,68 +10,68 @@ class Nodeinfo { public static function get() { - $res = Cache::remember('api:nodeinfo', now()->addMinutes(15), function () { - $activeHalfYear = Cache::remember('api:nodeinfo:ahy', now()->addHours(12), function() { - // todo: replace with last_active_at after July 9, 2021 (96afc3e781) - $count = collect([]); - $likes = Like::select('profile_id')->with('actor')->where('created_at', '>', now()->subMonths(6)->toDateTimeString())->groupBy('profile_id')->get()->filter(function($like) {return $like->actor && $like->actor->domain == null;})->pluck('profile_id')->toArray(); - $count = $count->merge($likes); - $statuses = Status::select('profile_id')->whereLocal(true)->where('created_at', '>', now()->subMonths(6)->toDateTimeString())->groupBy('profile_id')->pluck('profile_id')->toArray(); - $count = $count->merge($statuses); - $profiles = User::select('profile_id', 'last_active_at') - ->whereNotNull('last_active_at') - ->where('last_active_at', '>', now()->subMonths(6)) - ->pluck('profile_id') - ->toArray(); - $newProfiles = User::select('profile_id', 'last_active_at', 'created_at') - ->whereNull('last_active_at') - ->where('created_at', '>', now()->subMonths(6)) - ->pluck('profile_id') - ->toArray(); - $count = $count->merge($newProfiles); - $count = $count->merge($profiles); - return $count->unique()->count(); - }); - $activeMonth = Cache::remember('api:nodeinfo:am', now()->addHours(2), function() { - return User::select('last_active_at') - ->where('last_active_at', '>', now()->subMonths(1)) - ->orWhere('created_at', '>', now()->subMonths(1)) - ->count(); - }); - return [ - 'metadata' => [ - 'nodeName' => config('pixelfed.domain.app'), - 'software' => [ - 'homepage' => 'https://pixelfed.org', - 'repo' => 'https://github.com/pixelfed/pixelfed', - ], - 'config' => \App\Util\Site\Config::get() - ], - 'protocols' => [ - 'activitypub', - ], - 'services' => [ - 'inbound' => [], - 'outbound' => [], - ], - 'software' => [ - 'name' => 'pixelfed', - 'version' => config('pixelfed.version'), - ], - 'usage' => [ - 'localPosts' => Status::whereLocal(true)->count(), - 'localComments' => 0, - 'users' => [ - 'total' => User::count(), - 'activeHalfyear' => (int) $activeHalfYear, - 'activeMonth' => (int) $activeMonth, - ], - ], - 'version' => '2.0', - ]; - }); - $res['openRegistrations'] = config('pixelfed.open_registration'); - return $res; + $res = Cache::remember('api:nodeinfo', now()->addMinutes(15), function () { + $activeHalfYear = Cache::remember('api:nodeinfo:ahy', now()->addHours(12), function() { + // todo: replace with last_active_at after July 9, 2021 (96afc3e781) + $count = collect([]); + $likes = Like::select('profile_id')->with('actor')->where('created_at', '>', now()->subMonths(6)->toDateTimeString())->groupBy('profile_id')->get()->filter(function($like) {return $like->actor && $like->actor->domain == null;})->pluck('profile_id')->toArray(); + $count = $count->merge($likes); + $statuses = Status::select('profile_id')->whereLocal(true)->where('created_at', '>', now()->subMonths(6)->toDateTimeString())->groupBy('profile_id')->pluck('profile_id')->toArray(); + $count = $count->merge($statuses); + $profiles = User::select('profile_id', 'last_active_at') + ->whereNotNull('last_active_at') + ->where('last_active_at', '>', now()->subMonths(6)) + ->pluck('profile_id') + ->toArray(); + $newProfiles = User::select('profile_id', 'last_active_at', 'created_at') + ->whereNull('last_active_at') + ->where('created_at', '>', now()->subMonths(6)) + ->pluck('profile_id') + ->toArray(); + $count = $count->merge($newProfiles); + $count = $count->merge($profiles); + return $count->unique()->count(); + }); + $activeMonth = Cache::remember('api:nodeinfo:am', now()->addHours(2), function() { + return User::select('last_active_at') + ->where('last_active_at', '>', now()->subMonths(1)) + ->orWhere('created_at', '>', now()->subMonths(1)) + ->count(); + }); + return [ + 'metadata' => [ + 'nodeName' => config_cache('app.name'), + 'software' => [ + 'homepage' => 'https://pixelfed.org', + 'repo' => 'https://github.com/pixelfed/pixelfed', + ], + 'config' => \App\Util\Site\Config::get() + ], + 'protocols' => [ + 'activitypub', + ], + 'services' => [ + 'inbound' => [], + 'outbound' => [], + ], + 'software' => [ + 'name' => 'pixelfed', + 'version' => config('pixelfed.version'), + ], + 'usage' => [ + 'localPosts' => Status::whereLocal(true)->count(), + 'localComments' => 0, + 'users' => [ + 'total' => User::count(), + 'activeHalfyear' => (int) $activeHalfYear, + 'activeMonth' => (int) $activeMonth, + ], + ], + 'version' => '2.0', + ]; + }); + $res['openRegistrations'] = (bool) config_cache('pixelfed.open_registration'); + return $res; } public static function wellKnown() @@ -86,4 +86,4 @@ class Nodeinfo { ]; } -} \ No newline at end of file +} diff --git a/app/helpers.php b/app/helpers.php new file mode 100644 index 000000000..eddc95f96 --- /dev/null +++ b/app/helpers.php @@ -0,0 +1,9 @@ + 'AES-256-CBC', + 'short_description' => 'Pixelfed - Photo sharing for everyone', + 'description' => 'Pixelfed - Photo sharing for everyone', + 'rules' => null, + 'logo' => '/img/pixelfed-icon-color.svg', + /* |-------------------------------------------------------------------------- | Autoloaded Service Providers diff --git a/config/instance.php b/config/instance.php index 30bcc4c4c..a4cc534b9 100644 --- a/config/instance.php +++ b/config/instance.php @@ -72,4 +72,6 @@ return [ 'org' => env('COVID_LABEL_ORG', 'visit the WHO website') ] ], + + 'enable_cc' => env('ENABLE_CONFIG_CACHE', false) ]; diff --git a/database/migrations/2021_04_28_060450_create_config_caches_table.php b/database/migrations/2021_04_28_060450_create_config_caches_table.php new file mode 100644 index 000000000..aab7ab782 --- /dev/null +++ b/database/migrations/2021_04_28_060450_create_config_caches_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('k')->unique()->index(); + $table->text('v')->nullable(); + $table->json('metadata')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('config_cache'); + } +} diff --git a/database/migrations/2021_05_12_042153_create_user_pronouns_table.php b/database/migrations/2021_05_12_042153_create_user_pronouns_table.php new file mode 100644 index 000000000..9e394e2b1 --- /dev/null +++ b/database/migrations/2021_05_12_042153_create_user_pronouns_table.php @@ -0,0 +1,34 @@ +id(); + $table->unsignedInteger('user_id')->nullable()->unique()->index(); + $table->bigInteger('profile_id')->unique()->index(); + $table->json('pronouns')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('user_pronouns'); + } +} diff --git a/public/js/profile.js b/public/js/profile.js index 2638697a4..496ec1b0f 100644 --- a/public/js/profile.js +++ b/public/js/profile.js @@ -1 +1 @@ -(window.webpackJsonp=window.webpackJsonp||[]).push([[17],{"2Jpm":function(t,e,n){"use strict";n.r(e);var i={props:["status"],methods:{playOrPause:function(t){var e=t.target;1==e.getAttribute("playing")?(e.removeAttribute("playing"),e.pause()):(e.setAttribute("playing",1),e.play())}}},o=n("KHd+"),s=Object(o.a)(i,(function(){var t=this,e=t.$createElement,n=t._self._c||e;return 1==t.status.sensitive?n("div",[n("details",{staticClass:"details-animated"},[n("summary",[n("p",{staticClass:"mb-0 lead font-weight-bold"},[t._v(t._s(t.status.spoiler_text?t.status.spoiler_text:"CW / NSFW / Hidden Media"))]),t._v(" "),n("p",{staticClass:"font-weight-light"},[t._v("(click to show)")])]),t._v(" "),n("div",{staticClass:"embed-responsive embed-responsive-1by1"},[n("video",{staticClass:"video",attrs:{preload:"none",loop:"",poster:t.status.media_attachments[0].preview_url,"data-id":t.status.id},on:{click:function(e){return t.playOrPause(e)}}},[n("source",{attrs:{src:t.status.media_attachments[0].url,type:t.status.media_attachments[0].mime}})])])])]):n("div",{staticClass:"embed-responsive embed-responsive-16by9"},[n("video",{staticClass:"video",attrs:{controls:"",preload:"metadata",loop:"",poster:t.status.media_attachments[0].preview_url,"data-id":t.status.id}},[n("source",{attrs:{src:t.status.media_attachments[0].url,type:t.status.media_attachments[0].mime}})])])}),[],!1,null,null,null);e.default=s.exports},"2wtg":function(t,e,n){Vue.component("photo-presenter",n("d+I4").default),Vue.component("video-presenter",n("2Jpm").default),Vue.component("photo-album-presenter",n("Mrqh").default),Vue.component("video-album-presenter",n("9wGH").default),Vue.component("mixed-album-presenter",n("exej").default),Vue.component("post-menu",n("yric").default),Vue.component("story-viewer",n("Zhjo").default),Vue.component("profile",n("EHjT").default)},4:function(t,e,n){t.exports=n("2wtg")},"9tPo":function(t,e){t.exports=function(t){var e="undefined"!=typeof window&&window.location;if(!e)throw new Error("fixUrls requires window.location");if(!t||"string"!=typeof t)return t;var n=e.protocol+"//"+e.host,i=n+e.pathname.replace(/\/[^\/]*$/,"/");return t.replace(/url\s*\(((?:[^)(]|\((?:[^)(]+|\([^)(]*\))*\))*)\)/gi,(function(t,e){var o,s=e.trim().replace(/^"(.*)"$/,(function(t,e){return e})).replace(/^'(.*)'$/,(function(t,e){return e}));return/^(#|data:|http:\/\/|https:\/\/|file:\/\/\/|\s*$)/i.test(s)?t:(o=0===s.indexOf("//")?s:0===s.indexOf("/")?n+s:i+s.replace(/^\.\//,""),"url("+JSON.stringify(o)+")")}))}},"9wGH":function(t,e,n){"use strict";n.r(e);var i={props:["status"]},o=n("KHd+"),s=Object(o.a)(i,(function(){var t=this,e=t.$createElement,n=t._self._c||e;return 1==t.status.sensitive?n("div",[n("details",{staticClass:"details-animated"},[n("summary",[n("p",{staticClass:"mb-0 lead font-weight-bold"},[t._v(t._s(t.status.spoiler_text?t.status.spoiler_text:"CW / NSFW / Hidden Media"))]),t._v(" "),n("p",{staticClass:"font-weight-light"},[t._v("(click to show)")])]),t._v(" "),n("b-carousel",{staticStyle:{"text-shadow":"1px 1px 2px #333","background-color":"#000"},attrs:{id:t.status.id+"-carousel",controls:"","img-blank":"",background:"#ffffff",interval:0}},t._l(t.status.media_attachments,(function(t,e){return n("b-carousel-slide",{key:t.id+"-media"},[n("video",{staticClass:"embed-responsive-item",attrs:{slot:"img",preload:"none",controls:"",loop:"",alt:t.description,width:"100%",height:"100%",poster:t.preview_url},slot:"img"},[n("source",{attrs:{src:t.url,type:t.mime}})])])})),1)],1)]):n("div",[n("b-carousel",{staticStyle:{"text-shadow":"1px 1px 2px #333","background-color":"#000"},attrs:{id:t.status.id+"-carousel",controls:"","img-blank":"",background:"#ffffff",interval:0}},t._l(t.status.media_attachments,(function(t,e){return n("b-carousel-slide",{key:t.id+"-media"},[n("video",{staticClass:"embed-responsive-item",attrs:{slot:"img",preload:"none",controls:"",loop:"",alt:t.description,width:"100%",height:"100%",poster:t.preview_url},slot:"img"},[n("source",{attrs:{src:t.url,type:t.mime}})])])})),1)],1)}),[],!1,null,null,null);e.default=s.exports},EHjT:function(t,e,n){"use strict";n.r(e);n("la7V");function i(t){return function(t){if(Array.isArray(t))return o(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(!t)return;if("string"==typeof t)return o(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);"Object"===n&&t.constructor&&(n=t.constructor.name);if("Map"===n||"Set"===n)return Array.from(t);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return o(t,e)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function o(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);n0})),o=n.map((function(t){return t.id}));t.ids=o,t.min_id=Math.max.apply(Math,i(o)),t.max_id=Math.min.apply(Math,i(o)),t.modalStatus=_.first(e.data),t.timeline=n,t.ownerCheck(),t.loading=!1})).catch((function(t){swal("Oops, something went wrong","Please release the page.","error")}))},ownerCheck:function(){0!=$("body").hasClass("loggedIn")?this.owner=this.profile.id===this.user.id:this.owner=!1},infiniteTimeline:function(t){var e=this;if(this.loading||this.timeline.length<9)t.complete();else{var n="/api/pixelfed/v1/accounts/"+this.profileId+"/statuses";axios.get(n,{params:{only_media:!0,max_id:this.max_id}}).then((function(n){if(n.data.length&&0==e.loading){var o=n.data,s=e;o.forEach((function(t){-1==s.ids.indexOf(t.id)&&(s.timeline.push(t),s.ids.push(t.id))}));var a=Math.min.apply(Math,i(e.ids));if(a==e.max_id)return void t.complete();e.min_id=Math.max.apply(Math,i(e.ids)),e.max_id=a,t.loaded(),e.loading=!1}else t.complete()}))}},previewUrl:function(t){return t.sensitive?"/storage/no-preview.png?v="+(new Date).getTime():t.media_attachments[0].preview_url},previewBackground:function(t){return"background-image: url("+this.previewUrl(t)+");"},blurhHashMedia:function(t){return t.sensitive?null:t.media_attachments[0].preview_url},switchMode:function(t){var e=this;this.mode=_.indexOf(this.modes,t)?t:"grid","bookmarks"==this.mode&&0==this.bookmarks.length&&axios.get("/api/local/bookmarks").then((function(t){e.bookmarks=t.data,e.bookmarksLoading=!1})),"collections"==this.mode&&0==this.collections.length&&axios.get("/api/local/profile/collections/"+this.profileId).then((function(t){e.collections=t.data}))},reportProfile:function(){var t=this.profile.id;window.location.href="/i/report?type=user&id="+t},reportUrl:function(t){return"/i/report?type="+(t.in_reply_to?"comment":"post")+"&id="+t.id},commentFocus:function(t,e){var n=event.target.parentElement.parentElement.parentElement,i=n.getElementsByClassName("comments")[0];0==i.children.length&&(i.classList.add("mb-2"),this.fetchStatusComments(t,n));var o=n.querySelectorAll(".card-footer")[0],s=n.querySelectorAll(".status-reply-input")[0];1==o.classList.contains("d-none")?(o.classList.remove("d-none"),s.focus()):(o.classList.add("d-none"),s.blur())},likeStatus:function(t,e){0!=$("body").hasClass("loggedIn")&&axios.post("/i/like",{item:t.id}).then((function(e){t.favourites_count=e.data.count,1==t.favourited?t.favourited=!1:t.favourited=!0})).catch((function(t){swal("Error","Something went wrong, please try again later.","error")}))},shareStatus:function(t,e){0!=$("body").hasClass("loggedIn")&&axios.post("/i/share",{item:t.id}).then((function(e){t.reblogs_count=e.data.count,1==t.reblogged?t.reblogged=!1:t.reblogged=!0})).catch((function(t){swal("Error","Something went wrong, please try again later.","error")}))},timestampFormat:function(t){var e=new Date(t);return e.toDateString()+" "+e.toLocaleTimeString()},editUrl:function(t){return t.url+"/edit"},redirect:function(t){window.location.href=t},remoteRedirect:function(t){window.location.href=window.App.config.site.url+"/i/redirect?url="+encodeURIComponent(t)},replyUrl:function(t){return"/p/"+this.profile.username+"/"+(t.account.id==this.profile.id?t.id:t.in_reply_to_id)},mentionUrl:function(t){return"/p/"+t.account.username+"/"+t.id},statusOwner:function(t){return t.account.id==this.profile.id},fetchRelationships:function(){var t=this;0!=document.querySelectorAll("body")[0].classList.contains("loggedIn")&&axios.get("/api/pixelfed/v1/accounts/relationships",{params:{"id[]":this.profileId}}).then((function(e){e.data.length&&(t.relationship=e.data[0],1==e.data[0].blocking&&(t.warning=!0))}))},muteProfile:function(){var t=this;if(0!=$("body").hasClass("loggedIn")){var e=this.profileId;axios.post("/i/mute",{type:"user",item:e}).then((function(e){t.fetchRelationships(),t.$refs.visitorContextMenu.hide(),swal("Success","You have successfully muted "+t.profile.acct,"success")})).catch((function(t){swal("Error","Something went wrong. Please try again later.","error")}))}},unmuteProfile:function(){var t=this;if(0!=$("body").hasClass("loggedIn")){var e=this.profileId;axios.post("/i/unmute",{type:"user",item:e}).then((function(e){t.fetchRelationships(),t.$refs.visitorContextMenu.hide(),swal("Success","You have successfully unmuted "+t.profile.acct,"success")})).catch((function(t){swal("Error","Something went wrong. Please try again later.","error")}))}},blockProfile:function(){var t=this;if(0!=$("body").hasClass("loggedIn")){var e=this.profileId;axios.post("/i/block",{type:"user",item:e}).then((function(e){t.warning=!0,t.fetchRelationships(),t.$refs.visitorContextMenu.hide(),swal("Success","You have successfully blocked "+t.profile.acct,"success")})).catch((function(t){swal("Error","Something went wrong. Please try again later.","error")}))}},unblockProfile:function(){var t=this;if(0!=$("body").hasClass("loggedIn")){var e=this.profileId;axios.post("/i/unblock",{type:"user",item:e}).then((function(e){t.fetchRelationships(),t.$refs.visitorContextMenu.hide(),swal("Success","You have successfully unblocked "+t.profile.acct,"success")})).catch((function(t){swal("Error","Something went wrong. Please try again later.","error")}))}},deletePost:function(t,e){var n=this;0!=$("body").hasClass("loggedIn")&&t.account.id===this.profile.id&&axios.post("/i/delete",{type:"status",item:t.id}).then((function(t){n.timeline.splice(e,1),swal("Success","You have successfully deleted this post","success")})).catch((function(t){swal("Error","Something went wrong. Please try again later.","error")}))},followProfile:function(){var t=this;0!=$("body").hasClass("loggedIn")&&axios.post("/i/follow",{item:this.profileId}).then((function(e){t.$refs.visitorContextMenu.hide(),t.relationship.following?(t.profile.followers_count--,1==t.profile.locked&&(window.location.href="/")):t.profile.followers_count++,t.relationship.following=!t.relationship.following})).catch((function(t){t.response.data.message&&swal("Error",t.response.data.message,"error")}))},followingModal:function(){var t=this;if(0!=$("body").hasClass("loggedIn")){if(0!=this.profileSettings.following.list)return this.followingCursor>1||axios.get("/api/pixelfed/v1/accounts/"+this.profileId+"/following",{params:{page:this.followingCursor}}).then((function(e){t.following=e.data,t.followingModalSearchCache=e.data,t.followingCursor++,e.data.length<10&&(t.followingMore=!1)})),void this.$refs.followingModal.show()}else window.location.href=encodeURI("/login?next=/"+this.profileUsername+"/")},followersModal:function(){var t=this;if(0!=$("body").hasClass("loggedIn")){if(0!=this.profileSettings.followers.list)return this.followerCursor>1||axios.get("/api/pixelfed/v1/accounts/"+this.profileId+"/followers",{params:{page:this.followerCursor}}).then((function(e){var n;(n=t.followers).push.apply(n,i(e.data)),t.followerCursor++,e.data.length<10&&(t.followerMore=!1)})),void this.$refs.followerModal.show()}else window.location.href=encodeURI("/login?next=/"+this.profileUsername+"/")},followingLoadMore:function(){var t=this;0!=$("body").hasClass("loggedIn")?axios.get("/api/pixelfed/v1/accounts/"+this.profile.id+"/following",{params:{page:this.followingCursor,fbu:this.followingModalSearch}}).then((function(e){var n;e.data.length>0&&((n=t.following).push.apply(n,i(e.data)),t.followingCursor++,t.followingModalSearchCache=t.following);e.data.length<10&&(t.followingModalSearchCache=t.following,t.followingMore=!1)})):window.location.href=encodeURI("/login?next=/"+this.profile.username+"/")},followersLoadMore:function(){var t=this;0!=$("body").hasClass("loggedIn")&&axios.get("/api/pixelfed/v1/accounts/"+this.profile.id+"/followers",{params:{page:this.followerCursor}}).then((function(e){var n;e.data.length>0&&((n=t.followers).push.apply(n,i(e.data)),t.followerCursor++);e.data.length<10&&(t.followerMore=!1)}))},visitorMenu:function(){this.$refs.visitorContextMenu.show()},followModalAction:function(t,e){var n=this,i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:"following";axios.post("/i/follow",{item:t}).then((function(t){"following"==i&&(n.following.splice(e,1),n.profile.following_count--)})).catch((function(t){t.response.data.message&&swal("Error",t.response.data.message,"error")}))},momentBackground:function(){var t="w-100 h-100 mt-n3 ";return this.profile.header_bg?t+="default"==this.profile.header_bg?"bg-pixelfed":"bg-moment-"+this.profile.header_bg:t+="bg-pixelfed",t},loadSponsor:function(){var t=this;axios.get("/api/local/profile/sponsor/"+this.profileId).then((function(e){t.sponsorList=e.data}))},showSponsorModal:function(){this.$refs.sponsorModal.show()},goBack:function(){return window.history.length>2?void window.history.back():void(window.location.href="/")},copyProfileLink:function(){navigator.clipboard.writeText(window.location.href),this.$refs.visitorContextMenu.hide()},formatCount:function(t){return App.util.format.count(t)},statusUrl:function(t){return t.url},profileUrl:function(t){return t.url},showEmbedProfileModal:function(){this.ctxEmbedPayload=window.App.util.embed.profile(this.profile.url),this.$refs.visitorContextMenu.hide(),this.$refs.embedModal.show()},ctxCopyEmbed:function(){navigator.clipboard.writeText(this.ctxEmbedPayload),this.$refs.embedModal.hide(),this.$refs.visitorContextMenu.hide()},storyRedirect:function(){window.location.href="/stories/"+this.profileUsername},followingModalSearchHandler:function(){var t=this,e=this,n=this.followingModalSearch;if(0==n.length&&(this.following=this.followingModalSearchCache,this.followingModalSearch=null),n.length>0){var i="/api/pixelfed/v1/accounts/"+e.profileId+"/following?page=1&fbu="+n;axios.get(i).then((function(e){t.following=e.data})).catch((function(t){e.following=e.followingModalSearchCache,e.followingModalSearch=null}))}},truncate:function(t,e){return _.truncate(t,{length:e})}}},a=(n("USQp"),n("KHd+")),r=Object(a.a)(s,(function(){var t=this,e=t.$createElement,n=t._self._c||e;return n("div",{staticClass:"w-100 h-100"},[t.owner&&"moment"==t.layout?n("div",[t._m(0)]):t._e(),t._v(" "),t.isMobile?n("div",{staticClass:"bg-white p-3 border-bottom"},[n("div",{staticClass:"d-flex justify-content-between align-items-center"},[n("div",{staticClass:"cursor-pointer",on:{click:t.goBack}},[n("i",{staticClass:"fas fa-chevron-left fa-lg"})]),t._v(" "),n("div",{staticClass:"font-weight-bold"},[t._v("\n\t\t\t\t"+t._s(this.profileUsername)+"\n\n\t\t\t")]),t._v(" "),n("div",[n("a",{staticClass:"fas fa-ellipsis-v fa-lg text-muted text-decoration-none",attrs:{href:"#"},on:{click:function(e){return e.preventDefault(),t.visitorMenu(e)}}})])])]):t._e(),t._v(" "),t.relationship&&t.relationship.blocking&&t.warning?n("div",{staticClass:"bg-white pt-3 border-bottom"},[n("div",{staticClass:"container"},[n("p",{staticClass:"text-center font-weight-bold"},[t._v("You are blocking this account")]),t._v(" "),n("p",{staticClass:"text-center font-weight-bold"},[t._v("Click "),n("a",{staticClass:"cursor-pointer",attrs:{href:"#"},on:{click:function(e){e.preventDefault(),t.warning=!1}}},[t._v("here")]),t._v(" to view profile")])])]):t._e(),t._v(" "),t.loading?n("div",{staticClass:"d-flex justify-content-center align-items-center",staticStyle:{height:"80vh"}},[n("img",{attrs:{src:"/img/pixelfed-icon-grey.svg"}})]):t._e(),t._v(" "),t.loading||t.warning?t._e():n("div",["metro"==t.layout?n("div",{staticClass:"container"},[n("div",{class:t.isMobile?"pt-5":"pt-5 border-bottom"},[n("div",{staticClass:"container px-0"},[n("div",{staticClass:"row"},[n("div",{staticClass:"col-12 col-md-4 d-md-flex"},[n("div",{staticClass:"profile-avatar mx-md-auto"},[n("div",{staticClass:"d-block d-md-none mt-n3 mb-3"},[n("div",{staticClass:"row"},[n("div",{staticClass:"col-4"},[t.hasStory?n("div",{staticClass:"has-story cursor-pointer shadow-sm",on:{click:function(e){return t.storyRedirect()}}},[n("img",{staticClass:"rounded-circle",attrs:{alt:t.profileUsername+"'s profile picture",src:t.profile.avatar,width:"77px",height:"77px"}})]):n("div",[n("img",{staticClass:"rounded-circle border",attrs:{alt:t.profileUsername+"'s profile picture",src:t.profile.avatar,width:"77px",height:"77px"}})])]),t._v(" "),n("div",{staticClass:"col-8"},[n("div",{staticClass:"d-block d-md-none mt-3 py-2"},[n("ul",{staticClass:"nav d-flex justify-content-between"},[n("li",{staticClass:"nav-item"},[n("div",{staticClass:"font-weight-light"},[n("span",{staticClass:"text-dark text-center"},[n("p",{staticClass:"font-weight-bold mb-0"},[t._v(t._s(t.formatCount(t.profile.statuses_count)))]),t._v(" "),n("p",{staticClass:"text-muted mb-0 small"},[t._v("Posts")])])])]),t._v(" "),n("li",{staticClass:"nav-item"},[t.profileSettings.followers.count?n("div",{staticClass:"font-weight-light"},[n("a",{staticClass:"text-dark cursor-pointer text-center",on:{click:function(e){return t.followersModal()}}},[n("p",{staticClass:"font-weight-bold mb-0"},[t._v(t._s(t.formatCount(t.profile.followers_count)))]),t._v(" "),n("p",{staticClass:"text-muted mb-0 small"},[t._v("Followers")])])]):t._e()]),t._v(" "),n("li",{staticClass:"nav-item"},[t.profileSettings.following.count?n("div",{staticClass:"font-weight-light"},[n("a",{staticClass:"text-dark cursor-pointer text-center",on:{click:function(e){return t.followingModal()}}},[n("p",{staticClass:"font-weight-bold mb-0"},[t._v(t._s(t.formatCount(t.profile.following_count)))]),t._v(" "),n("p",{staticClass:"text-muted mb-0 small"},[t._v("Following")])])]):t._e()])])])])])]),t._v(" "),n("div",{staticClass:"d-none d-md-block pb-3"},[t.hasStory?n("div",{staticClass:"has-story-lg cursor-pointer shadow-sm",on:{click:function(e){return t.storyRedirect()}}},[n("img",{staticClass:"rounded-circle box-shadow cursor-pointer",attrs:{alt:t.profileUsername+"'s profile picture",src:t.profile.avatar,width:"150px",height:"150px"}})]):n("div",[n("img",{staticClass:"rounded-circle box-shadow",attrs:{alt:t.profileUsername+"'s profile picture",src:t.profile.avatar,width:"150px",height:"150px"}})]),t._v(" "),t.sponsorList.patreon||t.sponsorList.liberapay||t.sponsorList.opencollective?n("p",{staticClass:"text-center mt-3"},[n("button",{staticClass:"btn btn-outline-secondary font-weight-bold py-0",attrs:{type:"button"},on:{click:t.showSponsorModal}},[n("i",{staticClass:"fas fa-heart text-danger"}),t._v("\n\t\t\t\t\t\t\t\t\t\t\tDonate\n\t\t\t\t\t\t\t\t\t\t")])]):t._e()])])]),t._v(" "),n("div",{staticClass:"col-12 col-md-8 d-flex align-items-center"},[n("div",{staticClass:"profile-details"},[n("div",{staticClass:"d-none d-md-flex username-bar pb-3 align-items-center"},[n("span",{staticClass:"font-weight-ultralight h3 mb-0"},[t._v(t._s(t.profile.username))]),t._v(" "),t.profile.is_admin?n("span",{staticClass:"pl-1 pb-2 fa-stack",attrs:{title:"Admin Account","data-toggle":"tooltip"}},[n("i",{staticClass:"fas fa-certificate fa-lg text-danger fa-stack-1x"}),t._v(" "),n("i",{staticClass:"fas fa-crown text-white fa-sm fa-stack-1x",staticStyle:{"font-size":"9px"}})]):t._e(),t._v(" "),t.profile.id!=t.user.id&&t.user.hasOwnProperty("id")?n("span",[1==t.relationship.following?n("span",{staticClass:"pl-4"},[n("a",{staticClass:"btn btn-outline-secondary font-weight-bold btn-sm py-1 text-dark mr-2 px-3 btn-sec-alt",staticStyle:{border:"1px solid #dbdbdb"},attrs:{href:"/account/direct/t/"+t.profile.id,"data-toggle":"tooltip",title:"Message"}},[t._v("Message")]),t._v(" "),n("button",{staticClass:"btn btn-outline-secondary font-weight-bold btn-sm py-1 text-dark btn-sec-alt",staticStyle:{border:"1px solid #dbdbdb"},attrs:{type:"button","data-toggle":"tooltip",title:"Unfollow"},on:{click:t.followProfile}},[n("i",{staticClass:"fas fa-user-check mx-3"})])]):t._e(),t._v(" "),t.relationship.following?t._e():n("span",{staticClass:"pl-4"},[n("button",{staticClass:"btn btn-primary font-weight-bold btn-sm py-1 px-3",attrs:{type:"button","data-toggle":"tooltip",title:"Follow"},on:{click:t.followProfile}},[t._v("Follow")])])]):t._e(),t._v(" "),t.owner&&t.user.hasOwnProperty("id")?n("span",{staticClass:"pl-4"},[n("a",{staticClass:"btn btn-outline-secondary btn-sm",staticStyle:{"font-weight":"600"},attrs:{href:"/settings/home"}},[t._v("Edit Profile")])]):t._e(),t._v(" "),n("span",{staticClass:"pl-4"},[n("a",{staticClass:"fas fa-ellipsis-h fa-lg text-dark text-decoration-none",attrs:{href:"#"},on:{click:function(e){return e.preventDefault(),t.visitorMenu(e)}}})])]),t._v(" "),n("div",{staticClass:"font-size-16px"},[n("div",{staticClass:"d-none d-md-inline-flex profile-stats pb-3"},[n("div",{staticClass:"font-weight-light pr-5"},[n("span",{staticClass:"text-dark"},[n("span",{staticClass:"font-weight-bold"},[t._v(t._s(t.formatCount(t.profile.statuses_count)))]),t._v("\n\t\t\t\t\t\t\t\t\t\t\t\tPosts\n\t\t\t\t\t\t\t\t\t\t\t")])]),t._v(" "),t.profileSettings.followers.count?n("div",{staticClass:"font-weight-light pr-5"},[n("a",{staticClass:"text-dark cursor-pointer",on:{click:function(e){return t.followersModal()}}},[n("span",{staticClass:"font-weight-bold"},[t._v(t._s(t.formatCount(t.profile.followers_count)))]),t._v("\n\t\t\t\t\t\t\t\t\t\t\t\tFollowers\n\t\t\t\t\t\t\t\t\t\t\t")])]):t._e(),t._v(" "),t.profileSettings.following.count?n("div",{staticClass:"font-weight-light"},[n("a",{staticClass:"text-dark cursor-pointer",on:{click:function(e){return t.followingModal()}}},[n("span",{staticClass:"font-weight-bold"},[t._v(t._s(t.formatCount(t.profile.following_count)))]),t._v("\n\t\t\t\t\t\t\t\t\t\t\t\tFollowing\n\t\t\t\t\t\t\t\t\t\t\t")])]):t._e()]),t._v(" "),n("p",{staticClass:"mb-0 d-flex align-items-center"},[n("span",{staticClass:"font-weight-bold pr-3"},[t._v(t._s(t.profile.display_name))])]),t._v(" "),t.profile.note?n("div",{staticClass:"mb-0",domProps:{innerHTML:t._s(t.profile.note)}}):t._e(),t._v(" "),t.profile.website?n("p",{},[n("a",{staticClass:"profile-website",attrs:{href:t.profile.website,rel:"me external nofollow noopener",target:"_blank"},on:{click:function(e){return e.preventDefault(),t.remoteRedirect(t.profile.website)}}},[t._v(t._s(t.truncate(t.profile.website,24)))])]):t._e()])])])])])]),t._v(" "),n("div",{staticClass:"d-block d-md-none my-0 pt-3 border-bottom"},[t.user&&t.user.hasOwnProperty("id")?n("p",{staticClass:"pt-3"},[t.owner?n("button",{staticClass:"btn btn-outline-secondary bg-white btn-sm py-1 btn-block text-center font-weight-bold text-dark border border-lighter",on:{click:function(e){return e.preventDefault(),t.redirect("/settings/home")}}},[t._v("Edit Profile")]):t._e(),t._v(" "),!t.owner&&t.relationship.following?n("button",{staticClass:"btn btn-outline-secondary bg-white btn-sm py-1 px-5 font-weight-bold text-dark border border-lighter",on:{click:t.followProfile}},[t._v("   Unfollow   ")]):t._e(),t._v(" "),t.owner||t.relationship.following?t._e():n("button",{staticClass:"btn btn-primary btn-sm py-1 px-5 font-weight-bold",on:{click:t.followProfile}},[t._v(t._s(t.relationship.followed_by?"Follow Back":"     Follow     "))])]):t._e()]),t._v(" "),n("div",{},[n("ul",{staticClass:"nav nav-topbar d-flex justify-content-center border-0"},[n("li",{staticClass:"nav-item border-top"},[n("a",{class:"grid"==this.mode?"nav-link text-dark":"nav-link",attrs:{href:"#"},on:{click:function(e){return e.preventDefault(),t.switchMode("grid")}}},[n("i",{staticClass:"fas fa-th"}),t._v(" "),n("span",{staticClass:"d-none d-md-inline-block small pl-1"},[t._v("POSTS")])])]),t._v(" "),n("li",{staticClass:"nav-item px-0 border-top"},[n("a",{class:"collections"==this.mode?"nav-link text-dark":"nav-link",attrs:{href:"#"},on:{click:function(e){return e.preventDefault(),t.switchMode("collections")}}},[n("i",{staticClass:"fas fa-images"}),t._v(" "),n("span",{staticClass:"d-none d-md-inline-block small pl-1"},[t._v("COLLECTIONS")])])]),t._v(" "),t.owner?n("li",{staticClass:"nav-item border-top"},[n("a",{class:"bookmarks"==this.mode?"nav-link text-dark":"nav-link",attrs:{href:"#"},on:{click:function(e){return e.preventDefault(),t.switchMode("bookmarks")}}},[n("i",{staticClass:"fas fa-bookmark"}),t._v(" "),n("span",{staticClass:"d-none d-md-inline-block small pl-1"},[t._v("SAVED")])])]):t._e()])]),t._v(" "),n("div",{staticClass:"container px-0"},[n("div",{staticClass:"profile-timeline mt-md-4"},["grid"==t.mode?n("div",{staticClass:"row"},[t._l(t.timeline,(function(e,i){return n("div",{key:"tlob:"+i,staticClass:"col-4 p-1 p-md-3"},[t._o(n("a",{staticClass:"card info-overlay card-md-border-0",attrs:{href:t.statusUrl(e)}},[n("div",{staticClass:"square"},[e.sensitive?n("div",{staticClass:"square-content"},[t._m(1,!0),t._v(" "),n("blur-hash-canvas",{attrs:{width:"32",height:"32",hash:e.media_attachments[0].blurhash}})],1):n("div",{staticClass:"square-content"},[n("blur-hash-image",{attrs:{width:"32",height:"32",hash:e.media_attachments[0].blurhash,src:e.media_attachments[0].preview_url}})],1),t._v(" "),"photo:album"==e.pf_type?n("span",{staticClass:"float-right mr-3 post-icon"},[n("i",{staticClass:"fas fa-images fa-2x"})]):t._e(),t._v(" "),"video"==e.pf_type?n("span",{staticClass:"float-right mr-3 post-icon"},[n("i",{staticClass:"fas fa-video fa-2x"})]):t._e(),t._v(" "),"video:album"==e.pf_type?n("span",{staticClass:"float-right mr-3 post-icon"},[n("i",{staticClass:"fas fa-film fa-2x"})]):t._e(),t._v(" "),n("div",{staticClass:"info-overlay-text"},[n("h5",{staticClass:"text-white m-auto font-weight-bold"},[n("span",[n("span",{staticClass:"far fa-comment fa-lg p-2 d-flex-inline"}),t._v(" "),n("span",{staticClass:"d-flex-inline"},[t._v(t._s(t.formatCount(e.reply_count)))])])])])])]),0,"tlob:"+i)])})),t._v(" "),0==t.timeline.length?n("div",{staticClass:"col-12"},[t._m(2)]):t._e()],2):t._e(),t._v(" "),t.timeline.length&&"grid"==t.mode?n("div",[n("infinite-loading",{on:{infinite:t.infiniteTimeline}},[n("div",{attrs:{slot:"no-more"},slot:"no-more"}),t._v(" "),n("div",{attrs:{slot:"no-results"},slot:"no-results"})])],1):t._e(),t._v(" "),"bookmarks"==t.mode?n("div",[t.bookmarksLoading?n("div",[t._m(3)]):n("div",[t.bookmarks.length?n("div",{staticClass:"row"},t._l(t.bookmarks,(function(e,i){return n("div",{staticClass:"col-4 p-1 p-sm-2 p-md-3"},[n("a",{staticClass:"card info-overlay card-md-border-0",attrs:{href:e.url}},[n("div",{staticClass:"square"},["photo:album"==e.pf_type?n("span",{staticClass:"float-right mr-3 post-icon"},[n("i",{staticClass:"fas fa-images fa-2x"})]):t._e(),t._v(" "),"video"==e.pf_type?n("span",{staticClass:"float-right mr-3 post-icon"},[n("i",{staticClass:"fas fa-video fa-2x"})]):t._e(),t._v(" "),"video:album"==e.pf_type?n("span",{staticClass:"float-right mr-3 post-icon"},[n("i",{staticClass:"fas fa-film fa-2x"})]):t._e(),t._v(" "),n("div",{staticClass:"square-content",style:t.previewBackground(e)}),t._v(" "),n("div",{staticClass:"info-overlay-text"},[n("h5",{staticClass:"text-white m-auto font-weight-bold"},[n("span",[n("span",{staticClass:"fas fa-retweet fa-lg p-2 d-flex-inline"}),t._v(" "),n("span",{staticClass:"d-flex-inline"},[t._v(t._s(e.reblogs_count))])])])])])])])})),0):n("div",{staticClass:"col-12"},[t._m(4)])])]):t._e(),t._v(" "),"collections"==t.mode?n("div",[t.collections.length?n("div",{staticClass:"row"},t._l(t.collections,(function(t,e){return n("div",{staticClass:"col-4 p-1 p-sm-2 p-md-3"},[n("a",{staticClass:"card info-overlay card-md-border-0",attrs:{href:t.url}},[n("div",{staticClass:"square"},[n("div",{staticClass:"square-content",style:"background-image: url("+t.thumb+");"})])])])})),0):n("div",[t._m(5)])]):t._e()])])]):t._e(),t._v(" "),"moment"==t.layout?n("div",{staticClass:"mt-3"},[n("div",{class:t.momentBackground(),staticStyle:{width:"100%","min-height":"274px"}}),t._v(" "),n("div",{staticClass:"bg-white border-bottom"},[n("div",{staticClass:"container"},[n("div",{staticClass:"row"},[n("div",{staticClass:"col-12 row mx-0"},[n("div",{staticClass:"col-4 text-left mt-2"},[t.relationship&&t.relationship.followed_by?n("span",[n("span",{staticClass:"bg-light border border-secondary font-weight-bold small py-1 px-2 text-muted rounded"},[t._v("FOLLOWS YOU")])]):t._e(),t._v(" "),t.profile.is_admin?n("span",[n("span",{staticClass:"bg-light border border-danger font-weight-bold small py-1 px-2 text-danger rounded"},[t._v("ADMIN")])]):t._e()]),t._v(" "),n("div",{staticClass:"col-4 text-center"},[n("div",{staticClass:"d-block d-md-none"},[n("img",{staticClass:"rounded-circle box-shadow",staticStyle:{"margin-top":"-60px",border:"5px solid #fff"},attrs:{src:t.profile.avatar,width:"110px",height:"110px"}})]),t._v(" "),n("div",{staticClass:"d-none d-md-block"},[n("img",{staticClass:"rounded-circle box-shadow",staticStyle:{"margin-top":"-90px",border:"5px solid #fff"},attrs:{src:t.profile.avatar,width:"172px",height:"172px"}})])]),t._v(" "),n("div",{staticClass:"col-4 text-right mt-2"},[n("span",{staticClass:"d-none d-md-inline-block pl-4"},[n("a",{staticClass:"fas fa-rss fa-lg text-muted text-decoration-none",attrs:{href:"/users/"+t.profile.username+".atom"}})]),t._v(" "),t.owner?n("span",{staticClass:"pl-md-4 pl-sm-2"},[n("a",{staticClass:"fas fa-cog fa-lg text-muted text-decoration-none",attrs:{href:"/settings/home"}})]):t._e(),t._v(" "),t.profile.id!=t.user.id&&t.user.hasOwnProperty("id")?n("span",{staticClass:"pl-md-4 pl-sm-2"},[n("a",{staticClass:"fas fa-cog fa-lg text-muted text-decoration-none",attrs:{href:"#"},on:{click:function(e){return e.preventDefault(),t.visitorMenu(e)}}})]):t._e(),t._v(" "),t.profile.id!=t.user.id&&t.user.hasOwnProperty("id")?n("span",[1==t.relationship.following?n("span",{staticClass:"pl-md-4 pl-sm-2"},[n("button",{staticClass:"btn btn-outline-secondary font-weight-bold btn-sm",attrs:{type:"button"},on:{click:function(e){return e.preventDefault(),t.followProfile()}}},[t._v("Unfollow")])]):n("span",{staticClass:"pl-md-4 pl-sm-2"},[n("button",{staticClass:"btn btn-primary font-weight-bold btn-sm",attrs:{type:"button"},on:{click:function(e){return e.preventDefault(),t.followProfile()}}},[t._v("Follow")])])]):t._e()])]),t._v(" "),n("div",{staticClass:"col-12 text-center"},[n("div",{staticClass:"profile-details my-3"},[n("p",{staticClass:"font-weight-ultralight h2 text-center"},[t._v(t._s(t.profile.username))]),t._v(" "),t.profile.note?n("div",{staticClass:"text-center text-muted p-3",domProps:{innerHTML:t._s(t.profile.note)}}):t._e(),t._v(" "),n("div",{staticClass:"pb-3 text-muted text-center"},[n("a",{staticClass:"text-lighter",attrs:{href:t.profile.url}},[n("span",{staticClass:"font-weight-bold"},[t._v(t._s(t.formatCount(t.profile.statuses_count)))]),t._v("\n\t\t\t\t\t\t\t\t\t\tPosts\n\t\t\t\t\t\t\t\t\t")]),t._v(" "),t.profileSettings.followers.count?n("a",{staticClass:"text-lighter cursor-pointer px-3",on:{click:function(e){return t.followersModal()}}},[n("span",{staticClass:"font-weight-bold"},[t._v(t._s(t.formatCount(t.profile.followers_count)))]),t._v("\n\t\t\t\t\t\t\t\t\t\tFollowers\n\t\t\t\t\t\t\t\t\t")]):t._e(),t._v(" "),t.profileSettings.following.count?n("a",{staticClass:"text-lighter cursor-pointer",on:{click:function(e){return t.followingModal()}}},[n("span",{staticClass:"font-weight-bold"},[t._v(t._s(t.formatCount(t.profile.following_count)))]),t._v("\n\t\t\t\t\t\t\t\t\t\tFollowing\n\t\t\t\t\t\t\t\t\t")]):t._e()])])])])])]),t._v(" "),n("div",{staticClass:"container-fluid"},[n("div",{staticClass:"profile-timeline mt-md-4"},["grid"==t.mode?n("div",{},[n("masonry",{attrs:{cols:{default:3,700:2,400:1},gutter:{default:"5px"}}},t._l(t.timeline,(function(e,i){return n("div",{staticClass:"p-1"},[n("a",{class:[e.sensitive?"card info-overlay card-md-border-0":e.media_attachments[0].filter_class+" card info-overlay card-md-border-0"],attrs:{href:t.statusUrl(e)}},[n("img",{staticClass:"img-fluid w-100",attrs:{src:t.previewUrl(e)}})])])})),0)],1):t._e(),t._v(" "),t.timeline.length?n("div",[n("infinite-loading",{on:{infinite:t.infiniteTimeline}},[n("div",{attrs:{slot:"no-more"},slot:"no-more"}),t._v(" "),n("div",{attrs:{slot:"no-results"},slot:"no-results"})])],1):t._e()])])]):t._e()]),t._v(" "),t.profile&&t.following?n("b-modal",{ref:"followingModal",attrs:{id:"following-modal","hide-footer":"",centered:"",scrollable:"",title:"Following","body-class":"list-group-flush py-3 px-0","dialog-class":"follow-modal"}},[t.loading?t._e():n("div",{staticClass:"list-group",staticStyle:{"min-height":"60vh"}},[1==t.owner?n("div",{staticClass:"list-group-item border-0 pt-0 px-0 mt-n2 mb-3"},[n("span",{staticClass:"d-flex px-4 pb-0 align-items-center"},[n("i",{staticClass:"fas fa-search text-lighter"}),t._v(" "),n("input",{directives:[{name:"model",rawName:"v-model",value:t.followingModalSearch,expression:"followingModalSearch"}],staticClass:"form-control border-0 shadow-0 no-focus",attrs:{type:"text",placeholder:"Search Following..."},domProps:{value:t.followingModalSearch},on:{keyup:t.followingModalSearchHandler,input:function(e){e.target.composing||(t.followingModalSearch=e.target.value)}}})])]):t._e(),t._v(" "),1==t.owner?n("div",{staticClass:"btn-group rounded-0 mt-n3 mb-3 border-top",attrs:{role:"group","aria-label":"Following"}}):n("div",{staticClass:"btn-group rounded-0 mt-n3 mb-3",attrs:{role:"group","aria-label":"Following"}}),t._v(" "),t._l(t.following,(function(e,i){return n("div",{key:"following_"+i,staticClass:"list-group-item border-0 py-1"},[n("div",{staticClass:"media"},[n("a",{attrs:{href:e.url}},[n("img",{staticClass:"mr-3 rounded-circle box-shadow",attrs:{src:e.avatar,alt:e.username+"’s avatar",width:"30px",loading:"lazy"}})]),t._v(" "),n("div",{staticClass:"media-body text-truncate"},[n("p",{staticClass:"mb-0",staticStyle:{"font-size":"14px"}},[n("a",{staticClass:"font-weight-bold text-dark",attrs:{href:e.url}},[t._v("\n\t\t\t\t\t\t\t\t"+t._s(e.username)+"\n\t\t\t\t\t\t\t")])]),t._v(" "),e.local?n("p",{staticClass:"text-muted mb-0 text-truncate",staticStyle:{"font-size":"14px"}},[t._v("\n\t\t\t\t\t\t\t"+t._s(e.display_name)+"\n\t\t\t\t\t\t")]):n("p",{staticClass:"text-muted mb-0 text-truncate mr-3",staticStyle:{"font-size":"14px"},attrs:{title:e.acct,"data-toggle":"dropdown","data-placement":"bottom"}},[n("span",{staticClass:"font-weight-bold"},[t._v(t._s(e.acct.split("@")[0]))]),n("span",{staticClass:"text-lighter"},[t._v("@"+t._s(e.acct.split("@")[1]))])])]),t._v(" "),t.owner?n("div",[n("a",{staticClass:"btn btn-outline-dark btn-sm font-weight-bold",attrs:{href:"#"},on:{click:function(n){return n.preventDefault(),t.followModalAction(e.id,i,"following")}}},[t._v("Following")])]):t._e()])])})),t._v(" "),t.followingModalSearch&&0==t.following.length?n("div",{staticClass:"list-group-item border-0"},[n("div",{staticClass:"list-group-item border-0 pt-5"},[n("p",{staticClass:"p-3 text-center mb-0 lead"},[t._v("No Results Found")])])]):t._e(),t._v(" "),t.following.length>0&&t.followingMore?n("div",{staticClass:"list-group-item text-center",on:{click:function(e){return t.followingLoadMore()}}},[n("p",{staticClass:"mb-0 small text-muted font-weight-light cursor-pointer"},[t._v("Load more")])]):t._e()],2)]):t._e(),t._v(" "),n("b-modal",{ref:"followerModal",attrs:{id:"follower-modal","hide-footer":"",centered:"",scrollable:"",title:"Followers","body-class":"list-group-flush py-3 px-0","dialog-class":"follow-modal"}},[n("div",{staticClass:"list-group"},[0==t.followers.length?n("div",{staticClass:"list-group-item border-0"},[n("p",{staticClass:"text-center mb-0 font-weight-bold text-muted py-5"},[n("span",{staticClass:"text-dark"},[t._v(t._s(t.profileUsername))]),t._v(" has no followers yet")])]):t._e(),t._v(" "),t._l(t.followers,(function(e,i){return n("div",{key:"follower_"+i,staticClass:"list-group-item border-0 py-1"},[n("div",{staticClass:"media mb-0"},[n("a",{attrs:{href:e.url}},[n("img",{staticClass:"mr-3 rounded-circle box-shadow",attrs:{src:e.avatar,alt:e.username+"’s avatar",width:"30px",height:"30px",loading:"lazy"}})]),t._v(" "),n("div",{staticClass:"media-body mb-0"},[n("p",{staticClass:"mb-0",staticStyle:{"font-size":"14px"}},[n("a",{staticClass:"font-weight-bold text-dark",attrs:{href:e.url}},[t._v("\n\t\t\t\t\t\t\t\t"+t._s(e.username)+"\n\t\t\t\t\t\t\t")])]),t._v(" "),n("p",{staticClass:"text-secondary mb-0",staticStyle:{"font-size":"13px"}},[t._v("\n\t\t\t\t\t\t\t"+t._s(e.display_name)+"\n\t\t\t\t\t\t")])])])])})),t._v(" "),t.followers.length&&t.followerMore?n("div",{staticClass:"list-group-item text-center",on:{click:function(e){return t.followersLoadMore()}}},[n("p",{staticClass:"mb-0 small text-muted font-weight-light cursor-pointer"},[t._v("Load more")])]):t._e()],2)]),t._v(" "),n("b-modal",{ref:"visitorContextMenu",attrs:{id:"visitor-context-menu","hide-footer":"","hide-header":"",centered:"",size:"sm","body-class":"list-group-flush p-0"}},[t.relationship?n("div",{staticClass:"list-group"},[n("div",{staticClass:"list-group-item cursor-pointer text-center rounded text-dark",on:{click:t.copyProfileLink}},[t._v("\n\t\t\t\tCopy Link\n\t\t\t")]),t._v(" "),0==t.profile.locked?n("div",{staticClass:"list-group-item cursor-pointer text-center rounded text-dark",on:{click:t.showEmbedProfileModal}},[t._v("\n\t\t\t\tEmbed\n\t\t\t")]):t._e(),t._v(" "),!t.user||t.owner||t.relationship.following?t._e():n("div",{staticClass:"list-group-item cursor-pointer text-center rounded text-dark",on:{click:t.followProfile}},[t._v("\n\t\t\t\tFollow\n\t\t\t")]),t._v(" "),t.user&&!t.owner&&t.relationship.following?n("div",{staticClass:"list-group-item cursor-pointer text-center rounded",on:{click:t.followProfile}},[t._v("\n\t\t\t\tUnfollow\n\t\t\t")]):t._e(),t._v(" "),!t.user||t.owner||t.relationship.muting?t._e():n("div",{staticClass:"list-group-item cursor-pointer text-center rounded",on:{click:t.muteProfile}},[t._v("\n\t\t\t\tMute\n\t\t\t")]),t._v(" "),t.user&&!t.owner&&t.relationship.muting?n("div",{staticClass:"list-group-item cursor-pointer text-center rounded",on:{click:t.unmuteProfile}},[t._v("\n\t\t\t\tUnmute\n\t\t\t")]):t._e(),t._v(" "),t.user&&!t.owner?n("div",{staticClass:"list-group-item cursor-pointer text-center rounded text-dark",on:{click:t.reportProfile}},[t._v("\n\t\t\t\tReport User\n\t\t\t")]):t._e(),t._v(" "),!t.user||t.owner||t.relationship.blocking?t._e():n("div",{staticClass:"list-group-item cursor-pointer text-center rounded text-dark",on:{click:t.blockProfile}},[t._v("\n\t\t\t\tBlock\n\t\t\t")]),t._v(" "),t.user&&!t.owner&&t.relationship.blocking?n("div",{staticClass:"list-group-item cursor-pointer text-center rounded text-dark",on:{click:t.unblockProfile}},[t._v("\n\t\t\t\tUnblock\n\t\t\t")]):t._e(),t._v(" "),t.user&&t.owner?n("div",{staticClass:"list-group-item cursor-pointer text-center rounded text-dark",on:{click:function(e){return t.redirect("/settings/home")}}},[t._v("\n\t\t\t\tSettings\n\t\t\t")]):t._e(),t._v(" "),n("div",{staticClass:"list-group-item cursor-pointer text-center rounded text-dark",on:{click:function(e){return t.redirect("/users/"+t.profileUsername+".atom")}}},[t._v("\n\t\t\t\tAtom Feed\n\t\t\t")]),t._v(" "),n("div",{staticClass:"list-group-item cursor-pointer text-center rounded text-muted font-weight-bold",on:{click:function(e){return t.$refs.visitorContextMenu.hide()}}},[t._v("\n\t\t\t\tClose\n\t\t\t")])]):t._e()]),t._v(" "),n("b-modal",{ref:"sponsorModal",attrs:{id:"sponsor-modal","hide-footer":"",title:"Sponsor "+t.profileUsername,centered:"",size:"md","body-class":"px-5"}},[n("div",[n("p",{staticClass:"font-weight-bold"},[t._v("External Links")]),t._v(" "),t.sponsorList.patreon?n("p",{staticClass:"pt-2"},[n("a",{staticClass:"font-weight-bold",attrs:{href:"https://"+t.sponsorList.patreon,rel:"nofollow"}},[t._v(t._s(t.sponsorList.patreon))])]):t._e(),t._v(" "),t.sponsorList.liberapay?n("p",{staticClass:"pt-2"},[n("a",{staticClass:"font-weight-bold",attrs:{href:"https://"+t.sponsorList.liberapay,rel:"nofollow"}},[t._v(t._s(t.sponsorList.liberapay))])]):t._e(),t._v(" "),t.sponsorList.opencollective?n("p",{staticClass:"pt-2"},[n("a",{staticClass:"font-weight-bold",attrs:{href:"https://"+t.sponsorList.opencollective,rel:"nofollow"}},[t._v(t._s(t.sponsorList.opencollective))])]):t._e()])]),t._v(" "),n("b-modal",{ref:"embedModal",attrs:{id:"ctx-embed-modal","hide-header":"","hide-footer":"",centered:"",rounded:"",size:"md","body-class":"p-2 rounded"}},[n("div",[n("textarea",{directives:[{name:"model",rawName:"v-model",value:t.ctxEmbedPayload,expression:"ctxEmbedPayload"}],staticClass:"form-control disabled text-monospace",staticStyle:{"overflow-y":"hidden",border:"1px solid #efefef","font-size":"12px","line-height":"18px",margin:"0 0 7px",resize:"none"},attrs:{rows:"6",disabled:""},domProps:{value:t.ctxEmbedPayload},on:{input:function(e){e.target.composing||(t.ctxEmbedPayload=e.target.value)}}}),t._v(" "),n("hr"),t._v(" "),n("button",{class:t.copiedEmbed?"btn btn-primary btn-block btn-sm py-1 font-weight-bold disabed":"btn btn-primary btn-block btn-sm py-1 font-weight-bold",attrs:{disabled:t.copiedEmbed},on:{click:t.ctxCopyEmbed}},[t._v(t._s(t.copiedEmbed?"Embed Code Copied!":"Copy Embed Code"))]),t._v(" "),n("p",{staticClass:"mb-0 px-2 small text-muted"},[t._v("By using this embed, you agree to our "),n("a",{attrs:{href:"/site/terms"}},[t._v("Terms of Use")])])])])],1)}),[function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"bg-primary shadow"},[e("p",{staticClass:"text-center text-white mb-0 py-3 font-weight-bold border-bottom border-info"},[e("i",{staticClass:"fas fa-exclamation-triangle fa-lg mr-2"}),this._v(" The Moment UI layout has been deprecated and will be removed in a future release.\n\t\t\t")])])},function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"info-overlay-text-label"},[e("h5",{staticClass:"text-white m-auto font-weight-bold"},[e("span",[e("span",{staticClass:"far fa-eye-slash fa-lg p-2 d-flex-inline"})])])])},function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"py-5 text-center text-muted"},[e("p",[e("i",{staticClass:"fas fa-camera-retro fa-2x"})]),this._v(" "),e("p",{staticClass:"h2 font-weight-light pt-3"},[this._v("No posts yet")])])},function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"row"},[e("div",{staticClass:"col-12"},[e("div",{staticClass:"p-1 p-sm-2 p-md-3 d-flex justify-content-center align-items-center",staticStyle:{height:"30vh"}},[e("img",{attrs:{src:"/img/pixelfed-icon-grey.svg"}})])])])},function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"py-5 text-center text-muted"},[e("p",[e("i",{staticClass:"fas fa-bookmark fa-2x"})]),this._v(" "),e("p",{staticClass:"h2 font-weight-light pt-3"},[this._v("No saved bookmarks")])])},function(){var t=this.$createElement,e=this._self._c||t;return e("div",{staticClass:"py-5 text-center text-muted"},[e("p",[e("i",{staticClass:"fas fa-images fa-2x"})]),this._v(" "),e("p",{staticClass:"h2 font-weight-light pt-3"},[this._v("No collections yet")])])}],!1,null,"09a026f7",null);e.default=r.exports},I1BE:function(t,e){t.exports=function(t){var e=[];return e.toString=function(){return this.map((function(e){var n=function(t,e){var n=t[1]||"",i=t[3];if(!i)return n;if(e&&"function"==typeof btoa){var o=(a=i,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(a))))+" */"),s=i.sources.map((function(t){return"/*# sourceURL="+i.sourceRoot+t+" */"}));return[n].concat(s).concat([o]).join("\n")}var a;return[n].join("\n")}(e,t);return e[2]?"@media "+e[2]+"{"+n+"}":n})).join("")},e.i=function(t,n){"string"==typeof t&&(t=[[null,t,""]]);for(var i={},o=0;o