forked from mirror/pixelfed
commit
0b162dc15e
|
@ -21,7 +21,12 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- checkout
|
- 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
|
# Download and cache dependencies
|
||||||
|
|
||||||
|
@ -36,18 +41,17 @@ jobs:
|
||||||
- run: composer install -n --prefer-dist
|
- run: composer install -n --prefer-dist
|
||||||
|
|
||||||
- save_cache:
|
- save_cache:
|
||||||
key: composer-v2-{{ checksum "composer.lock" }}
|
key: v2-dependencies-{{ checksum "composer.json" }}
|
||||||
paths:
|
paths:
|
||||||
- vendor
|
- vendor
|
||||||
|
|
||||||
- run: cp .env.testing .env
|
|
||||||
- run: php artisan config:cache
|
- run: php artisan config:cache
|
||||||
- run: php artisan route:clear
|
- run: php artisan route:clear
|
||||||
- run: php artisan storage:link
|
- run: php artisan storage:link
|
||||||
- run: php artisan key:generate
|
- run: php artisan key:generate
|
||||||
|
|
||||||
# run tests with phpunit or codecept
|
# run tests with phpunit or codecept
|
||||||
- run: ./vendor/bin/phpunit
|
- run: php artisan test
|
||||||
- store_test_results:
|
- store_test_results:
|
||||||
path: tests/_output
|
path: tests/_output
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
|
|
|
@ -82,7 +82,7 @@ class AvatarStorage extends Command
|
||||||
|
|
||||||
$this->line(' ');
|
$this->line(' ');
|
||||||
|
|
||||||
if(config_cache('pixelfed.cloud_storage')) {
|
if((bool) config_cache('pixelfed.cloud_storage')) {
|
||||||
$this->info('✅ - Cloud storage configured');
|
$this->info('✅ - Cloud storage configured');
|
||||||
$this->line(' ');
|
$this->line(' ');
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ class AvatarStorage extends Command
|
||||||
$this->line(' ');
|
$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'));
|
$disk = Storage::disk(config_cache('filesystems.cloud'));
|
||||||
$exists = $disk->exists('cache/avatars/default.jpg');
|
$exists = $disk->exists('cache/avatars/default.jpg');
|
||||||
$state = $exists ? '✅' : '❌';
|
$state = $exists ? '✅' : '❌';
|
||||||
|
@ -100,7 +100,7 @@ class AvatarStorage extends Command
|
||||||
$this->info($msg);
|
$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',
|
'Cancel',
|
||||||
'Upload default avatar to cloud',
|
'Upload default avatar to cloud',
|
||||||
|
@ -164,7 +164,7 @@ class AvatarStorage extends Command
|
||||||
|
|
||||||
protected function uploadAvatarsToCloud()
|
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');
|
$this->error('Enable cloud storage and avatar cloud storage to perform this action');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -213,7 +213,7 @@ class AvatarStorage extends Command
|
||||||
return;
|
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.');
|
$this->error('You have cloud storage disabled and local avatar storage disabled, we cannot refetch avatars.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ class AvatarStorageDeepClean extends Command
|
||||||
$this->line(' ');
|
$this->line(' ');
|
||||||
|
|
||||||
$storage = [
|
$storage = [
|
||||||
'cloud' => boolval(config_cache('pixelfed.cloud_storage')),
|
'cloud' => (bool) config_cache('pixelfed.cloud_storage'),
|
||||||
'local' => boolval(config_cache('federation.avatars.store_local'))
|
'local' => boolval(config_cache('federation.avatars.store_local'))
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -35,12 +35,16 @@ class CloudMediaMigrate extends Command
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$enabled = config('pixelfed.cloud_storage');
|
$enabled = (bool) config_cache('pixelfed.cloud_storage');
|
||||||
if(!$enabled) {
|
if(!$enabled) {
|
||||||
$this->error('Cloud storage not enabled. Exiting...');
|
$this->error('Cloud storage not enabled. Exiting...');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!$this->confirm('Are you sure you want to proceed?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$limit = $this->option('limit');
|
$limit = $this->option('limit');
|
||||||
$hugeMode = $this->option('huge');
|
$hugeMode = $this->option('huge');
|
||||||
|
|
||||||
|
|
|
@ -2,11 +2,11 @@
|
||||||
|
|
||||||
namespace App\Console\Commands;
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
use Illuminate\Console\Command;
|
|
||||||
use App\Media;
|
use App\Media;
|
||||||
use Illuminate\Support\Facades\Http;
|
|
||||||
use App\Services\MediaService;
|
use App\Services\MediaService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
class FetchMissingMediaMimeType extends Command
|
class FetchMissingMediaMimeType extends Command
|
||||||
{
|
{
|
||||||
|
@ -29,20 +29,20 @@ class FetchMissingMediaMimeType extends Command
|
||||||
*/
|
*/
|
||||||
public function handle()
|
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);
|
$res = Http::retry(2, 100, throw: false)->head($media->remote_url);
|
||||||
|
|
||||||
if(!$res->successful()) {
|
if (! $res->successful()) {
|
||||||
continue;
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$media->mime = $res->header('content-type');
|
$media->mime = $res->header('content-type');
|
||||||
|
|
||||||
if($res->hasHeader('content-length')) {
|
if ($res->hasHeader('content-length')) {
|
||||||
$media->size = $res->header('content-length');
|
$media->size = $res->header('content-length');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ class FetchMissingMediaMimeType extends Command
|
||||||
|
|
||||||
MediaService::del($media->status_id);
|
MediaService::del($media->status_id);
|
||||||
StatusService::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;
|
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...');
|
$this->error('Cloud storage not enabled, exiting...');
|
||||||
return Command::SUCCESS;
|
return Command::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ class MediaCloudUrlRewrite extends Command implements PromptsForMissingInput
|
||||||
|
|
||||||
protected function preflightCheck()
|
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->info('Error: Cloud storage is not enabled!');
|
||||||
$this->error('Aborting...');
|
$this->error('Aborting...');
|
||||||
exit;
|
exit;
|
||||||
|
|
|
@ -45,7 +45,7 @@ class MediaS3GarbageCollector extends Command
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$enabled = in_array(config_cache('pixelfed.cloud_storage'), ['1', true, 'true']);
|
$enabled = (bool) config_cache('pixelfed.cloud_storage');
|
||||||
if(!$enabled) {
|
if(!$enabled) {
|
||||||
$this->error('Cloud storage not enabled. Exiting...');
|
$this->error('Cloud storage not enabled. Exiting...');
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -33,7 +33,7 @@ class Kernel extends ConsoleKernel
|
||||||
$schedule->command('gc:passwordreset')->dailyAt('09:41')->onOneServer();
|
$schedule->command('gc:passwordreset')->dailyAt('09:41')->onOneServer();
|
||||||
$schedule->command('gc:sessions')->twiceDaily(13, 23)->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);
|
$schedule->command('media:s3gc')->hourlyAt(15);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -157,7 +157,7 @@ class AccountController extends Controller
|
||||||
|
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$count = UserFilterService::muteCount($pid);
|
$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');
|
abort_if($count >= $maxLimit, 422, self::FILTER_LIMIT_MUTE_TEXT . $maxLimit . ' accounts');
|
||||||
if($count == 0) {
|
if($count == 0) {
|
||||||
$filterCount = UserFilter::whereUserId($pid)->count();
|
$filterCount = UserFilter::whereUserId($pid)->count();
|
||||||
|
@ -260,7 +260,7 @@ class AccountController extends Controller
|
||||||
]);
|
]);
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$count = UserFilterService::blockCount($pid);
|
$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');
|
abort_if($count >= $maxLimit, 422, self::FILTER_LIMIT_BLOCK_TEXT . $maxLimit . ' accounts');
|
||||||
if($count == 0) {
|
if($count == 0) {
|
||||||
$filterCount = UserFilter::whereUserId($pid)->whereFilterType('block')->count();
|
$filterCount = UserFilter::whereUserId($pid)->whereFilterType('block')->count();
|
||||||
|
|
|
@ -2,30 +2,20 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers\Admin;
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
use DB, Cache;
|
use App\Http\Controllers\PixelfedDirectoryController;
|
||||||
use App\{
|
|
||||||
DiscoverCategory,
|
|
||||||
DiscoverCategoryHashtag,
|
|
||||||
Hashtag,
|
|
||||||
Media,
|
|
||||||
Profile,
|
|
||||||
Status,
|
|
||||||
StatusHashtag,
|
|
||||||
User
|
|
||||||
};
|
|
||||||
use App\Models\ConfigCache;
|
use App\Models\ConfigCache;
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\ConfigCacheService;
|
use App\Services\ConfigCacheService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
use Carbon\Carbon;
|
use App\Status;
|
||||||
|
use App\User;
|
||||||
|
use Cache;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Support\Facades\Http;
|
||||||
use League\ISO3166\ISO3166;
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Facades\Validator;
|
use Illuminate\Support\Facades\Validator;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Str;
|
||||||
use App\Http\Controllers\PixelfedDirectoryController;
|
use League\ISO3166\ISO3166;
|
||||||
|
|
||||||
trait AdminDirectoryController
|
trait AdminDirectoryController
|
||||||
{
|
{
|
||||||
|
@ -41,37 +31,37 @@ trait AdminDirectoryController
|
||||||
$res['countries'] = collect((new ISO3166)->all())->pluck('name');
|
$res['countries'] = collect((new ISO3166)->all())->pluck('name');
|
||||||
$res['admins'] = User::whereIsAdmin(true)
|
$res['admins'] = User::whereIsAdmin(true)
|
||||||
->where('2fa_enabled', true)
|
->where('2fa_enabled', true)
|
||||||
->get()->map(function($user) {
|
->get()->map(function ($user) {
|
||||||
return [
|
return [
|
||||||
'uid' => (string) $user->id,
|
'uid' => (string) $user->id,
|
||||||
'pid' => (string) $user->profile_id,
|
'pid' => (string) $user->profile_id,
|
||||||
'username' => $user->username,
|
'username' => $user->username,
|
||||||
'created_at' => $user->created_at
|
'created_at' => $user->created_at,
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
$config = ConfigCache::whereK('pixelfed.directory')->first();
|
$config = ConfigCache::whereK('pixelfed.directory')->first();
|
||||||
if($config) {
|
if ($config) {
|
||||||
$data = $config->v ? json_decode($config->v, true) : [];
|
$data = $config->v ? json_decode($config->v, true) : [];
|
||||||
$res = array_merge($res, $data);
|
$res = array_merge($res, $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(empty($res['summary'])) {
|
if (empty($res['summary'])) {
|
||||||
$summary = ConfigCache::whereK('app.short_description')->pluck('v');
|
$summary = ConfigCache::whereK('app.short_description')->pluck('v');
|
||||||
$res['summary'] = $summary ? $summary[0] : null;
|
$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']));
|
$res['banner_image'] = url(Storage::url($res['banner_image']));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($res['favourite_posts'])) {
|
if (isset($res['favourite_posts'])) {
|
||||||
$res['favourite_posts'] = collect($res['favourite_posts'])->map(function($id) {
|
$res['favourite_posts'] = collect($res['favourite_posts'])->map(function ($id) {
|
||||||
return StatusService::get($id);
|
return StatusService::get($id);
|
||||||
})
|
})
|
||||||
->filter(function($post) {
|
->filter(function ($post) {
|
||||||
return $post && isset($post['account']);
|
return $post && isset($post['account']);
|
||||||
})
|
})
|
||||||
->values();
|
->values();
|
||||||
}
|
}
|
||||||
|
|
||||||
$res['community_guidelines'] = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : [];
|
$res['community_guidelines'] = config_cache('app.rules') ? json_decode(config_cache('app.rules'), true) : [];
|
||||||
|
@ -84,22 +74,22 @@ trait AdminDirectoryController
|
||||||
$res['feature_config'] = [
|
$res['feature_config'] = [
|
||||||
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
|
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
|
||||||
'image_quality' => config_cache('pixelfed.image_quality'),
|
'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_photo_size' => config_cache('pixelfed.max_photo_size'),
|
||||||
'max_caption_length' => config_cache('pixelfed.max_caption_length'),
|
'max_caption_length' => config_cache('pixelfed.max_caption_length'),
|
||||||
'max_altext_length' => config_cache('pixelfed.max_altext_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_account_size' => config_cache('pixelfed.max_account_size'),
|
||||||
'max_album_length' => config_cache('pixelfed.max_album_length'),
|
'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')) {
|
if (config_cache('pixelfed.directory.testimonials')) {
|
||||||
$testimonials = collect(json_decode(config_cache('pixelfed.directory.testimonials'),true))
|
$testimonials = collect(json_decode(config_cache('pixelfed.directory.testimonials'), true))
|
||||||
->map(function($t) {
|
->map(function ($t) {
|
||||||
return [
|
return [
|
||||||
'profile' => AccountService::get($t['profile_id']),
|
'profile' => AccountService::get($t['profile_id']),
|
||||||
'body' => $t['body']
|
'body' => $t['body'],
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
$res['testimonials'] = $testimonials;
|
$res['testimonials'] = $testimonials;
|
||||||
|
@ -108,8 +98,8 @@ trait AdminDirectoryController
|
||||||
$validator = Validator::make($res['feature_config'], [
|
$validator = Validator::make($res['feature_config'], [
|
||||||
'media_types' => [
|
'media_types' => [
|
||||||
'required',
|
'required',
|
||||||
function ($attribute, $value, $fail) {
|
function ($attribute, $value, $fail) {
|
||||||
if (!in_array('image/jpeg', $value->toArray()) || !in_array('image/png', $value->toArray())) {
|
if (! in_array('image/jpeg', $value->toArray()) || ! in_array('image/png', $value->toArray())) {
|
||||||
$fail('You must enable image/jpeg and image/png support.');
|
$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_account_size' => 'required_if:enforce_account_limit,true|integer|min:1000000',
|
||||||
'max_album_length' => 'required|integer|min:4|max:20',
|
'max_album_length' => 'required|integer|min:4|max:20',
|
||||||
'account_deletion' => 'required|accepted',
|
'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();
|
$res['requirements_validator'] = $validator->errors();
|
||||||
|
@ -146,11 +136,11 @@ trait AdminDirectoryController
|
||||||
foreach (new \DirectoryIterator($path) as $io) {
|
foreach (new \DirectoryIterator($path) as $io) {
|
||||||
$name = $io->getFilename();
|
$name = $io->getFilename();
|
||||||
$skip = ['vendor'];
|
$skip = ['vendor'];
|
||||||
if($io->isDot() || in_array($name, $skip)) {
|
if ($io->isDot() || in_array($name, $skip)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($io->isDir()) {
|
if ($io->isDir()) {
|
||||||
$langs->push(['code' => $name, 'name' => locale_get_display_name($name)]);
|
$langs->push(['code' => $name, 'name' => locale_get_display_name($name)]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -159,25 +149,26 @@ trait AdminDirectoryController
|
||||||
$res['primary_locale'] = config('app.locale');
|
$res['primary_locale'] = config('app.locale');
|
||||||
|
|
||||||
$submissionState = Http::withoutVerifying()
|
$submissionState = Http::withoutVerifying()
|
||||||
->post('https://pixelfed.org/api/v1/directory/check-submission', [
|
->post('https://pixelfed.org/api/v1/directory/check-submission', [
|
||||||
'domain' => config('pixelfed.domain.app')
|
'domain' => config('pixelfed.domain.app'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$res['submission_state'] = $submissionState->json();
|
$res['submission_state'] = $submissionState->json();
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function validVal($res, $val, $count = false, $minLen = false)
|
protected function validVal($res, $val, $count = false, $minLen = false)
|
||||||
{
|
{
|
||||||
if(!isset($res[$val])) {
|
if (! isset($res[$val])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($count) {
|
if ($count) {
|
||||||
return count($res[$val]) >= $count;
|
return count($res[$val]) >= $count;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($minLen) {
|
if ($minLen) {
|
||||||
return strlen($res[$val]) >= $minLen;
|
return strlen($res[$val]) >= $minLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,11 +185,11 @@ trait AdminDirectoryController
|
||||||
'favourite_posts' => 'array|max:12',
|
'favourite_posts' => 'array|max:12',
|
||||||
'favourite_posts.*' => 'distinct',
|
'favourite_posts.*' => 'distinct',
|
||||||
'privacy_pledge' => 'sometimes',
|
'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([
|
$config = ConfigCache::firstOrNew([
|
||||||
'k' => 'pixelfed.directory'
|
'k' => 'pixelfed.directory',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$res = $config->v ? json_decode($config->v, true) : [];
|
$res = $config->v ? json_decode($config->v, true) : [];
|
||||||
|
@ -208,26 +199,27 @@ trait AdminDirectoryController
|
||||||
$res['contact_email'] = $request->input('contact_email');
|
$res['contact_email'] = $request->input('contact_email');
|
||||||
$res['privacy_pledge'] = (bool) $request->input('privacy_pledge');
|
$res['privacy_pledge'] = (bool) $request->input('privacy_pledge');
|
||||||
|
|
||||||
if($request->filled('location')) {
|
if ($request->filled('location')) {
|
||||||
$exists = (new ISO3166)->name($request->location);
|
$exists = (new ISO3166)->name($request->location);
|
||||||
if($exists) {
|
if ($exists) {
|
||||||
$res['location'] = $request->input('location');
|
$res['location'] = $request->input('location');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($request->hasFile('banner_image')) {
|
if ($request->hasFile('banner_image')) {
|
||||||
collect(Storage::files('public/headers'))
|
collect(Storage::files('public/headers'))
|
||||||
->filter(function($name) {
|
->filter(function ($name) {
|
||||||
$protected = [
|
$protected = [
|
||||||
'public/headers/.gitignore',
|
'public/headers/.gitignore',
|
||||||
'public/headers/default.jpg',
|
'public/headers/default.jpg',
|
||||||
'public/headers/missing.png'
|
'public/headers/missing.png',
|
||||||
];
|
];
|
||||||
return !in_array($name, $protected);
|
|
||||||
})
|
return ! in_array($name, $protected);
|
||||||
->each(function($name) {
|
})
|
||||||
Storage::delete($name);
|
->each(function ($name) {
|
||||||
});
|
Storage::delete($name);
|
||||||
|
});
|
||||||
$path = $request->file('banner_image')->storePublicly('public/headers');
|
$path = $request->file('banner_image')->storePublicly('public/headers');
|
||||||
$res['banner_image'] = $path;
|
$res['banner_image'] = $path;
|
||||||
ConfigCacheService::put('app.banner_image', url(Storage::url($path)));
|
ConfigCacheService::put('app.banner_image', url(Storage::url($path)));
|
||||||
|
@ -240,9 +232,10 @@ trait AdminDirectoryController
|
||||||
|
|
||||||
ConfigCacheService::put('pixelfed.directory', $config->v);
|
ConfigCacheService::put('pixelfed.directory', $config->v);
|
||||||
$updated = json_decode($config->v, true);
|
$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']));
|
$updated['banner_image'] = url(Storage::url($updated['banner_image']));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $updated;
|
return $updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,7 +246,7 @@ trait AdminDirectoryController
|
||||||
'open_registration' => (bool) config_cache('pixelfed.open_registration'),
|
'open_registration' => (bool) config_cache('pixelfed.open_registration'),
|
||||||
'curated_onboarding' => (bool) config_cache('instance.curated_registration.enabled'),
|
'curated_onboarding' => (bool) config_cache('instance.curated_registration.enabled'),
|
||||||
'activitypub_enabled' => config_cache('federation.activitypub.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(','),
|
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
|
||||||
'image_quality' => config_cache('pixelfed.image_quality'),
|
'image_quality' => config_cache('pixelfed.image_quality'),
|
||||||
'optimize_image' => config_cache('pixelfed.optimize_image'),
|
'optimize_image' => config_cache('pixelfed.optimize_image'),
|
||||||
|
@ -273,8 +266,8 @@ trait AdminDirectoryController
|
||||||
'oauth_enabled' => 'required|accepted',
|
'oauth_enabled' => 'required|accepted',
|
||||||
'media_types' => [
|
'media_types' => [
|
||||||
'required',
|
'required',
|
||||||
function ($attribute, $value, $fail) {
|
function ($attribute, $value, $fail) {
|
||||||
if (!in_array('image/jpeg', $value->toArray()) || !in_array('image/png', $value->toArray())) {
|
if (! in_array('image/jpeg', $value->toArray()) || ! in_array('image/png', $value->toArray())) {
|
||||||
$fail('You must enable image/jpeg and image/png support.');
|
$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_account_size' => 'required_if:enforce_account_limit,true|integer|min:1000000',
|
||||||
'max_album_length' => 'required|integer|min:4|max:20',
|
'max_album_length' => 'required|integer|min:4|max:20',
|
||||||
'account_deletion' => 'required|accepted',
|
'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);
|
return response()->json($validator->errors(), 422);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,6 +290,7 @@ trait AdminDirectoryController
|
||||||
|
|
||||||
$data = (new PixelfedDirectoryController())->buildListing();
|
$data = (new PixelfedDirectoryController())->buildListing();
|
||||||
$res = Http::withoutVerifying()->post('https://pixelfed.org/api/v1/directory/submission', $data);
|
$res = Http::withoutVerifying()->post('https://pixelfed.org/api/v1/directory/submission', $data);
|
||||||
|
|
||||||
return 200;
|
return 200;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,7 +298,7 @@ trait AdminDirectoryController
|
||||||
{
|
{
|
||||||
$bannerImage = ConfigCache::whereK('app.banner_image')->first();
|
$bannerImage = ConfigCache::whereK('app.banner_image')->first();
|
||||||
$directory = ConfigCache::whereK('pixelfed.directory')->first();
|
$directory = ConfigCache::whereK('pixelfed.directory')->first();
|
||||||
if(!$bannerImage && !$directory || empty($directory->v)) {
|
if (! $bannerImage && ! $directory || empty($directory->v)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$directoryArr = json_decode($directory->v, true);
|
$directoryArr = json_decode($directory->v, true);
|
||||||
|
@ -312,12 +306,12 @@ trait AdminDirectoryController
|
||||||
$protected = [
|
$protected = [
|
||||||
'public/headers/.gitignore',
|
'public/headers/.gitignore',
|
||||||
'public/headers/default.jpg',
|
'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;
|
return;
|
||||||
}
|
}
|
||||||
if(Storage::exists($directoryArr['banner_image'])) {
|
if (Storage::exists($directoryArr['banner_image'])) {
|
||||||
Storage::delete($directoryArr['banner_image']);
|
Storage::delete($directoryArr['banner_image']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -328,12 +322,13 @@ trait AdminDirectoryController
|
||||||
$bannerImage->save();
|
$bannerImage->save();
|
||||||
Cache::forget('api:v1:instance-data-response-v1');
|
Cache::forget('api:v1:instance-data-response-v1');
|
||||||
ConfigCacheService::put('pixelfed.directory', $directory);
|
ConfigCacheService::put('pixelfed.directory', $directory);
|
||||||
|
|
||||||
return $bannerImage->v;
|
return $bannerImage->v;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function directoryGetPopularPosts(Request $request)
|
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)
|
return Status::whereLocal(true)
|
||||||
->whereScope('public')
|
->whereScope('public')
|
||||||
->whereType('photo')
|
->whereType('photo')
|
||||||
|
@ -343,21 +338,21 @@ trait AdminDirectoryController
|
||||||
->pluck('id');
|
->pluck('id');
|
||||||
});
|
});
|
||||||
|
|
||||||
$res = $ids->map(function($id) {
|
$res = $ids->map(function ($id) {
|
||||||
return StatusService::get($id);
|
return StatusService::get($id);
|
||||||
})
|
})
|
||||||
->filter(function($post) {
|
->filter(function ($post) {
|
||||||
return $post && isset($post['account']);
|
return $post && isset($post['account']);
|
||||||
})
|
})
|
||||||
->values();
|
->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)
|
public function directoryGetAddPostByIdSearch(Request $request)
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'q' => 'required|integer'
|
'q' => 'required|integer',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$id = $request->input('q');
|
$id = $request->input('q');
|
||||||
|
@ -380,11 +375,12 @@ trait AdminDirectoryController
|
||||||
$profile_id = $request->input('profile_id');
|
$profile_id = $request->input('profile_id');
|
||||||
$testimonials = ConfigCache::whereK('pixelfed.directory.testimonials')->firstOrFail();
|
$testimonials = ConfigCache::whereK('pixelfed.directory.testimonials')->firstOrFail();
|
||||||
$existing = collect(json_decode($testimonials->v, true))
|
$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;
|
return $t['profile_id'] !== $profile_id;
|
||||||
})
|
})
|
||||||
->values();
|
->values();
|
||||||
ConfigCacheService::put('pixelfed.directory.testimonials', $existing);
|
ConfigCacheService::put('pixelfed.directory.testimonials', $existing);
|
||||||
|
|
||||||
return $existing;
|
return $existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,13 +388,13 @@ trait AdminDirectoryController
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'username' => 'required',
|
'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();
|
$user = User::whereUsername($request->input('username'))->whereNull('status')->firstOrFail();
|
||||||
|
|
||||||
$configCache = ConfigCache::firstOrCreate([
|
$configCache = ConfigCache::firstOrCreate([
|
||||||
'k' => 'pixelfed.directory.testimonials'
|
'k' => 'pixelfed.directory.testimonials',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$testimonials = $configCache->v ? collect(json_decode($configCache->v, true)) : collect([]);
|
$testimonials = $configCache->v ? collect(json_decode($configCache->v, true)) : collect([]);
|
||||||
|
@ -409,7 +405,7 @@ trait AdminDirectoryController
|
||||||
$testimonials->push([
|
$testimonials->push([
|
||||||
'profile_id' => (string) $user->profile_id,
|
'profile_id' => (string) $user->profile_id,
|
||||||
'username' => $request->input('username'),
|
'username' => $request->input('username'),
|
||||||
'body' => $request->input('body')
|
'body' => $request->input('body'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$configCache->v = json_encode($testimonials->toArray());
|
$configCache->v = json_encode($testimonials->toArray());
|
||||||
|
@ -417,8 +413,9 @@ trait AdminDirectoryController
|
||||||
ConfigCacheService::put('pixelfed.directory.testimonials', $configCache->v);
|
ConfigCacheService::put('pixelfed.directory.testimonials', $configCache->v);
|
||||||
$res = [
|
$res = [
|
||||||
'profile' => AccountService::get($user->profile_id),
|
'profile' => AccountService::get($user->profile_id),
|
||||||
'body' => $request->input('body')
|
'body' => $request->input('body'),
|
||||||
];
|
];
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,7 +423,7 @@ trait AdminDirectoryController
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'profile_id' => 'required',
|
'profile_id' => 'required',
|
||||||
'body' => 'required|string|min:5|max:500'
|
'body' => 'required|string|min:5|max:500',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$profile_id = $request->input('profile_id');
|
$profile_id = $request->input('profile_id');
|
||||||
|
@ -434,18 +431,19 @@ trait AdminDirectoryController
|
||||||
$user = User::whereProfileId($profile_id)->firstOrFail();
|
$user = User::whereProfileId($profile_id)->firstOrFail();
|
||||||
|
|
||||||
$configCache = ConfigCache::firstOrCreate([
|
$configCache = ConfigCache::firstOrCreate([
|
||||||
'k' => 'pixelfed.directory.testimonials'
|
'k' => 'pixelfed.directory.testimonials',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$testimonials = $configCache->v ? collect(json_decode($configCache->v, true)) : collect([]);
|
$testimonials = $configCache->v ? collect(json_decode($configCache->v, true)) : collect([]);
|
||||||
|
|
||||||
$updated = $testimonials->map(function($t) use($profile_id, $body) {
|
$updated = $testimonials->map(function ($t) use ($profile_id, $body) {
|
||||||
if($t['profile_id'] == $profile_id) {
|
if ($t['profile_id'] == $profile_id) {
|
||||||
$t['body'] = $body;
|
$t['body'] = $body;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $t;
|
return $t;
|
||||||
})
|
})
|
||||||
->values();
|
->values();
|
||||||
|
|
||||||
$configCache->v = json_encode($updated);
|
$configCache->v = json_encode($updated);
|
||||||
$configCache->save();
|
$configCache->save();
|
||||||
|
|
|
@ -7,7 +7,9 @@ use App\Models\InstanceActor;
|
||||||
use App\Page;
|
use App\Page;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
|
use App\Services\AdminSettingsService;
|
||||||
use App\Services\ConfigCacheService;
|
use App\Services\ConfigCacheService;
|
||||||
|
use App\Services\FilesystemService;
|
||||||
use App\User;
|
use App\User;
|
||||||
use App\Util\Site\Config;
|
use App\Util\Site\Config;
|
||||||
use Artisan;
|
use Artisan;
|
||||||
|
@ -71,6 +73,7 @@ trait AdminSettingsController
|
||||||
'admin_account_id' => 'nullable',
|
'admin_account_id' => 'nullable',
|
||||||
'regs' => 'required|in:open,filtered,closed',
|
'regs' => 'required|in:open,filtered,closed',
|
||||||
'account_migration' => 'nullable',
|
'account_migration' => 'nullable',
|
||||||
|
'rule_delete' => 'sometimes',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$orb = false;
|
$orb = false;
|
||||||
|
@ -310,4 +313,573 @@ trait AdminSettingsController
|
||||||
|
|
||||||
return view('admin.settings.system', compact('sys'));
|
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)
|
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');
|
return view('admin.custom-emoji.not-enabled');
|
||||||
}
|
}
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
|
@ -497,7 +497,7 @@ class AdminController extends Controller
|
||||||
|
|
||||||
public function customEmojiToggleActive(Request $request, $id)
|
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 = CustomEmoji::findOrFail($id);
|
||||||
$emoji->disabled = !$emoji->disabled;
|
$emoji->disabled = !$emoji->disabled;
|
||||||
$emoji->save();
|
$emoji->save();
|
||||||
|
@ -508,13 +508,13 @@ class AdminController extends Controller
|
||||||
|
|
||||||
public function customEmojiAdd(Request $request)
|
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');
|
return view('admin.custom-emoji.add');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function customEmojiStore(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'shortcode' => [
|
'shortcode' => [
|
||||||
'required',
|
'required',
|
||||||
|
@ -545,7 +545,7 @@ class AdminController extends Controller
|
||||||
|
|
||||||
public function customEmojiDelete(Request $request, $id)
|
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);
|
$emoji = CustomEmoji::findOrFail($id);
|
||||||
Storage::delete("public/{$emoji->media_path}");
|
Storage::delete("public/{$emoji->media_path}");
|
||||||
Cache::forget('pf:custom_emoji');
|
Cache::forget('pf:custom_emoji');
|
||||||
|
@ -555,7 +555,7 @@ class AdminController extends Controller
|
||||||
|
|
||||||
public function customEmojiShowDuplicates(Request $request, $id)
|
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();
|
$emoji = CustomEmoji::orderBy('id')->whereDisabled(false)->whereShortcode($id)->firstOrFail();
|
||||||
$emojis = CustomEmoji::whereShortcode($id)->where('id', '!=', $emoji->id)->cursorPaginate(10);
|
$emojis = CustomEmoji::whereShortcode($id)->where('id', '!=', $emoji->id)->cursorPaginate(10);
|
||||||
return view('admin.custom-emoji.duplicates', compact('emoji', 'emojis'));
|
return view('admin.custom-emoji.duplicates', compact('emoji', 'emojis'));
|
||||||
|
|
|
@ -131,7 +131,7 @@ class ApiV1Controller extends Controller
|
||||||
*/
|
*/
|
||||||
public function apps(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'client_name' => 'required',
|
'client_name' => 'required',
|
||||||
|
@ -1103,7 +1103,7 @@ class ApiV1Controller extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$count = UserFilterService::blockCount($pid);
|
$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) {
|
if ($count == 0) {
|
||||||
$filterCount = UserFilter::whereUserId($pid)
|
$filterCount = UserFilter::whereUserId($pid)
|
||||||
->whereFilterType('block')
|
->whereFilterType('block')
|
||||||
|
@ -1632,7 +1632,7 @@ class ApiV1Controller extends Controller
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'uri' => config('pixelfed.domain.app'),
|
'uri' => config('pixelfed.domain.app'),
|
||||||
'title' => config('app.name'),
|
'title' => config_cache('app.name'),
|
||||||
'short_description' => config_cache('app.short_description'),
|
'short_description' => config_cache('app.short_description'),
|
||||||
'description' => config_cache('app.description'),
|
'description' => config_cache('app.description'),
|
||||||
'email' => config('instance.email'),
|
'email' => config('instance.email'),
|
||||||
|
@ -1650,11 +1650,11 @@ class ApiV1Controller extends Controller
|
||||||
'configuration' => [
|
'configuration' => [
|
||||||
'media_attachments' => [
|
'media_attachments' => [
|
||||||
'image_matrix_limit' => 16777216,
|
'image_matrix_limit' => 16777216,
|
||||||
'image_size_limit' => config('pixelfed.max_photo_size') * 1024,
|
'image_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
|
||||||
'supported_mime_types' => explode(',', config('pixelfed.media_types')),
|
'supported_mime_types' => explode(',', config_cache('pixelfed.media_types')),
|
||||||
'video_frame_rate_limit' => 120,
|
'video_frame_rate_limit' => 120,
|
||||||
'video_matrix_limit' => 2304000,
|
'video_matrix_limit' => 2304000,
|
||||||
'video_size_limit' => config('pixelfed.max_photo_size') * 1024,
|
'video_size_limit' => config_cache('pixelfed.max_photo_size') * 1024,
|
||||||
],
|
],
|
||||||
'polls' => [
|
'polls' => [
|
||||||
'max_characters_per_option' => 50,
|
'max_characters_per_option' => 50,
|
||||||
|
@ -1665,7 +1665,7 @@ class ApiV1Controller extends Controller
|
||||||
'statuses' => [
|
'statuses' => [
|
||||||
'characters_reserved_per_url' => 23,
|
'characters_reserved_per_url' => 23,
|
||||||
'max_characters' => (int) config_cache('pixelfed.max_caption_length'),
|
'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);
|
$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) {
|
if ($count == 0) {
|
||||||
$filterCount = UserFilter::whereUserId($pid)
|
$filterCount = UserFilter::whereUserId($pid)
|
||||||
->whereFilterType('mute')
|
->whereFilterType('mute')
|
||||||
|
@ -3308,9 +3308,9 @@ class ApiV1Controller extends Controller
|
||||||
abort_unless($request->user()->tokenCan('write'), 403);
|
abort_unless($request->user()->tokenCan('write'), 403);
|
||||||
|
|
||||||
$this->validate($request, [
|
$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',
|
'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',
|
'sensitive' => 'nullable',
|
||||||
'visibility' => 'string|in:private,unlisted,public',
|
'visibility' => 'string|in:private,unlisted,public',
|
||||||
'spoiler_text' => 'sometimes|max:140',
|
'spoiler_text' => 'sometimes|max:140',
|
||||||
|
@ -3436,7 +3436,7 @@ class ApiV1Controller extends Controller
|
||||||
$mimes = [];
|
$mimes = [];
|
||||||
|
|
||||||
foreach ($ids as $k => $v) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
$m = Media::whereUserId($user->id)->whereNull('status_id')->findOrFail($v);
|
$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');
|
abort_if(config_cache('pixelfed.domain.app') == $domain, 400, 'Cannot ban your own server');
|
||||||
|
|
||||||
$existingCount = UserDomainBlock::whereProfileId($pid)->count();
|
$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]);
|
$errorMsg = __('profile.block.domain.max', ['max' => $maxLimit]);
|
||||||
|
|
||||||
abort_if($existingCount >= $maxLimit, 400, $errorMsg);
|
abort_if($existingCount >= $maxLimit, 400, $errorMsg);
|
||||||
|
|
|
@ -62,7 +62,7 @@ class ForgotPasswordController extends Controller
|
||||||
|
|
||||||
usleep(random_int(100000, 3000000));
|
usleep(random_int(100000, 3000000));
|
||||||
|
|
||||||
if(config('captcha.enabled')) {
|
if((bool) config_cache('captcha.enabled')) {
|
||||||
$rules = [
|
$rules = [
|
||||||
'email' => 'required|email',
|
'email' => 'required|email',
|
||||||
'h-captcha-response' => 'required|captcha'
|
'h-captcha-response' => 'required|captcha'
|
||||||
|
|
|
@ -74,10 +74,10 @@ class LoginController extends Controller
|
||||||
$messages = [];
|
$messages = [];
|
||||||
|
|
||||||
if(
|
if(
|
||||||
config('captcha.enabled') ||
|
(bool) config_cache('captcha.enabled') &&
|
||||||
config('captcha.active.login') ||
|
(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()->has('login_attempts') &&
|
||||||
request()->session()->get('login_attempts') >= config('captcha.triggers.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',
|
'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';
|
$rules['h-captcha-response'] = 'required|captcha';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ class ResetPasswordController extends Controller
|
||||||
{
|
{
|
||||||
usleep(random_int(100000, 3000000));
|
usleep(random_int(100000, 3000000));
|
||||||
|
|
||||||
if(config('captcha.enabled')) {
|
if((bool) config_cache('captcha.enabled')) {
|
||||||
return [
|
return [
|
||||||
'token' => 'required',
|
'token' => 'required',
|
||||||
'email' => 'required|email',
|
'email' => 'required|email',
|
||||||
|
|
|
@ -741,7 +741,7 @@ class ComposeController extends Controller
|
||||||
case 'image/jpeg':
|
case 'image/jpeg':
|
||||||
case 'image/png':
|
case 'image/png':
|
||||||
case 'video/mp4':
|
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;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -2,57 +2,42 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Jobs\InboxPipeline\{
|
use App\Jobs\InboxPipeline\DeleteWorker;
|
||||||
DeleteWorker,
|
use App\Jobs\InboxPipeline\InboxValidator;
|
||||||
InboxWorker,
|
use App\Jobs\InboxPipeline\InboxWorker;
|
||||||
InboxValidator
|
use App\Profile;
|
||||||
};
|
|
||||||
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\Services\AccountService;
|
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
|
class FederationController extends Controller
|
||||||
{
|
{
|
||||||
public function nodeinfoWellKnown()
|
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)
|
return response()->json(Nodeinfo::wellKnown(), 200, [], JSON_UNESCAPED_SLASHES)
|
||||||
->header('Access-Control-Allow-Origin','*');
|
->header('Access-Control-Allow-Origin', '*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function nodeinfo()
|
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)
|
return response()->json(Nodeinfo::get(), 200, [], JSON_UNESCAPED_SLASHES)
|
||||||
->header('Access-Control-Allow-Origin','*');
|
->header('Access-Control-Allow-Origin', '*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function webfinger(Request $request)
|
public function webfinger(Request $request)
|
||||||
{
|
{
|
||||||
if (!config('federation.webfinger.enabled') ||
|
if (! config('federation.webfinger.enabled') ||
|
||||||
!$request->has('resource') ||
|
! $request->has('resource') ||
|
||||||
!$request->filled('resource')
|
! $request->filled('resource')
|
||||||
) {
|
) {
|
||||||
return response('', 400);
|
return response('', 400);
|
||||||
}
|
}
|
||||||
|
@ -60,55 +45,56 @@ class FederationController extends Controller
|
||||||
$resource = $request->input('resource');
|
$resource = $request->input('resource');
|
||||||
$domain = config('pixelfed.domain.app');
|
$domain = config('pixelfed.domain.app');
|
||||||
|
|
||||||
if(config('federation.activitypub.sharedInbox') &&
|
if (config('federation.activitypub.sharedInbox') &&
|
||||||
$resource == 'acct:' . $domain . '@' . $domain) {
|
$resource == 'acct:'.$domain.'@'.$domain) {
|
||||||
$res = [
|
$res = [
|
||||||
'subject' => 'acct:' . $domain . '@' . $domain,
|
'subject' => 'acct:'.$domain.'@'.$domain,
|
||||||
'aliases' => [
|
'aliases' => [
|
||||||
'https://' . $domain . '/i/actor'
|
'https://'.$domain.'/i/actor',
|
||||||
],
|
],
|
||||||
'links' => [
|
'links' => [
|
||||||
[
|
[
|
||||||
'rel' => 'http://webfinger.net/rel/profile-page',
|
'rel' => 'http://webfinger.net/rel/profile-page',
|
||||||
'type' => 'text/html',
|
'type' => 'text/html',
|
||||||
'href' => 'https://' . $domain . '/site/kb/instance-actor'
|
'href' => 'https://'.$domain.'/site/kb/instance-actor',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'rel' => 'self',
|
'rel' => 'self',
|
||||||
'type' => 'application/activity+json',
|
'type' => 'application/activity+json',
|
||||||
'href' => 'https://' . $domain . '/i/actor'
|
'href' => 'https://'.$domain.'/i/actor',
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
|
return response()->json($res, 200, [], JSON_UNESCAPED_SLASHES);
|
||||||
}
|
}
|
||||||
$hash = hash('sha256', $resource);
|
$hash = hash('sha256', $resource);
|
||||||
$key = 'federation:webfinger:sha256:' . $hash;
|
$key = 'federation:webfinger:sha256:'.$hash;
|
||||||
if($cached = Cache::get($key)) {
|
if ($cached = Cache::get($key)) {
|
||||||
return response()->json($cached, 200, [], JSON_UNESCAPED_SLASHES);
|
return response()->json($cached, 200, [], JSON_UNESCAPED_SLASHES);
|
||||||
}
|
}
|
||||||
if(strpos($resource, $domain) == false) {
|
if (strpos($resource, $domain) == false) {
|
||||||
return response('', 400);
|
return response('', 400);
|
||||||
}
|
}
|
||||||
$parsed = Nickname::normalizeProfileUrl($resource);
|
$parsed = Nickname::normalizeProfileUrl($resource);
|
||||||
if(empty($parsed) || $parsed['domain'] !== $domain) {
|
if (empty($parsed) || $parsed['domain'] !== $domain) {
|
||||||
return response('', 400);
|
return response('', 400);
|
||||||
}
|
}
|
||||||
$username = $parsed['username'];
|
$username = $parsed['username'];
|
||||||
$profile = Profile::whereNull('domain')->whereUsername($username)->first();
|
$profile = Profile::whereNull('domain')->whereUsername($username)->first();
|
||||||
if(!$profile || $profile->status !== null) {
|
if (! $profile || $profile->status !== null) {
|
||||||
return response('', 400);
|
return response('', 400);
|
||||||
}
|
}
|
||||||
$webfinger = (new Webfinger($profile))->generate();
|
$webfinger = (new Webfinger($profile))->generate();
|
||||||
Cache::put($key, $webfinger, 1209600);
|
Cache::put($key, $webfinger, 1209600);
|
||||||
|
|
||||||
return response()->json($webfinger, 200, [], JSON_UNESCAPED_SLASHES)
|
return response()->json($webfinger, 200, [], JSON_UNESCAPED_SLASHES)
|
||||||
->header('Access-Control-Allow-Origin','*');
|
->header('Access-Control-Allow-Origin', '*');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function hostMeta(Request $request)
|
public function hostMeta(Request $request)
|
||||||
{
|
{
|
||||||
abort_if(!config('federation.webfinger.enabled'), 404);
|
abort_if(! config('federation.webfinger.enabled'), 404);
|
||||||
|
|
||||||
$path = route('well-known.webfinger');
|
$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>';
|
$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)
|
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()) {
|
if (! $request->wantsJson()) {
|
||||||
return redirect('/' . $username);
|
return redirect('/'.$username);
|
||||||
}
|
}
|
||||||
|
|
||||||
$id = AccountService::usernameToId($username);
|
$id = AccountService::usernameToId($username);
|
||||||
abort_if(!$id, 404);
|
abort_if(! $id, 404);
|
||||||
$account = AccountService::get($id);
|
$account = AccountService::get($id);
|
||||||
abort_if(!$account || !isset($account['statuses_count']), 404);
|
abort_if(! $account || ! isset($account['statuses_count']), 404);
|
||||||
$res = [
|
$res = [
|
||||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
'@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',
|
'type' => 'OrderedCollection',
|
||||||
'totalItems' => $account['statuses_count'] ?? 0,
|
'totalItems' => $account['statuses_count'] ?? 0,
|
||||||
];
|
];
|
||||||
|
@ -140,135 +126,145 @@ class FederationController extends Controller
|
||||||
|
|
||||||
public function userInbox(Request $request, $username)
|
public function userInbox(Request $request, $username)
|
||||||
{
|
{
|
||||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404);
|
||||||
abort_if(!config('federation.activitypub.inbox'), 404);
|
abort_if(! config('federation.activitypub.inbox'), 404);
|
||||||
|
|
||||||
$headers = $request->headers->all();
|
$headers = $request->headers->all();
|
||||||
$payload = $request->getContent();
|
$payload = $request->getContent();
|
||||||
if(!$payload || empty($payload)) {
|
if (! $payload || empty($payload)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$obj = json_decode($payload, true, 8);
|
$obj = json_decode($payload, true, 8);
|
||||||
if(!isset($obj['id'])) {
|
if (! isset($obj['id'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$domain = parse_url($obj['id'], PHP_URL_HOST);
|
$domain = parse_url($obj['id'], PHP_URL_HOST);
|
||||||
if(in_array($domain, InstanceService::getBannedDomains())) {
|
if (in_array($domain, InstanceService::getBannedDomains())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($obj['type']) && $obj['type'] === 'Delete') {
|
if (isset($obj['type']) && $obj['type'] === 'Delete') {
|
||||||
if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) {
|
if (isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) {
|
||||||
if($obj['object']['type'] === 'Person') {
|
if ($obj['object']['type'] === 'Person') {
|
||||||
if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) {
|
if (Profile::whereRemoteUrl($obj['object']['id'])->exists()) {
|
||||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox');
|
dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($obj['object']['type'] === 'Tombstone') {
|
if ($obj['object']['type'] === 'Tombstone') {
|
||||||
if(Status::whereObjectUrl($obj['object']['id'])->exists()) {
|
if (Status::whereObjectUrl($obj['object']['id'])->exists()) {
|
||||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('delete');
|
dispatch(new DeleteWorker($headers, $payload))->onQueue('delete');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($obj['object']['type'] === 'Story') {
|
if ($obj['object']['type'] === 'Story') {
|
||||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('story');
|
dispatch(new DeleteWorker($headers, $payload))->onQueue('story');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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');
|
dispatch(new InboxValidator($username, $headers, $payload))->onQueue('follow');
|
||||||
} else {
|
} else {
|
||||||
dispatch(new InboxValidator($username, $headers, $payload))->onQueue('high');
|
dispatch(new InboxValidator($username, $headers, $payload))->onQueue('high');
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function sharedInbox(Request $request)
|
public function sharedInbox(Request $request)
|
||||||
{
|
{
|
||||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
abort_if(! (bool) config_cache('federation.activitypub.enabled'), 404);
|
||||||
abort_if(!config('federation.activitypub.sharedInbox'), 404);
|
abort_if(! config('federation.activitypub.sharedInbox'), 404);
|
||||||
|
|
||||||
$headers = $request->headers->all();
|
$headers = $request->headers->all();
|
||||||
$payload = $request->getContent();
|
$payload = $request->getContent();
|
||||||
|
|
||||||
if(!$payload || empty($payload)) {
|
if (! $payload || empty($payload)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$obj = json_decode($payload, true, 8);
|
$obj = json_decode($payload, true, 8);
|
||||||
if(!isset($obj['id'])) {
|
if (! isset($obj['id'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$domain = parse_url($obj['id'], PHP_URL_HOST);
|
$domain = parse_url($obj['id'], PHP_URL_HOST);
|
||||||
if(in_array($domain, InstanceService::getBannedDomains())) {
|
if (in_array($domain, InstanceService::getBannedDomains())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($obj['type']) && $obj['type'] === 'Delete') {
|
if (isset($obj['type']) && $obj['type'] === 'Delete') {
|
||||||
if(isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) {
|
if (isset($obj['object']) && isset($obj['object']['type']) && isset($obj['object']['id'])) {
|
||||||
if($obj['object']['type'] === 'Person') {
|
if ($obj['object']['type'] === 'Person') {
|
||||||
if(Profile::whereRemoteUrl($obj['object']['id'])->exists()) {
|
if (Profile::whereRemoteUrl($obj['object']['id'])->exists()) {
|
||||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox');
|
dispatch(new DeleteWorker($headers, $payload))->onQueue('inbox');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($obj['object']['type'] === 'Tombstone') {
|
if ($obj['object']['type'] === 'Tombstone') {
|
||||||
if(Status::whereObjectUrl($obj['object']['id'])->exists()) {
|
if (Status::whereObjectUrl($obj['object']['id'])->exists()) {
|
||||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('delete');
|
dispatch(new DeleteWorker($headers, $payload))->onQueue('delete');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($obj['object']['type'] === 'Story') {
|
if ($obj['object']['type'] === 'Story') {
|
||||||
dispatch(new DeleteWorker($headers, $payload))->onQueue('story');
|
dispatch(new DeleteWorker($headers, $payload))->onQueue('story');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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');
|
dispatch(new InboxWorker($headers, $payload))->onQueue('follow');
|
||||||
} else {
|
} else {
|
||||||
dispatch(new InboxWorker($headers, $payload))->onQueue('shared');
|
dispatch(new InboxWorker($headers, $payload))->onQueue('shared');
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function userFollowing(Request $request, $username)
|
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);
|
$id = AccountService::usernameToId($username);
|
||||||
abort_if(!$id, 404);
|
abort_if(! $id, 404);
|
||||||
$account = AccountService::get($id);
|
$account = AccountService::get($id);
|
||||||
abort_if(!$account || !isset($account['following_count']), 404);
|
abort_if(! $account || ! isset($account['following_count']), 404);
|
||||||
$obj = [
|
$obj = [
|
||||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
'id' => $request->getUri(),
|
'id' => $request->getUri(),
|
||||||
'type' => 'OrderedCollection',
|
'type' => 'OrderedCollection',
|
||||||
'totalItems' => $account['following_count'] ?? 0,
|
'totalItems' => $account['following_count'] ?? 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($obj)->header('Content-Type', 'application/activity+json');
|
return response()->json($obj)->header('Content-Type', 'application/activity+json');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function userFollowers(Request $request, $username)
|
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);
|
$id = AccountService::usernameToId($username);
|
||||||
abort_if(!$id, 404);
|
abort_if(! $id, 404);
|
||||||
$account = AccountService::get($id);
|
$account = AccountService::get($id);
|
||||||
abort_if(!$account || !isset($account['followers_count']), 404);
|
abort_if(! $account || ! isset($account['followers_count']), 404);
|
||||||
$obj = [
|
$obj = [
|
||||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
'id' => $request->getUri(),
|
'id' => $request->getUri(),
|
||||||
'type' => 'OrderedCollection',
|
'type' => 'OrderedCollection',
|
||||||
'totalItems' => $account['followers_count'] ?? 0,
|
'totalItems' => $account['followers_count'] ?? 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($obj)->header('Content-Type', 'application/activity+json');
|
return response()->json($obj)->header('Content-Type', 'application/activity+json');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ trait Instagram
|
||||||
{
|
{
|
||||||
public function 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');
|
abort(404, 'Feature not enabled');
|
||||||
}
|
}
|
||||||
return view('settings.import.instagram.home');
|
return view('settings.import.instagram.home');
|
||||||
|
@ -25,6 +25,9 @@ trait Instagram
|
||||||
|
|
||||||
public function instagramStart(Request $request)
|
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)
|
$completed = ImportJob::whereProfileId(Auth::user()->profile->id)
|
||||||
->whereService('instagram')
|
->whereService('instagram')
|
||||||
->whereNotNull('completed_at')
|
->whereNotNull('completed_at')
|
||||||
|
@ -38,6 +41,9 @@ trait Instagram
|
||||||
|
|
||||||
protected function instagramRedirectOrNew()
|
protected function instagramRedirectOrNew()
|
||||||
{
|
{
|
||||||
|
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||||
|
abort(404, 'Feature not enabled');
|
||||||
|
}
|
||||||
$profile = Auth::user()->profile;
|
$profile = Auth::user()->profile;
|
||||||
$exists = ImportJob::whereProfileId($profile->id)
|
$exists = ImportJob::whereProfileId($profile->id)
|
||||||
->whereService('instagram')
|
->whereService('instagram')
|
||||||
|
@ -61,6 +67,9 @@ trait Instagram
|
||||||
|
|
||||||
public function instagramStepOne(Request $request, $uuid)
|
public function instagramStepOne(Request $request, $uuid)
|
||||||
{
|
{
|
||||||
|
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||||
|
abort(404, 'Feature not enabled');
|
||||||
|
}
|
||||||
$profile = Auth::user()->profile;
|
$profile = Auth::user()->profile;
|
||||||
$job = ImportJob::whereProfileId($profile->id)
|
$job = ImportJob::whereProfileId($profile->id)
|
||||||
->whereNull('completed_at')
|
->whereNull('completed_at')
|
||||||
|
@ -72,6 +81,9 @@ trait Instagram
|
||||||
|
|
||||||
public function instagramStepOneStore(Request $request, $uuid)
|
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');
|
$max = 'max:' . config('pixelfed.import.instagram.limits.size');
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'media.*' => 'required|mimes:bin,jpeg,png,gif|'.$max,
|
'media.*' => 'required|mimes:bin,jpeg,png,gif|'.$max,
|
||||||
|
@ -114,6 +126,9 @@ trait Instagram
|
||||||
|
|
||||||
public function instagramStepTwo(Request $request, $uuid)
|
public function instagramStepTwo(Request $request, $uuid)
|
||||||
{
|
{
|
||||||
|
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||||
|
abort(404, 'Feature not enabled');
|
||||||
|
}
|
||||||
$profile = Auth::user()->profile;
|
$profile = Auth::user()->profile;
|
||||||
$job = ImportJob::whereProfileId($profile->id)
|
$job = ImportJob::whereProfileId($profile->id)
|
||||||
->whereNull('completed_at')
|
->whereNull('completed_at')
|
||||||
|
@ -125,6 +140,9 @@ trait Instagram
|
||||||
|
|
||||||
public function instagramStepTwoStore(Request $request, $uuid)
|
public function instagramStepTwoStore(Request $request, $uuid)
|
||||||
{
|
{
|
||||||
|
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||||
|
abort(404, 'Feature not enabled');
|
||||||
|
}
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'media' => 'required|file|max:1000'
|
'media' => 'required|file|max:1000'
|
||||||
]);
|
]);
|
||||||
|
@ -150,6 +168,9 @@ trait Instagram
|
||||||
|
|
||||||
public function instagramStepThree(Request $request, $uuid)
|
public function instagramStepThree(Request $request, $uuid)
|
||||||
{
|
{
|
||||||
|
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||||
|
abort(404, 'Feature not enabled');
|
||||||
|
}
|
||||||
$profile = Auth::user()->profile;
|
$profile = Auth::user()->profile;
|
||||||
$job = ImportJob::whereProfileId($profile->id)
|
$job = ImportJob::whereProfileId($profile->id)
|
||||||
->whereService('instagram')
|
->whereService('instagram')
|
||||||
|
@ -162,6 +183,9 @@ trait Instagram
|
||||||
|
|
||||||
public function instagramStepThreeStore(Request $request, $uuid)
|
public function instagramStepThreeStore(Request $request, $uuid)
|
||||||
{
|
{
|
||||||
|
if((bool) config_cache('pixelfed.import.instagram.enabled') != true) {
|
||||||
|
abort(404, 'Feature not enabled');
|
||||||
|
}
|
||||||
$profile = Auth::user()->profile;
|
$profile = Auth::user()->profile;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -179,7 +179,7 @@ class ImportPostController extends Controller
|
||||||
'required',
|
'required',
|
||||||
'file',
|
'file',
|
||||||
$mimes,
|
$mimes,
|
||||||
'max:' . config('pixelfed.max_photo_size')
|
'max:' . config_cache('pixelfed.max_photo_size')
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -2,44 +2,43 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Profile;
|
|
||||||
use App\Services\AccountService;
|
|
||||||
use App\Http\Resources\DirectoryProfile;
|
use App\Http\Resources\DirectoryProfile;
|
||||||
|
use App\Profile;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class LandingController extends Controller
|
class LandingController extends Controller
|
||||||
{
|
{
|
||||||
public function directoryRedirect(Request $request)
|
public function directoryRedirect(Request $request)
|
||||||
{
|
{
|
||||||
if($request->user()) {
|
if ($request->user()) {
|
||||||
return redirect('/');
|
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)
|
public function exploreRedirect(Request $request)
|
||||||
{
|
{
|
||||||
if($request->user()) {
|
if ($request->user()) {
|
||||||
return redirect('/');
|
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)
|
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(
|
return DirectoryProfile::collection(
|
||||||
Profile::whereNull('domain')
|
Profile::whereNull('domain')
|
||||||
->whereIsSuggestable(true)
|
->whereIsSuggestable(true)
|
||||||
->orderByDesc('updated_at')
|
->orderByDesc('updated_at')
|
||||||
->cursorPaginate(20)
|
->cursorPaginate(20)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,30 +2,31 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Media;
|
use App\Media;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class MediaController extends Controller
|
class MediaController extends Controller
|
||||||
{
|
{
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
//return view('settings.drive.index');
|
//return view('settings.drive.index');
|
||||||
}
|
abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
public function composeUpdate(Request $request, $id)
|
public function composeUpdate(Request $request, $id)
|
||||||
{
|
{
|
||||||
abort(400, 'Endpoint deprecated');
|
abort(400, 'Endpoint deprecated');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function fallbackRedirect(Request $request, $pid, $mhash, $uhash, $f)
|
public function fallbackRedirect(Request $request, $pid, $mhash, $uhash, $f)
|
||||||
{
|
{
|
||||||
abort_if(!config_cache('pixelfed.cloud_storage'), 404);
|
abort_if(! (bool) config_cache('pixelfed.cloud_storage'), 404);
|
||||||
$path = 'public/m/_v2/' . $pid . '/' . $mhash . '/' . $uhash . '/' . $f;
|
$path = 'public/m/_v2/'.$pid.'/'.$mhash.'/'.$uhash.'/'.$f;
|
||||||
$media = Media::whereProfileId($pid)
|
$media = Media::whereProfileId($pid)
|
||||||
->whereMediaPath($path)
|
->whereMediaPath($path)
|
||||||
->whereNotNull('cdn_url')
|
->whereNotNull('cdn_url')
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
|
|
||||||
return redirect()->away($media->cdn_url);
|
return redirect()->away($media->cdn_url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,37 +2,41 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Models\ConfigCache;
|
use App\Models\ConfigCache;
|
||||||
use Storage;
|
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\StatusService;
|
use App\Services\StatusService;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Cache;
|
||||||
|
use Storage;
|
||||||
|
use App\Status;
|
||||||
|
use App\User;
|
||||||
|
|
||||||
class PixelfedDirectoryController extends Controller
|
class PixelfedDirectoryController extends Controller
|
||||||
{
|
{
|
||||||
public function get(Request $request)
|
public function get(Request $request)
|
||||||
{
|
{
|
||||||
if(!$request->filled('sk')) {
|
if (! $request->filled('sk')) {
|
||||||
abort(404);
|
abort(404);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!config_cache('pixelfed.directory.submission-key')) {
|
if (! config_cache('pixelfed.directory.submission-key')) {
|
||||||
abort(404);
|
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);
|
abort(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = $this->buildListing();
|
$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()
|
public function buildListing()
|
||||||
{
|
{
|
||||||
$res = config_cache('pixelfed.directory');
|
$res = config_cache('pixelfed.directory');
|
||||||
if($res) {
|
if ($res) {
|
||||||
$res = is_string($res) ? json_decode($res, true) : $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['_ts'] = config_cache('pixelfed.directory.submission-ts');
|
||||||
$res['version'] = config_cache('pixelfed.version');
|
$res['version'] = config_cache('pixelfed.version');
|
||||||
|
|
||||||
if(empty($res['summary'])) {
|
if (empty($res['summary'])) {
|
||||||
$summary = ConfigCache::whereK('app.short_description')->pluck('v');
|
$summary = ConfigCache::whereK('app.short_description')->pluck('v');
|
||||||
$res['summary'] = $summary ? $summary[0] : null;
|
$res['summary'] = $summary ? $summary[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($res['admin'])) {
|
if (isset($res['admin'])) {
|
||||||
$res['admin'] = AccountService::get($res['admin'], true);
|
$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']));
|
$res['banner_image'] = url(Storage::url($res['banner_image']));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($res['favourite_posts'])) {
|
if (isset($res['favourite_posts'])) {
|
||||||
$res['favourite_posts'] = collect($res['favourite_posts'])->map(function($id) {
|
$res['favourite_posts'] = collect($res['favourite_posts'])->map(function ($id) {
|
||||||
return StatusService::get($id);
|
return StatusService::get($id);
|
||||||
})
|
})
|
||||||
->filter(function($post) {
|
->filter(function ($post) {
|
||||||
return $post && isset($post['account']);
|
return $post && isset($post['account']);
|
||||||
})
|
})
|
||||||
->map(function($post) {
|
->map(function ($post) {
|
||||||
return [
|
return [
|
||||||
'avatar' => $post['account']['avatar'],
|
'avatar' => $post['account']['avatar'],
|
||||||
'display_name' => $post['account']['display_name'],
|
'display_name' => $post['account']['display_name'],
|
||||||
'username' => $post['account']['username'],
|
'username' => $post['account']['username'],
|
||||||
'media' => $post['media_attachments'][0]['url'],
|
'media' => $post['media_attachments'][0]['url'],
|
||||||
'url' => $post['url']
|
'url' => $post['url'],
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
->values();
|
->values();
|
||||||
}
|
}
|
||||||
|
|
||||||
$guidelines = ConfigCache::whereK('app.rules')->first();
|
$guidelines = ConfigCache::whereK('app.rules')->first();
|
||||||
if($guidelines) {
|
if ($guidelines) {
|
||||||
$res['community_guidelines'] = json_decode($guidelines->v, true);
|
$res['community_guidelines'] = json_decode($guidelines->v, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,27 +89,27 @@ class PixelfedDirectoryController extends Controller
|
||||||
$res['curated_onboarding'] = $curatedOnboarding;
|
$res['curated_onboarding'] = $curatedOnboarding;
|
||||||
|
|
||||||
$oauthEnabled = ConfigCache::whereK('pixelfed.oauth_enabled')->first();
|
$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'));
|
$keys = file_exists(storage_path('oauth-public.key')) && file_exists(storage_path('oauth-private.key'));
|
||||||
$res['oauth_enabled'] = (bool) $oauthEnabled && $keys;
|
$res['oauth_enabled'] = (bool) $oauthEnabled && $keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
$activityPubEnabled = ConfigCache::whereK('federation.activitypub.enabled')->first();
|
$activityPubEnabled = ConfigCache::whereK('federation.activitypub.enabled')->first();
|
||||||
if($activityPubEnabled) {
|
if ($activityPubEnabled) {
|
||||||
$res['activitypub_enabled'] = (bool) $activityPubEnabled;
|
$res['activitypub_enabled'] = (bool) $activityPubEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
$res['feature_config'] = [
|
$res['feature_config'] = [
|
||||||
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
|
'media_types' => Str::of(config_cache('pixelfed.media_types'))->explode(','),
|
||||||
'image_quality' => config_cache('pixelfed.image_quality'),
|
'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_photo_size' => config_cache('pixelfed.max_photo_size'),
|
||||||
'max_caption_length' => config_cache('pixelfed.max_caption_length'),
|
'max_caption_length' => config_cache('pixelfed.max_caption_length'),
|
||||||
'max_altext_length' => config_cache('pixelfed.max_altext_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_account_size' => config_cache('pixelfed.max_account_size'),
|
||||||
'max_album_length' => config_cache('pixelfed.max_album_length'),
|
'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') &&
|
$res['is_eligible'] = $this->validVal($res, 'admin') &&
|
||||||
|
@ -115,29 +119,36 @@ class PixelfedDirectoryController extends Controller
|
||||||
$this->validVal($res, 'privacy_pledge') &&
|
$this->validVal($res, 'privacy_pledge') &&
|
||||||
$this->validVal($res, 'location');
|
$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))
|
$res['testimonials'] = collect(json_decode(config_cache('pixelfed.directory.testimonials'), true))
|
||||||
->map(function($testimonial) {
|
->map(function ($testimonial) {
|
||||||
$profile = AccountService::get($testimonial['profile_id']);
|
$profile = AccountService::get($testimonial['profile_id']);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'profile' => [
|
'profile' => [
|
||||||
'username' => $profile['username'],
|
'username' => $profile['username'],
|
||||||
'display_name' => $profile['display_name'],
|
'display_name' => $profile['display_name'],
|
||||||
'avatar' => $profile['avatar'],
|
'avatar' => $profile['avatar'],
|
||||||
'created_at' => $profile['created_at']
|
'created_at' => $profile['created_at'],
|
||||||
],
|
],
|
||||||
'body' => $testimonial['body']
|
'body' => $testimonial['body'],
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$res['features_enabled'] = [
|
$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'] = [
|
$res['stats'] = [
|
||||||
'user_count' => \App\User::count(),
|
'user_count' => (int) $usersCount,
|
||||||
'post_count' => \App\Status::whereNull('uri')->count(),
|
'post_count' => (int) $statusesCount,
|
||||||
];
|
];
|
||||||
|
|
||||||
$res['primary_locale'] = config('app.locale');
|
$res['primary_locale'] = config('app.locale');
|
||||||
|
@ -150,19 +161,18 @@ class PixelfedDirectoryController extends Controller
|
||||||
|
|
||||||
protected function validVal($res, $val, $count = false, $minLen = false)
|
protected function validVal($res, $val, $count = false, $minLen = false)
|
||||||
{
|
{
|
||||||
if(!isset($res[$val])) {
|
if (! isset($res[$val])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($count) {
|
if ($count) {
|
||||||
return count($res[$val]) >= $count;
|
return count($res[$val]) >= $count;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($minLen) {
|
if ($minLen) {
|
||||||
return strlen($res[$val]) >= $minLen;
|
return strlen($res[$val]) >= $minLen;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $res[$val];
|
return $res[$val];
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,6 +172,8 @@ class ProfileController extends Controller
|
||||||
|
|
||||||
$user = $this->getCachedUser($username);
|
$user = $this->getCachedUser($username);
|
||||||
|
|
||||||
|
abort_if(!$user, 404);
|
||||||
|
|
||||||
return redirect($user->url());
|
return redirect($user->url());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,7 +373,7 @@ class ProfileController extends Controller
|
||||||
|
|
||||||
public function stories(Request $request, $username)
|
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();
|
$profile = Profile::whereNull('domain')->whereUsername($username)->firstOrFail();
|
||||||
$pid = $profile->id;
|
$pid = $profile->id;
|
||||||
$authed = Auth::user()->profile_id;
|
$authed = Auth::user()->profile_id;
|
||||||
|
|
|
@ -2,22 +2,20 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Services\Account\RemoteAuthService;
|
|
||||||
use App\Models\RemoteAuth;
|
use App\Models\RemoteAuth;
|
||||||
use App\Profile;
|
use App\Services\Account\RemoteAuthService;
|
||||||
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\EmailService;
|
use App\Services\EmailService;
|
||||||
use App\Services\MediaStorageService;
|
use App\Services\MediaStorageService;
|
||||||
|
use App\User;
|
||||||
use App\Util\ActivityPub\Helpers;
|
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 InvalidArgumentException;
|
||||||
|
use Purify;
|
||||||
|
|
||||||
class RemoteAuthController extends Controller
|
class RemoteAuthController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -30,9 +28,10 @@ class RemoteAuthController extends Controller
|
||||||
config('remote-auth.mastodon.ignore_closed_state') &&
|
config('remote-auth.mastodon.ignore_closed_state') &&
|
||||||
config('remote-auth.mastodon.enabled')
|
config('remote-auth.mastodon.enabled')
|
||||||
), 404);
|
), 404);
|
||||||
if($request->user()) {
|
if ($request->user()) {
|
||||||
return redirect('/');
|
return redirect('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('auth.remote.start');
|
return view('auth.remote.start');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,25 +50,27 @@ class RemoteAuthController extends Controller
|
||||||
config('remote-auth.mastodon.enabled')
|
config('remote-auth.mastodon.enabled')
|
||||||
), 404);
|
), 404);
|
||||||
|
|
||||||
if(config('remote-auth.mastodon.domains.only_custom')) {
|
if (config('remote-auth.mastodon.domains.only_custom')) {
|
||||||
$res = config('remote-auth.mastodon.domains.custom');
|
$res = config('remote-auth.mastodon.domains.custom');
|
||||||
if(!$res || !strlen($res)) {
|
if (! $res || ! strlen($res)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
$res = explode(',', $res);
|
$res = explode(',', $res);
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
if( config('remote-auth.mastodon.domains.custom') &&
|
if (config('remote-auth.mastodon.domains.custom') &&
|
||||||
!config('remote-auth.mastodon.domains.only_default') &&
|
! config('remote-auth.mastodon.domains.only_default') &&
|
||||||
strlen(config('remote-auth.mastodon.domains.custom')) > 3 &&
|
strlen(config('remote-auth.mastodon.domains.custom')) > 3 &&
|
||||||
strpos(config('remote-auth.mastodon.domains.custom'), '.') > -1
|
strpos(config('remote-auth.mastodon.domains.custom'), '.') > -1
|
||||||
) {
|
) {
|
||||||
$res = config('remote-auth.mastodon.domains.custom');
|
$res = config('remote-auth.mastodon.domains.custom');
|
||||||
if(!$res || !strlen($res)) {
|
if (! $res || ! strlen($res)) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
$res = explode(',', $res);
|
$res = explode(',', $res);
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,57 +94,62 @@ class RemoteAuthController extends Controller
|
||||||
|
|
||||||
$domain = $request->input('domain');
|
$domain = $request->input('domain');
|
||||||
|
|
||||||
if(str_starts_with(strtolower($domain), 'http')) {
|
if (str_starts_with(strtolower($domain), 'http')) {
|
||||||
$res = [
|
$res = [
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
'ready' => false,
|
'ready' => false,
|
||||||
'action' => 'incompatible_domain'
|
'action' => 'incompatible_domain',
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
$validateInstance = Helpers::validateUrl('https://' . $domain . '/?block-check=' . time());
|
$validateInstance = Helpers::validateUrl('https://'.$domain.'/?block-check='.time());
|
||||||
|
|
||||||
if(!$validateInstance) {
|
if (! $validateInstance) {
|
||||||
$res = [
|
$res = [
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
'ready' => false,
|
'ready' => false,
|
||||||
'action' => 'blocked_domain'
|
'action' => 'blocked_domain',
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
$compatible = RemoteAuthService::isDomainCompatible($domain);
|
$compatible = RemoteAuthService::isDomainCompatible($domain);
|
||||||
|
|
||||||
if(!$compatible) {
|
if (! $compatible) {
|
||||||
$res = [
|
$res = [
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
'ready' => false,
|
'ready' => false,
|
||||||
'action' => 'incompatible_domain'
|
'action' => 'incompatible_domain',
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($res);
|
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'));
|
$defaultDomains = explode(',', config('remote-auth.mastodon.domains.default'));
|
||||||
if(!in_array($domain, $defaultDomains)) {
|
if (! in_array($domain, $defaultDomains)) {
|
||||||
$res = [
|
$res = [
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
'ready' => false,
|
'ready' => false,
|
||||||
'action' => 'incompatible_domain'
|
'action' => 'incompatible_domain',
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($res);
|
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'));
|
$customDomains = explode(',', config('remote-auth.mastodon.domains.custom'));
|
||||||
if(!in_array($domain, $customDomains)) {
|
if (! in_array($domain, $customDomains)) {
|
||||||
$res = [
|
$res = [
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
'ready' => false,
|
'ready' => false,
|
||||||
'action' => 'incompatible_domain'
|
'action' => 'incompatible_domain',
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,13 +169,13 @@ class RemoteAuthController extends Controller
|
||||||
'state' => $state,
|
'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);
|
$dsh = Str::random(17);
|
||||||
$res = [
|
$res = [
|
||||||
'domain' => $domain,
|
'domain' => $domain,
|
||||||
'ready' => true,
|
'ready' => true,
|
||||||
'dsh' => $dsh
|
'dsh' => $dsh,
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->json($res);
|
return response()->json($res);
|
||||||
|
@ -185,7 +191,7 @@ class RemoteAuthController extends Controller
|
||||||
config('remote-auth.mastodon.enabled')
|
config('remote-auth.mastodon.enabled')
|
||||||
), 404);
|
), 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');
|
return redirect('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,7 +210,7 @@ class RemoteAuthController extends Controller
|
||||||
|
|
||||||
$domain = $request->session()->get('oauth_domain');
|
$domain = $request->session()->get('oauth_domain');
|
||||||
|
|
||||||
if($request->filled('code')) {
|
if ($request->filled('code')) {
|
||||||
$code = $request->input('code');
|
$code = $request->input('code');
|
||||||
$state = $request->session()->pull('state');
|
$state = $request->session()->pull('state');
|
||||||
|
|
||||||
|
@ -216,12 +222,14 @@ class RemoteAuthController extends Controller
|
||||||
|
|
||||||
$res = RemoteAuthService::getToken($domain, $code);
|
$res = RemoteAuthService::getToken($domain, $code);
|
||||||
|
|
||||||
if(!$res || !isset($res['access_token'])) {
|
if (! $res || ! isset($res['access_token'])) {
|
||||||
$request->session()->regenerate();
|
$request->session()->regenerate();
|
||||||
|
|
||||||
return redirect('/login');
|
return redirect('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
$request->session()->put('oauth_remote_session_token', $res['access_token']);
|
$request->session()->put('oauth_remote_session_token', $res['access_token']);
|
||||||
|
|
||||||
return redirect('/auth/mastodon/getting-started');
|
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.ignore_closed_state') &&
|
||||||
config('remote-auth.mastodon.enabled')
|
config('remote-auth.mastodon.enabled')
|
||||||
), 404);
|
), 404);
|
||||||
if($request->user()) {
|
if ($request->user()) {
|
||||||
return redirect('/');
|
return redirect('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('auth.remote.onboarding');
|
return view('auth.remote.onboarding');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,36 +270,36 @@ class RemoteAuthController extends Controller
|
||||||
|
|
||||||
$res = RemoteAuthService::getVerifyCredentials($domain, $token);
|
$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);
|
$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');
|
$limit = config('remote-auth.mastodon.max_uses.limit');
|
||||||
$uses = RemoteAuthService::lookupWebfingerUses($webfinger);
|
$uses = RemoteAuthService::lookupWebfingerUses($webfinger);
|
||||||
if($uses >= $limit) {
|
if ($uses >= $limit) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'msg' => 'Success!',
|
'msg' => 'Success!',
|
||||||
'action' => 'max_uses_reached'
|
'action' => 'max_uses_reached',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$exists = RemoteAuth::whereDomain($domain)->where('webfinger', $webfinger)->whereNotNull('user_id')->first();
|
$exists = RemoteAuth::whereDomain($domain)->where('webfinger', $webfinger)->whereNotNull('user_id')->first();
|
||||||
if($exists && $exists->user_id) {
|
if ($exists && $exists->user_id) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'msg' => 'Success!',
|
'msg' => 'Success!',
|
||||||
'action' => 'redirect_existing_user'
|
'action' => 'redirect_existing_user',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'msg' => 'Success!',
|
'msg' => 'Success!',
|
||||||
'action' => 'onboard'
|
'action' => 'onboard',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,7 +320,7 @@ class RemoteAuthController extends Controller
|
||||||
$token = $request->session()->get('oauth_remote_session_token');
|
$token = $request->session()->get('oauth_remote_session_token');
|
||||||
|
|
||||||
$res = RemoteAuthService::getVerifyCredentials($domain, $token);
|
$res = RemoteAuthService::getVerifyCredentials($domain, $token);
|
||||||
$res['_webfinger'] = strtolower('@' . $res['acct'] . '@' . $domain);
|
$res['_webfinger'] = strtolower('@'.$res['acct'].'@'.$domain);
|
||||||
$res['_domain'] = strtolower($domain);
|
$res['_domain'] = strtolower($domain);
|
||||||
$request->session()->put('oauth_remasto_id', $res['id']);
|
$request->session()->put('oauth_remasto_id', $res['id']);
|
||||||
|
|
||||||
|
@ -324,7 +333,7 @@ class RemoteAuthController extends Controller
|
||||||
'bearer_token' => $token,
|
'bearer_token' => $token,
|
||||||
'verify_credentials' => $res,
|
'verify_credentials' => $res,
|
||||||
'last_verify_credentials_at' => now(),
|
'last_verify_credentials_at' => now(),
|
||||||
'last_successful_login_at' => now()
|
'last_successful_login_at' => now(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$request->session()->put('oauth_masto_raid', $ra->id);
|
$request->session()->put('oauth_masto_raid', $ra->id);
|
||||||
|
@ -355,24 +364,24 @@ class RemoteAuthController extends Controller
|
||||||
$underscore = substr_count($value, '_');
|
$underscore = substr_count($value, '_');
|
||||||
$period = 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.');
|
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 (_).');
|
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.');
|
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.');
|
return $fail('Username is invalid. Must end with a letter or number.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$val = str_replace(['_', '.', '-'], '', $value);
|
$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 (_).');
|
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))) {
|
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
|
||||||
return $fail('Username cannot be used.');
|
return $fail('Username cannot be used.');
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
$username = strtolower($request->input('username'));
|
$username = strtolower($request->input('username'));
|
||||||
|
|
||||||
|
@ -390,7 +399,7 @@ class RemoteAuthController extends Controller
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'username' => $username,
|
'username' => $username,
|
||||||
'exists' => $exists
|
'exists' => $exists,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -411,7 +420,7 @@ class RemoteAuthController extends Controller
|
||||||
'email' => [
|
'email' => [
|
||||||
'required',
|
'required',
|
||||||
'email:strict,filter_unicode,dns,spoof',
|
'email:strict,filter_unicode,dns,spoof',
|
||||||
]
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$email = $request->input('email');
|
$email = $request->input('email');
|
||||||
|
@ -422,7 +431,7 @@ class RemoteAuthController extends Controller
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'email' => $email,
|
'email' => $email,
|
||||||
'exists' => $exists,
|
'exists' => $exists,
|
||||||
'banned' => $banned
|
'banned' => $banned,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,18 +454,18 @@ class RemoteAuthController extends Controller
|
||||||
|
|
||||||
$res = RemoteAuthService::getFollowing($domain, $token, $id);
|
$res = RemoteAuthService::getFollowing($domain, $token, $id);
|
||||||
|
|
||||||
if(!$res) {
|
if (! $res) {
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'code' => 200,
|
'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([
|
return response()->json([
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'following' => $res
|
'following' => $res,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -487,24 +496,24 @@ class RemoteAuthController extends Controller
|
||||||
$underscore = substr_count($value, '_');
|
$underscore = substr_count($value, '_');
|
||||||
$period = 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.');
|
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 (_).');
|
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.');
|
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.');
|
return $fail('Username is invalid. Must end with a letter or number.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$val = str_replace(['_', '.', '-'], '', $value);
|
$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 (_).');
|
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))) {
|
if (in_array(strtolower($value), array_map('strtolower', $restricted))) {
|
||||||
return $fail('Username cannot be used.');
|
return $fail('Username cannot be used.');
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
'password' => 'required|string|min:8|confirmed',
|
'password' => 'required|string|min:8|confirmed',
|
||||||
'name' => 'nullable|max:30'
|
'name' => 'nullable|max:30',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$email = $request->input('email');
|
$email = $request->input('email');
|
||||||
|
@ -527,7 +536,7 @@ class RemoteAuthController extends Controller
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'username' => $username,
|
'username' => $username,
|
||||||
'password' => $password,
|
'password' => $password,
|
||||||
'email' => $email
|
'email' => $email,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$raid = $request->session()->pull('oauth_masto_raid');
|
$raid = $request->session()->pull('oauth_masto_raid');
|
||||||
|
@ -541,7 +550,7 @@ class RemoteAuthController extends Controller
|
||||||
return [
|
return [
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'msg' => 'Success',
|
'msg' => 'Success',
|
||||||
'token' => $token
|
'token' => $token,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -585,7 +594,7 @@ class RemoteAuthController extends Controller
|
||||||
abort_unless($request->session()->exists('oauth_remasto_id'), 403);
|
abort_unless($request->session()->exists('oauth_remasto_id'), 403);
|
||||||
|
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'account' => 'required|url'
|
'account' => 'required|url',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$account = $request->input('account');
|
$account = $request->input('account');
|
||||||
|
@ -594,10 +603,10 @@ class RemoteAuthController extends Controller
|
||||||
$host = strtolower(config('pixelfed.domain.app'));
|
$host = strtolower(config('pixelfed.domain.app'));
|
||||||
$domain = strtolower(parse_url($account, PHP_URL_HOST));
|
$domain = strtolower(parse_url($account, PHP_URL_HOST));
|
||||||
|
|
||||||
if($domain == $host) {
|
if ($domain == $host) {
|
||||||
$username = Str::of($account)->explode('/')->last();
|
$username = Str::of($account)->explode('/')->last();
|
||||||
$user = User::where('username', $username)->first();
|
$user = User::where('username', $username)->first();
|
||||||
if($user) {
|
if ($user) {
|
||||||
return ['id' => (string) $user->profile_id];
|
return ['id' => (string) $user->profile_id];
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
|
@ -605,7 +614,7 @@ class RemoteAuthController extends Controller
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
$profile = Helpers::profileFetch($account);
|
$profile = Helpers::profileFetch($account);
|
||||||
if($profile) {
|
if ($profile) {
|
||||||
return ['id' => (string) $profile->id];
|
return ['id' => (string) $profile->id];
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
|
@ -635,13 +644,13 @@ class RemoteAuthController extends Controller
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
$profile = $user->profile;
|
$profile = $user->profile;
|
||||||
|
|
||||||
abort_if(!$profile->avatar, 404, 'Missing avatar');
|
abort_if(! $profile->avatar, 404, 'Missing avatar');
|
||||||
|
|
||||||
$avatar = $profile->avatar;
|
$avatar = $profile->avatar;
|
||||||
$avatar->remote_url = $request->input('avatar_url');
|
$avatar->remote_url = $request->input('avatar_url');
|
||||||
$avatar->save();
|
$avatar->save();
|
||||||
|
|
||||||
MediaStorageService::avatar($avatar, config_cache('pixelfed.cloud_storage') == false);
|
MediaStorageService::avatar($avatar, (bool) config_cache('pixelfed.cloud_storage') == false);
|
||||||
|
|
||||||
return [200];
|
return [200];
|
||||||
}
|
}
|
||||||
|
@ -657,7 +666,7 @@ class RemoteAuthController extends Controller
|
||||||
), 404);
|
), 404);
|
||||||
abort_unless($request->user(), 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();
|
$ra = RemoteAuth::where('user_id', $request->user()->id)->firstOrFail();
|
||||||
RemoteAuthService::submitToBeagle(
|
RemoteAuthService::submitToBeagle(
|
||||||
$ra->webfinger,
|
$ra->webfinger,
|
||||||
|
@ -691,19 +700,20 @@ class RemoteAuthController extends Controller
|
||||||
$user = User::findOrFail($ra->user_id);
|
$user = User::findOrFail($ra->user_id);
|
||||||
abort_if($user->is_admin || $user->status != null, 422, 'Invalid auth action');
|
abort_if($user->is_admin || $user->status != null, 422, 'Invalid auth action');
|
||||||
Auth::loginUsingId($ra->user_id);
|
Auth::loginUsingId($ra->user_id);
|
||||||
|
|
||||||
return [200];
|
return [200];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createUser($data)
|
protected function createUser($data)
|
||||||
{
|
{
|
||||||
event(new Registered($user = User::create([
|
event(new Registered($user = User::create([
|
||||||
'name' => Purify::clean($data['name']),
|
'name' => Purify::clean($data['name']),
|
||||||
'username' => $data['username'],
|
'username' => $data['username'],
|
||||||
'email' => $data['email'],
|
'email' => $data['email'],
|
||||||
'password' => Hash::make($data['password']),
|
'password' => Hash::make($data['password']),
|
||||||
'email_verified_at' => config('remote-auth.mastodon.contraints.skip_email_verification') ? now() : null,
|
'email_verified_at' => config('remote-auth.mastodon.contraints.skip_email_verification') ? now() : null,
|
||||||
'app_register_ip' => request()->ip(),
|
'app_register_ip' => request()->ip(),
|
||||||
'register_source' => 'mastodon'
|
'register_source' => 'mastodon',
|
||||||
])));
|
])));
|
||||||
|
|
||||||
$this->guarder()->login($user);
|
$this->guarder()->login($user);
|
||||||
|
|
|
@ -2,368 +2,367 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Auth;
|
|
||||||
use App\Hashtag;
|
use App\Hashtag;
|
||||||
use App\Place;
|
use App\Place;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
|
use App\Services\WebfingerService;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use Illuminate\Http\Request;
|
|
||||||
use App\Util\ActivityPub\Helpers;
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use Auth;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use App\Transformer\Api\{
|
|
||||||
AccountTransformer,
|
|
||||||
HashtagTransformer,
|
|
||||||
StatusTransformer,
|
|
||||||
};
|
|
||||||
use App\Services\WebfingerService;
|
|
||||||
|
|
||||||
class SearchController extends Controller
|
class SearchController extends Controller
|
||||||
{
|
{
|
||||||
public $tokens = [];
|
public $tokens = [];
|
||||||
public $term = '';
|
|
||||||
public $hash = '';
|
|
||||||
public $cacheKey = 'api:search:tag:';
|
|
||||||
|
|
||||||
public function __construct()
|
public $term = '';
|
||||||
{
|
|
||||||
$this->middleware('auth');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function searchAPI(Request $request)
|
public $hash = '';
|
||||||
{
|
|
||||||
$this->validate($request, [
|
|
||||||
'q' => 'required|string|min:3|max:120',
|
|
||||||
'src' => 'required|string|in:metro',
|
|
||||||
'v' => 'required|integer|in:2',
|
|
||||||
'scope' => 'required|in:all,hashtag,profile,remote,webfinger'
|
|
||||||
]);
|
|
||||||
|
|
||||||
$scope = $request->input('scope') ?? 'all';
|
public $cacheKey = 'api:search:tag:';
|
||||||
$this->term = e(urldecode($request->input('q')));
|
|
||||||
$this->hash = hash('sha256', $this->term);
|
|
||||||
|
|
||||||
switch ($scope) {
|
public function __construct()
|
||||||
case 'all':
|
{
|
||||||
$this->getHashtags();
|
$this->middleware('auth');
|
||||||
$this->getPosts();
|
}
|
||||||
$this->getProfiles();
|
|
||||||
// $this->getPlaces();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'hashtag':
|
public function searchAPI(Request $request)
|
||||||
$this->getHashtags();
|
{
|
||||||
break;
|
$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':
|
$scope = $request->input('scope') ?? 'all';
|
||||||
$this->getProfiles();
|
$this->term = e(urldecode($request->input('q')));
|
||||||
break;
|
$this->hash = hash('sha256', $this->term);
|
||||||
|
|
||||||
case 'webfinger':
|
switch ($scope) {
|
||||||
$this->webfingerSearch();
|
case 'all':
|
||||||
break;
|
$this->getHashtags();
|
||||||
|
$this->getPosts();
|
||||||
|
$this->getProfiles();
|
||||||
|
// $this->getPlaces();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'remote':
|
case 'hashtag':
|
||||||
$this->remoteLookupSearch();
|
$this->getHashtags();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'place':
|
case 'profile':
|
||||||
$this->getPlaces();
|
$this->getProfiles();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
case 'webfinger':
|
||||||
break;
|
$this->webfingerSearch();
|
||||||
}
|
break;
|
||||||
|
|
||||||
return response()->json($this->tokens, 200, [], JSON_PRETTY_PRINT);
|
case 'remote':
|
||||||
}
|
$this->remoteLookupSearch();
|
||||||
|
break;
|
||||||
|
|
||||||
protected function getPosts()
|
case 'place':
|
||||||
{
|
$this->getPlaces();
|
||||||
$tag = $this->term;
|
break;
|
||||||
$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();
|
|
||||||
|
|
||||||
if($posts->count() > 0) {
|
default:
|
||||||
$posts = $posts->map(function($item, $key) {
|
break;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getHashtags()
|
return response()->json($this->tokens, 200, [], JSON_PRETTY_PRINT);
|
||||||
{
|
}
|
||||||
$tag = $this->term;
|
|
||||||
$key = $this->cacheKey . 'hashtags:' . $this->hash;
|
|
||||||
$ttl = now()->addMinutes(1);
|
|
||||||
$tokens = Cache::remember($key, $ttl, function() use($tag) {
|
|
||||||
$htag = Str::startsWith($tag, '#') == true ? mb_substr($tag, 1) : $tag;
|
|
||||||
$hashtags = Hashtag::select('id', 'name', 'slug')
|
|
||||||
->where('slug', 'like', '%'.$htag.'%')
|
|
||||||
->whereHas('posts')
|
|
||||||
->limit(20)
|
|
||||||
->get();
|
|
||||||
if($hashtags->count() > 0) {
|
|
||||||
$tags = $hashtags->map(function ($item, $key) {
|
|
||||||
return [
|
|
||||||
'count' => $item->posts()->count(),
|
|
||||||
'url' => $item->url(),
|
|
||||||
'type' => 'hashtag',
|
|
||||||
'value' => $item->name,
|
|
||||||
'tokens' => '',
|
|
||||||
'name' => null,
|
|
||||||
];
|
|
||||||
});
|
|
||||||
return $tags;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
$this->tokens['hashtags'] = $tokens;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function getPlaces()
|
protected function getPosts()
|
||||||
{
|
{
|
||||||
$tag = $this->term;
|
$tag = $this->term;
|
||||||
// $key = $this->cacheKey . 'places:' . $this->hash;
|
$hash = hash('sha256', $tag);
|
||||||
// $ttl = now()->addHours(12);
|
if (Helpers::validateUrl($tag) != false &&
|
||||||
// $tokens = Cache::remember($key, $ttl, function() use($tag) {
|
Helpers::validateLocalUrl($tag) != true &&
|
||||||
$htag = Str::contains($tag, ',') == true ? explode(',', $tag) : [$tag];
|
(bool) config_cache('federation.activitypub.enabled') == true &&
|
||||||
$hashtags = Place::select('id', 'name', 'slug', 'country')
|
config('federation.activitypub.remoteFollow') == true
|
||||||
->where('name', 'like', '%'.$htag[0].'%')
|
) {
|
||||||
->paginate(20);
|
$remote = Helpers::fetchFromUrl($tag);
|
||||||
$tags = [];
|
if (isset($remote['type']) &&
|
||||||
if($hashtags->count() > 0) {
|
in_array($remote['type'], ['Note', 'Question'])
|
||||||
$tags = $hashtags->map(function ($item, $key) {
|
) {
|
||||||
return [
|
$item = Helpers::statusFetch($tag);
|
||||||
'count' => null,
|
$this->tokens['posts'] = [[
|
||||||
'url' => $item->url(),
|
'count' => 0,
|
||||||
'type' => 'place',
|
'url' => $item->url(),
|
||||||
'value' => $item->name . ', ' . $item->country,
|
'type' => 'status',
|
||||||
'tokens' => '',
|
'value' => "by {$item->profile->username} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
||||||
'name' => null,
|
'tokens' => [$item->caption],
|
||||||
'city' => $item->name,
|
'name' => $item->caption,
|
||||||
'country' => $item->country
|
'thumb' => $item->thumb(),
|
||||||
];
|
]];
|
||||||
});
|
}
|
||||||
// return $tags;
|
} else {
|
||||||
}
|
$posts = Status::select('id', 'profile_id', 'caption', 'created_at')
|
||||||
// });
|
->whereHas('media')
|
||||||
$this->tokens['places'] = $tags;
|
->whereNull('in_reply_to_id')
|
||||||
$this->tokens['placesPagination'] = [
|
->whereNull('reblog_of_id')
|
||||||
'total' => $hashtags->total(),
|
->whereProfileId(Auth::user()->profile_id)
|
||||||
'current_page' => $hashtags->currentPage(),
|
->where('caption', 'like', '%'.$tag.'%')
|
||||||
'last_page' => $hashtags->lastPage()
|
->latest()
|
||||||
];
|
->limit(10)
|
||||||
}
|
->get();
|
||||||
|
|
||||||
protected function getProfiles()
|
if ($posts->count() > 0) {
|
||||||
{
|
$posts = $posts->map(function ($item, $key) {
|
||||||
$tag = $this->term;
|
return [
|
||||||
$remoteKey = $this->cacheKey . 'profiles:remote:' . $this->hash;
|
'count' => 0,
|
||||||
$key = $this->cacheKey . 'profiles:' . $this->hash;
|
'url' => $item->url(),
|
||||||
$remoteTtl = now()->addMinutes(15);
|
'type' => 'status',
|
||||||
$ttl = now()->addHours(2);
|
'value' => "by {$item->profile->username} <span class='float-right'>{$item->created_at->diffForHumans(null, true, true)}</span>",
|
||||||
if( Helpers::validateUrl($tag) != false &&
|
'tokens' => [$item->caption],
|
||||||
Helpers::validateLocalUrl($tag) != true &&
|
'name' => $item->caption,
|
||||||
config_cache('federation.activitypub.enabled') == true &&
|
'thumb' => $item->thumb(),
|
||||||
config('federation.activitypub.remoteFollow') == true
|
'filter' => $item->firstMedia()->filter_class,
|
||||||
) {
|
];
|
||||||
$remote = Helpers::fetchFromUrl($tag);
|
});
|
||||||
if( isset($remote['type']) &&
|
$this->tokens['posts'] = $posts;
|
||||||
$remote['type'] == 'Person'
|
}
|
||||||
) {
|
}
|
||||||
$this->tokens['profiles'] = Cache::remember($remoteKey, $remoteTtl, function() use($tag) {
|
}
|
||||||
$item = Helpers::profileFirstOrNew($tag);
|
|
||||||
$tokens = [[
|
|
||||||
'count' => 1,
|
|
||||||
'url' => $item->url(),
|
|
||||||
'type' => 'profile',
|
|
||||||
'value' => $item->username,
|
|
||||||
'tokens' => [$item->username],
|
|
||||||
'name' => $item->name,
|
|
||||||
'entity' => [
|
|
||||||
'id' => (string) $item->id,
|
|
||||||
'following' => $item->followedBy(Auth::user()->profile),
|
|
||||||
'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id),
|
|
||||||
'thumb' => $item->avatarUrl(),
|
|
||||||
'local' => (bool) !$item->domain,
|
|
||||||
'post_count' => $item->statuses()->count()
|
|
||||||
]
|
|
||||||
]];
|
|
||||||
return $tokens;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else {
|
protected function getHashtags()
|
||||||
$this->tokens['profiles'] = Cache::remember($key, $ttl, function() use($tag) {
|
{
|
||||||
if(Str::startsWith($tag, '@')) {
|
$tag = $this->term;
|
||||||
$tag = substr($tag, 1);
|
$key = $this->cacheKey.'hashtags:'.$this->hash;
|
||||||
}
|
$ttl = now()->addMinutes(1);
|
||||||
$users = Profile::select('status', 'domain', 'username', 'name', 'id')
|
$tokens = Cache::remember($key, $ttl, function () use ($tag) {
|
||||||
->whereNull('status')
|
$htag = Str::startsWith($tag, '#') == true ? mb_substr($tag, 1) : $tag;
|
||||||
->where('username', 'like', '%'.$tag.'%')
|
$hashtags = Hashtag::select('id', 'name', 'slug')
|
||||||
->limit(20)
|
->where('slug', 'like', '%'.$htag.'%')
|
||||||
->orderBy('domain')
|
->whereHas('posts')
|
||||||
->get();
|
->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 $tags;
|
||||||
return $users->map(function ($item, $key) {
|
}
|
||||||
return [
|
});
|
||||||
'count' => 0,
|
$this->tokens['hashtags'] = $tokens;
|
||||||
'url' => $item->url(),
|
}
|
||||||
'type' => 'profile',
|
|
||||||
'value' => $item->username,
|
|
||||||
'tokens' => [$item->username],
|
|
||||||
'name' => $item->name,
|
|
||||||
'avatar' => $item->avatarUrl(),
|
|
||||||
'id' => (string) $item->id,
|
|
||||||
'entity' => [
|
|
||||||
'id' => (string) $item->id,
|
|
||||||
'following' => $item->followedBy(Auth::user()->profile),
|
|
||||||
'follow_request' => $item->hasFollowRequestById(Auth::user()->profile_id),
|
|
||||||
'thumb' => $item->avatarUrl(),
|
|
||||||
'local' => (bool) !$item->domain,
|
|
||||||
'post_count' => $item->statuses()->count()
|
|
||||||
]
|
|
||||||
];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function results(Request $request)
|
protected function getPlaces()
|
||||||
{
|
{
|
||||||
$this->validate($request, [
|
$tag = $this->term;
|
||||||
'q' => 'required|string|min:1',
|
// $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()
|
return $tokens;
|
||||||
{
|
});
|
||||||
$wfs = WebfingerService::lookup($this->term);
|
}
|
||||||
|
} 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)) {
|
if ($users->count() > 0) {
|
||||||
return;
|
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'] = [
|
public function results(Request $request)
|
||||||
[
|
{
|
||||||
'count' => 1,
|
$this->validate($request, [
|
||||||
'url' => $wfs['url'],
|
'q' => 'required|string|min:1',
|
||||||
'type' => 'profile',
|
]);
|
||||||
'value' => $wfs['username'],
|
|
||||||
'tokens' => [$wfs['username']],
|
|
||||||
'name' => $wfs['display_name'],
|
|
||||||
'entity' => [
|
|
||||||
'id' => (string) $wfs['id'],
|
|
||||||
'following' => null,
|
|
||||||
'follow_request' => null,
|
|
||||||
'thumb' => $wfs['avatar'],
|
|
||||||
'local' => (bool) $wfs['local']
|
|
||||||
]
|
|
||||||
]
|
|
||||||
];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function remotePostLookup()
|
return view('search.results');
|
||||||
{
|
}
|
||||||
$tag = $this->term;
|
|
||||||
$hash = hash('sha256', $tag);
|
|
||||||
$local = Helpers::validateLocalUrl($tag);
|
|
||||||
$valid = Helpers::validateUrl($tag);
|
|
||||||
|
|
||||||
if($valid == false || $local == true) {
|
protected function webfingerSearch()
|
||||||
return;
|
{
|
||||||
}
|
$wfs = WebfingerService::lookup($this->term);
|
||||||
|
|
||||||
if(Status::whereUri($tag)->whereLocal(false)->exists()) {
|
if (empty($wfs)) {
|
||||||
$item = Status::whereUri($tag)->first();
|
return;
|
||||||
$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);
|
$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()
|
protected function remotePostLookup()
|
||||||
{
|
{
|
||||||
if(!Helpers::validateUrl($this->term)) {
|
$tag = $this->term;
|
||||||
return;
|
$hash = hash('sha256', $tag);
|
||||||
}
|
$local = Helpers::validateLocalUrl($tag);
|
||||||
$this->getProfiles();
|
$valid = Helpers::validateUrl($tag);
|
||||||
$this->remotePostLookup();
|
|
||||||
}
|
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);
|
return $this->showActivityPub($request, $status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,54 +2,56 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers\Stories;
|
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\DirectMessage;
|
||||||
use App\Notification;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Story;
|
use App\Http\Resources\StoryView as StoryViewResource;
|
||||||
use App\Status;
|
|
||||||
use App\StoryView;
|
|
||||||
use App\Jobs\StoryPipeline\StoryDelete;
|
use App\Jobs\StoryPipeline\StoryDelete;
|
||||||
use App\Jobs\StoryPipeline\StoryFanout;
|
use App\Jobs\StoryPipeline\StoryFanout;
|
||||||
use App\Jobs\StoryPipeline\StoryReplyDeliver;
|
use App\Jobs\StoryPipeline\StoryReplyDeliver;
|
||||||
use App\Jobs\StoryPipeline\StoryViewDeliver;
|
use App\Jobs\StoryPipeline\StoryViewDeliver;
|
||||||
|
use App\Models\Conversation;
|
||||||
|
use App\Notification;
|
||||||
use App\Services\AccountService;
|
use App\Services\AccountService;
|
||||||
use App\Services\MediaPathService;
|
use App\Services\MediaPathService;
|
||||||
use App\Services\StoryService;
|
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
|
class StoryApiV1Controller extends Controller
|
||||||
{
|
{
|
||||||
const RECENT_KEY = 'pf:stories:recent-by-id:';
|
const RECENT_KEY = 'pf:stories:recent-by-id:';
|
||||||
|
|
||||||
const RECENT_TTL = 300;
|
const RECENT_TTL = 300;
|
||||||
|
|
||||||
public function carousel(Request $request)
|
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;
|
$pid = $request->user()->profile_id;
|
||||||
|
|
||||||
if(config('database.default') == 'pgsql') {
|
if (config('database.default') == 'pgsql') {
|
||||||
$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')
|
return Story::select('stories.*', 'followers.following_id')
|
||||||
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
||||||
->where('followers.profile_id', $pid)
|
->where('followers.profile_id', $pid)
|
||||||
->where('stories.active', true)
|
->where('stories.active', true)
|
||||||
->map(function($s) {
|
->map(function ($s) {
|
||||||
$r = new \StdClass;
|
$r = new \StdClass;
|
||||||
$r->id = $s->id;
|
$r->id = $s->id;
|
||||||
$r->profile_id = $s->profile_id;
|
$r->profile_id = $s->profile_id;
|
||||||
$r->type = $s->type;
|
$r->type = $s->type;
|
||||||
$r->path = $s->path;
|
$r->path = $s->path;
|
||||||
|
|
||||||
return $r;
|
return $r;
|
||||||
})
|
})
|
||||||
->unique('profile_id');
|
->unique('profile_id');
|
||||||
});
|
});
|
||||||
} else {
|
} 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')
|
return Story::select('stories.*', 'followers.following_id')
|
||||||
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
||||||
->where('followers.profile_id', $pid)
|
->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);
|
$profile = AccountService::get($s->profile_id, true);
|
||||||
if(!$profile || !isset($profile['id'])) {
|
if (! $profile || ! isset($profile['id'])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,50 +74,51 @@ class StoryApiV1Controller extends Controller
|
||||||
'src' => url(Storage::url($s->path)),
|
'src' => url(Storage::url($s->path)),
|
||||||
'duration' => $s->duration ?? 3,
|
'duration' => $s->duration ?? 3,
|
||||||
'seen' => StoryService::hasSeen($pid, $s->id),
|
'seen' => StoryService::hasSeen($pid, $s->id),
|
||||||
'created_at' => $s->created_at->format('c')
|
'created_at' => $s->created_at->format('c'),
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
->filter()
|
->filter()
|
||||||
->groupBy('pid')
|
->groupBy('pid')
|
||||||
->map(function($item) use($pid) {
|
->map(function ($item) use ($pid) {
|
||||||
$profile = AccountService::get($item[0]['pid'], true);
|
$profile = AccountService::get($item[0]['pid'], true);
|
||||||
$url = $profile['local'] ? url("/stories/{$profile['username']}") :
|
$url = $profile['local'] ? url("/stories/{$profile['username']}") :
|
||||||
url("/i/rs/{$profile['id']}");
|
url("/i/rs/{$profile['id']}");
|
||||||
return [
|
|
||||||
'id' => 'pfs:' . $profile['id'],
|
return [
|
||||||
'user' => [
|
'id' => 'pfs:'.$profile['id'],
|
||||||
'id' => (string) $profile['id'],
|
'user' => [
|
||||||
'username' => $profile['username'],
|
'id' => (string) $profile['id'],
|
||||||
'username_acct' => $profile['acct'],
|
'username' => $profile['username'],
|
||||||
'avatar' => $profile['avatar'],
|
'username_acct' => $profile['acct'],
|
||||||
'local' => $profile['local'],
|
'avatar' => $profile['avatar'],
|
||||||
'is_author' => $profile['id'] == $pid
|
'local' => $profile['local'],
|
||||||
],
|
'is_author' => $profile['id'] == $pid,
|
||||||
'nodes' => $item,
|
],
|
||||||
'url' => $url,
|
'nodes' => $item,
|
||||||
'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])),
|
'url' => $url,
|
||||||
];
|
'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])),
|
||||||
})
|
];
|
||||||
->sortBy('seen')
|
})
|
||||||
->values();
|
->sortBy('seen')
|
||||||
|
->values();
|
||||||
|
|
||||||
$res = [
|
$res = [
|
||||||
'self' => [],
|
'self' => [],
|
||||||
'nodes' => $nodes,
|
'nodes' => $nodes,
|
||||||
];
|
];
|
||||||
|
|
||||||
if(Story::whereProfileId($pid)->whereActive(true)->exists()) {
|
if (Story::whereProfileId($pid)->whereActive(true)->exists()) {
|
||||||
$selfStories = Story::whereProfileId($pid)
|
$selfStories = Story::whereProfileId($pid)
|
||||||
->whereActive(true)
|
->whereActive(true)
|
||||||
->get()
|
->get()
|
||||||
->map(function($s) use($pid) {
|
->map(function ($s) {
|
||||||
return [
|
return [
|
||||||
'id' => (string) $s->id,
|
'id' => (string) $s->id,
|
||||||
'type' => $s->type,
|
'type' => $s->type,
|
||||||
'src' => url(Storage::url($s->path)),
|
'src' => url(Storage::url($s->path)),
|
||||||
'duration' => $s->duration,
|
'duration' => $s->duration,
|
||||||
'seen' => true,
|
'seen' => true,
|
||||||
'created_at' => $s->created_at->format('c')
|
'created_at' => $s->created_at->format('c'),
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
->sortBy('id')
|
->sortBy('id')
|
||||||
|
@ -127,38 +130,40 @@ class StoryApiV1Controller extends Controller
|
||||||
'username' => $selfProfile['acct'],
|
'username' => $selfProfile['acct'],
|
||||||
'avatar' => $selfProfile['avatar'],
|
'avatar' => $selfProfile['avatar'],
|
||||||
'local' => $selfProfile['local'],
|
'local' => $selfProfile['local'],
|
||||||
'is_author' => true
|
'is_author' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
'nodes' => $selfStories,
|
'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)
|
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;
|
$pid = $request->user()->profile_id;
|
||||||
|
|
||||||
if(config('database.default') == 'pgsql') {
|
if (config('database.default') == 'pgsql') {
|
||||||
$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')
|
return Story::select('stories.*', 'followers.following_id')
|
||||||
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
||||||
->where('followers.profile_id', $pid)
|
->where('followers.profile_id', $pid)
|
||||||
->where('stories.active', true)
|
->where('stories.active', true)
|
||||||
->map(function($s) {
|
->map(function ($s) {
|
||||||
$r = new \StdClass;
|
$r = new \StdClass;
|
||||||
$r->id = $s->id;
|
$r->id = $s->id;
|
||||||
$r->profile_id = $s->profile_id;
|
$r->profile_id = $s->profile_id;
|
||||||
$r->type = $s->type;
|
$r->type = $s->type;
|
||||||
$r->path = $s->path;
|
$r->path = $s->path;
|
||||||
|
|
||||||
return $r;
|
return $r;
|
||||||
})
|
})
|
||||||
->unique('profile_id');
|
->unique('profile_id');
|
||||||
});
|
});
|
||||||
} else {
|
} 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')
|
return Story::select('stories.*', 'followers.following_id')
|
||||||
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
->leftJoin('followers', 'followers.following_id', 'stories.profile_id')
|
||||||
->where('followers.profile_id', $pid)
|
->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);
|
$profile = AccountService::get($s->profile_id, true);
|
||||||
if(!$profile || !isset($profile['id'])) {
|
if (! $profile || ! isset($profile['id'])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,32 +186,33 @@ class StoryApiV1Controller extends Controller
|
||||||
'src' => url(Storage::url($s->path)),
|
'src' => url(Storage::url($s->path)),
|
||||||
'duration' => $s->duration ?? 3,
|
'duration' => $s->duration ?? 3,
|
||||||
'seen' => StoryService::hasSeen($pid, $s->id),
|
'seen' => StoryService::hasSeen($pid, $s->id),
|
||||||
'created_at' => $s->created_at->format('c')
|
'created_at' => $s->created_at->format('c'),
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
->filter()
|
->filter()
|
||||||
->groupBy('pid')
|
->groupBy('pid')
|
||||||
->map(function($item) use($pid) {
|
->map(function ($item) use ($pid) {
|
||||||
$profile = AccountService::get($item[0]['pid'], true);
|
$profile = AccountService::get($item[0]['pid'], true);
|
||||||
$url = $profile['local'] ? url("/stories/{$profile['username']}") :
|
$url = $profile['local'] ? url("/stories/{$profile['username']}") :
|
||||||
url("/i/rs/{$profile['id']}");
|
url("/i/rs/{$profile['id']}");
|
||||||
return [
|
|
||||||
'id' => 'pfs:' . $profile['id'],
|
return [
|
||||||
'user' => [
|
'id' => 'pfs:'.$profile['id'],
|
||||||
'id' => (string) $profile['id'],
|
'user' => [
|
||||||
'username' => $profile['username'],
|
'id' => (string) $profile['id'],
|
||||||
'username_acct' => $profile['acct'],
|
'username' => $profile['username'],
|
||||||
'avatar' => $profile['avatar'],
|
'username_acct' => $profile['acct'],
|
||||||
'local' => $profile['local'],
|
'avatar' => $profile['avatar'],
|
||||||
'is_author' => $profile['id'] == $pid
|
'local' => $profile['local'],
|
||||||
],
|
'is_author' => $profile['id'] == $pid,
|
||||||
'nodes' => $item,
|
],
|
||||||
'url' => $url,
|
'nodes' => $item,
|
||||||
'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])),
|
'url' => $url,
|
||||||
];
|
'seen' => StoryService::hasSeen($pid, StoryService::latest($profile['id'])),
|
||||||
})
|
];
|
||||||
->sortBy('seen')
|
})
|
||||||
->values();
|
->sortBy('seen')
|
||||||
|
->values();
|
||||||
|
|
||||||
$selfProfile = AccountService::get($pid, true);
|
$selfProfile = AccountService::get($pid, true);
|
||||||
$res = [
|
$res = [
|
||||||
|
@ -216,7 +222,7 @@ class StoryApiV1Controller extends Controller
|
||||||
'username' => $selfProfile['acct'],
|
'username' => $selfProfile['acct'],
|
||||||
'avatar' => $selfProfile['avatar'],
|
'avatar' => $selfProfile['avatar'],
|
||||||
'local' => $selfProfile['local'],
|
'local' => $selfProfile['local'],
|
||||||
'is_author' => true
|
'is_author' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
'nodes' => [],
|
'nodes' => [],
|
||||||
|
@ -224,40 +230,41 @@ class StoryApiV1Controller extends Controller
|
||||||
'nodes' => $nodes,
|
'nodes' => $nodes,
|
||||||
];
|
];
|
||||||
|
|
||||||
if(Story::whereProfileId($pid)->whereActive(true)->exists()) {
|
if (Story::whereProfileId($pid)->whereActive(true)->exists()) {
|
||||||
$selfStories = Story::whereProfileId($pid)
|
$selfStories = Story::whereProfileId($pid)
|
||||||
->whereActive(true)
|
->whereActive(true)
|
||||||
->get()
|
->get()
|
||||||
->map(function($s) use($pid) {
|
->map(function ($s) {
|
||||||
return [
|
return [
|
||||||
'id' => (string) $s->id,
|
'id' => (string) $s->id,
|
||||||
'type' => $s->type,
|
'type' => $s->type,
|
||||||
'src' => url(Storage::url($s->path)),
|
'src' => url(Storage::url($s->path)),
|
||||||
'duration' => $s->duration,
|
'duration' => $s->duration,
|
||||||
'seen' => true,
|
'seen' => true,
|
||||||
'created_at' => $s->created_at->format('c')
|
'created_at' => $s->created_at->format('c'),
|
||||||
];
|
];
|
||||||
})
|
})
|
||||||
->sortBy('id')
|
->sortBy('id')
|
||||||
->values();
|
->values();
|
||||||
$res['self']['nodes'] = $selfStories;
|
$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)
|
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, [
|
$this->validate($request, [
|
||||||
'file' => function() {
|
'file' => function () {
|
||||||
return [
|
return [
|
||||||
'required',
|
'required',
|
||||||
'mimetypes:image/jpeg,image/png,video/mp4',
|
'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();
|
$user = $request->user();
|
||||||
|
@ -267,7 +274,7 @@ class StoryApiV1Controller extends Controller
|
||||||
->where('expires_at', '>', now())
|
->where('expires_at', '>', now())
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
if($count >= Story::MAX_PER_DAY) {
|
if ($count >= Story::MAX_PER_DAY) {
|
||||||
abort(418, 'You have reached your limit for new Stories today.');
|
abort(418, 'You have reached your limit for new Stories today.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,7 +284,7 @@ class StoryApiV1Controller extends Controller
|
||||||
$story = new Story();
|
$story = new Story();
|
||||||
$story->duration = $request->input('duration', 3);
|
$story->duration = $request->input('duration', 3);
|
||||||
$story->profile_id = $user->profile_id;
|
$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->mime = $photo->getMimeType();
|
||||||
$story->path = $path;
|
$story->path = $path;
|
||||||
$story->local = true;
|
$story->local = true;
|
||||||
|
@ -290,10 +297,10 @@ class StoryApiV1Controller extends Controller
|
||||||
|
|
||||||
$res = [
|
$res = [
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'msg' => 'Successfully added',
|
'msg' => 'Successfully added',
|
||||||
'media_id' => (string) $story->id,
|
'media_id' => (string) $story->id,
|
||||||
'media_url' => url(Storage::url($url)) . '?v=' . time(),
|
'media_url' => url(Storage::url($url)).'?v='.time(),
|
||||||
'media_type' => $story->type
|
'media_type' => $story->type,
|
||||||
];
|
];
|
||||||
|
|
||||||
return $res;
|
return $res;
|
||||||
|
@ -301,13 +308,13 @@ class StoryApiV1Controller extends Controller
|
||||||
|
|
||||||
public function publish(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'media_id' => 'required',
|
'media_id' => 'required',
|
||||||
'duration' => 'required|integer|min:0|max:30',
|
'duration' => 'required|integer|min:0|max:30',
|
||||||
'can_reply' => 'required|boolean',
|
'can_reply' => 'required|boolean',
|
||||||
'can_react' => 'required|boolean'
|
'can_react' => 'required|boolean',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$id = $request->input('media_id');
|
$id = $request->input('media_id');
|
||||||
|
@ -327,13 +334,13 @@ class StoryApiV1Controller extends Controller
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'msg' => 'Successfully published',
|
'msg' => 'Successfully published',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(Request $request, $id)
|
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();
|
$user = $request->user();
|
||||||
|
|
||||||
|
@ -346,16 +353,16 @@ class StoryApiV1Controller extends Controller
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'msg' => 'Successfully deleted'
|
'msg' => 'Successfully deleted',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function viewed(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'id' => 'required|min:1',
|
'id' => 'required|min:1',
|
||||||
]);
|
]);
|
||||||
$id = $request->input('id');
|
$id = $request->input('id');
|
||||||
|
|
||||||
|
@ -367,44 +374,45 @@ class StoryApiV1Controller extends Controller
|
||||||
|
|
||||||
$profile = $story->profile;
|
$profile = $story->profile;
|
||||||
|
|
||||||
if($story->profile_id == $authed->id) {
|
if ($story->profile_id == $authed->id) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$publicOnly = (bool) $profile->followedBy($authed);
|
$publicOnly = (bool) $profile->followedBy($authed);
|
||||||
abort_if(!$publicOnly, 403);
|
abort_if(! $publicOnly, 403);
|
||||||
|
|
||||||
$v = StoryView::firstOrCreate([
|
$v = StoryView::firstOrCreate([
|
||||||
'story_id' => $id,
|
'story_id' => $id,
|
||||||
'profile_id' => $authed->id
|
'profile_id' => $authed->id,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if($v->wasRecentlyCreated) {
|
if ($v->wasRecentlyCreated) {
|
||||||
Story::findOrFail($story->id)->increment('view_count');
|
Story::findOrFail($story->id)->increment('view_count');
|
||||||
|
|
||||||
if($story->local == false) {
|
if ($story->local == false) {
|
||||||
StoryViewDeliver::dispatch($story, $authed)->onQueue('story');
|
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);
|
StoryService::addSeen($authed->id, $story->id);
|
||||||
|
|
||||||
return ['code' => 200];
|
return ['code' => 200];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function comment(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'sid' => 'required',
|
'sid' => 'required',
|
||||||
'caption' => 'required|string'
|
'caption' => 'required|string',
|
||||||
]);
|
]);
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$text = $request->input('caption');
|
$text = $request->input('caption');
|
||||||
|
|
||||||
$story = Story::findOrFail($request->input('sid'));
|
$story = Story::findOrFail($request->input('sid'));
|
||||||
|
|
||||||
abort_if(!$story->can_reply, 422);
|
abort_if(! $story->can_reply, 422);
|
||||||
|
|
||||||
$status = new Status;
|
$status = new Status;
|
||||||
$status->type = 'story:reply';
|
$status->type = 'story:reply';
|
||||||
|
@ -415,7 +423,7 @@ class StoryApiV1Controller extends Controller
|
||||||
$status->visibility = 'direct';
|
$status->visibility = 'direct';
|
||||||
$status->in_reply_to_profile_id = $story->profile_id;
|
$status->in_reply_to_profile_id = $story->profile_id;
|
||||||
$status->entities = json_encode([
|
$status->entities = json_encode([
|
||||||
'story_id' => $story->id
|
'story_id' => $story->id,
|
||||||
]);
|
]);
|
||||||
$status->save();
|
$status->save();
|
||||||
|
|
||||||
|
@ -429,24 +437,24 @@ class StoryApiV1Controller extends Controller
|
||||||
'story_actor_username' => $request->user()->username,
|
'story_actor_username' => $request->user()->username,
|
||||||
'story_id' => $story->id,
|
'story_id' => $story->id,
|
||||||
'story_media_url' => url(Storage::url($story->path)),
|
'story_media_url' => url(Storage::url($story->path)),
|
||||||
'caption' => $text
|
'caption' => $text,
|
||||||
]);
|
]);
|
||||||
$dm->save();
|
$dm->save();
|
||||||
|
|
||||||
Conversation::updateOrInsert(
|
Conversation::updateOrInsert(
|
||||||
[
|
[
|
||||||
'to_id' => $story->profile_id,
|
'to_id' => $story->profile_id,
|
||||||
'from_id' => $pid
|
'from_id' => $pid,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'type' => 'story:comment',
|
'type' => 'story:comment',
|
||||||
'status_id' => $status->id,
|
'status_id' => $status->id,
|
||||||
'dm_id' => $dm->id,
|
'dm_id' => $dm->id,
|
||||||
'is_hidden' => false
|
'is_hidden' => false,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
if($story->local) {
|
if ($story->local) {
|
||||||
$n = new Notification;
|
$n = new Notification;
|
||||||
$n->profile_id = $dm->to_id;
|
$n->profile_id = $dm->to_id;
|
||||||
$n->actor_id = $dm->from_id;
|
$n->actor_id = $dm->from_id;
|
||||||
|
@ -460,33 +468,35 @@ class StoryApiV1Controller extends Controller
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'msg' => 'Sent!'
|
'msg' => 'Sent!',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function storeMedia($photo, $user)
|
protected function storeMedia($photo, $user)
|
||||||
{
|
{
|
||||||
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
||||||
if(in_array($photo->getMimeType(), [
|
if (in_array($photo->getMimeType(), [
|
||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
'image/png',
|
'image/png',
|
||||||
'video/mp4'
|
'video/mp4',
|
||||||
]) == false) {
|
]) == false) {
|
||||||
abort(400, 'Invalid media type');
|
abort(400, 'Invalid media type');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$storagePath = MediaPathService::story($user->profile);
|
$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;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function viewers(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'sid' => 'required|string|min:1|max:50'
|
'sid' => 'required|string|min:1|max:50',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
|
|
|
@ -2,59 +2,52 @@
|
||||||
|
|
||||||
namespace App\Http\Controllers;
|
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\DirectMessage;
|
||||||
use App\Notification;
|
use App\Jobs\StoryPipeline\StoryDelete;
|
||||||
use App\Status;
|
use App\Jobs\StoryPipeline\StoryFanout;
|
||||||
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\StoryReactionDeliver;
|
use App\Jobs\StoryPipeline\StoryReactionDeliver;
|
||||||
use App\Jobs\StoryPipeline\StoryReplyDeliver;
|
use App\Jobs\StoryPipeline\StoryReplyDeliver;
|
||||||
use App\Jobs\StoryPipeline\StoryFanout;
|
|
||||||
use App\Jobs\StoryPipeline\StoryDelete;
|
|
||||||
use ImageOptimizer;
|
|
||||||
use App\Models\Conversation;
|
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\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
|
class StoryComposeController extends Controller
|
||||||
{
|
{
|
||||||
public function apiV1Add(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'file' => function() {
|
'file' => function () {
|
||||||
return [
|
return [
|
||||||
'required',
|
'required',
|
||||||
'mimetypes:image/jpeg,image/png,video/mp4',
|
'mimetypes:image/jpeg,image/png,video/mp4',
|
||||||
'max:' . config_cache('pixelfed.max_photo_size'),
|
'max:'.config_cache('pixelfed.max_photo_size'),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $request->user();
|
$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)
|
$count = Story::whereProfileId($user->profile_id)
|
||||||
->whereActive(true)
|
->whereActive(true)
|
||||||
->where('expires_at', '>', now())
|
->where('expires_at', '>', now())
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
if($count >= Story::MAX_PER_DAY) {
|
if ($count >= Story::MAX_PER_DAY) {
|
||||||
abort(418, 'You have reached your limit for new Stories today.');
|
abort(418, 'You have reached your limit for new Stories today.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,7 +57,7 @@ class StoryComposeController extends Controller
|
||||||
$story = new Story();
|
$story = new Story();
|
||||||
$story->duration = 3;
|
$story->duration = 3;
|
||||||
$story->profile_id = $user->profile_id;
|
$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->mime = $photo->getMimeType();
|
||||||
$story->path = $path;
|
$story->path = $path;
|
||||||
$story->local = true;
|
$story->local = true;
|
||||||
|
@ -77,21 +70,22 @@ class StoryComposeController extends Controller
|
||||||
|
|
||||||
$res = [
|
$res = [
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'msg' => 'Successfully added',
|
'msg' => 'Successfully added',
|
||||||
'media_id' => (string) $story->id,
|
'media_id' => (string) $story->id,
|
||||||
'media_url' => url(Storage::url($url)) . '?v=' . time(),
|
'media_url' => url(Storage::url($url)).'?v='.time(),
|
||||||
'media_type' => $story->type
|
'media_type' => $story->type,
|
||||||
];
|
];
|
||||||
|
|
||||||
if($story->type === 'video') {
|
if ($story->type === 'video') {
|
||||||
$video = FFMpeg::open($path);
|
$video = FFMpeg::open($path);
|
||||||
$duration = $video->getDurationInSeconds();
|
$duration = $video->getDurationInSeconds();
|
||||||
$res['media_duration'] = $duration;
|
$res['media_duration'] = $duration;
|
||||||
if($duration > 500) {
|
if ($duration > 500) {
|
||||||
Storage::delete($story->path);
|
Storage::delete($story->path);
|
||||||
$story->delete();
|
$story->delete();
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Video duration cannot exceed 60 seconds'
|
'message' => 'Video duration cannot exceed 60 seconds',
|
||||||
], 422);
|
], 422);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,37 +96,39 @@ class StoryComposeController extends Controller
|
||||||
protected function storePhoto($photo, $user)
|
protected function storePhoto($photo, $user)
|
||||||
{
|
{
|
||||||
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
$mimes = explode(',', config_cache('pixelfed.media_types'));
|
||||||
if(in_array($photo->getMimeType(), [
|
if (in_array($photo->getMimeType(), [
|
||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
'image/png',
|
'image/png',
|
||||||
'video/mp4'
|
'video/mp4',
|
||||||
]) == false) {
|
]) == false) {
|
||||||
abort(400, 'Invalid media type');
|
abort(400, 'Invalid media type');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$storagePath = MediaPathService::story($user->profile);
|
$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());
|
||||||
if(in_array($photo->getMimeType(), ['image/jpeg','image/png'])) {
|
if (in_array($photo->getMimeType(), ['image/jpeg', 'image/png'])) {
|
||||||
$fpath = storage_path('app/' . $path);
|
$fpath = storage_path('app/'.$path);
|
||||||
$img = Intervention::make($fpath);
|
$img = Intervention::make($fpath);
|
||||||
$img->orientate();
|
$img->orientate();
|
||||||
$img->save($fpath, config_cache('pixelfed.image_quality'));
|
$img->save($fpath, config_cache('pixelfed.image_quality'));
|
||||||
$img->destroy();
|
$img->destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
return $path;
|
return $path;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function cropPhoto(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'media_id' => 'required|integer|min:1',
|
'media_id' => 'required|integer|min:1',
|
||||||
'width' => 'required',
|
'width' => 'required',
|
||||||
'height' => 'required',
|
'height' => 'required',
|
||||||
'x' => 'required',
|
'x' => 'required',
|
||||||
'y' => 'required'
|
'y' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
@ -144,13 +140,13 @@ class StoryComposeController extends Controller
|
||||||
|
|
||||||
$story = Story::whereProfileId($user->profile_id)->findOrFail($id);
|
$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.');
|
abort(400, 'Invalid or missing media.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if($story->type === 'photo') {
|
if ($story->type === 'photo') {
|
||||||
$img = Intervention::make($path);
|
$img = Intervention::make($path);
|
||||||
$img->crop($width, $height, $x, $y);
|
$img->crop($width, $height, $x, $y);
|
||||||
$img->resize(1080, 1920, function ($constraint) {
|
$img->resize(1080, 1920, function ($constraint) {
|
||||||
|
@ -161,24 +157,24 @@ class StoryComposeController extends Controller
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'msg' => 'Successfully cropped',
|
'msg' => 'Successfully cropped',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function publishStory(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'media_id' => 'required',
|
'media_id' => 'required',
|
||||||
'duration' => 'required|integer|min:3|max:120',
|
'duration' => 'required|integer|min:3|max:120',
|
||||||
'can_reply' => 'required|boolean',
|
'can_reply' => 'required|boolean',
|
||||||
'can_react' => 'required|boolean'
|
'can_react' => 'required|boolean',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$id = $request->input('media_id');
|
$id = $request->input('media_id');
|
||||||
$user = $request->user();
|
$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)
|
$story = Story::whereProfileId($user->profile_id)
|
||||||
->findOrFail($id);
|
->findOrFail($id);
|
||||||
|
|
||||||
|
@ -194,13 +190,13 @@ class StoryComposeController extends Controller
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'msg' => 'Successfully published',
|
'msg' => 'Successfully published',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function apiV1Delete(Request $request, $id)
|
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();
|
$user = $request->user();
|
||||||
|
|
||||||
|
@ -213,40 +209,40 @@ class StoryComposeController extends Controller
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'msg' => 'Successfully deleted'
|
'msg' => 'Successfully deleted',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function compose(Request $request)
|
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();
|
$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');
|
return view('stories.compose');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function createPoll(Request $request)
|
public function createPoll(Request $request)
|
||||||
{
|
{
|
||||||
abort_if(!config_cache('instance.stories.enabled') || !$request->user(), 404);
|
abort_if(! (bool) config_cache('instance.stories.enabled') || ! $request->user(), 404);
|
||||||
abort_if(!config_cache('instance.polls.enabled'), 404);
|
abort_if(! config_cache('instance.polls.enabled'), 404);
|
||||||
|
|
||||||
return $request->all();
|
return $request->all();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function publishStoryPoll(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'question' => 'required|string|min:6|max:140',
|
'question' => 'required|string|min:6|max:140',
|
||||||
'options' => 'required|array|min:2|max:4',
|
'options' => 'required|array|min:2|max:4',
|
||||||
'can_reply' => 'required|boolean',
|
'can_reply' => 'required|boolean',
|
||||||
'can_react' => 'required|boolean'
|
'can_react' => 'required|boolean',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $request->user();
|
$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;
|
$pid = $request->user()->profile_id;
|
||||||
|
|
||||||
$count = Story::whereProfileId($pid)
|
$count = Story::whereProfileId($pid)
|
||||||
|
@ -254,7 +250,7 @@ class StoryComposeController extends Controller
|
||||||
->where('expires_at', '>', now())
|
->where('expires_at', '>', now())
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
if($count >= Story::MAX_PER_DAY) {
|
if ($count >= Story::MAX_PER_DAY) {
|
||||||
abort(418, 'You have reached your limit for new Stories today.');
|
abort(418, 'You have reached your limit for new Stories today.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +258,7 @@ class StoryComposeController extends Controller
|
||||||
$story->type = 'poll';
|
$story->type = 'poll';
|
||||||
$story->story = json_encode([
|
$story->story = json_encode([
|
||||||
'question' => $request->input('question'),
|
'question' => $request->input('question'),
|
||||||
'options' => $request->input('options')
|
'options' => $request->input('options'),
|
||||||
]);
|
]);
|
||||||
$story->public = false;
|
$story->public = false;
|
||||||
$story->local = true;
|
$story->local = true;
|
||||||
|
@ -278,7 +274,7 @@ class StoryComposeController extends Controller
|
||||||
$poll->profile_id = $pid;
|
$poll->profile_id = $pid;
|
||||||
$poll->poll_options = $request->input('options');
|
$poll->poll_options = $request->input('options');
|
||||||
$poll->expires_at = $story->expires_at;
|
$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;
|
return 0;
|
||||||
})->toArray();
|
})->toArray();
|
||||||
$poll->save();
|
$poll->save();
|
||||||
|
@ -290,23 +286,23 @@ class StoryComposeController extends Controller
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'code' => 200,
|
'code' => 200,
|
||||||
'msg' => 'Successfully published',
|
'msg' => 'Successfully published',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function storyPollVote(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'sid' => 'required',
|
'sid' => 'required',
|
||||||
'ci' => 'required|integer|min:0|max:3'
|
'ci' => 'required|integer|min:0|max:3',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$ci = $request->input('ci');
|
$ci = $request->input('ci');
|
||||||
$story = Story::findOrFail($request->input('sid'));
|
$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();
|
$poll = Poll::whereStoryId($story->id)->firstOrFail();
|
||||||
|
|
||||||
$vote = new PollVote;
|
$vote = new PollVote;
|
||||||
|
@ -318,7 +314,7 @@ class StoryComposeController extends Controller
|
||||||
$vote->save();
|
$vote->save();
|
||||||
|
|
||||||
$poll->votes_count = $poll->votes_count + 1;
|
$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;
|
return $ci == $key ? $tally + 1 : $tally;
|
||||||
})->toArray();
|
})->toArray();
|
||||||
$poll->save();
|
$poll->save();
|
||||||
|
@ -328,15 +324,15 @@ class StoryComposeController extends Controller
|
||||||
|
|
||||||
public function storeReport(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'type' => 'required|alpha_dash',
|
'type' => 'required|alpha_dash',
|
||||||
'id' => 'required|integer|min:1',
|
'id' => 'required|integer|min:1',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$user = $request->user();
|
$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;
|
$pid = $request->user()->profile_id;
|
||||||
$sid = $request->input('id');
|
$sid = $request->input('id');
|
||||||
|
@ -353,24 +349,24 @@ class StoryComposeController extends Controller
|
||||||
'copyright',
|
'copyright',
|
||||||
'impersonation',
|
'impersonation',
|
||||||
'scam',
|
'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);
|
$story = Story::findOrFail($sid);
|
||||||
|
|
||||||
abort_if($story->profile_id == $pid, 422, 'Cannot report your own story');
|
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')
|
->whereObjectType('App\Story')
|
||||||
->whereObjectId($story->id)
|
->whereObjectId($story->id)
|
||||||
->exists()
|
->exists()
|
||||||
) {
|
) {
|
||||||
return response()->json(['error' => [
|
return response()->json(['error' => [
|
||||||
'code' => 409,
|
'code' => 409,
|
||||||
'message' => 'Cannot report the same story again'
|
'message' => 'Cannot report the same story again',
|
||||||
]], 409);
|
]], 409);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -389,18 +385,18 @@ class StoryComposeController extends Controller
|
||||||
|
|
||||||
public function react(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'sid' => 'required',
|
'sid' => 'required',
|
||||||
'reaction' => 'required|string'
|
'reaction' => 'required|string',
|
||||||
]);
|
]);
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$text = $request->input('reaction');
|
$text = $request->input('reaction');
|
||||||
$user = $request->user();
|
$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'));
|
$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');
|
abort_if(StoryService::reactCounter($story->id, $pid) >= 5, 422, 'You have already reacted to this story');
|
||||||
|
|
||||||
$status = new Status;
|
$status = new Status;
|
||||||
|
@ -413,7 +409,7 @@ class StoryComposeController extends Controller
|
||||||
$status->in_reply_to_profile_id = $story->profile_id;
|
$status->in_reply_to_profile_id = $story->profile_id;
|
||||||
$status->entities = json_encode([
|
$status->entities = json_encode([
|
||||||
'story_id' => $story->id,
|
'story_id' => $story->id,
|
||||||
'reaction' => $text
|
'reaction' => $text,
|
||||||
]);
|
]);
|
||||||
$status->save();
|
$status->save();
|
||||||
|
|
||||||
|
@ -427,24 +423,24 @@ class StoryComposeController extends Controller
|
||||||
'story_actor_username' => $request->user()->username,
|
'story_actor_username' => $request->user()->username,
|
||||||
'story_id' => $story->id,
|
'story_id' => $story->id,
|
||||||
'story_media_url' => url(Storage::url($story->path)),
|
'story_media_url' => url(Storage::url($story->path)),
|
||||||
'reaction' => $text
|
'reaction' => $text,
|
||||||
]);
|
]);
|
||||||
$dm->save();
|
$dm->save();
|
||||||
|
|
||||||
Conversation::updateOrInsert(
|
Conversation::updateOrInsert(
|
||||||
[
|
[
|
||||||
'to_id' => $story->profile_id,
|
'to_id' => $story->profile_id,
|
||||||
'from_id' => $pid
|
'from_id' => $pid,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'type' => 'story:react',
|
'type' => 'story:react',
|
||||||
'status_id' => $status->id,
|
'status_id' => $status->id,
|
||||||
'dm_id' => $dm->id,
|
'dm_id' => $dm->id,
|
||||||
'is_hidden' => false
|
'is_hidden' => false,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
if($story->local) {
|
if ($story->local) {
|
||||||
// generate notification
|
// generate notification
|
||||||
$n = new Notification;
|
$n = new Notification;
|
||||||
$n->profile_id = $dm->to_id;
|
$n->profile_id = $dm->to_id;
|
||||||
|
@ -464,18 +460,18 @@ class StoryComposeController extends Controller
|
||||||
|
|
||||||
public function comment(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'sid' => 'required',
|
'sid' => 'required',
|
||||||
'caption' => 'required|string'
|
'caption' => 'required|string',
|
||||||
]);
|
]);
|
||||||
$pid = $request->user()->profile_id;
|
$pid = $request->user()->profile_id;
|
||||||
$text = $request->input('caption');
|
$text = $request->input('caption');
|
||||||
$user = $request->user();
|
$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'));
|
$story = Story::findOrFail($request->input('sid'));
|
||||||
|
|
||||||
abort_if(!$story->can_reply, 422);
|
abort_if(! $story->can_reply, 422);
|
||||||
|
|
||||||
$status = new Status;
|
$status = new Status;
|
||||||
$status->type = 'story:reply';
|
$status->type = 'story:reply';
|
||||||
|
@ -486,7 +482,7 @@ class StoryComposeController extends Controller
|
||||||
$status->visibility = 'direct';
|
$status->visibility = 'direct';
|
||||||
$status->in_reply_to_profile_id = $story->profile_id;
|
$status->in_reply_to_profile_id = $story->profile_id;
|
||||||
$status->entities = json_encode([
|
$status->entities = json_encode([
|
||||||
'story_id' => $story->id
|
'story_id' => $story->id,
|
||||||
]);
|
]);
|
||||||
$status->save();
|
$status->save();
|
||||||
|
|
||||||
|
@ -500,24 +496,24 @@ class StoryComposeController extends Controller
|
||||||
'story_actor_username' => $request->user()->username,
|
'story_actor_username' => $request->user()->username,
|
||||||
'story_id' => $story->id,
|
'story_id' => $story->id,
|
||||||
'story_media_url' => url(Storage::url($story->path)),
|
'story_media_url' => url(Storage::url($story->path)),
|
||||||
'caption' => $text
|
'caption' => $text,
|
||||||
]);
|
]);
|
||||||
$dm->save();
|
$dm->save();
|
||||||
|
|
||||||
Conversation::updateOrInsert(
|
Conversation::updateOrInsert(
|
||||||
[
|
[
|
||||||
'to_id' => $story->profile_id,
|
'to_id' => $story->profile_id,
|
||||||
'from_id' => $pid
|
'from_id' => $pid,
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'type' => 'story:comment',
|
'type' => 'story:comment',
|
||||||
'status_id' => $status->id,
|
'status_id' => $status->id,
|
||||||
'dm_id' => $dm->id,
|
'dm_id' => $dm->id,
|
||||||
'is_hidden' => false
|
'is_hidden' => false,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
if($story->local) {
|
if ($story->local) {
|
||||||
// generate notification
|
// generate notification
|
||||||
$n = new Notification;
|
$n = new Notification;
|
||||||
$n->profile_id = $dm->to_id;
|
$n->profile_id = $dm->to_id;
|
||||||
|
|
|
@ -34,7 +34,7 @@ class StoryController extends StoryComposeController
|
||||||
{
|
{
|
||||||
public function recent(Request $request)
|
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();
|
$user = $request->user();
|
||||||
if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id)) {
|
if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id)) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -117,7 +117,7 @@ class StoryController extends StoryComposeController
|
||||||
|
|
||||||
public function profile(Request $request, $id)
|
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();
|
$user = $request->user();
|
||||||
if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id)) {
|
if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id)) {
|
||||||
|
@ -176,7 +176,7 @@ class StoryController extends StoryComposeController
|
||||||
|
|
||||||
public function viewed(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'id' => 'required|min:1',
|
'id' => 'required|min:1',
|
||||||
|
@ -221,7 +221,7 @@ class StoryController extends StoryComposeController
|
||||||
|
|
||||||
public function exists(Request $request, $id)
|
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();
|
$user = $request->user();
|
||||||
if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id)) {
|
if($user->has_roles && !UserRoleService::can('can-use-stories', $user->id)) {
|
||||||
return response()->json(false);
|
return response()->json(false);
|
||||||
|
@ -233,7 +233,7 @@ class StoryController extends StoryComposeController
|
||||||
|
|
||||||
public function iRedirect(Request $request)
|
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();
|
$user = $request->user();
|
||||||
abort_if(!$user, 404);
|
abort_if(!$user, 404);
|
||||||
|
@ -243,7 +243,7 @@ class StoryController extends StoryComposeController
|
||||||
|
|
||||||
public function viewers(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'sid' => 'required|string'
|
'sid' => 'required|string'
|
||||||
|
@ -274,7 +274,7 @@ class StoryController extends StoryComposeController
|
||||||
|
|
||||||
public function remoteStory(Request $request, $id)
|
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);
|
$profile = Profile::findOrFail($id);
|
||||||
if($profile->user_id != null || $profile->domain == null) {
|
if($profile->user_id != null || $profile->domain == null) {
|
||||||
|
@ -286,7 +286,7 @@ class StoryController extends StoryComposeController
|
||||||
|
|
||||||
public function pollResults(Request $request)
|
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, [
|
$this->validate($request, [
|
||||||
'sid' => 'required|string'
|
'sid' => 'required|string'
|
||||||
|
@ -304,7 +304,7 @@ class StoryController extends StoryComposeController
|
||||||
|
|
||||||
public function getActivityObject(Request $request, $username, $id)
|
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()) {
|
if(!$request->wantsJson()) {
|
||||||
return redirect('/stories/' . $username);
|
return redirect('/stories/' . $username);
|
||||||
|
|
|
@ -34,7 +34,7 @@ class UserEmailForgotController extends Controller
|
||||||
'username.exists' => 'This username is no longer active or does not exist!'
|
'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';
|
$rules['h-captcha-response'] = 'required|captcha';
|
||||||
$messages['h-captcha-response.required'] = 'You need to complete the captcha!';
|
$messages['h-captcha-response.required'] = 'You need to complete the captcha!';
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
|
|
||||||
namespace App\Http\Requests\Status;
|
namespace App\Http\Requests\Status;
|
||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
|
||||||
use App\Media;
|
use App\Media;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use Closure;
|
use Closure;
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
class StoreStatusEditRequest extends FormRequest
|
class StoreStatusEditRequest extends FormRequest
|
||||||
{
|
{
|
||||||
|
@ -14,24 +14,25 @@ class StoreStatusEditRequest extends FormRequest
|
||||||
*/
|
*/
|
||||||
public function authorize(): bool
|
public function authorize(): bool
|
||||||
{
|
{
|
||||||
$profile = $this->user()->profile;
|
$profile = $this->user()->profile;
|
||||||
if($profile->status != null) {
|
if ($profile->status != null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if($profile->unlisted == true && $profile->cw == true) {
|
if ($profile->unlisted == true && $profile->cw == true) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$types = [
|
$types = [
|
||||||
"photo",
|
'photo',
|
||||||
"photo:album",
|
'photo:album',
|
||||||
"photo:video:album",
|
'photo:video:album',
|
||||||
"reply",
|
'reply',
|
||||||
"text",
|
'text',
|
||||||
"video",
|
'video',
|
||||||
"video:album"
|
'video:album',
|
||||||
];
|
];
|
||||||
$scopes = ['public', 'unlisted', 'private'];
|
$scopes = ['public', 'unlisted', 'private'];
|
||||||
$status = Status::whereNull('reblog_of_id')->whereIn('type', $types)->whereIn('scope', $scopes)->find($this->route('id'));
|
$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;
|
return $status && $this->user()->profile_id === $status->profile_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,18 +48,18 @@ class StoreStatusEditRequest extends FormRequest
|
||||||
'spoiler_text' => 'nullable|string|max:140',
|
'spoiler_text' => 'nullable|string|max:140',
|
||||||
'sensitive' => 'sometimes|boolean',
|
'sensitive' => 'sometimes|boolean',
|
||||||
'media_ids' => [
|
'media_ids' => [
|
||||||
'nullable',
|
'nullable',
|
||||||
'required_without:status',
|
'required_without:status',
|
||||||
'array',
|
'array',
|
||||||
'max:' . config('pixelfed.max_album_length'),
|
'max:'.(int) config_cache('pixelfed.max_album_length'),
|
||||||
function (string $attribute, mixed $value, Closure $fail) {
|
function (string $attribute, mixed $value, Closure $fail) {
|
||||||
Media::whereProfileId($this->user()->profile_id)
|
Media::whereProfileId($this->user()->profile_id)
|
||||||
->where(function($query) {
|
->where(function ($query) {
|
||||||
return $query->whereNull('status_id')
|
return $query->whereNull('status_id')
|
||||||
->orWhere('status_id', '=', $this->route('id'));
|
->orWhere('status_id', '=', $this->route('id'));
|
||||||
})
|
})
|
||||||
->findOrFail($value);
|
->findOrFail($value);
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'location' => 'sometimes|nullable',
|
'location' => 'sometimes|nullable',
|
||||||
'location.id' => 'sometimes|integer|min:1|max:128769',
|
'location.id' => 'sometimes|integer|min:1|max:128769',
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
namespace App\Jobs\AvatarPipeline;
|
namespace App\Jobs\AvatarPipeline;
|
||||||
|
|
||||||
use Cache;
|
|
||||||
use App\Avatar;
|
use App\Avatar;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
|
use Cache;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
@ -17,88 +17,88 @@ use Storage;
|
||||||
|
|
||||||
class AvatarOptimize implements ShouldQueue
|
class AvatarOptimize implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
protected $profile;
|
protected $profile;
|
||||||
protected $current;
|
|
||||||
|
|
||||||
/**
|
protected $current;
|
||||||
* Delete the job if its models no longer exist.
|
|
||||||
*
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
public $deleteWhenMissingModels = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Delete the job if its models no longer exist.
|
||||||
*
|
*
|
||||||
* @return void
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public function __construct(Profile $profile, $current)
|
public $deleteWhenMissingModels = true;
|
||||||
{
|
|
||||||
$this->profile = $profile;
|
|
||||||
$this->current = $current;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the job.
|
* Create a new job instance.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function __construct(Profile $profile, $current)
|
||||||
{
|
{
|
||||||
$avatar = $this->profile->avatar;
|
$this->profile = $profile;
|
||||||
$file = storage_path("app/$avatar->media_path");
|
$this->current = $current;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
/**
|
||||||
$img = Intervention::make($file)->orientate();
|
* Execute the job.
|
||||||
$img->fit(200, 200, function ($constraint) {
|
*
|
||||||
$constraint->upsize();
|
* @return void
|
||||||
});
|
*/
|
||||||
$quality = config_cache('pixelfed.image_quality');
|
public function handle()
|
||||||
$img->save($file, $quality);
|
{
|
||||||
|
$avatar = $this->profile->avatar;
|
||||||
|
$file = storage_path("app/$avatar->media_path");
|
||||||
|
|
||||||
$avatar = Avatar::whereProfileId($this->profile->id)->firstOrFail();
|
try {
|
||||||
$avatar->change_count = ++$avatar->change_count;
|
$img = Intervention::make($file)->orientate();
|
||||||
$avatar->last_processed_at = Carbon::now();
|
$img->fit(200, 200, function ($constraint) {
|
||||||
$avatar->save();
|
$constraint->upsize();
|
||||||
Cache::forget('avatar:' . $avatar->profile_id);
|
});
|
||||||
$this->deleteOldAvatar($avatar->media_path, $this->current);
|
$quality = config_cache('pixelfed.image_quality');
|
||||||
|
$img->save($file, $quality);
|
||||||
|
|
||||||
if(config_cache('pixelfed.cloud_storage') && config('instance.avatar.local_to_cloud')) {
|
$avatar = Avatar::whereProfileId($this->profile->id)->firstOrFail();
|
||||||
$this->uploadToCloud($avatar);
|
$avatar->change_count = ++$avatar->change_count;
|
||||||
} else {
|
$avatar->last_processed_at = Carbon::now();
|
||||||
$avatar->cdn_url = null;
|
$avatar->save();
|
||||||
$avatar->save();
|
Cache::forget('avatar:'.$avatar->profile_id);
|
||||||
}
|
$this->deleteOldAvatar($avatar->media_path, $this->current);
|
||||||
} catch (Exception $e) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function deleteOldAvatar($new, $current)
|
if ((bool) config_cache('pixelfed.cloud_storage') && (bool) config_cache('instance.avatar.local_to_cloud')) {
|
||||||
{
|
$this->uploadToCloud($avatar);
|
||||||
if ( storage_path('app/'.$new) == $current ||
|
} else {
|
||||||
Str::endsWith($current, 'avatars/default.png') ||
|
$avatar->cdn_url = null;
|
||||||
Str::endsWith($current, 'avatars/default.jpg'))
|
$avatar->save();
|
||||||
{
|
}
|
||||||
return;
|
} catch (Exception $e) {
|
||||||
}
|
}
|
||||||
if (is_file($current)) {
|
}
|
||||||
@unlink($current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function uploadToCloud($avatar)
|
protected function deleteOldAvatar($new, $current)
|
||||||
{
|
{
|
||||||
$base = 'cache/avatars/' . $avatar->profile_id;
|
if (storage_path('app/'.$new) == $current ||
|
||||||
$disk = Storage::disk(config('filesystems.cloud'));
|
Str::endsWith($current, 'avatars/default.png') ||
|
||||||
$disk->deleteDirectory($base);
|
Str::endsWith($current, 'avatars/default.jpg')) {
|
||||||
$path = $base . '/' . 'avatar_' . strtolower(Str::random(random_int(3,6))) . $avatar->change_count . '.' . pathinfo($avatar->media_path, PATHINFO_EXTENSION);
|
return;
|
||||||
$url = $disk->put($path, Storage::get($avatar->media_path));
|
}
|
||||||
$avatar->media_path = $path;
|
if (is_file($current)) {
|
||||||
$avatar->cdn_url = $disk->url($path);
|
@unlink($current);
|
||||||
$avatar->save();
|
}
|
||||||
Storage::delete($avatar->media_path);
|
}
|
||||||
Cache::forget('avatar:' . $avatar->profile_id);
|
|
||||||
}
|
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\Avatar;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
|
use App\Services\MediaStorageService;
|
||||||
|
use App\Util\ActivityPub\Helpers;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
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
|
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.
|
* Delete the job if its models no longer exist.
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public $deleteWhenMissingModels = true;
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of times the job may be attempted.
|
* The number of times the job may be attempted.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
public $timeout = 300;
|
|
||||||
public $maxExceptions = 1;
|
|
||||||
|
|
||||||
/**
|
public $timeout = 300;
|
||||||
* Create a new job instance.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function __construct(Profile $profile)
|
|
||||||
{
|
|
||||||
$this->profile = $profile;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public $maxExceptions = 1;
|
||||||
* Execute the job.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$profile = $this->profile;
|
|
||||||
|
|
||||||
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) {
|
if ($profile->domain == null || $profile->private_key) {
|
||||||
$avatar = new Avatar;
|
return 1;
|
||||||
$avatar->profile_id = $profile->id;
|
}
|
||||||
$avatar->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
if($avatar->media_path == null && $avatar->remote_url == null) {
|
$avatar = Avatar::whereProfileId($profile->id)->first();
|
||||||
$avatar->media_path = 'public/avatars/default.jpg';
|
|
||||||
$avatar->is_remote = true;
|
|
||||||
$avatar->save();
|
|
||||||
}
|
|
||||||
|
|
||||||
$person = Helpers::fetchFromUrl($profile->remote_url);
|
if (! $avatar) {
|
||||||
|
$avatar = new Avatar;
|
||||||
|
$avatar->profile_id = $profile->id;
|
||||||
|
$avatar->save();
|
||||||
|
}
|
||||||
|
|
||||||
if(!$person || !isset($person['@context'])) {
|
if ($avatar->media_path == null && $avatar->remote_url == null) {
|
||||||
return 1;
|
$avatar->media_path = 'public/avatars/default.jpg';
|
||||||
}
|
$avatar->is_remote = true;
|
||||||
|
$avatar->save();
|
||||||
|
}
|
||||||
|
|
||||||
if( !isset($person['icon']) ||
|
$person = Helpers::fetchFromUrl($profile->remote_url);
|
||||||
!isset($person['icon']['type']) ||
|
|
||||||
!isset($person['icon']['url'])
|
|
||||||
) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if($person['icon']['type'] !== 'Image') {
|
if (! $person || ! isset($person['@context'])) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!Helpers::validateUrl($person['icon']['url'])) {
|
if (! isset($person['icon']) ||
|
||||||
return 1;
|
! 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'];
|
if (! Helpers::validateUrl($person['icon']['url'])) {
|
||||||
$avatar->save();
|
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\Avatar;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
|
use App\Services\AccountService;
|
||||||
|
use App\Services\MediaStorageService;
|
||||||
|
use Cache;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
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
|
class RemoteAvatarFetchFromUrl implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
protected $profile;
|
protected $profile;
|
||||||
protected $url;
|
|
||||||
|
|
||||||
/**
|
protected $url;
|
||||||
* Delete the job if its models no longer exist.
|
|
||||||
*
|
|
||||||
* @var bool
|
|
||||||
*/
|
|
||||||
public $deleteWhenMissingModels = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The number of times the job may be attempted.
|
* Delete the job if its models no longer exist.
|
||||||
*
|
*
|
||||||
* @var int
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public $tries = 1;
|
public $deleteWhenMissingModels = true;
|
||||||
public $timeout = 300;
|
|
||||||
public $maxExceptions = 1;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* The number of times the job may be attempted.
|
||||||
*
|
*
|
||||||
* @return void
|
* @var int
|
||||||
*/
|
*/
|
||||||
public function __construct(Profile $profile, $url)
|
public $tries = 1;
|
||||||
{
|
|
||||||
$this->profile = $profile;
|
|
||||||
$this->url = $url;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public $timeout = 300;
|
||||||
* Execute the job.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function handle()
|
|
||||||
{
|
|
||||||
$profile = $this->profile;
|
|
||||||
|
|
||||||
Cache::forget('avatar:' . $profile->id);
|
public $maxExceptions = 1;
|
||||||
AccountService::del($profile->id);
|
|
||||||
|
|
||||||
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) {
|
if ((bool) config_cache('pixelfed.cloud_storage') == false && (bool) config_cache('federation.avatars.store_local') == false) {
|
||||||
$avatar = new Avatar;
|
return 1;
|
||||||
$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, 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config('pixelfed.optimize_image') == false) {
|
if((bool) config_cache('pixelfed.optimize_image') == false) {
|
||||||
ImageThumbnail::dispatch($media)->onQueue('mmo');
|
ImageThumbnail::dispatch($media)->onQueue('mmo');
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -51,7 +51,7 @@ class ImageResize implements ShouldQueue
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!config('pixelfed.optimize_image')) {
|
if((bool) config_cache('pixelfed.optimize_image') === false) {
|
||||||
ImageThumbnail::dispatch($media)->onQueue('mmo');
|
ImageThumbnail::dispatch($media)->onQueue('mmo');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ class ImageUpdate implements ShouldQueue
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config('pixelfed.optimize_image')) {
|
if((bool) config_cache('pixelfed.optimize_image')) {
|
||||||
if (in_array($media->mime, $this->protectedMimes) == true) {
|
if (in_array($media->mime, $this->protectedMimes) == true) {
|
||||||
ImageOptimizer::optimize($thumb);
|
ImageOptimizer::optimize($thumb);
|
||||||
if(!$media->skip_optimize) {
|
if(!$media->skip_optimize) {
|
||||||
|
|
|
@ -3,27 +3,30 @@
|
||||||
namespace App\Jobs\MediaPipeline;
|
namespace App\Jobs\MediaPipeline;
|
||||||
|
|
||||||
use App\Media;
|
use App\Media;
|
||||||
|
use App\Services\Media\MediaHlsService;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
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\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 $timeout = 300;
|
||||||
|
|
||||||
public $tries = 3;
|
public $tries = 3;
|
||||||
|
|
||||||
public $maxExceptions = 1;
|
public $maxExceptions = 1;
|
||||||
|
|
||||||
public $failOnTimeout = true;
|
public $failOnTimeout = true;
|
||||||
|
|
||||||
public $deleteWhenMissingModels = true;
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,7 +41,7 @@ class MediaDeletePipeline implements ShouldQueue, ShouldBeUniqueUntilProcessing
|
||||||
*/
|
*/
|
||||||
public function uniqueId(): string
|
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()];
|
return [(new WithoutOverlapping("media:purge-job:id-{$this->media->id}"))->shared()->dontRelease()];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __construct(Media $media)
|
public function __construct(Media $media)
|
||||||
{
|
{
|
||||||
$this->media = $media;
|
$this->media = $media;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$media = $this->media;
|
$media = $this->media;
|
||||||
$path = $media->media_path;
|
$path = $media->media_path;
|
||||||
$thumb = $media->thumbnail_path;
|
$thumb = $media->thumbnail_path;
|
||||||
|
|
||||||
if(!$path) {
|
if (! $path) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
$e = explode('/', $path);
|
$e = explode('/', $path);
|
||||||
array_pop($e);
|
array_pop($e);
|
||||||
$i = implode('/', $e);
|
$i = implode('/', $e);
|
||||||
|
|
||||||
if(config_cache('pixelfed.cloud_storage') == true) {
|
if ((bool) config_cache('pixelfed.cloud_storage') == true) {
|
||||||
$disk = Storage::disk(config('filesystems.cloud'));
|
$disk = Storage::disk(config('filesystems.cloud'));
|
||||||
|
|
||||||
if($path && $disk->exists($path)) {
|
if ($path && $disk->exists($path)) {
|
||||||
$disk->delete($path);
|
$disk->delete($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($thumb && $disk->exists($thumb)) {
|
if ($thumb && $disk->exists($thumb)) {
|
||||||
$disk->delete($thumb);
|
$disk->delete($thumb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$disk = Storage::disk(config('filesystems.local'));
|
$disk = Storage::disk(config('filesystems.local'));
|
||||||
|
|
||||||
if($path && $disk->exists($path)) {
|
if ($path && $disk->exists($path)) {
|
||||||
$disk->delete($path);
|
$disk->delete($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($thumb && $disk->exists($thumb)) {
|
if ($thumb && $disk->exists($thumb)) {
|
||||||
$disk->delete($thumb);
|
$disk->delete($thumb);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($media->hls_path != null) {
|
if ($media->hls_path != null) {
|
||||||
$files = MediaHlsService::allFiles($media);
|
$files = MediaHlsService::allFiles($media);
|
||||||
if($files && count($files)) {
|
if ($files && count($files)) {
|
||||||
foreach($files as $file) {
|
foreach ($files as $file) {
|
||||||
$disk->delete($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\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
class MediaFixLocalFilesystemCleanupPipeline implements ShouldQueue
|
class MediaFixLocalFilesystemCleanupPipeline implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public $timeout = 1800;
|
public $timeout = 1800;
|
||||||
public $tries = 5;
|
|
||||||
public $maxExceptions = 1;
|
|
||||||
|
|
||||||
public function handle()
|
public $tries = 5;
|
||||||
{
|
|
||||||
if(config_cache('pixelfed.cloud_storage') == false) {
|
|
||||||
// Only run if cloud storage is enabled
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$disk = Storage::disk('local');
|
public $maxExceptions = 1;
|
||||||
$cloud = Storage::disk(config('filesystems.cloud'));
|
|
||||||
|
|
||||||
Media::whereNotNull(['status_id', 'cdn_url', 'replicated_at'])
|
public function handle()
|
||||||
->chunk(20, function ($medias) use($disk, $cloud) {
|
{
|
||||||
foreach($medias as $media) {
|
if ((bool) config_cache('pixelfed.cloud_storage') == false) {
|
||||||
if(!str_starts_with($media->media_path, 'public')) {
|
// Only run if cloud storage is enabled
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($disk->exists($media->media_path) && $cloud->exists($media->media_path)) {
|
$disk = Storage::disk('local');
|
||||||
$disk->delete($media->media_path);
|
$cloud = Storage::disk(config('filesystems.cloud'));
|
||||||
}
|
|
||||||
|
|
||||||
if($media->thumbnail_path) {
|
Media::whereNotNull(['status_id', 'cdn_url', 'replicated_at'])
|
||||||
if($disk->exists($media->thumbnail_path)) {
|
->chunk(20, function ($medias) use ($disk, $cloud) {
|
||||||
$disk->delete($media->thumbnail_path);
|
foreach ($medias as $media) {
|
||||||
}
|
if (! str_starts_with($media->media_path, 'public')) {
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$paths = explode('/', $media->media_path);
|
if ($disk->exists($media->media_path) && $cloud->exists($media->media_path)) {
|
||||||
if(count($paths) === 7) {
|
$disk->delete($media->media_path);
|
||||||
array_pop($paths);
|
}
|
||||||
$baseDir = implode('/', $paths);
|
|
||||||
|
|
||||||
if(count($disk->allFiles($baseDir)) === 0) {
|
if ($media->thumbnail_path) {
|
||||||
$disk->deleteDirectory($baseDir);
|
if ($disk->exists($media->thumbnail_path)) {
|
||||||
|
$disk->delete($media->thumbnail_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
array_pop($paths);
|
$paths = explode('/', $media->media_path);
|
||||||
$baseDir = implode('/', $paths);
|
if (count($paths) === 7) {
|
||||||
|
array_pop($paths);
|
||||||
|
$baseDir = implode('/', $paths);
|
||||||
|
|
||||||
if(count($disk->allFiles($baseDir)) === 0) {
|
if (count($disk->allFiles($baseDir)) === 0) {
|
||||||
$disk->deleteDirectory($baseDir);
|
$disk->deleteDirectory($baseDir);
|
||||||
|
|
||||||
array_pop($paths);
|
array_pop($paths);
|
||||||
$baseDir = implode('/', $paths);
|
$baseDir = implode('/', $paths);
|
||||||
|
|
||||||
if(count($disk->allFiles($baseDir)) === 0) {
|
if (count($disk->allFiles($baseDir)) === 0) {
|
||||||
$disk->deleteDirectory($baseDir);
|
$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 Storage;
|
||||||
use Zttp\Zttp;
|
use Zttp\Zttp;
|
||||||
use App\Util\ActivityPub\Helpers;
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use App\Services\MediaPathService;
|
||||||
|
|
||||||
class RemoteFollowImportRecent implements ShouldQueue
|
class RemoteFollowImportRecent implements ShouldQueue
|
||||||
{
|
{
|
||||||
|
@ -45,7 +46,6 @@ class RemoteFollowImportRecent implements ShouldQueue
|
||||||
'image/jpg',
|
'image/jpg',
|
||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
'image/png',
|
'image/png',
|
||||||
'image/gif',
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,9 +208,7 @@ class RemoteFollowImportRecent implements ShouldQueue
|
||||||
public function importMedia($url, $mime, $status)
|
public function importMedia($url, $mime, $status)
|
||||||
{
|
{
|
||||||
$user = $this->profile;
|
$user = $this->profile;
|
||||||
$monthHash = hash('sha1', date('Y').date('m'));
|
$storagePath = MediaPathService::get($user, 2);
|
||||||
$userHash = hash('sha1', $user->id.(string) $user->created_at);
|
|
||||||
$storagePath = "public/m/{$monthHash}/{$userHash}";
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$info = pathinfo($url);
|
$info = pathinfo($url);
|
||||||
|
|
|
@ -2,9 +2,15 @@
|
||||||
|
|
||||||
namespace App\Jobs\SharePipeline;
|
namespace App\Jobs\SharePipeline;
|
||||||
|
|
||||||
use Cache, Log;
|
use App\Jobs\HomeFeedPipeline\FeedInsertPipeline;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use App\Notification;
|
||||||
use App\{Status, 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\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
@ -12,141 +18,136 @@ use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
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
|
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.
|
* Delete the job if its models no longer exist.
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public $deleteWhenMissingModels = true;
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct(Status $status)
|
public function __construct(Status $status)
|
||||||
{
|
{
|
||||||
$this->status = $status;
|
$this->status = $status;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the job.
|
* Execute the job.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$status = $this->status;
|
$status = $this->status;
|
||||||
$parent = Status::find($this->status->reblog_of_id);
|
$parent = Status::find($this->status->reblog_of_id);
|
||||||
if(!$parent) {
|
if (! $parent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$actor = $status->profile;
|
$actor = $status->profile;
|
||||||
$target = $parent->profile;
|
$target = $parent->profile;
|
||||||
|
|
||||||
if ($status->uri !== null) {
|
if ($status->uri !== null) {
|
||||||
// Ignore notifications to remote statuses
|
// Ignore notifications to remote statuses
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($target->id === $status->profile_id) {
|
if ($target->id === $status->profile_id) {
|
||||||
$this->remoteAnnounceDeliver();
|
$this->remoteAnnounceDeliver();
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReblogService::addPostReblog($parent->profile_id, $status->id);
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
$parent->reblogs_count = $parent->reblogs_count + 1;
|
ReblogService::addPostReblog($parent->profile_id, $status->id);
|
||||||
$parent->save();
|
|
||||||
StatusService::del($parent->id);
|
|
||||||
|
|
||||||
Notification::firstOrCreate(
|
$parent->reblogs_count = $parent->reblogs_count + 1;
|
||||||
[
|
$parent->save();
|
||||||
'profile_id' => $target->id,
|
StatusService::del($parent->id);
|
||||||
'actor_id' => $actor->id,
|
|
||||||
'action' => 'share',
|
|
||||||
'item_type' => 'App\Status',
|
|
||||||
'item_id' => $status->reblog_of_id ?? $status->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()
|
return $this->remoteAnnounceDeliver();
|
||||||
{
|
}
|
||||||
if(config('app.env') !== 'production' || config_cache('federation.activitypub.enabled') == false) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
$status = $this->status;
|
|
||||||
$profile = $status->profile;
|
|
||||||
|
|
||||||
$fractal = new Fractal\Manager();
|
public function remoteAnnounceDeliver()
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
{
|
||||||
$resource = new Fractal\Resource\Item($status, new Announce());
|
if (config('app.env') !== 'production' || (bool) config_cache('federation.activitypub.enabled') == false) {
|
||||||
$activity = $fractal->createData($resource)->toArray();
|
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') {
|
$audience = $status->profile->getAudienceInbox();
|
||||||
// Return on profiles with no remote followers
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$payload = json_encode($activity);
|
if (empty($audience) || $status->scope != 'public') {
|
||||||
|
// Return on profiles with no remote followers
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$client = new Client([
|
$payload = json_encode($activity);
|
||||||
'timeout' => config('federation.activitypub.delivery.timeout')
|
|
||||||
]);
|
|
||||||
|
|
||||||
$version = config('pixelfed.version');
|
$client = new Client([
|
||||||
$appUrl = config('app.url');
|
'timeout' => config('federation.activitypub.delivery.timeout'),
|
||||||
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
]);
|
||||||
|
|
||||||
$requests = function($audience) use ($client, $activity, $profile, $payload, $userAgent) {
|
$version = config('pixelfed.version');
|
||||||
foreach($audience as $url) {
|
$appUrl = config('app.url');
|
||||||
$headers = HttpSignature::sign($profile, $url, $activity, [
|
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||||
'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), [
|
$requests = function ($audience) use ($client, $activity, $profile, $payload, $userAgent) {
|
||||||
'concurrency' => config('federation.activitypub.delivery.concurrency'),
|
foreach ($audience as $url) {
|
||||||
'fulfilled' => function ($response, $index) {
|
$headers = HttpSignature::sign($profile, $url, $activity, [
|
||||||
},
|
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||||
'rejected' => function ($reason, $index) {
|
'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;
|
namespace App\Jobs\SharePipeline;
|
||||||
|
|
||||||
use Cache, Log;
|
use App\Jobs\HomeFeedPipeline\FeedRemovePipeline;
|
||||||
use Illuminate\Support\Facades\Redis;
|
use App\Notification;
|
||||||
use App\{Status, 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\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
@ -12,128 +18,125 @@ use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
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
|
class UndoSharePipeline implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
protected $status;
|
|
||||||
public $deleteWhenMissingModels = true;
|
|
||||||
|
|
||||||
public function __construct(Status $status)
|
protected $status;
|
||||||
{
|
|
||||||
$this->status = $status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle()
|
public $deleteWhenMissingModels = true;
|
||||||
{
|
|
||||||
$status = $this->status;
|
|
||||||
$actor = $status->profile;
|
|
||||||
$parent = Status::find($status->reblog_of_id);
|
|
||||||
|
|
||||||
FeedRemovePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed');
|
public function __construct(Status $status)
|
||||||
|
{
|
||||||
|
$this->status = $status;
|
||||||
|
}
|
||||||
|
|
||||||
if($parent) {
|
public function handle()
|
||||||
$target = $parent->profile_id;
|
{
|
||||||
ReblogService::removePostReblog($parent->profile_id, $status->id);
|
$status = $this->status;
|
||||||
|
$actor = $status->profile;
|
||||||
|
$parent = Status::find($status->reblog_of_id);
|
||||||
|
|
||||||
if($parent->reblogs_count > 0) {
|
FeedRemovePipeline::dispatch($status->id, $status->profile_id)->onQueue('feed');
|
||||||
$parent->reblogs_count = $parent->reblogs_count - 1;
|
|
||||||
$parent->save();
|
|
||||||
StatusService::del($parent->id);
|
|
||||||
}
|
|
||||||
|
|
||||||
$notification = Notification::whereProfileId($target)
|
if ($parent) {
|
||||||
->whereActorId($status->profile_id)
|
$target = $parent->profile_id;
|
||||||
->whereAction('share')
|
ReblogService::removePostReblog($parent->profile_id, $status->id);
|
||||||
->whereItemId($status->reblog_of_id)
|
|
||||||
->whereItemType('App\Status')
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if($notification) {
|
if ($parent->reblogs_count > 0) {
|
||||||
$notification->forceDelete();
|
$parent->reblogs_count = $parent->reblogs_count - 1;
|
||||||
}
|
$parent->save();
|
||||||
}
|
StatusService::del($parent->id);
|
||||||
|
}
|
||||||
|
|
||||||
if ($status->uri != null) {
|
$notification = Notification::whereProfileId($target)
|
||||||
return;
|
->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) {
|
if ($notification) {
|
||||||
return $status->delete();
|
$notification->forceDelete();
|
||||||
} else {
|
}
|
||||||
return $this->remoteAnnounceDeliver();
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function remoteAnnounceDeliver()
|
if ($status->uri != null) {
|
||||||
{
|
return;
|
||||||
if(config('app.env') !== 'production' || config_cache('federation.activitypub.enabled') == false) {
|
}
|
||||||
|
|
||||||
|
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();
|
$status->delete();
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$status = $this->status;
|
return 1;
|
||||||
$profile = $status->profile;
|
}
|
||||||
|
|
||||||
$fractal = new Fractal\Manager();
|
$status = $this->status;
|
||||||
$fractal->setSerializer(new ArraySerializer());
|
$profile = $status->profile;
|
||||||
$resource = new Fractal\Resource\Item($status, new UndoAnnounce());
|
|
||||||
$activity = $fractal->createData($resource)->toArray();
|
|
||||||
|
|
||||||
$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') {
|
$audience = $status->profile->getAudienceInbox();
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
$payload = json_encode($activity);
|
if (empty($audience) || $status->scope != 'public') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
$client = new Client([
|
$payload = json_encode($activity);
|
||||||
'timeout' => config('federation.activitypub.delivery.timeout')
|
|
||||||
]);
|
|
||||||
|
|
||||||
$version = config('pixelfed.version');
|
$client = new Client([
|
||||||
$appUrl = config('app.url');
|
'timeout' => config('federation.activitypub.delivery.timeout'),
|
||||||
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
]);
|
||||||
|
|
||||||
$requests = function($audience) use ($client, $activity, $profile, $payload, $userAgent) {
|
$version = config('pixelfed.version');
|
||||||
foreach($audience as $url) {
|
$appUrl = config('app.url');
|
||||||
$headers = HttpSignature::sign($profile, $url, $activity, [
|
$userAgent = "(Pixelfed/{$version}; +{$appUrl})";
|
||||||
'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), [
|
$requests = function ($audience) use ($client, $activity, $profile, $payload, $userAgent) {
|
||||||
'concurrency' => config('federation.activitypub.delivery.concurrency'),
|
foreach ($audience as $url) {
|
||||||
'fulfilled' => function ($response, $index) {
|
$headers = HttpSignature::sign($profile, $url, $activity, [
|
||||||
},
|
'Content-Type' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
|
||||||
'rejected' => function ($reason, $index) {
|
'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;
|
namespace App\Jobs\StatusPipeline;
|
||||||
|
|
||||||
use DB, Cache, Storage;
|
use App\AccountInterstitial;
|
||||||
use App\{
|
use App\Bookmark;
|
||||||
AccountInterstitial,
|
use App\CollectionItem;
|
||||||
Bookmark,
|
use App\DirectMessage;
|
||||||
CollectionItem,
|
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
||||||
DirectMessage,
|
use App\Like;
|
||||||
Like,
|
use App\Media;
|
||||||
Media,
|
use App\MediaTag;
|
||||||
MediaTag,
|
use App\Mention;
|
||||||
Mention,
|
use App\Notification;
|
||||||
Notification,
|
use App\Report;
|
||||||
Report,
|
use App\Services\CollectionService;
|
||||||
Status,
|
use App\Services\NotificationService;
|
||||||
StatusArchived,
|
use App\Services\StatusService;
|
||||||
StatusHashtag,
|
use App\Status;
|
||||||
StatusView
|
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\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use League\Fractal;
|
use League\Fractal;
|
||||||
use Illuminate\Support\Str;
|
|
||||||
use League\Fractal\Serializer\ArraySerializer;
|
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
|
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.
|
* Delete the job if its models no longer exist.
|
||||||
*
|
*
|
||||||
* @var bool
|
* @var bool
|
||||||
*/
|
*/
|
||||||
public $deleteWhenMissingModels = true;
|
public $deleteWhenMissingModels = true;
|
||||||
|
|
||||||
public $timeout = 900;
|
public $timeout = 900;
|
||||||
|
|
||||||
public $tries = 2;
|
public $tries = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct(Status $status)
|
public function __construct(Status $status)
|
||||||
{
|
{
|
||||||
$this->status = $status;
|
$this->status = $status;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the job.
|
* Execute the job.
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function handle()
|
public function handle()
|
||||||
{
|
{
|
||||||
$status = $this->status;
|
$status = $this->status;
|
||||||
$profile = $this->status->profile;
|
$profile = $this->status->profile;
|
||||||
|
|
||||||
StatusService::del($status->id, true);
|
StatusService::del($status->id, true);
|
||||||
if($profile) {
|
if ($profile) {
|
||||||
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->status_count = $profile->status_count - 1;
|
||||||
$profile->save();
|
$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) {
|
if ((bool) config_cache('federation.activitypub.enabled') == true) {
|
||||||
return $this->fanoutDelete($status);
|
return $this->fanoutDelete($status);
|
||||||
} else {
|
} else {
|
||||||
return $this->unlinkRemoveMedia($status);
|
return $this->unlinkRemoveMedia($status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function unlinkRemoveMedia($status)
|
public function unlinkRemoveMedia($status)
|
||||||
{
|
{
|
||||||
Media::whereStatusId($status->id)
|
Media::whereStatusId($status->id)
|
||||||
->get()
|
->get()
|
||||||
->each(function($media) {
|
->each(function ($media) {
|
||||||
MediaDeletePipeline::dispatch($media);
|
MediaDeletePipeline::dispatch($media);
|
||||||
});
|
});
|
||||||
|
|
||||||
if($status->in_reply_to_id) {
|
if ($status->in_reply_to_id) {
|
||||||
$parent = Status::findOrFail($status->in_reply_to_id);
|
$parent = Status::findOrFail($status->in_reply_to_id);
|
||||||
--$parent->reply_count;
|
$parent->reply_count--;
|
||||||
$parent->save();
|
$parent->save();
|
||||||
StatusService::del($parent->id);
|
StatusService::del($parent->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Bookmark::whereStatusId($status->id)->delete();
|
Bookmark::whereStatusId($status->id)->delete();
|
||||||
|
|
||||||
CollectionItem::whereObjectType('App\Status')
|
CollectionItem::whereObjectType('App\Status')
|
||||||
->whereObjectId($status->id)
|
->whereObjectId($status->id)
|
||||||
->get()
|
->get()
|
||||||
->each(function($col) {
|
->each(function ($col) {
|
||||||
CollectionService::removeItem($col->collection_id, $col->object_id);
|
CollectionService::removeItem($col->collection_id, $col->object_id);
|
||||||
$col->delete();
|
$col->delete();
|
||||||
});
|
});
|
||||||
|
|
||||||
$dms = DirectMessage::whereStatusId($status->id)->get();
|
$dms = DirectMessage::whereStatusId($status->id)->get();
|
||||||
foreach($dms as $dm) {
|
foreach ($dms as $dm) {
|
||||||
$not = Notification::whereItemType('App\DirectMessage')
|
$not = Notification::whereItemType('App\DirectMessage')
|
||||||
->whereItemId($dm->id)
|
->whereItemId($dm->id)
|
||||||
->first();
|
->first();
|
||||||
if($not) {
|
if ($not) {
|
||||||
NotificationService::del($not->profile_id, $not->id);
|
NotificationService::del($not->profile_id, $not->id);
|
||||||
$not->forceDeleteQuietly();
|
$not->forceDeleteQuietly();
|
||||||
}
|
}
|
||||||
|
@ -130,11 +126,11 @@ class StatusDelete implements ShouldQueue
|
||||||
Like::whereStatusId($status->id)->delete();
|
Like::whereStatusId($status->id)->delete();
|
||||||
|
|
||||||
$mediaTags = MediaTag::where('status_id', $status->id)->get();
|
$mediaTags = MediaTag::where('status_id', $status->id)->get();
|
||||||
foreach($mediaTags as $mtag) {
|
foreach ($mediaTags as $mtag) {
|
||||||
$not = Notification::whereItemType('App\MediaTag')
|
$not = Notification::whereItemType('App\MediaTag')
|
||||||
->whereItemId($mtag->id)
|
->whereItemId($mtag->id)
|
||||||
->first();
|
->first();
|
||||||
if($not) {
|
if ($not) {
|
||||||
NotificationService::del($not->profile_id, $not->id);
|
NotificationService::del($not->profile_id, $not->id);
|
||||||
$not->forceDeleteQuietly();
|
$not->forceDeleteQuietly();
|
||||||
}
|
}
|
||||||
|
@ -142,85 +138,85 @@ class StatusDelete implements ShouldQueue
|
||||||
}
|
}
|
||||||
Mention::whereStatusId($status->id)->forceDelete();
|
Mention::whereStatusId($status->id)->forceDelete();
|
||||||
|
|
||||||
Notification::whereItemType('App\Status')
|
Notification::whereItemType('App\Status')
|
||||||
->whereItemId($status->id)
|
->whereItemId($status->id)
|
||||||
->forceDelete();
|
->forceDelete();
|
||||||
|
|
||||||
Report::whereObjectType('App\Status')
|
Report::whereObjectType('App\Status')
|
||||||
->whereObjectId($status->id)
|
->whereObjectId($status->id)
|
||||||
->delete();
|
->delete();
|
||||||
|
|
||||||
StatusArchived::whereStatusId($status->id)->delete();
|
StatusArchived::whereStatusId($status->id)->delete();
|
||||||
StatusHashtag::whereStatusId($status->id)->delete();
|
StatusHashtag::whereStatusId($status->id)->delete();
|
||||||
StatusView::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')
|
AccountInterstitial::where('item_type', 'App\Status')
|
||||||
->where('item_id', $status->id)
|
->where('item_id', $status->id)
|
||||||
->delete();
|
->delete();
|
||||||
|
|
||||||
$status->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();
|
|
||||||
|
|
||||||
return 1;
|
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;
|
namespace App\Jobs\StatusPipeline;
|
||||||
|
|
||||||
use App\Hashtag;
|
use App\Hashtag;
|
||||||
|
use App\Jobs\HomeFeedPipeline\FeedInsertPipeline;
|
||||||
use App\Jobs\MentionPipeline\MentionPipeline;
|
use App\Jobs\MentionPipeline\MentionPipeline;
|
||||||
use App\Mention;
|
use App\Mention;
|
||||||
use App\Profile;
|
use App\Profile;
|
||||||
|
use App\Services\AdminShadowFilterService;
|
||||||
|
use App\Services\PublicTimelineService;
|
||||||
|
use App\Services\StatusService;
|
||||||
|
use App\Services\UserFilterService;
|
||||||
use App\Status;
|
use App\Status;
|
||||||
use App\StatusHashtag;
|
use App\StatusHashtag;
|
||||||
use App\Services\PublicTimelineService;
|
|
||||||
use App\Util\Lexer\Autolink;
|
use App\Util\Lexer\Autolink;
|
||||||
use App\Util\Lexer\Extractor;
|
use App\Util\Lexer\Extractor;
|
||||||
use App\Util\Sentiment\Bouncer;
|
use App\Util\Sentiment\Bouncer;
|
||||||
|
@ -19,18 +23,15 @@ use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
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
|
class StatusEntityLexer implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
protected $status;
|
protected $status;
|
||||||
|
|
||||||
protected $entities;
|
protected $entities;
|
||||||
|
|
||||||
protected $autolink;
|
protected $autolink;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,12 +61,12 @@ class StatusEntityLexer implements ShouldQueue
|
||||||
$profile = $this->status->profile;
|
$profile = $this->status->profile;
|
||||||
$status = $this->status;
|
$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->status_count = $profile->status_count + 1;
|
||||||
$profile->save();
|
$profile->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
if($profile->no_autolink == false) {
|
if ($profile->no_autolink == false) {
|
||||||
$this->parseEntities();
|
$this->parseEntities();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,16 +104,16 @@ class StatusEntityLexer implements ShouldQueue
|
||||||
$status = $this->status;
|
$status = $this->status;
|
||||||
|
|
||||||
foreach ($tags as $tag) {
|
foreach ($tags as $tag) {
|
||||||
if(mb_strlen($tag) > 124) {
|
if (mb_strlen($tag) > 124) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
DB::transaction(function () use ($status, $tag) {
|
DB::transaction(function () use ($status, $tag) {
|
||||||
$slug = str_slug($tag, '-', false);
|
$slug = str_slug($tag, '-', false);
|
||||||
|
|
||||||
$hashtag = Hashtag::firstOrCreate([
|
$hashtag = Hashtag::firstOrCreate([
|
||||||
'slug' => $slug
|
'slug' => $slug,
|
||||||
], [
|
], [
|
||||||
'name' => $tag
|
'name' => $tag,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
StatusHashtag::firstOrCreate(
|
StatusHashtag::firstOrCreate(
|
||||||
|
@ -136,11 +137,11 @@ class StatusEntityLexer implements ShouldQueue
|
||||||
foreach ($mentions as $mention) {
|
foreach ($mentions as $mention) {
|
||||||
$mentioned = Profile::whereUsername($mention)->first();
|
$mentioned = Profile::whereUsername($mention)->first();
|
||||||
|
|
||||||
if (empty($mentioned) || !isset($mentioned->id)) {
|
if (empty($mentioned) || ! isset($mentioned->id)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$blocks = UserFilterService::blocks($mentioned->id);
|
$blocks = UserFilterService::blocks($mentioned->id);
|
||||||
if($blocks && in_array($status->profile_id, $blocks)) {
|
if ($blocks && in_array($status->profile_id, $blocks)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,8 +162,8 @@ class StatusEntityLexer implements ShouldQueue
|
||||||
$status = $this->status;
|
$status = $this->status;
|
||||||
StatusService::refresh($status->id);
|
StatusService::refresh($status->id);
|
||||||
|
|
||||||
if(config('exp.cached_home_timeline')) {
|
if (config('exp.cached_home_timeline')) {
|
||||||
if( $status->in_reply_to_id === null &&
|
if ($status->in_reply_to_id === null &&
|
||||||
in_array($status->scope, ['public', 'unlisted', 'private'])
|
in_array($status->scope, ['public', 'unlisted', 'private'])
|
||||||
) {
|
) {
|
||||||
FeedInsertPipeline::dispatch($status->id, $status->profile_id)->onQueue('feed');
|
FeedInsertPipeline::dispatch($status->id, $status->profile_id)->onQueue('feed');
|
||||||
|
@ -179,28 +180,28 @@ class StatusEntityLexer implements ShouldQueue
|
||||||
'photo:album',
|
'photo:album',
|
||||||
'video',
|
'video',
|
||||||
'video:album',
|
'video:album',
|
||||||
'photo:video:album'
|
'photo:video:album',
|
||||||
];
|
];
|
||||||
|
|
||||||
if(config_cache('pixelfed.bouncer.enabled')) {
|
if ((bool) config_cache('pixelfed.bouncer.enabled')) {
|
||||||
Bouncer::get($status);
|
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');
|
$hideNsfw = config('instance.hide_nsfw_on_public_feeds');
|
||||||
if( $status->uri == null &&
|
if ($status->uri == null &&
|
||||||
$status->scope == 'public' &&
|
$status->scope == 'public' &&
|
||||||
in_array($status->type, $types) &&
|
in_array($status->type, $types) &&
|
||||||
$status->in_reply_to_id === null &&
|
$status->in_reply_to_id === null &&
|
||||||
$status->reblog_of_id === null &&
|
$status->reblog_of_id === null &&
|
||||||
($hideNsfw ? $status->is_nsfw == false : true)
|
($hideNsfw ? $status->is_nsfw == false : true)
|
||||||
) {
|
) {
|
||||||
if(AdminShadowFilterService::canAddToPublicFeedByProfileId($status->profile_id)) {
|
if (AdminShadowFilterService::canAddToPublicFeedByProfileId($status->profile_id)) {
|
||||||
PublicTimelineService::add($status->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);
|
StatusActivityPubDeliver::dispatch($status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,172 +2,171 @@
|
||||||
|
|
||||||
namespace App\Jobs\StatusPipeline;
|
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\Bus\Queueable;
|
||||||
use Illuminate\Contracts\Queue\ShouldBeUnique;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
use Illuminate\Queue\SerializesModels;
|
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 Illuminate\Support\Facades\Http;
|
||||||
|
use Purify;
|
||||||
|
|
||||||
class StatusRemoteUpdatePipeline implements ShouldQueue
|
class StatusRemoteUpdatePipeline implements ShouldQueue
|
||||||
{
|
{
|
||||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||||
|
|
||||||
public $activity;
|
public $activity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new job instance.
|
* Create a new job instance.
|
||||||
*/
|
*/
|
||||||
public function __construct($activity)
|
public function __construct($activity)
|
||||||
{
|
{
|
||||||
$this->activity = $activity;
|
$this->activity = $activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute the job.
|
* Execute the job.
|
||||||
*/
|
*/
|
||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
$activity = $this->activity;
|
$activity = $this->activity;
|
||||||
$status = Status::with('media')->whereObjectUrl($activity['id'])->first();
|
$status = Status::with('media')->whereObjectUrl($activity['id'])->first();
|
||||||
if(!$status) {
|
if (! $status) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->createPreviousEdit($status);
|
$this->createPreviousEdit($status);
|
||||||
$this->updateMedia($status, $activity);
|
$this->updateMedia($status, $activity);
|
||||||
$this->updateImmediateAttributes($status, $activity);
|
$this->updateImmediateAttributes($status, $activity);
|
||||||
$this->createEdit($status, $activity);
|
$this->createEdit($status, $activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createPreviousEdit($status)
|
protected function createPreviousEdit($status)
|
||||||
{
|
{
|
||||||
if(!$status->edits()->count()) {
|
if (! $status->edits()->count()) {
|
||||||
StatusEdit::create([
|
StatusEdit::create([
|
||||||
'status_id' => $status->id,
|
'status_id' => $status->id,
|
||||||
'profile_id' => $status->profile_id,
|
'profile_id' => $status->profile_id,
|
||||||
'caption' => $status->caption,
|
'caption' => $status->caption,
|
||||||
'spoiler_text' => $status->cw_summary,
|
'spoiler_text' => $status->cw_summary,
|
||||||
'is_nsfw' => $status->is_nsfw,
|
'is_nsfw' => $status->is_nsfw,
|
||||||
'ordered_media_attachment_ids' => $status->media()->orderBy('order')->pluck('id')->toArray(),
|
'ordered_media_attachment_ids' => $status->media()->orderBy('order')->pluck('id')->toArray(),
|
||||||
'created_at' => $status->created_at
|
'created_at' => $status->created_at,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function updateMedia($status, $activity)
|
protected function updateMedia($status, $activity)
|
||||||
{
|
{
|
||||||
if(!isset($activity['attachment'])) {
|
if (! isset($activity['attachment'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$ogm = $status->media->count() ? $status->media()->orderBy('order')->get() : collect([]);
|
$ogm = $status->media->count() ? $status->media()->orderBy('order')->get() : collect([]);
|
||||||
$nm = collect($activity['attachment'])->filter(function($nm) {
|
$nm = collect($activity['attachment'])->filter(function ($nm) {
|
||||||
return isset(
|
return isset(
|
||||||
$nm['type'],
|
$nm['type'],
|
||||||
$nm['mediaType'],
|
$nm['mediaType'],
|
||||||
$nm['url']
|
$nm['url']
|
||||||
) &&
|
) &&
|
||||||
in_array($nm['type'], ['Document', 'Image', 'Video']) &&
|
in_array($nm['type'], ['Document', 'Image', 'Video']) &&
|
||||||
in_array($nm['mediaType'], explode(',', config('pixelfed.media_types')));
|
in_array($nm['mediaType'], explode(',', config_cache('pixelfed.media_types')));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Skip when no media
|
// Skip when no media
|
||||||
if(!$ogm->count() && !$nm->count()) {
|
if (! $ogm->count() && ! $nm->count()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Media::whereProfileId($status->profile_id)
|
Media::whereProfileId($status->profile_id)
|
||||||
->whereStatusId($status->id)
|
->whereStatusId($status->id)
|
||||||
->update([
|
->update([
|
||||||
'status_id' => null
|
'status_id' => null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$nm->each(function($n, $key) use($status) {
|
$nm->each(function ($n, $key) use ($status) {
|
||||||
$res = Http::withOptions(['allow_redirects' => false])->retry(3, 100, throw: false)->head($n['url']);
|
$res = Http::withOptions(['allow_redirects' => false])->retry(3, 100, throw: false)->head($n['url']);
|
||||||
|
|
||||||
if(!$res->successful()) {
|
if (! $res->successful()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
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')))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$m = new Media;
|
$m = new Media;
|
||||||
$m->status_id = $status->id;
|
$m->status_id = $status->id;
|
||||||
$m->profile_id = $status->profile_id;
|
$m->profile_id = $status->profile_id;
|
||||||
$m->remote_media = true;
|
$m->remote_media = true;
|
||||||
$m->media_path = $n['url'];
|
$m->media_path = $n['url'];
|
||||||
$m->mime = $res->header('content-type');
|
$m->mime = $res->header('content-type');
|
||||||
$m->size = $res->hasHeader('content-length') ? $res->header('content-length') : null;
|
$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->caption = isset($n['name']) && ! empty($n['name']) ? Purify::clean($n['name']) : null;
|
||||||
$m->remote_url = $n['url'];
|
$m->remote_url = $n['url'];
|
||||||
$m->blurhash = isset($n['blurhash']) && (strlen($n['blurhash']) < 50) ? $n['blurhash'] : null;
|
$m->blurhash = isset($n['blurhash']) && (strlen($n['blurhash']) < 50) ? $n['blurhash'] : null;
|
||||||
$m->width = isset($n['width']) && !empty($n['width']) ? $n['width'] : null;
|
$m->width = isset($n['width']) && ! empty($n['width']) ? $n['width'] : null;
|
||||||
$m->height = isset($n['height']) && !empty($n['height']) ? $n['height'] : null;
|
$m->height = isset($n['height']) && ! empty($n['height']) ? $n['height'] : null;
|
||||||
$m->skip_optimize = true;
|
$m->skip_optimize = true;
|
||||||
$m->order = $key + 1;
|
$m->order = $key + 1;
|
||||||
$m->save();
|
$m->save();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function updateImmediateAttributes($status, $activity)
|
protected function updateImmediateAttributes($status, $activity)
|
||||||
{
|
{
|
||||||
if(isset($activity['content'])) {
|
if (isset($activity['content'])) {
|
||||||
$status->caption = strip_tags($activity['content']);
|
$status->caption = strip_tags($activity['content']);
|
||||||
$status->rendered = Purify::clean($activity['content']);
|
$status->rendered = Purify::clean($activity['content']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($activity['sensitive'])) {
|
if (isset($activity['sensitive'])) {
|
||||||
if((bool) $activity['sensitive'] == false) {
|
if ((bool) $activity['sensitive'] == false) {
|
||||||
$status->is_nsfw = false;
|
$status->is_nsfw = false;
|
||||||
$exists = ModLog::whereObjectType('App\Status::class')
|
$exists = ModLog::whereObjectType('App\Status::class')
|
||||||
->whereObjectId($status->id)
|
->whereObjectId($status->id)
|
||||||
->whereAction('admin.status.moderate')
|
->whereAction('admin.status.moderate')
|
||||||
->exists();
|
->exists();
|
||||||
if($exists == true) {
|
if ($exists == true) {
|
||||||
$status->is_nsfw = true;
|
$status->is_nsfw = true;
|
||||||
}
|
}
|
||||||
$profile = Profile::find($status->profile_id);
|
$profile = Profile::find($status->profile_id);
|
||||||
if(!$profile || $profile->cw == true) {
|
if (! $profile || $profile->cw == true) {
|
||||||
$status->is_nsfw = true;
|
$status->is_nsfw = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$status->is_nsfw = true;
|
$status->is_nsfw = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($activity['summary'])) {
|
if (isset($activity['summary'])) {
|
||||||
$status->cw_summary = Purify::clean($activity['summary']);
|
$status->cw_summary = Purify::clean($activity['summary']);
|
||||||
} else {
|
} else {
|
||||||
$status->cw_summary = null;
|
$status->cw_summary = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$status->edited_at = now();
|
$status->edited_at = now();
|
||||||
$status->save();
|
$status->save();
|
||||||
StatusService::del($status->id);
|
StatusService::del($status->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function createEdit($status, $activity)
|
protected function createEdit($status, $activity)
|
||||||
{
|
{
|
||||||
$cleaned = isset($activity['content']) ? Purify::clean($activity['content']) : null;
|
$cleaned = isset($activity['content']) ? Purify::clean($activity['content']) : null;
|
||||||
$spoiler_text = isset($activity['summary']) ? Purify::clean($activity['summary']) : null;
|
$spoiler_text = isset($activity['summary']) ? Purify::clean($activity['summary']) : null;
|
||||||
$sensitive = isset($activity['sensitive']) ? $activity['sensitive'] : null;
|
$sensitive = isset($activity['sensitive']) ? $activity['sensitive'] : null;
|
||||||
$mids = $status->media()->count() ? $status->media()->orderBy('order')->pluck('id')->toArray() : null;
|
$mids = $status->media()->count() ? $status->media()->orderBy('order')->pluck('id')->toArray() : null;
|
||||||
StatusEdit::create([
|
StatusEdit::create([
|
||||||
'status_id' => $status->id,
|
'status_id' => $status->id,
|
||||||
'profile_id' => $status->profile_id,
|
'profile_id' => $status->profile_id,
|
||||||
'caption' => $cleaned,
|
'caption' => $cleaned,
|
||||||
'spoiler_text' => $spoiler_text,
|
'spoiler_text' => $spoiler_text,
|
||||||
'is_nsfw' => $sensitive,
|
'is_nsfw' => $sensitive,
|
||||||
'ordered_media_attachment_ids' => $mids
|
'ordered_media_attachment_ids' => $mids,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ class CustomEmoji extends Model
|
||||||
|
|
||||||
public static function scan($text, $activitypub = false)
|
public static function scan($text, $activitypub = false)
|
||||||
{
|
{
|
||||||
if(config('federation.custom_emoji.enabled') == false) {
|
if((bool) config_cache('federation.custom_emoji.enabled') == false) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,9 @@
|
||||||
namespace App\Observers;
|
namespace App\Observers;
|
||||||
|
|
||||||
use App\Avatar;
|
use App\Avatar;
|
||||||
|
use App\Services\AccountService;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use App\Services\AccountService;
|
|
||||||
|
|
||||||
class AvatarObserver
|
class AvatarObserver
|
||||||
{
|
{
|
||||||
|
@ -19,7 +19,6 @@ class AvatarObserver
|
||||||
/**
|
/**
|
||||||
* Handle the avatar "created" event.
|
* Handle the avatar "created" event.
|
||||||
*
|
*
|
||||||
* @param \App\Avatar $avatar
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function created(Avatar $avatar)
|
public function created(Avatar $avatar)
|
||||||
|
@ -30,7 +29,6 @@ class AvatarObserver
|
||||||
/**
|
/**
|
||||||
* Handle the avatar "updated" event.
|
* Handle the avatar "updated" event.
|
||||||
*
|
*
|
||||||
* @param \App\Avatar $avatar
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function updated(Avatar $avatar)
|
public function updated(Avatar $avatar)
|
||||||
|
@ -41,7 +39,6 @@ class AvatarObserver
|
||||||
/**
|
/**
|
||||||
* Handle the avatar "deleted" event.
|
* Handle the avatar "deleted" event.
|
||||||
*
|
*
|
||||||
* @param \App\Avatar $avatar
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function deleted(Avatar $avatar)
|
public function deleted(Avatar $avatar)
|
||||||
|
@ -52,23 +49,22 @@ class AvatarObserver
|
||||||
/**
|
/**
|
||||||
* Handle the avatar "deleting" event.
|
* Handle the avatar "deleting" event.
|
||||||
*
|
*
|
||||||
* @param \App\Avatar $avatar
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function deleting(Avatar $avatar)
|
public function deleting(Avatar $avatar)
|
||||||
{
|
{
|
||||||
$path = storage_path('app/'.$avatar->media_path);
|
$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.png' &&
|
||||||
$avatar->media_path != 'public/avatars/default.jpg'
|
$avatar->media_path != 'public/avatars/default.jpg'
|
||||||
) {
|
) {
|
||||||
@unlink($path);
|
@unlink($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config_cache('pixelfed.cloud_storage')) {
|
if ((bool) config_cache('pixelfed.cloud_storage')) {
|
||||||
$disk = Storage::disk(config('filesystems.cloud'));
|
$disk = Storage::disk(config('filesystems.cloud'));
|
||||||
$base = Str::startsWith($avatar->media_path, 'cache/avatars/');
|
$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);
|
$disk->delete($avatar->media_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,7 +74,6 @@ class AvatarObserver
|
||||||
/**
|
/**
|
||||||
* Handle the avatar "restored" event.
|
* Handle the avatar "restored" event.
|
||||||
*
|
*
|
||||||
* @param \App\Avatar $avatar
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function restored(Avatar $avatar)
|
public function restored(Avatar $avatar)
|
||||||
|
@ -89,7 +84,6 @@ class AvatarObserver
|
||||||
/**
|
/**
|
||||||
* Handle the avatar "force deleted" event.
|
* Handle the avatar "force deleted" event.
|
||||||
*
|
*
|
||||||
* @param \App\Avatar $avatar
|
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function forceDeleted(Avatar $avatar)
|
public function forceDeleted(Avatar $avatar)
|
||||||
|
|
|
@ -107,7 +107,7 @@ class UserObserver
|
||||||
CreateAvatar::dispatch($profile);
|
CreateAvatar::dispatch($profile);
|
||||||
});
|
});
|
||||||
|
|
||||||
if(config_cache('account.autofollow') == true) {
|
if((bool) config_cache('account.autofollow') == true) {
|
||||||
$names = config_cache('account.autofollow_usernames');
|
$names = config_cache('account.autofollow_usernames');
|
||||||
$names = explode(',', $names);
|
$names = explode(',', $names);
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Gate;
|
||||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||||
use Laravel\Passport\Passport;
|
use Laravel\Passport\Passport;
|
||||||
use Gate;
|
|
||||||
|
|
||||||
class AuthServiceProvider extends ServiceProvider
|
class AuthServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
|
@ -24,11 +24,11 @@ class AuthServiceProvider extends ServiceProvider
|
||||||
*/
|
*/
|
||||||
public function boot()
|
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::tokensExpireIn(now()->addDays(config('instance.oauth.token_expiration', 356)));
|
||||||
Passport::refreshTokensExpireIn(now()->addDays(config('instance.oauth.refresh_expiration', 400)));
|
Passport::refreshTokensExpireIn(now()->addDays(config('instance.oauth.refresh_expiration', 400)));
|
||||||
Passport::enableImplicitGrant();
|
Passport::enableImplicitGrant();
|
||||||
if(config('instance.oauth.pat.enabled')) {
|
if (config('instance.oauth.pat.enabled')) {
|
||||||
Passport::personalAccessClientId(config('instance.oauth.pat.id'));
|
Passport::personalAccessClientId(config('instance.oauth.pat.id'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||||
'follow' => 'Ability to follow other profiles',
|
'follow' => 'Ability to follow other profiles',
|
||||||
'admin:read' => 'Read all data on the server',
|
'admin:read' => 'Read all data on the server',
|
||||||
'admin:write' => 'Modify 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([
|
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;
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Util\Lexer\Classifier;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use App\Util\Lexer\Classifier;
|
|
||||||
|
|
||||||
class AutospamService
|
class AutospamService
|
||||||
{
|
{
|
||||||
const CHCKD_CACHE_KEY = 'pf:services:autospam:nlp:checked';
|
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';
|
|
||||||
|
|
||||||
public static function check($text)
|
const MODEL_CACHE_KEY = 'pf:services:autospam:nlp:model-cache';
|
||||||
{
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function eligible()
|
const MODEL_FILE_PATH = 'nlp/active-training-data.json';
|
||||||
{
|
|
||||||
return Cache::remember(self::CHCKD_CACHE_KEY, 86400, function() {
|
|
||||||
if(!config_cache('pixelfed.bouncer.enabled') || !config('autospam.enabled')) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!Storage::exists(self::MODEL_SPAM_PATH)) {
|
const MODEL_SPAM_PATH = 'nlp/spam.json';
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!Storage::exists(self::MODEL_HAM_PATH)) {
|
const MODEL_HAM_PATH = 'nlp/ham.json';
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!Storage::exists(self::MODEL_FILE_PATH)) {
|
public static function check($text)
|
||||||
return false;
|
{
|
||||||
} else {
|
if (! $text || strlen($text) == 0) {
|
||||||
if(Storage::size(self::MODEL_FILE_PATH) < 1000) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
}
|
||||||
});
|
if (! self::active()) {
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
|
$model = self::getCachedModel();
|
||||||
|
$classifier = new Classifier;
|
||||||
|
$classifier->import($model['documents'], $model['words']);
|
||||||
|
|
||||||
public static function active()
|
return $classifier->most($text) === 'spam';
|
||||||
{
|
}
|
||||||
return config_cache('autospam.nlp.enabled') && self::eligible();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function getCachedModel()
|
public static function eligible()
|
||||||
{
|
{
|
||||||
if(!self::active()) {
|
return Cache::remember(self::CHCKD_CACHE_KEY, 86400, function () {
|
||||||
return null;
|
if (! (bool) config_cache('pixelfed.bouncer.enabled') || ! (bool) config_cache('autospam.enabled')) {
|
||||||
}
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return Cache::remember(self::MODEL_CACHE_KEY, 86400, function() {
|
if (! Storage::exists(self::MODEL_SPAM_PATH)) {
|
||||||
$res = Storage::get(self::MODEL_FILE_PATH);
|
return false;
|
||||||
if(!$res || empty($res)) {
|
}
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
class ConfigCacheService
|
||||||
{
|
{
|
||||||
const CACHE_KEY = 'config_cache:_v0-key:';
|
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)
|
public static function get($key)
|
||||||
{
|
{
|
||||||
|
@ -89,6 +97,41 @@ class ConfigCacheService
|
||||||
'pixelfed.app_registration_confirm_rate_limit_decay',
|
'pixelfed.app_registration_confirm_rate_limit_decay',
|
||||||
'instance.embed.profile',
|
'instance.embed.profile',
|
||||||
'instance.embed.post',
|
'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'
|
// 'system.user_mode'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -100,20 +143,34 @@ class ConfigCacheService
|
||||||
return config($key);
|
return config($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$protect = false;
|
||||||
|
$protected = null;
|
||||||
|
if(in_array($key, self::PROTECTED_KEYS)) {
|
||||||
|
$protect = true;
|
||||||
|
}
|
||||||
|
|
||||||
$v = config($key);
|
$v = config($key);
|
||||||
$c = ConfigCacheModel::where('k', $key)->first();
|
$c = ConfigCacheModel::where('k', $key)->first();
|
||||||
|
|
||||||
if ($c) {
|
if ($c) {
|
||||||
return $c->v ?? config($key);
|
if($protect) {
|
||||||
|
return decrypt($c->v) ?? config($key);
|
||||||
|
} else {
|
||||||
|
return $c->v ?? config($key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $v) {
|
if (! $v) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if($protect && $v) {
|
||||||
|
$protected = encrypt($v);
|
||||||
|
}
|
||||||
|
|
||||||
$cc = new ConfigCacheModel;
|
$cc = new ConfigCacheModel;
|
||||||
$cc->k = $key;
|
$cc->k = $key;
|
||||||
$cc->v = $v;
|
$cc->v = $protect ? $protected : $v;
|
||||||
$cc->save();
|
$cc->save();
|
||||||
|
|
||||||
return $v;
|
return $v;
|
||||||
|
@ -124,8 +181,15 @@ class ConfigCacheService
|
||||||
{
|
{
|
||||||
$exists = ConfigCacheModel::whereK($key)->first();
|
$exists = ConfigCacheModel::whereK($key)->first();
|
||||||
|
|
||||||
|
$protect = false;
|
||||||
|
$protected = null;
|
||||||
|
if(in_array($key, self::PROTECTED_KEYS)) {
|
||||||
|
$protect = true;
|
||||||
|
$protected = encrypt($val);
|
||||||
|
}
|
||||||
|
|
||||||
if ($exists) {
|
if ($exists) {
|
||||||
$exists->v = $val;
|
$exists->v = $protect ? $protected : $val;
|
||||||
$exists->save();
|
$exists->save();
|
||||||
Cache::put(self::CACHE_KEY.$key, $val, now()->addHours(12));
|
Cache::put(self::CACHE_KEY.$key, $val, now()->addHours(12));
|
||||||
|
|
||||||
|
@ -134,7 +198,7 @@ class ConfigCacheService
|
||||||
|
|
||||||
$cc = new ConfigCacheModel;
|
$cc = new ConfigCacheModel;
|
||||||
$cc->k = $key;
|
$cc->k = $key;
|
||||||
$cc->v = $val;
|
$cc->v = $protect ? $protected : $val;
|
||||||
$cc->save();
|
$cc->save();
|
||||||
|
|
||||||
Cache::put(self::CACHE_KEY.$key, $val, now()->addHours(12));
|
Cache::put(self::CACHE_KEY.$key, $val, now()->addHours(12));
|
||||||
|
|
|
@ -13,7 +13,7 @@ class CustomEmojiService
|
||||||
{
|
{
|
||||||
public static function get($shortcode)
|
public static function get($shortcode)
|
||||||
{
|
{
|
||||||
if(config('federation.custom_emoji.enabled') == false) {
|
if((bool) config_cache('federation.custom_emoji.enabled') == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ class CustomEmojiService
|
||||||
|
|
||||||
public static function import($url, $id = false)
|
public static function import($url, $id = false)
|
||||||
{
|
{
|
||||||
if(config('federation.custom_emoji.enabled') == false) {
|
if((bool) config_cache('federation.custom_emoji.enabled') == false) {
|
||||||
return;
|
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'),
|
'name' => config_cache('app.name'),
|
||||||
'url' => config_cache('app.url'),
|
'url' => config_cache('app.url'),
|
||||||
'domain' => config('pixelfed.domain.app'),
|
'domain' => config('pixelfed.domain.app'),
|
||||||
'show_directory' => config_cache('instance.landing.show_directory'),
|
'show_directory' => (bool) config_cache('instance.landing.show_directory'),
|
||||||
'show_explore_feed' => config_cache('instance.landing.show_explore'),
|
'show_explore_feed' => (bool) config_cache('instance.landing.show_explore'),
|
||||||
'open_registration' => (bool) $openReg,
|
'open_registration' => (bool) $openReg,
|
||||||
'curated_onboarding' => (bool) config_cache('instance.curated_registration.enabled'),
|
'curated_onboarding' => (bool) config_cache('instance.curated_registration.enabled'),
|
||||||
'version' => config('pixelfed.version'),
|
'version' => config('pixelfed.version'),
|
||||||
|
@ -85,7 +85,7 @@ class LandingService
|
||||||
'media_types' => config_cache('pixelfed.media_types'),
|
'media_types' => config_cache('pixelfed.media_types'),
|
||||||
],
|
],
|
||||||
'features' => [
|
'features' => [
|
||||||
'federation' => config_cache('federation.activitypub.enabled'),
|
'federation' => (bool) config_cache('federation.activitypub.enabled'),
|
||||||
'timelines' => [
|
'timelines' => [
|
||||||
'local' => true,
|
'local' => true,
|
||||||
'network' => (bool) config_cache('federation.network_timeline'),
|
'network' => (bool) config_cache('federation.network_timeline'),
|
||||||
|
|
|
@ -2,44 +2,38 @@
|
||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
|
use App\Jobs\AvatarPipeline\AvatarStorageCleanup;
|
||||||
|
use App\Jobs\MediaPipeline\MediaDeletePipeline;
|
||||||
|
use App\Media;
|
||||||
use App\Util\ActivityPub\Helpers;
|
use App\Util\ActivityPub\Helpers;
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Exception\RequestException;
|
||||||
use Illuminate\Http\File;
|
use Illuminate\Http\File;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\Redis;
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Illuminate\Support\Str;
|
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)
|
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);
|
(new self())->cloudStore($media);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function move(Media $media)
|
public static function move(Media $media)
|
||||||
{
|
{
|
||||||
if($media->remote_media) {
|
if ($media->remote_media) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config_cache('pixelfed.cloud_storage') == true) {
|
if ((bool) config_cache('pixelfed.cloud_storage') == true) {
|
||||||
return (new self())->cloudMove($media);
|
return (new self())->cloudMove($media);
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function avatar($avatar, $local = false, $skipRecentCheck = false)
|
public static function avatar($avatar, $local = false, $skipRecentCheck = false)
|
||||||
|
@ -56,31 +50,31 @@ class MediaStorageService {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$h = Arr::mapWithKeys($r->getHeaders(), function($item, $key) {
|
$h = Arr::mapWithKeys($r->getHeaders(), function ($item, $key) {
|
||||||
return [strtolower($key) => last($item)];
|
return [strtolower($key) => last($item)];
|
||||||
});
|
});
|
||||||
|
|
||||||
if(!isset($h['content-length'], $h['content-type'])) {
|
if (! isset($h['content-length'], $h['content-type'])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$len = (int) $h['content-length'];
|
$len = (int) $h['content-length'];
|
||||||
$mime = $h['content-type'];
|
$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 false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'length' => $len,
|
'length' => $len,
|
||||||
'mime' => $mime
|
'mime' => $mime,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function cloudStore($media)
|
protected function cloudStore($media)
|
||||||
{
|
{
|
||||||
if($media->remote_media == true) {
|
if ($media->remote_media == true) {
|
||||||
if(config('media.storage.remote.cloud')) {
|
if (config('media.storage.remote.cloud')) {
|
||||||
(new self())->remoteToCloud($media);
|
(new self())->remoteToCloud($media);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -100,7 +94,7 @@ class MediaStorageService {
|
||||||
$storagePath = implode('/', $p);
|
$storagePath = implode('/', $p);
|
||||||
|
|
||||||
$url = ResilientMediaStorageService::store($storagePath, $path, $name);
|
$url = ResilientMediaStorageService::store($storagePath, $path, $name);
|
||||||
if($thumb) {
|
if ($thumb) {
|
||||||
$thumbUrl = ResilientMediaStorageService::store($storagePath, $thumb, $thumbname);
|
$thumbUrl = ResilientMediaStorageService::store($storagePath, $thumb, $thumbname);
|
||||||
$media->thumbnail_url = $thumbUrl;
|
$media->thumbnail_url = $thumbUrl;
|
||||||
}
|
}
|
||||||
|
@ -108,8 +102,8 @@ class MediaStorageService {
|
||||||
$media->optimized_url = $url;
|
$media->optimized_url = $url;
|
||||||
$media->replicated_at = now();
|
$media->replicated_at = now();
|
||||||
$media->save();
|
$media->save();
|
||||||
if($media->status_id) {
|
if ($media->status_id) {
|
||||||
Cache::forget('status:transformer:media:attachments:' . $media->status_id);
|
Cache::forget('status:transformer:media:attachments:'.$media->status_id);
|
||||||
MediaService::del($media->status_id);
|
MediaService::del($media->status_id);
|
||||||
StatusService::del($media->status_id, false);
|
StatusService::del($media->status_id, false);
|
||||||
}
|
}
|
||||||
|
@ -119,20 +113,20 @@ class MediaStorageService {
|
||||||
{
|
{
|
||||||
$url = $media->remote_url;
|
$url = $media->remote_url;
|
||||||
|
|
||||||
if(!Helpers::validateUrl($url)) {
|
if (! Helpers::validateUrl($url)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$head = $this->head($media->remote_url);
|
$head = $this->head($media->remote_url);
|
||||||
|
|
||||||
if(!$head) {
|
if (! $head) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mimes = [
|
$mimes = [
|
||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
'image/png',
|
'image/png',
|
||||||
'video/mp4'
|
'video/mp4',
|
||||||
];
|
];
|
||||||
|
|
||||||
$mime = $head['mime'];
|
$mime = $head['mime'];
|
||||||
|
@ -141,11 +135,11 @@ class MediaStorageService {
|
||||||
$media->remote_media = true;
|
$media->remote_media = true;
|
||||||
$media->save();
|
$media->save();
|
||||||
|
|
||||||
if(!in_array($mime, $mimes)) {
|
if (! in_array($mime, $mimes)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($head['length'] >= $max_size) {
|
if ($head['length'] >= $max_size) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,10 +162,10 @@ class MediaStorageService {
|
||||||
}
|
}
|
||||||
|
|
||||||
$base = MediaPathService::get($media->profile);
|
$base = MediaPathService::get($media->profile);
|
||||||
$path = Str::random(40) . $ext;
|
$path = Str::random(40).$ext;
|
||||||
$tmpBase = storage_path('app/remcache/');
|
$tmpBase = storage_path('app/remcache/');
|
||||||
$tmpPath = $media->profile_id . '-' . $path;
|
$tmpPath = $media->profile_id.'-'.$path;
|
||||||
$tmpName = $tmpBase . $tmpPath;
|
$tmpName = $tmpBase.$tmpPath;
|
||||||
$data = file_get_contents($url, false, null, 0, $head['length']);
|
$data = file_get_contents($url, false, null, 0, $head['length']);
|
||||||
file_put_contents($tmpName, $data);
|
file_put_contents($tmpName, $data);
|
||||||
$hash = hash_file('sha256', $tmpName);
|
$hash = hash_file('sha256', $tmpName);
|
||||||
|
@ -186,8 +180,8 @@ class MediaStorageService {
|
||||||
$media->replicated_at = now();
|
$media->replicated_at = now();
|
||||||
$media->save();
|
$media->save();
|
||||||
|
|
||||||
if($media->status_id) {
|
if ($media->status_id) {
|
||||||
Cache::forget('status:transformer:media:attachments:' . $media->status_id);
|
Cache::forget('status:transformer:media:attachments:'.$media->status_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
unlink($tmpName);
|
unlink($tmpName);
|
||||||
|
@ -199,13 +193,13 @@ class MediaStorageService {
|
||||||
$url = $avatar->remote_url;
|
$url = $avatar->remote_url;
|
||||||
$driver = $local ? 'local' : config('filesystems.cloud');
|
$driver = $local ? 'local' : config('filesystems.cloud');
|
||||||
|
|
||||||
if(empty($url) || Helpers::validateUrl($url) == false) {
|
if (empty($url) || Helpers::validateUrl($url) == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$head = $this->head($url);
|
$head = $this->head($url);
|
||||||
|
|
||||||
if($head == false) {
|
if ($head == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,46 +212,47 @@ class MediaStorageService {
|
||||||
$mime = $head['mime'];
|
$mime = $head['mime'];
|
||||||
$max_size = (int) config('pixelfed.max_avatar_size') * 1000;
|
$max_size = (int) config('pixelfed.max_avatar_size') * 1000;
|
||||||
|
|
||||||
if(!$skipRecentCheck) {
|
if (! $skipRecentCheck) {
|
||||||
if($avatar->last_fetched_at && $avatar->last_fetched_at->gt(now()->subMonths(3))) {
|
if ($avatar->last_fetched_at && $avatar->last_fetched_at->gt(now()->subMonths(3))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Cache::forget('avatar:' . $avatar->profile_id);
|
Cache::forget('avatar:'.$avatar->profile_id);
|
||||||
AccountService::del($avatar->profile_id);
|
AccountService::del($avatar->profile_id);
|
||||||
|
|
||||||
// handle pleroma edge case
|
// 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);
|
$mime = str_replace('; charset=utf-8', '', $mime);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!in_array($mime, $mimes)) {
|
if (! in_array($mime, $mimes)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($head['length'] >= $max_size) {
|
if ($head['length'] >= $max_size) {
|
||||||
return;
|
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';
|
$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/');
|
$tmpBase = storage_path('app/remcache/');
|
||||||
$tmpPath = 'avatar_' . $avatar->profile_id . '-' . $path;
|
$tmpPath = 'avatar_'.$avatar->profile_id.'-'.$path;
|
||||||
$tmpName = $tmpBase . $tmpPath;
|
$tmpName = $tmpBase.$tmpPath;
|
||||||
$data = @file_get_contents($url, false, null, 0, $head['length']);
|
$data = @file_get_contents($url, false, null, 0, $head['length']);
|
||||||
if(!$data) {
|
if (! $data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
file_put_contents($tmpName, $data);
|
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->last_fetched_at = now();
|
||||||
$avatar->save();
|
$avatar->save();
|
||||||
unlink($tmpName);
|
unlink($tmpName);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -265,15 +260,15 @@ class MediaStorageService {
|
||||||
$file = $disk->putFileAs($base, new File($tmpName), $path, 'public');
|
$file = $disk->putFileAs($base, new File($tmpName), $path, 'public');
|
||||||
$permalink = $disk->url($file);
|
$permalink = $disk->url($file);
|
||||||
|
|
||||||
$avatar->media_path = $base . '/' . $path;
|
$avatar->media_path = $base.'/'.$path;
|
||||||
$avatar->is_remote = true;
|
$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->size = $head['length'];
|
||||||
$avatar->change_count = $avatar->change_count + 1;
|
$avatar->change_count = $avatar->change_count + 1;
|
||||||
$avatar->last_fetched_at = now();
|
$avatar->last_fetched_at = now();
|
||||||
$avatar->save();
|
$avatar->save();
|
||||||
|
|
||||||
Cache::forget('avatar:' . $avatar->profile_id);
|
Cache::forget('avatar:'.$avatar->profile_id);
|
||||||
AccountService::del($avatar->profile_id);
|
AccountService::del($avatar->profile_id);
|
||||||
AvatarStorageCleanup::dispatch($avatar)->onQueue($queue)->delay(now()->addMinutes(random_int(3, 15)));
|
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)
|
public static function delete(Media $media, $confirm = false)
|
||||||
{
|
{
|
||||||
if(!$confirm) {
|
if (! $confirm) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MediaDeletePipeline::dispatch($media)->onQueue('mmo');
|
MediaDeletePipeline::dispatch($media)->onQueue('mmo');
|
||||||
|
@ -290,13 +285,13 @@ class MediaStorageService {
|
||||||
|
|
||||||
protected function cloudMove($media)
|
protected function cloudMove($media)
|
||||||
{
|
{
|
||||||
if(!Storage::exists($media->media_path)) {
|
if (! Storage::exists($media->media_path)) {
|
||||||
return 'invalid file';
|
return 'invalid file';
|
||||||
}
|
}
|
||||||
|
|
||||||
$path = storage_path('app/'.$media->media_path);
|
$path = storage_path('app/'.$media->media_path);
|
||||||
$thumb = false;
|
$thumb = false;
|
||||||
if($media->thumbnail_path) {
|
if ($media->thumbnail_path) {
|
||||||
$thumb = storage_path('app/'.$media->thumbnail_path);
|
$thumb = storage_path('app/'.$media->thumbnail_path);
|
||||||
$pt = explode('/', $media->thumbnail_path);
|
$pt = explode('/', $media->thumbnail_path);
|
||||||
$thumbname = array_pop($pt);
|
$thumbname = array_pop($pt);
|
||||||
|
@ -307,7 +302,7 @@ class MediaStorageService {
|
||||||
$storagePath = implode('/', $p);
|
$storagePath = implode('/', $p);
|
||||||
|
|
||||||
$url = ResilientMediaStorageService::store($storagePath, $path, $name);
|
$url = ResilientMediaStorageService::store($storagePath, $path, $name);
|
||||||
if($thumb) {
|
if ($thumb) {
|
||||||
$thumbUrl = ResilientMediaStorageService::store($storagePath, $thumb, $thumbname);
|
$thumbUrl = ResilientMediaStorageService::store($storagePath, $thumb, $thumbname);
|
||||||
$media->thumbnail_url = $thumbUrl;
|
$media->thumbnail_url = $thumbUrl;
|
||||||
}
|
}
|
||||||
|
@ -316,8 +311,8 @@ class MediaStorageService {
|
||||||
$media->replicated_at = now();
|
$media->replicated_at = now();
|
||||||
$media->save();
|
$media->save();
|
||||||
|
|
||||||
if($media->status_id) {
|
if ($media->status_id) {
|
||||||
Cache::forget('status:transformer:media:attachments:' . $media->status_id);
|
Cache::forget('status:transformer:media:attachments:'.$media->status_id);
|
||||||
MediaService::del($media->status_id);
|
MediaService::del($media->status_id);
|
||||||
StatusService::del($media->status_id, false);
|
StatusService::del($media->status_id, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,49 +2,34 @@
|
||||||
|
|
||||||
namespace App\Util\ActivityPub;
|
namespace App\Util\ActivityPub;
|
||||||
|
|
||||||
use DB, Cache, Purify, Storage, Request, Validator;
|
use App\Instance;
|
||||||
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\Jobs\AvatarPipeline\RemoteAvatarFetch;
|
use App\Jobs\AvatarPipeline\RemoteAvatarFetch;
|
||||||
use App\Jobs\HomeFeedPipeline\FeedInsertRemotePipeline;
|
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 App\Models\Poll;
|
||||||
use Illuminate\Contracts\Cache\LockTimeoutException;
|
use App\Profile;
|
||||||
use App\Services\DomainService;
|
|
||||||
use App\Services\UserFilterService;
|
|
||||||
use App\Services\Account\AccountStatService;
|
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)
|
public static function validateObject($data)
|
||||||
{
|
{
|
||||||
$verbs = ['Create', 'Announce', 'Like', 'Follow', 'Delete', 'Accept', 'Reject', 'Undo', 'Tombstone'];
|
$verbs = ['Create', 'Announce', 'Like', 'Follow', 'Delete', 'Accept', 'Reject', 'Undo', 'Tombstone'];
|
||||||
|
@ -53,14 +38,14 @@ class Helpers {
|
||||||
'type' => [
|
'type' => [
|
||||||
'required',
|
'required',
|
||||||
'string',
|
'string',
|
||||||
Rule::in($verbs)
|
Rule::in($verbs),
|
||||||
],
|
],
|
||||||
'id' => 'required|string',
|
'id' => 'required|string',
|
||||||
'actor' => 'required|string|url',
|
'actor' => 'required|string|url',
|
||||||
'object' => 'required',
|
'object' => 'required',
|
||||||
'object.type' => 'required_if:type,Create',
|
'object.type' => 'required_if:type,Create',
|
||||||
'object.attributedTo' => 'required_if:type,Create|url',
|
'object.attributedTo' => 'required_if:type,Create|url',
|
||||||
'published' => 'required_if:type,Create|date'
|
'published' => 'required_if:type,Create|date',
|
||||||
])->passes();
|
])->passes();
|
||||||
|
|
||||||
return $valid;
|
return $valid;
|
||||||
|
@ -68,8 +53,8 @@ class Helpers {
|
||||||
|
|
||||||
public static function verifyAttachments($data)
|
public static function verifyAttachments($data)
|
||||||
{
|
{
|
||||||
if(!isset($data['object']) || empty($data['object'])) {
|
if (! isset($data['object']) || empty($data['object'])) {
|
||||||
$data = ['object'=>$data];
|
$data = ['object' => $data];
|
||||||
}
|
}
|
||||||
|
|
||||||
$activity = $data['object'];
|
$activity = $data['object'];
|
||||||
|
@ -80,7 +65,7 @@ class Helpers {
|
||||||
// Peertube
|
// Peertube
|
||||||
// $mediaTypes = in_array('video/mp4', $mimeTypes) ? ['Document', 'Image', 'Video', 'Link'] : ['Document', 'Image'];
|
// $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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,13 +85,13 @@ class Helpers {
|
||||||
'*.type' => [
|
'*.type' => [
|
||||||
'required',
|
'required',
|
||||||
'string',
|
'string',
|
||||||
Rule::in($mediaTypes)
|
Rule::in($mediaTypes),
|
||||||
],
|
],
|
||||||
'*.url' => 'required|url',
|
'*.url' => 'required|url',
|
||||||
'*.mediaType' => [
|
'*.mediaType' => [
|
||||||
'required',
|
'required',
|
||||||
'string',
|
'string',
|
||||||
Rule::in($mimeTypes)
|
Rule::in($mimeTypes),
|
||||||
],
|
],
|
||||||
'*.name' => 'sometimes|nullable|string',
|
'*.name' => 'sometimes|nullable|string',
|
||||||
'*.blurhash' => 'sometimes|nullable|string|min:6|max:164',
|
'*.blurhash' => 'sometimes|nullable|string|min:6|max:164',
|
||||||
|
@ -119,7 +104,7 @@ class Helpers {
|
||||||
|
|
||||||
public static function normalizeAudience($data, $localOnly = true)
|
public static function normalizeAudience($data, $localOnly = true)
|
||||||
{
|
{
|
||||||
if(!isset($data['to'])) {
|
if (! isset($data['to'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,32 +113,35 @@ class Helpers {
|
||||||
$audience['cc'] = [];
|
$audience['cc'] = [];
|
||||||
$scope = 'private';
|
$scope = 'private';
|
||||||
|
|
||||||
if(is_array($data['to']) && !empty($data['to'])) {
|
if (is_array($data['to']) && ! empty($data['to'])) {
|
||||||
foreach ($data['to'] as $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';
|
$scope = 'public';
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$url = $localOnly ? self::validateLocalUrl($to) : self::validateUrl($to);
|
$url = $localOnly ? self::validateLocalUrl($to) : self::validateUrl($to);
|
||||||
if($url != false) {
|
if ($url != false) {
|
||||||
array_push($audience['to'], $url);
|
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) {
|
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';
|
$scope = 'unlisted';
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$url = $localOnly ? self::validateLocalUrl($cc) : self::validateUrl($cc);
|
$url = $localOnly ? self::validateLocalUrl($cc) : self::validateUrl($cc);
|
||||||
if($url != false) {
|
if ($url != false) {
|
||||||
array_push($audience['cc'], $url);
|
array_push($audience['cc'], $url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$audience['scope'] = $scope;
|
$audience['scope'] = $scope;
|
||||||
|
|
||||||
return $audience;
|
return $audience;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,56 +149,57 @@ class Helpers {
|
||||||
{
|
{
|
||||||
$audience = self::normalizeAudience($data);
|
$audience = self::normalizeAudience($data);
|
||||||
$url = $profile->permalink();
|
$url = $profile->permalink();
|
||||||
|
|
||||||
return in_array($url, $audience['to']) || in_array($url, $audience['cc']);
|
return in_array($url, $audience['to']) || in_array($url, $audience['cc']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function validateUrl($url)
|
public static function validateUrl($url)
|
||||||
{
|
{
|
||||||
if(is_array($url)) {
|
if (is_array($url)) {
|
||||||
$url = $url[0];
|
$url = $url[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
$hash = hash('sha256', $url);
|
$hash = hash('sha256', $url);
|
||||||
$key = "helpers:url:valid:sha256-{$hash}";
|
$key = "helpers:url:valid:sha256-{$hash}";
|
||||||
|
|
||||||
$valid = Cache::remember($key, 900, function() use($url) {
|
$valid = Cache::remember($key, 900, function () use ($url) {
|
||||||
$localhosts = [
|
$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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(substr_count($url, '://') !== 1) {
|
if (substr_count($url, '://') !== 1) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(mb_substr($url, 0, 8) !== 'https://') {
|
if (mb_substr($url, 0, 8) !== 'https://') {
|
||||||
$url = 'https://' . substr($url, 8);
|
$url = 'https://'.substr($url, 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
$valid = filter_var($url, FILTER_VALIDATE_URL);
|
$valid = filter_var($url, FILTER_VALIDATE_URL);
|
||||||
|
|
||||||
if(!$valid) {
|
if (! $valid) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$host = parse_url($valid, PHP_URL_HOST);
|
$host = parse_url($valid, PHP_URL_HOST);
|
||||||
|
|
||||||
if(in_array($host, $localhosts)) {
|
if (in_array($host, $localhosts)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config('security.url.verify_dns')) {
|
if (config('security.url.verify_dns')) {
|
||||||
if(DomainService::hasValidDns($host) === false) {
|
if (DomainService::hasValidDns($host) === false) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(app()->environment() === 'production') {
|
if (app()->environment() === 'production') {
|
||||||
$bannedInstances = InstanceService::getBannedDomains();
|
$bannedInstances = InstanceService::getBannedDomains();
|
||||||
if(in_array($host, $bannedInstances)) {
|
if (in_array($host, $bannedInstances)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,12 +213,14 @@ class Helpers {
|
||||||
public static function validateLocalUrl($url)
|
public static function validateLocalUrl($url)
|
||||||
{
|
{
|
||||||
$url = self::validateUrl($url);
|
$url = self::validateUrl($url);
|
||||||
if($url == true) {
|
if ($url == true) {
|
||||||
$domain = config('pixelfed.domain.app');
|
$domain = config('pixelfed.domain.app');
|
||||||
$host = parse_url($url, PHP_URL_HOST);
|
$host = parse_url($url, PHP_URL_HOST);
|
||||||
$url = strtolower($domain) === strtolower($host) ? $url : false;
|
$url = strtolower($domain) === strtolower($host) ? $url : false;
|
||||||
|
|
||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,15 +228,16 @@ class Helpers {
|
||||||
{
|
{
|
||||||
$version = config('pixelfed.version');
|
$version = config('pixelfed.version');
|
||||||
$url = config('app.url');
|
$url = config('app.url');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'Accept' => 'application/activity+json',
|
'Accept' => 'application/activity+json',
|
||||||
'User-Agent' => "(Pixelfed/{$version}; +{$url})",
|
'User-Agent' => "(Pixelfed/{$version}; +{$url})",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function fetchFromUrl($url = false)
|
public static function fetchFromUrl($url = false)
|
||||||
{
|
{
|
||||||
if(self::validateUrl($url) == false) {
|
if (self::validateUrl($url) == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,13 +245,13 @@ class Helpers {
|
||||||
$key = "helpers:url:fetcher:sha256-{$hash}";
|
$key = "helpers:url:fetcher:sha256-{$hash}";
|
||||||
$ttl = now()->addMinutes(15);
|
$ttl = now()->addMinutes(15);
|
||||||
|
|
||||||
return Cache::remember($key, $ttl, function() use($url) {
|
return Cache::remember($key, $ttl, function () use ($url) {
|
||||||
$res = ActivityPubFetchService::get($url);
|
$res = ActivityPubFetchService::get($url);
|
||||||
if(!$res || empty($res)) {
|
if (! $res || empty($res)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
$res = json_decode($res, true, 8);
|
$res = json_decode($res, true, 8);
|
||||||
if(json_last_error() == JSON_ERROR_NONE) {
|
if (json_last_error() == JSON_ERROR_NONE) {
|
||||||
return $res;
|
return $res;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
|
@ -274,12 +266,12 @@ class Helpers {
|
||||||
|
|
||||||
public static function pluckval($val)
|
public static function pluckval($val)
|
||||||
{
|
{
|
||||||
if(is_string($val)) {
|
if (is_string($val)) {
|
||||||
return $val;
|
return $val;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(is_array($val)) {
|
if (is_array($val)) {
|
||||||
return !empty($val) ? head($val) : null;
|
return ! empty($val) ? head($val) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -288,51 +280,52 @@ class Helpers {
|
||||||
public static function statusFirstOrFetch($url, $replyTo = false)
|
public static function statusFirstOrFetch($url, $replyTo = false)
|
||||||
{
|
{
|
||||||
$url = self::validateUrl($url);
|
$url = self::validateUrl($url);
|
||||||
if($url == false) {
|
if ($url == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$host = parse_url($url, PHP_URL_HOST);
|
$host = parse_url($url, PHP_URL_HOST);
|
||||||
$local = config('pixelfed.domain.app') == $host ? true : false;
|
$local = config('pixelfed.domain.app') == $host ? true : false;
|
||||||
|
|
||||||
if($local) {
|
if ($local) {
|
||||||
$id = (int) last(explode('/', $url));
|
$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)
|
->whereUri($url)
|
||||||
->orWhere('object_url', $url)
|
->orWhere('object_url', $url)
|
||||||
->first();
|
->first();
|
||||||
|
|
||||||
if($cached) {
|
if ($cached) {
|
||||||
return $cached;
|
return $cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
$res = self::fetchFromUrl($url);
|
$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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config('autospam.live_filters.enabled')) {
|
if (config('autospam.live_filters.enabled')) {
|
||||||
$filters = config('autospam.live_filters.filters');
|
$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));
|
$filters = array_map('trim', explode(',', $filters));
|
||||||
$content = $res['content'];
|
$content = $res['content'];
|
||||||
foreach($filters as $filter) {
|
foreach ($filters as $filter) {
|
||||||
$filter = trim(strtolower($filter));
|
$filter = trim(strtolower($filter));
|
||||||
if(!$filter || !strlen($filter)) {
|
if (! $filter || ! strlen($filter)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if(str_contains(strtolower($content), $filter)) {
|
if (str_contains(strtolower($content), $filter)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($res['object'])) {
|
if (isset($res['object'])) {
|
||||||
$activity = $res;
|
$activity = $res;
|
||||||
} else {
|
} else {
|
||||||
$activity = ['object' => $res];
|
$activity = ['object' => $res];
|
||||||
|
@ -342,37 +335,37 @@ class Helpers {
|
||||||
|
|
||||||
$cw = isset($res['sensitive']) ? (bool) $res['sensitive'] : false;
|
$cw = isset($res['sensitive']) ? (bool) $res['sensitive'] : false;
|
||||||
|
|
||||||
if(isset($res['to']) == true) {
|
if (isset($res['to']) == true) {
|
||||||
if(is_array($res['to']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['to'])) {
|
if (is_array($res['to']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['to'])) {
|
||||||
$scope = 'public';
|
$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';
|
$scope = 'public';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($res['cc']) == true) {
|
if (isset($res['cc']) == true) {
|
||||||
if(is_array($res['cc']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['cc'])) {
|
if (is_array($res['cc']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['cc'])) {
|
||||||
$scope = 'unlisted';
|
$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';
|
$scope = 'unlisted';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(config('costar.enabled') == true) {
|
if (config('costar.enabled') == true) {
|
||||||
$blockedKeywords = config('costar.keyword.block');
|
$blockedKeywords = config('costar.keyword.block');
|
||||||
if($blockedKeywords !== null) {
|
if ($blockedKeywords !== null) {
|
||||||
$keywords = config('costar.keyword.block');
|
$keywords = config('costar.keyword.block');
|
||||||
foreach($keywords as $kw) {
|
foreach ($keywords as $kw) {
|
||||||
if(Str::contains($res['content'], $kw) == true) {
|
if (Str::contains($res['content'], $kw) == true) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$unlisted = config('costar.domain.unlisted');
|
$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;
|
$unlisted = true;
|
||||||
$scope = 'unlisted';
|
$scope = 'unlisted';
|
||||||
} else {
|
} else {
|
||||||
|
@ -380,7 +373,7 @@ class Helpers {
|
||||||
}
|
}
|
||||||
|
|
||||||
$cwDomains = config('costar.domain.cw');
|
$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;
|
$cw = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -389,15 +382,15 @@ class Helpers {
|
||||||
$idDomain = parse_url($id, PHP_URL_HOST);
|
$idDomain = parse_url($id, PHP_URL_HOST);
|
||||||
$urlDomain = parse_url($url, 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!self::validateUrl($id)) {
|
if (! self::validateUrl($id)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!isset($activity['object']['attributedTo'])) {
|
if (! isset($activity['object']['attributedTo'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,39 +398,38 @@ class Helpers {
|
||||||
$activity['object']['attributedTo'] :
|
$activity['object']['attributedTo'] :
|
||||||
(is_array($activity['object']['attributedTo']) ?
|
(is_array($activity['object']['attributedTo']) ?
|
||||||
collect($activity['object']['attributedTo'])
|
collect($activity['object']['attributedTo'])
|
||||||
->filter(function($o) {
|
->filter(function ($o) {
|
||||||
return $o && isset($o['type']) && $o['type'] == 'Person';
|
return $o && isset($o['type']) && $o['type'] == 'Person';
|
||||||
})
|
})
|
||||||
->pluck('id')
|
->pluck('id')
|
||||||
->first() : null
|
->first() : null
|
||||||
);
|
);
|
||||||
|
|
||||||
if($attributedTo) {
|
if ($attributedTo) {
|
||||||
$actorDomain = parse_url($attributedTo, PHP_URL_HOST);
|
$actorDomain = parse_url($attributedTo, PHP_URL_HOST);
|
||||||
if(!self::validateUrl($attributedTo) ||
|
if (! self::validateUrl($attributedTo) ||
|
||||||
$idDomain !== $actorDomain ||
|
$idDomain !== $actorDomain ||
|
||||||
$actorDomain !== $urlDomain
|
$actorDomain !== $urlDomain
|
||||||
)
|
) {
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($idDomain !== $urlDomain) {
|
if ($idDomain !== $urlDomain) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$profile = self::profileFirstOrNew($attributedTo);
|
$profile = self::profileFirstOrNew($attributedTo);
|
||||||
|
|
||||||
if(!$profile) {
|
if (! $profile) {
|
||||||
return;
|
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);
|
$reply_to = self::statusFirstOrFetch(self::pluckval($activity['object']['inReplyTo']), false);
|
||||||
if($reply_to) {
|
if ($reply_to) {
|
||||||
$blocks = UserFilterService::blocks($reply_to->profile_id);
|
$blocks = UserFilterService::blocks($reply_to->profile_id);
|
||||||
if(in_array($profile->id, $blocks)) {
|
if (in_array($profile->id, $blocks)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -447,15 +439,15 @@ class Helpers {
|
||||||
}
|
}
|
||||||
$ts = self::pluckval($res['published']);
|
$ts = self::pluckval($res['published']);
|
||||||
|
|
||||||
if($scope == 'public' && in_array($urlDomain, InstanceService::getUnlistedDomains())) {
|
if ($scope == 'public' && in_array($urlDomain, InstanceService::getUnlistedDomains())) {
|
||||||
$scope = 'unlisted';
|
$scope = 'unlisted';
|
||||||
}
|
}
|
||||||
|
|
||||||
if(in_array($urlDomain, InstanceService::getNsfwDomains())) {
|
if (in_array($urlDomain, InstanceService::getNsfwDomains())) {
|
||||||
$cw = true;
|
$cw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($res['type'] === 'Question') {
|
if ($res['type'] === 'Question') {
|
||||||
$status = self::storePoll(
|
$status = self::storePoll(
|
||||||
$profile,
|
$profile,
|
||||||
$res,
|
$res,
|
||||||
|
@ -466,6 +458,7 @@ class Helpers {
|
||||||
$scope,
|
$scope,
|
||||||
$id
|
$id
|
||||||
);
|
);
|
||||||
|
|
||||||
return $status;
|
return $status;
|
||||||
} else {
|
} else {
|
||||||
$status = self::storeStatus($url, $profile, $res);
|
$status = self::storeStatus($url, $profile, $res);
|
||||||
|
@ -482,12 +475,12 @@ class Helpers {
|
||||||
$idDomain = parse_url($id, PHP_URL_HOST);
|
$idDomain = parse_url($id, PHP_URL_HOST);
|
||||||
$urlDomain = parse_url($url, PHP_URL_HOST);
|
$urlDomain = parse_url($url, PHP_URL_HOST);
|
||||||
$originalUrlDomain = parse_url($originalUrl, 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if( strtolower($originalUrlDomain) !== strtolower($idDomain) ||
|
if (strtolower($originalUrlDomain) !== strtolower($idDomain) ||
|
||||||
strtolower($originalUrlDomain) !== strtolower($urlDomain) ) {
|
strtolower($originalUrlDomain) !== strtolower($urlDomain)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -498,21 +491,21 @@ class Helpers {
|
||||||
$cw = self::getSensitive($activity, $url);
|
$cw = self::getSensitive($activity, $url);
|
||||||
$pid = is_object($profile) ? $profile->id : (is_array($profile) ? $profile['id'] : null);
|
$pid = is_object($profile) ? $profile->id : (is_array($profile) ? $profile['id'] : null);
|
||||||
$isUnlisted = is_object($profile) ? $profile->unlisted : (is_array($profile) ? $profile['unlisted'] : false);
|
$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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if($scope == 'public') {
|
if ($scope == 'public') {
|
||||||
if($isUnlisted == true) {
|
if ($isUnlisted == true) {
|
||||||
$scope = 'unlisted';
|
$scope = 'unlisted';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$status = Status::updateOrCreate(
|
$status = Status::updateOrCreate(
|
||||||
[
|
[
|
||||||
'uri' => $url
|
'uri' => $url,
|
||||||
], [
|
], [
|
||||||
'profile_id' => $pid,
|
'profile_id' => $pid,
|
||||||
'url' => $url,
|
'url' => $url,
|
||||||
|
@ -527,24 +520,24 @@ class Helpers {
|
||||||
'visibility' => $scope,
|
'visibility' => $scope,
|
||||||
'cw_summary' => ($cw == true && isset($activity['summary']) ?
|
'cw_summary' => ($cw == true && isset($activity['summary']) ?
|
||||||
Purify::clean(strip_tags($activity['summary'])) : null),
|
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);
|
self::importNoteAttachment($activity, $status);
|
||||||
} else {
|
} else {
|
||||||
if(isset($activity['attachment']) && !empty($activity['attachment'])) {
|
if (isset($activity['attachment']) && ! empty($activity['attachment'])) {
|
||||||
self::importNoteAttachment($activity, $status);
|
self::importNoteAttachment($activity, $status);
|
||||||
}
|
}
|
||||||
StatusReplyPipeline::dispatch($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);
|
StatusTagsPipeline::dispatch($activity, $status);
|
||||||
}
|
}
|
||||||
|
|
||||||
if( config('instance.timeline.network.cached') &&
|
if (config('instance.timeline.network.cached') &&
|
||||||
$status->in_reply_to_id === null &&
|
$status->in_reply_to_id === null &&
|
||||||
$status->reblog_of_id === null &&
|
$status->reblog_of_id === null &&
|
||||||
in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) &&
|
in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) &&
|
||||||
|
@ -556,8 +549,8 @@ class Helpers {
|
||||||
->unique()
|
->unique()
|
||||||
->values()
|
->values()
|
||||||
->toArray();
|
->toArray();
|
||||||
if(!in_array($urlDomain, $filteredDomains)) {
|
if (! in_array($urlDomain, $filteredDomains)) {
|
||||||
if(!$isUnlisted) {
|
if (! $isUnlisted) {
|
||||||
NetworkTimelineService::add($status->id);
|
NetworkTimelineService::add($status->id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -565,7 +558,7 @@ class Helpers {
|
||||||
|
|
||||||
AccountStatService::incrementPostCount($pid);
|
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'])
|
in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album'])
|
||||||
) {
|
) {
|
||||||
FeedInsertRemotePipeline::dispatch($status->id, $pid)->onQueue('feed');
|
FeedInsertRemotePipeline::dispatch($status->id, $pid)->onQueue('feed');
|
||||||
|
@ -576,14 +569,14 @@ class Helpers {
|
||||||
|
|
||||||
public static function getSensitive($activity, $url)
|
public static function getSensitive($activity, $url)
|
||||||
{
|
{
|
||||||
if(!$url || !strlen($url)) {
|
if (! $url || ! strlen($url)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$urlDomain = parse_url($url, PHP_URL_HOST);
|
$urlDomain = parse_url($url, PHP_URL_HOST);
|
||||||
$cw = isset($activity['sensitive']) ? (bool) $activity['sensitive'] : false;
|
$cw = isset($activity['sensitive']) ? (bool) $activity['sensitive'] : false;
|
||||||
|
|
||||||
if(in_array($urlDomain, InstanceService::getNsfwDomains())) {
|
if (in_array($urlDomain, InstanceService::getNsfwDomains())) {
|
||||||
$cw = true;
|
$cw = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -593,13 +586,13 @@ class Helpers {
|
||||||
public static function getReplyTo($activity)
|
public static function getReplyTo($activity)
|
||||||
{
|
{
|
||||||
$reply_to = null;
|
$reply_to = null;
|
||||||
$inReplyTo = isset($activity['inReplyTo']) && !empty($activity['inReplyTo']) ?
|
$inReplyTo = isset($activity['inReplyTo']) && ! empty($activity['inReplyTo']) ?
|
||||||
self::pluckval($activity['inReplyTo']) :
|
self::pluckval($activity['inReplyTo']) :
|
||||||
false;
|
false;
|
||||||
|
|
||||||
if($inReplyTo) {
|
if ($inReplyTo) {
|
||||||
$reply_to = self::statusFirstOrFetch($inReplyTo);
|
$reply_to = self::statusFirstOrFetch($inReplyTo);
|
||||||
if($reply_to) {
|
if ($reply_to) {
|
||||||
$reply_to = optional($reply_to)->id;
|
$reply_to = optional($reply_to)->id;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -616,25 +609,25 @@ class Helpers {
|
||||||
$urlDomain = parse_url(self::pluckval($url), PHP_URL_HOST);
|
$urlDomain = parse_url(self::pluckval($url), PHP_URL_HOST);
|
||||||
$scope = 'private';
|
$scope = 'private';
|
||||||
|
|
||||||
if(isset($activity['to']) == true) {
|
if (isset($activity['to']) == true) {
|
||||||
if(is_array($activity['to']) && in_array('https://www.w3.org/ns/activitystreams#Public', $activity['to'])) {
|
if (is_array($activity['to']) && in_array('https://www.w3.org/ns/activitystreams#Public', $activity['to'])) {
|
||||||
$scope = 'public';
|
$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';
|
$scope = 'public';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isset($activity['cc']) == true) {
|
if (isset($activity['cc']) == true) {
|
||||||
if(is_array($activity['cc']) && in_array('https://www.w3.org/ns/activitystreams#Public', $activity['cc'])) {
|
if (is_array($activity['cc']) && in_array('https://www.w3.org/ns/activitystreams#Public', $activity['cc'])) {
|
||||||
$scope = 'unlisted';
|
$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';
|
$scope = 'unlisted';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if($scope == 'public' && in_array($urlDomain, InstanceService::getUnlistedDomains())) {
|
if ($scope == 'public' && in_array($urlDomain, InstanceService::getUnlistedDomains())) {
|
||||||
$scope = 'unlisted';
|
$scope = 'unlisted';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -643,15 +636,15 @@ class Helpers {
|
||||||
|
|
||||||
private static function storePoll($profile, $res, $url, $ts, $reply_to, $cw, $scope, $id)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$options = collect($res['oneOf'])->map(function($option) {
|
$options = collect($res['oneOf'])->map(function ($option) {
|
||||||
return $option['name'];
|
return $option['name'];
|
||||||
})->toArray();
|
})->toArray();
|
||||||
|
|
||||||
$cachedTallies = collect($res['oneOf'])->map(function($option) {
|
$cachedTallies = collect($res['oneOf'])->map(function ($option) {
|
||||||
return $option['replies']['totalItems'] ?? 0;
|
return $option['replies']['totalItems'] ?? 0;
|
||||||
})->toArray();
|
})->toArray();
|
||||||
|
|
||||||
|
@ -697,9 +690,10 @@ class Helpers {
|
||||||
|
|
||||||
public static function importNoteAttachment($data, Status $status)
|
public static function importNoteAttachment($data, Status $status)
|
||||||
{
|
{
|
||||||
if(self::verifyAttachments($data) == false) {
|
if (self::verifyAttachments($data) == false) {
|
||||||
// \Log::info('importNoteAttachment::failedVerification.', [$data['id']]);
|
// \Log::info('importNoteAttachment::failedVerification.', [$data['id']]);
|
||||||
$status->viewType();
|
$status->viewType();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$attachments = isset($data['object']) ? $data['object']['attachment'] : $data['attachment'];
|
$attachments = isset($data['object']) ? $data['object']['attachment'] : $data['attachment'];
|
||||||
|
@ -712,11 +706,11 @@ class Helpers {
|
||||||
$storagePath = MediaPathService::get($user, 2);
|
$storagePath = MediaPathService::get($user, 2);
|
||||||
$allowed = explode(',', config_cache('pixelfed.media_types'));
|
$allowed = explode(',', config_cache('pixelfed.media_types'));
|
||||||
|
|
||||||
foreach($attachments as $key => $media) {
|
foreach ($attachments as $key => $media) {
|
||||||
$type = $media['mediaType'];
|
$type = $media['mediaType'];
|
||||||
$url = $media['url'];
|
$url = $media['url'];
|
||||||
$valid = self::validateUrl($url);
|
$valid = self::validateUrl($url);
|
||||||
if(in_array($type, $allowed) == false || $valid == false) {
|
if (in_array($type, $allowed) == false || $valid == false) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$blurhash = isset($media['blurhash']) ? $media['blurhash'] : null;
|
$blurhash = isset($media['blurhash']) ? $media['blurhash'] : null;
|
||||||
|
@ -735,50 +729,52 @@ class Helpers {
|
||||||
$media->remote_url = $url;
|
$media->remote_url = $url;
|
||||||
$media->caption = $caption;
|
$media->caption = $caption;
|
||||||
$media->order = $key + 1;
|
$media->order = $key + 1;
|
||||||
if($width) {
|
if ($width) {
|
||||||
$media->width = $width;
|
$media->width = $width;
|
||||||
}
|
}
|
||||||
if($height) {
|
if ($height) {
|
||||||
$media->height = $height;
|
$media->height = $height;
|
||||||
}
|
}
|
||||||
if($license) {
|
if ($license) {
|
||||||
$media->license = $license;
|
$media->license = $license;
|
||||||
}
|
}
|
||||||
$media->mime = $type;
|
$media->mime = $type;
|
||||||
$media->version = 3;
|
$media->version = 3;
|
||||||
$media->save();
|
$media->save();
|
||||||
|
|
||||||
if(config_cache('pixelfed.cloud_storage') == true) {
|
if ((bool) config_cache('pixelfed.cloud_storage') == true) {
|
||||||
MediaStoragePipeline::dispatch($media);
|
MediaStoragePipeline::dispatch($media);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$status->viewType();
|
$status->viewType();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function profileFirstOrNew($url)
|
public static function profileFirstOrNew($url)
|
||||||
{
|
{
|
||||||
$url = self::validateUrl($url);
|
$url = self::validateUrl($url);
|
||||||
if($url == false) {
|
if ($url == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$host = parse_url($url, PHP_URL_HOST);
|
$host = parse_url($url, PHP_URL_HOST);
|
||||||
$local = config('pixelfed.domain.app') == $host ? true : false;
|
$local = config('pixelfed.domain.app') == $host ? true : false;
|
||||||
|
|
||||||
if($local == true) {
|
if ($local == true) {
|
||||||
$id = last(explode('/', $url));
|
$id = last(explode('/', $url));
|
||||||
|
|
||||||
return Profile::whereNull('status')
|
return Profile::whereNull('status')
|
||||||
->whereNull('domain')
|
->whereNull('domain')
|
||||||
->whereUsername($id)
|
->whereUsername($id)
|
||||||
->firstOrFail();
|
->firstOrFail();
|
||||||
}
|
}
|
||||||
|
|
||||||
if($profile = Profile::whereRemoteUrl($url)->first()) {
|
if ($profile = Profile::whereRemoteUrl($url)->first()) {
|
||||||
if($profile->last_fetched_at && $profile->last_fetched_at->lt(now()->subHours(24))) {
|
if ($profile->last_fetched_at && $profile->last_fetched_at->lt(now()->subHours(24))) {
|
||||||
return self::profileUpdateOrCreate($url);
|
return self::profileUpdateOrCreate($url);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $profile;
|
return $profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -788,42 +784,42 @@ class Helpers {
|
||||||
public static function profileUpdateOrCreate($url)
|
public static function profileUpdateOrCreate($url)
|
||||||
{
|
{
|
||||||
$res = self::fetchProfileFromUrl($url);
|
$res = self::fetchProfileFromUrl($url);
|
||||||
if(!$res || isset($res['id']) == false) {
|
if (! $res || isset($res['id']) == false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$urlDomain = parse_url($url, PHP_URL_HOST);
|
$urlDomain = parse_url($url, PHP_URL_HOST);
|
||||||
$domain = parse_url($res['id'], PHP_URL_HOST);
|
$domain = parse_url($res['id'], PHP_URL_HOST);
|
||||||
if(strtolower($urlDomain) !== strtolower($domain)) {
|
if (strtolower($urlDomain) !== strtolower($domain)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!isset($res['preferredUsername']) && !isset($res['nickname'])) {
|
if (! isset($res['preferredUsername']) && ! isset($res['nickname'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// skip invalid usernames
|
// skip invalid usernames
|
||||||
if(!ctype_alnum($res['preferredUsername'])) {
|
if (! ctype_alnum($res['preferredUsername'])) {
|
||||||
$tmpUsername = str_replace(['_', '.', '-'], '', $res['preferredUsername']);
|
$tmpUsername = str_replace(['_', '.', '-'], '', $res['preferredUsername']);
|
||||||
if(!ctype_alnum($tmpUsername)) {
|
if (! ctype_alnum($tmpUsername)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$username = (string) Purify::clean($res['preferredUsername'] ?? $res['nickname']);
|
$username = (string) Purify::clean($res['preferredUsername'] ?? $res['nickname']);
|
||||||
if(empty($username)) {
|
if (empty($username)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$remoteUsername = $username;
|
$remoteUsername = $username;
|
||||||
$webfinger = "@{$username}@{$domain}";
|
$webfinger = "@{$username}@{$domain}";
|
||||||
|
|
||||||
if(!self::validateUrl($res['inbox'])) {
|
if (! self::validateUrl($res['inbox'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!self::validateUrl($res['id'])) {
|
if (! self::validateUrl($res['id'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$instance = Instance::updateOrCreate([
|
$instance = Instance::updateOrCreate([
|
||||||
'domain' => $domain
|
'domain' => $domain,
|
||||||
]);
|
]);
|
||||||
if($instance->wasRecentlyCreated == true) {
|
if ($instance->wasRecentlyCreated == true) {
|
||||||
\App\Jobs\InstancePipeline\FetchNodeinfoPipeline::dispatch($instance)->onQueue('low');
|
\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))
|
$profile->last_fetched_at->lt(now()->subMonths(3))
|
||||||
) {
|
) {
|
||||||
RemoteAvatarFetch::dispatch($profile);
|
RemoteAvatarFetch::dispatch($profile);
|
||||||
}
|
}
|
||||||
$profile->last_fetched_at = now();
|
$profile->last_fetched_at = now();
|
||||||
$profile->save();
|
$profile->save();
|
||||||
|
|
||||||
return $profile;
|
return $profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -863,7 +860,7 @@ class Helpers {
|
||||||
|
|
||||||
public static function sendSignedObject($profile, $url, $body)
|
public static function sendSignedObject($profile, $url, $body)
|
||||||
{
|
{
|
||||||
if(app()->environment() !== 'production') {
|
if (app()->environment() !== 'production') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ActivityPubDeliveryService::queue()
|
ActivityPubDeliveryService::queue()
|
||||||
|
|
|
@ -2,34 +2,32 @@
|
||||||
|
|
||||||
namespace App\Util\ActivityPub;
|
namespace App\Util\ActivityPub;
|
||||||
|
|
||||||
use App\Profile;
|
|
||||||
use App\Status;
|
|
||||||
use League\Fractal;
|
|
||||||
use App\Http\Controllers\ProfileController;
|
use App\Http\Controllers\ProfileController;
|
||||||
use App\Transformer\ActivityPub\ProfileOutbox;
|
use App\Status;
|
||||||
use App\Transformer\ActivityPub\Verb\CreateNote;
|
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)
|
if ($profile->status != null) {
|
||||||
{
|
|
||||||
abort_if(!config_cache('federation.activitypub.enabled'), 404);
|
|
||||||
abort_if(!config('federation.activitypub.outbox'), 404);
|
|
||||||
|
|
||||||
if($profile->status != null) {
|
|
||||||
return ProfileController::accountCheck($profile);
|
return ProfileController::accountCheck($profile);
|
||||||
}
|
}
|
||||||
|
|
||||||
if($profile->is_private) {
|
if ($profile->is_private) {
|
||||||
return ['error'=>'403', 'msg' => 'private profile'];
|
return ['error' => '403', 'msg' => 'private profile'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$timeline = $profile
|
$timeline = $profile
|
||||||
->statuses()
|
->statuses()
|
||||||
->whereScope('public')
|
->whereScope('public')
|
||||||
->orderBy('created_at', 'desc')
|
->orderBy('created_at', 'desc')
|
||||||
->take(10)
|
->take(10)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
$count = Status::whereProfileId($profile->id)->count();
|
$count = Status::whereProfileId($profile->id)->count();
|
||||||
|
|
||||||
|
@ -38,14 +36,14 @@ class Outbox {
|
||||||
$res = $fractal->createData($resource)->toArray();
|
$res = $fractal->createData($resource)->toArray();
|
||||||
|
|
||||||
$outbox = [
|
$outbox = [
|
||||||
'@context' => 'https://www.w3.org/ns/activitystreams',
|
'@context' => 'https://www.w3.org/ns/activitystreams',
|
||||||
'_debug' => 'Outbox only supports latest 10 objects, pagination is not supported',
|
'_debug' => 'Outbox only supports latest 10 objects, pagination is not supported',
|
||||||
'id' => $profile->permalink('/outbox'),
|
'id' => $profile->permalink('/outbox'),
|
||||||
'type' => 'OrderedCollection',
|
'type' => 'OrderedCollection',
|
||||||
'totalItems' => $count,
|
'totalItems' => $count,
|
||||||
'orderedItems' => $res['data']
|
'orderedItems' => $res['data'],
|
||||||
];
|
];
|
||||||
return $outbox;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
return $outbox;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,16 +30,16 @@ class Config
|
||||||
'version' => config('pixelfed.version'),
|
'version' => config('pixelfed.version'),
|
||||||
'open_registration' => (bool) config_cache('pixelfed.open_registration'),
|
'open_registration' => (bool) config_cache('pixelfed.open_registration'),
|
||||||
'uploader' => [
|
'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_caption_length' => (int) config_cache('pixelfed.max_caption_length'),
|
||||||
'max_altext_length' => (int) config_cache('pixelfed.max_altext_length', 150),
|
'max_altext_length' => (int) config_cache('pixelfed.max_altext_length', 150),
|
||||||
'album_limit' => (int) config_cache('pixelfed.max_album_length'),
|
'album_limit' => (int) config_cache('pixelfed.max_album_length'),
|
||||||
'image_quality' => (int) config_cache('pixelfed.image_quality'),
|
'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_image' => (bool) config_cache('pixelfed.optimize_image'),
|
||||||
'optimize_video' => (bool) config('pixelfed.optimize_video'),
|
'optimize_video' => (bool) config_cache('pixelfed.optimize_video'),
|
||||||
|
|
||||||
'media_types' => config_cache('pixelfed.media_types'),
|
'media_types' => config_cache('pixelfed.media_types'),
|
||||||
'mime_types' => config_cache('pixelfed.media_types') ? explode(',', 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()
|
public function up()
|
||||||
{
|
{
|
||||||
ini_set('memory_limit', '-1');
|
ini_set('memory_limit', '-1');
|
||||||
if(config_cache('pixelfed.cloud_storage') == false) {
|
if((bool) config_cache('pixelfed.cloud_storage') == false) {
|
||||||
return;
|
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/profile-directory.js": "/js/profile-directory.js?id=1615064235d2acf08d84c3e3d1232d7e",
|
||||||
"/js/story-compose.js": "/js/story-compose.js?id=50d723634d8d22db14d630a02774e5b7",
|
"/js/story-compose.js": "/js/story-compose.js?id=50d723634d8d22db14d630a02774e5b7",
|
||||||
"/js/direct.js": "/js/direct.js?id=2f7df211df1b62a0637ed87f2457e918",
|
"/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/spa.js": "/js/spa.js?id=cd6a07d612c09f17c34a6e8441768020",
|
||||||
"/js/stories.js": "/js/stories.js?id=f3d502fa937e5fa90d173d5d7aa64e2c",
|
"/js/stories.js": "/js/stories.js?id=f3d502fa937e5fa90d173d5d7aa64e2c",
|
||||||
"/js/portfolio.js": "/js/portfolio.js?id=e8a1f57ef2c7c9ff40265502da5b84ac",
|
"/js/portfolio.js": "/js/portfolio.js?id=e8a1f57ef2c7c9ff40265502da5b84ac",
|
||||||
|
@ -24,11 +24,11 @@
|
||||||
"/js/admin_invite.js": "/js/admin_invite.js?id=0a0036f59cfb186f7698207ae432365b",
|
"/js/admin_invite.js": "/js/admin_invite.js?id=0a0036f59cfb186f7698207ae432365b",
|
||||||
"/js/landing.js": "/js/landing.js?id=753a52aacb8bb884f50ed3ae9ed99a38",
|
"/js/landing.js": "/js/landing.js?id=753a52aacb8bb884f50ed3ae9ed99a38",
|
||||||
"/js/remote_auth.js": "/js/remote_auth.js?id=37e5bdf3bc1896eee063db7a186b9876",
|
"/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/home.chunk.264eeb47bfac56c1.js": "/js/home.chunk.264eeb47bfac56c1.js?id=c5704eda3f241103f1ed1fa6fa4cefad",
|
||||||
"/js/compose.chunk.a0cfdf07f5062445.js": "/js/compose.chunk.a0cfdf07f5062445.js?id=71f4bcf44739473ee369521ea785f63e",
|
"/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/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~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/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",
|
"/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/appdark.css": "/css/appdark.css?id=7f9ba0a926020571e9c8fbedd2ec6a6f",
|
||||||
"/css/app.css": "/css/app.css?id=838b7d90a81e16b8a9adc8644237606a",
|
"/css/app.css": "/css/app.css?id=838b7d90a81e16b8a9adc8644237606a",
|
||||||
"/css/portfolio.css": "/css/portfolio.css?id=d98e354f173c6a8b729626384dceaa90",
|
"/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/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"
|
"/js/vendor.js": "/js/vendor.js?id=9e8c3caac2c4d0119e99070a0bb36dfc"
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,16 +202,12 @@
|
||||||
if(res.data && res.data.length) {
|
if(res.data && res.data.length) {
|
||||||
this.feed = res.data;
|
this.feed = res.data;
|
||||||
this.maxId = res.data[res.data.length - 1].id;
|
this.maxId = res.data[res.data.length - 1].id;
|
||||||
return true;
|
this.canLoadMore = true;
|
||||||
} else {
|
} else {
|
||||||
this.feedLoaded = true;
|
this.feedLoaded = true;
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(res => {
|
|
||||||
this.canLoadMore = res;
|
|
||||||
})
|
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.feedLoaded = true;
|
this.feedLoaded = true;
|
||||||
this.isLoaded = true;
|
this.isLoaded = true;
|
||||||
|
@ -242,14 +238,11 @@
|
||||||
if(res.data && res.data.length) {
|
if(res.data && res.data.length) {
|
||||||
this.feed.push(...res.data);
|
this.feed.push(...res.data);
|
||||||
this.maxId = res.data[res.data.length - 1].id;
|
this.maxId = res.data[res.data.length - 1].id;
|
||||||
return true;
|
this.canLoadMore = true;
|
||||||
} else {
|
} else {
|
||||||
return false;
|
this.canLoadMore = false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(res => {
|
|
||||||
this.canLoadMore = res;
|
|
||||||
})
|
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.isIntersecting = false;
|
this.isIntersecting = false;
|
||||||
})
|
})
|
||||||
|
|
|
@ -251,10 +251,11 @@
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<b-button
|
<b-button
|
||||||
variant="secondary"
|
variant="link-dark"
|
||||||
@click="showInstanceModal = false"
|
size="sm"
|
||||||
|
@click="onViewMoreInstance"
|
||||||
>
|
>
|
||||||
Close
|
View More
|
||||||
</b-button>
|
</b-button>
|
||||||
<b-button
|
<b-button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
|
@ -885,8 +886,12 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
onViewMoreInstance() {
|
||||||
|
this.showInstanceModal = false;
|
||||||
|
window.location.href = '/i/admin/instances/show/' + this.instanceModal.id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-0" :style="{ 'font-size':`${fontSize}px` }">{{ contentText }}</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>
|
</div>
|
||||||
</template>
|
</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
|
require('./../components/admin/AdminReports.vue').default
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Vue.component(
|
||||||
|
'admin-settings',
|
||||||
|
require('./../components/admin/AdminSettings.vue').default
|
||||||
|
);
|
||||||
|
|
||||||
Vue.component(
|
Vue.component(
|
||||||
'instances-component',
|
'instances-component',
|
||||||
require('./../components/admin/AdminInstances.vue').default
|
require('./../components/admin/AdminInstances.vue').default
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Vue.component(
|
||||||
|
// 'instance-details-component',
|
||||||
|
// require('./../components/admin/AdminInstanceDetails.vue').default
|
||||||
|
// );
|
||||||
|
|
||||||
Vue.component(
|
Vue.component(
|
||||||
'hashtag-component',
|
'hashtag-component',
|
||||||
require('./../components/admin/AdminHashtags.vue').default
|
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');
|
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-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
}
|
}
|
||||||
/*------------------------
|
/*------------------------
|
||||||
base class definition
|
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;
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.timestamp-overlay-badge {
|
||||||
|
color: var(--dark);
|
||||||
|
}
|
||||||
|
|
||||||
.timeline-status-component {
|
.timeline-status-component {
|
||||||
.username {
|
.username {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
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="col-12 col-md-6 offset-md-3 my-3">
|
||||||
<div class="border rounded p-3 border-primary">
|
<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="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>
|
</div>
|
||||||
|
|
||||||
|
@ -100,4 +100,4 @@
|
||||||
ctx.putImageData(imageData, 0, 0);
|
ctx.putImageData(imageData, 0, 0);
|
||||||
</script>
|
</script>
|
||||||
@endif
|
@endif
|
||||||
@endpush
|
@endpush
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
<div class="col-12 col-md-6 offset-md-3 my-3">
|
<div class="col-12 col-md-6 offset-md-3 my-3">
|
||||||
<div class="border rounded p-3 border-primary">
|
<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="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>
|
</div>
|
||||||
|
|
||||||
|
@ -127,4 +127,4 @@
|
||||||
ctx.putImageData(imageData, 0, 0);
|
ctx.putImageData(imageData, 0, 0);
|
||||||
</script>
|
</script>
|
||||||
@endif
|
@endif
|
||||||
@endpush
|
@endpush
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
<div class="col-12 col-md-6 offset-md-3 my-3">
|
<div class="col-12 col-md-6 offset-md-3 my-3">
|
||||||
<div class="border rounded p-3 border-primary">
|
<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="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>
|
</div>
|
||||||
<div class="col-12 col-md-6 offset-md-3 mt-4 mb-5">
|
<div class="col-12 col-md-6 offset-md-3 mt-4 mb-5">
|
||||||
|
@ -96,4 +96,4 @@
|
||||||
ctx.putImageData(imageData, 0, 0);
|
ctx.putImageData(imageData, 0, 0);
|
||||||
</script>
|
</script>
|
||||||
@endif
|
@endif
|
||||||
@endpush
|
@endpush
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
<div class="col-12 col-md-6 offset-md-3 my-3">
|
<div class="col-12 col-md-6 offset-md-3 my-3">
|
||||||
<div class="border rounded p-3 border-primary">
|
<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="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>
|
</div>
|
||||||
|
|
||||||
|
@ -125,4 +125,4 @@
|
||||||
ctx.putImageData(imageData, 0, 0);
|
ctx.putImageData(imageData, 0, 0);
|
||||||
</script>
|
</script>
|
||||||
@endif
|
@endif
|
||||||
@endpush
|
@endpush
|
||||||
|
|
|
@ -66,7 +66,7 @@
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<strong><span class="badge badge-primary">OAUTH</span> enabled: </strong>
|
<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>
|
||||||
<li>
|
<li>
|
||||||
<strong><span class="badge badge-primary">OAUTH</span> token_expiration</strong>
|
<strong><span class="badge badge-primary">OAUTH</span> token_expiration</strong>
|
||||||
|
@ -298,7 +298,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">FEDERATION</span></td>
|
<td><span class="badge badge-primary">FEDERATION</span></td>
|
||||||
<td><strong>ACTIVITY_PUB</strong></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>
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">FEDERATION</span></td>
|
<td><span class="badge badge-primary">FEDERATION</span></td>
|
||||||
|
@ -358,7 +358,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">FEDERATION</span></td>
|
<td><span class="badge badge-primary">FEDERATION</span></td>
|
||||||
<td><strong>PF_NETWORK_TIMELINE</strong></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>
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">FEDERATION</span></td>
|
<td><span class="badge badge-primary">FEDERATION</span></td>
|
||||||
|
@ -368,7 +368,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">FEDERATION</span></td>
|
<td><span class="badge badge-primary">FEDERATION</span></td>
|
||||||
<td><strong>CUSTOM_EMOJI</strong></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>
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">FEDERATION</span></td>
|
<td><span class="badge badge-primary">FEDERATION</span></td>
|
||||||
|
@ -545,7 +545,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">INSTANCE</span></td>
|
<td><span class="badge badge-primary">INSTANCE</span></td>
|
||||||
<td><strong>STORIES_ENABLED</strong></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>
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">INSTANCE</span></td>
|
<td><span class="badge badge-primary">INSTANCE</span></td>
|
||||||
|
@ -740,7 +740,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||||
<td><strong>PF_ENABLE_CLOUD</strong></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>
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||||
|
@ -750,12 +750,12 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||||
<td><strong>PF_OPTIMIZE_IMAGES</strong></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>
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||||
<td><strong>PF_OPTIMIZE_VIDEOS</strong></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>
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||||
|
@ -810,12 +810,12 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||||
<td><strong>OAUTH_ENABLED</strong></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>
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||||
<td><strong>PF_BOUNCER_ENABLED</strong></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>
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge badge-primary">PIXELFED</span></td>
|
<td><span class="badge badge-primary">PIXELFED</span></td>
|
||||||
|
|
|
@ -1,421 +1,12 @@
|
||||||
@extends('admin.partial.template-full')
|
@extends('admin.partial.template-full')
|
||||||
|
|
||||||
@section('section')
|
@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>
|
</div>
|
||||||
<form method="post">
|
<admin-settings />
|
||||||
@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
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
$('.rule-delete').on('click', function(e) {
|
new Vue({ el: '#panel'});
|
||||||
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);
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<label class="font-weight-bold small text-muted">Captcha</label>
|
||||||
<div class="d-flex flex-grow-1">
|
<div class="d-flex flex-grow-1">
|
||||||
{!! Captcha::display(['data-theme' => 'dark']) !!}
|
{!! Captcha::display(['data-theme' => 'dark']) !!}
|
||||||
|
|
|
@ -76,10 +76,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(
|
@if(
|
||||||
config('captcha.enabled') ||
|
(bool) config_cache('captcha.enabled') &&
|
||||||
config('captcha.active.login') ||
|
(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()->has('login_attempts') &&
|
||||||
request()->session()->get('login_attempts') >= config('captcha.triggers.login.attempts')
|
request()->session()->get('login_attempts') >= config('captcha.triggers.login.attempts')
|
||||||
)
|
)
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(config('captcha.enabled'))
|
@if((bool) config_cache('captcha.enabled'))
|
||||||
<label class="font-weight-bold small text-muted">Captcha</label>
|
<label class="font-weight-bold small text-muted">Captcha</label>
|
||||||
<div class="d-flex flex-grow-1">
|
<div class="d-flex flex-grow-1">
|
||||||
{!! Captcha::display(['data-theme' => 'dark']) !!}
|
{!! Captcha::display(['data-theme' => 'dark']) !!}
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(config('captcha.enabled'))
|
@if((bool) config_cache('captcha.enabled'))
|
||||||
<label class="font-weight-bold small pt-3 text-muted">Captcha</label>
|
<label class="font-weight-bold small pt-3 text-muted">Captcha</label>
|
||||||
<div class="d-flex flex-grow-1">
|
<div class="d-flex flex-grow-1">
|
||||||
{!! Captcha::display(['data-theme' => 'dark']) !!}
|
{!! Captcha::display(['data-theme' => 'dark']) !!}
|
||||||
|
|
|
@ -81,7 +81,7 @@
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="d-flex justify-content-center my-3">
|
||||||
{!! Captcha::display() !!}
|
{!! Captcha::display() !!}
|
||||||
</div>
|
</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')
|
@section('content')
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<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')}}">
|
<link rel="manifest" href="{{url('/manifest.json')}}">
|
||||||
|
|
||||||
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
|
<meta property="og:site_name" content="{{ config_cache('app.name', 'pixelfed') }}">
|
||||||
<meta property="og:title" content="{{ $title ?? config('app.name', 'pixelfed') }}">
|
<meta property="og:title" content="{{ $title ?? config_cache('app.name', 'pixelfed') }}">
|
||||||
<meta property="og:type" content="article">
|
<meta property="og:type" content="article">
|
||||||
<meta property="og:url" content="{{url(request()->url())}}">
|
<meta property="og:url" content="{{url(request()->url())}}">
|
||||||
@stack('meta')
|
@stack('meta')
|
||||||
|
|
|
@ -70,11 +70,11 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<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">
|
<link rel="manifest" href="/manifest.json">
|
||||||
|
|
||||||
<meta property="og:site_name" content="Pixelfed">
|
<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:type" content="{{ $ogType ?? 'article' }}">
|
||||||
<meta property="og:url" content="{{url(request()->url())}}">
|
<meta property="og:url" content="{{url(request()->url())}}">
|
||||||
@stack('meta')
|
@stack('meta')
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
|
|
||||||
<title>{{ $title ?? config_cache('app.name') }}</title>
|
<title>{{ $title ?? config_cache('app.name') }}</title>
|
||||||
|
|
||||||
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
|
<meta property="og:site_name" content="{{ config_cache('app.name', 'pixelfed') }}">
|
||||||
<meta property="og:title" content="{{ $title ?? config('app.name', 'pixelfed') }}">
|
<meta property="og:title" content="{{ $title ?? config_cache('app.name', 'pixelfed') }}">
|
||||||
<meta property="og:type" content="article">
|
<meta property="og:type" content="article">
|
||||||
<meta property="og:url" content="{{request()->url()}}">
|
<meta property="og:url" content="{{request()->url()}}">
|
||||||
@stack('meta')
|
@stack('meta')
|
||||||
|
|
|
@ -9,11 +9,11 @@
|
||||||
|
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<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">
|
<link rel="manifest" href="/manifest.json">
|
||||||
|
|
||||||
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
|
<meta property="og:site_name" content="{{ config_cache('app.name', 'pixelfed') }}">
|
||||||
<meta property="og:title" content="{{ $title ?? config('app.name', 'pixelfed') }}">
|
<meta property="og:title" content="{{ $title ?? config_cache('app.name', 'pixelfed') }}">
|
||||||
<meta property="og:type" content="article">
|
<meta property="og:type" content="article">
|
||||||
<meta property="og:url" content="{{request()->url()}}">
|
<meta property="og:url" content="{{request()->url()}}">
|
||||||
@stack('meta')
|
@stack('meta')
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
{{__('navmenu.discover')}}
|
{{__('navmenu.discover')}}
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
@if(config_cache('instance.stories.enabled'))
|
@if((bool) config_cache('instance.stories.enabled'))
|
||||||
<a class="dropdown-item lead" href="/i/stories/new">
|
<a class="dropdown-item lead" href="/i/stories/new">
|
||||||
<span style="width: 50px;margin-right:14px;">
|
<span style="width: 50px;margin-right:14px;">
|
||||||
<span class="fal fa-history text-lighter fa-lg"></span>
|
<span class="fal fa-history text-lighter fa-lg"></span>
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a class="navbar-brand d-flex align-items-center" href="{{ url('/') }}">
|
<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">
|
<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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
|
|
||||||
<title>{!! $title ?? config_cache('app.name') !!}</title>
|
<title>{!! $title ?? config_cache('app.name') !!}</title>
|
||||||
|
|
||||||
<meta property="og:site_name" content="{{ config('app.name', 'pixelfed') }}">
|
<meta property="og:site_name" content="{{ config_cache('app.name', 'pixelfed') }}">
|
||||||
<meta property="og:title" content="{{ $title ?? config('app.name', 'pixelfed') }}">
|
<meta property="og:title" content="{{ $title ?? config_cache('app.name', 'pixelfed') }}">
|
||||||
<meta property="og:type" content="article">
|
<meta property="og:type" content="article">
|
||||||
<meta property="og:url" content="{{request()->url()}}">
|
<meta property="og:url" content="{{request()->url()}}">
|
||||||
@stack('meta')
|
@stack('meta')
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue