mirror of https://github.com/pixelfed/pixelfed.git
commit
0b162dc15e
|
@ -21,7 +21,12 @@ jobs:
|
|||
steps:
|
||||
- checkout
|
||||
|
||||
- run: sudo apt update && sudo apt install zlib1g-dev libsqlite3-dev
|
||||
- run:
|
||||
name: "Create Environment file and generate app key"
|
||||
command: |
|
||||
mv .env.testing .env
|
||||
|
||||
- run: sudo apt install zlib1g-dev libsqlite3-dev
|
||||
|
||||
# Download and cache dependencies
|
||||
|
||||
|
@ -36,18 +41,17 @@ jobs:
|
|||
- run: composer install -n --prefer-dist
|
||||
|
||||
- save_cache:
|
||||
key: composer-v2-{{ checksum "composer.lock" }}
|
||||
key: v2-dependencies-{{ checksum "composer.json" }}
|
||||
paths:
|
||||
- vendor
|
||||
|
||||
- run: cp .env.testing .env
|
||||
- run: php artisan config:cache
|
||||
- run: php artisan route:clear
|
||||
- run: php artisan storage:link
|
||||
- run: php artisan key:generate
|
||||
|
||||
# run tests with phpunit or codecept
|
||||
- run: ./vendor/bin/phpunit
|
||||
- run: php artisan test
|
||||
- store_test_results:
|
||||
path: tests/_output
|
||||
- store_artifacts:
|
||||
|
|
|
@ -82,7 +82,7 @@ class AvatarStorage extends Command
|
|||
|
||||
$this->line(' ');
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage')) {
|
||||
if((bool) config_cache('pixelfed.cloud_storage')) {
|
||||
$this->info('✅ - Cloud storage configured');
|
||||
$this->line(' ');
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ class AvatarStorage extends Command
|
|||
$this->line(' ');
|
||||
}
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage') && config('instance.avatar.local_to_cloud')) {
|
||||
if((bool) config_cache('pixelfed.cloud_storage') && config('instance.avatar.local_to_cloud')) {
|
||||
$disk = Storage::disk(config_cache('filesystems.cloud'));
|
||||
$exists = $disk->exists('cache/avatars/default.jpg');
|
||||
$state = $exists ? '✅' : '❌';
|
||||
|
@ -100,7 +100,7 @@ class AvatarStorage extends Command
|
|||
$this->info($msg);
|
||||
}
|
||||
|
||||
$options = config_cache('pixelfed.cloud_storage') && config('instance.avatar.local_to_cloud') ?
|
||||
$options = (bool) config_cache('pixelfed.cloud_storage') && config('instance.avatar.local_to_cloud') ?
|
||||
[
|
||||
'Cancel',
|
||||
'Upload default avatar to cloud',
|
||||
|
@ -164,7 +164,7 @@ class AvatarStorage extends Command
|
|||
|
||||
protected function uploadAvatarsToCloud()
|
||||
{
|
||||
if(!config_cache('pixelfed.cloud_storage') || !config('instance.avatar.local_to_cloud')) {
|
||||
if(!(bool) config_cache('pixelfed.cloud_storage') || !config('instance.avatar.local_to_cloud')) {
|
||||
$this->error('Enable cloud storage and avatar cloud storage to perform this action');
|
||||
return;
|
||||
}
|
||||
|
@ -213,7 +213,7 @@ class AvatarStorage extends Command
|
|||
return;
|
||||
}
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage') == false && config_cache('federation.avatars.store_local') == false) {
|
||||
if((bool) config_cache('pixelfed.cloud_storage') == false && config_cache('federation.avatars.store_local') == false) {
|
||||
$this->error('You have cloud storage disabled and local avatar storage disabled, we cannot refetch avatars.');
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ class AvatarStorageDeepClean extends Command
|
|||
$this->line(' ');
|
||||
|
||||
$storage = [
|
||||
'cloud' => boolval(config_cache('pixelfed.cloud_storage')),
|
||||
'cloud' => (bool) config_cache('pixelfed.cloud_storage'),
|
||||
'local' => boolval(config_cache('federation.avatars.store_local'))
|
||||
];
|
||||
|
||||
|
|
|
@ -35,12 +35,16 @@ class CloudMediaMigrate extends Command
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
$enabled = config('pixelfed.cloud_storage');
|
||||
$enabled = (bool) config_cache('pixelfed.cloud_storage');
|
||||
if(!$enabled) {
|
||||
$this->error('Cloud storage not enabled. Exiting...');
|
||||
return;
|
||||
}
|
||||
|
||||
if(!$this->confirm('Are you sure you want to proceed?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$limit = $this->option('limit');
|
||||
$hugeMode = $this->option('huge');
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use App\Media;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\Services\MediaService;
|
||||
use App\Services\StatusService;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class FetchMissingMediaMimeType extends Command
|
||||
{
|
||||
|
@ -29,20 +29,20 @@ class FetchMissingMediaMimeType extends Command
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
foreach(Media::whereNotNull(['remote_url', 'status_id'])->whereNull('mime')->lazyByIdDesc(50, 'id') as $media) {
|
||||
foreach (Media::whereNotNull(['remote_url', 'status_id'])->whereNull('mime')->lazyByIdDesc(50, 'id') as $media) {
|
||||
$res = Http::retry(2, 100, throw: false)->head($media->remote_url);
|
||||
|
||||
if(!$res->successful()) {
|
||||
if (! $res->successful()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!in_array($res->header('content-type'), explode(',',config('pixelfed.media_types')))) {
|
||||
if (! in_array($res->header('content-type'), explode(',', config_cache('pixelfed.media_types')))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$media->mime = $res->header('content-type');
|
||||
|
||||
if($res->hasHeader('content-length')) {
|
||||
if ($res->hasHeader('content-length')) {
|
||||
$media->size = $res->header('content-length');
|
||||
}
|
||||
|
||||
|
@ -50,7 +50,7 @@ class FetchMissingMediaMimeType extends Command
|
|||
|
||||
MediaService::del($media->status_id);
|
||||
StatusService::del($media->status_id);
|
||||
$this->info('mid:'.$media->id . ' (' . $res->header('content-type') . ':' . $res->header('content-length') . ' bytes)');
|
||||
$this->info('mid:'.$media->id.' ('.$res->header('content-type').':'.$res->header('content-length').' bytes)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ class FixMediaDriver extends Command
|
|||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage') == false) {
|
||||
if((bool) config_cache('pixelfed.cloud_storage') == false) {
|
||||
$this->error('Cloud storage not enabled, exiting...');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ class MediaCloudUrlRewrite extends Command implements PromptsForMissingInput
|
|||
|
||||
protected function preflightCheck()
|
||||
{
|
||||
if(config_cache('pixelfed.cloud_storage') != true) {
|
||||
if(!(bool) config_cache('pixelfed.cloud_storage')) {
|
||||
$this->info('Error: Cloud storage is not enabled!');
|
||||
$this->error('Aborting...');
|
||||
exit;
|
||||
|
|
|
@ -45,7 +45,7 @@ class MediaS3GarbageCollector extends Command
|
|||
*/
|
||||
public function handle()
|
||||
{
|
||||
$enabled = in_array(config_cache('pixelfed.cloud_storage'), ['1', true, 'true']);
|
||||
$enabled = (bool) config_cache('pixelfed.cloud_storage');
|
||||
if(!$enabled) {
|
||||
$this->error('Cloud storage not enabled. Exiting...');
|
||||
return;
|
||||
|
|
|
@ -33,7 +33,7 @@ class Kernel extends ConsoleKernel
|
|||
$schedule->command('gc:passwordreset')->dailyAt('09:41')->onOneServer();
|
||||
$schedule->command('gc:sessions')->twiceDaily(13, 23)->onOneServer();
|
||||
|
||||
if (in_array(config_cache('pixelfed.cloud_storage'), ['1', true, 'true']) && config('media.delete_local_after_cloud')) {
|
||||
if ((bool) config_cache('pixelfed.cloud_storage') && (bool) config_cache('media.delete_local_after_cloud')) {
|
||||
$schedule->command('media:s3gc')->hourlyAt(15);
|
||||
}
|
||||
|
||||
|
|
|
@ -157,7 +157,7 @@ class AccountController extends Controller
|
|||
|
||||
$pid = $request->user()->profile_id;
|
||||
$count = UserFilterService::muteCount($pid);
|
||||
$maxLimit = intval(config('instance.user_filters.max_user_mutes'));
|
||||
$maxLimit = (int) config_cache('instance.user_filters.max_user_mutes');
|
||||
abort_if($count >= $maxLimit, 422, self::FILTER_LIMIT_MUTE_TEXT . $maxLimit . ' accounts');
|
||||
if($count == 0) {
|
||||
$filterCount = UserFilter::whereUserId($pid)->count();
|
||||
|
@ -260,7 +260,7 @@ class AccountController extends Controller
|
|||
]);
|
||||
$pid = $request->user()->profile_id;
|
||||
$count = UserFilterService::blockCount($pid);
|
||||
$maxLimit = intval(config('instance.user_filters.max_user_blocks'));
|
||||
$maxLimit = (int) config_cache('instance.user_filters.max_user_blocks');
|
||||
abort_if($count >= $maxLimit, 422, self::FILTER_LIMIT_BLOCK_TEXT . $maxLimit . ' accounts');
|
||||
if($count == 0) {
|
||||
$filterCount = UserFilter::whereUserId($pid)->whereFilterType('block')->count();
|
||||
|
|
|
@ -2,30 +2,20 @@
|
|||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use DB, Cache;
|
||||
use App\{
|
||||
DiscoverCategory,
|
||||
DiscoverCategoryHashtag,
|
||||
Hashtag,
|
||||
Media,
|
||||
Profile,
|
||||
Status,
|
||||
StatusHashtag,
|
||||
User
|
||||
};
|
||||
use App\Http\Controllers\PixelfedDirectoryController;
|
||||
use App\Models\ConfigCache;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\ConfigCacheService;
|
||||
use App\Services\StatusService;
|
||||
use Carbon\Carbon;
|
||||
use App\Status;
|
||||
use App\User;
|
||||
use Cache;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use League\ISO3166\ISO3166;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use App\Http\Controllers\PixelfedDirectoryController;
|
||||
use Illuminate\Support\Str;
|
||||
use League\ISO3166\ISO3166;
|
||||
|
||||
trait AdminDirectoryController
|
||||
{
|
||||
|
@ -41,37 +31,37 @@ trait AdminDirectoryController
|
|||
$res['countries'] = collect((new ISO3166)->all())->pluck('name');
|
||||
$res['admins'] = User::whereIsAdmin(true)
|
||||
->where('2fa_enabled', true)
|
||||
->get()->map(function($user) {
|
||||
return [
|
||||
'uid' => (string) $user->id,
|
||||
'pid' => (string) $user->profile_id,
|
||||
'username' => $user->username,
|
||||
'created_at' => $user->created_at
|
||||
];
|
||||
});
|
||||
->get()->map(function ($user) {
|
||||
return [
|
||||
'uid' => (string) $user->id,
|
||||
'pid' => (string) $user->profile_id,
|
||||
'username' => $user->username,
|
||||
'created_at' => $user->created_at,
|
||||
];
|
||||
});
|
||||
$config = ConfigCache::whereK('pixelfed.directory')->first();
|
||||
if($config) {
|
||||
if ($config) {
|
||||
$data = $config->v ? json_decode($config->v, true) : [];
|
||||
$res = array_merge($res, $data);
|
||||
}
|
||||
|
||||
if(empty($res['summary'])) {
|
||||
if (empty($res['summary'])) {
|
||||
$summary = ConfigCache::whereK('app.short_description')->pluck('v');
|
||||
$res['summary'] = $summary ? $summary[0] : null;
|
||||
}
|
||||
|
||||
if(isset($res['banner_image']) && !empty($res['banner_image'])) {
|
||||
if (isset($res['banner_image']) && ! empty($res['banner_image'])) {
|
||||
$res['banner_image'] = url(Storage::url($res['banner_image']));
|
||||
}
|
||||
|
||||
if(isset($res['favourite_posts'])) {
|
||||
$res['favourite_posts'] = collect($res['favourite_posts'])->map(function($id) {
|
||||
if (isset($res['favourite_posts'])) {
|
||||
$res['favourite_posts'] = collect($res['favourite_posts'])->map(function ($id) {
|
||||
return StatusService::get($id);
|
||||
})
|
||||
->filter(function($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->values();
|
||||
->filter(function ($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->values();
|
||||
}
|
||||
|
||||
$res['community_guidelines'] = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : [];
|
||||
|
@ -84,22 +74,22 @@ trait AdminDirectoryController
|
|||
$res['feature_config'] = [
|
||||
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
|
||||
'image_quality' => config_cache('pixelfed.image_quality'),
|
||||
'optimize_image' => config_cache('pixelfed.optimize_image'),
|
||||
'optimize_image' => (bool) config_cache('pixelfed.optimize_image'),
|
||||
'max_photo_size' => config_cache('pixelfed.max_photo_size'),
|
||||
'max_caption_length' => config_cache('pixelfed.max_caption_length'),
|
||||
'max_altext_length' => config_cache('pixelfed.max_altext_length'),
|
||||
'enforce_account_limit' => config_cache('pixelfed.enforce_account_limit'),
|
||||
'enforce_account_limit' => (bool) config_cache('pixelfed.enforce_account_limit'),
|
||||
'max_account_size' => config_cache('pixelfed.max_account_size'),
|
||||
'max_album_length' => config_cache('pixelfed.max_album_length'),
|
||||
'account_deletion' => config_cache('pixelfed.account_deletion'),
|
||||
'account_deletion' => (bool) config_cache('pixelfed.account_deletion'),
|
||||
];
|
||||
|
||||
if(config_cache('pixelfed.directory.testimonials')) {
|
||||
$testimonials = collect(json_decode(config_cache('pixelfed.directory.testimonials'),true))
|
||||
->map(function($t) {
|
||||
if (config_cache('pixelfed.directory.testimonials')) {
|
||||
$testimonials = collect(json_decode(config_cache('pixelfed.directory.testimonials'), true))
|
||||
->map(function ($t) {
|
||||
return [
|
||||
'profile' => AccountService::get($t['profile_id']),
|
||||
'body' => $t['body']
|
||||
'body' => $t['body'],
|
||||
];
|
||||
});
|
||||
$res['testimonials'] = $testimonials;
|
||||
|
@ -108,8 +98,8 @@ trait AdminDirectoryController
|
|||
$validator = Validator::make($res['feature_config'], [
|
||||
'media_types' => [
|
||||
'required',
|
||||
function ($attribute, $value, $fail) {
|
||||
if (!in_array('image/jpeg', $value->toArray()) || !in_array('image/png', $value->toArray())) {
|
||||
function ($attribute, $value, $fail) {
|
||||
if (! in_array('image/jpeg', $value->toArray()) || ! in_array('image/png', $value->toArray())) {
|
||||
$fail('You must enable image/jpeg and image/png support.');
|
||||
}
|
||||
},
|
||||
|
@ -120,7 +110,7 @@ trait AdminDirectoryController
|
|||
'max_account_size' => 'required_if:enforce_account_limit,true|integer|min:1000000',
|
||||
'max_album_length' => 'required|integer|min:4|max:20',
|
||||
'account_deletion' => 'required|accepted',
|
||||
'max_caption_length' => 'required|integer|min:500|max:10000'
|
||||
'max_caption_length' => 'required|integer|min:500|max:10000',
|
||||
]);
|
||||
|
||||
$res['requirements_validator'] = $validator->errors();
|
||||
|
@ -146,11 +136,11 @@ trait AdminDirectoryController
|
|||
foreach (new \DirectoryIterator($path) as $io) {
|
||||
$name = $io->getFilename();
|
||||
$skip = ['vendor'];
|
||||
if($io->isDot() || in_array($name, $skip)) {
|
||||
if ($io->isDot() || in_array($name, $skip)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if($io->isDir()) {
|
||||
if ($io->isDir()) {
|
||||
$langs->push(['code' => $name, 'name' => locale_get_display_name($name)]);
|
||||
}
|
||||
}
|
||||
|
@ -159,25 +149,26 @@ trait AdminDirectoryController
|
|||
$res['primary_locale'] = config('app.locale');
|
||||
|
||||
$submissionState = Http::withoutVerifying()
|
||||
->post('https://pixelfed.org/api/v1/directory/check-submission', [
|
||||
'domain' => config('pixelfed.domain.app')
|
||||
]);
|
||||
->post('https://pixelfed.org/api/v1/directory/check-submission', [
|
||||
'domain' => config('pixelfed.domain.app'),
|
||||
]);
|
||||
|
||||
$res['submission_state'] = $submissionState->json();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function validVal($res, $val, $count = false, $minLen = false)
|
||||
{
|
||||
if(!isset($res[$val])) {
|
||||
if (! isset($res[$val])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($count) {
|
||||
if ($count) {
|
||||
return count($res[$val]) >= $count;
|
||||
}
|
||||
|
||||
if($minLen) {
|
||||
if ($minLen) {
|
||||
return strlen($res[$val]) >= $minLen;
|
||||
}
|
||||
|
||||
|
@ -194,11 +185,11 @@ trait AdminDirectoryController
|
|||
'favourite_posts' => 'array|max:12',
|
||||
'favourite_posts.*' => 'distinct',
|
||||
'privacy_pledge' => 'sometimes',
|
||||
'banner_image' => 'sometimes|mimes:jpg,png|dimensions:width=1920,height:1080|max:5000'
|
||||
'banner_image' => 'sometimes|mimes:jpg,png|dimensions:width=1920,height:1080|max:5000',
|
||||
]);
|
||||
|
||||
$config = ConfigCache::firstOrNew([
|
||||
'k' => 'pixelfed.directory'
|
||||
'k' => 'pixelfed.directory',
|
||||
]);
|
||||
|
||||
$res = $config->v ? json_decode($config->v, true) : [];
|
||||
|
@ -208,26 +199,27 @@ trait AdminDirectoryController
|
|||
$res['contact_email'] = $request->input('contact_email');
|
||||
$res['privacy_pledge'] = (bool) $request->input('privacy_pledge');
|
||||
|
||||
if($request->filled('location')) {
|
||||
if ($request->filled('location')) {
|
||||
$exists = (new ISO3166)->name($request->location);
|
||||
if($exists) {
|
||||
if ($exists) {
|
||||
$res['location'] = $request->input('location');
|
||||
}
|
||||
}
|
||||
|
||||
if($request->hasFile('banner_image')) {
|
||||
if ($request->hasFile('banner_image')) {
|
||||
collect(Storage::files('public/headers'))
|
||||
->filter(function($name) {
|
||||
$protected = [
|
||||
'public/headers/.gitignore',
|
||||
'public/headers/default.jpg',
|
||||
'public/headers/missing.png'
|
||||
];
|
||||
return !in_array($name, $protected);
|
||||
})
|
||||
->each(function($name) {
|
||||
Storage::delete($name);
|
||||
});
|
||||
->filter(function ($name) {
|
||||
$protected = [
|
||||
'public/headers/.gitignore',
|
||||
'public/headers/default.jpg',
|
||||
'public/headers/missing.png',
|
||||
];
|
||||
|
||||
return ! in_array($name, $protected);
|
||||
})
|
||||
->each(function ($name) {
|
||||
Storage::delete($name);
|
||||
});
|
||||
$path = $request->file('banner_image')->storePublicly('public/headers');
|
||||
$res['banner_image'] = $path;
|
||||
ConfigCacheService::put('app.banner_image', url(Storage::url($path)));
|
||||
|
@ -240,9 +232,10 @@ trait AdminDirectoryController
|
|||
|
||||
ConfigCacheService::put('pixelfed.directory', $config->v);
|
||||
$updated = json_decode($config->v, true);
|
||||
if(isset($updated['banner_image'])) {
|
||||
if (isset($updated['banner_image'])) {
|
||||
$updated['banner_image'] = url(Storage::url($updated['banner_image']));
|
||||
}
|
||||
|
||||
return $updated;
|
||||
}
|
||||
|
||||
|
@ -253,7 +246,7 @@ trait AdminDirectoryController
|
|||
'open_registration' => (bool) config_cache('pixelfed.open_registration'),
|
||||
'curated_onboarding' => (bool) config_cache('instance.curated_registration.enabled'),
|
||||
'activitypub_enabled' => config_cache('federation.activitypub.enabled'),
|
||||
'oauth_enabled' => config_cache('pixelfed.oauth_enabled'),
|
||||
'oauth_enabled' => (bool) config_cache('pixelfed.oauth_enabled'),
|
||||
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
|
||||
'image_quality' => config_cache('pixelfed.image_quality'),
|
||||
'optimize_image' => config_cache('pixelfed.optimize_image'),
|
||||
|
@ -273,8 +266,8 @@ trait AdminDirectoryController
|
|||
'oauth_enabled' => 'required|accepted',
|
||||
'media_types' => [
|
||||
'required',
|
||||
function ($attribute, $value, $fail) {
|
||||
if (!in_array('image/jpeg', $value->toArray()) || !in_array('image/png', $value->toArray())) {
|
||||
function ($attribute, $value, $fail) {
|
||||
if (! in_array('image/jpeg', $value->toArray()) || ! in_array('image/png', $value->toArray())) {
|
||||
$fail('You must enable image/jpeg and image/png support.');
|
||||
}
|
||||
},
|
||||
|
@ -285,10 +278,10 @@ trait AdminDirectoryController
|
|||
'max_account_size' => 'required_if:enforce_account_limit,true|integer|min:1000000',
|
||||
'max_album_length' => 'required|integer|min:4|max:20',
|
||||
'account_deletion' => 'required|accepted',
|
||||
'max_caption_length' => 'required|integer|min:500|max:10000'
|
||||
'max_caption_length' => 'required|integer|min:500|max:10000',
|
||||
]);
|
||||
|
||||
if(!$validator->validate()) {
|
||||
if (! $validator->validate()) {
|
||||
return response()->json($validator->errors(), 422);
|
||||
}
|
||||
|
||||
|
@ -297,6 +290,7 @@ trait AdminDirectoryController
|
|||
|
||||
$data = (new PixelfedDirectoryController())->buildListing();
|
||||
$res = Http::withoutVerifying()->post('https://pixelfed.org/api/v1/directory/submission', $data);
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
|
@ -304,7 +298,7 @@ trait AdminDirectoryController
|
|||
{
|
||||
$bannerImage = ConfigCache::whereK('app.banner_image')->first();
|
||||
$directory = ConfigCache::whereK('pixelfed.directory')->first();
|
||||
if(!$bannerImage && !$directory || empty($directory->v)) {
|
||||
if (! $bannerImage && ! $directory || empty($directory->v)) {
|
||||
return;
|
||||
}
|
||||
$directoryArr = json_decode($directory->v, true);
|
||||
|
@ -312,12 +306,12 @@ trait AdminDirectoryController
|
|||
$protected = [
|
||||
'public/headers/.gitignore',
|
||||
'public/headers/default.jpg',
|
||||
'public/headers/missing.png'
|
||||
'public/headers/missing.png',
|
||||
];
|
||||
if(!$path || in_array($path, $protected)) {
|
||||
if (! $path || in_array($path, $protected)) {
|
||||
return;
|
||||
}
|
||||
if(Storage::exists($directoryArr['banner_image'])) {
|
||||
if (Storage::exists($directoryArr['banner_image'])) {
|
||||
Storage::delete($directoryArr['banner_image']);
|
||||
}
|
||||
|
||||
|
@ -328,12 +322,13 @@ trait AdminDirectoryController
|
|||
$bannerImage->save();
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
ConfigCacheService::put('pixelfed.directory', $directory);
|
||||
|
||||
return $bannerImage->v;
|
||||
}
|
||||
|
||||
public function directoryGetPopularPosts(Request $request)
|
||||
{
|
||||
$ids = Cache::remember('admin:api:popular_posts', 86400, function() {
|
||||
$ids = Cache::remember('admin:api:popular_posts', 86400, function () {
|
||||
return Status::whereLocal(true)
|
||||
->whereScope('public')
|
||||
->whereType('photo')
|
||||
|
@ -343,21 +338,21 @@ trait AdminDirectoryController
|
|||
->pluck('id');
|
||||
});
|
||||
|
||||
$res = $ids->map(function($id) {
|
||||
$res = $ids->map(function ($id) {
|
||||
return StatusService::get($id);
|
||||
})
|
||||
->filter(function($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->values();
|
||||
->filter(function ($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->values();
|
||||
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function directoryGetAddPostByIdSearch(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'q' => 'required|integer'
|
||||
'q' => 'required|integer',
|
||||
]);
|
||||
|
||||
$id = $request->input('q');
|
||||
|
@ -380,11 +375,12 @@ trait AdminDirectoryController
|
|||
$profile_id = $request->input('profile_id');
|
||||
$testimonials = ConfigCache::whereK('pixelfed.directory.testimonials')->firstOrFail();
|
||||
$existing = collect(json_decode($testimonials->v, true))
|
||||
->filter(function($t) use($profile_id) {
|
||||
->filter(function ($t) use ($profile_id) {
|
||||
return $t['profile_id'] !== $profile_id;
|
||||
})
|
||||
->values();
|
||||
ConfigCacheService::put('pixelfed.directory.testimonials', $existing);
|
||||
|
||||
return $existing;
|
||||
}
|
||||
|
||||
|
@ -392,13 +388,13 @@ trait AdminDirectoryController
|
|||
{
|
||||
$this->validate($request, [
|
||||
'username' => 'required',
|
||||
'body' => 'required|string|min:5|max:500'
|
||||
'body' => 'required|string|min:5|max:500',
|
||||
]);
|
||||
|
||||
$user = User::whereUsername($request->input('username'))->whereNull('status')->firstOrFail();
|
||||
|
||||
$configCache = ConfigCache::firstOrCreate([
|
||||
'k' => 'pixelfed.directory.testimonials'
|
||||
'k' => 'pixelfed.directory.testimonials',
|
||||
]);
|
||||
|
||||
$testimonials = $configCache->v ? collect(json_decode($configCache->v, true)) : collect([]);
|
||||
|
@ -409,7 +405,7 @@ trait AdminDirectoryController
|
|||
$testimonials->push([
|
||||
'profile_id' => (string) $user->profile_id,
|
||||
'username' => $request->input('username'),
|
||||
'body' => $request->input('body')
|
||||
'body' => $request->input('body'),
|
||||
]);
|
||||
|
||||
$configCache->v = json_encode($testimonials->toArray());
|
||||
|
@ -417,8 +413,9 @@ trait AdminDirectoryController
|
|||
ConfigCacheService::put('pixelfed.directory.testimonials', $configCache->v);
|
||||
$res = [
|
||||
'profile' => AccountService::get($user->profile_id),
|
||||
'body' => $request->input('body')
|
||||
'body' => $request->input('body'),
|
||||
];
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
@ -426,7 +423,7 @@ trait AdminDirectoryController
|
|||
{
|
||||
$this->validate($request, [
|
||||
'profile_id' => 'required',
|
||||
'body' => 'required|string|min:5|max:500'
|
||||
'body' => 'required|string|min:5|max:500',
|
||||
]);
|
||||
|
||||
$profile_id = $request->input('profile_id');
|
||||
|
@ -434,18 +431,19 @@ trait AdminDirectoryController
|
|||
$user = User::whereProfileId($profile_id)->firstOrFail();
|
||||
|
||||
$configCache = ConfigCache::firstOrCreate([
|
||||
'k' => 'pixelfed.directory.testimonials'
|
||||
'k' => 'pixelfed.directory.testimonials',
|
||||
]);
|
||||
|
||||
$testimonials = $configCache->v ? collect(json_decode($configCache->v, true)) : collect([]);
|
||||
|
||||
$updated = $testimonials->map(function($t) use($profile_id, $body) {
|
||||
if($t['profile_id'] == $profile_id) {
|
||||
$updated = $testimonials->map(function ($t) use ($profile_id, $body) {
|
||||
if ($t['profile_id'] == $profile_id) {
|
||||
$t['body'] = $body;
|
||||
}
|
||||
|
||||
return $t;
|
||||
})
|
||||
->values();
|
||||
->values();
|
||||
|
||||
$configCache->v = json_encode($updated);
|
||||
$configCache->save();
|
||||
|
|
|
@ -7,7 +7,9 @@ use App\Models\InstanceActor;
|
|||
use App\Page;
|
||||
use App\Profile;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\AdminSettingsService;
|
||||
use App\Services\ConfigCacheService;
|
||||
use App\Services\FilesystemService;
|
||||
use App\User;
|
||||
use App\Util\Site\Config;
|
||||
use Artisan;
|
||||
|
@ -71,6 +73,7 @@ trait AdminSettingsController
|
|||
'admin_account_id' => 'nullable',
|
||||
'regs' => 'required|in:open,filtered,closed',
|
||||
'account_migration' => 'nullable',
|
||||
'rule_delete' => 'sometimes',
|
||||
]);
|
||||
|
||||
$orb = false;
|
||||
|
@ -310,4 +313,573 @@ trait AdminSettingsController
|
|||
|
||||
return view('admin.settings.system', compact('sys'));
|
||||
}
|
||||
|
||||
public function settingsApiFetch(Request $request)
|
||||
{
|
||||
$cloud_storage = ConfigCacheService::get('pixelfed.cloud_storage');
|
||||
$cloud_disk = config('filesystems.cloud');
|
||||
$cloud_ready = ! empty(config('filesystems.disks.'.$cloud_disk.'.key')) && ! empty(config('filesystems.disks.'.$cloud_disk.'.secret'));
|
||||
$types = explode(',', ConfigCacheService::get('pixelfed.media_types'));
|
||||
$rules = ConfigCacheService::get('app.rules') ? json_decode(ConfigCacheService::get('app.rules'), true) : [];
|
||||
$jpeg = in_array('image/jpg', $types) || in_array('image/jpeg', $types);
|
||||
$png = in_array('image/png', $types);
|
||||
$gif = in_array('image/gif', $types);
|
||||
$mp4 = in_array('video/mp4', $types);
|
||||
$webp = in_array('image/webp', $types);
|
||||
|
||||
$availableAdmins = User::whereIsAdmin(true)->get();
|
||||
$currentAdmin = config_cache('instance.admin.pid') ? AccountService::get(config_cache('instance.admin.pid'), true) : null;
|
||||
$openReg = (bool) config_cache('pixelfed.open_registration');
|
||||
$curOnboarding = (bool) config_cache('instance.curated_registration.enabled');
|
||||
$regState = $openReg ? 'open' : ($curOnboarding ? 'filtered' : 'closed');
|
||||
$accountMigration = (bool) config_cache('federation.migration');
|
||||
$autoFollow = config_cache('account.autofollow_usernames');
|
||||
if (strlen($autoFollow) > 3) {
|
||||
$autoFollow = explode(',', $autoFollow);
|
||||
}
|
||||
|
||||
$res = AdminSettingsService::getAll();
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
public function settingsApiRulesAdd(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'rule' => 'required|string|min:5|max:1000',
|
||||
]);
|
||||
|
||||
$rules = ConfigCacheService::get('app.rules');
|
||||
$val = $request->input('rule');
|
||||
if (! $rules) {
|
||||
ConfigCacheService::put('app.rules', json_encode([$val]));
|
||||
} else {
|
||||
$json = json_decode($rules, true);
|
||||
$count = count($json);
|
||||
if ($count >= 30) {
|
||||
return response()->json(['message' => 'Max rules limit reached, you can set up to 30 rules at a time.'], 400);
|
||||
}
|
||||
$json[] = $val;
|
||||
ConfigCacheService::put('app.rules', json_encode(array_values($json)));
|
||||
}
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Config::refresh();
|
||||
|
||||
return [$val];
|
||||
}
|
||||
|
||||
public function settingsApiRulesDelete(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'rule' => 'required|string',
|
||||
]);
|
||||
|
||||
$rules = ConfigCacheService::get('app.rules');
|
||||
$val = $request->input('rule');
|
||||
|
||||
if (! $rules) {
|
||||
return [];
|
||||
} else {
|
||||
$json = json_decode($rules, true);
|
||||
$idx = array_search($val, $json);
|
||||
if ($idx !== false) {
|
||||
unset($json[$idx]);
|
||||
$json = array_values($json);
|
||||
}
|
||||
ConfigCacheService::put('app.rules', json_encode(array_values($json)));
|
||||
}
|
||||
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Config::refresh();
|
||||
|
||||
return response()->json($json);
|
||||
}
|
||||
|
||||
public function settingsApiRulesDeleteAll(Request $request)
|
||||
{
|
||||
$rules = ConfigCacheService::get('app.rules');
|
||||
|
||||
if (! $rules) {
|
||||
return [];
|
||||
} else {
|
||||
ConfigCacheService::put('app.rules', json_encode([]));
|
||||
}
|
||||
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Config::refresh();
|
||||
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
public function settingsApiAutofollowDelete(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'username' => 'required|string',
|
||||
]);
|
||||
|
||||
$username = $request->input('username');
|
||||
$names = [];
|
||||
$existing = config_cache('account.autofollow_usernames');
|
||||
if ($existing) {
|
||||
$names = explode(',', $existing);
|
||||
}
|
||||
|
||||
if (in_array($username, $names)) {
|
||||
$key = array_search($username, $names);
|
||||
if ($key !== false) {
|
||||
unset($names[$key]);
|
||||
}
|
||||
}
|
||||
ConfigCacheService::put('account.autofollow_usernames', implode(',', $names));
|
||||
|
||||
return response()->json(['accounts' => array_values($names)]);
|
||||
}
|
||||
|
||||
public function settingsApiAutofollowAdd(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'username' => 'required|string',
|
||||
]);
|
||||
|
||||
$username = $request->input('username');
|
||||
$names = [];
|
||||
$existing = config_cache('account.autofollow_usernames');
|
||||
if ($existing) {
|
||||
$names = explode(',', $existing);
|
||||
}
|
||||
|
||||
if ($existing && count($names)) {
|
||||
if (count($names) >= 5) {
|
||||
return response()->json(['message' => 'You can only add up to 5 accounts to be autofollowed.'], 400);
|
||||
}
|
||||
if (in_array(strtolower($username), array_map('strtolower', $names))) {
|
||||
return response()->json(['message' => 'User already exists, please try again.'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
$p = User::whereUsername($username)->whereNull('status')->first();
|
||||
if (! $p || in_array($p->username, $names)) {
|
||||
abort(404);
|
||||
}
|
||||
array_push($names, $p->username);
|
||||
ConfigCacheService::put('account.autofollow_usernames', implode(',', $names));
|
||||
|
||||
return response()->json(['accounts' => array_values($names)]);
|
||||
}
|
||||
|
||||
public function settingsApiUpdateType(Request $request, $type)
|
||||
{
|
||||
abort_unless(in_array($type, [
|
||||
'posts',
|
||||
'platform',
|
||||
'home',
|
||||
'landing',
|
||||
'branding',
|
||||
'media',
|
||||
'users',
|
||||
'storage',
|
||||
]), 400);
|
||||
|
||||
switch ($type) {
|
||||
case 'home':
|
||||
return $this->settingsApiUpdateHomeType($request);
|
||||
break;
|
||||
|
||||
case 'landing':
|
||||
return $this->settingsApiUpdateLandingType($request);
|
||||
break;
|
||||
|
||||
case 'posts':
|
||||
return $this->settingsApiUpdatePostsType($request);
|
||||
break;
|
||||
|
||||
case 'platform':
|
||||
return $this->settingsApiUpdatePlatformType($request);
|
||||
break;
|
||||
|
||||
case 'branding':
|
||||
return $this->settingsApiUpdateBrandingType($request);
|
||||
break;
|
||||
|
||||
case 'media':
|
||||
return $this->settingsApiUpdateMediaType($request);
|
||||
break;
|
||||
|
||||
case 'users':
|
||||
return $this->settingsApiUpdateUsersType($request);
|
||||
break;
|
||||
|
||||
case 'storage':
|
||||
return $this->settingsApiUpdateStorageType($request);
|
||||
break;
|
||||
|
||||
default:
|
||||
abort(404);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function settingsApiUpdateHomeType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'registration_status' => 'required|in:open,filtered,closed',
|
||||
'cloud_storage' => 'required',
|
||||
'activitypub_enabled' => 'required',
|
||||
'account_migration' => 'required',
|
||||
'mobile_apis' => 'required',
|
||||
'stories' => 'required',
|
||||
'instagram_import' => 'required',
|
||||
'autospam_enabled' => 'required',
|
||||
]);
|
||||
|
||||
$regStatus = $request->input('registration_status');
|
||||
ConfigCacheService::put('pixelfed.open_registration', $regStatus === 'open');
|
||||
ConfigCacheService::put('instance.curated_registration.enabled', $regStatus === 'filtered');
|
||||
$cloudStorage = $request->boolean('cloud_storage');
|
||||
if ($cloudStorage !== (bool) config_cache('pixelfed.cloud_storage')) {
|
||||
if (! $cloudStorage) {
|
||||
ConfigCacheService::put('pixelfed.cloud_storage', false);
|
||||
} else {
|
||||
$cloud_disk = config('filesystems.cloud');
|
||||
$cloud_ready = ! empty(config('filesystems.disks.'.$cloud_disk.'.key')) && ! empty(config('filesystems.disks.'.$cloud_disk.'.secret'));
|
||||
if (! $cloud_ready) {
|
||||
return redirect()->back()->withErrors(['cloud_storage' => 'Must configure cloud storage before enabling!']);
|
||||
} else {
|
||||
ConfigCacheService::put('pixelfed.cloud_storage', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
ConfigCacheService::put('federation.activitypub.enabled', $request->boolean('activitypub_enabled'));
|
||||
ConfigCacheService::put('federation.migration', $request->boolean('account_migration'));
|
||||
ConfigCacheService::put('pixelfed.oauth_enabled', $request->boolean('mobile_apis'));
|
||||
ConfigCacheService::put('instance.stories.enabled', $request->boolean('stories'));
|
||||
ConfigCacheService::put('pixelfed.import.instagram.enabled', $request->boolean('instagram_import'));
|
||||
ConfigCacheService::put('pixelfed.bouncer.enabled', $request->boolean('autospam_enabled'));
|
||||
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Cache::forget('api:v1:instance-data:contact');
|
||||
Config::refresh();
|
||||
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
public function settingsApiUpdateLandingType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'current_admin' => 'required',
|
||||
'show_directory' => 'required',
|
||||
'show_explore' => 'required',
|
||||
]);
|
||||
|
||||
ConfigCacheService::put('instance.admin.pid', $request->input('current_admin'));
|
||||
ConfigCacheService::put('instance.landing.show_directory', $request->boolean('show_directory'));
|
||||
ConfigCacheService::put('instance.landing.show_explore', $request->boolean('show_explore'));
|
||||
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Cache::forget('api:v1:instance-data:contact');
|
||||
Config::refresh();
|
||||
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
public function settingsApiUpdateMediaType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'image_quality' => 'required|integer|min:1|max:100',
|
||||
'max_album_length' => 'required|integer|min:1|max:20',
|
||||
'max_photo_size' => 'required|integer|min:100|max:50000',
|
||||
'media_types' => 'required',
|
||||
'optimize_image' => 'required',
|
||||
'optimize_video' => 'required',
|
||||
]);
|
||||
|
||||
$mediaTypes = $request->input('media_types');
|
||||
$mediaArray = explode(',', $mediaTypes);
|
||||
foreach ($mediaArray as $mediaType) {
|
||||
if (! in_array($mediaType, ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'video/mp4'])) {
|
||||
return redirect()->back()->withErrors(['media_types' => 'Invalid media type']);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigCacheService::put('pixelfed.media_types', $request->input('media_types'));
|
||||
ConfigCacheService::put('pixelfed.image_quality', $request->input('image_quality'));
|
||||
ConfigCacheService::put('pixelfed.max_album_length', $request->input('max_album_length'));
|
||||
ConfigCacheService::put('pixelfed.max_photo_size', $request->input('max_photo_size'));
|
||||
ConfigCacheService::put('pixelfed.optimize_image', $request->boolean('optimize_image'));
|
||||
ConfigCacheService::put('pixelfed.optimize_video', $request->boolean('optimize_video'));
|
||||
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Cache::forget('api:v1:instance-data:contact');
|
||||
Config::refresh();
|
||||
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
public function settingsApiUpdateBrandingType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'name' => 'required',
|
||||
'short_description' => 'required',
|
||||
'long_description' => 'required',
|
||||
]);
|
||||
|
||||
ConfigCacheService::put('app.name', $request->input('name'));
|
||||
ConfigCacheService::put('app.short_description', $request->input('short_description'));
|
||||
ConfigCacheService::put('app.description', $request->input('long_description'));
|
||||
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Cache::forget('api:v1:instance-data:contact');
|
||||
Config::refresh();
|
||||
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
public function settingsApiUpdatePostsType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'max_caption_length' => 'required|integer|min:5|max:10000',
|
||||
'max_altext_length' => 'required|integer|min:5|max:40000',
|
||||
]);
|
||||
|
||||
ConfigCacheService::put('pixelfed.max_caption_length', $request->input('max_caption_length'));
|
||||
ConfigCacheService::put('pixelfed.max_altext_length', $request->input('max_altext_length'));
|
||||
$res = [
|
||||
'max_caption_length' => $request->input('max_caption_length'),
|
||||
'max_altext_length' => $request->input('max_altext_length'),
|
||||
];
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Config::refresh();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function settingsApiUpdatePlatformType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'allow_app_registration' => 'required',
|
||||
'app_registration_rate_limit_attempts' => 'required|integer|min:1',
|
||||
'app_registration_rate_limit_decay' => 'required|integer|min:1',
|
||||
'app_registration_confirm_rate_limit_attempts' => 'required|integer|min:1',
|
||||
'app_registration_confirm_rate_limit_decay' => 'required|integer|min:1',
|
||||
'allow_post_embeds' => 'required',
|
||||
'allow_profile_embeds' => 'required',
|
||||
'captcha_enabled' => 'required',
|
||||
'captcha_on_login' => 'required_if_accepted:captcha_enabled',
|
||||
'captcha_on_register' => 'required_if_accepted:captcha_enabled',
|
||||
'captcha_secret' => 'required_if_accepted:captcha_enabled',
|
||||
'captcha_sitekey' => 'required_if_accepted:captcha_enabled',
|
||||
'custom_emoji_enabled' => 'required',
|
||||
]);
|
||||
|
||||
ConfigCacheService::put('pixelfed.allow_app_registration', $request->boolean('allow_app_registration'));
|
||||
ConfigCacheService::put('pixelfed.app_registration_rate_limit_attempts', $request->input('app_registration_rate_limit_attempts'));
|
||||
ConfigCacheService::put('pixelfed.app_registration_rate_limit_decay', $request->input('app_registration_rate_limit_decay'));
|
||||
ConfigCacheService::put('pixelfed.app_registration_confirm_rate_limit_attempts', $request->input('app_registration_confirm_rate_limit_attempts'));
|
||||
ConfigCacheService::put('pixelfed.app_registration_confirm_rate_limit_decay', $request->input('app_registration_confirm_rate_limit_decay'));
|
||||
ConfigCacheService::put('instance.embed.post', $request->boolean('allow_post_embeds'));
|
||||
ConfigCacheService::put('instance.embed.profile', $request->boolean('allow_profile_embeds'));
|
||||
ConfigCacheService::put('federation.custom_emoji.enabled', $request->boolean('custom_emoji_enabled'));
|
||||
$captcha = $request->boolean('captcha_enabled');
|
||||
if ($captcha) {
|
||||
$secret = $request->input('captcha_secret');
|
||||
$sitekey = $request->input('captcha_sitekey');
|
||||
if (config_cache('captcha.secret') != $secret && strpos($secret, '*') === false) {
|
||||
ConfigCacheService::put('captcha.secret', $secret);
|
||||
}
|
||||
if (config_cache('captcha.sitekey') != $sitekey && strpos($sitekey, '*') === false) {
|
||||
ConfigCacheService::put('captcha.sitekey', $sitekey);
|
||||
}
|
||||
ConfigCacheService::put('captcha.active.login', $request->boolean('captcha_on_login'));
|
||||
ConfigCacheService::put('captcha.active.register', $request->boolean('captcha_on_register'));
|
||||
ConfigCacheService::put('captcha.triggers.login.enabled', $request->boolean('captcha_on_login'));
|
||||
ConfigCacheService::put('captcha.enabled', true);
|
||||
} else {
|
||||
ConfigCacheService::put('captcha.enabled', false);
|
||||
}
|
||||
$res = [
|
||||
'allow_app_registration' => $request->boolean('allow_app_registration'),
|
||||
'app_registration_rate_limit_attempts' => $request->input('app_registration_rate_limit_attempts'),
|
||||
'app_registration_rate_limit_decay' => $request->input('app_registration_rate_limit_decay'),
|
||||
'app_registration_confirm_rate_limit_attempts' => $request->input('app_registration_confirm_rate_limit_attempts'),
|
||||
'app_registration_confirm_rate_limit_decay' => $request->input('app_registration_confirm_rate_limit_decay'),
|
||||
'allow_post_embeds' => $request->boolean('allow_post_embeds'),
|
||||
'allow_profile_embeds' => $request->boolean('allow_profile_embeds'),
|
||||
'captcha_enabled' => $request->boolean('captcha_enabled'),
|
||||
'captcha_on_login' => $request->boolean('captcha_on_login'),
|
||||
'captcha_on_register' => $request->boolean('captcha_on_register'),
|
||||
'captcha_secret' => $request->input('captcha_secret'),
|
||||
'captcha_sitekey' => $request->input('captcha_sitekey'),
|
||||
'custom_emoji_enabled' => $request->boolean('custom_emoji_enabled'),
|
||||
];
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Config::refresh();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function settingsApiUpdateUsersType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'require_email_verification' => 'required',
|
||||
'enforce_account_limit' => 'required',
|
||||
'max_account_size' => 'required|integer|min:50000',
|
||||
'admin_autofollow' => 'required',
|
||||
'admin_autofollow_accounts' => 'sometimes',
|
||||
'max_user_blocks' => 'required|integer|min:0|max:5000',
|
||||
'max_user_mutes' => 'required|integer|min:0|max:5000',
|
||||
'max_domain_blocks' => 'required|integer|min:0|max:5000',
|
||||
]);
|
||||
|
||||
$adminAutofollow = $request->boolean('admin_autofollow');
|
||||
$adminAutofollowAccounts = $request->input('admin_autofollow_accounts');
|
||||
if ($adminAutofollow) {
|
||||
if ($request->filled('admin_autofollow_accounts')) {
|
||||
$names = [];
|
||||
$existing = config_cache('account.autofollow_usernames');
|
||||
if ($existing) {
|
||||
$names = explode(',', $existing);
|
||||
foreach (array_map('strtolower', $adminAutofollowAccounts) as $afc) {
|
||||
if (in_array(strtolower($afc), array_map('strtolower', $names))) {
|
||||
continue;
|
||||
}
|
||||
$names[] = $afc;
|
||||
}
|
||||
} else {
|
||||
$names = $adminAutofollowAccounts;
|
||||
}
|
||||
if (! $names || count($names) == 0) {
|
||||
return response()->json(['message' => 'You need to assign autofollow accounts before you can enable it.'], 400);
|
||||
}
|
||||
if (count($names) > 5) {
|
||||
return response()->json(['message' => 'You can only add up to 5 accounts to be autofollowed.'.json_encode($names)], 400);
|
||||
}
|
||||
$autofollows = User::whereIn('username', $names)->whereNull('status')->pluck('username');
|
||||
$adminAutofollowAccounts = $autofollows->implode(',');
|
||||
ConfigCacheService::put('account.autofollow_usernames', $adminAutofollowAccounts);
|
||||
} else {
|
||||
return response()->json(['message' => 'You need to assign autofollow accounts before you can enable it.'], 400);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigCacheService::put('pixelfed.enforce_email_verification', $request->boolean('require_email_verification'));
|
||||
ConfigCacheService::put('pixelfed.enforce_account_limit', $request->boolean('enforce_account_limit'));
|
||||
ConfigCacheService::put('pixelfed.max_account_size', $request->input('max_account_size'));
|
||||
ConfigCacheService::put('account.autofollow', $request->boolean('admin_autofollow'));
|
||||
ConfigCacheService::put('instance.user_filters.max_user_blocks', (int) $request->input('max_user_blocks'));
|
||||
ConfigCacheService::put('instance.user_filters.max_user_mutes', (int) $request->input('max_user_mutes'));
|
||||
ConfigCacheService::put('instance.user_filters.max_domain_blocks', (int) $request->input('max_domain_blocks'));
|
||||
$res = [
|
||||
'require_email_verification' => $request->boolean('require_email_verification'),
|
||||
'enforce_account_limit' => $request->boolean('enforce_account_limit'),
|
||||
'admin_autofollow' => $request->boolean('admin_autofollow'),
|
||||
'admin_autofollow_accounts' => $adminAutofollowAccounts,
|
||||
'max_user_blocks' => $request->input('max_user_blocks'),
|
||||
'max_user_mutes' => $request->input('max_user_mutes'),
|
||||
'max_domain_blocks' => $request->input('max_domain_blocks'),
|
||||
];
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Config::refresh();
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function settingsApiUpdateStorageType($request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'primary_disk' => 'required|in:local,cloud',
|
||||
'update_disk' => 'sometimes',
|
||||
'disk_config' => 'required_if_accepted:update_disk',
|
||||
'disk_config.driver' => 'required|in:s3,spaces',
|
||||
'disk_config.key' => 'required',
|
||||
'disk_config.secret' => 'required',
|
||||
'disk_config.region' => 'required',
|
||||
'disk_config.bucket' => 'required',
|
||||
'disk_config.visibility' => 'required',
|
||||
'disk_config.endpoint' => 'required',
|
||||
'disk_config.url' => 'nullable',
|
||||
]);
|
||||
|
||||
ConfigCacheService::put('pixelfed.cloud_storage', $request->input('primary_disk') === 'cloud');
|
||||
$res = [
|
||||
'primary_disk' => $request->input('primary_disk'),
|
||||
];
|
||||
if ($request->has('update_disk')) {
|
||||
$res['disk_config'] = $request->input('disk_config');
|
||||
$changes = [];
|
||||
$dkey = $request->input('disk_config.driver') === 's3' ? 'filesystems.disks.s3.' : 'filesystems.disks.spaces.';
|
||||
$key = $request->input('disk_config.key');
|
||||
$ckey = null;
|
||||
$secret = $request->input('disk_config.secret');
|
||||
$csecret = null;
|
||||
$region = $request->input('disk_config.region');
|
||||
$bucket = $request->input('disk_config.bucket');
|
||||
$visibility = $request->input('disk_config.visibility');
|
||||
$url = $request->input('disk_config.url');
|
||||
$endpoint = $request->input('disk_config.endpoint');
|
||||
if (strpos($key, '*') === false && $key != config_cache($dkey.'key')) {
|
||||
array_push($changes, 'key');
|
||||
} else {
|
||||
$ckey = config_cache($dkey.'key');
|
||||
}
|
||||
if (strpos($secret, '*') === false && $secret != config_cache($dkey.'secret')) {
|
||||
array_push($changes, 'secret');
|
||||
} else {
|
||||
$csecret = config_cache($dkey.'secret');
|
||||
}
|
||||
if ($region != config_cache($dkey.'region')) {
|
||||
array_push($changes, 'region');
|
||||
}
|
||||
if ($bucket != config_cache($dkey.'bucket')) {
|
||||
array_push($changes, 'bucket');
|
||||
}
|
||||
if ($visibility != config_cache($dkey.'visibility')) {
|
||||
array_push($changes, 'visibility');
|
||||
}
|
||||
if ($url != config_cache($dkey.'url')) {
|
||||
array_push($changes, 'url');
|
||||
}
|
||||
if ($endpoint != config_cache($dkey.'endpoint')) {
|
||||
array_push($changes, 'endpoint');
|
||||
}
|
||||
|
||||
if ($changes && count($changes)) {
|
||||
$isValid = FilesystemService::getVerifyCredentials(
|
||||
$ckey ?? $key,
|
||||
$csecret ?? $secret,
|
||||
$region,
|
||||
$bucket,
|
||||
$endpoint,
|
||||
);
|
||||
if (! $isValid) {
|
||||
return response()->json(['error' => true, 's3_vce' => true, 'message' => "<div class='border border-danger text-danger p-3 font-weight-bold rounded-lg'>The S3/Spaces credentials you provided are invalid, or the bucket does not have the proper permissions.</div><br/>Please check all fields and try again.<br/><br/><strong>Any cloud storage configuration changes you made have NOT been saved due to invalid credentials.</strong>"], 400);
|
||||
}
|
||||
}
|
||||
$res['changes'] = json_encode($changes);
|
||||
}
|
||||
Cache::forget('api:v1:instance-data:rules');
|
||||
Cache::forget('api:v1:instance-data-response-v1');
|
||||
Cache::forget('api:v2:instance-data-response-v2');
|
||||
Config::refresh();
|
||||
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -424,7 +424,7 @@ class AdminController extends Controller
|
|||
|
||||
public function customEmojiHome(Request $request)
|
||||
{
|
||||
if(!config('federation.custom_emoji.enabled')) {
|
||||
if(!(bool) config_cache('federation.custom_emoji.enabled')) {
|
||||
return view('admin.custom-emoji.not-enabled');
|
||||
}
|
||||
$this->validate($request, [
|
||||
|
@ -497,7 +497,7 @@ class AdminController extends Controller
|
|||
|
||||
public function customEmojiToggleActive(Request $request, $id)
|
||||
{
|
||||
abort_unless(config('federation.custom_emoji.enabled'), 404);
|
||||
abort_unless((bool) config_cache('federation.custom_emoji.enabled'), 404);
|
||||
$emoji = CustomEmoji::findOrFail($id);
|
||||
$emoji->disabled = !$emoji->disabled;
|
||||
$emoji->save();
|
||||
|
@ -508,13 +508,13 @@ class AdminController extends Controller
|
|||
|
||||
public function customEmojiAdd(Request $request)
|
||||
{
|
||||
abort_unless(config('federation.custom_emoji.enabled'), 404);
|
||||
abort_unless((bool) config_cache('federation.custom_emoji.enabled'), 404);
|
||||
return view('admin.custom-emoji.add');
|
||||
}
|
||||
|
||||
public function customEmojiStore(Request $request)
|
||||
{
|
||||
abort_unless(config('federation.custom_emoji.enabled'), 404);
|
||||
abort_unless((bool) config_cache('federation.custom_emoji.enabled'), 404);
|
||||
$this->validate($request, [
|
||||
'shortcode' => [
|
||||
'required',
|
||||
|
@ -545,7 +545,7 @@ class AdminController extends Controller
|
|||
|
||||
public function customEmojiDelete(Request $request, $id)
|
||||
{
|
||||
abort_unless(config('federation.custom_emoji.enabled'), 404);
|
||||
abort_unless((bool) config_cache('federation.custom_emoji.enabled'), 404);
|
||||
$emoji = CustomEmoji::findOrFail($id);
|
||||
Storage::delete("public/{$emoji->media_path}");
|
||||
Cache::forget('pf:custom_emoji');
|
||||
|
@ -555,7 +555,7 @@ class AdminController extends Controller
|
|||
|
||||
public function customEmojiShowDuplicates(Request $request, $id)
|
||||
{
|
||||
abort_unless(config('federation.custom_emoji.enabled'), 404);
|
||||
abort_unless((bool) config_cache('federation.custom_emoji.enabled'), 404);
|
||||
$emoji = CustomEmoji::orderBy('id')->whereDisabled(false)->whereShortcode($id)->firstOrFail();
|
||||
$emojis = CustomEmoji::whereShortcode($id)->where('id', '!=', $emoji->id)->cursorPaginate(10);
|
||||
return view('admin.custom-emoji.duplicates', compact('emoji', 'emojis'));
|
||||
|
|
|
@ -131,7 +131,7 @@ class ApiV1Controller extends Controller
|
|||
*/
|
||||
public function apps(Request $request)
|
||||
{
|
||||
abort_if(! config_cache('pixelfed.oauth_enabled'), 404);
|
||||
abort_if(! (bool) config_cache('pixelfed.oauth_enabled'), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'client_name' => 'required',
|
||||
|
@ -1103,7 +1103,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
$count = UserFilterService::blockCount($pid);
|
||||
$maxLimit = intval(config('instance.user_filters.max_user_blocks'));
|
||||
$maxLimit = (int) config_cache('instance.user_filters.max_user_blocks');
|
||||
if ($count == 0) {
|
||||
$filterCount = UserFilter::whereUserId($pid)
|
||||
->whereFilterType('block')
|
||||
|
@ -1632,7 +1632,7 @@ class ApiV1Controller extends Controller
|
|||
|
||||
return [
|
||||
'uri' => config('pixelfed.domain.app'),
|
||||
'title' => config('app.name'),
|
||||
'title' => config_cache('app.name'),
|
||||
'short_description' => config_cache('app.short_description'),
|
||||
'description' => config_cache('app.description'),
|
||||
'email' => config('instance.email'),
|
||||
|
@ -1650,11 +1650,11 @@ class ApiV1Controller extends Controller
|
|||
'configuration' => [
|
||||
'media_attachments' => [
|
||||
'image_matrix_limit' => 16777216,
|
||||
'image_size_limit' => config('pixelfed.max_photo_size') * 1024,
|
||||
'supported_mime_types' => explode(',', config('pixelfed.media_types')),
|
||||
'image_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
|
||||
'supported_mime_types' => explode(',', config_cache('pixelfed.media_types')),
|
||||
'video_frame_rate_limit' => 120,
|
||||
'video_matrix_limit' => 2304000,
|
||||
'video_size_limit' => config('pixelfed.max_photo_size') * 1024,
|
||||
'video_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
|
||||
],
|
||||
'polls' => [
|
||||
'max_characters_per_option' => 50,
|
||||
|
@ -1665,7 +1665,7 @@ class ApiV1Controller extends Controller
|
|||
'statuses' => [
|
||||
'characters_reserved_per_url' => 23,
|
||||
'max_characters' => (int) config_cache('pixelfed.max_caption_length'),
|
||||
'max_media_attachments' => (int) config('pixelfed.max_album_length'),
|
||||
'max_media_attachments' => (int) config_cache('pixelfed.max_album_length'),
|
||||
],
|
||||
],
|
||||
];
|
||||
|
@ -2145,7 +2145,7 @@ class ApiV1Controller extends Controller
|
|||
}
|
||||
|
||||
$count = UserFilterService::muteCount($pid);
|
||||
$maxLimit = intval(config('instance.user_filters.max_user_mutes'));
|
||||
$maxLimit = (int) config_cache('instance.user_filters.max_user_mutes');
|
||||
if ($count == 0) {
|
||||
$filterCount = UserFilter::whereUserId($pid)
|
||||
->whereFilterType('mute')
|
||||
|
@ -3308,9 +3308,9 @@ class ApiV1Controller extends Controller
|
|||
abort_unless($request->user()->tokenCan('write'), 403);
|
||||
|
||||
$this->validate($request, [
|
||||
'status' => 'nullable|string|max:' . config_cache('pixelfed.max_caption_length'),
|
||||
'status' => 'nullable|string|max:'.(int) config_cache('pixelfed.max_caption_length'),
|
||||
'in_reply_to_id' => 'nullable',
|
||||
'media_ids' => 'sometimes|array|max:'.config_cache('pixelfed.max_album_length'),
|
||||
'media_ids' => 'sometimes|array|max:'.(int) config_cache('pixelfed.max_album_length'),
|
||||
'sensitive' => 'nullable',
|
||||
'visibility' => 'string|in:private,unlisted,public',
|
||||
'spoiler_text' => 'sometimes|max:140',
|
||||
|
@ -3436,7 +3436,7 @@ class ApiV1Controller extends Controller
|
|||
$mimes = [];
|
||||
|
||||
foreach ($ids as $k => $v) {
|
||||
if ($k + 1 > config_cache('pixelfed.max_album_length')) {
|
||||
if ($k + 1 > (int) config_cache('pixelfed.max_album_length')) {
|
||||
continue;
|
||||
}
|
||||
$m = Media::whereUserId($user->id)->whereNull('status_id')->findOrFail($v);
|
||||
|
|
|
@ -72,7 +72,7 @@ class DomainBlockController extends Controller
|
|||
abort_if(config_cache('pixelfed.domain.app') == $domain, 400, 'Cannot ban your own server');
|
||||
|
||||
$existingCount = UserDomainBlock::whereProfileId($pid)->count();
|
||||
$maxLimit = config('instance.user_filters.max_domain_blocks');
|
||||
$maxLimit = (int) config_cache('instance.user_filters.max_domain_blocks');
|
||||
$errorMsg = __('profile.block.domain.max', ['max' => $maxLimit]);
|
||||
|
||||
abort_if($existingCount >= $maxLimit, 400, $errorMsg);
|
||||
|
|
|
@ -62,7 +62,7 @@ class ForgotPasswordController extends Controller
|
|||
|
||||
usleep(random_int(100000, 3000000));
|
||||
|
||||
if(config('captcha.enabled')) {
|
||||
if((bool) config_cache('captcha.enabled')) {
|
||||
$rules = [
|
||||
'email' => 'required|email',
|
||||
'h-captcha-response' => 'required|captcha'
|
||||
|
|
|
@ -74,10 +74,10 @@ class LoginController extends Controller
|
|||
$messages = [];
|
||||
|
||||
if(
|
||||
config('captcha.enabled') ||
|
||||
config('captcha.active.login') ||
|
||||
(bool) config_cache('captcha.enabled') &&
|
||||
(bool) config_cache('captcha.active.login') ||
|
||||
(
|
||||
config('captcha.triggers.login.enabled') &&
|
||||
(bool) config_cache('captcha.triggers.login.enabled') &&
|
||||
request()->session()->has('login_attempts') &&
|
||||
request()->session()->get('login_attempts') >= config('captcha.triggers.login.attempts')
|
||||
)
|
||||
|
|
|
@ -137,7 +137,7 @@ class RegisterController extends Controller
|
|||
'password' => 'required|string|min:'.config('pixelfed.min_password_length').'|confirmed',
|
||||
];
|
||||
|
||||
if(config('captcha.enabled') || config('captcha.active.register')) {
|
||||
if((bool) config_cache('captcha.enabled') && (bool) config_cache('captcha.active.register')) {
|
||||
$rules['h-captcha-response'] = 'required|captcha';
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ class ResetPasswordController extends Controller
|
|||
{
|
||||
usleep(random_int(100000, 3000000));
|
||||
|
||||
if(config('captcha.enabled')) {
|
||||
if((bool) config_cache('captcha.enabled')) {
|
||||
return [
|
||||
'token' => 'required',
|
||||
'email' => 'required|email',
|
||||
|
|
|
@ -741,7 +741,7 @@ class ComposeController extends Controller
|
|||
case 'image/jpeg':
|
||||
case 'image/png':
|
||||
case 'video/mp4':
|
||||
$finished = config_cache('pixelfed.cloud_storage') ? (bool) $media->cdn_url : (bool) $media->processed_at;
|
||||
$finished = (bool) config_cache('pixelfed.cloud_storage') ? (bool) $media->cdn_url : (bool) $media->processed_at;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
|
@ -2,57 +2,42 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Jobs\InboxPipeline\{
|
||||
DeleteWorker,
|
||||
InboxWorker,
|
||||
InboxValidator
|
||||
};
|
||||
use App\Jobs\RemoteFollowPipeline\RemoteFollowPipeline;
|
||||
use App\{
|
||||
AccountLog,
|
||||
Like,
|
||||
Profile,
|
||||
Status,
|
||||
User
|
||||
};
|
||||
use App\Util\Lexer\Nickname;
|
||||
use App\Util\Webfinger\Webfinger;
|
||||
use Auth;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use League\Fractal;
|
||||
use App\Util\Site\Nodeinfo;
|
||||
use App\Util\ActivityPub\{
|
||||
Helpers,
|
||||
HttpSignature,
|
||||
Outbox
|
||||
};
|
||||
use Zttp\Zttp;
|
||||
use App\Services\InstanceService;
|
||||
use App\Jobs\InboxPipeline\DeleteWorker;
|
||||
use App\Jobs\InboxPipeline\InboxValidator;
|
||||
use App\Jobs\InboxPipeline\InboxWorker;
|
||||
use App\Profile;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\InstanceService;
|
||||
use App\Status;
|
||||
use App\Util\Lexer\Nickname;
|
||||
use App\Util\Site\Nodeinfo;
|
||||
use App\Util\Webfinger\Webfinger;
|
||||
use Cache;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class FederationController extends Controller
|
||||
{
|
||||
public function nodeinfoWellKnown()
|
||||
{
|
||||
abort_if(!config('federation.nodeinfo.enabled'), 404);
|
||||
abort_if(! config('federation.nodeinfo.enabled'), 404);
|
||||
|
||||
return response()->json(Nodeinfo::wellKnown(), 200, [], JSON_UNESCAPED_SLASHES)
|
||||
->header('Access-Control-Allow-Origin','*');
|
||||
->header('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
|
||||
public function nodeinfo()
|
||||
{
|
||||
abort_if(!config('federation.nodeinfo.enabled'), 404);
|
||||
abort_if(! config('federation.nodeinfo.enabled'), 404);
|
||||
|
||||
return response()->json(Nodeinfo::get(), 200, [], JSON_UNESCAPED_SLASHES)
|
||||
->header('Access-Control-Allow-Origin','*');
|
||||
->header('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
|
||||
public function webfinger(Request $request)
|
||||
{
|
||||
if (!config('federation.webfinger.enabled') ||
|
||||
!$request->has('resource') ||
|
||||
!$request->filled('resource')
|
||||
if (! config('federation.webfinger.enabled') ||
|
||||
! $request->has('resource') ||
|
||||
! $request->filled('resource')
|
||||
) {
|
||||
return response('', 400);
|
||||
}
|
||||
|
@ -60,55 +45,56 @@ class FederationController extends Controller
|
|||
$resource = $request->input('resource');
|
||||
$domain = config('pixelfed.domain.app');
|
||||
|
||||
if(config('federation.activitypub.sharedInbox') &&
|
||||
$resource == 'acct:' . $domain . '@' . $domain) {
|
||||
if (config('federation.activitypub.sharedInbox') &&
|
||||
$resource == 'acct:'.$domain.'@'.$domain) {
|
||||
$res = [
|
||||
'subject' => 'acct:' . $domain . '@' . $domain,
|
||||
'subject' => 'acct:'.$domain.'@'.$domain,
|
||||
'aliases' => [
|
||||
'https://' . $domain . '/i/actor'
|
||||
'https://'.$domain.'/i/actor',
|
||||
],
|
||||
'links' => [
|
||||
[
|
||||
'rel' => 'http://webfinger.net/rel/profile-page',
|
||||
'type' => 'text/html',
|
||||
'href' => 'https://' . $domain . '/site/kb/instance-actor'
|
||||
'href' => 'https://'.$domain.'/site/kb/instance-actor',
|
||||
],
|
||||
[
|
||||
'rel' => 'self',
|
||||
'type' => 'application/activity+json',
|
||||
'href' => 'https://' . $domain . '/i/actor'
|
||||
]
|
||||
]
|
||||
'href' => 'https://'.$domain.'/i/actor',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
$hash = hash('sha256', $resource);
|
||||
$key = 'federation:webfinger:sha256:' . $hash;
|
||||
if($cached = Cache::get($key)) {
|
||||
$key = 'federation:webfinger:sha256:'.$hash;
|
||||
if ($cached = Cache::get($key)) {
|
||||
return response()->json($cached, 200, [], JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
if(strpos($resource, $domain) == false) {
|
||||
if (strpos($resource, $domain) == false) {
|
||||
return response('', 400);
|
||||
}
|
||||
$parsed = Nickname::normalizeProfileUrl($resource);
|
||||
if(empty($parsed) || $parsed['domain'] !== $domain) {
|
||||
if (empty($parsed) || $parsed['domain'] !== $domain) {
|
||||
return response('', 400);
|
||||
}
|
||||
$username = $parsed['username'];
|
||||
$profile = Profile::whereNull('domain')->whereUsername($username)->first();
|
||||
if(!$profile || $profile->status !== null) {
|
||||
if (! $profile || $profile->status !== null) {
|
||||
return response('', 400);
|
||||
}
|
||||
$webfinger = (new Webfinger($profile))->generate();
|
||||
Cache::put($key, $webfinger, 1209600);
|
||||
|
||||
return response()->json($webfinger, 200, [], JSON_UNESCAPED_SLASHES)
|
||||
->header('Access-Control-Allow-Origin','*');
|
||||
->header('Access-Control-Allow-Origin', '*');
|
||||
}
|
||||
|
||||
public function hostMeta(Request $request)
|
||||
{
|
||||
abort_if(!config('federation.webfinger.enabled'), 404);
|
||||
abort_if(! config('federation.webfinger.enabled'), 404);
|
||||
|
||||
$path = route('well-known.webfinger');
|
||||
$xml = '<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" type="application/xrd+xml" template="'.$path.'?resource={uri}"/></XRD>';
|
||||
|
@ -118,19 +104,19 @@ class FederationController extends Controller
|
|||
|
||||
public function userOutbox(Request $request, $username)
|
||||
{
|
||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404);
|
||||
|
||||
if(!$request->wantsJson()) {
|
||||
return redirect('/' . $username);
|
||||
if (! $request->wantsJson()) {
|
||||
return redirect('/'.$username);
|
||||
}
|
||||
|
||||
$id = AccountService::usernameToId($username);
|
||||
abort_if(!$id, 404);
|
||||
abort_if(! $id, 404);
|
||||
$account = AccountService::get($id);
|
||||
abort_if(!$account || !isset($account['statuses_count']), 404);
|
||||
abort_if(! $account || ! isset($account['statuses_count']), 404);
|
||||
$res = [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'id' => 'https://' . config('pixelfed.domain.app') . '/users/' . $username . '/outbox',
|
||||
'id' => 'https://'.config('pixelfed.domain.app').'/users/'.$username.'/outbox',
|
||||
'type' => 'OrderedCollection',
|
||||
'totalItems' => $account['statuses_count'] ?? 0,
|
||||
];
|
||||
|
@ -140,135 +126,145 @@ class FederationController extends Controller
|
|||
|
||||
public function userInbox(Request $request, $username)
|
||||
{
|
||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(!config('federation.activitypub.inbox'), 404);
|
||||
abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(! config('federation.activitypub.inbox'), 404);
|
||||
|
||||
$headers = $request->headers->all();
|
||||
$payload = $request->getContent();
|
||||
if(!$payload || empty($payload)) {
|
||||
if (! $payload || empty($payload)) {
|
||||
return;
|
||||
}
|
||||
$obj = json_decode($payload, true, 8);
|
||||
if(!isset($obj['id'])) {
|
||||
if (! isset($obj['id'])) {
|
||||
return;
|
||||
}
|
||||
$domain = parse_url($obj['id'], PHP_URL_HOST);
|
||||
if(in_array($domain, InstanceService::getBannedDomains())) {
|
||||
if (in_array($domain, InstanceService::getBannedDomains())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(isset($obj['type']) && $obj['type'] === 'Delete') {
|
||||
if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) {
|
||||
if($obj['object']['type'] === 'Person') {
|
||||
if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) {
|
||||
if (isset($obj['type']) && $obj['type'] === 'Delete') {
|
||||
if (isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) {
|
||||
if ($obj['object']['type'] === 'Person') {
|
||||
if (Profile::whereRemoteUrl($obj['object']['id'])->exists()) {
|
||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if($obj['object']['type'] === 'Tombstone') {
|
||||
if(Status::whereObjectUrl($obj['object']['id'])->exists()) {
|
||||
if ($obj['object']['type'] === 'Tombstone') {
|
||||
if (Status::whereObjectUrl($obj['object']['id'])->exists()) {
|
||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('delete');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if($obj['object']['type'] === 'Story') {
|
||||
if ($obj['object']['type'] === 'Story') {
|
||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('story');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
} else if( isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) {
|
||||
} elseif (isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) {
|
||||
dispatch(new InboxValidator($username, $headers, $payload))->onQueue('follow');
|
||||
} else {
|
||||
dispatch(new InboxValidator($username, $headers, $payload))->onQueue('high');
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public function sharedInbox(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(!config('federation.activitypub.sharedInbox'), 404);
|
||||
abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(! config('federation.activitypub.sharedInbox'), 404);
|
||||
|
||||
$headers = $request->headers->all();
|
||||
$payload = $request->getContent();
|
||||
|
||||
if(!$payload || empty($payload)) {
|
||||
if (! $payload || empty($payload)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$obj = json_decode($payload, true, 8);
|
||||
if(!isset($obj['id'])) {
|
||||
if (! isset($obj['id'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$domain = parse_url($obj['id'], PHP_URL_HOST);
|
||||
if(in_array($domain, InstanceService::getBannedDomains())) {
|
||||
if (in_array($domain, InstanceService::getBannedDomains())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(isset($obj['type']) && $obj['type'] === 'Delete') {
|
||||
if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) {
|
||||
if($obj['object']['type'] === 'Person') {
|
||||
if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) {
|
||||
if (isset($obj['type']) && $obj['type'] === 'Delete') {
|
||||
if (isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) {
|
||||
if ($obj['object']['type'] === 'Person') {
|
||||
if (Profile::whereRemoteUrl($obj['object']['id'])->exists()) {
|
||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if($obj['object']['type'] === 'Tombstone') {
|
||||
if(Status::whereObjectUrl($obj['object']['id'])->exists()) {
|
||||
if ($obj['object']['type'] === 'Tombstone') {
|
||||
if (Status::whereObjectUrl($obj['object']['id'])->exists()) {
|
||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('delete');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if($obj['object']['type'] === 'Story') {
|
||||
if ($obj['object']['type'] === 'Story') {
|
||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('story');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
} else if( isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) {
|
||||
} elseif (isset($obj['type']) && in_array($obj['type'], ['Follow', 'Accept'])) {
|
||||
dispatch(new InboxWorker($headers, $payload))->onQueue('follow');
|
||||
} else {
|
||||
dispatch(new InboxWorker($headers, $payload))->onQueue('shared');
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public function userFollowing(Request $request, $username)
|
||||
{
|
||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404);
|
||||
|
||||
$id = AccountService::usernameToId($username);
|
||||
abort_if(!$id, 404);
|
||||
abort_if(! $id, 404);
|
||||
$account = AccountService::get($id);
|
||||
abort_if(!$account || !isset($account['following_count']), 404);
|
||||
abort_if(! $account || ! isset($account['following_count']), 404);
|
||||
$obj = [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'id' => $request->getUri(),
|
||||
'type' => 'OrderedCollection',
|
||||
'id' => $request->getUri(),
|
||||
'type' => 'OrderedCollection',
|
||||
'totalItems' => $account['following_count'] ?? 0,
|
||||
];
|
||||
|
||||
return response()->json($obj)->header('Content-Type', 'application/activity+json');
|
||||
}
|
||||
|
||||
public function userFollowers(Request $request, $username)
|
||||
{
|
||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404);
|
||||
$id = AccountService::usernameToId($username);
|
||||
abort_if(!$id, 404);
|
||||
abort_if(! $id, 404);
|
||||
$account = AccountService::get($id);
|
||||
abort_if(!$account || !isset($account['followers_count']), 404);
|
||||
abort_if(! $account || ! isset($account['followers_count']), 404);
|
||||
$obj = [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'id' => $request->getUri(),
|
||||
'type' => 'OrderedCollection',
|
||||
'id' => $request->getUri(),
|
||||
'type' => 'OrderedCollection',
|
||||
'totalItems' => $account['followers_count'] ?? 0,
|
||||
];
|
||||
|
||||
return response()->json($obj)->header('Content-Type', 'application/activity+json');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ trait Instagram
|
|||
{
|
||||
public function instagram()
|
||||
{
|
||||
if(config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
return view('settings.import.instagram.home');
|
||||
|
@ -25,6 +25,9 @@ trait Instagram
|
|||
|
||||
public function instagramStart(Request $request)
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$completed = ImportJob::whereProfileId(Auth::user()->profile->id)
|
||||
->whereService('instagram')
|
||||
->whereNotNull('completed_at')
|
||||
|
@ -38,6 +41,9 @@ trait Instagram
|
|||
|
||||
protected function instagramRedirectOrNew()
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$profile = Auth::user()->profile;
|
||||
$exists = ImportJob::whereProfileId($profile->id)
|
||||
->whereService('instagram')
|
||||
|
@ -61,6 +67,9 @@ trait Instagram
|
|||
|
||||
public function instagramStepOne(Request $request, $uuid)
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$profile = Auth::user()->profile;
|
||||
$job = ImportJob::whereProfileId($profile->id)
|
||||
->whereNull('completed_at')
|
||||
|
@ -72,6 +81,9 @@ trait Instagram
|
|||
|
||||
public function instagramStepOneStore(Request $request, $uuid)
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$max = 'max:' . config('pixelfed.import.instagram.limits.size');
|
||||
$this->validate($request, [
|
||||
'media.*' => 'required|mimes:bin,jpeg,png,gif|'.$max,
|
||||
|
@ -114,6 +126,9 @@ trait Instagram
|
|||
|
||||
public function instagramStepTwo(Request $request, $uuid)
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$profile = Auth::user()->profile;
|
||||
$job = ImportJob::whereProfileId($profile->id)
|
||||
->whereNull('completed_at')
|
||||
|
@ -125,6 +140,9 @@ trait Instagram
|
|||
|
||||
public function instagramStepTwoStore(Request $request, $uuid)
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$this->validate($request, [
|
||||
'media' => 'required|file|max:1000'
|
||||
]);
|
||||
|
@ -150,6 +168,9 @@ trait Instagram
|
|||
|
||||
public function instagramStepThree(Request $request, $uuid)
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$profile = Auth::user()->profile;
|
||||
$job = ImportJob::whereProfileId($profile->id)
|
||||
->whereService('instagram')
|
||||
|
@ -162,6 +183,9 @@ trait Instagram
|
|||
|
||||
public function instagramStepThreeStore(Request $request, $uuid)
|
||||
{
|
||||
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||
abort(404, 'Feature not enabled');
|
||||
}
|
||||
$profile = Auth::user()->profile;
|
||||
|
||||
try {
|
||||
|
|
|
@ -179,7 +179,7 @@ class ImportPostController extends Controller
|
|||
'required',
|
||||
'file',
|
||||
$mimes,
|
||||
'max:' . config('pixelfed.max_photo_size')
|
||||
'max:' . config_cache('pixelfed.max_photo_size')
|
||||
]
|
||||
]);
|
||||
|
||||
|
|
|
@ -2,44 +2,43 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Profile;
|
||||
use App\Services\AccountService;
|
||||
use App\Http\Resources\DirectoryProfile;
|
||||
use App\Profile;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class LandingController extends Controller
|
||||
{
|
||||
public function directoryRedirect(Request $request)
|
||||
{
|
||||
if($request->user()) {
|
||||
return redirect('/');
|
||||
}
|
||||
if ($request->user()) {
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
abort_if(config_cache('instance.landing.show_directory') == false, 404);
|
||||
abort_if((bool) config_cache('instance.landing.show_directory') == false, 404);
|
||||
|
||||
return view('site.index');
|
||||
return view('site.index');
|
||||
}
|
||||
|
||||
public function exploreRedirect(Request $request)
|
||||
{
|
||||
if($request->user()) {
|
||||
return redirect('/');
|
||||
}
|
||||
if ($request->user()) {
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
abort_if(config_cache('instance.landing.show_explore') == false, 404);
|
||||
abort_if((bool) config_cache('instance.landing.show_explore') == false, 404);
|
||||
|
||||
return view('site.index');
|
||||
return view('site.index');
|
||||
}
|
||||
|
||||
public function getDirectoryApi(Request $request)
|
||||
{
|
||||
abort_if(config_cache('instance.landing.show_directory') == false, 404);
|
||||
abort_if((bool) config_cache('instance.landing.show_directory') == false, 404);
|
||||
|
||||
return DirectoryProfile::collection(
|
||||
Profile::whereNull('domain')
|
||||
->whereIsSuggestable(true)
|
||||
->orderByDesc('updated_at')
|
||||
->cursorPaginate(20)
|
||||
);
|
||||
return DirectoryProfile::collection(
|
||||
Profile::whereNull('domain')
|
||||
->whereIsSuggestable(true)
|
||||
->orderByDesc('updated_at')
|
||||
->cursorPaginate(20)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,30 +2,31 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Media;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class MediaController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
//return view('settings.drive.index');
|
||||
}
|
||||
public function index(Request $request)
|
||||
{
|
||||
//return view('settings.drive.index');
|
||||
abort(404);
|
||||
}
|
||||
|
||||
public function composeUpdate(Request $request, $id)
|
||||
{
|
||||
public function composeUpdate(Request $request, $id)
|
||||
{
|
||||
abort(400, 'Endpoint deprecated');
|
||||
}
|
||||
}
|
||||
|
||||
public function fallbackRedirect(Request $request, $pid, $mhash, $uhash, $f)
|
||||
{
|
||||
abort_if(!config_cache('pixelfed.cloud_storage'), 404);
|
||||
$path = 'public/m/_v2/' . $pid . '/' . $mhash . '/' . $uhash . '/' . $f;
|
||||
$media = Media::whereProfileId($pid)
|
||||
->whereMediaPath($path)
|
||||
->whereNotNull('cdn_url')
|
||||
->firstOrFail();
|
||||
public function fallbackRedirect(Request $request, $pid, $mhash, $uhash, $f)
|
||||
{
|
||||
abort_if(! (bool) config_cache('pixelfed.cloud_storage'), 404);
|
||||
$path = 'public/m/_v2/'.$pid.'/'.$mhash.'/'.$uhash.'/'.$f;
|
||||
$media = Media::whereProfileId($pid)
|
||||
->whereMediaPath($path)
|
||||
->whereNotNull('cdn_url')
|
||||
->firstOrFail();
|
||||
|
||||
return redirect()->away($media->cdn_url);
|
||||
}
|
||||
return redirect()->away($media->cdn_url);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,37 +2,41 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Models\ConfigCache;
|
||||
use Storage;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\StatusService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Cache;
|
||||
use Storage;
|
||||
use App\Status;
|
||||
use App\User;
|
||||
|
||||
class PixelfedDirectoryController extends Controller
|
||||
{
|
||||
public function get(Request $request)
|
||||
{
|
||||
if(!$request->filled('sk')) {
|
||||
if (! $request->filled('sk')) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
if(!config_cache('pixelfed.directory.submission-key')) {
|
||||
if (! config_cache('pixelfed.directory.submission-key')) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
if(!hash_equals(config_cache('pixelfed.directory.submission-key'), $request->input('sk'))) {
|
||||
if (! hash_equals(config_cache('pixelfed.directory.submission-key'), $request->input('sk'))) {
|
||||
abort(403);
|
||||
}
|
||||
|
||||
$res = $this->buildListing();
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function buildListing()
|
||||
{
|
||||
$res = config_cache('pixelfed.directory');
|
||||
if($res) {
|
||||
if ($res) {
|
||||
$res = is_string($res) ? json_decode($res, true) : $res;
|
||||
}
|
||||
|
||||
|
@ -41,40 +45,40 @@ class PixelfedDirectoryController extends Controller
|
|||
$res['_ts'] = config_cache('pixelfed.directory.submission-ts');
|
||||
$res['version'] = config_cache('pixelfed.version');
|
||||
|
||||
if(empty($res['summary'])) {
|
||||
if (empty($res['summary'])) {
|
||||
$summary = ConfigCache::whereK('app.short_description')->pluck('v');
|
||||
$res['summary'] = $summary ? $summary[0] : null;
|
||||
}
|
||||
|
||||
if(isset($res['admin'])) {
|
||||
if (isset($res['admin'])) {
|
||||
$res['admin'] = AccountService::get($res['admin'], true);
|
||||
}
|
||||
|
||||
if(isset($res['banner_image']) && !empty($res['banner_image'])) {
|
||||
if (isset($res['banner_image']) && ! empty($res['banner_image'])) {
|
||||
$res['banner_image'] = url(Storage::url($res['banner_image']));
|
||||
}
|
||||
|
||||
if(isset($res['favourite_posts'])) {
|
||||
$res['favourite_posts'] = collect($res['favourite_posts'])->map(function($id) {
|
||||
if (isset($res['favourite_posts'])) {
|
||||
$res['favourite_posts'] = collect($res['favourite_posts'])->map(function ($id) {
|
||||
return StatusService::get($id);
|
||||
})
|
||||
->filter(function($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->map(function($post) {
|
||||
return [
|
||||
'avatar' => $post['account']['avatar'],
|
||||
'display_name' => $post['account']['display_name'],
|
||||
'username' => $post['account']['username'],
|
||||
'media' => $post['media_attachments'][0]['url'],
|
||||
'url' => $post['url']
|
||||
];
|
||||
})
|
||||
->values();
|
||||
->filter(function ($post) {
|
||||
return $post && isset($post['account']);
|
||||
})
|
||||
->map(function ($post) {
|
||||
return [
|
||||
'avatar' => $post['account']['avatar'],
|
||||
'display_name' => $post['account']['display_name'],
|
||||
'username' => $post['account']['username'],
|
||||
'media' => $post['media_attachments'][0]['url'],
|
||||
'url' => $post['url'],
|
||||
];
|
||||
})
|
||||
->values();
|
||||
}
|
||||
|
||||
$guidelines = ConfigCache::whereK('app.rules')->first();
|
||||
if($guidelines) {
|
||||
if ($guidelines) {
|
||||
$res['community_guidelines'] = json_decode($guidelines->v, true);
|
||||
}
|
||||
|
||||
|
@ -85,27 +89,27 @@ class PixelfedDirectoryController extends Controller
|
|||
$res['curated_onboarding'] = $curatedOnboarding;
|
||||
|
||||
$oauthEnabled = ConfigCache::whereK('pixelfed.oauth_enabled')->first();
|
||||
if($oauthEnabled) {
|
||||
if ($oauthEnabled) {
|
||||
$keys = file_exists(storage_path('oauth-public.key')) && file_exists(storage_path('oauth-private.key'));
|
||||
$res['oauth_enabled'] = (bool) $oauthEnabled && $keys;
|
||||
}
|
||||
|
||||
$activityPubEnabled = ConfigCache::whereK('federation.activitypub.enabled')->first();
|
||||
if($activityPubEnabled) {
|
||||
if ($activityPubEnabled) {
|
||||
$res['activitypub_enabled'] = (bool) $activityPubEnabled;
|
||||
}
|
||||
|
||||
$res['feature_config'] = [
|
||||
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
|
||||
'image_quality' => config_cache('pixelfed.image_quality'),
|
||||
'optimize_image' => config_cache('pixelfed.optimize_image'),
|
||||
'optimize_image' => (bool) config_cache('pixelfed.optimize_image'),
|
||||
'max_photo_size' => config_cache('pixelfed.max_photo_size'),
|
||||
'max_caption_length' => config_cache('pixelfed.max_caption_length'),
|
||||
'max_altext_length' => config_cache('pixelfed.max_altext_length'),
|
||||
'enforce_account_limit' => config_cache('pixelfed.enforce_account_limit'),
|
||||
'enforce_account_limit' => (bool) config_cache('pixelfed.enforce_account_limit'),
|
||||
'max_account_size' => config_cache('pixelfed.max_account_size'),
|
||||
'max_album_length' => config_cache('pixelfed.max_album_length'),
|
||||
'account_deletion' => config_cache('pixelfed.account_deletion'),
|
||||
'account_deletion' => (bool) config_cache('pixelfed.account_deletion'),
|
||||
];
|
||||
|
||||
$res['is_eligible'] = $this->validVal($res, 'admin') &&
|
||||
|
@ -115,29 +119,36 @@ class PixelfedDirectoryController extends Controller
|
|||
$this->validVal($res, 'privacy_pledge') &&
|
||||
$this->validVal($res, 'location');
|
||||
|
||||
if(config_cache('pixelfed.directory.testimonials')) {
|
||||
if (config_cache('pixelfed.directory.testimonials')) {
|
||||
$res['testimonials'] = collect(json_decode(config_cache('pixelfed.directory.testimonials'), true))
|
||||
->map(function($testimonial) {
|
||||
->map(function ($testimonial) {
|
||||
$profile = AccountService::get($testimonial['profile_id']);
|
||||
|
||||
return [
|
||||
'profile' => [
|
||||
'username' => $profile['username'],
|
||||
'display_name' => $profile['display_name'],
|
||||
'avatar' => $profile['avatar'],
|
||||
'created_at' => $profile['created_at']
|
||||
'created_at' => $profile['created_at'],
|
||||
],
|
||||
'body' => $testimonial['body']
|
||||
'body' => $testimonial['body'],
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
$res['features_enabled'] = [
|
||||
'stories' => (bool) config_cache('instance.stories.enabled')
|
||||
'stories' => (bool) config_cache('instance.stories.enabled'),
|
||||
];
|
||||
|
||||
$statusesCount = Cache::remember('api:nodeinfo:statuses', 21600, function() {
|
||||
return Status::whereLocal(true)->count();
|
||||
});
|
||||
$usersCount = Cache::remember('api:nodeinfo:users', 43200, function() {
|
||||
return User::count();
|
||||
});
|
||||
$res['stats'] = [
|
||||
'user_count' => \App\User::count(),
|
||||
'post_count' => \App\Status::whereNull('uri')->count(),
|
||||
'user_count' => (int) $usersCount,
|
||||
'post_count' => (int) $statusesCount,
|
||||
];
|
||||
|
||||
$res['primary_locale'] = config('app.locale');
|
||||
|
@ -150,19 +161,18 @@ class PixelfedDirectoryController extends Controller
|
|||
|
||||
protected function validVal($res, $val, $count = false, $minLen = false)
|
||||
{
|
||||
if(!isset($res[$val])) {
|
||||
if (! isset($res[$val])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($count) {
|
||||
if ($count) {
|
||||
return count($res[$val]) >= $count;
|
||||
}
|
||||
|
||||
if($minLen) {
|
||||
if ($minLen) {
|
||||
return strlen($res[$val]) >= $minLen;
|
||||
}
|
||||
|
||||
return $res[$val];
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -172,6 +172,8 @@ class ProfileController extends Controller
|
|||
|
||||
$user = $this->getCachedUser($username);
|
||||
|
||||
abort_if(!$user, 404);
|
||||
|
||||
return redirect($user->url());
|
||||
}
|
||||
|
||||
|
@ -371,7 +373,7 @@ class ProfileController extends Controller
|
|||
|
||||
public function stories(Request $request, $username)
|
||||
{
|
||||
abort_if(! config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
$profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
|
||||
$pid = $profile->id;
|
||||
$authed = Auth::user()->profile_id;
|
||||
|
|
|
@ -2,22 +2,20 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Services\Account\RemoteAuthService;
|
||||
use App\Models\RemoteAuth;
|
||||
use App\Profile;
|
||||
use App\Instance;
|
||||
use App\User;
|
||||
use Purify;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use App\Util\Lexer\RestrictedNames;
|
||||
use App\Services\Account\RemoteAuthService;
|
||||
use App\Services\EmailService;
|
||||
use App\Services\MediaStorageService;
|
||||
use App\User;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use App\Util\Lexer\RestrictedNames;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
use InvalidArgumentException;
|
||||
use Purify;
|
||||
|
||||
class RemoteAuthController extends Controller
|
||||
{
|
||||
|
@ -30,9 +28,10 @@ class RemoteAuthController extends Controller
|
|||
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||
config('remote-auth.mastodon.enabled')
|
||||
), 404);
|
||||
if($request->user()) {
|
||||
if ($request->user()) {
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
return view('auth.remote.start');
|
||||
}
|
||||
|
||||
|
@ -51,25 +50,27 @@ class RemoteAuthController extends Controller
|
|||
config('remote-auth.mastodon.enabled')
|
||||
), 404);
|
||||
|
||||
if(config('remote-auth.mastodon.domains.only_custom')) {
|
||||
if (config('remote-auth.mastodon.domains.only_custom')) {
|
||||
$res = config('remote-auth.mastodon.domains.custom');
|
||||
if(!$res || !strlen($res)) {
|
||||
if (! $res || ! strlen($res)) {
|
||||
return [];
|
||||
}
|
||||
$res = explode(',', $res);
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
if( config('remote-auth.mastodon.domains.custom') &&
|
||||
!config('remote-auth.mastodon.domains.only_default') &&
|
||||
if (config('remote-auth.mastodon.domains.custom') &&
|
||||
! config('remote-auth.mastodon.domains.only_default') &&
|
||||
strlen(config('remote-auth.mastodon.domains.custom')) > 3 &&
|
||||
strpos(config('remote-auth.mastodon.domains.custom'), '.') > -1
|
||||
) {
|
||||
$res = config('remote-auth.mastodon.domains.custom');
|
||||
if(!$res || !strlen($res)) {
|
||||
if (! $res || ! strlen($res)) {
|
||||
return [];
|
||||
}
|
||||
$res = explode(',', $res);
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
|
@ -93,57 +94,62 @@ class RemoteAuthController extends Controller
|
|||
|
||||
$domain = $request->input('domain');
|
||||
|
||||
if(str_starts_with(strtolower($domain), 'http')) {
|
||||
if (str_starts_with(strtolower($domain), 'http')) {
|
||||
$res = [
|
||||
'domain' => $domain,
|
||||
'ready' => false,
|
||||
'action' => 'incompatible_domain'
|
||||
'action' => 'incompatible_domain',
|
||||
];
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
$validateInstance = Helpers::validateUrl('https://' . $domain . '/?block-check=' . time());
|
||||
$validateInstance = Helpers::validateUrl('https://'.$domain.'/?block-check='.time());
|
||||
|
||||
if(!$validateInstance) {
|
||||
$res = [
|
||||
if (! $validateInstance) {
|
||||
$res = [
|
||||
'domain' => $domain,
|
||||
'ready' => false,
|
||||
'action' => 'blocked_domain'
|
||||
'action' => 'blocked_domain',
|
||||
];
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
$compatible = RemoteAuthService::isDomainCompatible($domain);
|
||||
|
||||
if(!$compatible) {
|
||||
if (! $compatible) {
|
||||
$res = [
|
||||
'domain' => $domain,
|
||||
'ready' => false,
|
||||
'action' => 'incompatible_domain'
|
||||
'action' => 'incompatible_domain',
|
||||
];
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
|
||||
if(config('remote-auth.mastodon.domains.only_default')) {
|
||||
if (config('remote-auth.mastodon.domains.only_default')) {
|
||||
$defaultDomains = explode(',', config('remote-auth.mastodon.domains.default'));
|
||||
if(!in_array($domain, $defaultDomains)) {
|
||||
if (! in_array($domain, $defaultDomains)) {
|
||||
$res = [
|
||||
'domain' => $domain,
|
||||
'ready' => false,
|
||||
'action' => 'incompatible_domain'
|
||||
'action' => 'incompatible_domain',
|
||||
];
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
}
|
||||
|
||||
if(config('remote-auth.mastodon.domains.only_custom') && config('remote-auth.mastodon.domains.custom')) {
|
||||
if (config('remote-auth.mastodon.domains.only_custom') && config('remote-auth.mastodon.domains.custom')) {
|
||||
$customDomains = explode(',', config('remote-auth.mastodon.domains.custom'));
|
||||
if(!in_array($domain, $customDomains)) {
|
||||
if (! in_array($domain, $customDomains)) {
|
||||
$res = [
|
||||
'domain' => $domain,
|
||||
'ready' => false,
|
||||
'action' => 'incompatible_domain'
|
||||
'action' => 'incompatible_domain',
|
||||
];
|
||||
|
||||
return response()->json($res);
|
||||
}
|
||||
}
|
||||
|
@ -163,13 +169,13 @@ class RemoteAuthController extends Controller
|
|||
'state' => $state,
|
||||
]);
|
||||
|
||||
$request->session()->put('oauth_redirect_to', 'https://' . $domain . '/oauth/authorize?' . $query);
|
||||
$request->session()->put('oauth_redirect_to', 'https://'.$domain.'/oauth/authorize?'.$query);
|
||||
|
||||
$dsh = Str::random(17);
|
||||
$res = [
|
||||
'domain' => $domain,
|
||||
'ready' => true,
|
||||
'dsh' => $dsh
|
||||
'dsh' => $dsh,
|
||||
];
|
||||
|
||||
return response()->json($res);
|
||||
|
@ -185,7 +191,7 @@ class RemoteAuthController extends Controller
|
|||
config('remote-auth.mastodon.enabled')
|
||||
), 404);
|
||||
|
||||
if(!$request->filled('d') || !$request->filled('dsh') || !$request->session()->exists('oauth_redirect_to')) {
|
||||
if (! $request->filled('d') || ! $request->filled('dsh') || ! $request->session()->exists('oauth_redirect_to')) {
|
||||
return redirect('/login');
|
||||
}
|
||||
|
||||
|
@ -204,7 +210,7 @@ class RemoteAuthController extends Controller
|
|||
|
||||
$domain = $request->session()->get('oauth_domain');
|
||||
|
||||
if($request->filled('code')) {
|
||||
if ($request->filled('code')) {
|
||||
$code = $request->input('code');
|
||||
$state = $request->session()->pull('state');
|
||||
|
||||
|
@ -216,12 +222,14 @@ class RemoteAuthController extends Controller
|
|||
|
||||
$res = RemoteAuthService::getToken($domain, $code);
|
||||
|
||||
if(!$res || !isset($res['access_token'])) {
|
||||
if (! $res || ! isset($res['access_token'])) {
|
||||
$request->session()->regenerate();
|
||||
|
||||
return redirect('/login');
|
||||
}
|
||||
|
||||
$request->session()->put('oauth_remote_session_token', $res['access_token']);
|
||||
|
||||
return redirect('/auth/mastodon/getting-started');
|
||||
}
|
||||
|
||||
|
@ -237,9 +245,10 @@ class RemoteAuthController extends Controller
|
|||
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||
config('remote-auth.mastodon.enabled')
|
||||
), 404);
|
||||
if($request->user()) {
|
||||
if ($request->user()) {
|
||||
return redirect('/');
|
||||
}
|
||||
|
||||
return view('auth.remote.onboarding');
|
||||
}
|
||||
|
||||
|
@ -261,36 +270,36 @@ class RemoteAuthController extends Controller
|
|||
|
||||
$res = RemoteAuthService::getVerifyCredentials($domain, $token);
|
||||
|
||||
abort_if(!$res || !isset($res['acct']), 403, 'Invalid credentials');
|
||||
abort_if(! $res || ! isset($res['acct']), 403, 'Invalid credentials');
|
||||
|
||||
$webfinger = strtolower('@' . $res['acct'] . '@' . $domain);
|
||||
$webfinger = strtolower('@'.$res['acct'].'@'.$domain);
|
||||
$request->session()->put('oauth_masto_webfinger', $webfinger);
|
||||
|
||||
if(config('remote-auth.mastodon.max_uses.enabled')) {
|
||||
if (config('remote-auth.mastodon.max_uses.enabled')) {
|
||||
$limit = config('remote-auth.mastodon.max_uses.limit');
|
||||
$uses = RemoteAuthService::lookupWebfingerUses($webfinger);
|
||||
if($uses >= $limit) {
|
||||
if ($uses >= $limit) {
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'msg' => 'Success!',
|
||||
'action' => 'max_uses_reached'
|
||||
'action' => 'max_uses_reached',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$exists = RemoteAuth::whereDomain($domain)->where('webfinger', $webfinger)->whereNotNull('user_id')->first();
|
||||
if($exists && $exists->user_id) {
|
||||
if ($exists && $exists->user_id) {
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'msg' => 'Success!',
|
||||
'action' => 'redirect_existing_user'
|
||||
'action' => 'redirect_existing_user',
|
||||
]);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'msg' => 'Success!',
|
||||
'action' => 'onboard'
|
||||
'action' => 'onboard',
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -311,7 +320,7 @@ class RemoteAuthController extends Controller
|
|||
$token = $request->session()->get('oauth_remote_session_token');
|
||||
|
||||
$res = RemoteAuthService::getVerifyCredentials($domain, $token);
|
||||
$res['_webfinger'] = strtolower('@' . $res['acct'] . '@' . $domain);
|
||||
$res['_webfinger'] = strtolower('@'.$res['acct'].'@'.$domain);
|
||||
$res['_domain'] = strtolower($domain);
|
||||
$request->session()->put('oauth_remasto_id', $res['id']);
|
||||
|
||||
|
@ -324,7 +333,7 @@ class RemoteAuthController extends Controller
|
|||
'bearer_token' => $token,
|
||||
'verify_credentials' => $res,
|
||||
'last_verify_credentials_at' => now(),
|
||||
'last_successful_login_at' => now()
|
||||
'last_successful_login_at' => now(),
|
||||
]);
|
||||
|
||||
$request->session()->put('oauth_masto_raid', $ra->id);
|
||||
|
@ -355,24 +364,24 @@ class RemoteAuthController extends Controller
|
|||
$underscore = substr_count($value, '_');
|
||||
$period = substr_count($value, '.');
|
||||
|
||||
if(ends_with($value, ['.php', '.js', '.css'])) {
|
||||
if (ends_with($value, ['.php', '.js', '.css'])) {
|
||||
return $fail('Username is invalid.');
|
||||
}
|
||||
|
||||
if(($dash + $underscore + $period) > 1) {
|
||||
if (($dash + $underscore + $period) > 1) {
|
||||
return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
|
||||
}
|
||||
|
||||
if (!ctype_alnum($value[0])) {
|
||||
if (! ctype_alnum($value[0])) {
|
||||
return $fail('Username is invalid. Must start with a letter or number.');
|
||||
}
|
||||
|
||||
if (!ctype_alnum($value[strlen($value) - 1])) {
|
||||
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)) {
|
||||
if (! ctype_alnum($val)) {
|
||||
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
|
||||
}
|
||||
|
||||
|
@ -380,8 +389,8 @@ class RemoteAuthController extends Controller
|
|||
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
|
||||
return $fail('Username cannot be used.');
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
]);
|
||||
$username = strtolower($request->input('username'));
|
||||
|
||||
|
@ -390,7 +399,7 @@ class RemoteAuthController extends Controller
|
|||
return response()->json([
|
||||
'code' => 200,
|
||||
'username' => $username,
|
||||
'exists' => $exists
|
||||
'exists' => $exists,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -411,7 +420,7 @@ class RemoteAuthController extends Controller
|
|||
'email' => [
|
||||
'required',
|
||||
'email:strict,filter_unicode,dns,spoof',
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
||||
$email = $request->input('email');
|
||||
|
@ -422,7 +431,7 @@ class RemoteAuthController extends Controller
|
|||
'code' => 200,
|
||||
'email' => $email,
|
||||
'exists' => $exists,
|
||||
'banned' => $banned
|
||||
'banned' => $banned,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -445,18 +454,18 @@ class RemoteAuthController extends Controller
|
|||
|
||||
$res = RemoteAuthService::getFollowing($domain, $token, $id);
|
||||
|
||||
if(!$res) {
|
||||
if (! $res) {
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'following' => []
|
||||
'following' => [],
|
||||
]);
|
||||
}
|
||||
|
||||
$res = collect($res)->filter(fn($acct) => Helpers::validateUrl($acct['url']))->values()->toArray();
|
||||
$res = collect($res)->filter(fn ($acct) => Helpers::validateUrl($acct['url']))->values()->toArray();
|
||||
|
||||
return response()->json([
|
||||
'code' => 200,
|
||||
'following' => $res
|
||||
'following' => $res,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -487,24 +496,24 @@ class RemoteAuthController extends Controller
|
|||
$underscore = substr_count($value, '_');
|
||||
$period = substr_count($value, '.');
|
||||
|
||||
if(ends_with($value, ['.php', '.js', '.css'])) {
|
||||
if (ends_with($value, ['.php', '.js', '.css'])) {
|
||||
return $fail('Username is invalid.');
|
||||
}
|
||||
|
||||
if(($dash + $underscore + $period) > 1) {
|
||||
if (($dash + $underscore + $period) > 1) {
|
||||
return $fail('Username is invalid. Can only contain one dash (-), period (.) or underscore (_).');
|
||||
}
|
||||
|
||||
if (!ctype_alnum($value[0])) {
|
||||
if (! ctype_alnum($value[0])) {
|
||||
return $fail('Username is invalid. Must start with a letter or number.');
|
||||
}
|
||||
|
||||
if (!ctype_alnum($value[strlen($value) - 1])) {
|
||||
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)) {
|
||||
if (! ctype_alnum($val)) {
|
||||
return $fail('Username is invalid. Username must be alpha-numeric and may contain dashes (-), periods (.) and underscores (_).');
|
||||
}
|
||||
|
||||
|
@ -512,10 +521,10 @@ class RemoteAuthController extends Controller
|
|||
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
|
||||
return $fail('Username cannot be used.');
|
||||
}
|
||||
}
|
||||
},
|
||||
],
|
||||
'password' => 'required|string|min:8|confirmed',
|
||||
'name' => 'nullable|max:30'
|
||||
'name' => 'nullable|max:30',
|
||||
]);
|
||||
|
||||
$email = $request->input('email');
|
||||
|
@ -527,7 +536,7 @@ class RemoteAuthController extends Controller
|
|||
'name' => $name,
|
||||
'username' => $username,
|
||||
'password' => $password,
|
||||
'email' => $email
|
||||
'email' => $email,
|
||||
]);
|
||||
|
||||
$raid = $request->session()->pull('oauth_masto_raid');
|
||||
|
@ -541,7 +550,7 @@ class RemoteAuthController extends Controller
|
|||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Success',
|
||||
'token' => $token
|
||||
'token' => $token,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -585,7 +594,7 @@ class RemoteAuthController extends Controller
|
|||
abort_unless($request->session()->exists('oauth_remasto_id'), 403);
|
||||
|
||||
$this->validate($request, [
|
||||
'account' => 'required|url'
|
||||
'account' => 'required|url',
|
||||
]);
|
||||
|
||||
$account = $request->input('account');
|
||||
|
@ -594,10 +603,10 @@ class RemoteAuthController extends Controller
|
|||
$host = strtolower(config('pixelfed.domain.app'));
|
||||
$domain = strtolower(parse_url($account, PHP_URL_HOST));
|
||||
|
||||
if($domain == $host) {
|
||||
if ($domain == $host) {
|
||||
$username = Str::of($account)->explode('/')->last();
|
||||
$user = User::where('username', $username)->first();
|
||||
if($user) {
|
||||
if ($user) {
|
||||
return ['id' => (string) $user->profile_id];
|
||||
} else {
|
||||
return [];
|
||||
|
@ -605,7 +614,7 @@ class RemoteAuthController extends Controller
|
|||
} else {
|
||||
try {
|
||||
$profile = Helpers::profileFetch($account);
|
||||
if($profile) {
|
||||
if ($profile) {
|
||||
return ['id' => (string) $profile->id];
|
||||
} else {
|
||||
return [];
|
||||
|
@ -635,13 +644,13 @@ class RemoteAuthController extends Controller
|
|||
$user = $request->user();
|
||||
$profile = $user->profile;
|
||||
|
||||
abort_if(!$profile->avatar, 404, 'Missing avatar');
|
||||
abort_if(! $profile->avatar, 404, 'Missing avatar');
|
||||
|
||||
$avatar = $profile->avatar;
|
||||
$avatar->remote_url = $request->input('avatar_url');
|
||||
$avatar->save();
|
||||
|
||||
MediaStorageService::avatar($avatar, config_cache('pixelfed.cloud_storage') == false);
|
||||
MediaStorageService::avatar($avatar, (bool) config_cache('pixelfed.cloud_storage') == false);
|
||||
|
||||
return [200];
|
||||
}
|
||||
|
@ -657,7 +666,7 @@ class RemoteAuthController extends Controller
|
|||
), 404);
|
||||
abort_unless($request->user(), 404);
|
||||
|
||||
$currentWebfinger = '@' . $request->user()->username . '@' . config('pixelfed.domain.app');
|
||||
$currentWebfinger = '@'.$request->user()->username.'@'.config('pixelfed.domain.app');
|
||||
$ra = RemoteAuth::where('user_id', $request->user()->id)->firstOrFail();
|
||||
RemoteAuthService::submitToBeagle(
|
||||
$ra->webfinger,
|
||||
|
@ -691,19 +700,20 @@ class RemoteAuthController extends Controller
|
|||
$user = User::findOrFail($ra->user_id);
|
||||
abort_if($user->is_admin || $user->status != null, 422, 'Invalid auth action');
|
||||
Auth::loginUsingId($ra->user_id);
|
||||
|
||||
return [200];
|
||||
}
|
||||
|
||||
protected function createUser($data)
|
||||
{
|
||||
event(new Registered($user = User::create([
|
||||
'name' => Purify::clean($data['name']),
|
||||
'name' => Purify::clean($data['name']),
|
||||
'username' => $data['username'],
|
||||
'email' => $data['email'],
|
||||
'email' => $data['email'],
|
||||
'password' => Hash::make($data['password']),
|
||||
'email_verified_at' => config('remote-auth.mastodon.contraints.skip_email_verification') ? now() : null,
|
||||
'app_register_ip' => request()->ip(),
|
||||
'register_source' => 'mastodon'
|
||||
'register_source' => 'mastodon',
|
||||
])));
|
||||
|
||||
$this->guarder()->login($user);
|
||||
|
|
|
@ -2,368 +2,367 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Auth;
|
||||
use App\Hashtag;
|
||||
use App\Place;
|
||||
use App\Profile;
|
||||
use App\Services\WebfingerService;
|
||||
use App\Status;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use Auth;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Transformer\Api\{
|
||||
AccountTransformer,
|
||||
HashtagTransformer,
|
||||
StatusTransformer,
|
||||
};
|
||||
use App\Services\WebfingerService;
|
||||
|
||||
class SearchController extends Controller
|
||||
{
|
||||
public $tokens = [];
|
||||
public $term = '';
|
||||
public $hash = '';
|
||||
public $cacheKey = 'api:search:tag:';
|
||||
public $tokens = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
public $term = '';
|
||||
|
||||
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'
|
||||
]);
|
||||
public $hash = '';
|
||||
|
||||
$scope = $request->input('scope') ?? 'all';
|
||||
$this->term = e(urldecode($request->input('q')));
|
||||
$this->hash = hash('sha256', $this->term);
|
||||
public $cacheKey = 'api:search:tag:';
|
||||
|
||||
switch ($scope) {
|
||||
case 'all':
|
||||
$this->getHashtags();
|
||||
$this->getPosts();
|
||||
$this->getProfiles();
|
||||
// $this->getPlaces();
|
||||
break;
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware('auth');
|
||||
}
|
||||
|
||||
case 'hashtag':
|
||||
$this->getHashtags();
|
||||
break;
|
||||
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',
|
||||
]);
|
||||
|
||||
case 'profile':
|
||||
$this->getProfiles();
|
||||
break;
|
||||
$scope = $request->input('scope') ?? 'all';
|
||||
$this->term = e(urldecode($request->input('q')));
|
||||
$this->hash = hash('sha256', $this->term);
|
||||
|
||||
case 'webfinger':
|
||||
$this->webfingerSearch();
|
||||
break;
|
||||
switch ($scope) {
|
||||
case 'all':
|
||||
$this->getHashtags();
|
||||
$this->getPosts();
|
||||
$this->getProfiles();
|
||||
// $this->getPlaces();
|
||||
break;
|
||||
|
||||
case 'remote':
|
||||
$this->remoteLookupSearch();
|
||||
break;
|
||||
case 'hashtag':
|
||||
$this->getHashtags();
|
||||
break;
|
||||
|
||||
case 'place':
|
||||
$this->getPlaces();
|
||||
break;
|
||||
case 'profile':
|
||||
$this->getProfiles();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case 'webfinger':
|
||||
$this->webfingerSearch();
|
||||
break;
|
||||
|
||||
return response()->json($this->tokens, 200, [], JSON_PRETTY_PRINT);
|
||||
}
|
||||
case 'remote':
|
||||
$this->remoteLookupSearch();
|
||||
break;
|
||||
|
||||
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} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
||||
'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();
|
||||
case 'place':
|
||||
$this->getPlaces();
|
||||
break;
|
||||
|
||||
if($posts->count() > 0) {
|
||||
$posts = $posts->map(function($item, $key) {
|
||||
return [
|
||||
'count' => 0,
|
||||
'url' => $item->url(),
|
||||
'type' => 'status',
|
||||
'value' => "by {$item->profile->username} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
||||
'tokens' => [$item->caption],
|
||||
'name' => $item->caption,
|
||||
'thumb' => $item->thumb(),
|
||||
'filter' => $item->firstMedia()->filter_class
|
||||
];
|
||||
});
|
||||
$this->tokens['posts'] = $posts;
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
return response()->json($this->tokens, 200, [], JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
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 getPosts()
|
||||
{
|
||||
$tag = $this->term;
|
||||
$hash = hash('sha256', $tag);
|
||||
if (Helpers::validateUrl($tag) != false &&
|
||||
Helpers::validateLocalUrl($tag) != true &&
|
||||
(bool) config_cache('federation.activitypub.enabled') == true &&
|
||||
config('federation.activitypub.remoteFollow') == true
|
||||
) {
|
||||
$remote = Helpers::fetchFromUrl($tag);
|
||||
if (isset($remote['type']) &&
|
||||
in_array($remote['type'], ['Note', 'Question'])
|
||||
) {
|
||||
$item = Helpers::statusFetch($tag);
|
||||
$this->tokens['posts'] = [[
|
||||
'count' => 0,
|
||||
'url' => $item->url(),
|
||||
'type' => 'status',
|
||||
'value' => "by {$item->profile->username} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
||||
'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();
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
if ($posts->count() > 0) {
|
||||
$posts = $posts->map(function ($item, $key) {
|
||||
return [
|
||||
'count' => 0,
|
||||
'url' => $item->url(),
|
||||
'type' => 'status',
|
||||
'value' => "by {$item->profile->username} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
||||
'tokens' => [$item->caption],
|
||||
'name' => $item->caption,
|
||||
'thumb' => $item->thumb(),
|
||||
'filter' => $item->firstMedia()->filter_class,
|
||||
];
|
||||
});
|
||||
$this->tokens['posts'] = $posts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
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,
|
||||
];
|
||||
});
|
||||
|
||||
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()
|
||||
]
|
||||
];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return $tags;
|
||||
}
|
||||
});
|
||||
$this->tokens['hashtags'] = $tokens;
|
||||
}
|
||||
|
||||
public function results(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'q' => 'required|string|min:1',
|
||||
]);
|
||||
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(),
|
||||
];
|
||||
}
|
||||
|
||||
return view('search.results');
|
||||
}
|
||||
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 &&
|
||||
(bool) 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(),
|
||||
],
|
||||
]];
|
||||
|
||||
protected function webfingerSearch()
|
||||
{
|
||||
$wfs = WebfingerService::lookup($this->term);
|
||||
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(empty($wfs)) {
|
||||
return;
|
||||
}
|
||||
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(),
|
||||
],
|
||||
];
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
public function results(Request $request)
|
||||
{
|
||||
$this->validate($request, [
|
||||
'q' => 'required|string|min:1',
|
||||
]);
|
||||
|
||||
protected function remotePostLookup()
|
||||
{
|
||||
$tag = $this->term;
|
||||
$hash = hash('sha256', $tag);
|
||||
$local = Helpers::validateLocalUrl($tag);
|
||||
$valid = Helpers::validateUrl($tag);
|
||||
return view('search.results');
|
||||
}
|
||||
|
||||
if($valid == false || $local == true) {
|
||||
return;
|
||||
}
|
||||
protected function webfingerSearch()
|
||||
{
|
||||
$wfs = WebfingerService::lookup($this->term);
|
||||
|
||||
if(Status::whereUri($tag)->whereLocal(false)->exists()) {
|
||||
$item = Status::whereUri($tag)->first();
|
||||
$media = $item->firstMedia();
|
||||
$url = null;
|
||||
if($media) {
|
||||
$url = $media->remote_url;
|
||||
}
|
||||
$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' => $url,
|
||||
'timestamp' => $item->created_at->diffForHumans()
|
||||
]];
|
||||
}
|
||||
if (empty($wfs)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$remote = Helpers::fetchFromUrl($tag);
|
||||
$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'],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if(isset($remote['type']) && $remote['type'] == 'Note') {
|
||||
$item = Helpers::statusFetch($tag);
|
||||
$media = $item->firstMedia();
|
||||
$url = null;
|
||||
if($media) {
|
||||
$url = $media->remote_url;
|
||||
}
|
||||
$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' => $url,
|
||||
'timestamp' => $item->created_at->diffForHumans()
|
||||
]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function remoteLookupSearch()
|
||||
{
|
||||
if(!Helpers::validateUrl($this->term)) {
|
||||
return;
|
||||
}
|
||||
$this->getProfiles();
|
||||
$this->remotePostLookup();
|
||||
}
|
||||
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();
|
||||
$media = $item->firstMedia();
|
||||
$url = null;
|
||||
if ($media) {
|
||||
$url = $media->remote_url;
|
||||
}
|
||||
$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' => $url,
|
||||
'timestamp' => $item->created_at->diffForHumans(),
|
||||
]];
|
||||
}
|
||||
|
||||
$remote = Helpers::fetchFromUrl($tag);
|
||||
|
||||
if (isset($remote['type']) && $remote['type'] == 'Note') {
|
||||
$item = Helpers::statusFetch($tag);
|
||||
$media = $item->firstMedia();
|
||||
$url = null;
|
||||
if ($media) {
|
||||
$url = $media->remote_url;
|
||||
}
|
||||
$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' => $url,
|
||||
'timestamp' => $item->created_at->diffForHumans(),
|
||||
]];
|
||||
}
|
||||
}
|
||||
|
||||
protected function remoteLookupSearch()
|
||||
{
|
||||
if (! Helpers::validateUrl($this->term)) {
|
||||
return;
|
||||
}
|
||||
$this->getProfiles();
|
||||
$this->remotePostLookup();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ class StatusController extends Controller
|
|||
]);
|
||||
}
|
||||
|
||||
if ($request->wantsJson() && config_cache('federation.activitypub.enabled')) {
|
||||
if ($request->wantsJson() && (bool) config_cache('federation.activitypub.enabled')) {
|
||||
return $this->showActivityPub($request, $status);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,54 +2,56 @@
|
|||
|
||||
namespace App\Http\Controllers\Stories;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Models\Conversation;
|
||||
use App\DirectMessage;
|
||||
use App\Notification;
|
||||
use App\Story;
|
||||
use App\Status;
|
||||
use App\StoryView;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Resources\StoryView as StoryViewResource;
|
||||
use App\Jobs\StoryPipeline\StoryDelete;
|
||||
use App\Jobs\StoryPipeline\StoryFanout;
|
||||
use App\Jobs\StoryPipeline\StoryReplyDeliver;
|
||||
use App\Jobs\StoryPipeline\StoryViewDeliver;
|
||||
use App\Models\Conversation;
|
||||
use App\Notification;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\MediaPathService;
|
||||
use App\Services\StoryService;
|
||||
use App\Http\Resources\StoryView as StoryViewResource;
|
||||
use App\Status;
|
||||
use App\Story;
|
||||
use App\StoryView;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class StoryApiV1Controller extends Controller
|
||||
{
|
||||
const RECENT_KEY = 'pf:stories:recent-by-id:';
|
||||
|
||||
const RECENT_TTL = 300;
|
||||
|
||||
public function carousel(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
$pid = $request->user()->profile_id;
|
||||
|
||||
if(config('database.default') == 'pgsql') {
|
||||
$s = Cache::remember(self::RECENT_KEY . $pid, self::RECENT_TTL, function() use($pid) {
|
||||
if (config('database.default') == 'pgsql') {
|
||||
$s = Cache::remember(self::RECENT_KEY.$pid, self::RECENT_TTL, function () use ($pid) {
|
||||
return Story::select('stories.*', 'followers.following_id')
|
||||
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
||||
->where('followers.profile_id', $pid)
|
||||
->where('stories.active', true)
|
||||
->map(function($s) {
|
||||
$r = new \StdClass;
|
||||
->map(function ($s) {
|
||||
$r = new \StdClass;
|
||||
$r->id = $s->id;
|
||||
$r->profile_id = $s->profile_id;
|
||||
$r->type = $s->type;
|
||||
$r->path = $s->path;
|
||||
|
||||
return $r;
|
||||
})
|
||||
->unique('profile_id');
|
||||
});
|
||||
} else {
|
||||
$s = Cache::remember(self::RECENT_KEY . $pid, self::RECENT_TTL, function() use($pid) {
|
||||
$s = Cache::remember(self::RECENT_KEY.$pid, self::RECENT_TTL, function () use ($pid) {
|
||||
return Story::select('stories.*', 'followers.following_id')
|
||||
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
||||
->where('followers.profile_id', $pid)
|
||||
|
@ -59,9 +61,9 @@ class StoryApiV1Controller extends Controller
|
|||
});
|
||||
}
|
||||
|
||||
$nodes = $s->map(function($s) use($pid) {
|
||||
$nodes = $s->map(function ($s) use ($pid) {
|
||||
$profile = AccountService::get($s->profile_id, true);
|
||||
if(!$profile || !isset($profile['id'])) {
|
||||
if (! $profile || ! isset($profile['id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -72,50 +74,51 @@ class StoryApiV1Controller extends Controller
|
|||
'src' => url(Storage::url($s->path)),
|
||||
'duration' => $s->duration ?? 3,
|
||||
'seen' => StoryService::hasSeen($pid, $s->id),
|
||||
'created_at' => $s->created_at->format('c')
|
||||
'created_at' => $s->created_at->format('c'),
|
||||
];
|
||||
})
|
||||
->filter()
|
||||
->groupBy('pid')
|
||||
->map(function($item) use($pid) {
|
||||
$profile = AccountService::get($item[0]['pid'], true);
|
||||
$url = $profile['local'] ? url("/stories/{$profile['username']}") :
|
||||
url("/i/rs/{$profile['id']}");
|
||||
return [
|
||||
'id' => 'pfs:' . $profile['id'],
|
||||
'user' => [
|
||||
'id' => (string) $profile['id'],
|
||||
'username' => $profile['username'],
|
||||
'username_acct' => $profile['acct'],
|
||||
'avatar' => $profile['avatar'],
|
||||
'local' => $profile['local'],
|
||||
'is_author' => $profile['id'] == $pid
|
||||
],
|
||||
'nodes' => $item,
|
||||
'url' => $url,
|
||||
'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])),
|
||||
];
|
||||
})
|
||||
->sortBy('seen')
|
||||
->values();
|
||||
->filter()
|
||||
->groupBy('pid')
|
||||
->map(function ($item) use ($pid) {
|
||||
$profile = AccountService::get($item[0]['pid'], true);
|
||||
$url = $profile['local'] ? url("/stories/{$profile['username']}") :
|
||||
url("/i/rs/{$profile['id']}");
|
||||
|
||||
return [
|
||||
'id' => 'pfs:'.$profile['id'],
|
||||
'user' => [
|
||||
'id' => (string) $profile['id'],
|
||||
'username' => $profile['username'],
|
||||
'username_acct' => $profile['acct'],
|
||||
'avatar' => $profile['avatar'],
|
||||
'local' => $profile['local'],
|
||||
'is_author' => $profile['id'] == $pid,
|
||||
],
|
||||
'nodes' => $item,
|
||||
'url' => $url,
|
||||
'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])),
|
||||
];
|
||||
})
|
||||
->sortBy('seen')
|
||||
->values();
|
||||
|
||||
$res = [
|
||||
'self' => [],
|
||||
'nodes' => $nodes,
|
||||
];
|
||||
|
||||
if(Story::whereProfileId($pid)->whereActive(true)->exists()) {
|
||||
if (Story::whereProfileId($pid)->whereActive(true)->exists()) {
|
||||
$selfStories = Story::whereProfileId($pid)
|
||||
->whereActive(true)
|
||||
->get()
|
||||
->map(function($s) use($pid) {
|
||||
->map(function ($s) {
|
||||
return [
|
||||
'id' => (string) $s->id,
|
||||
'type' => $s->type,
|
||||
'src' => url(Storage::url($s->path)),
|
||||
'duration' => $s->duration,
|
||||
'seen' => true,
|
||||
'created_at' => $s->created_at->format('c')
|
||||
'created_at' => $s->created_at->format('c'),
|
||||
];
|
||||
})
|
||||
->sortBy('id')
|
||||
|
@ -127,38 +130,40 @@ class StoryApiV1Controller extends Controller
|
|||
'username' => $selfProfile['acct'],
|
||||
'avatar' => $selfProfile['avatar'],
|
||||
'local' => $selfProfile['local'],
|
||||
'is_author' => true
|
||||
'is_author' => true,
|
||||
],
|
||||
|
||||
'nodes' => $selfStories,
|
||||
];
|
||||
}
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function selfCarousel(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
$pid = $request->user()->profile_id;
|
||||
|
||||
if(config('database.default') == 'pgsql') {
|
||||
$s = Cache::remember(self::RECENT_KEY . $pid, self::RECENT_TTL, function() use($pid) {
|
||||
if (config('database.default') == 'pgsql') {
|
||||
$s = Cache::remember(self::RECENT_KEY.$pid, self::RECENT_TTL, function () use ($pid) {
|
||||
return Story::select('stories.*', 'followers.following_id')
|
||||
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
||||
->where('followers.profile_id', $pid)
|
||||
->where('stories.active', true)
|
||||
->map(function($s) {
|
||||
$r = new \StdClass;
|
||||
->map(function ($s) {
|
||||
$r = new \StdClass;
|
||||
$r->id = $s->id;
|
||||
$r->profile_id = $s->profile_id;
|
||||
$r->type = $s->type;
|
||||
$r->path = $s->path;
|
||||
|
||||
return $r;
|
||||
})
|
||||
->unique('profile_id');
|
||||
});
|
||||
} else {
|
||||
$s = Cache::remember(self::RECENT_KEY . $pid, self::RECENT_TTL, function() use($pid) {
|
||||
$s = Cache::remember(self::RECENT_KEY.$pid, self::RECENT_TTL, function () use ($pid) {
|
||||
return Story::select('stories.*', 'followers.following_id')
|
||||
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
||||
->where('followers.profile_id', $pid)
|
||||
|
@ -168,9 +173,9 @@ class StoryApiV1Controller extends Controller
|
|||
});
|
||||
}
|
||||
|
||||
$nodes = $s->map(function($s) use($pid) {
|
||||
$nodes = $s->map(function ($s) use ($pid) {
|
||||
$profile = AccountService::get($s->profile_id, true);
|
||||
if(!$profile || !isset($profile['id'])) {
|
||||
if (! $profile || ! isset($profile['id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -181,32 +186,33 @@ class StoryApiV1Controller extends Controller
|
|||
'src' => url(Storage::url($s->path)),
|
||||
'duration' => $s->duration ?? 3,
|
||||
'seen' => StoryService::hasSeen($pid, $s->id),
|
||||
'created_at' => $s->created_at->format('c')
|
||||
'created_at' => $s->created_at->format('c'),
|
||||
];
|
||||
})
|
||||
->filter()
|
||||
->groupBy('pid')
|
||||
->map(function($item) use($pid) {
|
||||
$profile = AccountService::get($item[0]['pid'], true);
|
||||
$url = $profile['local'] ? url("/stories/{$profile['username']}") :
|
||||
url("/i/rs/{$profile['id']}");
|
||||
return [
|
||||
'id' => 'pfs:' . $profile['id'],
|
||||
'user' => [
|
||||
'id' => (string) $profile['id'],
|
||||
'username' => $profile['username'],
|
||||
'username_acct' => $profile['acct'],
|
||||
'avatar' => $profile['avatar'],
|
||||
'local' => $profile['local'],
|
||||
'is_author' => $profile['id'] == $pid
|
||||
],
|
||||
'nodes' => $item,
|
||||
'url' => $url,
|
||||
'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])),
|
||||
];
|
||||
})
|
||||
->sortBy('seen')
|
||||
->values();
|
||||
->filter()
|
||||
->groupBy('pid')
|
||||
->map(function ($item) use ($pid) {
|
||||
$profile = AccountService::get($item[0]['pid'], true);
|
||||
$url = $profile['local'] ? url("/stories/{$profile['username']}") :
|
||||
url("/i/rs/{$profile['id']}");
|
||||
|
||||
return [
|
||||
'id' => 'pfs:'.$profile['id'],
|
||||
'user' => [
|
||||
'id' => (string) $profile['id'],
|
||||
'username' => $profile['username'],
|
||||
'username_acct' => $profile['acct'],
|
||||
'avatar' => $profile['avatar'],
|
||||
'local' => $profile['local'],
|
||||
'is_author' => $profile['id'] == $pid,
|
||||
],
|
||||
'nodes' => $item,
|
||||
'url' => $url,
|
||||
'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])),
|
||||
];
|
||||
})
|
||||
->sortBy('seen')
|
||||
->values();
|
||||
|
||||
$selfProfile = AccountService::get($pid, true);
|
||||
$res = [
|
||||
|
@ -216,7 +222,7 @@ class StoryApiV1Controller extends Controller
|
|||
'username' => $selfProfile['acct'],
|
||||
'avatar' => $selfProfile['avatar'],
|
||||
'local' => $selfProfile['local'],
|
||||
'is_author' => true
|
||||
'is_author' => true,
|
||||
],
|
||||
|
||||
'nodes' => [],
|
||||
|
@ -224,40 +230,41 @@ class StoryApiV1Controller extends Controller
|
|||
'nodes' => $nodes,
|
||||
];
|
||||
|
||||
if(Story::whereProfileId($pid)->whereActive(true)->exists()) {
|
||||
if (Story::whereProfileId($pid)->whereActive(true)->exists()) {
|
||||
$selfStories = Story::whereProfileId($pid)
|
||||
->whereActive(true)
|
||||
->get()
|
||||
->map(function($s) use($pid) {
|
||||
->map(function ($s) {
|
||||
return [
|
||||
'id' => (string) $s->id,
|
||||
'type' => $s->type,
|
||||
'src' => url(Storage::url($s->path)),
|
||||
'duration' => $s->duration,
|
||||
'seen' => true,
|
||||
'created_at' => $s->created_at->format('c')
|
||||
'created_at' => $s->created_at->format('c'),
|
||||
];
|
||||
})
|
||||
->sortBy('id')
|
||||
->values();
|
||||
$res['self']['nodes'] = $selfStories;
|
||||
}
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES);
|
||||
|
||||
return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
}
|
||||
|
||||
public function add(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'file' => function() {
|
||||
'file' => function () {
|
||||
return [
|
||||
'required',
|
||||
'mimetypes:image/jpeg,image/png,video/mp4',
|
||||
'max:' . config_cache('pixelfed.max_photo_size'),
|
||||
'max:'.config_cache('pixelfed.max_photo_size'),
|
||||
];
|
||||
},
|
||||
'duration' => 'sometimes|integer|min:0|max:30'
|
||||
'duration' => 'sometimes|integer|min:0|max:30',
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
@ -267,7 +274,7 @@ class StoryApiV1Controller extends Controller
|
|||
->where('expires_at', '>', now())
|
||||
->count();
|
||||
|
||||
if($count >= Story::MAX_PER_DAY) {
|
||||
if ($count >= Story::MAX_PER_DAY) {
|
||||
abort(418, 'You have reached your limit for new Stories today.');
|
||||
}
|
||||
|
||||
|
@ -277,7 +284,7 @@ class StoryApiV1Controller extends Controller
|
|||
$story = new Story();
|
||||
$story->duration = $request->input('duration', 3);
|
||||
$story->profile_id = $user->profile_id;
|
||||
$story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' :'photo';
|
||||
$story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' : 'photo';
|
||||
$story->mime = $photo->getMimeType();
|
||||
$story->path = $path;
|
||||
$story->local = true;
|
||||
|
@ -290,10 +297,10 @@ class StoryApiV1Controller extends Controller
|
|||
|
||||
$res = [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully added',
|
||||
'msg' => 'Successfully added',
|
||||
'media_id' => (string) $story->id,
|
||||
'media_url' => url(Storage::url($url)) . '?v=' . time(),
|
||||
'media_type' => $story->type
|
||||
'media_url' => url(Storage::url($url)).'?v='.time(),
|
||||
'media_type' => $story->type,
|
||||
];
|
||||
|
||||
return $res;
|
||||
|
@ -301,13 +308,13 @@ class StoryApiV1Controller extends Controller
|
|||
|
||||
public function publish(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'media_id' => 'required',
|
||||
'duration' => 'required|integer|min:0|max:30',
|
||||
'can_reply' => 'required|boolean',
|
||||
'can_react' => 'required|boolean'
|
||||
'can_react' => 'required|boolean',
|
||||
]);
|
||||
|
||||
$id = $request->input('media_id');
|
||||
|
@ -327,13 +334,13 @@ class StoryApiV1Controller extends Controller
|
|||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully published',
|
||||
'msg' => 'Successfully published',
|
||||
];
|
||||
}
|
||||
|
||||
public function delete(Request $request, $id)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
|
@ -346,16 +353,16 @@ class StoryApiV1Controller extends Controller
|
|||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully deleted'
|
||||
'msg' => 'Successfully deleted',
|
||||
];
|
||||
}
|
||||
|
||||
public function viewed(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'id' => 'required|min:1',
|
||||
'id' => 'required|min:1',
|
||||
]);
|
||||
$id = $request->input('id');
|
||||
|
||||
|
@ -367,44 +374,45 @@ class StoryApiV1Controller extends Controller
|
|||
|
||||
$profile = $story->profile;
|
||||
|
||||
if($story->profile_id == $authed->id) {
|
||||
if ($story->profile_id == $authed->id) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$publicOnly = (bool) $profile->followedBy($authed);
|
||||
abort_if(!$publicOnly, 403);
|
||||
abort_if(! $publicOnly, 403);
|
||||
|
||||
$v = StoryView::firstOrCreate([
|
||||
'story_id' => $id,
|
||||
'profile_id' => $authed->id
|
||||
'profile_id' => $authed->id,
|
||||
]);
|
||||
|
||||
if($v->wasRecentlyCreated) {
|
||||
if ($v->wasRecentlyCreated) {
|
||||
Story::findOrFail($story->id)->increment('view_count');
|
||||
|
||||
if($story->local == false) {
|
||||
if ($story->local == false) {
|
||||
StoryViewDeliver::dispatch($story, $authed)->onQueue('story');
|
||||
}
|
||||
}
|
||||
|
||||
Cache::forget('stories:recent:by_id:' . $authed->id);
|
||||
Cache::forget('stories:recent:by_id:'.$authed->id);
|
||||
StoryService::addSeen($authed->id, $story->id);
|
||||
|
||||
return ['code' => 200];
|
||||
}
|
||||
|
||||
public function comment(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
$this->validate($request, [
|
||||
'sid' => 'required',
|
||||
'caption' => 'required|string'
|
||||
'caption' => 'required|string',
|
||||
]);
|
||||
$pid = $request->user()->profile_id;
|
||||
$text = $request->input('caption');
|
||||
|
||||
$story = Story::findOrFail($request->input('sid'));
|
||||
|
||||
abort_if(!$story->can_reply, 422);
|
||||
abort_if(! $story->can_reply, 422);
|
||||
|
||||
$status = new Status;
|
||||
$status->type = 'story:reply';
|
||||
|
@ -415,7 +423,7 @@ class StoryApiV1Controller extends Controller
|
|||
$status->visibility = 'direct';
|
||||
$status->in_reply_to_profile_id = $story->profile_id;
|
||||
$status->entities = json_encode([
|
||||
'story_id' => $story->id
|
||||
'story_id' => $story->id,
|
||||
]);
|
||||
$status->save();
|
||||
|
||||
|
@ -429,24 +437,24 @@ class StoryApiV1Controller extends Controller
|
|||
'story_actor_username' => $request->user()->username,
|
||||
'story_id' => $story->id,
|
||||
'story_media_url' => url(Storage::url($story->path)),
|
||||
'caption' => $text
|
||||
'caption' => $text,
|
||||
]);
|
||||
$dm->save();
|
||||
|
||||
Conversation::updateOrInsert(
|
||||
[
|
||||
'to_id' => $story->profile_id,
|
||||
'from_id' => $pid
|
||||
'from_id' => $pid,
|
||||
],
|
||||
[
|
||||
'type' => 'story:comment',
|
||||
'status_id' => $status->id,
|
||||
'dm_id' => $dm->id,
|
||||
'is_hidden' => false
|
||||
'is_hidden' => false,
|
||||
]
|
||||
);
|
||||
|
||||
if($story->local) {
|
||||
if ($story->local) {
|
||||
$n = new Notification;
|
||||
$n->profile_id = $dm->to_id;
|
||||
$n->actor_id = $dm->from_id;
|
||||
|
@ -460,33 +468,35 @@ class StoryApiV1Controller extends Controller
|
|||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Sent!'
|
||||
'msg' => 'Sent!',
|
||||
];
|
||||
}
|
||||
|
||||
protected function storeMedia($photo, $user)
|
||||
{
|
||||
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
||||
if(in_array($photo->getMimeType(), [
|
||||
if (in_array($photo->getMimeType(), [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'video/mp4'
|
||||
'video/mp4',
|
||||
]) == false) {
|
||||
abort(400, 'Invalid media type');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$storagePath = MediaPathService::story($user->profile);
|
||||
$path = $photo->storePubliclyAs($storagePath, Str::random(random_int(2, 12)) . '_' . Str::random(random_int(32, 35)) . '_' . Str::random(random_int(1, 14)) . '.' . $photo->extension());
|
||||
$path = $photo->storePubliclyAs($storagePath, Str::random(random_int(2, 12)).'_'.Str::random(random_int(32, 35)).'_'.Str::random(random_int(1, 14)).'.'.$photo->extension());
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function viewers(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'sid' => 'required|string|min:1|max:50'
|
||||
'sid' => 'required|string|min:1|max:50',
|
||||
]);
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
|
|
|
@ -2,59 +2,52 @@
|
|||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Media;
|
||||
use App\Profile;
|
||||
use App\Report;
|
||||
use App\DirectMessage;
|
||||
use App\Notification;
|
||||
use App\Status;
|
||||
use App\Story;
|
||||
use App\StoryView;
|
||||
use App\Models\Poll;
|
||||
use App\Models\PollVote;
|
||||
use App\Services\ProfileService;
|
||||
use App\Services\StoryService;
|
||||
use Cache, Storage;
|
||||
use Image as Intervention;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\MediaPathService;
|
||||
use FFMpeg;
|
||||
use FFMpeg\Coordinate\Dimension;
|
||||
use FFMpeg\Format\Video\X264;
|
||||
use App\Jobs\StoryPipeline\StoryDelete;
|
||||
use App\Jobs\StoryPipeline\StoryFanout;
|
||||
use App\Jobs\StoryPipeline\StoryReactionDeliver;
|
||||
use App\Jobs\StoryPipeline\StoryReplyDeliver;
|
||||
use App\Jobs\StoryPipeline\StoryFanout;
|
||||
use App\Jobs\StoryPipeline\StoryDelete;
|
||||
use ImageOptimizer;
|
||||
use App\Models\Conversation;
|
||||
use App\Models\Poll;
|
||||
use App\Models\PollVote;
|
||||
use App\Notification;
|
||||
use App\Report;
|
||||
use App\Services\FollowerService;
|
||||
use App\Services\MediaPathService;
|
||||
use App\Services\StoryService;
|
||||
use App\Services\UserRoleService;
|
||||
use App\Status;
|
||||
use App\Story;
|
||||
use FFMpeg;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Str;
|
||||
use Image as Intervention;
|
||||
use Storage;
|
||||
|
||||
class StoryComposeController extends Controller
|
||||
{
|
||||
public function apiV1Add(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'file' => function() {
|
||||
'file' => function () {
|
||||
return [
|
||||
'required',
|
||||
'mimetypes:image/jpeg,image/png,video/mp4',
|
||||
'max:' . config_cache('pixelfed.max_photo_size'),
|
||||
'max:'.config_cache('pixelfed.max_photo_size'),
|
||||
];
|
||||
},
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
abort_if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
$count = Story::whereProfileId($user->profile_id)
|
||||
->whereActive(true)
|
||||
->where('expires_at', '>', now())
|
||||
->count();
|
||||
|
||||
if($count >= Story::MAX_PER_DAY) {
|
||||
if ($count >= Story::MAX_PER_DAY) {
|
||||
abort(418, 'You have reached your limit for new Stories today.');
|
||||
}
|
||||
|
||||
|
@ -64,7 +57,7 @@ class StoryComposeController extends Controller
|
|||
$story = new Story();
|
||||
$story->duration = 3;
|
||||
$story->profile_id = $user->profile_id;
|
||||
$story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' :'photo';
|
||||
$story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' : 'photo';
|
||||
$story->mime = $photo->getMimeType();
|
||||
$story->path = $path;
|
||||
$story->local = true;
|
||||
|
@ -77,21 +70,22 @@ class StoryComposeController extends Controller
|
|||
|
||||
$res = [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully added',
|
||||
'msg' => 'Successfully added',
|
||||
'media_id' => (string) $story->id,
|
||||
'media_url' => url(Storage::url($url)) . '?v=' . time(),
|
||||
'media_type' => $story->type
|
||||
'media_url' => url(Storage::url($url)).'?v='.time(),
|
||||
'media_type' => $story->type,
|
||||
];
|
||||
|
||||
if($story->type === 'video') {
|
||||
if ($story->type === 'video') {
|
||||
$video = FFMpeg::open($path);
|
||||
$duration = $video->getDurationInSeconds();
|
||||
$res['media_duration'] = $duration;
|
||||
if($duration > 500) {
|
||||
if ($duration > 500) {
|
||||
Storage::delete($story->path);
|
||||
$story->delete();
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Video duration cannot exceed 60 seconds'
|
||||
'message' => 'Video duration cannot exceed 60 seconds',
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
@ -102,37 +96,39 @@ class StoryComposeController extends Controller
|
|||
protected function storePhoto($photo, $user)
|
||||
{
|
||||
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
||||
if(in_array($photo->getMimeType(), [
|
||||
if (in_array($photo->getMimeType(), [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'video/mp4'
|
||||
'video/mp4',
|
||||
]) == false) {
|
||||
abort(400, 'Invalid media type');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$storagePath = MediaPathService::story($user->profile);
|
||||
$path = $photo->storePubliclyAs($storagePath, Str::random(random_int(2, 12)) . '_' . Str::random(random_int(32, 35)) . '_' . Str::random(random_int(1, 14)) . '.' . $photo->extension());
|
||||
if(in_array($photo->getMimeType(), ['image/jpeg','image/png'])) {
|
||||
$fpath = storage_path('app/' . $path);
|
||||
$path = $photo->storePubliclyAs($storagePath, Str::random(random_int(2, 12)).'_'.Str::random(random_int(32, 35)).'_'.Str::random(random_int(1, 14)).'.'.$photo->extension());
|
||||
if (in_array($photo->getMimeType(), ['image/jpeg', 'image/png'])) {
|
||||
$fpath = storage_path('app/'.$path);
|
||||
$img = Intervention::make($fpath);
|
||||
$img->orientate();
|
||||
$img->save($fpath, config_cache('pixelfed.image_quality'));
|
||||
$img->destroy();
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function cropPhoto(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'media_id' => 'required|integer|min:1',
|
||||
'width' => 'required',
|
||||
'height' => 'required',
|
||||
'x' => 'required',
|
||||
'y' => 'required'
|
||||
'y' => 'required',
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
@ -144,13 +140,13 @@ class StoryComposeController extends Controller
|
|||
|
||||
$story = Story::whereProfileId($user->profile_id)->findOrFail($id);
|
||||
|
||||
$path = storage_path('app/' . $story->path);
|
||||
$path = storage_path('app/'.$story->path);
|
||||
|
||||
if(!is_file($path)) {
|
||||
if (! is_file($path)) {
|
||||
abort(400, 'Invalid or missing media.');
|
||||
}
|
||||
|
||||
if($story->type === 'photo') {
|
||||
if ($story->type === 'photo') {
|
||||
$img = Intervention::make($path);
|
||||
$img->crop($width, $height, $x, $y);
|
||||
$img->resize(1080, 1920, function ($constraint) {
|
||||
|
@ -161,24 +157,24 @@ class StoryComposeController extends Controller
|
|||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully cropped',
|
||||
'msg' => 'Successfully cropped',
|
||||
];
|
||||
}
|
||||
|
||||
public function publishStory(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'media_id' => 'required',
|
||||
'duration' => 'required|integer|min:3|max:120',
|
||||
'can_reply' => 'required|boolean',
|
||||
'can_react' => 'required|boolean'
|
||||
'can_react' => 'required|boolean',
|
||||
]);
|
||||
|
||||
$id = $request->input('media_id');
|
||||
$user = $request->user();
|
||||
abort_if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
$story = Story::whereProfileId($user->profile_id)
|
||||
->findOrFail($id);
|
||||
|
||||
|
@ -194,13 +190,13 @@ class StoryComposeController extends Controller
|
|||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully published',
|
||||
'msg' => 'Successfully published',
|
||||
];
|
||||
}
|
||||
|
||||
public function apiV1Delete(Request $request, $id)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
|
@ -213,40 +209,40 @@ class StoryComposeController extends Controller
|
|||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully deleted'
|
||||
'msg' => 'Successfully deleted',
|
||||
];
|
||||
}
|
||||
|
||||
public function compose(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
$user = $request->user();
|
||||
abort_if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
|
||||
return view('stories.compose');
|
||||
}
|
||||
|
||||
public function createPoll(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!config_cache('instance.polls.enabled'), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
abort_if(! config_cache('instance.polls.enabled'), 404);
|
||||
|
||||
return $request->all();
|
||||
}
|
||||
|
||||
public function publishStoryPoll(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'question' => 'required|string|min:6|max:140',
|
||||
'options' => 'required|array|min:2|max:4',
|
||||
'can_reply' => 'required|boolean',
|
||||
'can_react' => 'required|boolean'
|
||||
'can_react' => 'required|boolean',
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
abort_if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
$pid = $request->user()->profile_id;
|
||||
|
||||
$count = Story::whereProfileId($pid)
|
||||
|
@ -254,7 +250,7 @@ class StoryComposeController extends Controller
|
|||
->where('expires_at', '>', now())
|
||||
->count();
|
||||
|
||||
if($count >= Story::MAX_PER_DAY) {
|
||||
if ($count >= Story::MAX_PER_DAY) {
|
||||
abort(418, 'You have reached your limit for new Stories today.');
|
||||
}
|
||||
|
||||
|
@ -262,7 +258,7 @@ class StoryComposeController extends Controller
|
|||
$story->type = 'poll';
|
||||
$story->story = json_encode([
|
||||
'question' => $request->input('question'),
|
||||
'options' => $request->input('options')
|
||||
'options' => $request->input('options'),
|
||||
]);
|
||||
$story->public = false;
|
||||
$story->local = true;
|
||||
|
@ -278,7 +274,7 @@ class StoryComposeController extends Controller
|
|||
$poll->profile_id = $pid;
|
||||
$poll->poll_options = $request->input('options');
|
||||
$poll->expires_at = $story->expires_at;
|
||||
$poll->cached_tallies = collect($poll->poll_options)->map(function($o) {
|
||||
$poll->cached_tallies = collect($poll->poll_options)->map(function ($o) {
|
||||
return 0;
|
||||
})->toArray();
|
||||
$poll->save();
|
||||
|
@ -290,23 +286,23 @@ class StoryComposeController extends Controller
|
|||
|
||||
return [
|
||||
'code' => 200,
|
||||
'msg' => 'Successfully published',
|
||||
'msg' => 'Successfully published',
|
||||
];
|
||||
}
|
||||
|
||||
public function storyPollVote(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'sid' => 'required',
|
||||
'ci' => 'required|integer|min:0|max:3'
|
||||
'ci' => 'required|integer|min:0|max:3',
|
||||
]);
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
$ci = $request->input('ci');
|
||||
$story = Story::findOrFail($request->input('sid'));
|
||||
abort_if(!FollowerService::follows($pid, $story->profile_id), 403);
|
||||
abort_if(! FollowerService::follows($pid, $story->profile_id), 403);
|
||||
$poll = Poll::whereStoryId($story->id)->firstOrFail();
|
||||
|
||||
$vote = new PollVote;
|
||||
|
@ -318,7 +314,7 @@ class StoryComposeController extends Controller
|
|||
$vote->save();
|
||||
|
||||
$poll->votes_count = $poll->votes_count + 1;
|
||||
$poll->cached_tallies = collect($poll->getTallies())->map(function($tally, $key) use($ci) {
|
||||
$poll->cached_tallies = collect($poll->getTallies())->map(function ($tally, $key) use ($ci) {
|
||||
return $ci == $key ? $tally + 1 : $tally;
|
||||
})->toArray();
|
||||
$poll->save();
|
||||
|
@ -328,15 +324,15 @@ class StoryComposeController extends Controller
|
|||
|
||||
public function storeReport(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'type' => 'required|alpha_dash',
|
||||
'id' => 'required|integer|min:1',
|
||||
'type' => 'required|alpha_dash',
|
||||
'id' => 'required|integer|min:1',
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
abort_if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
|
||||
$pid = $request->user()->profile_id;
|
||||
$sid = $request->input('id');
|
||||
|
@ -353,24 +349,24 @@ class StoryComposeController extends Controller
|
|||
'copyright',
|
||||
'impersonation',
|
||||
'scam',
|
||||
'terrorism'
|
||||
'terrorism',
|
||||
];
|
||||
|
||||
abort_if(!in_array($type, $types), 422, 'Invalid story report type');
|
||||
abort_if(! in_array($type, $types), 422, 'Invalid story report type');
|
||||
|
||||
$story = Story::findOrFail($sid);
|
||||
|
||||
abort_if($story->profile_id == $pid, 422, 'Cannot report your own story');
|
||||
abort_if(!FollowerService::follows($pid, $story->profile_id), 422, 'Cannot report a story from an account you do not follow');
|
||||
abort_if(! FollowerService::follows($pid, $story->profile_id), 422, 'Cannot report a story from an account you do not follow');
|
||||
|
||||
if( Report::whereProfileId($pid)
|
||||
if (Report::whereProfileId($pid)
|
||||
->whereObjectType('App\Story')
|
||||
->whereObjectId($story->id)
|
||||
->exists()
|
||||
) {
|
||||
return response()->json(['error' => [
|
||||
'code' => 409,
|
||||
'message' => 'Cannot report the same story again'
|
||||
'message' => 'Cannot report the same story again',
|
||||
]], 409);
|
||||
}
|
||||
|
||||
|
@ -389,18 +385,18 @@ class StoryComposeController extends Controller
|
|||
|
||||
public function react(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
$this->validate($request, [
|
||||
'sid' => 'required',
|
||||
'reaction' => 'required|string'
|
||||
'reaction' => 'required|string',
|
||||
]);
|
||||
$pid = $request->user()->profile_id;
|
||||
$text = $request->input('reaction');
|
||||
$user = $request->user();
|
||||
abort_if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
$story = Story::findOrFail($request->input('sid'));
|
||||
|
||||
abort_if(!$story->can_react, 422);
|
||||
abort_if(! $story->can_react, 422);
|
||||
abort_if(StoryService::reactCounter($story->id, $pid) >= 5, 422, 'You have already reacted to this story');
|
||||
|
||||
$status = new Status;
|
||||
|
@ -413,7 +409,7 @@ class StoryComposeController extends Controller
|
|||
$status->in_reply_to_profile_id = $story->profile_id;
|
||||
$status->entities = json_encode([
|
||||
'story_id' => $story->id,
|
||||
'reaction' => $text
|
||||
'reaction' => $text,
|
||||
]);
|
||||
$status->save();
|
||||
|
||||
|
@ -427,24 +423,24 @@ class StoryComposeController extends Controller
|
|||
'story_actor_username' => $request->user()->username,
|
||||
'story_id' => $story->id,
|
||||
'story_media_url' => url(Storage::url($story->path)),
|
||||
'reaction' => $text
|
||||
'reaction' => $text,
|
||||
]);
|
||||
$dm->save();
|
||||
|
||||
Conversation::updateOrInsert(
|
||||
[
|
||||
'to_id' => $story->profile_id,
|
||||
'from_id' => $pid
|
||||
'from_id' => $pid,
|
||||
],
|
||||
[
|
||||
'type' => 'story:react',
|
||||
'status_id' => $status->id,
|
||||
'dm_id' => $dm->id,
|
||||
'is_hidden' => false
|
||||
'is_hidden' => false,
|
||||
]
|
||||
);
|
||||
|
||||
if($story->local) {
|
||||
if ($story->local) {
|
||||
// generate notification
|
||||
$n = new Notification;
|
||||
$n->profile_id = $dm->to_id;
|
||||
|
@ -464,18 +460,18 @@ class StoryComposeController extends Controller
|
|||
|
||||
public function comment(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||
$this->validate($request, [
|
||||
'sid' => 'required',
|
||||
'caption' => 'required|string'
|
||||
'caption' => 'required|string',
|
||||
]);
|
||||
$pid = $request->user()->profile_id;
|
||||
$text = $request->input('caption');
|
||||
$user = $request->user();
|
||||
abort_if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
abort_if($user->has_roles && ! UserRoleService::can('can-use-stories', $user->id), 403, 'Invalid permissions for this action');
|
||||
$story = Story::findOrFail($request->input('sid'));
|
||||
|
||||
abort_if(!$story->can_reply, 422);
|
||||
abort_if(! $story->can_reply, 422);
|
||||
|
||||
$status = new Status;
|
||||
$status->type = 'story:reply';
|
||||
|
@ -486,7 +482,7 @@ class StoryComposeController extends Controller
|
|||
$status->visibility = 'direct';
|
||||
$status->in_reply_to_profile_id = $story->profile_id;
|
||||
$status->entities = json_encode([
|
||||
'story_id' => $story->id
|
||||
'story_id' => $story->id,
|
||||
]);
|
||||
$status->save();
|
||||
|
||||
|
@ -500,24 +496,24 @@ class StoryComposeController extends Controller
|
|||
'story_actor_username' => $request->user()->username,
|
||||
'story_id' => $story->id,
|
||||
'story_media_url' => url(Storage::url($story->path)),
|
||||
'caption' => $text
|
||||
'caption' => $text,
|
||||
]);
|
||||
$dm->save();
|
||||
|
||||
Conversation::updateOrInsert(
|
||||
[
|
||||
'to_id' => $story->profile_id,
|
||||
'from_id' => $pid
|
||||
'from_id' => $pid,
|
||||
],
|
||||
[
|
||||
'type' => 'story:comment',
|
||||
'status_id' => $status->id,
|
||||
'dm_id' => $dm->id,
|
||||
'is_hidden' => false
|
||||
'is_hidden' => false,
|
||||
]
|
||||
);
|
||||
|
||||
if($story->local) {
|
||||
if ($story->local) {
|
||||
// generate notification
|
||||
$n = new Notification;
|
||||
$n->profile_id = $dm->to_id;
|
||||
|
|
|
@ -34,7 +34,7 @@ class StoryController extends StoryComposeController
|
|||
{
|
||||
public function recent(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
$user = $request->user();
|
||||
if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id)) {
|
||||
return [];
|
||||
|
@ -117,7 +117,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function profile(Request $request, $id)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$user = $request->user();
|
||||
if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id)) {
|
||||
|
@ -176,7 +176,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function viewed(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'id' => 'required|min:1',
|
||||
|
@ -221,7 +221,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function exists(Request $request, $id)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
$user = $request->user();
|
||||
if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id)) {
|
||||
return response()->json(false);
|
||||
|
@ -233,7 +233,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function iRedirect(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$user = $request->user();
|
||||
abort_if(!$user, 404);
|
||||
|
@ -243,7 +243,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function viewers(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'sid' => 'required|string'
|
||||
|
@ -274,7 +274,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function remoteStory(Request $request, $id)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$profile = Profile::findOrFail($id);
|
||||
if($profile->user_id != null || $profile->domain == null) {
|
||||
|
@ -286,7 +286,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function pollResults(Request $request)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled') || !$request->user(), 404);
|
||||
|
||||
$this->validate($request, [
|
||||
'sid' => 'required|string'
|
||||
|
@ -304,7 +304,7 @@ class StoryController extends StoryComposeController
|
|||
|
||||
public function getActivityObject(Request $request, $username, $id)
|
||||
{
|
||||
abort_if(!config_cache('instance.stories.enabled'), 404);
|
||||
abort_if(!(bool) config_cache('instance.stories.enabled'), 404);
|
||||
|
||||
if(!$request->wantsJson()) {
|
||||
return redirect('/stories/' . $username);
|
||||
|
|
|
@ -34,7 +34,7 @@ class UserEmailForgotController extends Controller
|
|||
'username.exists' => 'This username is no longer active or does not exist!'
|
||||
];
|
||||
|
||||
if(config('captcha.enabled') || config('captcha.active.login') || config('captcha.active.register')) {
|
||||
if((bool) config_cache('captcha.enabled')) {
|
||||
$rules['h-captcha-response'] = 'required|captcha';
|
||||
$messages['h-captcha-response.required'] = 'You need to complete the captcha!';
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
|
||||
namespace App\Http\Requests\Status;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use App\Media;
|
||||
use App\Status;
|
||||
use Closure;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class StoreStatusEditRequest extends FormRequest
|
||||
{
|
||||
|
@ -14,24 +14,25 @@ class StoreStatusEditRequest extends FormRequest
|
|||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
$profile = $this->user()->profile;
|
||||
if($profile->status != null) {
|
||||
return false;
|
||||
}
|
||||
if($profile->unlisted == true && $profile->cw == true) {
|
||||
return false;
|
||||
}
|
||||
$types = [
|
||||
"photo",
|
||||
"photo:album",
|
||||
"photo:video:album",
|
||||
"reply",
|
||||
"text",
|
||||
"video",
|
||||
"video:album"
|
||||
];
|
||||
$scopes = ['public', 'unlisted', 'private'];
|
||||
$status = Status::whereNull('reblog_of_id')->whereIn('type', $types)->whereIn('scope', $scopes)->find($this->route('id'));
|
||||
$profile = $this->user()->profile;
|
||||
if ($profile->status != null) {
|
||||
return false;
|
||||
}
|
||||
if ($profile->unlisted == true && $profile->cw == true) {
|
||||
return false;
|
||||
}
|
||||
$types = [
|
||||
'photo',
|
||||
'photo:album',
|
||||
'photo:video:album',
|
||||
'reply',
|
||||
'text',
|
||||
'video',
|
||||
'video:album',
|
||||
];
|
||||
$scopes = ['public', 'unlisted', 'private'];
|
||||
$status = Status::whereNull('reblog_of_id')->whereIn('type', $types)->whereIn('scope', $scopes)->find($this->route('id'));
|
||||
|
||||
return $status && $this->user()->profile_id === $status->profile_id;
|
||||
}
|
||||
|
||||
|
@ -47,18 +48,18 @@ class StoreStatusEditRequest extends FormRequest
|
|||
'spoiler_text' => 'nullable|string|max:140',
|
||||
'sensitive' => 'sometimes|boolean',
|
||||
'media_ids' => [
|
||||
'nullable',
|
||||
'required_without:status',
|
||||
'array',
|
||||
'max:' . config('pixelfed.max_album_length'),
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
Media::whereProfileId($this->user()->profile_id)
|
||||
->where(function($query) {
|
||||
return $query->whereNull('status_id')
|
||||
->orWhere('status_id', '=', $this->route('id'));
|
||||
})
|
||||
->findOrFail($value);
|
||||
},
|
||||
'nullable',
|
||||
'required_without:status',
|
||||
'array',
|
||||
'max:'.(int) config_cache('pixelfed.max_album_length'),
|
||||
function (string $attribute, mixed $value, Closure $fail) {
|
||||
Media::whereProfileId($this->user()->profile_id)
|
||||
->where(function ($query) {
|
||||
return $query->whereNull('status_id')
|
||||
->orWhere('status_id', '=', $this->route('id'));
|
||||
})
|
||||
->findOrFail($value);
|
||||
},
|
||||
],
|
||||
'location' => 'sometimes|nullable',
|
||||
'location.id' => 'sometimes|integer|min:1|max:128769',
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
namespace App\Jobs\AvatarPipeline;
|
||||
|
||||
use Cache;
|
||||
use App\Avatar;
|
||||
use App\Profile;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
|
@ -17,88 +17,88 @@ use Storage;
|
|||
|
||||
class AvatarOptimize implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $profile;
|
||||
protected $current;
|
||||
protected $profile;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
protected $current;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Profile $profile, $current)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
$this->current = $current;
|
||||
}
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$avatar = $this->profile->avatar;
|
||||
$file = storage_path("app/$avatar->media_path");
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Profile $profile, $current)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
$this->current = $current;
|
||||
}
|
||||
|
||||
try {
|
||||
$img = Intervention::make($file)->orientate();
|
||||
$img->fit(200, 200, function ($constraint) {
|
||||
$constraint->upsize();
|
||||
});
|
||||
$quality = config_cache('pixelfed.image_quality');
|
||||
$img->save($file, $quality);
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$avatar = $this->profile->avatar;
|
||||
$file = storage_path("app/$avatar->media_path");
|
||||
|
||||
$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);
|
||||
try {
|
||||
$img = Intervention::make($file)->orientate();
|
||||
$img->fit(200, 200, function ($constraint) {
|
||||
$constraint->upsize();
|
||||
});
|
||||
$quality = config_cache('pixelfed.image_quality');
|
||||
$img->save($file, $quality);
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage') && config('instance.avatar.local_to_cloud')) {
|
||||
$this->uploadToCloud($avatar);
|
||||
} else {
|
||||
$avatar->cdn_url = null;
|
||||
$avatar->save();
|
||||
}
|
||||
} 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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
if ((bool) config_cache('pixelfed.cloud_storage') && (bool) config_cache('instance.avatar.local_to_cloud')) {
|
||||
$this->uploadToCloud($avatar);
|
||||
} else {
|
||||
$avatar->cdn_url = null;
|
||||
$avatar->save();
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
protected function uploadToCloud($avatar)
|
||||
{
|
||||
$base = 'cache/avatars/' . $avatar->profile_id;
|
||||
$disk = Storage::disk(config('filesystems.cloud'));
|
||||
$disk->deleteDirectory($base);
|
||||
$path = $base . '/' . 'avatar_' . strtolower(Str::random(random_int(3,6))) . $avatar->change_count . '.' . pathinfo($avatar->media_path, PATHINFO_EXTENSION);
|
||||
$url = $disk->put($path, Storage::get($avatar->media_path));
|
||||
$avatar->media_path = $path;
|
||||
$avatar->cdn_url = $disk->url($path);
|
||||
$avatar->save();
|
||||
Storage::delete($avatar->media_path);
|
||||
Cache::forget('avatar:' . $avatar->profile_id);
|
||||
}
|
||||
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 uploadToCloud($avatar)
|
||||
{
|
||||
$base = 'cache/avatars/'.$avatar->profile_id;
|
||||
$disk = Storage::disk(config('filesystems.cloud'));
|
||||
$disk->deleteDirectory($base);
|
||||
$path = $base.'/'.'avatar_'.strtolower(Str::random(random_int(3, 6))).$avatar->change_count.'.'.pathinfo($avatar->media_path, PATHINFO_EXTENSION);
|
||||
$url = $disk->put($path, Storage::get($avatar->media_path));
|
||||
$avatar->media_path = $path;
|
||||
$avatar->cdn_url = $disk->url($path);
|
||||
$avatar->save();
|
||||
Storage::delete($avatar->media_path);
|
||||
Cache::forget('avatar:'.$avatar->profile_id);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,112 +4,107 @@ namespace App\Jobs\AvatarPipeline;
|
|||
|
||||
use App\Avatar;
|
||||
use App\Profile;
|
||||
use App\Services\MediaStorageService;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use Illuminate\Support\Str;
|
||||
use Zttp\Zttp;
|
||||
use App\Http\Controllers\AvatarController;
|
||||
use Storage;
|
||||
use Log;
|
||||
use Illuminate\Http\File;
|
||||
use App\Services\MediaStorageService;
|
||||
use App\Services\ActivityPubFetchService;
|
||||
|
||||
class RemoteAvatarFetch implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $profile;
|
||||
protected $profile;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 1;
|
||||
public $timeout = 300;
|
||||
public $maxExceptions = 1;
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 1;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Profile $profile)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
}
|
||||
public $timeout = 300;
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$profile = $this->profile;
|
||||
public $maxExceptions = 1;
|
||||
|
||||
if(boolval(config_cache('pixelfed.cloud_storage')) == false && boolval(config_cache('federation.avatars.store_local')) == false) {
|
||||
return 1;
|
||||
}
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Profile $profile)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
}
|
||||
|
||||
if($profile->domain == null || $profile->private_key) {
|
||||
return 1;
|
||||
}
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$profile = $this->profile;
|
||||
|
||||
$avatar = Avatar::whereProfileId($profile->id)->first();
|
||||
if ((bool) config_cache('pixelfed.cloud_storage') == false && (bool) config_cache('federation.avatars.store_local') == false) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(!$avatar) {
|
||||
$avatar = new Avatar;
|
||||
$avatar->profile_id = $profile->id;
|
||||
$avatar->save();
|
||||
}
|
||||
if ($profile->domain == null || $profile->private_key) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if($avatar->media_path == null && $avatar->remote_url == null) {
|
||||
$avatar->media_path = 'public/avatars/default.jpg';
|
||||
$avatar->is_remote = true;
|
||||
$avatar->save();
|
||||
}
|
||||
$avatar = Avatar::whereProfileId($profile->id)->first();
|
||||
|
||||
$person = Helpers::fetchFromUrl($profile->remote_url);
|
||||
if (! $avatar) {
|
||||
$avatar = new Avatar;
|
||||
$avatar->profile_id = $profile->id;
|
||||
$avatar->save();
|
||||
}
|
||||
|
||||
if(!$person || !isset($person['@context'])) {
|
||||
return 1;
|
||||
}
|
||||
if ($avatar->media_path == null && $avatar->remote_url == null) {
|
||||
$avatar->media_path = 'public/avatars/default.jpg';
|
||||
$avatar->is_remote = true;
|
||||
$avatar->save();
|
||||
}
|
||||
|
||||
if( !isset($person['icon']) ||
|
||||
!isset($person['icon']['type']) ||
|
||||
!isset($person['icon']['url'])
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
$person = Helpers::fetchFromUrl($profile->remote_url);
|
||||
|
||||
if($person['icon']['type'] !== 'Image') {
|
||||
return 1;
|
||||
}
|
||||
if (! $person || ! isset($person['@context'])) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(!Helpers::validateUrl($person['icon']['url'])) {
|
||||
return 1;
|
||||
}
|
||||
if (! isset($person['icon']) ||
|
||||
! isset($person['icon']['type']) ||
|
||||
! isset($person['icon']['url'])
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$icon = $person['icon'];
|
||||
if ($person['icon']['type'] !== 'Image') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$avatar->remote_url = $icon['url'];
|
||||
$avatar->save();
|
||||
if (! Helpers::validateUrl($person['icon']['url'])) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false, true);
|
||||
$icon = $person['icon'];
|
||||
|
||||
return 1;
|
||||
}
|
||||
$avatar->remote_url = $icon['url'];
|
||||
$avatar->save();
|
||||
|
||||
MediaStorageService::avatar($avatar, (bool) config_cache('pixelfed.cloud_storage') == false, true);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,93 +4,88 @@ namespace App\Jobs\AvatarPipeline;
|
|||
|
||||
use App\Avatar;
|
||||
use App\Profile;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\MediaStorageService;
|
||||
use Cache;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use Illuminate\Support\Str;
|
||||
use Zttp\Zttp;
|
||||
use App\Http\Controllers\AvatarController;
|
||||
use Cache;
|
||||
use Storage;
|
||||
use Log;
|
||||
use Illuminate\Http\File;
|
||||
use App\Services\AccountService;
|
||||
use App\Services\MediaStorageService;
|
||||
use App\Services\ActivityPubFetchService;
|
||||
|
||||
class RemoteAvatarFetchFromUrl implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $profile;
|
||||
protected $url;
|
||||
protected $profile;
|
||||
|
||||
/**
|
||||
* Delete the job if its models no longer exist.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $deleteWhenMissingModels = true;
|
||||
protected $url;
|
||||
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 1;
|
||||
public $timeout = 300;
|
||||
public $maxExceptions = 1;
|
||||
/**
|
||||
* 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, $url)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
$this->url = $url;
|
||||
}
|
||||
/**
|
||||
* The number of times the job may be attempted.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $tries = 1;
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$profile = $this->profile;
|
||||
public $timeout = 300;
|
||||
|
||||
Cache::forget('avatar:' . $profile->id);
|
||||
AccountService::del($profile->id);
|
||||
public $maxExceptions = 1;
|
||||
|
||||
if(boolval(config_cache('pixelfed.cloud_storage')) == false && boolval(config_cache('federation.avatars.store_local')) == false) {
|
||||
return 1;
|
||||
}
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Profile $profile, $url)
|
||||
{
|
||||
$this->profile = $profile;
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
if($profile->domain == null || $profile->private_key) {
|
||||
return 1;
|
||||
}
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$profile = $this->profile;
|
||||
|
||||
$avatar = Avatar::whereProfileId($profile->id)->first();
|
||||
Cache::forget('avatar:'.$profile->id);
|
||||
AccountService::del($profile->id);
|
||||
|
||||
if(!$avatar) {
|
||||
$avatar = new Avatar;
|
||||
$avatar->profile_id = $profile->id;
|
||||
$avatar->is_remote = true;
|
||||
$avatar->remote_url = $this->url;
|
||||
$avatar->save();
|
||||
} else {
|
||||
$avatar->remote_url = $this->url;
|
||||
$avatar->is_remote = true;
|
||||
$avatar->save();
|
||||
}
|
||||
if ((bool) config_cache('pixelfed.cloud_storage') == false && (bool) config_cache('federation.avatars.store_local') == false) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
MediaStorageService::avatar($avatar, boolval(config_cache('pixelfed.cloud_storage')) == false, true);
|
||||
if ($profile->domain == null || $profile->private_key) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
$avatar = Avatar::whereProfileId($profile->id)->first();
|
||||
|
||||
if (! $avatar) {
|
||||
$avatar = new Avatar;
|
||||
$avatar->profile_id = $profile->id;
|
||||
$avatar->is_remote = true;
|
||||
$avatar->remote_url = $this->url;
|
||||
$avatar->save();
|
||||
} else {
|
||||
$avatar->remote_url = $this->url;
|
||||
$avatar->is_remote = true;
|
||||
$avatar->save();
|
||||
}
|
||||
|
||||
MediaStorageService::avatar($avatar, (bool) config_cache('pixelfed.cloud_storage') == false, true);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ class ImageOptimize implements ShouldQueue
|
|||
return;
|
||||
}
|
||||
|
||||
if(config('pixelfed.optimize_image') == false) {
|
||||
if((bool) config_cache('pixelfed.optimize_image') == false) {
|
||||
ImageThumbnail::dispatch($media)->onQueue('mmo');
|
||||
return;
|
||||
} else {
|
||||
|
|
|
@ -51,7 +51,7 @@ class ImageResize implements ShouldQueue
|
|||
return;
|
||||
}
|
||||
|
||||
if(!config('pixelfed.optimize_image')) {
|
||||
if((bool) config_cache('pixelfed.optimize_image') === false) {
|
||||
ImageThumbnail::dispatch($media)->onQueue('mmo');
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ class ImageUpdate implements ShouldQueue
|
|||
return;
|
||||
}
|
||||
|
||||
if(config('pixelfed.optimize_image')) {
|
||||
if((bool) config_cache('pixelfed.optimize_image')) {
|
||||
if (in_array($media->mime, $this->protectedMimes) == true) {
|
||||
ImageOptimizer::optimize($thumb);
|
||||
if(!$media->skip_optimize) {
|
||||
|
|
|
@ -3,27 +3,30 @@
|
|||
namespace App\Jobs\MediaPipeline;
|
||||
|
||||
use App\Media;
|
||||
use App\Services\Media\MediaHlsService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Services\Media\MediaHlsService;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class MediaDeletePipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||
class MediaDeletePipeline implements ShouldBeUniqueUntilProcessing, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $media;
|
||||
protected $media;
|
||||
|
||||
public $timeout = 300;
|
||||
|
||||
public $tries = 3;
|
||||
|
||||
public $maxExceptions = 1;
|
||||
|
||||
public $failOnTimeout = true;
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
/**
|
||||
|
@ -38,7 +41,7 @@ class MediaDeletePipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
|||
*/
|
||||
public function uniqueId(): string
|
||||
{
|
||||
return 'media:purge-job:id-' . $this->media->id;
|
||||
return 'media:purge-job:id-'.$this->media->id;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -51,58 +54,58 @@ class MediaDeletePipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
|||
return [(new WithoutOverlapping("media:purge-job:id-{$this->media->id}"))->shared()->dontRelease()];
|
||||
}
|
||||
|
||||
public function __construct(Media $media)
|
||||
{
|
||||
$this->media = $media;
|
||||
}
|
||||
public function __construct(Media $media)
|
||||
{
|
||||
$this->media = $media;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$media = $this->media;
|
||||
$path = $media->media_path;
|
||||
$thumb = $media->thumbnail_path;
|
||||
public function handle()
|
||||
{
|
||||
$media = $this->media;
|
||||
$path = $media->media_path;
|
||||
$thumb = $media->thumbnail_path;
|
||||
|
||||
if(!$path) {
|
||||
return 1;
|
||||
}
|
||||
if (! $path) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$e = explode('/', $path);
|
||||
array_pop($e);
|
||||
$i = implode('/', $e);
|
||||
$e = explode('/', $path);
|
||||
array_pop($e);
|
||||
$i = implode('/', $e);
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage') == true) {
|
||||
$disk = Storage::disk(config('filesystems.cloud'));
|
||||
if ((bool) config_cache('pixelfed.cloud_storage') == true) {
|
||||
$disk = Storage::disk(config('filesystems.cloud'));
|
||||
|
||||
if($path && $disk->exists($path)) {
|
||||
$disk->delete($path);
|
||||
}
|
||||
if ($path && $disk->exists($path)) {
|
||||
$disk->delete($path);
|
||||
}
|
||||
|
||||
if($thumb && $disk->exists($thumb)) {
|
||||
$disk->delete($thumb);
|
||||
}
|
||||
}
|
||||
if ($thumb && $disk->exists($thumb)) {
|
||||
$disk->delete($thumb);
|
||||
}
|
||||
}
|
||||
|
||||
$disk = Storage::disk(config('filesystems.local'));
|
||||
$disk = Storage::disk(config('filesystems.local'));
|
||||
|
||||
if($path && $disk->exists($path)) {
|
||||
$disk->delete($path);
|
||||
}
|
||||
if ($path && $disk->exists($path)) {
|
||||
$disk->delete($path);
|
||||
}
|
||||
|
||||
if($thumb && $disk->exists($thumb)) {
|
||||
$disk->delete($thumb);
|
||||
}
|
||||
if ($thumb && $disk->exists($thumb)) {
|
||||
$disk->delete($thumb);
|
||||
}
|
||||
|
||||
if($media->hls_path != null) {
|
||||
if ($media->hls_path != null) {
|
||||
$files = MediaHlsService::allFiles($media);
|
||||
if($files && count($files)) {
|
||||
foreach($files as $file) {
|
||||
if ($files && count($files)) {
|
||||
foreach ($files as $file) {
|
||||
$disk->delete($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$media->delete();
|
||||
$media->delete();
|
||||
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,68 +8,69 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
|||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class MediaFixLocalFilesystemCleanupPipeline implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 1800;
|
||||
public $tries = 5;
|
||||
public $maxExceptions = 1;
|
||||
public $timeout = 1800;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
if(config_cache('pixelfed.cloud_storage') == false) {
|
||||
// Only run if cloud storage is enabled
|
||||
return;
|
||||
}
|
||||
public $tries = 5;
|
||||
|
||||
$disk = Storage::disk('local');
|
||||
$cloud = Storage::disk(config('filesystems.cloud'));
|
||||
public $maxExceptions = 1;
|
||||
|
||||
Media::whereNotNull(['status_id', 'cdn_url', 'replicated_at'])
|
||||
->chunk(20, function ($medias) use($disk, $cloud) {
|
||||
foreach($medias as $media) {
|
||||
if(!str_starts_with($media->media_path, 'public')) {
|
||||
continue;
|
||||
}
|
||||
public function handle()
|
||||
{
|
||||
if ((bool) config_cache('pixelfed.cloud_storage') == false) {
|
||||
// Only run if cloud storage is enabled
|
||||
return;
|
||||
}
|
||||
|
||||
if($disk->exists($media->media_path) && $cloud->exists($media->media_path)) {
|
||||
$disk->delete($media->media_path);
|
||||
}
|
||||
$disk = Storage::disk('local');
|
||||
$cloud = Storage::disk(config('filesystems.cloud'));
|
||||
|
||||
if($media->thumbnail_path) {
|
||||
if($disk->exists($media->thumbnail_path)) {
|
||||
$disk->delete($media->thumbnail_path);
|
||||
}
|
||||
}
|
||||
Media::whereNotNull(['status_id', 'cdn_url', 'replicated_at'])
|
||||
->chunk(20, function ($medias) use ($disk, $cloud) {
|
||||
foreach ($medias as $media) {
|
||||
if (! str_starts_with($media->media_path, 'public')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$paths = explode('/', $media->media_path);
|
||||
if(count($paths) === 7) {
|
||||
array_pop($paths);
|
||||
$baseDir = implode('/', $paths);
|
||||
if ($disk->exists($media->media_path) && $cloud->exists($media->media_path)) {
|
||||
$disk->delete($media->media_path);
|
||||
}
|
||||
|
||||
if(count($disk->allFiles($baseDir)) === 0) {
|
||||
$disk->deleteDirectory($baseDir);
|
||||
if ($media->thumbnail_path) {
|
||||
if ($disk->exists($media->thumbnail_path)) {
|
||||
$disk->delete($media->thumbnail_path);
|
||||
}
|
||||
}
|
||||
|
||||
array_pop($paths);
|
||||
$baseDir = implode('/', $paths);
|
||||
$paths = explode('/', $media->media_path);
|
||||
if (count($paths) === 7) {
|
||||
array_pop($paths);
|
||||
$baseDir = implode('/', $paths);
|
||||
|
||||
if(count($disk->allFiles($baseDir)) === 0) {
|
||||
$disk->deleteDirectory($baseDir);
|
||||
if (count($disk->allFiles($baseDir)) === 0) {
|
||||
$disk->deleteDirectory($baseDir);
|
||||
|
||||
array_pop($paths);
|
||||
$baseDir = implode('/', $paths);
|
||||
array_pop($paths);
|
||||
$baseDir = implode('/', $paths);
|
||||
|
||||
if(count($disk->allFiles($baseDir)) === 0) {
|
||||
$disk->deleteDirectory($baseDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if (count($disk->allFiles($baseDir)) === 0) {
|
||||
$disk->deleteDirectory($baseDir);
|
||||
|
||||
array_pop($paths);
|
||||
$baseDir = implode('/', $paths);
|
||||
|
||||
if (count($disk->allFiles($baseDir)) === 0) {
|
||||
$disk->deleteDirectory($baseDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ use Log;
|
|||
use Storage;
|
||||
use Zttp\Zttp;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use App\Services\MediaPathService;
|
||||
|
||||
class RemoteFollowImportRecent implements ShouldQueue
|
||||
{
|
||||
|
@ -45,7 +46,6 @@ class RemoteFollowImportRecent implements ShouldQueue
|
|||
'image/jpg',
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -208,9 +208,7 @@ class RemoteFollowImportRecent implements ShouldQueue
|
|||
public function importMedia($url, $mime, $status)
|
||||
{
|
||||
$user = $this->profile;
|
||||
$monthHash = hash('sha1', date('Y').date('m'));
|
||||
$userHash = hash('sha1', $user->id.(string) $user->created_at);
|
||||
$storagePath = "public/m/{$monthHash}/{$userHash}";
|
||||
$storagePath = MediaPathService::get($user, 2);
|
||||
|
||||
try {
|
||||
$info = pathinfo($url);
|
||||
|
|
|
@ -2,9 +2,15 @@
|
|||
|
||||
namespace App\Jobs\SharePipeline;
|
||||
|
||||
use Cache, Log;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use App\{Status, Notification};
|
||||
use App\Jobs\HomeFeedPipeline\FeedInsertPipeline;
|
||||
use App\Notification;
|
||||
use App\Services\ReblogService;
|
||||
use App\Services\StatusService;
|
||||
use App\Status;
|
||||
use App\Transformer\ActivityPub\Verb\Announce;
|
||||
use App\Util\ActivityPub\HttpSignature;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Pool;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
@ -12,141 +18,136 @@ use Illuminate\Queue\InteractsWithQueue;
|
|||
use Illuminate\Queue\SerializesModels;
|
||||
use League\Fractal;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
use App\Transformer\ActivityPub\Verb\Announce;
|
||||
use GuzzleHttp\{Pool, Client, Promise};
|
||||
use App\Util\ActivityPub\HttpSignature;
|
||||
use App\Services\ReblogService;
|
||||
use App\Services\StatusService;
|
||||
use App\Jobs\HomeFeedPipeline\FeedInsertPipeline;
|
||||
|
||||
class SharePipeline implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $status;
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* 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(Status $status)
|
||||
{
|
||||
$this->status = $status;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
$parent = Status::find($this->status->reblog_of_id);
|
||||
if(!$parent) {
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$status = $this->status;
|
||||
$parent = Status::find($this->status->reblog_of_id);
|
||||
if (! $parent) {
|
||||
return;
|
||||
}
|
||||
$actor = $status->profile;
|
||||
$target = $parent->profile;
|
||||
$actor = $status->profile;
|
||||
$target = $parent->profile;
|
||||
|
||||
if ($status->uri !== null) {
|
||||
// Ignore notifications to remote statuses
|
||||
return;
|
||||
}
|
||||
if ($status->uri !== null) {
|
||||
// Ignore notifications to remote statuses
|
||||
return;
|
||||
}
|
||||
|
||||
if($target->id === $status->profile_id) {
|
||||
$this->remoteAnnounceDeliver();
|
||||
return true;
|
||||
}
|
||||
if ($target->id === $status->profile_id) {
|
||||
$this->remoteAnnounceDeliver();
|
||||
|
||||
ReblogService::addPostReblog($parent->profile_id, $status->id);
|
||||
return true;
|
||||
}
|
||||
|
||||
$parent->reblogs_count = $parent->reblogs_count + 1;
|
||||
$parent->save();
|
||||
StatusService::del($parent->id);
|
||||
ReblogService::addPostReblog($parent->profile_id, $status->id);
|
||||
|
||||
Notification::firstOrCreate(
|
||||
[
|
||||
'profile_id' => $target->id,
|
||||
'actor_id' => $actor->id,
|
||||
'action' => 'share',
|
||||
'item_type' => 'App\Status',
|
||||
'item_id' => $status->reblog_of_id ?? $status->id,
|
||||
]
|
||||
);
|
||||
$parent->reblogs_count = $parent->reblogs_count + 1;
|
||||
$parent->save();
|
||||
StatusService::del($parent->id);
|
||||
|
||||
FeedInsertPipeline::dispatch($status->id, $status->profile_id)->onQueue('feed');
|
||||
Notification::firstOrCreate(
|
||||
[
|
||||
'profile_id' => $target->id,
|
||||
'actor_id' => $actor->id,
|
||||
'action' => 'share',
|
||||
'item_type' => 'App\Status',
|
||||
'item_id' => $status->reblog_of_id ?? $status->id,
|
||||
]
|
||||
);
|
||||
|
||||
return $this->remoteAnnounceDeliver();
|
||||
}
|
||||
FeedInsertPipeline::dispatch($status->id, $status->profile_id)->onQueue('feed');
|
||||
|
||||
public function remoteAnnounceDeliver()
|
||||
{
|
||||
if(config('app.env') !== 'production' || config_cache('federation.activitypub.enabled') == false) {
|
||||
return true;
|
||||
}
|
||||
$status = $this->status;
|
||||
$profile = $status->profile;
|
||||
return $this->remoteAnnounceDeliver();
|
||||
}
|
||||
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$resource = new Fractal\Resource\Item($status, new Announce());
|
||||
$activity = $fractal->createData($resource)->toArray();
|
||||
public function remoteAnnounceDeliver()
|
||||
{
|
||||
if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) {
|
||||
return true;
|
||||
}
|
||||
$status = $this->status;
|
||||
$profile = $status->profile;
|
||||
|
||||
$audience = $status->profile->getAudienceInbox();
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$resource = new Fractal\Resource\Item($status, new Announce());
|
||||
$activity = $fractal->createData($resource)->toArray();
|
||||
|
||||
if(empty($audience) || $status->scope != 'public') {
|
||||
// Return on profiles with no remote followers
|
||||
return;
|
||||
}
|
||||
$audience = $status->profile->getAudienceInbox();
|
||||
|
||||
$payload = json_encode($activity);
|
||||
if (empty($audience) || $status->scope != 'public') {
|
||||
// Return on profiles with no remote followers
|
||||
return;
|
||||
}
|
||||
|
||||
$client = new Client([
|
||||
'timeout' => config('federation.activitypub.delivery.timeout')
|
||||
]);
|
||||
$payload = json_encode($activity);
|
||||
|
||||
$version = config('pixelfed.version');
|
||||
$appUrl = config('app.url');
|
||||
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||
$client = new Client([
|
||||
'timeout' => config('federation.activitypub.delivery.timeout'),
|
||||
]);
|
||||
|
||||
$requests = function($audience) use ($client, $activity, $profile, $payload, $userAgent) {
|
||||
foreach($audience as $url) {
|
||||
$headers = HttpSignature::sign($profile, $url, $activity, [
|
||||
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => $userAgent,
|
||||
]);
|
||||
yield function() use ($client, $url, $headers, $payload) {
|
||||
return $client->postAsync($url, [
|
||||
'curl' => [
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HEADER => true
|
||||
]
|
||||
]);
|
||||
};
|
||||
}
|
||||
};
|
||||
$version = config('pixelfed.version');
|
||||
$appUrl = config('app.url');
|
||||
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||
|
||||
$pool = new Pool($client, $requests($audience), [
|
||||
'concurrency' => config('federation.activitypub.delivery.concurrency'),
|
||||
'fulfilled' => function ($response, $index) {
|
||||
},
|
||||
'rejected' => function ($reason, $index) {
|
||||
}
|
||||
]);
|
||||
$requests = function ($audience) use ($client, $activity, $profile, $payload, $userAgent) {
|
||||
foreach ($audience as $url) {
|
||||
$headers = HttpSignature::sign($profile, $url, $activity, [
|
||||
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => $userAgent,
|
||||
]);
|
||||
yield function () use ($client, $url, $headers, $payload) {
|
||||
return $client->postAsync($url, [
|
||||
'curl' => [
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HEADER => true,
|
||||
],
|
||||
]);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
$promise = $pool->promise();
|
||||
$pool = new Pool($client, $requests($audience), [
|
||||
'concurrency' => config('federation.activitypub.delivery.concurrency'),
|
||||
'fulfilled' => function ($response, $index) {
|
||||
},
|
||||
'rejected' => function ($reason, $index) {
|
||||
},
|
||||
]);
|
||||
|
||||
$promise->wait();
|
||||
$promise = $pool->promise();
|
||||
|
||||
}
|
||||
$promise->wait();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,15 @@
|
|||
|
||||
namespace App\Jobs\SharePipeline;
|
||||
|
||||
use Cache, Log;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use App\{Status, Notification};
|
||||
use App\Jobs\HomeFeedPipeline\FeedRemovePipeline;
|
||||
use App\Notification;
|
||||
use App\Services\ReblogService;
|
||||
use App\Services\StatusService;
|
||||
use App\Status;
|
||||
use App\Transformer\ActivityPub\Verb\UndoAnnounce;
|
||||
use App\Util\ActivityPub\HttpSignature;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Pool;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
|
@ -12,128 +18,125 @@ use Illuminate\Queue\InteractsWithQueue;
|
|||
use Illuminate\Queue\SerializesModels;
|
||||
use League\Fractal;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
use App\Transformer\ActivityPub\Verb\UndoAnnounce;
|
||||
use GuzzleHttp\{Pool, Client, Promise};
|
||||
use App\Util\ActivityPub\HttpSignature;
|
||||
use App\Services\ReblogService;
|
||||
use App\Services\StatusService;
|
||||
use App\Jobs\HomeFeedPipeline\FeedRemovePipeline;
|
||||
|
||||
class UndoSharePipeline implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
protected $status;
|
||||
public $deleteWhenMissingModels = true;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(Status $status)
|
||||
{
|
||||
$this->status = $status;
|
||||
}
|
||||
protected $status;
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$status = $this->status;
|
||||
$actor = $status->profile;
|
||||
$parent = Status::find($status->reblog_of_id);
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
FeedRemovePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed');
|
||||
public function __construct(Status $status)
|
||||
{
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
if($parent) {
|
||||
$target = $parent->profile_id;
|
||||
ReblogService::removePostReblog($parent->profile_id, $status->id);
|
||||
public function handle()
|
||||
{
|
||||
$status = $this->status;
|
||||
$actor = $status->profile;
|
||||
$parent = Status::find($status->reblog_of_id);
|
||||
|
||||
if($parent->reblogs_count > 0) {
|
||||
$parent->reblogs_count = $parent->reblogs_count - 1;
|
||||
$parent->save();
|
||||
StatusService::del($parent->id);
|
||||
}
|
||||
FeedRemovePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed');
|
||||
|
||||
$notification = Notification::whereProfileId($target)
|
||||
->whereActorId($status->profile_id)
|
||||
->whereAction('share')
|
||||
->whereItemId($status->reblog_of_id)
|
||||
->whereItemType('App\Status')
|
||||
->first();
|
||||
if ($parent) {
|
||||
$target = $parent->profile_id;
|
||||
ReblogService::removePostReblog($parent->profile_id, $status->id);
|
||||
|
||||
if($notification) {
|
||||
$notification->forceDelete();
|
||||
}
|
||||
}
|
||||
if ($parent->reblogs_count > 0) {
|
||||
$parent->reblogs_count = $parent->reblogs_count - 1;
|
||||
$parent->save();
|
||||
StatusService::del($parent->id);
|
||||
}
|
||||
|
||||
if ($status->uri != null) {
|
||||
return;
|
||||
}
|
||||
$notification = Notification::whereProfileId($target)
|
||||
->whereActorId($status->profile_id)
|
||||
->whereAction('share')
|
||||
->whereItemId($status->reblog_of_id)
|
||||
->whereItemType('App\Status')
|
||||
->first();
|
||||
|
||||
if(config('app.env') !== 'production' || config_cache('federation.activitypub.enabled') == false) {
|
||||
return $status->delete();
|
||||
} else {
|
||||
return $this->remoteAnnounceDeliver();
|
||||
}
|
||||
}
|
||||
if ($notification) {
|
||||
$notification->forceDelete();
|
||||
}
|
||||
}
|
||||
|
||||
public function remoteAnnounceDeliver()
|
||||
{
|
||||
if(config('app.env') !== 'production' || config_cache('federation.activitypub.enabled') == false) {
|
||||
if ($status->uri != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) {
|
||||
return $status->delete();
|
||||
} else {
|
||||
return $this->remoteAnnounceDeliver();
|
||||
}
|
||||
}
|
||||
|
||||
public function remoteAnnounceDeliver()
|
||||
{
|
||||
if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) {
|
||||
$status->delete();
|
||||
return 1;
|
||||
}
|
||||
|
||||
$status = $this->status;
|
||||
$profile = $status->profile;
|
||||
return 1;
|
||||
}
|
||||
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$resource = new Fractal\Resource\Item($status, new UndoAnnounce());
|
||||
$activity = $fractal->createData($resource)->toArray();
|
||||
$status = $this->status;
|
||||
$profile = $status->profile;
|
||||
|
||||
$audience = $status->profile->getAudienceInbox();
|
||||
$fractal = new Fractal\Manager();
|
||||
$fractal->setSerializer(new ArraySerializer());
|
||||
$resource = new Fractal\Resource\Item($status, new UndoAnnounce());
|
||||
$activity = $fractal->createData($resource)->toArray();
|
||||
|
||||
if(empty($audience) || $status->scope != 'public') {
|
||||
return 1;
|
||||
}
|
||||
$audience = $status->profile->getAudienceInbox();
|
||||
|
||||
$payload = json_encode($activity);
|
||||
if (empty($audience) || $status->scope != 'public') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$client = new Client([
|
||||
'timeout' => config('federation.activitypub.delivery.timeout')
|
||||
]);
|
||||
$payload = json_encode($activity);
|
||||
|
||||
$version = config('pixelfed.version');
|
||||
$appUrl = config('app.url');
|
||||
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||
$client = new Client([
|
||||
'timeout' => config('federation.activitypub.delivery.timeout'),
|
||||
]);
|
||||
|
||||
$requests = function($audience) use ($client, $activity, $profile, $payload, $userAgent) {
|
||||
foreach($audience as $url) {
|
||||
$headers = HttpSignature::sign($profile, $url, $activity, [
|
||||
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => $userAgent,
|
||||
]);
|
||||
yield function() use ($client, $url, $headers, $payload) {
|
||||
return $client->postAsync($url, [
|
||||
'curl' => [
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HEADER => true
|
||||
]
|
||||
]);
|
||||
};
|
||||
}
|
||||
};
|
||||
$version = config('pixelfed.version');
|
||||
$appUrl = config('app.url');
|
||||
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||
|
||||
$pool = new Pool($client, $requests($audience), [
|
||||
'concurrency' => config('federation.activitypub.delivery.concurrency'),
|
||||
'fulfilled' => function ($response, $index) {
|
||||
},
|
||||
'rejected' => function ($reason, $index) {
|
||||
}
|
||||
]);
|
||||
$requests = function ($audience) use ($client, $activity, $profile, $payload, $userAgent) {
|
||||
foreach ($audience as $url) {
|
||||
$headers = HttpSignature::sign($profile, $url, $activity, [
|
||||
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => $userAgent,
|
||||
]);
|
||||
yield function () use ($client, $url, $headers, $payload) {
|
||||
return $client->postAsync($url, [
|
||||
'curl' => [
|
||||
CURLOPT_HTTPHEADER => $headers,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HEADER => true,
|
||||
],
|
||||
]);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
$promise = $pool->promise();
|
||||
$pool = new Pool($client, $requests($audience), [
|
||||
'concurrency' => config('federation.activitypub.delivery.concurrency'),
|
||||
'fulfilled' => function ($response, $index) {
|
||||
},
|
||||
'rejected' => function ($reason, $index) {
|
||||
},
|
||||
]);
|
||||
|
||||
$promise->wait();
|
||||
$promise = $pool->promise();
|
||||
|
||||
$status->delete();
|
||||
$promise->wait();
|
||||
|
||||
return 1;
|
||||
}
|
||||
$status->delete();
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,126 +2,122 @@
|
|||
|
||||
namespace App\Jobs\StatusPipeline;
|
||||
|
||||
use DB, Cache, Storage;
|
||||
use App\{
|
||||
AccountInterstitial,
|
||||
Bookmark,
|
||||
CollectionItem,
|
||||
DirectMessage,
|
||||
Like,
|
||||
Media,
|
||||
MediaTag,
|
||||
Mention,
|
||||
Notification,
|
||||
Report,
|
||||
Status,
|
||||
StatusArchived,
|
||||
StatusHashtag,
|
||||
StatusView
|
||||
};
|
||||
use App\AccountInterstitial;
|
||||
use App\Bookmark;
|
||||
use App\CollectionItem;
|
||||
use App\DirectMessage;
|
||||
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
||||
use App\Like;
|
||||
use App\Media;
|
||||
use App\MediaTag;
|
||||
use App\Mention;
|
||||
use App\Notification;
|
||||
use App\Report;
|
||||
use App\Services\CollectionService;
|
||||
use App\Services\NotificationService;
|
||||
use App\Services\StatusService;
|
||||
use App\Status;
|
||||
use App\StatusArchived;
|
||||
use App\StatusHashtag;
|
||||
use App\StatusView;
|
||||
use App\Transformer\ActivityPub\Verb\DeleteNote;
|
||||
use App\Util\ActivityPub\HttpSignature;
|
||||
use Cache;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Pool;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use League\Fractal;
|
||||
use Illuminate\Support\Str;
|
||||
use League\Fractal\Serializer\ArraySerializer;
|
||||
use App\Transformer\ActivityPub\Verb\DeleteNote;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use GuzzleHttp\Pool;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Promise;
|
||||
use App\Util\ActivityPub\HttpSignature;
|
||||
use App\Services\CollectionService;
|
||||
use App\Services\StatusService;
|
||||
use App\Services\NotificationService;
|
||||
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
||||
|
||||
class StatusDelete implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $status;
|
||||
protected $status;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
public $timeout = 900;
|
||||
|
||||
public $tries = 2;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Status $status)
|
||||
{
|
||||
$this->status = $status;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$status = $this->status;
|
||||
$profile = $this->status->profile;
|
||||
|
||||
StatusService::del($status->id, true);
|
||||
if($profile) {
|
||||
if(in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) {
|
||||
$profile->status_count = $profile->status_count - 1;
|
||||
$profile->save();
|
||||
}
|
||||
}
|
||||
StatusService::del($status->id, true);
|
||||
if ($profile) {
|
||||
if (in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) {
|
||||
$profile->status_count = $profile->status_count - 1;
|
||||
$profile->save();
|
||||
}
|
||||
}
|
||||
|
||||
Cache::forget('pf:atom:user-feed:by-id:' . $status->profile_id);
|
||||
Cache::forget('pf:atom:user-feed:by-id:'.$status->profile_id);
|
||||
|
||||
if(config_cache('federation.activitypub.enabled') == true) {
|
||||
return $this->fanoutDelete($status);
|
||||
} else {
|
||||
return $this->unlinkRemoveMedia($status);
|
||||
}
|
||||
}
|
||||
if ((bool) config_cache('federation.activitypub.enabled') == true) {
|
||||
return $this->fanoutDelete($status);
|
||||
} else {
|
||||
return $this->unlinkRemoveMedia($status);
|
||||
}
|
||||
}
|
||||
|
||||
public function unlinkRemoveMedia($status)
|
||||
{
|
||||
public function unlinkRemoveMedia($status)
|
||||
{
|
||||
Media::whereStatusId($status->id)
|
||||
->get()
|
||||
->each(function($media) {
|
||||
MediaDeletePipeline::dispatch($media);
|
||||
});
|
||||
->get()
|
||||
->each(function ($media) {
|
||||
MediaDeletePipeline::dispatch($media);
|
||||
});
|
||||
|
||||
if($status->in_reply_to_id) {
|
||||
$parent = Status::findOrFail($status->in_reply_to_id);
|
||||
--$parent->reply_count;
|
||||
$parent->save();
|
||||
StatusService::del($parent->id);
|
||||
}
|
||||
if ($status->in_reply_to_id) {
|
||||
$parent = Status::findOrFail($status->in_reply_to_id);
|
||||
$parent->reply_count--;
|
||||
$parent->save();
|
||||
StatusService::del($parent->id);
|
||||
}
|
||||
|
||||
Bookmark::whereStatusId($status->id)->delete();
|
||||
|
||||
CollectionItem::whereObjectType('App\Status')
|
||||
->whereObjectId($status->id)
|
||||
->get()
|
||||
->each(function($col) {
|
||||
->each(function ($col) {
|
||||
CollectionService::removeItem($col->collection_id, $col->object_id);
|
||||
$col->delete();
|
||||
});
|
||||
});
|
||||
|
||||
$dms = DirectMessage::whereStatusId($status->id)->get();
|
||||
foreach($dms as $dm) {
|
||||
foreach ($dms as $dm) {
|
||||
$not = Notification::whereItemType('App\DirectMessage')
|
||||
->whereItemId($dm->id)
|
||||
->first();
|
||||
if($not) {
|
||||
if ($not) {
|
||||
NotificationService::del($not->profile_id, $not->id);
|
||||
$not->forceDeleteQuietly();
|
||||
}
|
||||
|
@ -130,11 +126,11 @@ class StatusDelete implements ShouldQueue
|
|||
Like::whereStatusId($status->id)->delete();
|
||||
|
||||
$mediaTags = MediaTag::where('status_id', $status->id)->get();
|
||||
foreach($mediaTags as $mtag) {
|
||||
foreach ($mediaTags as $mtag) {
|
||||
$not = Notification::whereItemType('App\MediaTag')
|
||||
->whereItemId($mtag->id)
|
||||
->first();
|
||||
if($not) {
|
||||
if ($not) {
|
||||
NotificationService::del($not->profile_id, $not->id);
|
||||
$not->forceDeleteQuietly();
|
||||
}
|
||||
|
@ -142,85 +138,85 @@ class StatusDelete implements ShouldQueue
|
|||
}
|
||||
Mention::whereStatusId($status->id)->forceDelete();
|
||||
|
||||
Notification::whereItemType('App\Status')
|
||||
->whereItemId($status->id)
|
||||
->forceDelete();
|
||||
Notification::whereItemType('App\Status')
|
||||
->whereItemId($status->id)
|
||||
->forceDelete();
|
||||
|
||||
Report::whereObjectType('App\Status')
|
||||
->whereObjectId($status->id)
|
||||
->delete();
|
||||
Report::whereObjectType('App\Status')
|
||||
->whereObjectId($status->id)
|
||||
->delete();
|
||||
|
||||
StatusArchived::whereStatusId($status->id)->delete();
|
||||
StatusHashtag::whereStatusId($status->id)->delete();
|
||||
StatusView::whereStatusId($status->id)->delete();
|
||||
Status::whereInReplyToId($status->id)->update(['in_reply_to_id' => null]);
|
||||
Status::whereInReplyToId($status->id)->update(['in_reply_to_id' => null]);
|
||||
|
||||
AccountInterstitial::where('item_type', 'App\Status')
|
||||
->where('item_id', $status->id)
|
||||
->delete();
|
||||
AccountInterstitial::where('item_type', 'App\Status')
|
||||
->where('item_id', $status->id)
|
||||
->delete();
|
||||
|
||||
$status->delete();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public function fanoutDelete($status)
|
||||
{
|
||||
$profile = $status->profile;
|
||||
|
||||
if(!$profile) {
|
||||
return;
|
||||
}
|
||||
|
||||
$audience = $status->profile->getAudienceInbox();
|
||||
|
||||
$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')
|
||||
]);
|
||||
|
||||
$version = config('pixelfed.version');
|
||||
$appUrl = config('app.url');
|
||||
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||
|
||||
$requests = function($audience) use ($client, $activity, $profile, $payload, $userAgent) {
|
||||
foreach($audience as $url) {
|
||||
$headers = HttpSignature::sign($profile, $url, $activity, [
|
||||
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => $userAgent,
|
||||
]);
|
||||
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();
|
||||
$status->delete();
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public function fanoutDelete($status)
|
||||
{
|
||||
$profile = $status->profile;
|
||||
|
||||
if (! $profile) {
|
||||
return;
|
||||
}
|
||||
|
||||
$audience = $status->profile->getAudienceInbox();
|
||||
|
||||
$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'),
|
||||
]);
|
||||
|
||||
$version = config('pixelfed.version');
|
||||
$appUrl = config('app.url');
|
||||
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||
|
||||
$requests = function ($audience) use ($client, $activity, $profile, $payload, $userAgent) {
|
||||
foreach ($audience as $url) {
|
||||
$headers = HttpSignature::sign($profile, $url, $activity, [
|
||||
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||
'User-Agent' => $userAgent,
|
||||
]);
|
||||
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();
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,16 @@
|
|||
namespace App\Jobs\StatusPipeline;
|
||||
|
||||
use App\Hashtag;
|
||||
use App\Jobs\HomeFeedPipeline\FeedInsertPipeline;
|
||||
use App\Jobs\MentionPipeline\MentionPipeline;
|
||||
use App\Mention;
|
||||
use App\Profile;
|
||||
use App\Services\AdminShadowFilterService;
|
||||
use App\Services\PublicTimelineService;
|
||||
use App\Services\StatusService;
|
||||
use App\Services\UserFilterService;
|
||||
use App\Status;
|
||||
use App\StatusHashtag;
|
||||
use App\Services\PublicTimelineService;
|
||||
use App\Util\Lexer\Autolink;
|
||||
use App\Util\Lexer\Extractor;
|
||||
use App\Util\Sentiment\Bouncer;
|
||||
|
@ -19,18 +23,15 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
|||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Services\StatusService;
|
||||
use App\Services\UserFilterService;
|
||||
use App\Services\AdminShadowFilterService;
|
||||
use App\Jobs\HomeFeedPipeline\FeedInsertPipeline;
|
||||
use App\Jobs\HomeFeedPipeline\HashtagInsertFanoutPipeline;
|
||||
|
||||
class StatusEntityLexer implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $status;
|
||||
|
||||
protected $entities;
|
||||
|
||||
protected $autolink;
|
||||
|
||||
/**
|
||||
|
@ -60,12 +61,12 @@ class StatusEntityLexer implements ShouldQueue
|
|||
$profile = $this->status->profile;
|
||||
$status = $this->status;
|
||||
|
||||
if(in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) {
|
||||
if (in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])) {
|
||||
$profile->status_count = $profile->status_count + 1;
|
||||
$profile->save();
|
||||
}
|
||||
|
||||
if($profile->no_autolink == false) {
|
||||
if ($profile->no_autolink == false) {
|
||||
$this->parseEntities();
|
||||
}
|
||||
}
|
||||
|
@ -103,16 +104,16 @@ class StatusEntityLexer implements ShouldQueue
|
|||
$status = $this->status;
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
if(mb_strlen($tag) > 124) {
|
||||
if (mb_strlen($tag) > 124) {
|
||||
continue;
|
||||
}
|
||||
DB::transaction(function () use ($status, $tag) {
|
||||
$slug = str_slug($tag, '-', false);
|
||||
|
||||
$hashtag = Hashtag::firstOrCreate([
|
||||
'slug' => $slug
|
||||
'slug' => $slug,
|
||||
], [
|
||||
'name' => $tag
|
||||
'name' => $tag,
|
||||
]);
|
||||
|
||||
StatusHashtag::firstOrCreate(
|
||||
|
@ -136,11 +137,11 @@ class StatusEntityLexer implements ShouldQueue
|
|||
foreach ($mentions as $mention) {
|
||||
$mentioned = Profile::whereUsername($mention)->first();
|
||||
|
||||
if (empty($mentioned) || !isset($mentioned->id)) {
|
||||
if (empty($mentioned) || ! isset($mentioned->id)) {
|
||||
continue;
|
||||
}
|
||||
$blocks = UserFilterService::blocks($mentioned->id);
|
||||
if($blocks && in_array($status->profile_id, $blocks)) {
|
||||
if ($blocks && in_array($status->profile_id, $blocks)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -161,8 +162,8 @@ class StatusEntityLexer implements ShouldQueue
|
|||
$status = $this->status;
|
||||
StatusService::refresh($status->id);
|
||||
|
||||
if(config('exp.cached_home_timeline')) {
|
||||
if( $status->in_reply_to_id === null &&
|
||||
if (config('exp.cached_home_timeline')) {
|
||||
if ($status->in_reply_to_id === null &&
|
||||
in_array($status->scope, ['public', 'unlisted', 'private'])
|
||||
) {
|
||||
FeedInsertPipeline::dispatch($status->id, $status->profile_id)->onQueue('feed');
|
||||
|
@ -179,28 +180,28 @@ class StatusEntityLexer implements ShouldQueue
|
|||
'photo:album',
|
||||
'video',
|
||||
'video:album',
|
||||
'photo:video:album'
|
||||
'photo:video:album',
|
||||
];
|
||||
|
||||
if(config_cache('pixelfed.bouncer.enabled')) {
|
||||
if ((bool) config_cache('pixelfed.bouncer.enabled')) {
|
||||
Bouncer::get($status);
|
||||
}
|
||||
|
||||
Cache::forget('pf:atom:user-feed:by-id:' . $status->profile_id);
|
||||
Cache::forget('pf:atom:user-feed:by-id:'.$status->profile_id);
|
||||
$hideNsfw = config('instance.hide_nsfw_on_public_feeds');
|
||||
if( $status->uri == null &&
|
||||
if ($status->uri == null &&
|
||||
$status->scope == 'public' &&
|
||||
in_array($status->type, $types) &&
|
||||
$status->in_reply_to_id === null &&
|
||||
$status->reblog_of_id === null &&
|
||||
($hideNsfw ? $status->is_nsfw == false : true)
|
||||
) {
|
||||
if(AdminShadowFilterService::canAddToPublicFeedByProfileId($status->profile_id)) {
|
||||
if (AdminShadowFilterService::canAddToPublicFeedByProfileId($status->profile_id)) {
|
||||
PublicTimelineService::add($status->id);
|
||||
}
|
||||
}
|
||||
|
||||
if(config_cache('federation.activitypub.enabled') == true && config('app.env') == 'production') {
|
||||
if ((bool) config_cache('federation.activitypub.enabled') == true && config('app.env') == 'production') {
|
||||
StatusActivityPubDeliver::dispatch($status);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,172 +2,171 @@
|
|||
|
||||
namespace App\Jobs\StatusPipeline;
|
||||
|
||||
use App\Media;
|
||||
use App\Models\StatusEdit;
|
||||
use App\ModLog;
|
||||
use App\Profile;
|
||||
use App\Services\StatusService;
|
||||
use App\Status;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Media;
|
||||
use App\ModLog;
|
||||
use App\Profile;
|
||||
use App\Status;
|
||||
use App\Models\StatusEdit;
|
||||
use App\Services\StatusService;
|
||||
use Purify;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Purify;
|
||||
|
||||
class StatusRemoteUpdatePipeline implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $activity;
|
||||
public $activity;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($activity)
|
||||
{
|
||||
$this->activity = $activity;
|
||||
}
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct($activity)
|
||||
{
|
||||
$this->activity = $activity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$activity = $this->activity;
|
||||
$status = Status::with('media')->whereObjectUrl($activity['id'])->first();
|
||||
if(!$status) {
|
||||
return;
|
||||
}
|
||||
$this->createPreviousEdit($status);
|
||||
$this->updateMedia($status, $activity);
|
||||
$this->updateImmediateAttributes($status, $activity);
|
||||
$this->createEdit($status, $activity);
|
||||
}
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
$activity = $this->activity;
|
||||
$status = Status::with('media')->whereObjectUrl($activity['id'])->first();
|
||||
if (! $status) {
|
||||
return;
|
||||
}
|
||||
$this->createPreviousEdit($status);
|
||||
$this->updateMedia($status, $activity);
|
||||
$this->updateImmediateAttributes($status, $activity);
|
||||
$this->createEdit($status, $activity);
|
||||
}
|
||||
|
||||
protected function createPreviousEdit($status)
|
||||
{
|
||||
if(!$status->edits()->count()) {
|
||||
StatusEdit::create([
|
||||
'status_id' => $status->id,
|
||||
'profile_id' => $status->profile_id,
|
||||
'caption' => $status->caption,
|
||||
'spoiler_text' => $status->cw_summary,
|
||||
'is_nsfw' => $status->is_nsfw,
|
||||
'ordered_media_attachment_ids' => $status->media()->orderBy('order')->pluck('id')->toArray(),
|
||||
'created_at' => $status->created_at
|
||||
]);
|
||||
}
|
||||
}
|
||||
protected function createPreviousEdit($status)
|
||||
{
|
||||
if (! $status->edits()->count()) {
|
||||
StatusEdit::create([
|
||||
'status_id' => $status->id,
|
||||
'profile_id' => $status->profile_id,
|
||||
'caption' => $status->caption,
|
||||
'spoiler_text' => $status->cw_summary,
|
||||
'is_nsfw' => $status->is_nsfw,
|
||||
'ordered_media_attachment_ids' => $status->media()->orderBy('order')->pluck('id')->toArray(),
|
||||
'created_at' => $status->created_at,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
protected function updateMedia($status, $activity)
|
||||
{
|
||||
if(!isset($activity['attachment'])) {
|
||||
return;
|
||||
}
|
||||
$ogm = $status->media->count() ? $status->media()->orderBy('order')->get() : collect([]);
|
||||
$nm = collect($activity['attachment'])->filter(function($nm) {
|
||||
return isset(
|
||||
$nm['type'],
|
||||
$nm['mediaType'],
|
||||
$nm['url']
|
||||
) &&
|
||||
in_array($nm['type'], ['Document', 'Image', 'Video']) &&
|
||||
in_array($nm['mediaType'], explode(',', config('pixelfed.media_types')));
|
||||
});
|
||||
protected function updateMedia($status, $activity)
|
||||
{
|
||||
if (! isset($activity['attachment'])) {
|
||||
return;
|
||||
}
|
||||
$ogm = $status->media->count() ? $status->media()->orderBy('order')->get() : collect([]);
|
||||
$nm = collect($activity['attachment'])->filter(function ($nm) {
|
||||
return isset(
|
||||
$nm['type'],
|
||||
$nm['mediaType'],
|
||||
$nm['url']
|
||||
) &&
|
||||
in_array($nm['type'], ['Document', 'Image', 'Video']) &&
|
||||
in_array($nm['mediaType'], explode(',', config_cache('pixelfed.media_types')));
|
||||
});
|
||||
|
||||
// Skip when no media
|
||||
if(!$ogm->count() && !$nm->count()) {
|
||||
return;
|
||||
}
|
||||
// Skip when no media
|
||||
if (! $ogm->count() && ! $nm->count()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Media::whereProfileId($status->profile_id)
|
||||
->whereStatusId($status->id)
|
||||
->update([
|
||||
'status_id' => null
|
||||
]);
|
||||
Media::whereProfileId($status->profile_id)
|
||||
->whereStatusId($status->id)
|
||||
->update([
|
||||
'status_id' => null,
|
||||
]);
|
||||
|
||||
$nm->each(function($n, $key) use($status) {
|
||||
$res = Http::withOptions(['allow_redirects' => false])->retry(3, 100, throw: false)->head($n['url']);
|
||||
$nm->each(function ($n, $key) use ($status) {
|
||||
$res = Http::withOptions(['allow_redirects' => false])->retry(3, 100, throw: false)->head($n['url']);
|
||||
|
||||
if(!$res->successful()) {
|
||||
return;
|
||||
}
|
||||
if (! $res->successful()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!in_array($res->header('content-type'), explode(',',config('pixelfed.media_types')))) {
|
||||
return;
|
||||
}
|
||||
if (! in_array($res->header('content-type'), explode(',', config_cache('pixelfed.media_types')))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$m = new Media;
|
||||
$m->status_id = $status->id;
|
||||
$m->profile_id = $status->profile_id;
|
||||
$m->remote_media = true;
|
||||
$m->media_path = $n['url'];
|
||||
$m = new Media;
|
||||
$m->status_id = $status->id;
|
||||
$m->profile_id = $status->profile_id;
|
||||
$m->remote_media = true;
|
||||
$m->media_path = $n['url'];
|
||||
$m->mime = $res->header('content-type');
|
||||
$m->size = $res->hasHeader('content-length') ? $res->header('content-length') : null;
|
||||
$m->caption = isset($n['name']) && !empty($n['name']) ? Purify::clean($n['name']) : null;
|
||||
$m->remote_url = $n['url'];
|
||||
$m->caption = isset($n['name']) && ! empty($n['name']) ? Purify::clean($n['name']) : null;
|
||||
$m->remote_url = $n['url'];
|
||||
$m->blurhash = isset($n['blurhash']) && (strlen($n['blurhash']) < 50) ? $n['blurhash'] : null;
|
||||
$m->width = isset($n['width']) && !empty($n['width']) ? $n['width'] : null;
|
||||
$m->height = isset($n['height']) && !empty($n['height']) ? $n['height'] : null;
|
||||
$m->skip_optimize = true;
|
||||
$m->order = $key + 1;
|
||||
$m->save();
|
||||
});
|
||||
}
|
||||
$m->width = isset($n['width']) && ! empty($n['width']) ? $n['width'] : null;
|
||||
$m->height = isset($n['height']) && ! empty($n['height']) ? $n['height'] : null;
|
||||
$m->skip_optimize = true;
|
||||
$m->order = $key + 1;
|
||||
$m->save();
|
||||
});
|
||||
}
|
||||
|
||||
protected function updateImmediateAttributes($status, $activity)
|
||||
{
|
||||
if(isset($activity['content'])) {
|
||||
$status->caption = strip_tags($activity['content']);
|
||||
$status->rendered = Purify::clean($activity['content']);
|
||||
}
|
||||
protected function updateImmediateAttributes($status, $activity)
|
||||
{
|
||||
if (isset($activity['content'])) {
|
||||
$status->caption = strip_tags($activity['content']);
|
||||
$status->rendered = Purify::clean($activity['content']);
|
||||
}
|
||||
|
||||
if(isset($activity['sensitive'])) {
|
||||
if((bool) $activity['sensitive'] == false) {
|
||||
$status->is_nsfw = false;
|
||||
$exists = ModLog::whereObjectType('App\Status::class')
|
||||
->whereObjectId($status->id)
|
||||
->whereAction('admin.status.moderate')
|
||||
->exists();
|
||||
if($exists == true) {
|
||||
$status->is_nsfw = true;
|
||||
}
|
||||
$profile = Profile::find($status->profile_id);
|
||||
if(!$profile || $profile->cw == true) {
|
||||
$status->is_nsfw = true;
|
||||
}
|
||||
} else {
|
||||
$status->is_nsfw = true;
|
||||
}
|
||||
}
|
||||
if (isset($activity['sensitive'])) {
|
||||
if ((bool) $activity['sensitive'] == false) {
|
||||
$status->is_nsfw = false;
|
||||
$exists = ModLog::whereObjectType('App\Status::class')
|
||||
->whereObjectId($status->id)
|
||||
->whereAction('admin.status.moderate')
|
||||
->exists();
|
||||
if ($exists == true) {
|
||||
$status->is_nsfw = true;
|
||||
}
|
||||
$profile = Profile::find($status->profile_id);
|
||||
if (! $profile || $profile->cw == true) {
|
||||
$status->is_nsfw = true;
|
||||
}
|
||||
} else {
|
||||
$status->is_nsfw = true;
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($activity['summary'])) {
|
||||
$status->cw_summary = Purify::clean($activity['summary']);
|
||||
} else {
|
||||
$status->cw_summary = null;
|
||||
}
|
||||
if (isset($activity['summary'])) {
|
||||
$status->cw_summary = Purify::clean($activity['summary']);
|
||||
} else {
|
||||
$status->cw_summary = null;
|
||||
}
|
||||
|
||||
$status->edited_at = now();
|
||||
$status->save();
|
||||
StatusService::del($status->id);
|
||||
}
|
||||
$status->edited_at = now();
|
||||
$status->save();
|
||||
StatusService::del($status->id);
|
||||
}
|
||||
|
||||
protected function createEdit($status, $activity)
|
||||
{
|
||||
$cleaned = isset($activity['content']) ? Purify::clean($activity['content']) : null;
|
||||
$spoiler_text = isset($activity['summary']) ? Purify::clean($activity['summary']) : null;
|
||||
$sensitive = isset($activity['sensitive']) ? $activity['sensitive'] : null;
|
||||
$mids = $status->media()->count() ? $status->media()->orderBy('order')->pluck('id')->toArray() : null;
|
||||
StatusEdit::create([
|
||||
'status_id' => $status->id,
|
||||
'profile_id' => $status->profile_id,
|
||||
'caption' => $cleaned,
|
||||
'spoiler_text' => $spoiler_text,
|
||||
'is_nsfw' => $sensitive,
|
||||
'ordered_media_attachment_ids' => $mids
|
||||
]);
|
||||
}
|
||||
protected function createEdit($status, $activity)
|
||||
{
|
||||
$cleaned = isset($activity['content']) ? Purify::clean($activity['content']) : null;
|
||||
$spoiler_text = isset($activity['summary']) ? Purify::clean($activity['summary']) : null;
|
||||
$sensitive = isset($activity['sensitive']) ? $activity['sensitive'] : null;
|
||||
$mids = $status->media()->count() ? $status->media()->orderBy('order')->pluck('id')->toArray() : null;
|
||||
StatusEdit::create([
|
||||
'status_id' => $status->id,
|
||||
'profile_id' => $status->profile_id,
|
||||
'caption' => $cleaned,
|
||||
'spoiler_text' => $spoiler_text,
|
||||
'is_nsfw' => $sensitive,
|
||||
'ordered_media_attachment_ids' => $mids,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ class CustomEmoji extends Model
|
|||
|
||||
public static function scan($text, $activitypub = false)
|
||||
{
|
||||
if(config('federation.custom_emoji.enabled') == false) {
|
||||
if((bool) config_cache('federation.custom_emoji.enabled') == false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
namespace App\Observers;
|
||||
|
||||
use App\Avatar;
|
||||
use App\Services\AccountService;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Services\AccountService;
|
||||
|
||||
class AvatarObserver
|
||||
{
|
||||
|
@ -19,7 +19,6 @@ class AvatarObserver
|
|||
/**
|
||||
* Handle the avatar "created" event.
|
||||
*
|
||||
* @param \App\Avatar $avatar
|
||||
* @return void
|
||||
*/
|
||||
public function created(Avatar $avatar)
|
||||
|
@ -30,7 +29,6 @@ class AvatarObserver
|
|||
/**
|
||||
* Handle the avatar "updated" event.
|
||||
*
|
||||
* @param \App\Avatar $avatar
|
||||
* @return void
|
||||
*/
|
||||
public function updated(Avatar $avatar)
|
||||
|
@ -41,7 +39,6 @@ class AvatarObserver
|
|||
/**
|
||||
* Handle the avatar "deleted" event.
|
||||
*
|
||||
* @param \App\Avatar $avatar
|
||||
* @return void
|
||||
*/
|
||||
public function deleted(Avatar $avatar)
|
||||
|
@ -52,23 +49,22 @@ class AvatarObserver
|
|||
/**
|
||||
* Handle the avatar "deleting" event.
|
||||
*
|
||||
* @param \App\Avatar $avatar
|
||||
* @return void
|
||||
*/
|
||||
public function deleting(Avatar $avatar)
|
||||
{
|
||||
$path = storage_path('app/'.$avatar->media_path);
|
||||
if( is_file($path) &&
|
||||
if (is_file($path) &&
|
||||
$avatar->media_path != 'public/avatars/default.png' &&
|
||||
$avatar->media_path != 'public/avatars/default.jpg'
|
||||
) {
|
||||
@unlink($path);
|
||||
}
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage')) {
|
||||
if ((bool) config_cache('pixelfed.cloud_storage')) {
|
||||
$disk = Storage::disk(config('filesystems.cloud'));
|
||||
$base = Str::startsWith($avatar->media_path, 'cache/avatars/');
|
||||
if($base && $disk->exists($avatar->media_path)) {
|
||||
if ($base && $disk->exists($avatar->media_path)) {
|
||||
$disk->delete($avatar->media_path);
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +74,6 @@ class AvatarObserver
|
|||
/**
|
||||
* Handle the avatar "restored" event.
|
||||
*
|
||||
* @param \App\Avatar $avatar
|
||||
* @return void
|
||||
*/
|
||||
public function restored(Avatar $avatar)
|
||||
|
@ -89,7 +84,6 @@ class AvatarObserver
|
|||
/**
|
||||
* Handle the avatar "force deleted" event.
|
||||
*
|
||||
* @param \App\Avatar $avatar
|
||||
* @return void
|
||||
*/
|
||||
public function forceDeleted(Avatar $avatar)
|
||||
|
|
|
@ -107,7 +107,7 @@ class UserObserver
|
|||
CreateAvatar::dispatch($profile);
|
||||
});
|
||||
|
||||
if(config_cache('account.autofollow') == true) {
|
||||
if((bool) config_cache('account.autofollow') == true) {
|
||||
$names = config_cache('account.autofollow_usernames');
|
||||
$names = explode(',', $names);
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
namespace App\Providers;
|
||||
|
||||
use Gate;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Laravel\Passport\Passport;
|
||||
use Gate;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
|
@ -24,11 +24,11 @@ class AuthServiceProvider extends ServiceProvider
|
|||
*/
|
||||
public function boot()
|
||||
{
|
||||
if(config('app.env') === 'production' && config('pixelfed.oauth_enabled') == true) {
|
||||
if (config('app.env') === 'production' && (bool) config_cache('pixelfed.oauth_enabled') == true) {
|
||||
Passport::tokensExpireIn(now()->addDays(config('instance.oauth.token_expiration', 356)));
|
||||
Passport::refreshTokensExpireIn(now()->addDays(config('instance.oauth.refresh_expiration', 400)));
|
||||
Passport::enableImplicitGrant();
|
||||
if(config('instance.oauth.pat.enabled')) {
|
||||
if (config('instance.oauth.pat.enabled')) {
|
||||
Passport::personalAccessClientId(config('instance.oauth.pat.id'));
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,7 @@ class AuthServiceProvider extends ServiceProvider
|
|||
'follow' => 'Ability to follow other profiles',
|
||||
'admin:read' => 'Read all data on the server',
|
||||
'admin:write' => 'Modify all data on the server',
|
||||
'push' => 'Receive your push notifications'
|
||||
'push' => 'Receive your push notifications',
|
||||
]);
|
||||
|
||||
Passport::setDefaultScope([
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Services\Internal\BeagleService;
|
||||
use App\User;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class AdminSettingsService
|
||||
{
|
||||
public static function getAll()
|
||||
{
|
||||
return [
|
||||
'features' => self::getFeatures(),
|
||||
'landing' => self::getLanding(),
|
||||
'branding' => self::getBranding(),
|
||||
'media' => self::getMedia(),
|
||||
'rules' => self::getRules(),
|
||||
'suggested_rules' => self::getSuggestedRules(),
|
||||
'users' => self::getUsers(),
|
||||
'posts' => self::getPosts(),
|
||||
'platform' => self::getPlatform(),
|
||||
'storage' => self::getStorage(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getFeatures()
|
||||
{
|
||||
$cloud_storage = (bool) config_cache('pixelfed.cloud_storage');
|
||||
$cloud_disk = config('filesystems.cloud');
|
||||
$cloud_ready = ! empty(config('filesystems.disks.'.$cloud_disk.'.key')) && ! empty(config('filesystems.disks.'.$cloud_disk.'.secret'));
|
||||
$openReg = (bool) config_cache('pixelfed.open_registration');
|
||||
$curOnboarding = (bool) config_cache('instance.curated_registration.enabled');
|
||||
$regState = $openReg ? 'open' : ($curOnboarding ? 'filtered' : 'closed');
|
||||
|
||||
return [
|
||||
'registration_status' => $regState,
|
||||
'cloud_storage' => $cloud_ready && $cloud_storage,
|
||||
'activitypub_enabled' => (bool) config_cache('federation.activitypub.enabled'),
|
||||
'account_migration' => (bool) config_cache('federation.migration'),
|
||||
'mobile_apis' => (bool) config_cache('pixelfed.oauth_enabled'),
|
||||
'stories' => (bool) config_cache('instance.stories.enabled'),
|
||||
'instagram_import' => (bool) config_cache('pixelfed.import.instagram.enabled'),
|
||||
'autospam_enabled' => (bool) config_cache('pixelfed.bouncer.enabled'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getLanding()
|
||||
{
|
||||
$availableAdmins = User::whereIsAdmin(true)->get();
|
||||
$currentAdmin = config_cache('instance.admin.pid');
|
||||
|
||||
return [
|
||||
'admins' => $availableAdmins,
|
||||
'current_admin' => $currentAdmin,
|
||||
'show_directory' => (bool) config_cache('instance.landing.show_directory'),
|
||||
'show_explore' => (bool) config_cache('instance.landing.show_explore'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getBranding()
|
||||
{
|
||||
return [
|
||||
'name' => config_cache('app.name'),
|
||||
'short_description' => config_cache('app.short_description'),
|
||||
'long_description' => config_cache('app.description'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getMedia()
|
||||
{
|
||||
return [
|
||||
'max_photo_size' => config_cache('pixelfed.max_photo_size'),
|
||||
'max_album_length' => config_cache('pixelfed.max_album_length'),
|
||||
'image_quality' => config_cache('pixelfed.image_quality'),
|
||||
'media_types' => config_cache('pixelfed.media_types'),
|
||||
'optimize_image' => (bool) config_cache('pixelfed.optimize_image'),
|
||||
'optimize_video' => (bool) config_cache('pixelfed.optimize_video'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getRules()
|
||||
{
|
||||
return config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : [];
|
||||
}
|
||||
|
||||
public static function getSuggestedRules()
|
||||
{
|
||||
return BeagleService::getDefaultRules();
|
||||
}
|
||||
|
||||
public static function getUsers()
|
||||
{
|
||||
$autoFollow = config_cache('account.autofollow_usernames');
|
||||
if (strlen($autoFollow) >= 2) {
|
||||
$autoFollow = explode(',', $autoFollow);
|
||||
} else {
|
||||
$autoFollow = [];
|
||||
}
|
||||
|
||||
return [
|
||||
'require_email_verification' => (bool) config_cache('pixelfed.enforce_email_verification'),
|
||||
'enforce_account_limit' => (bool) config_cache('pixelfed.enforce_account_limit'),
|
||||
'max_account_size' => config_cache('pixelfed.max_account_size'),
|
||||
'admin_autofollow' => (bool) config_cache('account.autofollow'),
|
||||
'admin_autofollow_accounts' => $autoFollow,
|
||||
'max_user_blocks' => (int) config_cache('instance.user_filters.max_user_blocks'),
|
||||
'max_user_mutes' => (int) config_cache('instance.user_filters.max_user_mutes'),
|
||||
'max_domain_blocks' => (int) config_cache('instance.user_filters.max_domain_blocks'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPosts()
|
||||
{
|
||||
return [
|
||||
'max_caption_length' => config_cache('pixelfed.max_caption_length'),
|
||||
'max_altext_length' => config_cache('pixelfed.max_altext_length'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getPlatform()
|
||||
{
|
||||
return [
|
||||
'allow_app_registration' => (bool) config_cache('pixelfed.allow_app_registration'),
|
||||
'app_registration_rate_limit_attempts' => config_cache('pixelfed.app_registration_rate_limit_attempts'),
|
||||
'app_registration_rate_limit_decay' => config_cache('pixelfed.app_registration_rate_limit_decay'),
|
||||
'app_registration_confirm_rate_limit_attempts' => config_cache('pixelfed.app_registration_confirm_rate_limit_attempts'),
|
||||
'app_registration_confirm_rate_limit_decay' => config_cache('pixelfed.app_registration_confirm_rate_limit_decay'),
|
||||
'allow_post_embeds' => (bool) config_cache('instance.embed.post'),
|
||||
'allow_profile_embeds' => (bool) config_cache('instance.embed.profile'),
|
||||
'captcha_enabled' => (bool) config_cache('captcha.enabled'),
|
||||
'captcha_on_login' => (bool) config_cache('captcha.active.login'),
|
||||
'captcha_on_register' => (bool) config_cache('captcha.active.register'),
|
||||
'captcha_secret' => Str::of(config_cache('captcha.secret'))->mask('*', 4, -4),
|
||||
'captcha_sitekey' => Str::of(config_cache('captcha.sitekey'))->mask('*', 4, -4),
|
||||
'custom_emoji_enabled' => (bool) config_cache('federation.custom_emoji.enabled'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function getStorage()
|
||||
{
|
||||
$cloud_storage = (bool) config_cache('pixelfed.cloud_storage');
|
||||
$cloud_disk = config('filesystems.cloud');
|
||||
$cloud_ready = ! empty(config('filesystems.disks.'.$cloud_disk.'.key')) && ! empty(config('filesystems.disks.'.$cloud_disk.'.secret'));
|
||||
$primaryDisk = (bool) $cloud_ready && $cloud_storage;
|
||||
$pkey = 'filesystems.disks.'.$cloud_disk.'.';
|
||||
$disk = [
|
||||
'driver' => $cloud_disk,
|
||||
'key' => Str::of(config_cache($pkey.'key'))->mask('*', 0, -2),
|
||||
'secret' => Str::of(config_cache($pkey.'secret'))->mask('*', 0, -2),
|
||||
'region' => config_cache($pkey.'region'),
|
||||
'bucket' => config_cache($pkey.'bucket'),
|
||||
'visibility' => config_cache($pkey.'visibility'),
|
||||
'endpoint' => config_cache($pkey.'endpoint'),
|
||||
'url' => config_cache($pkey.'url'),
|
||||
'use_path_style_endpoint' => config_cache($pkey.'use_path_style_endpoint'),
|
||||
];
|
||||
|
||||
return [
|
||||
'primary_disk' => $primaryDisk ? 'cloud' : 'local',
|
||||
'cloud_ready' => (bool) $cloud_ready,
|
||||
'cloud_disk' => $cloud_disk,
|
||||
'disk_config' => $disk,
|
||||
];
|
||||
}
|
||||
}
|
|
@ -2,77 +2,82 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Util\Lexer\Classifier;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use App\Util\Lexer\Classifier;
|
||||
|
||||
class AutospamService
|
||||
{
|
||||
const CHCKD_CACHE_KEY = 'pf:services:autospam:nlp:checked';
|
||||
const MODEL_CACHE_KEY = 'pf:services:autospam:nlp:model-cache';
|
||||
const MODEL_FILE_PATH = 'nlp/active-training-data.json';
|
||||
const MODEL_SPAM_PATH = 'nlp/spam.json';
|
||||
const MODEL_HAM_PATH = 'nlp/ham.json';
|
||||
const CHCKD_CACHE_KEY = 'pf:services:autospam:nlp:checked';
|
||||
|
||||
public static function check($text)
|
||||
{
|
||||
if(!$text || strlen($text) == 0) {
|
||||
false;
|
||||
}
|
||||
if(!self::active()) {
|
||||
return null;
|
||||
}
|
||||
$model = self::getCachedModel();
|
||||
$classifier = new Classifier;
|
||||
$classifier->import($model['documents'], $model['words']);
|
||||
return $classifier->most($text) === 'spam';
|
||||
}
|
||||
const MODEL_CACHE_KEY = 'pf:services:autospam:nlp:model-cache';
|
||||
|
||||
public static function eligible()
|
||||
{
|
||||
return Cache::remember(self::CHCKD_CACHE_KEY, 86400, function() {
|
||||
if(!config_cache('pixelfed.bouncer.enabled') || !config('autospam.enabled')) {
|
||||
return false;
|
||||
}
|
||||
const MODEL_FILE_PATH = 'nlp/active-training-data.json';
|
||||
|
||||
if(!Storage::exists(self::MODEL_SPAM_PATH)) {
|
||||
return false;
|
||||
}
|
||||
const MODEL_SPAM_PATH = 'nlp/spam.json';
|
||||
|
||||
if(!Storage::exists(self::MODEL_HAM_PATH)) {
|
||||
return false;
|
||||
}
|
||||
const MODEL_HAM_PATH = 'nlp/ham.json';
|
||||
|
||||
if(!Storage::exists(self::MODEL_FILE_PATH)) {
|
||||
return false;
|
||||
} else {
|
||||
if(Storage::size(self::MODEL_FILE_PATH) < 1000) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public static function check($text)
|
||||
{
|
||||
if (! $text || strlen($text) == 0) {
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
if (! self::active()) {
|
||||
return null;
|
||||
}
|
||||
$model = self::getCachedModel();
|
||||
$classifier = new Classifier;
|
||||
$classifier->import($model['documents'], $model['words']);
|
||||
|
||||
public static function active()
|
||||
{
|
||||
return config_cache('autospam.nlp.enabled') && self::eligible();
|
||||
}
|
||||
return $classifier->most($text) === 'spam';
|
||||
}
|
||||
|
||||
public static function getCachedModel()
|
||||
{
|
||||
if(!self::active()) {
|
||||
return null;
|
||||
}
|
||||
public static function eligible()
|
||||
{
|
||||
return Cache::remember(self::CHCKD_CACHE_KEY, 86400, function () {
|
||||
if (! (bool) config_cache('pixelfed.bouncer.enabled') || ! (bool) config_cache('autospam.enabled')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Cache::remember(self::MODEL_CACHE_KEY, 86400, function() {
|
||||
$res = Storage::get(self::MODEL_FILE_PATH);
|
||||
if(!$res || empty($res)) {
|
||||
return null;
|
||||
}
|
||||
if (! Storage::exists(self::MODEL_SPAM_PATH)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return json_decode($res, true);
|
||||
});
|
||||
}
|
||||
if (! Storage::exists(self::MODEL_HAM_PATH)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! Storage::exists(self::MODEL_FILE_PATH)) {
|
||||
return false;
|
||||
} else {
|
||||
if (Storage::size(self::MODEL_FILE_PATH) < 1000) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
public static function active()
|
||||
{
|
||||
return config_cache('autospam.nlp.enabled') && self::eligible();
|
||||
}
|
||||
|
||||
public static function getCachedModel()
|
||||
{
|
||||
if (! self::active()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Cache::remember(self::MODEL_CACHE_KEY, 86400, function () {
|
||||
$res = Storage::get(self::MODEL_FILE_PATH);
|
||||
if (! $res || empty($res)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return json_decode($res, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,14 @@ use Cache;
|
|||
class ConfigCacheService
|
||||
{
|
||||
const CACHE_KEY = 'config_cache:_v0-key:';
|
||||
const PROTECTED_KEYS = [
|
||||
'filesystems.disks.s3.key',
|
||||
'filesystems.disks.s3.secret',
|
||||
'filesystems.disks.spaces.key',
|
||||
'filesystems.disks.spaces.secret',
|
||||
'captcha.secret',
|
||||
'captcha.sitekey',
|
||||
];
|
||||
|
||||
public static function get($key)
|
||||
{
|
||||
|
@ -89,6 +97,41 @@ class ConfigCacheService
|
|||
'pixelfed.app_registration_confirm_rate_limit_decay',
|
||||
'instance.embed.profile',
|
||||
'instance.embed.post',
|
||||
|
||||
'captcha.enabled',
|
||||
'captcha.secret',
|
||||
'captcha.sitekey',
|
||||
'captcha.active.login',
|
||||
'captcha.active.register',
|
||||
'captcha.triggers.login.enabled',
|
||||
'captcha.triggers.login.attempts',
|
||||
'federation.custom_emoji.enabled',
|
||||
|
||||
'pixelfed.optimize_image',
|
||||
'pixelfed.optimize_video',
|
||||
'pixelfed.max_collection_length',
|
||||
'media.delete_local_after_cloud',
|
||||
'instance.user_filters.max_user_blocks',
|
||||
'instance.user_filters.max_user_mutes',
|
||||
'instance.user_filters.max_domain_blocks',
|
||||
|
||||
'filesystems.disks.s3.key',
|
||||
'filesystems.disks.s3.secret',
|
||||
'filesystems.disks.s3.region',
|
||||
'filesystems.disks.s3.bucket',
|
||||
'filesystems.disks.s3.visibility',
|
||||
'filesystems.disks.s3.url',
|
||||
'filesystems.disks.s3.endpoint',
|
||||
'filesystems.disks.s3.use_path_style_endpoint',
|
||||
|
||||
'filesystems.disks.spaces.key',
|
||||
'filesystems.disks.spaces.secret',
|
||||
'filesystems.disks.spaces.region',
|
||||
'filesystems.disks.spaces.bucket',
|
||||
'filesystems.disks.spaces.visibility',
|
||||
'filesystems.disks.spaces.url',
|
||||
'filesystems.disks.spaces.endpoint',
|
||||
'filesystems.disks.spaces.use_path_style_endpoint',
|
||||
// 'system.user_mode'
|
||||
];
|
||||
|
||||
|
@ -100,20 +143,34 @@ class ConfigCacheService
|
|||
return config($key);
|
||||
}
|
||||
|
||||
$protect = false;
|
||||
$protected = null;
|
||||
if(in_array($key, self::PROTECTED_KEYS)) {
|
||||
$protect = true;
|
||||
}
|
||||
|
||||
$v = config($key);
|
||||
$c = ConfigCacheModel::where('k', $key)->first();
|
||||
|
||||
if ($c) {
|
||||
return $c->v ?? config($key);
|
||||
if($protect) {
|
||||
return decrypt($c->v) ?? config($key);
|
||||
} else {
|
||||
return $c->v ?? config($key);
|
||||
}
|
||||
}
|
||||
|
||||
if (! $v) {
|
||||
return;
|
||||
}
|
||||
|
||||
if($protect && $v) {
|
||||
$protected = encrypt($v);
|
||||
}
|
||||
|
||||
$cc = new ConfigCacheModel;
|
||||
$cc->k = $key;
|
||||
$cc->v = $v;
|
||||
$cc->v = $protect ? $protected : $v;
|
||||
$cc->save();
|
||||
|
||||
return $v;
|
||||
|
@ -124,8 +181,15 @@ class ConfigCacheService
|
|||
{
|
||||
$exists = ConfigCacheModel::whereK($key)->first();
|
||||
|
||||
$protect = false;
|
||||
$protected = null;
|
||||
if(in_array($key, self::PROTECTED_KEYS)) {
|
||||
$protect = true;
|
||||
$protected = encrypt($val);
|
||||
}
|
||||
|
||||
if ($exists) {
|
||||
$exists->v = $val;
|
||||
$exists->v = $protect ? $protected : $val;
|
||||
$exists->save();
|
||||
Cache::put(self::CACHE_KEY.$key, $val, now()->addHours(12));
|
||||
|
||||
|
@ -134,7 +198,7 @@ class ConfigCacheService
|
|||
|
||||
$cc = new ConfigCacheModel;
|
||||
$cc->k = $key;
|
||||
$cc->v = $val;
|
||||
$cc->v = $protect ? $protected : $val;
|
||||
$cc->save();
|
||||
|
||||
Cache::put(self::CACHE_KEY.$key, $val, now()->addHours(12));
|
||||
|
|
|
@ -13,7 +13,7 @@ class CustomEmojiService
|
|||
{
|
||||
public static function get($shortcode)
|
||||
{
|
||||
if(config('federation.custom_emoji.enabled') == false) {
|
||||
if((bool) config_cache('federation.custom_emoji.enabled') == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ class CustomEmojiService
|
|||
|
||||
public static function import($url, $id = false)
|
||||
{
|
||||
if(config('federation.custom_emoji.enabled') == false) {
|
||||
if((bool) config_cache('federation.custom_emoji.enabled') == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
use Aws\S3\Exception\S3Exception;
|
||||
use GuzzleHttp\Exception\ConnectException;
|
||||
use League\Flysystem\AwsS3V3\AwsS3V3Adapter;
|
||||
use League\Flysystem\Filesystem;
|
||||
use League\Flysystem\UnableToRetrieveMetadata;
|
||||
use League\Flysystem\FilesystemException;
|
||||
use League\Flysystem\UnableToListContents;
|
||||
use League\Flysystem\FileAttributes;
|
||||
use League\Flysystem\UnableToWriteFile;
|
||||
|
||||
class FilesystemService
|
||||
{
|
||||
const VERIFY_FILE_NAME = 'cfstest.txt';
|
||||
|
||||
public static function getVerifyCredentials($key, $secret, $region, $bucket, $endpoint)
|
||||
{
|
||||
$client = new S3Client([
|
||||
'version' => 'latest',
|
||||
'region' => $region,
|
||||
'endpoint' => $endpoint,
|
||||
'credentials' => [
|
||||
'key' => $key,
|
||||
'secret' => $secret,
|
||||
]
|
||||
]);
|
||||
|
||||
$adapter = new AwsS3V3Adapter(
|
||||
$client,
|
||||
$bucket,
|
||||
);
|
||||
|
||||
$throw = false;
|
||||
$filesystem = new Filesystem($adapter);
|
||||
|
||||
$writable = false;
|
||||
try {
|
||||
$filesystem->write(self::VERIFY_FILE_NAME, 'ok', []);
|
||||
$writable = true;
|
||||
} catch (FilesystemException | UnableToWriteFile $exception) {
|
||||
$writable = false;
|
||||
}
|
||||
|
||||
if(!$writable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $filesystem->read(self::VERIFY_FILE_NAME);
|
||||
if($response === 'ok') {
|
||||
$writable = true;
|
||||
$res[] = self::VERIFY_FILE_NAME;
|
||||
} else {
|
||||
$writable = false;
|
||||
}
|
||||
} catch (FilesystemException | UnableToReadFile $exception) {
|
||||
$writable = false;
|
||||
}
|
||||
|
||||
if(in_array(self::VERIFY_FILE_NAME, $res)) {
|
||||
try {
|
||||
$filesystem->delete(self::VERIFY_FILE_NAME);
|
||||
} catch (FilesystemException | UnableToDeleteFile $exception) {
|
||||
$writable = false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!$writable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(in_array(self::VERIFY_FILE_NAME, $res)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Internal;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Http\Client\ConnectionException;
|
||||
use Illuminate\Http\Client\RequestException;
|
||||
|
||||
class BeagleService
|
||||
{
|
||||
const DEFAULT_RULES_CACHE_KEY = 'pf:services:beagle:default_rules:v1';
|
||||
|
||||
public static function getDefaultRules()
|
||||
{
|
||||
return Cache::remember(self::DEFAULT_RULES_CACHE_KEY, now()->addDays(7), function() {
|
||||
try {
|
||||
$res = Http::withOptions(['allow_redirects' => false])
|
||||
->timeout(5)
|
||||
->connectTimeout(5)
|
||||
->retry(2, 500)
|
||||
->get('https://beagle.pixelfed.net/api/v1/common/suggestions/rules');
|
||||
} catch (RequestException $e) {
|
||||
return;
|
||||
} catch (ConnectionException $e) {
|
||||
return;
|
||||
} catch (Exception $e) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!$res->ok()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$json = $res->json();
|
||||
|
||||
if(!isset($json['rule_suggestions']) || !count($json['rule_suggestions'])) {
|
||||
return [];
|
||||
}
|
||||
return $json['rule_suggestions'];
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -53,8 +53,8 @@ class LandingService
|
|||
'name' => config_cache('app.name'),
|
||||
'url' => config_cache('app.url'),
|
||||
'domain' => config('pixelfed.domain.app'),
|
||||
'show_directory' => config_cache('instance.landing.show_directory'),
|
||||
'show_explore_feed' => config_cache('instance.landing.show_explore'),
|
||||
'show_directory' => (bool) config_cache('instance.landing.show_directory'),
|
||||
'show_explore_feed' => (bool) config_cache('instance.landing.show_explore'),
|
||||
'open_registration' => (bool) $openReg,
|
||||
'curated_onboarding' => (bool) config_cache('instance.curated_registration.enabled'),
|
||||
'version' => config('pixelfed.version'),
|
||||
|
@ -85,7 +85,7 @@ class LandingService
|
|||
'media_types' => config_cache('pixelfed.media_types'),
|
||||
],
|
||||
'features' => [
|
||||
'federation' => config_cache('federation.activitypub.enabled'),
|
||||
'federation' => (bool) config_cache('federation.activitypub.enabled'),
|
||||
'timelines' => [
|
||||
'local' => true,
|
||||
'network' => (bool) config_cache('federation.network_timeline'),
|
||||
|
|
|
@ -2,44 +2,38 @@
|
|||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Jobs\AvatarPipeline\AvatarStorageCleanup;
|
||||
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
||||
use App\Media;
|
||||
use App\Util\ActivityPub\Helpers;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use Illuminate\Http\File;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Media;
|
||||
use App\Profile;
|
||||
use App\User;
|
||||
use GuzzleHttp\Client;
|
||||
use App\Services\AccountService;
|
||||
use App\Http\Controllers\AvatarController;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
||||
use Illuminate\Support\Arr;
|
||||
use App\Jobs\AvatarPipeline\AvatarStorageCleanup;
|
||||
|
||||
class MediaStorageService {
|
||||
|
||||
class MediaStorageService
|
||||
{
|
||||
public static function store(Media $media)
|
||||
{
|
||||
if(config_cache('pixelfed.cloud_storage') == true) {
|
||||
if ((bool) config_cache('pixelfed.cloud_storage') == true) {
|
||||
(new self())->cloudStore($media);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
public static function move(Media $media)
|
||||
{
|
||||
if($media->remote_media) {
|
||||
if ($media->remote_media) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage') == true) {
|
||||
if ((bool) config_cache('pixelfed.cloud_storage') == true) {
|
||||
return (new self())->cloudMove($media);
|
||||
}
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public static function avatar($avatar, $local = false, $skipRecentCheck = false)
|
||||
|
@ -56,31 +50,31 @@ class MediaStorageService {
|
|||
return false;
|
||||
}
|
||||
|
||||
$h = Arr::mapWithKeys($r->getHeaders(), function($item, $key) {
|
||||
$h = Arr::mapWithKeys($r->getHeaders(), function ($item, $key) {
|
||||
return [strtolower($key) => last($item)];
|
||||
});
|
||||
|
||||
if(!isset($h['content-length'], $h['content-type'])) {
|
||||
if (! isset($h['content-length'], $h['content-type'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$len = (int) $h['content-length'];
|
||||
$mime = $h['content-type'];
|
||||
|
||||
if($len < 10 || $len > ((config_cache('pixelfed.max_photo_size') * 1000))) {
|
||||
if ($len < 10 || $len > ((config_cache('pixelfed.max_photo_size') * 1000))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
'length' => $len,
|
||||
'mime' => $mime
|
||||
'mime' => $mime,
|
||||
];
|
||||
}
|
||||
|
||||
protected function cloudStore($media)
|
||||
{
|
||||
if($media->remote_media == true) {
|
||||
if(config('media.storage.remote.cloud')) {
|
||||
if ($media->remote_media == true) {
|
||||
if (config('media.storage.remote.cloud')) {
|
||||
(new self())->remoteToCloud($media);
|
||||
}
|
||||
} else {
|
||||
|
@ -100,7 +94,7 @@ class MediaStorageService {
|
|||
$storagePath = implode('/', $p);
|
||||
|
||||
$url = ResilientMediaStorageService::store($storagePath, $path, $name);
|
||||
if($thumb) {
|
||||
if ($thumb) {
|
||||
$thumbUrl = ResilientMediaStorageService::store($storagePath, $thumb, $thumbname);
|
||||
$media->thumbnail_url = $thumbUrl;
|
||||
}
|
||||
|
@ -108,8 +102,8 @@ class MediaStorageService {
|
|||
$media->optimized_url = $url;
|
||||
$media->replicated_at = now();
|
||||
$media->save();
|
||||
if($media->status_id) {
|
||||
Cache::forget('status:transformer:media:attachments:' . $media->status_id);
|
||||
if ($media->status_id) {
|
||||
Cache::forget('status:transformer:media:attachments:'.$media->status_id);
|
||||
MediaService::del($media->status_id);
|
||||
StatusService::del($media->status_id, false);
|
||||
}
|
||||
|
@ -119,20 +113,20 @@ class MediaStorageService {
|
|||
{
|
||||
$url = $media->remote_url;
|
||||
|
||||
if(!Helpers::validateUrl($url)) {
|
||||
if (! Helpers::validateUrl($url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$head = $this->head($media->remote_url);
|
||||
|
||||
if(!$head) {
|
||||
if (! $head) {
|
||||
return;
|
||||
}
|
||||
|
||||
$mimes = [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'video/mp4'
|
||||
'video/mp4',
|
||||
];
|
||||
|
||||
$mime = $head['mime'];
|
||||
|
@ -141,11 +135,11 @@ class MediaStorageService {
|
|||
$media->remote_media = true;
|
||||
$media->save();
|
||||
|
||||
if(!in_array($mime, $mimes)) {
|
||||
if (! in_array($mime, $mimes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if($head['length'] >= $max_size) {
|
||||
if ($head['length'] >= $max_size) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -168,10 +162,10 @@ class MediaStorageService {
|
|||
}
|
||||
|
||||
$base = MediaPathService::get($media->profile);
|
||||
$path = Str::random(40) . $ext;
|
||||
$path = Str::random(40).$ext;
|
||||
$tmpBase = storage_path('app/remcache/');
|
||||
$tmpPath = $media->profile_id . '-' . $path;
|
||||
$tmpName = $tmpBase . $tmpPath;
|
||||
$tmpPath = $media->profile_id.'-'.$path;
|
||||
$tmpName = $tmpBase.$tmpPath;
|
||||
$data = file_get_contents($url, false, null, 0, $head['length']);
|
||||
file_put_contents($tmpName, $data);
|
||||
$hash = hash_file('sha256', $tmpName);
|
||||
|
@ -186,8 +180,8 @@ class MediaStorageService {
|
|||
$media->replicated_at = now();
|
||||
$media->save();
|
||||
|
||||
if($media->status_id) {
|
||||
Cache::forget('status:transformer:media:attachments:' . $media->status_id);
|
||||
if ($media->status_id) {
|
||||
Cache::forget('status:transformer:media:attachments:'.$media->status_id);
|
||||
}
|
||||
|
||||
unlink($tmpName);
|
||||
|
@ -199,13 +193,13 @@ class MediaStorageService {
|
|||
$url = $avatar->remote_url;
|
||||
$driver = $local ? 'local' : config('filesystems.cloud');
|
||||
|
||||
if(empty($url) || Helpers::validateUrl($url) == false) {
|
||||
if (empty($url) || Helpers::validateUrl($url) == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$head = $this->head($url);
|
||||
|
||||
if($head == false) {
|
||||
if ($head == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -218,46 +212,47 @@ class MediaStorageService {
|
|||
$mime = $head['mime'];
|
||||
$max_size = (int) config('pixelfed.max_avatar_size') * 1000;
|
||||
|
||||
if(!$skipRecentCheck) {
|
||||
if($avatar->last_fetched_at && $avatar->last_fetched_at->gt(now()->subMonths(3))) {
|
||||
if (! $skipRecentCheck) {
|
||||
if ($avatar->last_fetched_at && $avatar->last_fetched_at->gt(now()->subMonths(3))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Cache::forget('avatar:' . $avatar->profile_id);
|
||||
Cache::forget('avatar:'.$avatar->profile_id);
|
||||
AccountService::del($avatar->profile_id);
|
||||
|
||||
// handle pleroma edge case
|
||||
if(Str::endsWith($mime, '; charset=utf-8')) {
|
||||
if (Str::endsWith($mime, '; charset=utf-8')) {
|
||||
$mime = str_replace('; charset=utf-8', '', $mime);
|
||||
}
|
||||
|
||||
if(!in_array($mime, $mimes)) {
|
||||
if (! in_array($mime, $mimes)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if($head['length'] >= $max_size) {
|
||||
if ($head['length'] >= $max_size) {
|
||||
return;
|
||||
}
|
||||
|
||||
$base = ($local ? 'public/cache/' : 'cache/') . 'avatars/' . $avatar->profile_id;
|
||||
$base = ($local ? 'public/cache/' : 'cache/').'avatars/'.$avatar->profile_id;
|
||||
$ext = $head['mime'] == 'image/jpeg' ? 'jpg' : 'png';
|
||||
$path = 'avatar_' . strtolower(Str::random(random_int(3,6))) . '.' . $ext;
|
||||
$path = 'avatar_'.strtolower(Str::random(random_int(3, 6))).'.'.$ext;
|
||||
$tmpBase = storage_path('app/remcache/');
|
||||
$tmpPath = 'avatar_' . $avatar->profile_id . '-' . $path;
|
||||
$tmpName = $tmpBase . $tmpPath;
|
||||
$tmpPath = 'avatar_'.$avatar->profile_id.'-'.$path;
|
||||
$tmpName = $tmpBase.$tmpPath;
|
||||
$data = @file_get_contents($url, false, null, 0, $head['length']);
|
||||
if(!$data) {
|
||||
if (! $data) {
|
||||
return;
|
||||
}
|
||||
file_put_contents($tmpName, $data);
|
||||
|
||||
$mimeCheck = Storage::mimeType('remcache/' . $tmpPath);
|
||||
$mimeCheck = Storage::mimeType('remcache/'.$tmpPath);
|
||||
|
||||
if(!$mimeCheck || !in_array($mimeCheck, ['image/png', 'image/jpeg'])) {
|
||||
if (! $mimeCheck || ! in_array($mimeCheck, ['image/png', 'image/jpeg'])) {
|
||||
$avatar->last_fetched_at = now();
|
||||
$avatar->save();
|
||||
unlink($tmpName);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -265,15 +260,15 @@ class MediaStorageService {
|
|||
$file = $disk->putFileAs($base, new File($tmpName), $path, 'public');
|
||||
$permalink = $disk->url($file);
|
||||
|
||||
$avatar->media_path = $base . '/' . $path;
|
||||
$avatar->media_path = $base.'/'.$path;
|
||||
$avatar->is_remote = true;
|
||||
$avatar->cdn_url = $local ? config('app.url') . $permalink : $permalink;
|
||||
$avatar->cdn_url = $local ? config('app.url').$permalink : $permalink;
|
||||
$avatar->size = $head['length'];
|
||||
$avatar->change_count = $avatar->change_count + 1;
|
||||
$avatar->last_fetched_at = now();
|
||||
$avatar->save();
|
||||
|
||||
Cache::forget('avatar:' . $avatar->profile_id);
|
||||
Cache::forget('avatar:'.$avatar->profile_id);
|
||||
AccountService::del($avatar->profile_id);
|
||||
AvatarStorageCleanup::dispatch($avatar)->onQueue($queue)->delay(now()->addMinutes(random_int(3, 15)));
|
||||
|
||||
|
@ -282,7 +277,7 @@ class MediaStorageService {
|
|||
|
||||
public static function delete(Media $media, $confirm = false)
|
||||
{
|
||||
if(!$confirm) {
|
||||
if (! $confirm) {
|
||||
return;
|
||||
}
|
||||
MediaDeletePipeline::dispatch($media)->onQueue('mmo');
|
||||
|
@ -290,13 +285,13 @@ class MediaStorageService {
|
|||
|
||||
protected function cloudMove($media)
|
||||
{
|
||||
if(!Storage::exists($media->media_path)) {
|
||||
if (! Storage::exists($media->media_path)) {
|
||||
return 'invalid file';
|
||||
}
|
||||
|
||||
$path = storage_path('app/'.$media->media_path);
|
||||
$thumb = false;
|
||||
if($media->thumbnail_path) {
|
||||
if ($media->thumbnail_path) {
|
||||
$thumb = storage_path('app/'.$media->thumbnail_path);
|
||||
$pt = explode('/', $media->thumbnail_path);
|
||||
$thumbname = array_pop($pt);
|
||||
|
@ -307,7 +302,7 @@ class MediaStorageService {
|
|||
$storagePath = implode('/', $p);
|
||||
|
||||
$url = ResilientMediaStorageService::store($storagePath, $path, $name);
|
||||
if($thumb) {
|
||||
if ($thumb) {
|
||||
$thumbUrl = ResilientMediaStorageService::store($storagePath, $thumb, $thumbname);
|
||||
$media->thumbnail_url = $thumbUrl;
|
||||
}
|
||||
|
@ -316,8 +311,8 @@ class MediaStorageService {
|
|||
$media->replicated_at = now();
|
||||
$media->save();
|
||||
|
||||
if($media->status_id) {
|
||||
Cache::forget('status:transformer:media:attachments:' . $media->status_id);
|
||||
if ($media->status_id) {
|
||||
Cache::forget('status:transformer:media:attachments:'.$media->status_id);
|
||||
MediaService::del($media->status_id);
|
||||
StatusService::del($media->status_id, false);
|
||||
}
|
||||
|
|
|
@ -2,49 +2,34 @@
|
|||
|
||||
namespace App\Util\ActivityPub;
|
||||
|
||||
use DB, Cache, Purify, Storage, Request, Validator;
|
||||
use App\{
|
||||
Activity,
|
||||
Follower,
|
||||
Instance,
|
||||
Like,
|
||||
Media,
|
||||
Notification,
|
||||
Profile,
|
||||
Status
|
||||
};
|
||||
use Zttp\Zttp;
|
||||
use Carbon\Carbon;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Http\File;
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Jobs\AvatarPipeline\CreateAvatar;
|
||||
use App\Jobs\RemoteFollowPipeline\RemoteFollowImportRecent;
|
||||
use App\Jobs\ImageOptimizePipeline\{ImageOptimize,ImageThumbnail};
|
||||
use App\Jobs\StatusPipeline\NewStatusPipeline;
|
||||
use App\Jobs\StatusPipeline\StatusReplyPipeline;
|
||||
use App\Jobs\StatusPipeline\StatusTagsPipeline;
|
||||
use App\Util\ActivityPub\HttpSignature;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Services\ActivityPubFetchService;
|
||||
use App\Services\ActivityPubDeliveryService;
|
||||
use App\Services\CustomEmojiService;
|
||||
use App\Services\InstanceService;
|
||||
use App\Services\MediaPathService;
|
||||
use App\Services\MediaStorageService;
|
||||
use App\Services\NetworkTimelineService;
|
||||
use App\Jobs\MediaPipeline\MediaStoragePipeline;
|
||||
use App\Instance;
|
||||
use App\Jobs\AvatarPipeline\RemoteAvatarFetch;
|
||||
use App\Jobs\HomeFeedPipeline\FeedInsertRemotePipeline;
|
||||
use App\Util\Media\License;
|
||||
use App\Jobs\MediaPipeline\MediaStoragePipeline;
|
||||
use App\Jobs\StatusPipeline\StatusReplyPipeline;
|
||||
use App\Jobs\StatusPipeline\StatusTagsPipeline;
|
||||
use App\Media;
|
||||
use App\Models\Poll;
|
||||
use Illuminate\Contracts\Cache\LockTimeoutException;
|
||||
use App\Services\DomainService;
|
||||
use App\Services\UserFilterService;
|
||||
use App\Profile;
|
||||
use App\Services\Account\AccountStatService;
|
||||
use App\Services\ActivityPubDeliveryService;
|
||||
use App\Services\ActivityPubFetchService;
|
||||
use App\Services\DomainService;
|
||||
use App\Services\InstanceService;
|
||||
use App\Services\MediaPathService;
|
||||
use App\Services\NetworkTimelineService;
|
||||
use App\Services\UserFilterService;
|
||||
use App\Status;
|
||||
use App\Util\Media\License;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Purify;
|
||||
use Validator;
|
||||
|
||||
class Helpers {
|
||||
|
||||
class Helpers
|
||||
{
|
||||
public static function validateObject($data)
|
||||
{
|
||||
$verbs = ['Create', 'Announce', 'Like', 'Follow', 'Delete', 'Accept', 'Reject', 'Undo', 'Tombstone'];
|
||||
|
@ -53,14 +38,14 @@ class Helpers {
|
|||
'type' => [
|
||||
'required',
|
||||
'string',
|
||||
Rule::in($verbs)
|
||||
Rule::in($verbs),
|
||||
],
|
||||
'id' => 'required|string',
|
||||
'actor' => 'required|string|url',
|
||||
'object' => 'required',
|
||||
'object.type' => 'required_if:type,Create',
|
||||
'object.attributedTo' => 'required_if:type,Create|url',
|
||||
'published' => 'required_if:type,Create|date'
|
||||
'published' => 'required_if:type,Create|date',
|
||||
])->passes();
|
||||
|
||||
return $valid;
|
||||
|
@ -68,8 +53,8 @@ class Helpers {
|
|||
|
||||
public static function verifyAttachments($data)
|
||||
{
|
||||
if(!isset($data['object']) || empty($data['object'])) {
|
||||
$data = ['object'=>$data];
|
||||
if (! isset($data['object']) || empty($data['object'])) {
|
||||
$data = ['object' => $data];
|
||||
}
|
||||
|
||||
$activity = $data['object'];
|
||||
|
@ -80,7 +65,7 @@ class Helpers {
|
|||
// Peertube
|
||||
// $mediaTypes = in_array('video/mp4', $mimeTypes) ? ['Document', 'Image', 'Video', 'Link'] : ['Document', 'Image'];
|
||||
|
||||
if(!isset($activity['attachment']) || empty($activity['attachment'])) {
|
||||
if (! isset($activity['attachment']) || empty($activity['attachment'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -100,13 +85,13 @@ class Helpers {
|
|||
'*.type' => [
|
||||
'required',
|
||||
'string',
|
||||
Rule::in($mediaTypes)
|
||||
Rule::in($mediaTypes),
|
||||
],
|
||||
'*.url' => 'required|url',
|
||||
'*.mediaType' => [
|
||||
'*.mediaType' => [
|
||||
'required',
|
||||
'string',
|
||||
Rule::in($mimeTypes)
|
||||
Rule::in($mimeTypes),
|
||||
],
|
||||
'*.name' => 'sometimes|nullable|string',
|
||||
'*.blurhash' => 'sometimes|nullable|string|min:6|max:164',
|
||||
|
@ -119,7 +104,7 @@ class Helpers {
|
|||
|
||||
public static function normalizeAudience($data, $localOnly = true)
|
||||
{
|
||||
if(!isset($data['to'])) {
|
||||
if (! isset($data['to'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -128,32 +113,35 @@ class Helpers {
|
|||
$audience['cc'] = [];
|
||||
$scope = 'private';
|
||||
|
||||
if(is_array($data['to']) && !empty($data['to'])) {
|
||||
if (is_array($data['to']) && ! empty($data['to'])) {
|
||||
foreach ($data['to'] as $to) {
|
||||
if($to == 'https://www.w3.org/ns/activitystreams#Public') {
|
||||
if ($to == 'https://www.w3.org/ns/activitystreams#Public') {
|
||||
$scope = 'public';
|
||||
|
||||
continue;
|
||||
}
|
||||
$url = $localOnly ? self::validateLocalUrl($to) : self::validateUrl($to);
|
||||
if($url != false) {
|
||||
if ($url != false) {
|
||||
array_push($audience['to'], $url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(is_array($data['cc']) && !empty($data['cc'])) {
|
||||
if (is_array($data['cc']) && ! empty($data['cc'])) {
|
||||
foreach ($data['cc'] as $cc) {
|
||||
if($cc == 'https://www.w3.org/ns/activitystreams#Public') {
|
||||
if ($cc == 'https://www.w3.org/ns/activitystreams#Public') {
|
||||
$scope = 'unlisted';
|
||||
|
||||
continue;
|
||||
}
|
||||
$url = $localOnly ? self::validateLocalUrl($cc) : self::validateUrl($cc);
|
||||
if($url != false) {
|
||||
if ($url != false) {
|
||||
array_push($audience['cc'], $url);
|
||||
}
|
||||
}
|
||||
}
|
||||
$audience['scope'] = $scope;
|
||||
|
||||
return $audience;
|
||||
}
|
||||
|
||||
|
@ -161,56 +149,57 @@ class Helpers {
|
|||
{
|
||||
$audience = self::normalizeAudience($data);
|
||||
$url = $profile->permalink();
|
||||
|
||||
return in_array($url, $audience['to']) || in_array($url, $audience['cc']);
|
||||
}
|
||||
|
||||
public static function validateUrl($url)
|
||||
{
|
||||
if(is_array($url)) {
|
||||
if (is_array($url)) {
|
||||
$url = $url[0];
|
||||
}
|
||||
|
||||
$hash = hash('sha256', $url);
|
||||
$key = "helpers:url:valid:sha256-{$hash}";
|
||||
|
||||
$valid = Cache::remember($key, 900, function() use($url) {
|
||||
$valid = Cache::remember($key, 900, function () use ($url) {
|
||||
$localhosts = [
|
||||
'127.0.0.1', 'localhost', '::1'
|
||||
'127.0.0.1', 'localhost', '::1',
|
||||
];
|
||||
|
||||
if(strtolower(mb_substr($url, 0, 8)) !== 'https://') {
|
||||
if (strtolower(mb_substr($url, 0, 8)) !== 'https://') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(substr_count($url, '://') !== 1) {
|
||||
if (substr_count($url, '://') !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(mb_substr($url, 0, 8) !== 'https://') {
|
||||
$url = 'https://' . substr($url, 8);
|
||||
if (mb_substr($url, 0, 8) !== 'https://') {
|
||||
$url = 'https://'.substr($url, 8);
|
||||
}
|
||||
|
||||
$valid = filter_var($url, FILTER_VALIDATE_URL);
|
||||
|
||||
if(!$valid) {
|
||||
if (! $valid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$host = parse_url($valid, PHP_URL_HOST);
|
||||
|
||||
if(in_array($host, $localhosts)) {
|
||||
if (in_array($host, $localhosts)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(config('security.url.verify_dns')) {
|
||||
if(DomainService::hasValidDns($host) === false) {
|
||||
if (config('security.url.verify_dns')) {
|
||||
if (DomainService::hasValidDns($host) === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(app()->environment() === 'production') {
|
||||
if (app()->environment() === 'production') {
|
||||
$bannedInstances = InstanceService::getBannedDomains();
|
||||
if(in_array($host, $bannedInstances)) {
|
||||
if (in_array($host, $bannedInstances)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -224,12 +213,14 @@ class Helpers {
|
|||
public static function validateLocalUrl($url)
|
||||
{
|
||||
$url = self::validateUrl($url);
|
||||
if($url == true) {
|
||||
if ($url == true) {
|
||||
$domain = config('pixelfed.domain.app');
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
$url = strtolower($domain) === strtolower($host) ? $url : false;
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -237,15 +228,16 @@ class Helpers {
|
|||
{
|
||||
$version = config('pixelfed.version');
|
||||
$url = config('app.url');
|
||||
|
||||
return [
|
||||
'Accept' => 'application/activity+json',
|
||||
'Accept' => 'application/activity+json',
|
||||
'User-Agent' => "(Pixelfed/{$version}; +{$url})",
|
||||
];
|
||||
}
|
||||
|
||||
public static function fetchFromUrl($url = false)
|
||||
{
|
||||
if(self::validateUrl($url) == false) {
|
||||
if (self::validateUrl($url) == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -253,13 +245,13 @@ class Helpers {
|
|||
$key = "helpers:url:fetcher:sha256-{$hash}";
|
||||
$ttl = now()->addMinutes(15);
|
||||
|
||||
return Cache::remember($key, $ttl, function() use($url) {
|
||||
return Cache::remember($key, $ttl, function () use ($url) {
|
||||
$res = ActivityPubFetchService::get($url);
|
||||
if(!$res || empty($res)) {
|
||||
if (! $res || empty($res)) {
|
||||
return false;
|
||||
}
|
||||
$res = json_decode($res, true, 8);
|
||||
if(json_last_error() == JSON_ERROR_NONE) {
|
||||
if (json_last_error() == JSON_ERROR_NONE) {
|
||||
return $res;
|
||||
} else {
|
||||
return false;
|
||||
|
@ -274,12 +266,12 @@ class Helpers {
|
|||
|
||||
public static function pluckval($val)
|
||||
{
|
||||
if(is_string($val)) {
|
||||
if (is_string($val)) {
|
||||
return $val;
|
||||
}
|
||||
|
||||
if(is_array($val)) {
|
||||
return !empty($val) ? head($val) : null;
|
||||
if (is_array($val)) {
|
||||
return ! empty($val) ? head($val) : null;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -288,51 +280,52 @@ class Helpers {
|
|||
public static function statusFirstOrFetch($url, $replyTo = false)
|
||||
{
|
||||
$url = self::validateUrl($url);
|
||||
if($url == false) {
|
||||
if ($url == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
$local = config('pixelfed.domain.app') == $host ? true : false;
|
||||
|
||||
if($local) {
|
||||
if ($local) {
|
||||
$id = (int) last(explode('/', $url));
|
||||
return Status::whereNotIn('scope', ['draft','archived'])->findOrFail($id);
|
||||
|
||||
return Status::whereNotIn('scope', ['draft', 'archived'])->findOrFail($id);
|
||||
}
|
||||
|
||||
$cached = Status::whereNotIn('scope', ['draft','archived'])
|
||||
$cached = Status::whereNotIn('scope', ['draft', 'archived'])
|
||||
->whereUri($url)
|
||||
->orWhere('object_url', $url)
|
||||
->first();
|
||||
|
||||
if($cached) {
|
||||
if ($cached) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$res = self::fetchFromUrl($url);
|
||||
|
||||
if(!$res || empty($res) || isset($res['error']) || !isset($res['@context']) || !isset($res['published']) ) {
|
||||
if (! $res || empty($res) || isset($res['error']) || ! isset($res['@context']) || ! isset($res['published'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(config('autospam.live_filters.enabled')) {
|
||||
if (config('autospam.live_filters.enabled')) {
|
||||
$filters = config('autospam.live_filters.filters');
|
||||
if(!empty($filters) && isset($res['content']) && !empty($res['content']) && strlen($filters) > 3) {
|
||||
if (! empty($filters) && isset($res['content']) && ! empty($res['content']) && strlen($filters) > 3) {
|
||||
$filters = array_map('trim', explode(',', $filters));
|
||||
$content = $res['content'];
|
||||
foreach($filters as $filter) {
|
||||
foreach ($filters as $filter) {
|
||||
$filter = trim(strtolower($filter));
|
||||
if(!$filter || !strlen($filter)) {
|
||||
if (! $filter || ! strlen($filter)) {
|
||||
continue;
|
||||
}
|
||||
if(str_contains(strtolower($content), $filter)) {
|
||||
if (str_contains(strtolower($content), $filter)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($res['object'])) {
|
||||
if (isset($res['object'])) {
|
||||
$activity = $res;
|
||||
} else {
|
||||
$activity = ['object' => $res];
|
||||
|
@ -342,37 +335,37 @@ class Helpers {
|
|||
|
||||
$cw = isset($res['sensitive']) ? (bool) $res['sensitive'] : false;
|
||||
|
||||
if(isset($res['to']) == true) {
|
||||
if(is_array($res['to']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['to'])) {
|
||||
if (isset($res['to']) == true) {
|
||||
if (is_array($res['to']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['to'])) {
|
||||
$scope = 'public';
|
||||
}
|
||||
if(is_string($res['to']) && 'https://www.w3.org/ns/activitystreams#Public' == $res['to']) {
|
||||
if (is_string($res['to']) && $res['to'] == 'https://www.w3.org/ns/activitystreams#Public') {
|
||||
$scope = 'public';
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($res['cc']) == true) {
|
||||
if(is_array($res['cc']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['cc'])) {
|
||||
if (isset($res['cc']) == true) {
|
||||
if (is_array($res['cc']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['cc'])) {
|
||||
$scope = 'unlisted';
|
||||
}
|
||||
if(is_string($res['cc']) && 'https://www.w3.org/ns/activitystreams#Public' == $res['cc']) {
|
||||
if (is_string($res['cc']) && $res['cc'] == 'https://www.w3.org/ns/activitystreams#Public') {
|
||||
$scope = 'unlisted';
|
||||
}
|
||||
}
|
||||
|
||||
if(config('costar.enabled') == true) {
|
||||
if (config('costar.enabled') == true) {
|
||||
$blockedKeywords = config('costar.keyword.block');
|
||||
if($blockedKeywords !== null) {
|
||||
if ($blockedKeywords !== null) {
|
||||
$keywords = config('costar.keyword.block');
|
||||
foreach($keywords as $kw) {
|
||||
if(Str::contains($res['content'], $kw) == true) {
|
||||
foreach ($keywords as $kw) {
|
||||
if (Str::contains($res['content'], $kw) == true) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$unlisted = config('costar.domain.unlisted');
|
||||
if(in_array(parse_url($url, PHP_URL_HOST), $unlisted) == true) {
|
||||
if (in_array(parse_url($url, PHP_URL_HOST), $unlisted) == true) {
|
||||
$unlisted = true;
|
||||
$scope = 'unlisted';
|
||||
} else {
|
||||
|
@ -380,7 +373,7 @@ class Helpers {
|
|||
}
|
||||
|
||||
$cwDomains = config('costar.domain.cw');
|
||||
if(in_array(parse_url($url, PHP_URL_HOST), $cwDomains) == true) {
|
||||
if (in_array(parse_url($url, PHP_URL_HOST), $cwDomains) == true) {
|
||||
$cw = true;
|
||||
}
|
||||
}
|
||||
|
@ -389,15 +382,15 @@ class Helpers {
|
|||
$idDomain = parse_url($id, PHP_URL_HOST);
|
||||
$urlDomain = parse_url($url, PHP_URL_HOST);
|
||||
|
||||
if($idDomain && $urlDomain && strtolower($idDomain) !== strtolower($urlDomain)) {
|
||||
if ($idDomain && $urlDomain && strtolower($idDomain) !== strtolower($urlDomain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!self::validateUrl($id)) {
|
||||
if (! self::validateUrl($id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!isset($activity['object']['attributedTo'])) {
|
||||
if (! isset($activity['object']['attributedTo'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -405,39 +398,38 @@ class Helpers {
|
|||
$activity['object']['attributedTo'] :
|
||||
(is_array($activity['object']['attributedTo']) ?
|
||||
collect($activity['object']['attributedTo'])
|
||||
->filter(function($o) {
|
||||
->filter(function ($o) {
|
||||
return $o && isset($o['type']) && $o['type'] == 'Person';
|
||||
})
|
||||
->pluck('id')
|
||||
->first() : null
|
||||
);
|
||||
|
||||
if($attributedTo) {
|
||||
if ($attributedTo) {
|
||||
$actorDomain = parse_url($attributedTo, PHP_URL_HOST);
|
||||
if(!self::validateUrl($attributedTo) ||
|
||||
if (! self::validateUrl($attributedTo) ||
|
||||
$idDomain !== $actorDomain ||
|
||||
$actorDomain !== $urlDomain
|
||||
)
|
||||
{
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if($idDomain !== $urlDomain) {
|
||||
if ($idDomain !== $urlDomain) {
|
||||
return;
|
||||
}
|
||||
|
||||
$profile = self::profileFirstOrNew($attributedTo);
|
||||
|
||||
if(!$profile) {
|
||||
if (! $profile) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(isset($activity['object']['inReplyTo']) && !empty($activity['object']['inReplyTo']) || $replyTo == true) {
|
||||
if (isset($activity['object']['inReplyTo']) && ! empty($activity['object']['inReplyTo']) || $replyTo == true) {
|
||||
$reply_to = self::statusFirstOrFetch(self::pluckval($activity['object']['inReplyTo']), false);
|
||||
if($reply_to) {
|
||||
if ($reply_to) {
|
||||
$blocks = UserFilterService::blocks($reply_to->profile_id);
|
||||
if(in_array($profile->id, $blocks)) {
|
||||
if (in_array($profile->id, $blocks)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -447,15 +439,15 @@ class Helpers {
|
|||
}
|
||||
$ts = self::pluckval($res['published']);
|
||||
|
||||
if($scope == 'public' && in_array($urlDomain, InstanceService::getUnlistedDomains())) {
|
||||
if ($scope == 'public' && in_array($urlDomain, InstanceService::getUnlistedDomains())) {
|
||||
$scope = 'unlisted';
|
||||
}
|
||||
|
||||
if(in_array($urlDomain, InstanceService::getNsfwDomains())) {
|
||||
if (in_array($urlDomain, InstanceService::getNsfwDomains())) {
|
||||
$cw = true;
|
||||
}
|
||||
|
||||
if($res['type'] === 'Question') {
|
||||
if ($res['type'] === 'Question') {
|
||||
$status = self::storePoll(
|
||||
$profile,
|
||||
$res,
|
||||
|
@ -466,6 +458,7 @@ class Helpers {
|
|||
$scope,
|
||||
$id
|
||||
);
|
||||
|
||||
return $status;
|
||||
} else {
|
||||
$status = self::storeStatus($url, $profile, $res);
|
||||
|
@ -482,12 +475,12 @@ class Helpers {
|
|||
$idDomain = parse_url($id, PHP_URL_HOST);
|
||||
$urlDomain = parse_url($url, PHP_URL_HOST);
|
||||
$originalUrlDomain = parse_url($originalUrl, PHP_URL_HOST);
|
||||
if(!self::validateUrl($id) || !self::validateUrl($url)) {
|
||||
if (! self::validateUrl($id) || ! self::validateUrl($url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if( strtolower($originalUrlDomain) !== strtolower($idDomain) ||
|
||||
strtolower($originalUrlDomain) !== strtolower($urlDomain) ) {
|
||||
if (strtolower($originalUrlDomain) !== strtolower($idDomain) ||
|
||||
strtolower($originalUrlDomain) !== strtolower($urlDomain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -498,21 +491,21 @@ class Helpers {
|
|||
$cw = self::getSensitive($activity, $url);
|
||||
$pid = is_object($profile) ? $profile->id : (is_array($profile) ? $profile['id'] : null);
|
||||
$isUnlisted = is_object($profile) ? $profile->unlisted : (is_array($profile) ? $profile['unlisted'] : false);
|
||||
$commentsDisabled = isset($activity['commentsEnabled']) ? !boolval($activity['commentsEnabled']) : false;
|
||||
$commentsDisabled = isset($activity['commentsEnabled']) ? ! boolval($activity['commentsEnabled']) : false;
|
||||
|
||||
if(!$pid) {
|
||||
if (! $pid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if($scope == 'public') {
|
||||
if($isUnlisted == true) {
|
||||
if ($scope == 'public') {
|
||||
if ($isUnlisted == true) {
|
||||
$scope = 'unlisted';
|
||||
}
|
||||
}
|
||||
|
||||
$status = Status::updateOrCreate(
|
||||
[
|
||||
'uri' => $url
|
||||
'uri' => $url,
|
||||
], [
|
||||
'profile_id' => $pid,
|
||||
'url' => $url,
|
||||
|
@ -527,24 +520,24 @@ class Helpers {
|
|||
'visibility' => $scope,
|
||||
'cw_summary' => ($cw == true && isset($activity['summary']) ?
|
||||
Purify::clean(strip_tags($activity['summary'])) : null),
|
||||
'comments_disabled' => $commentsDisabled
|
||||
'comments_disabled' => $commentsDisabled,
|
||||
]
|
||||
);
|
||||
|
||||
if($reply_to == null) {
|
||||
if ($reply_to == null) {
|
||||
self::importNoteAttachment($activity, $status);
|
||||
} else {
|
||||
if(isset($activity['attachment']) && !empty($activity['attachment'])) {
|
||||
if (isset($activity['attachment']) && ! empty($activity['attachment'])) {
|
||||
self::importNoteAttachment($activity, $status);
|
||||
}
|
||||
StatusReplyPipeline::dispatch($status);
|
||||
}
|
||||
|
||||
if(isset($activity['tag']) && is_array($activity['tag']) && !empty($activity['tag'])) {
|
||||
if (isset($activity['tag']) && is_array($activity['tag']) && ! empty($activity['tag'])) {
|
||||
StatusTagsPipeline::dispatch($activity, $status);
|
||||
}
|
||||
|
||||
if( config('instance.timeline.network.cached') &&
|
||||
if (config('instance.timeline.network.cached') &&
|
||||
$status->in_reply_to_id === null &&
|
||||
$status->reblog_of_id === null &&
|
||||
in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) &&
|
||||
|
@ -556,8 +549,8 @@ class Helpers {
|
|||
->unique()
|
||||
->values()
|
||||
->toArray();
|
||||
if(!in_array($urlDomain, $filteredDomains)) {
|
||||
if(!$isUnlisted) {
|
||||
if (! in_array($urlDomain, $filteredDomains)) {
|
||||
if (! $isUnlisted) {
|
||||
NetworkTimelineService::add($status->id);
|
||||
}
|
||||
}
|
||||
|
@ -565,7 +558,7 @@ class Helpers {
|
|||
|
||||
AccountStatService::incrementPostCount($pid);
|
||||
|
||||
if( $status->in_reply_to_id === null &&
|
||||
if ($status->in_reply_to_id === null &&
|
||||
in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
|
||||
) {
|
||||
FeedInsertRemotePipeline::dispatch($status->id, $pid)->onQueue('feed');
|
||||
|
@ -576,14 +569,14 @@ class Helpers {
|
|||
|
||||
public static function getSensitive($activity, $url)
|
||||
{
|
||||
if(!$url || !strlen($url)) {
|
||||
if (! $url || ! strlen($url)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$urlDomain = parse_url($url, PHP_URL_HOST);
|
||||
$cw = isset($activity['sensitive']) ? (bool) $activity['sensitive'] : false;
|
||||
|
||||
if(in_array($urlDomain, InstanceService::getNsfwDomains())) {
|
||||
if (in_array($urlDomain, InstanceService::getNsfwDomains())) {
|
||||
$cw = true;
|
||||
}
|
||||
|
||||
|
@ -593,13 +586,13 @@ class Helpers {
|
|||
public static function getReplyTo($activity)
|
||||
{
|
||||
$reply_to = null;
|
||||
$inReplyTo = isset($activity['inReplyTo']) && !empty($activity['inReplyTo']) ?
|
||||
$inReplyTo = isset($activity['inReplyTo']) && ! empty($activity['inReplyTo']) ?
|
||||
self::pluckval($activity['inReplyTo']) :
|
||||
false;
|
||||
|
||||
if($inReplyTo) {
|
||||
if ($inReplyTo) {
|
||||
$reply_to = self::statusFirstOrFetch($inReplyTo);
|
||||
if($reply_to) {
|
||||
if ($reply_to) {
|
||||
$reply_to = optional($reply_to)->id;
|
||||
}
|
||||
} else {
|
||||
|
@ -616,25 +609,25 @@ class Helpers {
|
|||
$urlDomain = parse_url(self::pluckval($url), PHP_URL_HOST);
|
||||
$scope = 'private';
|
||||
|
||||
if(isset($activity['to']) == true) {
|
||||
if(is_array($activity['to']) && in_array('https://www.w3.org/ns/activitystreams#Public', $activity['to'])) {
|
||||
if (isset($activity['to']) == true) {
|
||||
if (is_array($activity['to']) && in_array('https://www.w3.org/ns/activitystreams#Public', $activity['to'])) {
|
||||
$scope = 'public';
|
||||
}
|
||||
if(is_string($activity['to']) && 'https://www.w3.org/ns/activitystreams#Public' == $activity['to']) {
|
||||
if (is_string($activity['to']) && $activity['to'] == 'https://www.w3.org/ns/activitystreams#Public') {
|
||||
$scope = 'public';
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($activity['cc']) == true) {
|
||||
if(is_array($activity['cc']) && in_array('https://www.w3.org/ns/activitystreams#Public', $activity['cc'])) {
|
||||
if (isset($activity['cc']) == true) {
|
||||
if (is_array($activity['cc']) && in_array('https://www.w3.org/ns/activitystreams#Public', $activity['cc'])) {
|
||||
$scope = 'unlisted';
|
||||
}
|
||||
if(is_string($activity['cc']) && 'https://www.w3.org/ns/activitystreams#Public' == $activity['cc']) {
|
||||
if (is_string($activity['cc']) && $activity['cc'] == 'https://www.w3.org/ns/activitystreams#Public') {
|
||||
$scope = 'unlisted';
|
||||
}
|
||||
}
|
||||
|
||||
if($scope == 'public' && in_array($urlDomain, InstanceService::getUnlistedDomains())) {
|
||||
if ($scope == 'public' && in_array($urlDomain, InstanceService::getUnlistedDomains())) {
|
||||
$scope = 'unlisted';
|
||||
}
|
||||
|
||||
|
@ -643,15 +636,15 @@ class Helpers {
|
|||
|
||||
private static function storePoll($profile, $res, $url, $ts, $reply_to, $cw, $scope, $id)
|
||||
{
|
||||
if(!isset($res['endTime']) || !isset($res['oneOf']) || !is_array($res['oneOf']) || count($res['oneOf']) > 4) {
|
||||
if (! isset($res['endTime']) || ! isset($res['oneOf']) || ! is_array($res['oneOf']) || count($res['oneOf']) > 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
$options = collect($res['oneOf'])->map(function($option) {
|
||||
$options = collect($res['oneOf'])->map(function ($option) {
|
||||
return $option['name'];
|
||||
})->toArray();
|
||||
|
||||
$cachedTallies = collect($res['oneOf'])->map(function($option) {
|
||||
$cachedTallies = collect($res['oneOf'])->map(function ($option) {
|
||||
return $option['replies']['totalItems'] ?? 0;
|
||||
})->toArray();
|
||||
|
||||
|
@ -697,9 +690,10 @@ class Helpers {
|
|||
|
||||
public static function importNoteAttachment($data, Status $status)
|
||||
{
|
||||
if(self::verifyAttachments($data) == false) {
|
||||
if (self::verifyAttachments($data) == false) {
|
||||
// \Log::info('importNoteAttachment::failedVerification.', [$data['id']]);
|
||||
$status->viewType();
|
||||
|
||||
return;
|
||||
}
|
||||
$attachments = isset($data['object']) ? $data['object']['attachment'] : $data['attachment'];
|
||||
|
@ -712,11 +706,11 @@ class Helpers {
|
|||
$storagePath = MediaPathService::get($user, 2);
|
||||
$allowed = explode(',', config_cache('pixelfed.media_types'));
|
||||
|
||||
foreach($attachments as $key => $media) {
|
||||
foreach ($attachments as $key => $media) {
|
||||
$type = $media['mediaType'];
|
||||
$url = $media['url'];
|
||||
$valid = self::validateUrl($url);
|
||||
if(in_array($type, $allowed) == false || $valid == false) {
|
||||
if (in_array($type, $allowed) == false || $valid == false) {
|
||||
continue;
|
||||
}
|
||||
$blurhash = isset($media['blurhash']) ? $media['blurhash'] : null;
|
||||
|
@ -735,50 +729,52 @@ class Helpers {
|
|||
$media->remote_url = $url;
|
||||
$media->caption = $caption;
|
||||
$media->order = $key + 1;
|
||||
if($width) {
|
||||
if ($width) {
|
||||
$media->width = $width;
|
||||
}
|
||||
if($height) {
|
||||
if ($height) {
|
||||
$media->height = $height;
|
||||
}
|
||||
if($license) {
|
||||
if ($license) {
|
||||
$media->license = $license;
|
||||
}
|
||||
$media->mime = $type;
|
||||
$media->version = 3;
|
||||
$media->save();
|
||||
|
||||
if(config_cache('pixelfed.cloud_storage') == true) {
|
||||
if ((bool) config_cache('pixelfed.cloud_storage') == true) {
|
||||
MediaStoragePipeline::dispatch($media);
|
||||
}
|
||||
}
|
||||
|
||||
$status->viewType();
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
public static function profileFirstOrNew($url)
|
||||
{
|
||||
$url = self::validateUrl($url);
|
||||
if($url == false) {
|
||||
if ($url == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
$local = config('pixelfed.domain.app') == $host ? true : false;
|
||||
|
||||
if($local == true) {
|
||||
if ($local == true) {
|
||||
$id = last(explode('/', $url));
|
||||
|
||||
return Profile::whereNull('status')
|
||||
->whereNull('domain')
|
||||
->whereUsername($id)
|
||||
->firstOrFail();
|
||||
}
|
||||
|
||||
if($profile = Profile::whereRemoteUrl($url)->first()) {
|
||||
if($profile->last_fetched_at && $profile->last_fetched_at->lt(now()->subHours(24))) {
|
||||
if ($profile = Profile::whereRemoteUrl($url)->first()) {
|
||||
if ($profile->last_fetched_at && $profile->last_fetched_at->lt(now()->subHours(24))) {
|
||||
return self::profileUpdateOrCreate($url);
|
||||
}
|
||||
|
||||
return $profile;
|
||||
}
|
||||
|
||||
|
@ -788,42 +784,42 @@ class Helpers {
|
|||
public static function profileUpdateOrCreate($url)
|
||||
{
|
||||
$res = self::fetchProfileFromUrl($url);
|
||||
if(!$res || isset($res['id']) == false) {
|
||||
if (! $res || isset($res['id']) == false) {
|
||||
return;
|
||||
}
|
||||
$urlDomain = parse_url($url, PHP_URL_HOST);
|
||||
$domain = parse_url($res['id'], PHP_URL_HOST);
|
||||
if(strtolower($urlDomain) !== strtolower($domain)) {
|
||||
if (strtolower($urlDomain) !== strtolower($domain)) {
|
||||
return;
|
||||
}
|
||||
if(!isset($res['preferredUsername']) && !isset($res['nickname'])) {
|
||||
if (! isset($res['preferredUsername']) && ! isset($res['nickname'])) {
|
||||
return;
|
||||
}
|
||||
// skip invalid usernames
|
||||
if(!ctype_alnum($res['preferredUsername'])) {
|
||||
if (! ctype_alnum($res['preferredUsername'])) {
|
||||
$tmpUsername = str_replace(['_', '.', '-'], '', $res['preferredUsername']);
|
||||
if(!ctype_alnum($tmpUsername)) {
|
||||
if (! ctype_alnum($tmpUsername)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$username = (string) Purify::clean($res['preferredUsername'] ?? $res['nickname']);
|
||||
if(empty($username)) {
|
||||
if (empty($username)) {
|
||||
return;
|
||||
}
|
||||
$remoteUsername = $username;
|
||||
$webfinger = "@{$username}@{$domain}";
|
||||
|
||||
if(!self::validateUrl($res['inbox'])) {
|
||||
if (! self::validateUrl($res['inbox'])) {
|
||||
return;
|
||||
}
|
||||
if(!self::validateUrl($res['id'])) {
|
||||
if (! self::validateUrl($res['id'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$instance = Instance::updateOrCreate([
|
||||
'domain' => $domain
|
||||
'domain' => $domain,
|
||||
]);
|
||||
if($instance->wasRecentlyCreated == true) {
|
||||
if ($instance->wasRecentlyCreated == true) {
|
||||
\App\Jobs\InstancePipeline\FetchNodeinfoPipeline::dispatch($instance)->onQueue('low');
|
||||
}
|
||||
|
||||
|
@ -846,13 +842,14 @@ class Helpers {
|
|||
]
|
||||
);
|
||||
|
||||
if( $profile->last_fetched_at == null ||
|
||||
if ($profile->last_fetched_at == null ||
|
||||
$profile->last_fetched_at->lt(now()->subMonths(3))
|
||||
) {
|
||||
RemoteAvatarFetch::dispatch($profile);
|
||||
}
|
||||
$profile->last_fetched_at = now();
|
||||
$profile->save();
|
||||
|
||||
return $profile;
|
||||
}
|
||||
|
||||
|
@ -863,7 +860,7 @@ class Helpers {
|
|||
|
||||
public static function sendSignedObject($profile, $url, $body)
|
||||
{
|
||||
if(app()->environment() !== 'production') {
|
||||
if (app()->environment() !== 'production') {
|
||||
return;
|
||||
}
|
||||
ActivityPubDeliveryService::queue()
|
||||
|
|
|
@ -2,34 +2,32 @@
|
|||
|
||||
namespace App\Util\ActivityPub;
|
||||
|
||||
use App\Profile;
|
||||
use App\Status;
|
||||
use League\Fractal;
|
||||
use App\Http\Controllers\ProfileController;
|
||||
use App\Transformer\ActivityPub\ProfileOutbox;
|
||||
use App\Status;
|
||||
use App\Transformer\ActivityPub\Verb\CreateNote;
|
||||
use League\Fractal;
|
||||
|
||||
class Outbox {
|
||||
class Outbox
|
||||
{
|
||||
public static function get($profile)
|
||||
{
|
||||
abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(! config('federation.activitypub.outbox'), 404);
|
||||
|
||||
public static function get($profile)
|
||||
{
|
||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
||||
abort_if(!config('federation.activitypub.outbox'), 404);
|
||||
|
||||
if($profile->status != null) {
|
||||
if ($profile->status != null) {
|
||||
return ProfileController::accountCheck($profile);
|
||||
}
|
||||
|
||||
if($profile->is_private) {
|
||||
return ['error'=>'403', 'msg' => 'private profile'];
|
||||
if ($profile->is_private) {
|
||||
return ['error' => '403', 'msg' => 'private profile'];
|
||||
}
|
||||
|
||||
$timeline = $profile
|
||||
->statuses()
|
||||
->whereScope('public')
|
||||
->orderBy('created_at', 'desc')
|
||||
->take(10)
|
||||
->get();
|
||||
->statuses()
|
||||
->whereScope('public')
|
||||
->orderBy('created_at', 'desc')
|
||||
->take(10)
|
||||
->get();
|
||||
|
||||
$count = Status::whereProfileId($profile->id)->count();
|
||||
|
||||
|
@ -38,14 +36,14 @@ class Outbox {
|
|||
$res = $fractal->createData($resource)->toArray();
|
||||
|
||||
$outbox = [
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'_debug' => 'Outbox only supports latest 10 objects, pagination is not supported',
|
||||
'id' => $profile->permalink('/outbox'),
|
||||
'type' => 'OrderedCollection',
|
||||
'totalItems' => $count,
|
||||
'orderedItems' => $res['data']
|
||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||
'_debug' => 'Outbox only supports latest 10 objects, pagination is not supported',
|
||||
'id' => $profile->permalink('/outbox'),
|
||||
'type' => 'OrderedCollection',
|
||||
'totalItems' => $count,
|
||||
'orderedItems' => $res['data'],
|
||||
];
|
||||
return $outbox;
|
||||
}
|
||||
|
||||
return $outbox;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,16 +30,16 @@ class Config
|
|||
'version' => config('pixelfed.version'),
|
||||
'open_registration' => (bool) config_cache('pixelfed.open_registration'),
|
||||
'uploader' => [
|
||||
'max_photo_size' => (int) config('pixelfed.max_photo_size'),
|
||||
'max_photo_size' => (int) config_cache('pixelfed.max_photo_size'),
|
||||
'max_caption_length' => (int) config_cache('pixelfed.max_caption_length'),
|
||||
'max_altext_length' => (int) config_cache('pixelfed.max_altext_length', 150),
|
||||
'album_limit' => (int) config_cache('pixelfed.max_album_length'),
|
||||
'image_quality' => (int) config_cache('pixelfed.image_quality'),
|
||||
|
||||
'max_collection_length' => (int) config('pixelfed.max_collection_length', 18),
|
||||
'max_collection_length' => (int) config_cache('pixelfed.max_collection_length', 18),
|
||||
|
||||
'optimize_image' => (bool) config('pixelfed.optimize_image'),
|
||||
'optimize_video' => (bool) config('pixelfed.optimize_video'),
|
||||
'optimize_image' => (bool) config_cache('pixelfed.optimize_image'),
|
||||
'optimize_video' => (bool) config_cache('pixelfed.optimize_video'),
|
||||
|
||||
'media_types' => config_cache('pixelfed.media_types'),
|
||||
'mime_types' => config_cache('pixelfed.media_types') ? explode(',', config_cache('pixelfed.media_types')) : [],
|
||||
|
|
|
@ -19,7 +19,7 @@ return new class extends Migration
|
|||
public function up()
|
||||
{
|
||||
ini_set('memory_limit', '-1');
|
||||
if(config_cache('pixelfed.cloud_storage') == false) {
|
||||
if((bool) config_cache('pixelfed.cloud_storage') == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
|||
(()=>{"use strict";var e,r,n,o={},t={};function a(e){var r=t[e];if(void 0!==r)return r.exports;var n=t[e]={id:e,loaded:!1,exports:{}};return o[e].call(n.exports,n,n.exports,a),n.loaded=!0,n.exports}a.m=o,e=[],a.O=(r,n,o,t)=>{if(!n){var c=1/0;for(l=0;l<e.length;l++){for(var[n,o,t]=e[l],i=!0,d=0;d<n.length;d++)(!1&t||c>=t)&&Object.keys(a.O).every((e=>a.O[e](n[d])))?n.splice(d--,1):(i=!1,t<c&&(c=t));if(i){e.splice(l--,1);var s=o();void 0!==s&&(r=s)}}return r}t=t||0;for(var l=e.length;l>0&&e[l-1][2]>t;l--)e[l]=e[l-1];e[l]=[n,o,t]},a.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return a.d(r,{a:r}),r},a.d=(e,r)=>{for(var n in r)a.o(r,n)&&!a.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},a.f={},a.e=e=>Promise.all(Object.keys(a.f).reduce(((r,n)=>(a.f[n](e,r),r)),[])),a.u=e=>"js/"+{1179:"daci.chunk",1240:"discover~myhashtags.chunk",1645:"profile~following.bundle",2156:"dms.chunk",2966:"discover~hashtag.bundle",3688:"discover~serverfeed.chunk",4951:"home.chunk",6250:"discover~settings.chunk",6535:"discover.chunk",6740:"discover~memories.chunk",7399:"dms~message.chunk",7413:"error404.bundle",7521:"discover~findfriends.chunk",7744:"notifications.chunk",8087:"profile.chunk",8119:"i18n.bundle",8408:"post.chunk",8977:"profile~followers.bundle",9124:"compose.chunk",9919:"changelog.bundle"}[e]+"."+{1179:"34dc7bad3a0792cc",1240:"8886fc0d4736d819",1645:"7ca7cfa5aaae75e2",2156:"2b55effc0e8ba89f",2966:"a0f00fc7df1f313c",3688:"262bf7e3bce843c3",4951:"264eeb47bfac56c1",6250:"65d6f3cbe5323ed4",6535:"c2229e1d15bd3ada",6740:"37e0c325f900e163",7399:"976f7edaa6f71137",7413:"b397483e3991ab20",7521:"b1858bea66d9723b",7744:"0c5151643e4534aa",8087:"f74967e7910990ca",8119:"93a02e275ac1a708",8408:"9184101a2b809af1",8977:"5d796e79f32d066c",9124:"a0cfdf07f5062445",9919:"bf44edbbfa14bd53"}[e]+".js",a.miniCssF=e=>({2305:"css/portfolio",2540:"css/landing",3364:"css/admin",6952:"css/appdark",8252:"css/app",8759:"css/spa"}[e]+".css"),a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),a.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},n="pixelfed:",a.l=(e,o,t,c)=>{if(r[e])r[e].push(o);else{var i,d;if(void 0!==t)for(var s=document.getElementsByTagName("script"),l=0;l<s.length;l++){var f=s[l];if(f.getAttribute("src")==e||f.getAttribute("data-webpack")==n+t){i=f;break}}i||(d=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.setAttribute("data-webpack",n+t),i.src=e),r[e]=[o];var u=(n,o)=>{i.onerror=i.onload=null,clearTimeout(b);var t=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),t&&t.forEach((e=>e(o))),n)return n(o)},b=setTimeout(u.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=u.bind(null,i.onerror),i.onload=u.bind(null,i.onload),d&&document.head.appendChild(i)}},a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),a.p="/",(()=>{var e={461:0,6952:0,8252:0,2305:0,3364:0,2540:0,8759:0};a.f.j=(r,n)=>{var o=a.o(e,r)?e[r]:void 0;if(0!==o)if(o)n.push(o[2]);else if(/^((69|82)52|2305|2540|3364|461|8759)$/.test(r))e[r]=0;else{var t=new Promise(((n,t)=>o=e[r]=[n,t]));n.push(o[2]=t);var c=a.p+a.u(r),i=new Error;a.l(c,(n=>{if(a.o(e,r)&&(0!==(o=e[r])&&(e[r]=void 0),o)){var t=n&&("load"===n.type?"missing":n.type),c=n&&n.target&&n.target.src;i.message="Loading chunk "+r+" failed.\n("+t+": "+c+")",i.name="ChunkLoadError",i.type=t,i.request=c,o[1](i)}}),"chunk-"+r,r)}},a.O.j=r=>0===e[r];var r=(r,n)=>{var o,t,[c,i,d]=n,s=0;if(c.some((r=>0!==e[r]))){for(o in i)a.o(i,o)&&(a.m[o]=i[o]);if(d)var l=d(a)}for(r&&r(n);s<c.length;s++)t=c[s],a.o(e,t)&&e[t]&&e[t][0](),e[t]=0;return a.O(l)},n=self.webpackChunkpixelfed=self.webpackChunkpixelfed||[];n.forEach(r.bind(null,0)),n.push=r.bind(null,n.push.bind(n))})(),a.nc=void 0})();
|
||||
(()=>{"use strict";var e,r,n,o={},t={};function a(e){var r=t[e];if(void 0!==r)return r.exports;var n=t[e]={id:e,loaded:!1,exports:{}};return o[e].call(n.exports,n,n.exports,a),n.loaded=!0,n.exports}a.m=o,e=[],a.O=(r,n,o,t)=>{if(!n){var d=1/0;for(l=0;l<e.length;l++){for(var[n,o,t]=e[l],i=!0,c=0;c<n.length;c++)(!1&t||d>=t)&&Object.keys(a.O).every((e=>a.O[e](n[c])))?n.splice(c--,1):(i=!1,t<d&&(d=t));if(i){e.splice(l--,1);var s=o();void 0!==s&&(r=s)}}return r}t=t||0;for(var l=e.length;l>0&&e[l-1][2]>t;l--)e[l]=e[l-1];e[l]=[n,o,t]},a.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return a.d(r,{a:r}),r},a.d=(e,r)=>{for(var n in r)a.o(r,n)&&!a.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},a.f={},a.e=e=>Promise.all(Object.keys(a.f).reduce(((r,n)=>(a.f[n](e,r),r)),[])),a.u=e=>"js/"+{1179:"daci.chunk",1240:"discover~myhashtags.chunk",1645:"profile~following.bundle",2156:"dms.chunk",2966:"discover~hashtag.bundle",3688:"discover~serverfeed.chunk",4951:"home.chunk",6250:"discover~settings.chunk",6535:"discover.chunk",6740:"discover~memories.chunk",7399:"dms~message.chunk",7413:"error404.bundle",7521:"discover~findfriends.chunk",7744:"notifications.chunk",8087:"profile.chunk",8119:"i18n.bundle",8408:"post.chunk",8977:"profile~followers.bundle",9124:"compose.chunk",9919:"changelog.bundle"}[e]+"."+{1179:"34dc7bad3a0792cc",1240:"8886fc0d4736d819",1645:"7ca7cfa5aaae75e2",2156:"2b55effc0e8ba89f",2966:"a0f00fc7df1f313c",3688:"262bf7e3bce843c3",4951:"264eeb47bfac56c1",6250:"65d6f3cbe5323ed4",6535:"c2229e1d15bd3ada",6740:"37e0c325f900e163",7399:"976f7edaa6f71137",7413:"b397483e3991ab20",7521:"b1858bea66d9723b",7744:"0c5151643e4534aa",8087:"a2234f891ba86efd",8119:"93a02e275ac1a708",8408:"9184101a2b809af1",8977:"5d796e79f32d066c",9124:"a0cfdf07f5062445",9919:"bf44edbbfa14bd53"}[e]+".js",a.miniCssF=e=>({2305:"css/portfolio",2540:"css/landing",3364:"css/admin",6952:"css/appdark",8252:"css/app",8759:"css/spa"}[e]+".css"),a.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),a.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},n="pixelfed:",a.l=(e,o,t,d)=>{if(r[e])r[e].push(o);else{var i,c;if(void 0!==t)for(var s=document.getElementsByTagName("script"),l=0;l<s.length;l++){var f=s[l];if(f.getAttribute("src")==e||f.getAttribute("data-webpack")==n+t){i=f;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.setAttribute("data-webpack",n+t),i.src=e),r[e]=[o];var u=(n,o)=>{i.onerror=i.onload=null,clearTimeout(b);var t=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),t&&t.forEach((e=>e(o))),n)return n(o)},b=setTimeout(u.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=u.bind(null,i.onerror),i.onload=u.bind(null,i.onload),c&&document.head.appendChild(i)}},a.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),a.p="/",(()=>{var e={461:0,6952:0,8252:0,2305:0,3364:0,2540:0,8759:0};a.f.j=(r,n)=>{var o=a.o(e,r)?e[r]:void 0;if(0!==o)if(o)n.push(o[2]);else if(/^((69|82)52|2305|2540|3364|461|8759)$/.test(r))e[r]=0;else{var t=new Promise(((n,t)=>o=e[r]=[n,t]));n.push(o[2]=t);var d=a.p+a.u(r),i=new Error;a.l(d,(n=>{if(a.o(e,r)&&(0!==(o=e[r])&&(e[r]=void 0),o)){var t=n&&("load"===n.type?"missing":n.type),d=n&&n.target&&n.target.src;i.message="Loading chunk "+r+" failed.\n("+t+": "+d+")",i.name="ChunkLoadError",i.type=t,i.request=d,o[1](i)}}),"chunk-"+r,r)}},a.O.j=r=>0===e[r];var r=(r,n)=>{var o,t,[d,i,c]=n,s=0;if(d.some((r=>0!==e[r]))){for(o in i)a.o(i,o)&&(a.m[o]=i[o]);if(c)var l=c(a)}for(r&&r(n);s<d.length;s++)t=d[s],a.o(e,t)&&e[t]&&e[t][0](),e[t]=0;return a.O(l)},n=self.webpackChunkpixelfed=self.webpackChunkpixelfed||[];n.forEach(r.bind(null,0)),n.push=r.bind(null,n.push.bind(n))})(),a.nc=void 0})();
|
File diff suppressed because one or more lines are too long
|
@ -16,7 +16,7 @@
|
|||
"/js/profile-directory.js": "/js/profile-directory.js?id=1615064235d2acf08d84c3e3d1232d7e",
|
||||
"/js/story-compose.js": "/js/story-compose.js?id=50d723634d8d22db14d630a02774e5b7",
|
||||
"/js/direct.js": "/js/direct.js?id=2f7df211df1b62a0637ed87f2457e918",
|
||||
"/js/admin.js": "/js/admin.js?id=90ebc22c8ae78692cfb2c3e5dd7f6c5a",
|
||||
"/js/admin.js": "/js/admin.js?id=6b704e46b54b8ec1b0600ff50b33f7b9",
|
||||
"/js/spa.js": "/js/spa.js?id=cd6a07d612c09f17c34a6e8441768020",
|
||||
"/js/stories.js": "/js/stories.js?id=f3d502fa937e5fa90d173d5d7aa64e2c",
|
||||
"/js/portfolio.js": "/js/portfolio.js?id=e8a1f57ef2c7c9ff40265502da5b84ac",
|
||||
|
@ -24,11 +24,11 @@
|
|||
"/js/admin_invite.js": "/js/admin_invite.js?id=0a0036f59cfb186f7698207ae432365b",
|
||||
"/js/landing.js": "/js/landing.js?id=753a52aacb8bb884f50ed3ae9ed99a38",
|
||||
"/js/remote_auth.js": "/js/remote_auth.js?id=37e5bdf3bc1896eee063db7a186b9876",
|
||||
"/js/manifest.js": "/js/manifest.js?id=7488e498ae41ee645b354823d5ee96be",
|
||||
"/js/manifest.js": "/js/manifest.js?id=1aecad6992ea1cdb8054608c3604a5cc",
|
||||
"/js/home.chunk.264eeb47bfac56c1.js": "/js/home.chunk.264eeb47bfac56c1.js?id=c5704eda3f241103f1ed1fa6fa4cefad",
|
||||
"/js/compose.chunk.a0cfdf07f5062445.js": "/js/compose.chunk.a0cfdf07f5062445.js?id=71f4bcf44739473ee369521ea785f63e",
|
||||
"/js/post.chunk.9184101a2b809af1.js": "/js/post.chunk.9184101a2b809af1.js?id=b24c0c9949fba5b3e76cb95ea8bb6e5a",
|
||||
"/js/profile.chunk.f74967e7910990ca.js": "/js/profile.chunk.f74967e7910990ca.js?id=9b7dc3907736376f18db109b9d70c0fa",
|
||||
"/js/profile.chunk.a2234f891ba86efd.js": "/js/profile.chunk.a2234f891ba86efd.js?id=2c9f83cb28d3893a169200c8341dbbb1",
|
||||
"/js/discover~memories.chunk.37e0c325f900e163.js": "/js/discover~memories.chunk.37e0c325f900e163.js?id=02137ba179f0f2f3819597f262f423b8",
|
||||
"/js/discover~myhashtags.chunk.8886fc0d4736d819.js": "/js/discover~myhashtags.chunk.8886fc0d4736d819.js?id=0397f095e24b2bbdffd84be14bb9d8c4",
|
||||
"/js/daci.chunk.34dc7bad3a0792cc.js": "/js/daci.chunk.34dc7bad3a0792cc.js?id=53da4c2b40ecc1164592bd0f66767284",
|
||||
|
@ -48,8 +48,8 @@
|
|||
"/css/appdark.css": "/css/appdark.css?id=7f9ba0a926020571e9c8fbedd2ec6a6f",
|
||||
"/css/app.css": "/css/app.css?id=838b7d90a81e16b8a9adc8644237606a",
|
||||
"/css/portfolio.css": "/css/portfolio.css?id=d98e354f173c6a8b729626384dceaa90",
|
||||
"/css/admin.css": "/css/admin.css?id=0a66549bf79b75a0ca8cb83d11a4e2f4",
|
||||
"/css/admin.css": "/css/admin.css?id=20cdb9cce61b0e1bd9fb1aad30efcd2f",
|
||||
"/css/landing.css": "/css/landing.css?id=589f3fa192867727925921b0f68ce022",
|
||||
"/css/spa.css": "/css/spa.css?id=1bdfa6eb676f51cb5931729abfa6dfd8",
|
||||
"/css/spa.css": "/css/spa.css?id=24b025007b418a7e7efd18d47fa56b85",
|
||||
"/js/vendor.js": "/js/vendor.js?id=9e8c3caac2c4d0119e99070a0bb36dfc"
|
||||
}
|
||||
|
|
|
@ -202,16 +202,12 @@
|
|||
if(res.data && res.data.length) {
|
||||
this.feed = res.data;
|
||||
this.maxId = res.data[res.data.length - 1].id;
|
||||
return true;
|
||||
this.canLoadMore = true;
|
||||
} else {
|
||||
this.feedLoaded = true;
|
||||
this.isLoaded = true;
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
this.canLoadMore = res;
|
||||
})
|
||||
.finally(() => {
|
||||
this.feedLoaded = true;
|
||||
this.isLoaded = true;
|
||||
|
@ -242,14 +238,11 @@
|
|||
if(res.data && res.data.length) {
|
||||
this.feed.push(...res.data);
|
||||
this.maxId = res.data[res.data.length - 1].id;
|
||||
return true;
|
||||
this.canLoadMore = true;
|
||||
} else {
|
||||
return false;
|
||||
this.canLoadMore = false;
|
||||
}
|
||||
})
|
||||
.then(res => {
|
||||
this.canLoadMore = res;
|
||||
})
|
||||
.finally(() => {
|
||||
this.isIntersecting = false;
|
||||
})
|
||||
|
|
|
@ -251,10 +251,11 @@
|
|||
</div>
|
||||
<div>
|
||||
<b-button
|
||||
variant="secondary"
|
||||
@click="showInstanceModal = false"
|
||||
variant="link-dark"
|
||||
size="sm"
|
||||
@click="onViewMoreInstance"
|
||||
>
|
||||
Close
|
||||
View More
|
||||
</b-button>
|
||||
<b-button
|
||||
variant="primary"
|
||||
|
@ -885,8 +886,12 @@
|
|||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onViewMoreInstance() {
|
||||
this.showInstanceModal = false;
|
||||
window.location.href = '/i/admin/instances/show/' + this.instanceModal.id
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-0" :style="{ 'font-size':`${fontSize}px` }">{{ contentText }}</div>
|
||||
<p class="mb-0"><a v-if="canStepExpand || (canExpand && !expanded)" class="font-weight-bold small" href="#" @click="expand()">Read more</a></p>
|
||||
<p class="mb-0"><a v-if="canStepExpand || (canExpand && !expanded)" class="font-weight-bold small" href="#" @click.prevent="expand()">Read more</a></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div class="card shadow-none border card-body">
|
||||
<div class="form-group mb-0">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" :name="elementId" class="custom-control-input" :id="elementId" :checked="value" @change="$emit('change', !value)">
|
||||
<label class="custom-control-label font-weight-bold" :for="elementId">{{ name }}</label>
|
||||
</div>
|
||||
<p class="mt-1 mb-0 small text-muted" v-html="description"></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String
|
||||
},
|
||||
|
||||
value: {
|
||||
type: Boolean
|
||||
},
|
||||
|
||||
description: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
elementId: {
|
||||
get() {
|
||||
let name = this.name;
|
||||
name = name.toLowerCase();
|
||||
name = name.replace(/[^a-z0-9 -]/g, ' ');
|
||||
name = name.replace(/\s+/g, '-');
|
||||
name = name.replace(/^-+|-+$/g, '');
|
||||
return 'fec_' + name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<div :class="[ isCard ? 'card shadow-none border card-body' : '' ]">
|
||||
<div
|
||||
class="form-group"
|
||||
:class="[ isInline ? 'd-flex align-items-center gap-1' : 'mb-1' ]">
|
||||
<label :for="elementId" class="font-weight-bold mb-0">{{ name }}</label>
|
||||
<input
|
||||
:id="elementId"
|
||||
class="form-control form-control-muted mb-0"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
@input="$emit('change', $event.target.value)"
|
||||
:disabled="isDisabled" />
|
||||
</div>
|
||||
<p v-if="description && description.length" class="help-text small text-muted mb-0" v-html="description">
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
name: {
|
||||
type: String
|
||||
},
|
||||
|
||||
value: {
|
||||
type: String
|
||||
},
|
||||
|
||||
placeholder: {
|
||||
type: String
|
||||
},
|
||||
|
||||
description: {
|
||||
type: String
|
||||
},
|
||||
|
||||
isCard: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
|
||||
isInline: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
|
||||
isDisabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
elementId: {
|
||||
get() {
|
||||
let name = this.name;
|
||||
name = name.toLowerCase();
|
||||
name = name.replace(/[^a-z0-9 -]/g, ' ');
|
||||
name = name.replace(/\s+/g, '-');
|
||||
name = name.replace(/^-+|-+$/g, '');
|
||||
return 'fec_' + name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped="true">
|
||||
.gap-1 {
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,63 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div style="width:100px;"></div>
|
||||
<div>
|
||||
<h2 class="display-4 mb-0" style="font-weight: 800;">{{ title }}</h2>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-primary rounded-pill font-weight-bold px-5"
|
||||
:disabled="isSaving || saved"
|
||||
@click.prevent="save">
|
||||
<template v-if="isSaving === true"><b-spinner small class="mx-2" /></template>
|
||||
<template v-else>{{ buttonLabel }}</template>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="mt-3">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String
|
||||
},
|
||||
saving: {
|
||||
type: Boolean
|
||||
},
|
||||
saved: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
buttonLabel: {
|
||||
get() {
|
||||
if(this.saved) {
|
||||
return 'Saved';
|
||||
}
|
||||
if(this.saving) {
|
||||
return 'Saving';
|
||||
}
|
||||
|
||||
return 'Save';
|
||||
}
|
||||
},
|
||||
isSaving: {
|
||||
get() {
|
||||
return this.saving;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
save($event) {
|
||||
$event.currentTarget?.blur();
|
||||
this.$emit('save');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -36,11 +36,21 @@ Vue.component(
|
|||
require('./../components/admin/AdminReports.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'admin-settings',
|
||||
require('./../components/admin/AdminSettings.vue').default
|
||||
);
|
||||
|
||||
Vue.component(
|
||||
'instances-component',
|
||||
require('./../components/admin/AdminInstances.vue').default
|
||||
);
|
||||
|
||||
// Vue.component(
|
||||
// 'instance-details-component',
|
||||
// require('./../components/admin/AdminInstanceDetails.vue').default
|
||||
// );
|
||||
|
||||
Vue.component(
|
||||
'hashtag-component',
|
||||
require('./../components/admin/AdminHashtags.vue').default
|
||||
|
|
|
@ -10,6 +10,7 @@ License - nucleoapp.com/license/
|
|||
src: url('/fonts/nucleo-icons.eot') format('embedded-opentype'), url('/fonts/nucleo-icons.woff2') format('woff2'), url('/fonts/nucleo-icons.woff') format('woff'), url('/fonts/nucleo-icons.ttf') format('truetype'), url('/fonts/nucleo-icons.svg') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
/*------------------------
|
||||
base class definition
|
||||
|
|
|
@ -392,6 +392,10 @@ span.twitter-typeahead .tt-suggestion:focus {
|
|||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.timestamp-overlay-badge {
|
||||
color: var(--dark);
|
||||
}
|
||||
|
||||
.timeline-status-component {
|
||||
.username {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
<div class="col-12 col-md-6 offset-md-3 my-3">
|
||||
<div class="border rounded p-3 border-primary">
|
||||
<p class="h4 font-weight-bold pt-2 text-primary">Review the Community Guidelines</p>
|
||||
<p class="lead pt-4 text-primary">We want to keep {{config('app.name')}} a safe place for everyone, and we created these <a class="font-weight-bold text-primary" href="{{route('help.community-guidelines')}}">Community Guidelines</a> to support and protect our community.</p>
|
||||
<p class="lead pt-4 text-primary">We want to keep {{config_cache('app.name')}} a safe place for everyone, and we created these <a class="font-weight-bold text-primary" href="{{route('help.community-guidelines')}}">Community Guidelines</a> to support and protect our community.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -100,4 +100,4 @@
|
|||
ctx.putImageData(imageData, 0, 0);
|
||||
</script>
|
||||
@endif
|
||||
@endpush
|
||||
@endpush
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
<div class="col-12 col-md-6 offset-md-3 my-3">
|
||||
<div class="border rounded p-3 border-primary">
|
||||
<p class="h4 font-weight-bold pt-2 text-primary">Review the Community Guidelines</p>
|
||||
<p class="lead pt-4 text-primary">We want to keep {{config('app.name')}} a safe place for everyone, and we created these <a class="font-weight-bold text-primary" href="{{route('help.community-guidelines')}}">Community Guidelines</a> to support and protect our community.</p>
|
||||
<p class="lead pt-4 text-primary">We want to keep {{config_cache('app.name')}} a safe place for everyone, and we created these <a class="font-weight-bold text-primary" href="{{route('help.community-guidelines')}}">Community Guidelines</a> to support and protect our community.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -127,4 +127,4 @@
|
|||
ctx.putImageData(imageData, 0, 0);
|
||||
</script>
|
||||
@endif
|
||||
@endpush
|
||||
@endpush
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
<div class="col-12 col-md-6 offset-md-3 my-3">
|
||||
<div class="border rounded p-3 border-primary">
|
||||
<p class="h4 font-weight-bold pt-2 text-primary">Review the Community Guidelines</p>
|
||||
<p class="lead pt-4 text-primary">We want to keep {{config('app.name')}} a safe place for everyone, and we created these <a class="font-weight-bold text-primary" href="{{route('help.community-guidelines')}}">Community Guidelines</a> to support and protect our community.</p>
|
||||
<p class="lead pt-4 text-primary">We want to keep {{config_cache('app.name')}} a safe place for everyone, and we created these <a class="font-weight-bold text-primary" href="{{route('help.community-guidelines')}}">Community Guidelines</a> to support and protect our community.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 offset-md-3 mt-4 mb-5">
|
||||
|
@ -96,4 +96,4 @@
|
|||
ctx.putImageData(imageData, 0, 0);
|
||||
</script>
|
||||
@endif
|
||||
@endpush
|
||||
@endpush
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
<div class="col-12 col-md-6 offset-md-3 my-3">
|
||||
<div class="border rounded p-3 border-primary">
|
||||
<p class="h4 font-weight-bold pt-2 text-primary">Review the Community Guidelines</p>
|
||||
<p class="lead pt-4 text-primary">We want to keep {{config('app.name')}} a safe place for everyone, and we created these <a class="font-weight-bold text-primary" href="{{route('help.community-guidelines')}}">Community Guidelines</a> to support and protect our community.</p>
|
||||
<p class="lead pt-4 text-primary">We want to keep {{config_cache('app.name')}} a safe place for everyone, and we created these <a class="font-weight-bold text-primary" href="{{route('help.community-guidelines')}}">Community Guidelines</a> to support and protect our community.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -125,4 +125,4 @@
|
|||
ctx.putImageData(imageData, 0, 0);
|
||||
</script>
|
||||
@endif
|
||||
@endpush
|
||||
@endpush
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
</li>
|
||||
<li>
|
||||
<strong><span class="badge badge-primary">OAUTH</span> enabled: </strong>
|
||||
<span>{{ config_cache('pixelfed.oauth_enabled') ? '✅ true' : '❌ false' }}</span>
|
||||
<span>{{ (bool) config_cache('pixelfed.oauth_enabled') ? '✅ true' : '❌ false' }}</span>
|
||||
</li>
|
||||
<li>
|
||||
<strong><span class="badge badge-primary">OAUTH</span> token_expiration</strong>
|
||||
|
@ -298,7 +298,7 @@
|
|||
<tr>
|
||||
<td><span class="badge badge-primary">FEDERATION</span></td>
|
||||
<td><strong>ACTIVITY_PUB</strong></td>
|
||||
<td><span>{{config_cache('federation.activitypub.enabled') ? '✅ true' : '❌ false' }}</span></td>
|
||||
<td><span>{{(bool) config_cache('federation.activitypub.enabled') ? '✅ true' : '❌ false' }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">FEDERATION</span></td>
|
||||
|
@ -358,7 +358,7 @@
|
|||
<tr>
|
||||
<td><span class="badge badge-primary">FEDERATION</span></td>
|
||||
<td><strong>PF_NETWORK_TIMELINE</strong></td>
|
||||
<td><span>{{config_cache('federation.network_timeline') ? '✅ true' : '❌ false' }}</span></td>
|
||||
<td><span>{{(bool) config_cache('federation.network_timeline') ? '✅ true' : '❌ false' }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">FEDERATION</span></td>
|
||||
|
@ -368,7 +368,7 @@
|
|||
<tr>
|
||||
<td><span class="badge badge-primary">FEDERATION</span></td>
|
||||
<td><strong>CUSTOM_EMOJI</strong></td>
|
||||
<td><span>{{config_cache('federation.custom_emoji.enabled') ? '✅ true' : '❌ false' }}</span></td>
|
||||
<td><span>{{(bool) config_cache('federation.custom_emoji.enabled') ? '✅ true' : '❌ false' }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">FEDERATION</span></td>
|
||||
|
@ -545,7 +545,7 @@
|
|||
<tr>
|
||||
<td><span class="badge badge-primary">INSTANCE</span></td>
|
||||
<td><strong>STORIES_ENABLED</strong></td>
|
||||
<td><span>{{config_cache('instance.stories.enabled') ? '✅ true' : '❌ false' }}</span></td>
|
||||
<td><span>{{(bool) config_cache('instance.stories.enabled') ? '✅ true' : '❌ false' }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">INSTANCE</span></td>
|
||||
|
@ -740,7 +740,7 @@
|
|||
<tr>
|
||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||
<td><strong>PF_ENABLE_CLOUD</strong></td>
|
||||
<td><span>{{config_cache('pixelfed.cloud_storage') ? '✅ true' : '❌ false' }}</span></td>
|
||||
<td><span>{{(bool) config_cache('pixelfed.cloud_storage') ? '✅ true' : '❌ false' }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||
|
@ -750,12 +750,12 @@
|
|||
<tr>
|
||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||
<td><strong>PF_OPTIMIZE_IMAGES</strong></td>
|
||||
<td><span>{{config_cache('pixelfed.optimize_image') ? '✅ true' : '❌ false' }}</span></td>
|
||||
<td><span>{{(bool) config_cache('pixelfed.optimize_image') ? '✅ true' : '❌ false' }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||
<td><strong>PF_OPTIMIZE_VIDEOS</strong></td>
|
||||
<td><span>{{config_cache('pixelfed.optimize_video') ? '✅ true' : '❌ false' }}</span></td>
|
||||
<td><span>{{(bool) config_cache('pixelfed.optimize_video') ? '✅ true' : '❌ false' }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||
|
@ -810,12 +810,12 @@
|
|||
<tr>
|
||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||
<td><strong>OAUTH_ENABLED</strong></td>
|
||||
<td><span>{{config_cache('pixelfed.oauth_enabled') ? '✅ true' : '❌ false' }}</span></td>
|
||||
<td><span>{{ (bool) config_cache('pixelfed.oauth_enabled') ? '✅ true' : '❌ false' }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||
<td><strong>PF_BOUNCER_ENABLED</strong></td>
|
||||
<td><span>{{config_cache('pixelfed.bouncer.enabled') ? '✅ true' : '❌ false' }}</span></td>
|
||||
<td><span>{{(bool) config_cache('pixelfed.bouncer.enabled') ? '✅ true' : '❌ false' }}</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||
|
|
|
@ -1,421 +1,12 @@
|
|||
@extends('admin.partial.template-full')
|
||||
|
||||
@section('section')
|
||||
<div class="title mb-4">
|
||||
<h3 class="font-weight-bold">Settings</h3>
|
||||
@if(config('instance.enable_cc'))
|
||||
<p class="lead mb-0">Manage instance settings</p>
|
||||
</div>
|
||||
<form method="post">
|
||||
@csrf
|
||||
<ul class="nav nav-tabs nav-fill border-bottom-0" id="myTab" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link font-weight-bold active" id="home-tab" data-toggle="tab" href="#home" role="tab" aria-controls="home" aria-selected="true"><i class="fas fa-home"></i></a>
|
||||
</li>
|
||||
<li class="nav-item border-none">
|
||||
<a class="nav-link font-weight-bold px-4" id="landing-tab" data-toggle="tab" href="#landing" role="tab" aria-controls="landing">Landing</a>
|
||||
</li>
|
||||
<li class="nav-item border-none">
|
||||
<a class="nav-link font-weight-bold px-4" id="brand-tab" data-toggle="tab" href="#brand" role="tab" aria-controls="brand">Brand</a>
|
||||
</li>
|
||||
{{-- <li class="nav-item border-none">
|
||||
<a class="nav-link font-weight-bold px-4" id="media-tab" data-toggle="tab" href="#media" role="tab" aria-controls="media">Mail</a>
|
||||
</li> --}}
|
||||
<li class="nav-item border-none">
|
||||
<a class="nav-link font-weight-bold px-4" id="media-tab" data-toggle="tab" href="#media" role="tab" aria-controls="media">Media</a>
|
||||
</li>
|
||||
<li class="nav-item border-none">
|
||||
<a class="nav-link font-weight-bold px-4" id="rules-tab" data-toggle="tab" href="#rules" role="tab" aria-controls="rules">Rules</a>
|
||||
</li>
|
||||
<li class="nav-item border-none">
|
||||
<a class="nav-link font-weight-bold px-4" id="users-tab" data-toggle="tab" href="#users" role="tab" aria-controls="users">Users</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link font-weight-bold px-4" id="advanced-tab" data-toggle="tab" href="#advanced" role="tab" aria-controls="advanced">Advanced</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="tab-content" id="myTabContent">
|
||||
|
||||
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
|
||||
{{-- <div class="ml-n4 mr-n2 p-3 border-top border-bottom">
|
||||
<label class="font-weight-bold text-muted">System Configuration</label>
|
||||
<ul class="list-unstyled">
|
||||
<li>
|
||||
<span class="text-muted">Max Upload Size: </span>
|
||||
<span class="font-weight-bold">{{$system['max_upload_size']}}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="text-muted">Image Driver: </span>
|
||||
<span class="font-weight-bold">{{$system['image_driver']}}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="text-muted">Image Driver Loaded: </span>
|
||||
<span class="font-weight-bold">
|
||||
@if($system['image_driver_loaded'])
|
||||
<i class="fas fa-check text-success"></i>
|
||||
@else
|
||||
<i class="fas fa-times text-danger"></i>
|
||||
@endif
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="text-muted">File Permissions: </span>
|
||||
<span class="font-weight-bold">
|
||||
@if($system['permissions'])
|
||||
<i class="fas fa-check text-success"></i>
|
||||
@else
|
||||
<i class="fas fa-times text-danger"></i>
|
||||
@endif
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="text-muted"></span>
|
||||
<span class="font-weight-bold"></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div> --}}
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top border-bottom">
|
||||
<label class="font-weight-bold text-muted">Features</label>
|
||||
|
||||
<div class="form-group row mb-5">
|
||||
<label for="staticEmail" class="col-sm-12 col-form-label font-weight-bold">Registration Status</label>
|
||||
<div class="col-sm-4">
|
||||
<select class="custom-select" name="regs">
|
||||
<option value="open" {{ $regState === 'open' ? 'selected' : '' }}>Open - Anyone can register</option>
|
||||
<option value="filtered" {{ $regState === 'filtered' ? 'selected' : '' }}>Filtered - Anyone can apply (Curated Onboarding)</option>
|
||||
<option value="closed" {{ $regState === 'closed' ? 'selected' : '' }}>Closed - Nobody can register</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($cloud_ready)
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="cloud_storage" class="custom-control-input" id="cls1" {{config_cache('pixelfed.cloud_storage') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="cls1">Cloud Storage</label>
|
||||
</div>
|
||||
<p class="mb-4 small">Store photos & videos on S3 compatible object storage providers.</p>
|
||||
@endif
|
||||
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="activitypub" class="custom-control-input" id="ap" {{config_cache('federation.activitypub.enabled') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="ap">ActivityPub</label>
|
||||
</div>
|
||||
<p class="mb-4 small">ActivityPub federation, compatible with Pixelfed, Mastodon and other projects.</p>
|
||||
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="account_migration" class="custom-control-input" id="ap_mig" {{(bool)config_cache('federation.migration') ? 'checked' : ''}} {{(bool) config_cache('federation.activitypub.enabled') ? '' : 'disabled="disabled"'}}>
|
||||
<label class="custom-control-label font-weight-bold" for="ap_mig">Account Migration</label>
|
||||
</div>
|
||||
@if((bool) config_cache('federation.activitypub.enabled'))
|
||||
<p class="mb-4 small">Allow local accounts to migrate to other local or remote accounts.</p>
|
||||
@else
|
||||
<p class="mb-4 small text-muted"><strong>ActivityPub Required</strong> Allow local accounts to migrate to other local or remote accounts.</p>
|
||||
@endif
|
||||
|
||||
{{-- <div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="open_registration" class="custom-control-input" id="openReg" {{config_cache('pixelfed.open_registration') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="openReg">Open Registrations</label>
|
||||
</div>
|
||||
<p class="mb-4 small">Allow new user registrations.</p> --}}
|
||||
|
||||
|
||||
{{-- <div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="registration_approvals" class="custom-control-input" id="openRegApproval" {{config_cache('pixelfed.registration_approvals') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="openRegApproval">Registration Approval Mode</label>
|
||||
</div>
|
||||
<p class="mb-4 small">Manually review new account registration applications.</p> --}}
|
||||
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="mobile_apis" class="custom-control-input" id="cf2" {{config_cache('pixelfed.oauth_enabled') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="cf2">Mobile APIs</label>
|
||||
</div>
|
||||
<p class="mb-4 small">Enable apis required for mobile app support.</p>
|
||||
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="stories" class="custom-control-input" id="cf3" {{config_cache('instance.stories.enabled') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="cf3">Stories</label>
|
||||
</div>
|
||||
<p class="mb-4 small">Allow users to share ephemeral Stories.</p>
|
||||
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="ig_import" class="custom-control-input" id="cf4" {{config_cache('pixelfed.import.instagram.enabled') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="cf4">Instagram Import</label>
|
||||
</div>
|
||||
<p class="mb-4 small">Allow <span class="font-weight-bold">experimental</span> Instagram Import support.</p>
|
||||
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="spam_detection" class="custom-control-input" id="cf5" {{config_cache('pixelfed.bouncer.enabled') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="cf5">Spam detection</label>
|
||||
</div>
|
||||
<p class="mb-4 small">Detect and remove spam from timelines.</p>
|
||||
</div>
|
||||
</div>
|
||||
{{-- <div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top border-bottom">
|
||||
<label class="font-weight-bold text-muted">Name</label>
|
||||
<input class="form-control col-8" name="name" placeholder="Pixelfed" value="{{config_cache('app.name')}}">
|
||||
<p class="help-text small text-muted mt-3 mb-0">The instance name used in titles, metadata and apis.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-bottom">
|
||||
<label class="font-weight-bold text-muted">Short Description</label>
|
||||
<textarea class="form-control" rows="3" name="short_description">{{config_cache('app.short_description')}}</textarea>
|
||||
<p class="help-text small text-muted mt-3 mb-0">Short description of instance used on various pages and apis.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-bottom">
|
||||
<label class="font-weight-bold text-muted">Long Description</label>
|
||||
<textarea class="form-control" rows="3" name="long_description">{{config_cache('app.description')}}</textarea>
|
||||
<p class="help-text small text-muted mt-3 mb-0">Longer description of instance used on about page.</p>
|
||||
</div>
|
||||
</div> --}}
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="landing" role="tabpanel" aria-labelledby="landing-tab">
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top border-bottom">
|
||||
<p class="mb-0 small">Configure your landing page</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-bottom">
|
||||
<p class="font-weight-bold text-muted">Discovery</p>
|
||||
|
||||
<div class="my-3">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="show_directory" name="show_directory" {{ config_cache('instance.landing.show_directory') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="show_directory">Show Directory</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox" class="custom-control-input" id="show_explore_feed" name="show_explore_feed" {{ config_cache('instance.landing.show_explore') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="show_explore_feed">Show Explore Feed</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-bottom">
|
||||
<p class="font-weight-bold text-muted">Admin Account</p>
|
||||
|
||||
<div class="my-3">
|
||||
<select class="custom-select" name="admin_account_id" style="max-width: 300px;">
|
||||
<option selected disabled>Select an admin account</option>
|
||||
@foreach($availableAdmins as $acct)
|
||||
<option
|
||||
value="{{ $acct->profile_id }}" {!! $currentAdmin && $currentAdmin['id'] == $acct->profile_id ? 'selected' : null !!}
|
||||
>
|
||||
<span class="font-weight-bold">@{{ $acct->username }}</span>
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="brand" role="tabpanel" aria-labelledby="brand-tab">
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top border-bottom">
|
||||
<label class="font-weight-bold text-muted">Name</label>
|
||||
<input class="form-control col-8" name="name" placeholder="Pixelfed" value="{{config_cache('app.name')}}">
|
||||
<p class="help-text small text-muted mt-3 mb-0">The instance name used in titles, metadata and apis.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-bottom">
|
||||
<label class="font-weight-bold text-muted">Short Description</label>
|
||||
<textarea class="form-control" rows="3" name="short_description">{{config_cache('app.short_description')}}</textarea>
|
||||
<p class="help-text small text-muted mt-3 mb-0">Short description of instance used on various pages and apis.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-bottom">
|
||||
<label class="font-weight-bold text-muted">Long Description</label>
|
||||
<textarea class="form-control" rows="3" name="long_description">{{config_cache('app.description')}}</textarea>
|
||||
<p class="help-text small text-muted mt-3 mb-0">Longer description of instance used on about page.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top border-bottom">
|
||||
<label class="font-weight-bold text-muted">About Title</label>
|
||||
<input class="form-control col-8" name="about_title" placeholder="Photo Sharing. For Everyone" value="{{config_cache('about.title')}}">
|
||||
<p class="help-text small text-muted mt-3 mb-0">The header title used on the <a href="/site/about">about page</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="users" role="tabpanel" aria-labelledby="users-tab">
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top">
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="require_email_verification" class="custom-control-input" id="mailVerification" {{config_cache('pixelfed.enforce_email_verification') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="mailVerification">Require Email Verification</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top">
|
||||
<div class="custom-control custom-checkbox my-2">
|
||||
<input type="checkbox" name="enforce_account_limit" class="custom-control-input" id="userEnforceLimit" {{config_cache('pixelfed.enforce_account_limit') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="userEnforceLimit">Enable account storage limit</label>
|
||||
<p class="help-text small text-muted">Set a storage limit per user account.</p>
|
||||
</div>
|
||||
<label class="font-weight-bold text-muted">Account Limit</label>
|
||||
<input class="form-control" name="account_limit" placeholder="Pixelfed" value="{{config_cache('pixelfed.max_account_size')}}">
|
||||
<p class="help-text small text-muted mt-3 mb-0">Account limit size in KB.</p>
|
||||
<p class="help-text small text-muted mb-0">{{config_cache('pixelfed.max_account_size')}} KB = {{floor(config_cache('pixelfed.max_account_size') / 1024)}} MB</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top">
|
||||
<div class="custom-control custom-checkbox my-2">
|
||||
<input type="checkbox" name="account_autofollow" class="custom-control-input" id="userAccountAutofollow" {{config_cache('account.autofollow') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="userAccountAutofollow">Auto Follow Accounts</label>
|
||||
<p class="help-text small text-muted">Enable auto follow accounts, new accounts will follow accounts you set.</p>
|
||||
</div>
|
||||
<label class="font-weight-bold text-muted">Accounts</label>
|
||||
<textarea class="form-control" name="account_autofollow_usernames" placeholder="Add account usernames to follow separated by commas">{{config_cache('account.autofollow_usernames')}}</textarea>
|
||||
<p class="help-text small text-muted mt-3 mb-0">Add account usernames to follow separated by commas.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="media" role="tabpanel" aria-labelledby="media-tab">
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top">
|
||||
<label class="font-weight-bold text-muted">Max Size</label>
|
||||
<input class="form-control" name="max_photo_size" value="{{config_cache('pixelfed.max_photo_size')}}">
|
||||
<p class="help-text small text-muted mt-3 mb-0">Maximum file upload size in KB</p>
|
||||
<p class="help-text small text-muted mb-0">{{config_cache('pixelfed.max_photo_size')}} KB = {{number_format(config_cache('pixelfed.max_photo_size') / 1024)}} MB</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top">
|
||||
<label class="font-weight-bold text-muted">Photo Album Limit</label>
|
||||
<input class="form-control" name="max_album_length" value="{{config_cache('pixelfed.max_album_length')}}">
|
||||
<p class="help-text small text-muted mt-3 mb-0">The maximum number of photos or videos per album</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top">
|
||||
<label class="font-weight-bold text-muted">Image Quality</label>
|
||||
<input class="form-control" name="image_quality" value="{{config_cache('pixelfed.image_quality')}}">
|
||||
<p class="help-text small text-muted mt-3 mb-0">Image optimization quality from 0-100%. Set to 0 to disable image optimization.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top border-bottom">
|
||||
<label class="font-weight-bold text-muted">Media Types</label>
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="type_jpeg" class="custom-control-input" id="mediaType1" {{$jpeg ? 'checked' : ''}}>
|
||||
<label class="custom-control-label" for="mediaType1"><span class="border border-dark px-1 rounded font-weight-bold">JPEG</span></label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="type_png" class="custom-control-input" id="mediaType2" {{$png ? 'checked' : ''}}>
|
||||
<label class="custom-control-label" for="mediaType2"><span class="border border-dark px-1 rounded font-weight-bold">PNG</span></label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="type_gif" class="custom-control-input" id="mediaType3" {{$gif ? 'checked' : ''}}>
|
||||
<label class="custom-control-label" for="mediaType3"><span class="border border-dark px-1 rounded font-weight-bold">GIF</span></label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="type_webp" class="custom-control-input" id="mediaType4" {{$webp ? 'checked' : ''}}>
|
||||
<label class="custom-control-label" for="mediaType4"><span class="border border-dark px-1 rounded font-weight-bold">WebP</span></label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox mt-2">
|
||||
<input type="checkbox" name="type_mp4" class="custom-control-input" id="mediaType5" {{$mp4 ? 'checked' : ''}}>
|
||||
<label class="custom-control-label" for="mediaType5"><span class="border border-dark px-1 rounded font-weight-bold">MP4</span></label>
|
||||
</div>
|
||||
<p class="help-text small text-muted mt-3 mb-0">Allowed media types.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="rules" role="tabpanel" aria-labelledby="rules-tab">
|
||||
<div class="border-top">
|
||||
<p class="lead mt-3 py-3 text-center">Add rules that explain what is acceptable use.</p>
|
||||
</div>
|
||||
<div class="ml-n4 mr-n2 p-3 border-top border-bottom">
|
||||
<p class="font-weight-bold text-muted">Active Rules</p>
|
||||
<ol class="font-weight-bold">
|
||||
@if($rules)
|
||||
@foreach($rules as $rule)
|
||||
<li class="mb-4">
|
||||
<p class="mb-0">
|
||||
{{$rule}}
|
||||
</p>
|
||||
<p>
|
||||
<button type="button" class="btn btn-outline-danger btn-sm py-0 rule-delete" data-index="{{$loop->index}}">Delete</button>
|
||||
</p>
|
||||
</li>
|
||||
@endforeach
|
||||
@endif
|
||||
</ol>
|
||||
</div>
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top border-bottom">
|
||||
<label class="font-weight-bold text-muted">Add Rule</label>
|
||||
<input class="form-control" name="new_rule" placeholder="Add a new rule, we recommend being descriptive but keeping it short"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane" id="advanced" role="tabpanel" aria-labelledby="advanced-tab">
|
||||
<div class="form-group mb-0">
|
||||
<div class="ml-n4 mr-n2 p-3 border-top border-bottom">
|
||||
<label class="font-weight-bold text-muted">Custom CSS</label>
|
||||
<div class="custom-control custom-checkbox my-2">
|
||||
<input type="checkbox" name="show_custom_css" class="custom-control-input" id="showCustomCss" {{config_cache('uikit.show_custom.css') ? 'checked' : ''}}>
|
||||
<label class="custom-control-label font-weight-bold" for="showCustomCss">Enable custom CSS</label>
|
||||
</div>
|
||||
<textarea class="form-control" name="custom_css" rows="3">{{config_cache('uikit.custom.css')}}</textarea>
|
||||
<p class="help-text small text-muted mt-3 mb-0">Add custom CSS, will be used on all pages</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="form-group row mb-0 mt-4">
|
||||
<div class="col-12 text-right">
|
||||
<button type="submit" class="btn btn-primary font-weight-bold px-5">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@else
|
||||
</div>
|
||||
<div class="py-5">
|
||||
<p class="lead text-center font-weight-bold">Not enabled</p>
|
||||
<p class="text-center">Add <code>ENABLE_CONFIG_CACHE=true</code> in your <span class="font-weight-bold">.env</span> file <br /> and run <span class="font-weight-bold">php artisan config:cache</span></p>
|
||||
</div>
|
||||
@endif
|
||||
<admin-settings />
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script type="text/javascript">
|
||||
$('.rule-delete').on('click', function(e) {
|
||||
if(window.confirm('Are you sure you want to delete this rule?')) {
|
||||
let idx = e.target.dataset.index;
|
||||
axios.post(window.location.href, {
|
||||
'rule_delete': idx
|
||||
}).then(res => {
|
||||
$('.rule-delete[data-index="'+idx+'"]').parents().eq(1).remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
setTimeout(() => {
|
||||
$('.alert-success').fadeOut();
|
||||
}, 1000);
|
||||
});
|
||||
new Vue({ el: '#panel'});
|
||||
</script>
|
||||
@endpush
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@if(config('captcha.enabled') || config('captcha.active.login') || config('captcha.active.register'))
|
||||
@if((bool) config_cache('captcha.enabled'))
|
||||
<label class="font-weight-bold small text-muted">Captcha</label>
|
||||
<div class="d-flex flex-grow-1">
|
||||
{!! Captcha::display(['data-theme' => 'dark']) !!}
|
||||
|
|
|
@ -76,10 +76,10 @@
|
|||
</div>
|
||||
|
||||
@if(
|
||||
config('captcha.enabled') ||
|
||||
config('captcha.active.login') ||
|
||||
(bool) config_cache('captcha.enabled') &&
|
||||
(bool) config_cache('captcha.active.login') ||
|
||||
(
|
||||
config('captcha.triggers.login.enabled') &&
|
||||
(bool) config_cache('captcha.triggers.login.enabled') &&
|
||||
request()->session()->has('login_attempts') &&
|
||||
request()->session()->get('login_attempts') >= config('captcha.triggers.login.attempts')
|
||||
)
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@if(config('captcha.enabled'))
|
||||
@if((bool) config_cache('captcha.enabled'))
|
||||
<label class="font-weight-bold small text-muted">Captcha</label>
|
||||
<div class="d-flex flex-grow-1">
|
||||
{!! Captcha::display(['data-theme' => 'dark']) !!}
|
||||
|
|
|
@ -109,7 +109,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@if(config('captcha.enabled'))
|
||||
@if((bool) config_cache('captcha.enabled'))
|
||||
<label class="font-weight-bold small pt-3 text-muted">Captcha</label>
|
||||
<div class="d-flex flex-grow-1">
|
||||
{!! Captcha::display(['data-theme' => 'dark']) !!}
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@if(config('captcha.enabled') || config('captcha.active.register'))
|
||||
@if((bool) config_cache('captcha.enabled') && (bool) config_cache('captcha.active.register'))
|
||||
<div class="d-flex justify-content-center my-3">
|
||||
{!! Captcha::display() !!}
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
@extends('layouts.app',['title' => 'Welcome to ' . config('app.name')])
|
||||
@extends('layouts.app',['title' => 'Welcome to ' . config_cache('app.name')])
|
||||
|
||||
@section('content')
|
||||
<div class="container mt-4">
|
||||
|
@ -14,7 +14,7 @@
|
|||
</div>
|
||||
@endif
|
||||
|
||||
<p class="lead mb-0">Welcome to {{config('app.name')}}!</p>
|
||||
<p class="lead mb-0">Welcome to {{config_cache('app.name')}}!</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
|
||||
<title>{{ $title ?? config('app.name', 'Pixelfed') }}</title>
|
||||
<title>{{ $title ?? config_cache('app.name', 'Pixelfed') }}</title>
|
||||
<link rel="manifest" href="{{url('/manifest.json')}}">
|
||||
|
||||
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
|
||||
<meta property="og:title" content="{{ $title ?? config('app.name', 'pixelfed') }}">
|
||||
<meta property="og:site_name" content="{{ config_cache('app.name', 'pixelfed') }}">
|
||||
<meta property="og:title" content="{{ $title ?? config_cache('app.name', 'pixelfed') }}">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="{{url(request()->url())}}">
|
||||
@stack('meta')
|
||||
|
|
|
@ -70,11 +70,11 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
|
||||
<title>{{ $title ?? config('app.name', 'Pixelfed') }}</title>
|
||||
<title>{{ $title ?? config_cache('app.name', 'Pixelfed') }}</title>
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<meta property="og:site_name" content="Pixelfed">
|
||||
<meta property="og:title" content="{{ $ogTitle ?? $title ?? config('app.name', 'pixelfed') }}">
|
||||
<meta property="og:title" content="{{ $ogTitle ?? $title ?? config_cache('app.name', 'pixelfed') }}">
|
||||
<meta property="og:type" content="{{ $ogType ?? 'article' }}">
|
||||
<meta property="og:url" content="{{url(request()->url())}}">
|
||||
@stack('meta')
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
|
||||
<title>{{ $title ?? config_cache('app.name') }}</title>
|
||||
|
||||
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
|
||||
<meta property="og:title" content="{{ $title ?? config('app.name', 'pixelfed') }}">
|
||||
<meta property="og:site_name" content="{{ config_cache('app.name', 'pixelfed') }}">
|
||||
<meta property="og:title" content="{{ $title ?? config_cache('app.name', 'pixelfed') }}">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="{{request()->url()}}">
|
||||
@stack('meta')
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
|
||||
<title>{{ $title ?? config('app.name', 'Laravel') }}</title>
|
||||
<title>{{ $title ?? config_cache('app.name', 'Pixelfed') }}</title>
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
|
||||
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
|
||||
<meta property="og:title" content="{{ $title ?? config('app.name', 'pixelfed') }}">
|
||||
<meta property="og:site_name" content="{{ config_cache('app.name', 'pixelfed') }}">
|
||||
<meta property="og:title" content="{{ $title ?? config_cache('app.name', 'pixelfed') }}">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="{{request()->url()}}">
|
||||
@stack('meta')
|
||||
|
|
|
@ -105,7 +105,7 @@
|
|||
{{__('navmenu.discover')}}
|
||||
</a>
|
||||
|
||||
@if(config_cache('instance.stories.enabled'))
|
||||
@if((bool) config_cache('instance.stories.enabled'))
|
||||
<a class="dropdown-item lead" href="/i/stories/new">
|
||||
<span style="width: 50px;margin-right:14px;">
|
||||
<span class="fal fa-history text-lighter fa-lg"></span>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<div class="container">
|
||||
<a class="navbar-brand d-flex align-items-center" href="{{ url('/') }}">
|
||||
<img src="/img/pixelfed-icon-color.svg" height="30px" class="px-2" alt="Pixelfed logo">
|
||||
<span class="font-weight-bold mb-0" style="font-size:20px;">{{ config('app.name', 'Laravel') }}</span>
|
||||
<span class="font-weight-bold mb-0" style="font-size:20px;">{{ config_cache('app.name', 'Pixelfed') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
|
||||
<title>{!! $title ?? config_cache('app.name') !!}</title>
|
||||
|
||||
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
|
||||
<meta property="og:title" content="{{ $title ?? config('app.name', 'pixelfed') }}">
|
||||
<meta property="og:site_name" content="{{ config_cache('app.name', 'pixelfed') }}">
|
||||
<meta property="og:title" content="{{ $title ?? config_cache('app.name', 'pixelfed') }}">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="{{request()->url()}}">
|
||||
@stack('meta')
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue