diff --git a/.env.docker b/.env.docker index 0e373aedc..cdc5ad9c6 100644 --- a/.env.docker +++ b/.env.docker @@ -1270,7 +1270,7 @@ DOCKER_WORKER_HEALTHCHECK_INTERVAL="${DOCKER_ALL_DEFAULT_HEALTHCHECK_INTERVAL:?e # # @see https://hub.docker.com/r/nginxproxy/nginx-proxy # @dottie/validate required -DOCKER_PROXY_VERSION="1.4" +DOCKER_PROXY_VERSION="1.6" # How often Docker health check should run for [proxy] service # @dottie/validate required diff --git a/CHANGELOG.md b/CHANGELOG.md index 5aa1f0fda..290d53902 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,39 @@ # Release Notes ## [Unreleased](https://github.com/pixelfed/pixelfed/compare/v0.12.3...dev) + +### Features +- WebGL photo filters ([#5374](https://github.com/pixelfed/pixelfed/pull/5374)) + +### OAuth +- Fix oauth oob (urn:ietf:wg:oauth:2.0:oob) support. ([8afbdb03](https://github.com/pixelfed/pixelfed/commit/8afbdb03)) + +### Updates +- Update AP helpers, reject statuses with invalid dates ([960f3849](https://github.com/pixelfed/pixelfed/commit/960f3849)) +- Update DirectMessage API, fix broken threading ([044d410c](https://github.com/pixelfed/pixelfed/commit/044d410c)) +- Update Status caption render logic ([fb8dbb95](https://github.com/pixelfed/pixelfed/commit/fb8dbb95)) +- Update ApiV1Controller, fix bookmark bug. Closes #5216 ([9f7cc52c](https://github.com/pixelfed/pixelfed/commit/9f7cc52c)) +- Update Status caption logic, stop storing duplicate html caption in db and defer to cached StatusService rendering ([9eeb7b67](https://github.com/pixelfed/pixelfed/commit/9eeb7b67)) +- Update AutolinkService, optimize lookups ([eac2c196](https://github.com/pixelfed/pixelfed/commit/eac2c196)) +- Update DirectMessageController, remove 72h limit for admins ([639df410](https://github.com/pixelfed/pixelfed/commit/639df410)) +- Update StatusService, fix newlines ([56c07b7a](https://github.com/pixelfed/pixelfed/commit/56c07b7a)) +- Update confirm email template, add plaintext link. Fixes #5375 ([45986707](https://github.com/pixelfed/pixelfed/commit/45986707)) +- Update UserVerifyEmail command ([77da9ad8](https://github.com/pixelfed/pixelfed/commit/77da9ad8)) +- Update StatusStatelessTransformer, refactor the caption field to be compliant with the MastoAPI. Fixes #5364 ([79039ba5](https://github.com/pixelfed/pixelfed/commit/79039ba5)) +- Update mailgun config, add endpoint and scheme ([271d5114](https://github.com/pixelfed/pixelfed/commit/271d5114)) +- Update search and status logic to fix postgres bugs ([8c39ef4](https://github.com/pixelfed/pixelfed/commit/8c39ef4)) +- Update db, fix sqlite migrations ([#5379](https://github.com/pixelfed/pixelfed/pull/5379)) +- Update CatchUnoptimizedMedia command, make 1hr limit opt-in ([99b15b73](https://github.com/pixelfed/pixelfed/commit/99b15b73)) +- Update IG, fix Instagram import. Closes #5411 ([fd434aec](https://github.com/pixelfed/pixelfed/commit/fd434aec)) +- Update StatusTagsPipeline, fix hashtag bug and formatting ([d516b799](https://github.com/pixelfed/pixelfed/commit/d516b799)) +- Update CollectionController, fix showCollection signature ([4e1dd599](https://github.com/pixelfed/pixelfed/commit/4e1dd599)) +- Update ApiV1Dot1Controller, fix in-app registration ([56f17b99](https://github.com/pixelfed/pixelfed/commit/56f17b99)) +- Update VerifyCsrfToken middleware, add oauth token. Fixes #5426 ([79ebbc2d](https://github.com/pixelfed/pixelfed/commit/79ebbc2d)) +- Update AdminSettingsController, increase max photo size limit from 50MB to 1GB ([aa448354](https://github.com/pixelfed/pixelfed/commit/aa448354)) +- Update BearerTokenResponse, return scopes in /oauth/token endpoint. Fixes #5286 ([d8f5c302](https://github.com/pixelfed/pixelfed/commit/d8f5c302)) +- Update hashtag component, fix missing video thumbnails ([witten](https://github.com/witten)) ([#5427](https://github.com/pixelfed/pixelfed/pull/5427)) +- Update AP Status Transformer, fix inReplyTo. Fixes #5409 ([83cc932f](https://github.com/pixelfed/pixelfed/commit/83cc932f)) +- Update Data Export, refactor following/follower and statuses exports to allow accounts of any size with api entity instead of ap ([0d25917c](https://github.com/pixelfed/pixelfed/commit/0d25917c)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.12.4 (2024-11-08)](https://github.com/pixelfed/pixelfed/compare/v0.12.4...dev) diff --git a/README.md b/README.md index e2a90535e..08568fc6d 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ +
## Introduction diff --git a/app/Auth/BearerTokenResponse.php b/app/Auth/BearerTokenResponse.php index 0e1aa8a19..25c2c4bd5 100644 --- a/app/Auth/BearerTokenResponse.php +++ b/app/Auth/BearerTokenResponse.php @@ -11,13 +11,13 @@ class BearerTokenResponse extends \League\OAuth2\Server\ResponseTypes\BearerToke * AuthorizationServer::getResponseType() to pull in your version of * this class rather than the default. * - * @param AccessTokenEntityInterface $accessToken * * @return array */ protected function getExtraParams(AccessTokenEntityInterface $accessToken) { return [ + 'scope' => implode(' ', array_map(fn ($scope) => $scope->getIdentifier(), $accessToken->getScopes())), 'created_at' => time(), ]; } diff --git a/app/Console/Commands/CatchUnoptimizedMedia.php b/app/Console/Commands/CatchUnoptimizedMedia.php index a62bd8651..a5bb22f98 100644 --- a/app/Console/Commands/CatchUnoptimizedMedia.php +++ b/app/Console/Commands/CatchUnoptimizedMedia.php @@ -40,10 +40,11 @@ class CatchUnoptimizedMedia extends Command */ public function handle() { + $hasLimit = (bool) config('media.image_optimize.catch_unoptimized_media_hour_limit'); Media::whereNull('processed_at') - ->where('created_at', '>', now()->subHours(1)) - ->where('skip_optimize', '!=', true) - ->whereNull('remote_url') + ->when($hasLimit, function($q, $hasLimit) { + $q->where('created_at', '>', now()->subHours(1)); + })->whereNull('remote_url') ->whereNotNull('status_id') ->whereNotNull('media_path') ->whereIn('mime', [ @@ -52,6 +53,7 @@ class CatchUnoptimizedMedia extends Command ]) ->chunk(50, function($medias) { foreach ($medias as $media) { + if ($media->skip_optimize) continue; ImageOptimize::dispatch($media); } }); diff --git a/app/Console/Commands/ReclaimUsername.php b/app/Console/Commands/ReclaimUsername.php new file mode 100644 index 000000000..c3bda3120 --- /dev/null +++ b/app/Console/Commands/ReclaimUsername.php @@ -0,0 +1,85 @@ + strlen($search) > 0 ? $this->getUsernameOptions($search) : [], + required: true + ); + + $user = User::whereUsername($username)->withTrashed()->first(); + $profile = Profile::whereUsername($username)->withTrashed()->first(); + + if (!$user && !$profile) { + $this->error("No user or profile found with username: {$username}"); + return Command::FAILURE; + } + + if ($user->delete_after === null || $user->status !== 'deleted') { + $this->error("Cannot reclaim an active account: {$username}"); + return Command::FAILURE; + } + + $confirm = confirm( + label: "Are you sure you want to force delete user and profile with username: {$username}?", + default: false + ); + + if (!$confirm) { + $this->info('Operation cancelled.'); + return Command::SUCCESS; + } + + if ($user) { + $user->forceDelete(); + $this->info("User {$username} has been force deleted."); + } + + if ($profile) { + $profile->forceDelete(); + $this->info("Profile {$username} has been force deleted."); + } + + $this->info('Username reclaimed successfully!'); + return Command::SUCCESS; + } + + private function getUsernameOptions(string $search = ''): array + { + return User::where('username', 'like', "{$search}%") + ->withTrashed() + ->whereNotNull('delete_after') + ->take(10) + ->pluck('username') + ->toArray(); + } +} diff --git a/app/Console/Commands/TransformImports.php b/app/Console/Commands/TransformImports.php index a5a4dbb7a..6b6efa6e3 100644 --- a/app/Console/Commands/TransformImports.php +++ b/app/Console/Commands/TransformImports.php @@ -2,17 +2,16 @@ namespace App\Console\Commands; -use Illuminate\Console\Command; -use App\Models\ImportPost; -use App\Services\ImportService; use App\Media; +use App\Models\ImportPost; use App\Profile; -use App\Status; -use Storage; use App\Services\AccountService; +use App\Services\ImportService; use App\Services\MediaPathService; +use App\Status; +use Illuminate\Console\Command; use Illuminate\Support\Str; -use App\Util\Lexer\Autolink; +use Storage; class TransformImports extends Command { @@ -35,23 +34,24 @@ class TransformImports extends Command */ public function handle() { - if(!config('import.instagram.enabled')) { + if (! config('import.instagram.enabled')) { return; } $ips = ImportPost::whereNull('status_id')->where('skip_missing_media', '!=', true)->take(500)->get(); - if(!$ips->count()) { + if (! $ips->count()) { return; } - foreach($ips as $ip) { + foreach ($ips as $ip) { $id = $ip->user_id; $pid = $ip->profile_id; $profile = Profile::find($pid); - if(!$profile) { + if (! $profile) { $ip->skip_missing_media = true; $ip->save(); + continue; } @@ -63,39 +63,43 @@ class TransformImports extends Command ->where('creation_day', $ip->creation_day) ->exists(); - if($exists == true) { + if ($exists == true) { $ip->skip_missing_media = true; $ip->save(); + continue; } $idk = ImportService::getId($ip->user_id, $ip->creation_year, $ip->creation_month, $ip->creation_day); - if(!$idk) { + if (! $idk) { $ip->skip_missing_media = true; $ip->save(); + continue; } - if(Storage::exists('imports/' . $id . '/' . $ip->filename) === false) { + if (Storage::exists('imports/'.$id.'/'.$ip->filename) === false) { ImportService::clearAttempts($profile->id); ImportService::getPostCount($profile->id, true); $ip->skip_missing_media = true; $ip->save(); + continue; } $missingMedia = false; - foreach($ip->media as $ipm) { + foreach ($ip->media as $ipm) { $fileName = last(explode('/', $ipm['uri'])); - $og = 'imports/' . $id . '/' . $fileName; - if(!Storage::exists($og)) { + $og = 'imports/'.$id.'/'.$fileName; + if (! Storage::exists($og)) { $missingMedia = true; } } - if($missingMedia === true) { + if ($missingMedia === true) { $ip->skip_missing_media = true; $ip->save(); + continue; } @@ -103,7 +107,6 @@ class TransformImports extends Command $status = new Status; $status->profile_id = $pid; $status->caption = $caption; - $status->rendered = strlen(trim($caption)) ? Autolink::create()->autolink($ip->caption) : null; $status->type = $ip->post_type; $status->scope = 'unlisted'; @@ -112,20 +115,21 @@ class TransformImports extends Command $status->created_at = now()->parse($ip->creation_date); $status->save(); - foreach($ip->media as $ipm) { + foreach ($ip->media as $ipm) { $fileName = last(explode('/', $ipm['uri'])); $ext = last(explode('.', $fileName)); $basePath = MediaPathService::get($profile); - $og = 'imports/' . $id . '/' . $fileName; - if(!Storage::exists($og)) { + $og = 'imports/'.$id.'/'.$fileName; + if (! Storage::exists($og)) { $ip->skip_missing_media = true; $ip->save(); + continue; } $size = Storage::size($og); $mime = Storage::mimeType($og); - $newFile = Str::random(40) . '.' . $ext; - $np = $basePath . '/' . $newFile; + $newFile = Str::random(40).'.'.$ext; + $np = $basePath.'/'.$newFile; Storage::move($og, $np); $media = new Media; $media->profile_id = $pid; diff --git a/app/Console/Commands/UserVerifyEmail.php b/app/Console/Commands/UserVerifyEmail.php index 3b3cac5ef..b0461ca79 100644 --- a/app/Console/Commands/UserVerifyEmail.php +++ b/app/Console/Commands/UserVerifyEmail.php @@ -5,8 +5,9 @@ namespace App\Console\Commands; use Illuminate\Console\Command; use Illuminate\Support\Str; use App\User; +use Illuminate\Contracts\Console\PromptsForMissingInput; -class UserVerifyEmail extends Command +class UserVerifyEmail extends Command implements PromptsForMissingInput { /** * The name and signature of the console command. @@ -39,13 +40,19 @@ class UserVerifyEmail extends Command */ public function handle() { - $user = User::whereUsername($this->argument('username'))->first(); + $username = $this->argument('username'); + $user = User::whereUsername($username)->first(); if(!$user) { $this->error('Username not found'); return; } + if($user->email_verified_at) { + $this->error('Email already verified ' . $user->email_verified_at->diffForHumans()); + return; + } + $user->email_verified_at = now(); $user->save(); $this->info('Successfully verified email address for ' . $user->username); diff --git a/app/Http/Controllers/Admin/AdminSettingsController.php b/app/Http/Controllers/Admin/AdminSettingsController.php index 17ffd98fc..09b1da569 100644 --- a/app/Http/Controllers/Admin/AdminSettingsController.php +++ b/app/Http/Controllers/Admin/AdminSettingsController.php @@ -600,7 +600,7 @@ trait AdminSettingsController $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', + 'max_photo_size' => 'required|integer|min:100|max:1000000', 'media_types' => 'required', 'optimize_image' => 'required', 'optimize_video' => 'required', diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index 56617d57f..156c44a6b 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -137,7 +137,10 @@ class ApiV1Controller extends Controller 'redirect_uris' => 'required', ]); - $uris = implode(',', explode('\n', $request->redirect_uris)); + $uris = collect(explode("\n", $request->redirect_uris)) + ->map('urldecode') + ->filter() + ->join(','); $client = Passport::client()->forceFill([ 'user_id' => null, @@ -1426,6 +1429,8 @@ class ApiV1Controller extends Controller $status['favourited'] = true; $status['favourites_count'] = $status['favourites_count'] + 1; + $status['bookmarked'] = BookmarkService::get($user->profile_id, $status['id']); + $status['reblogged'] = ReblogService::get($user->profile_id, $status['id']); return $this->json($status); } @@ -1484,6 +1489,8 @@ class ApiV1Controller extends Controller $status['favourited'] = false; $status['favourites_count'] = isset($ogStatus) ? $ogStatus->likes_count : $status['favourites_count'] - 1; + $status['bookmarked'] = BookmarkService::get($user->profile_id, $status['id']); + $status['reblogged'] = ReblogService::get($user->profile_id, $status['id']); return $this->json($status); } @@ -1878,7 +1885,7 @@ class ApiV1Controller extends Controller $media->original_sha256 = $hash; $media->size = $photo->getSize(); $media->mime = $mime; - $media->caption = $request->input('description') ?? ""; + $media->caption = $request->input('description') ?? ''; $media->filter_class = $filterClass; $media->filter_name = $filterName; if ($license) { @@ -2106,7 +2113,7 @@ class ApiV1Controller extends Controller $media->original_sha256 = $hash; $media->size = $photo->getSize(); $media->mime = $mime; - $media->caption = $request->input('description') ?? ""; + $media->caption = $request->input('description') ?? ''; $media->filter_class = $filterClass; $media->filter_name = $filterName; if ($license) { @@ -3490,8 +3497,8 @@ class ApiV1Controller extends Controller return []; } - $content = strip_tags($request->input('status')); - $rendered = Autolink::create()->autolink($content); + $defaultCaption = ""; + $content = $request->filled('status') ? strip_tags($request->input('status')) : $defaultCaption; $cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false); $spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null; @@ -3505,7 +3512,7 @@ class ApiV1Controller extends Controller $status = new Status; $status->caption = $content; - $status->rendered = $rendered; + $status->rendered = $defaultCaption; $status->scope = $visibility; $status->visibility = $visibility; $status->profile_id = $user->profile_id; @@ -3530,7 +3537,7 @@ class ApiV1Controller extends Controller if (! $in_reply_to_id) { $status = new Status; $status->caption = $content; - $status->rendered = $rendered; + $status->rendered = $defaultCaption; $status->profile_id = $user->profile_id; $status->is_nsfw = $cw; $status->cw_summary = $spoilerText; @@ -3683,7 +3690,10 @@ class ApiV1Controller extends Controller } } + $defaultCaption = config_cache('database.default') === 'mysql' ? null : ''; $share = Status::firstOrCreate([ + 'caption' => $defaultCaption, + 'rendered' => $defaultCaption, 'profile_id' => $user->profile_id, 'reblog_of_id' => $status->id, 'type' => 'share', @@ -3698,6 +3708,8 @@ class ApiV1Controller extends Controller ReblogService::add($user->profile_id, $status->id); $res = StatusService::getMastodon($status->id); $res['reblogged'] = true; + $res['favourited'] = LikeService::liked($user->profile_id, $status->id); + $res['bookmarked'] = BookmarkService::get($user->profile_id, $status->id); return $this->json($res); } @@ -3744,6 +3756,8 @@ class ApiV1Controller extends Controller $res = StatusService::getMastodon($status->id); $res['reblogged'] = false; + $res['favourited'] = LikeService::liked($user->profile_id, $status->id); + $res['bookmarked'] = BookmarkService::get($user->profile_id, $status->id); return $this->json($res); } @@ -3951,6 +3965,7 @@ class ApiV1Controller extends Controller abort_unless($request->user()->tokenCan('write'), 403); $status = Status::findOrFail($id); + $user = $request->user(); $pid = $request->user()->profile_id; $account = AccountService::get($status->profile_id); abort_if(isset($account['moved'], $account['moved']['id']), 422, 'Cannot bookmark a post from an account that has migrated'); @@ -3994,6 +4009,7 @@ class ApiV1Controller extends Controller $status = Status::findOrFail($id); $pid = $request->user()->profile_id; + $user = $request->user(); abort_if($user->has_roles && ! UserRoleService::can('can-bookmark', $user->id), 403, 'Invalid permissions for this action'); abort_if($status->in_reply_to_id || $status->reblog_of_id, 404); diff --git a/app/Http/Controllers/Api/ApiV1Dot1Controller.php b/app/Http/Controllers/Api/ApiV1Dot1Controller.php index 38550e5fe..456f22da7 100644 --- a/app/Http/Controllers/Api/ApiV1Dot1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Dot1Controller.php @@ -37,7 +37,6 @@ use App\Status; use App\StatusArchived; use App\User; use App\UserSetting; -use App\Util\Lexer\Autolink; use App\Util\Lexer\RestrictedNames; use Cache; use DB; @@ -49,6 +48,7 @@ use Jenssegers\Agent\Agent; use League\Fractal; use League\Fractal\Serializer\ArraySerializer; use Mail; +use Purify; class ApiV1Dot1Controller extends Controller { @@ -629,9 +629,6 @@ class ApiV1Dot1Controller extends Controller abort_if(BouncerService::checkIp($request->ip()), 404); } - $rl = RateLimiter::attempt('pf:apiv1.1:iarc:'.$request->ip(), config('pixelfed.app_registration_confirm_rate_limit_attempts', 20), function () {}, config('pixelfed.app_registration_confirm_rate_limit_decay', 1800)); - abort_if(! $rl, 429, 'Too many requests'); - $request->validate([ 'user_token' => 'required', 'random_token' => 'required', @@ -658,7 +655,7 @@ class ApiV1Dot1Controller extends Controller $user->last_active_at = now(); $user->save(); - $token = $user->createToken('Pixelfed', ['read', 'write', 'follow', 'admin:read', 'admin:write', 'push']); + $token = $user->createToken('Pixelfed', ['read', 'write', 'follow', 'push']); return response()->json([ 'access_token' => $token->accessToken, @@ -1292,15 +1289,14 @@ class ApiV1Dot1Controller extends Controller if ($user->last_active_at == null) { return []; } - - $content = strip_tags($request->input('status')); - $rendered = Autolink::create()->autolink($content); + $defaultCaption = ''; + $content = $request->filled('status') ? strip_tags(Purify::clean($request->input('status'))) : $defaultCaption; $cw = $user->profile->cw == true ? true : $request->boolean('sensitive', false); $spoilerText = $cw && $request->filled('spoiler_text') ? $request->input('spoiler_text') : null; $status = new Status; $status->caption = $content; - $status->rendered = $rendered; + $status->rendered = $defaultCaption; $status->profile_id = $user->profile_id; $status->is_nsfw = $cw; $status->cw_summary = $spoilerText; diff --git a/app/Http/Controllers/CollectionController.php b/app/Http/Controllers/CollectionController.php index 447490433..bc9518baa 100644 --- a/app/Http/Controllers/CollectionController.php +++ b/app/Http/Controllers/CollectionController.php @@ -29,7 +29,7 @@ class CollectionController extends Controller return view('collection.create', compact('collection')); } - public function show(Request $request, int $id) + public function show(Request $request, $id) { $user = $request->user(); $collection = CollectionService::getCollection($id); diff --git a/app/Http/Controllers/CommentController.php b/app/Http/Controllers/CommentController.php index 1dd985723..3521a61d5 100644 --- a/app/Http/Controllers/CommentController.php +++ b/app/Http/Controllers/CommentController.php @@ -8,12 +8,12 @@ use App\Services\StatusService; use App\Status; use App\Transformer\Api\StatusTransformer; use App\UserFilter; -use App\Util\Lexer\Autolink; use Auth; use DB; use Illuminate\Http\Request; use League\Fractal; use League\Fractal\Serializer\ArraySerializer; +use Purify; class CommentController extends Controller { @@ -56,12 +56,11 @@ class CommentController extends Controller $reply = DB::transaction(function () use ($comment, $status, $profile, $nsfw) { $scope = $profile->is_private == true ? 'private' : 'public'; - $autolink = Autolink::create()->autolink($comment); - $reply = new Status(); + $reply = new Status; $reply->profile_id = $profile->id; $reply->is_nsfw = $nsfw; - $reply->caption = e($comment); - $reply->rendered = $autolink; + $reply->caption = Purify::clean($comment); + $reply->rendered = ""; $reply->in_reply_to_id = $status->id; $reply->in_reply_to_profile_id = $status->profile_id; $reply->scope = $scope; @@ -76,9 +75,9 @@ class CommentController extends Controller CommentPipeline::dispatch($status, $reply); if ($request->ajax()) { - $fractal = new Fractal\Manager(); - $fractal->setSerializer(new ArraySerializer()); - $entity = new Fractal\Resource\Item($reply, new StatusTransformer()); + $fractal = new Fractal\Manager; + $fractal->setSerializer(new ArraySerializer); + $entity = new Fractal\Resource\Item($reply, new StatusTransformer); $entity = $fractal->createData($entity)->toArray(); $response = [ 'code' => 200, diff --git a/app/Http/Controllers/ComposeController.php b/app/Http/Controllers/ComposeController.php index 480b9c8e4..4e65339c3 100644 --- a/app/Http/Controllers/ComposeController.php +++ b/app/Http/Controllers/ComposeController.php @@ -25,12 +25,12 @@ use App\Services\UserStorageService; use App\Status; use App\Transformer\Api\MediaTransformer; use App\UserFilter; -use App\Util\Lexer\Autolink; use App\Util\Media\Filter; use App\Util\Media\License; use Auth; use Cache; use DB; +use Purify; use Illuminate\Http\Request; use Illuminate\Support\Str; use League\Fractal; @@ -43,8 +43,8 @@ class ComposeController extends Controller public function __construct() { $this->middleware('auth'); - $this->fractal = new Fractal\Manager(); - $this->fractal->setSerializer(new ArraySerializer()); + $this->fractal = new Fractal\Manager; + $this->fractal->setSerializer(new ArraySerializer); } public function show(Request $request) @@ -112,14 +112,14 @@ class ComposeController extends Controller abort_if(MediaBlocklistService::exists($hash) == true, 451); - $media = new Media(); + $media = new Media; $media->status_id = null; $media->profile_id = $profile->id; $media->user_id = $user->id; $media->media_path = $path; $media->original_sha256 = $hash; $media->size = $photo->getSize(); - $media->caption = ""; + $media->caption = ''; $media->mime = $mime; $media->filter_class = $filterClass; $media->filter_name = $filterName; @@ -151,7 +151,7 @@ class ComposeController extends Controller $user->save(); Cache::forget($limitKey); - $resource = new Fractal\Resource\Item($media, new MediaTransformer()); + $resource = new Fractal\Resource\Item($media, new MediaTransformer); $res = $this->fractal->createData($resource)->toArray(); $res['preview_url'] = $preview_url; $res['url'] = $url; @@ -570,8 +570,9 @@ class ComposeController extends Controller $status->cw_summary = $request->input('spoiler_text'); } - $status->caption = strip_tags($request->caption); - $status->rendered = Autolink::create()->autolink($status->caption); + $defaultCaption = ""; + $status->caption = strip_tags($request->input('caption')) ?? $defaultCaption; + $status->rendered = $defaultCaption; $status->scope = 'draft'; $status->visibility = 'draft'; $status->profile_id = $profile->id; @@ -675,6 +676,7 @@ class ComposeController extends Controller $place = $request->input('place'); $cw = $request->input('cw'); $tagged = $request->input('tagged'); + $defaultCaption = config_cache('database.default') === 'mysql' ? null : ""; if ($place && is_array($place)) { $status->place_id = $place['id']; @@ -684,7 +686,8 @@ class ComposeController extends Controller $status->comments_disabled = (bool) $request->input('comments_disabled'); } - $status->caption = strip_tags($request->caption); + $status->caption = $request->filled('caption') ? strip_tags($request->caption) : $defaultCaption; + $status->rendered = $defaultCaption; $status->profile_id = $profile->id; $entities = []; $visibility = $profile->unlisted == true && $visibility == 'public' ? 'unlisted' : $visibility; @@ -693,7 +696,6 @@ class ComposeController extends Controller $status->visibility = $visibility; $status->scope = $visibility; $status->type = 'text'; - $status->rendered = Autolink::create()->autolink($status->caption); $status->entities = json_encode(array_merge([ 'timg' => [ 'version' => 0, @@ -806,7 +808,6 @@ class ComposeController extends Controller $status = new Status; $status->profile_id = $request->user()->profile_id; $status->caption = $request->input('caption'); - $status->rendered = Autolink::create()->autolink($status->caption); $status->visibility = 'draft'; $status->scope = 'draft'; $status->type = 'poll'; diff --git a/app/Http/Controllers/DirectMessageController.php b/app/Http/Controllers/DirectMessageController.php index 1a30032cd..b2e0df3a2 100644 --- a/app/Http/Controllers/DirectMessageController.php +++ b/app/Http/Controllers/DirectMessageController.php @@ -22,6 +22,7 @@ use App\Services\WebfingerService; use App\Status; use App\UserFilter; use App\Util\ActivityPub\Helpers; +use App\Util\Lexer\Autolink; use Illuminate\Http\Request; use Illuminate\Support\Str; @@ -306,7 +307,9 @@ class DirectMessageController extends Controller $user = $request->user(); abort_if($user->has_roles && ! UserRoleService::can('can-direct-message', $user->id), 403, 'Invalid permissions for this action'); - abort_if($user->created_at->gt(now()->subHours(72)), 400, 'You need to wait a bit before you can DM another account'); + if (! $user->is_admin) { + abort_if($user->created_at->gt(now()->subHours(72)), 400, 'You need to wait a bit before you can DM another account'); + } $profile = $user->profile; $recipient = Profile::where('id', '!=', $profile->id)->findOrFail($request->input('to_id')); @@ -326,7 +329,6 @@ class DirectMessageController extends Controller $status = new Status; $status->profile_id = $profile->id; $status->caption = $msg; - $status->rendered = $msg; $status->visibility = 'direct'; $status->scope = 'direct'; $status->in_reply_to_profile_id = $recipient->id; @@ -372,7 +374,7 @@ class DirectMessageController extends Controller ->exists(); if ($recipient->domain == null && $hidden == false && ! $nf) { - $notification = new Notification(); + $notification = new Notification; $notification->profile_id = $recipient->id; $notification->actor_id = $profile->id; $notification->action = 'dm'; @@ -405,6 +407,8 @@ class DirectMessageController extends Controller { $this->validate($request, [ 'pid' => 'required', + 'max_id' => 'sometimes|integer', + 'min_id' => 'sometimes|integer', ]); $user = $request->user(); abort_if($user->has_roles && ! UserRoleService::can('can-direct-message', $user->id), 403, 'Invalid permissions for this action'); @@ -419,29 +423,33 @@ class DirectMessageController extends Controller if ($min_id) { $res = DirectMessage::select('*') ->where('id', '>', $min_id) - ->where(function ($q) use ($pid, $uid) { - return $q->where([['from_id', $pid], ['to_id', $uid], - ])->orWhere([['from_id', $uid], ['to_id', $pid]]); + ->where(function ($query) use ($pid, $uid) { + $query->where('from_id', $pid)->where('to_id', $uid); + })->orWhere(function ($query) use ($pid, $uid) { + $query->where('from_id', $uid)->where('to_id', $pid); }) - ->latest() + ->orderBy('id', 'asc') ->take(8) - ->get(); + ->get() + ->reverse(); } elseif ($max_id) { $res = DirectMessage::select('*') ->where('id', '<', $max_id) - ->where(function ($q) use ($pid, $uid) { - return $q->where([['from_id', $pid], ['to_id', $uid], - ])->orWhere([['from_id', $uid], ['to_id', $pid]]); + ->where(function ($query) use ($pid, $uid) { + $query->where('from_id', $pid)->where('to_id', $uid); + })->orWhere(function ($query) use ($pid, $uid) { + $query->where('from_id', $uid)->where('to_id', $pid); }) - ->latest() + ->orderBy('id', 'desc') ->take(8) ->get(); } else { - $res = DirectMessage::where(function ($q) use ($pid, $uid) { - return $q->where([['from_id', $pid], ['to_id', $uid], - ])->orWhere([['from_id', $uid], ['to_id', $pid]]); + $res = DirectMessage::where(function ($query) use ($pid, $uid) { + $query->where('from_id', $pid)->where('to_id', $uid); + })->orWhere(function ($query) use ($pid, $uid) { + $query->where('from_id', $uid)->where('to_id', $pid); }) - ->latest() + ->orderBy('id', 'desc') ->take(8) ->get(); } @@ -630,13 +638,12 @@ class DirectMessageController extends Controller $status = new Status; $status->profile_id = $profile->id; $status->caption = null; - $status->rendered = null; $status->visibility = 'direct'; $status->scope = 'direct'; $status->in_reply_to_profile_id = $recipient->id; $status->save(); - $media = new Media(); + $media = new Media; $media->status_id = $status->id; $media->profile_id = $profile->id; $media->user_id = $user->id; @@ -824,6 +831,11 @@ class DirectMessageController extends Controller { $profile = $dm->author; $url = $dm->recipient->sharedInbox ?? $dm->recipient->inbox_url; + $status = $dm->status; + + if (! $status) { + return; + } $tags = [ [ @@ -833,6 +845,8 @@ class DirectMessageController extends Controller ], ]; + $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + $body = [ '@context' => [ 'https://w3id.org/security/v1', @@ -848,7 +862,7 @@ class DirectMessageController extends Controller 'id' => $dm->status->url(), 'type' => 'Note', 'summary' => null, - 'content' => $dm->status->rendered ?? $dm->status->caption, + 'content' => $content, 'inReplyTo' => null, 'published' => $dm->status->created_at->toAtomString(), 'url' => $dm->status->url(), diff --git a/app/Http/Controllers/GroupFederationController.php b/app/Http/Controllers/GroupFederationController.php index 7f45f74a4..0e5879b01 100644 --- a/app/Http/Controllers/GroupFederationController.php +++ b/app/Http/Controllers/GroupFederationController.php @@ -2,102 +2,106 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Cache; use App\Models\Group; use App\Models\GroupPost; -use App\Status; use App\Models\InstanceActor; use App\Services\MediaService; +use App\Status; +use App\Util\Lexer\Autolink; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Cache; class GroupFederationController extends Controller { - public function getGroupObject(Request $request, $id) - { - $group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($id); - $res = $this->showGroupObject($group); - return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); - } + public function getGroupObject(Request $request, $id) + { + $group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($id); + $res = $this->showGroupObject($group); - public function showGroupObject($group) - { - return Cache::remember('ap:groups:object:' . $group->id, 3600, function() use($group) { - return [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $group->url(), - 'inbox' => $group->permalink('/inbox'), - 'name' => $group->name, - 'outbox' => $group->permalink('/outbox'), - 'summary' => $group->description, - 'type' => 'Group', - 'attributedTo' => [ - 'type' => 'Person', - 'id' => $group->admin->permalink() - ], - // 'endpoints' => [ - // 'sharedInbox' => config('app.url') . '/f/inbox' - // ], - 'preferredUsername' => 'gid_' . $group->id, - 'publicKey' => [ - 'id' => $group->permalink('#main-key'), - 'owner' => $group->permalink(), - 'publicKeyPem' => InstanceActor::first()->public_key, - ], - 'url' => $group->permalink() - ]; + return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } - if($group->metadata && isset($group->metadata['avatar'])) { - $res['icon'] = [ - 'type' => 'Image', - 'url' => $group->metadata['avatar']['url'] - ]; - } + public function showGroupObject($group) + { + return Cache::remember('ap:groups:object:'.$group->id, 3600, function () use ($group) { + return [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $group->url(), + 'inbox' => $group->permalink('/inbox'), + 'name' => $group->name, + 'outbox' => $group->permalink('/outbox'), + 'summary' => $group->description, + 'type' => 'Group', + 'attributedTo' => [ + 'type' => 'Person', + 'id' => $group->admin->permalink(), + ], + // 'endpoints' => [ + // 'sharedInbox' => config('app.url') . '/f/inbox' + // ], + 'preferredUsername' => 'gid_'.$group->id, + 'publicKey' => [ + 'id' => $group->permalink('#main-key'), + 'owner' => $group->permalink(), + 'publicKeyPem' => InstanceActor::first()->public_key, + ], + 'url' => $group->permalink(), + ]; - if($group->metadata && isset($group->metadata['header'])) { - $res['image'] = [ - 'type' => 'Image', - 'url' => $group->metadata['header']['url'] - ]; - } - ksort($res); - return $res; - }); - } + if ($group->metadata && isset($group->metadata['avatar'])) { + $res['icon'] = [ + 'type' => 'Image', + 'url' => $group->metadata['avatar']['url'], + ]; + } - public function getStatusObject(Request $request, $gid, $sid) - { - $group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($gid); - $gp = GroupPost::whereGroupId($gid)->findOrFail($sid); - $status = Status::findOrFail($gp->status_id); - // permission check + if ($group->metadata && isset($group->metadata['header'])) { + $res['image'] = [ + 'type' => 'Image', + 'url' => $group->metadata['header']['url'], + ]; + } + ksort($res); - $res = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $gp->url(), + return $res; + }); + } - 'type' => 'Note', + public function getStatusObject(Request $request, $gid, $sid) + { + $group = Group::whereLocal(true)->whereActivitypub(true)->findOrFail($gid); + $gp = GroupPost::whereGroupId($gid)->findOrFail($sid); + $status = Status::findOrFail($gp->status_id); + // permission check + $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + $res = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $gp->url(), - 'summary' => null, - 'content' => $status->rendered ?? $status->caption, - 'inReplyTo' => null, + 'type' => 'Note', - 'published' => $status->created_at->toAtomString(), - 'url' => $gp->url(), - 'attributedTo' => $status->profile->permalink(), - 'to' => [ - 'https://www.w3.org/ns/activitystreams#Public', - $group->permalink('/followers'), - ], - 'cc' => [], - 'sensitive' => (bool) $status->is_nsfw, - 'attachment' => MediaService::activitypub($status->id), - 'target' => [ - 'type' => 'Collection', - 'id' => $group->permalink('/wall'), - 'attributedTo' => $group->permalink() - ] - ]; - // ksort($res); - return response()->json($res, 200, [], JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES); - } + 'summary' => null, + 'content' => $content, + 'inReplyTo' => null, + + 'published' => $status->created_at->toAtomString(), + 'url' => $gp->url(), + 'attributedTo' => $status->profile->permalink(), + 'to' => [ + 'https://www.w3.org/ns/activitystreams#Public', + $group->permalink('/followers'), + ], + 'cc' => [], + 'sensitive' => (bool) $status->is_nsfw, + 'attachment' => MediaService::activitypub($status->id), + 'target' => [ + 'type' => 'Collection', + 'id' => $group->permalink('/wall'), + 'attributedTo' => $group->permalink(), + ], + ]; + + // ksort($res); + return response()->json($res, 200, [], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } } diff --git a/app/Http/Controllers/InternalApiController.php b/app/Http/Controllers/InternalApiController.php index 299c9ceb6..e2795d6fc 100644 --- a/app/Http/Controllers/InternalApiController.php +++ b/app/Http/Controllers/InternalApiController.php @@ -2,442 +2,424 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; -use App\{ - AccountInterstitial, - Bookmark, - DirectMessage, - DiscoverCategory, - Hashtag, - Follower, - Like, - Media, - MediaTag, - Notification, - Profile, - StatusHashtag, - Status, - User, - UserFilter, -}; -use Auth,Cache; -use Illuminate\Support\Facades\Redis; -use Carbon\Carbon; -use League\Fractal; -use App\Transformer\Api\{ - AccountTransformer, - StatusTransformer, - // StatusMediaContainerTransformer, -}; -use App\Util\Media\Filter; -use App\Jobs\StatusPipeline\NewStatusPipeline; +use App\AccountInterstitial; +use App\Bookmark; +use App\DirectMessage; +use App\DiscoverCategory; +use App\Follower; use App\Jobs\ModPipeline\HandleSpammerPipeline; -use League\Fractal\Serializer\ArraySerializer; -use League\Fractal\Pagination\IlluminatePaginatorAdapter; -use Illuminate\Validation\Rule; -use Illuminate\Support\Str; -use App\Services\MediaTagService; +use App\Profile; +use App\Services\BookmarkService; +use App\Services\DiscoverService; use App\Services\ModLogService; use App\Services\PublicTimelineService; -use App\Services\SnowflakeService; use App\Services\StatusService; use App\Services\UserFilterService; -use App\Services\DiscoverService; -use App\Services\BookmarkService; +use App\Status; // StatusMediaContainerTransformer, +use App\Transformer\Api\StatusTransformer; +use App\User; +use Auth; +use Cache; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Redis; +use Illuminate\Validation\Rule; +use League\Fractal; +use League\Fractal\Serializer\ArraySerializer; class InternalApiController extends Controller { - protected $fractal; + protected $fractal; - public function __construct() - { - $this->middleware('auth'); - $this->fractal = new Fractal\Manager(); - $this->fractal->setSerializer(new ArraySerializer()); - } + public function __construct() + { + $this->middleware('auth'); + $this->fractal = new Fractal\Manager; + $this->fractal->setSerializer(new ArraySerializer); + } - // deprecated v2 compose api - public function compose(Request $request) - { - return redirect('/'); - } + // deprecated v2 compose api + public function compose(Request $request) + { + return redirect('/'); + } - // deprecated - public function discover(Request $request) - { - return; - } + // deprecated + public function discover(Request $request) {} - public function discoverPosts(Request $request) - { - $pid = $request->user()->profile_id; - $filters = UserFilterService::filters($pid); - $forYou = DiscoverService::getForYou(); - $posts = $forYou->take(50)->map(function($post) { - return StatusService::get($post); - }) - ->filter(function($post) use($filters) { - return $post && - isset($post['account']) && - isset($post['account']['id']) && - !in_array($post['account']['id'], $filters); - }) - ->take(12) - ->values(); - return response()->json(compact('posts')); - } + public function discoverPosts(Request $request) + { + $pid = $request->user()->profile_id; + $filters = UserFilterService::filters($pid); + $forYou = DiscoverService::getForYou(); + $posts = $forYou->take(50)->map(function ($post) { + return StatusService::get($post); + }) + ->filter(function ($post) use ($filters) { + return $post && + isset($post['account']) && + isset($post['account']['id']) && + ! in_array($post['account']['id'], $filters); + }) + ->take(12) + ->values(); - public function directMessage(Request $request, $profileId, $threadId) - { - $profile = Auth::user()->profile; + return response()->json(compact('posts')); + } - if($profileId != $profile->id) { - abort(403); - } + public function directMessage(Request $request, $profileId, $threadId) + { + $profile = Auth::user()->profile; - $msg = DirectMessage::whereToId($profile->id) - ->orWhere('from_id',$profile->id) - ->findOrFail($threadId); + if ($profileId != $profile->id) { + abort(403); + } - $thread = DirectMessage::with('status')->whereIn('to_id', [$profile->id, $msg->from_id]) - ->whereIn('from_id', [$profile->id,$msg->from_id]) - ->orderBy('created_at', 'asc') - ->paginate(30); + $msg = DirectMessage::whereToId($profile->id) + ->orWhere('from_id', $profile->id) + ->findOrFail($threadId); - return response()->json(compact('msg', 'profile', 'thread'), 200, [], JSON_PRETTY_PRINT); - } + $thread = DirectMessage::with('status')->whereIn('to_id', [$profile->id, $msg->from_id]) + ->whereIn('from_id', [$profile->id, $msg->from_id]) + ->orderBy('created_at', 'asc') + ->paginate(30); - public function statusReplies(Request $request, int $id) - { - $this->validate($request, [ - 'limit' => 'nullable|int|min:1|max:6' - ]); - $parent = Status::whereScope('public')->findOrFail($id); - $limit = $request->input('limit') ?? 3; - $children = Status::whereInReplyToId($parent->id) - ->orderBy('created_at', 'desc') - ->take($limit) - ->get(); - $resource = new Fractal\Resource\Collection($children, new StatusTransformer()); - $res = $this->fractal->createData($resource)->toArray(); + return response()->json(compact('msg', 'profile', 'thread'), 200, [], JSON_PRETTY_PRINT); + } - return response()->json($res); - } + public function statusReplies(Request $request, int $id) + { + $this->validate($request, [ + 'limit' => 'nullable|int|min:1|max:6', + ]); + $parent = Status::whereScope('public')->findOrFail($id); + $limit = $request->input('limit') ?? 3; + $children = Status::whereInReplyToId($parent->id) + ->orderBy('created_at', 'desc') + ->take($limit) + ->get(); + $resource = new Fractal\Resource\Collection($children, new StatusTransformer); + $res = $this->fractal->createData($resource)->toArray(); - public function stories(Request $request) - { + return response()->json($res); + } - } + public function stories(Request $request) {} - public function discoverCategories(Request $request) - { - $categories = DiscoverCategory::whereActive(true)->orderBy('order')->take(10)->get(); - $res = $categories->map(function($item) { - return [ - 'name' => $item->name, - 'url' => $item->url(), - 'thumb' => $item->thumb() - ]; - }); - return response()->json($res); - } + public function discoverCategories(Request $request) + { + $categories = DiscoverCategory::whereActive(true)->orderBy('order')->take(10)->get(); + $res = $categories->map(function ($item) { + return [ + 'name' => $item->name, + 'url' => $item->url(), + 'thumb' => $item->thumb(), + ]; + }); - public function modAction(Request $request) - { - abort_unless(Auth::user()->is_admin, 400); - $this->validate($request, [ - 'action' => [ - 'required', - 'string', - Rule::in([ - 'addcw', - 'remcw', - 'unlist', - 'spammer' - ]) - ], - 'item_id' => 'required|integer|min:1', - 'item_type' => [ - 'required', - 'string', - Rule::in(['profile', 'status']) - ] - ]); + return response()->json($res); + } - $action = $request->input('action'); - $item_id = $request->input('item_id'); - $item_type = $request->input('item_type'); + public function modAction(Request $request) + { + abort_unless(Auth::user()->is_admin, 400); + $this->validate($request, [ + 'action' => [ + 'required', + 'string', + Rule::in([ + 'addcw', + 'remcw', + 'unlist', + 'spammer', + ]), + ], + 'item_id' => 'required|integer|min:1', + 'item_type' => [ + 'required', + 'string', + Rule::in(['profile', 'status']), + ], + ]); - $status = Status::findOrFail($item_id); - $author = User::whereProfileId($status->profile_id)->first(); - abort_if($author && $author->is_admin, 422, 'Cannot moderate administrator accounts'); + $action = $request->input('action'); + $item_id = $request->input('item_id'); + $item_type = $request->input('item_type'); - switch($action) { - case 'addcw': - $status->is_nsfw = true; - $status->save(); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\Status::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'cw', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); + $status = Status::findOrFail($item_id); + $author = User::whereProfileId($status->profile_id)->first(); + abort_if($author && $author->is_admin, 422, 'Cannot moderate administrator accounts'); - if($status->uri == null) { - $media = $status->media; - $ai = new AccountInterstitial; - $ai->user_id = $status->profile->user_id; - $ai->type = 'post.cw'; - $ai->view = 'account.moderation.post.cw'; - $ai->item_type = 'App\Status'; - $ai->item_id = $status->id; - $ai->has_media = (bool) $media->count(); - $ai->blurhash = $media->count() ? $media->first()->blurhash : null; - $ai->meta = json_encode([ - 'caption' => $status->caption, - 'created_at' => $status->created_at, - 'type' => $status->type, - 'url' => $status->url(), - 'is_nsfw' => $status->is_nsfw, - 'scope' => $status->scope, - 'reblog' => $status->reblog_of_id, - 'likes_count' => $status->likes_count, - 'reblogs_count' => $status->reblogs_count, - ]); - $ai->save(); + switch ($action) { + case 'addcw': + $status->is_nsfw = true; + $status->save(); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\Status::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'cw', + 'message' => 'Success!', + ]) + ->accessLevel('admin') + ->save(); - $u = $status->profile->user; - $u->has_interstitial = true; - $u->save(); - } - break; + if ($status->uri == null) { + $media = $status->media; + $ai = new AccountInterstitial; + $ai->user_id = $status->profile->user_id; + $ai->type = 'post.cw'; + $ai->view = 'account.moderation.post.cw'; + $ai->item_type = 'App\Status'; + $ai->item_id = $status->id; + $ai->has_media = (bool) $media->count(); + $ai->blurhash = $media->count() ? $media->first()->blurhash : null; + $ai->meta = json_encode([ + 'caption' => $status->caption, + 'created_at' => $status->created_at, + 'type' => $status->type, + 'url' => $status->url(), + 'is_nsfw' => $status->is_nsfw, + 'scope' => $status->scope, + 'reblog' => $status->reblog_of_id, + 'likes_count' => $status->likes_count, + 'reblogs_count' => $status->reblogs_count, + ]); + $ai->save(); - case 'remcw': - $status->is_nsfw = false; - $status->save(); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\Status::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'remove_cw', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); - if($status->uri == null) { - $ai = AccountInterstitial::whereUserId($status->profile->user_id) - ->whereType('post.cw') - ->whereItemId($status->id) - ->whereItemType('App\Status') - ->first(); - $ai->delete(); - } - break; + $u = $status->profile->user; + $u->has_interstitial = true; + $u->save(); + } + break; - case 'unlist': - $status->scope = $status->visibility = 'unlisted'; - $status->save(); - PublicTimelineService::del($status->id); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\Status::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'unlist', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); + case 'remcw': + $status->is_nsfw = false; + $status->save(); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\Status::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'remove_cw', + 'message' => 'Success!', + ]) + ->accessLevel('admin') + ->save(); + if ($status->uri == null) { + $ai = AccountInterstitial::whereUserId($status->profile->user_id) + ->whereType('post.cw') + ->whereItemId($status->id) + ->whereItemType('App\Status') + ->first(); + $ai->delete(); + } + break; - if($status->uri == null) { - $media = $status->media; - $ai = new AccountInterstitial; - $ai->user_id = $status->profile->user_id; - $ai->type = 'post.unlist'; - $ai->view = 'account.moderation.post.unlist'; - $ai->item_type = 'App\Status'; - $ai->item_id = $status->id; - $ai->has_media = (bool) $media->count(); - $ai->blurhash = $media->count() ? $media->first()->blurhash : null; - $ai->meta = json_encode([ - 'caption' => $status->caption, - 'created_at' => $status->created_at, - 'type' => $status->type, - 'url' => $status->url(), - 'is_nsfw' => $status->is_nsfw, - 'scope' => $status->scope, - 'reblog' => $status->reblog_of_id, - 'likes_count' => $status->likes_count, - 'reblogs_count' => $status->reblogs_count, - ]); - $ai->save(); + case 'unlist': + $status->scope = $status->visibility = 'unlisted'; + $status->save(); + PublicTimelineService::del($status->id); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\Status::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'unlist', + 'message' => 'Success!', + ]) + ->accessLevel('admin') + ->save(); - $u = $status->profile->user; - $u->has_interstitial = true; - $u->save(); - } - break; + if ($status->uri == null) { + $media = $status->media; + $ai = new AccountInterstitial; + $ai->user_id = $status->profile->user_id; + $ai->type = 'post.unlist'; + $ai->view = 'account.moderation.post.unlist'; + $ai->item_type = 'App\Status'; + $ai->item_id = $status->id; + $ai->has_media = (bool) $media->count(); + $ai->blurhash = $media->count() ? $media->first()->blurhash : null; + $ai->meta = json_encode([ + 'caption' => $status->caption, + 'created_at' => $status->created_at, + 'type' => $status->type, + 'url' => $status->url(), + 'is_nsfw' => $status->is_nsfw, + 'scope' => $status->scope, + 'reblog' => $status->reblog_of_id, + 'likes_count' => $status->likes_count, + 'reblogs_count' => $status->reblogs_count, + ]); + $ai->save(); - case 'spammer': - HandleSpammerPipeline::dispatch($status->profile); - ModLogService::boot() - ->user(Auth::user()) - ->objectUid($status->profile->user_id) - ->objectId($status->id) - ->objectType('App\User::class') - ->action('admin.status.moderate') - ->metadata([ - 'action' => 'spammer', - 'message' => 'Success!' - ]) - ->accessLevel('admin') - ->save(); - break; - } + $u = $status->profile->user; + $u->has_interstitial = true; + $u->save(); + } + break; - StatusService::del($status->id, true); - return ['msg' => 200]; - } + case 'spammer': + HandleSpammerPipeline::dispatch($status->profile); + ModLogService::boot() + ->user(Auth::user()) + ->objectUid($status->profile->user_id) + ->objectId($status->id) + ->objectType('App\User::class') + ->action('admin.status.moderate') + ->metadata([ + 'action' => 'spammer', + 'message' => 'Success!', + ]) + ->accessLevel('admin') + ->save(); + break; + } - public function composePost(Request $request) - { - abort(400, 'Endpoint deprecated'); - } + StatusService::del($status->id, true); - public function bookmarks(Request $request) - { - $pid = $request->user()->profile_id; - $res = Bookmark::whereProfileId($pid) - ->orderByDesc('created_at') - ->simplePaginate(10) - ->map(function($bookmark) use($pid) { - $status = StatusService::get($bookmark->status_id, false); - if(!$status) { - return false; - } - $status['bookmarked_at'] = str_replace('+00:00', 'Z', $bookmark->created_at->format(DATE_RFC3339_EXTENDED)); + return ['msg' => 200]; + } - if($status) { - BookmarkService::add($pid, $status['id']); - } - return $status; - }) - ->filter(function($bookmark) { - return $bookmark && isset($bookmark['id']); - }) - ->values(); + public function composePost(Request $request) + { + abort(400, 'Endpoint deprecated'); + } - return response()->json($res); - } + public function bookmarks(Request $request) + { + $pid = $request->user()->profile_id; + $res = Bookmark::whereProfileId($pid) + ->orderByDesc('created_at') + ->simplePaginate(10) + ->map(function ($bookmark) use ($pid) { + $status = StatusService::get($bookmark->status_id, false); + if (! $status) { + return false; + } + $status['bookmarked_at'] = str_replace('+00:00', 'Z', $bookmark->created_at->format(DATE_RFC3339_EXTENDED)); - public function accountStatuses(Request $request, $id) - { - $this->validate($request, [ - 'only_media' => 'nullable', - 'pinned' => 'nullable', - 'exclude_replies' => 'nullable', - 'max_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'since_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'min_id' => 'nullable|integer|min:0|max:' . PHP_INT_MAX, - 'limit' => 'nullable|integer|min:1|max:24' - ]); + if ($status) { + BookmarkService::add($pid, $status['id']); + } - $profile = Profile::whereNull('status')->findOrFail($id); + return $status; + }) + ->filter(function ($bookmark) { + return $bookmark && isset($bookmark['id']); + }) + ->values(); - $limit = $request->limit ?? 9; - $max_id = $request->max_id; - $min_id = $request->min_id; - $scope = $request->only_media == true ? - ['photo', 'photo:album', 'video', 'video:album'] : - ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply']; + return response()->json($res); + } - if($profile->is_private) { - if(!Auth::check()) { - return response()->json([]); - } - $pid = Auth::user()->profile->id; - $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { - $following = Follower::whereProfileId($pid)->pluck('following_id'); - return $following->push($pid)->toArray(); - }); - $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : []; - } else { - if(Auth::check()) { - $pid = Auth::user()->profile->id; - $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function() use($pid) { - $following = Follower::whereProfileId($pid)->pluck('following_id'); - return $following->push($pid)->toArray(); - }); - $visibility = true == in_array($profile->id, $following) ? ['public', 'unlisted', 'private'] : ['public', 'unlisted']; - } else { - $visibility = ['public', 'unlisted']; - } - } + public function accountStatuses(Request $request, $id) + { + $this->validate($request, [ + 'only_media' => 'nullable', + 'pinned' => 'nullable', + 'exclude_replies' => 'nullable', + 'max_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX, + 'since_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX, + 'min_id' => 'nullable|integer|min:0|max:'.PHP_INT_MAX, + 'limit' => 'nullable|integer|min:1|max:24', + ]); - $dir = $min_id ? '>' : '<'; - $id = $min_id ?? $max_id; - $timeline = Status::select( - 'id', - 'uri', - 'caption', - 'rendered', - 'profile_id', - 'type', - 'in_reply_to_id', - 'reblog_of_id', - 'is_nsfw', - 'likes_count', - 'reblogs_count', - 'scope', - 'local', - 'created_at', - 'updated_at' - )->whereProfileId($profile->id) - ->whereIn('type', $scope) - ->where('id', $dir, $id) - ->whereIn('visibility', $visibility) - ->latest() - ->limit($limit) - ->get(); + $profile = Profile::whereNull('status')->findOrFail($id); - $resource = new Fractal\Resource\Collection($timeline, new StatusTransformer()); - $res = $this->fractal->createData($resource)->toArray(); + $limit = $request->limit ?? 9; + $max_id = $request->max_id; + $min_id = $request->min_id; + $scope = $request->only_media == true ? + ['photo', 'photo:album', 'video', 'video:album'] : + ['photo', 'photo:album', 'video', 'video:album', 'share', 'reply']; - return response()->json($res); - } + if ($profile->is_private) { + if (! Auth::check()) { + return response()->json([]); + } + $pid = Auth::user()->profile->id; + $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) { + $following = Follower::whereProfileId($pid)->pluck('following_id'); - public function remoteProfile(Request $request, $id) - { - return redirect('/i/web/profile/' . $id); - } + return $following->push($pid)->toArray(); + }); + $visibility = in_array($profile->id, $following) == true ? ['public', 'unlisted', 'private'] : []; + } else { + if (Auth::check()) { + $pid = Auth::user()->profile->id; + $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) { + $following = Follower::whereProfileId($pid)->pluck('following_id'); - public function remoteStatus(Request $request, $profileId, $statusId) - { - return redirect('/i/web/post/' . $statusId); - } + return $following->push($pid)->toArray(); + }); + $visibility = in_array($profile->id, $following) == true ? ['public', 'unlisted', 'private'] : ['public', 'unlisted']; + } else { + $visibility = ['public', 'unlisted']; + } + } - public function requestEmailVerification(Request $request) - { - $pid = $request->user()->profile_id; - $exists = Redis::sismember('email:manual', $pid); - return view('account.email.request_verification', compact('exists')); - } + $dir = $min_id ? '>' : '<'; + $id = $min_id ?? $max_id; + $timeline = Status::select( + 'id', + 'uri', + 'caption', + 'profile_id', + 'type', + 'in_reply_to_id', + 'reblog_of_id', + 'is_nsfw', + 'likes_count', + 'reblogs_count', + 'scope', + 'local', + 'created_at', + 'updated_at' + )->whereProfileId($profile->id) + ->whereIn('type', $scope) + ->where('id', $dir, $id) + ->whereIn('visibility', $visibility) + ->latest() + ->limit($limit) + ->get(); - public function requestEmailVerificationStore(Request $request) - { - $pid = $request->user()->profile_id; - Redis::sadd('email:manual', $pid); - return redirect('/i/verify-email')->with(['status' => 'Successfully sent manual verification request!']); - } + $resource = new Fractal\Resource\Collection($timeline, new StatusTransformer); + $res = $this->fractal->createData($resource)->toArray(); + + return response()->json($res); + } + + public function remoteProfile(Request $request, $id) + { + return redirect('/i/web/profile/'.$id); + } + + public function remoteStatus(Request $request, $profileId, $statusId) + { + return redirect('/i/web/post/'.$statusId); + } + + public function requestEmailVerification(Request $request) + { + $pid = $request->user()->profile_id; + $exists = Redis::sismember('email:manual', $pid); + + return view('account.email.request_verification', compact('exists')); + } + + public function requestEmailVerificationStore(Request $request) + { + $pid = $request->user()->profile_id; + Redis::sadd('email:manual', $pid); + + return redirect('/i/verify-email')->with(['status' => 'Successfully sent manual verification request!']); + } } diff --git a/app/Http/Controllers/MicroController.php b/app/Http/Controllers/MicroController.php index 420083f0f..62e0ae969 100644 --- a/app/Http/Controllers/MicroController.php +++ b/app/Http/Controllers/MicroController.php @@ -2,66 +2,65 @@ namespace App\Http\Controllers; +use App\Status; +use Auth; +use DB; use Illuminate\Http\Request; -use App\{ - Profile, - Status, -}; -use Auth, DB, Purify; use Illuminate\Validation\Rule; class MicroController extends Controller { - public function __construct() - { - $this->middleware('auth'); - } + public function __construct() + { + $this->middleware('auth'); + } - public function composeText(Request $request) - { - $this->validate($request, [ - 'type' => [ - 'required', - 'string', - Rule::in(['text']) - ], - 'title' => 'nullable|string|max:140', - 'content' => 'required|string|max:500', - 'visibility' => [ - 'required', - 'string', - Rule::in([ - 'public', - 'unlisted', - 'private', - 'draft' - ]) - ] - ]); - $profile = Auth::user()->profile; - $title = $request->input('title'); - $content = $request->input('content'); - $visibility = $request->input('visibility'); + public function composeText(Request $request) + { + $this->validate($request, [ + 'type' => [ + 'required', + 'string', + Rule::in(['text']), + ], + 'title' => 'nullable|string|max:140', + 'content' => 'required|string|max:500', + 'visibility' => [ + 'required', + 'string', + Rule::in([ + 'public', + 'unlisted', + 'private', + 'draft', + ]), + ], + ]); + $profile = Auth::user()->profile; + $title = $request->input('title'); + $content = $request->input('content'); + $visibility = $request->input('visibility'); - $status = DB::transaction(function() use($profile, $content, $visibility, $title) { - $status = new Status; - $status->type = 'text'; - $status->profile_id = $profile->id; - $status->caption = strip_tags($content); - $status->rendered = Purify::clean($content); - $status->is_nsfw = false; + $status = DB::transaction(function () use ($profile, $content, $visibility, $title) { + $status = new Status; + $status->type = 'text'; + $status->profile_id = $profile->id; + $status->caption = strip_tags($content); + $status->is_nsfw = false; - // TODO: remove deprecated visibility in favor of scope - $status->visibility = $visibility; - $status->scope = $visibility; - $status->entities = json_encode(['title'=>$title]); - $status->save(); - return $status; - }); + // TODO: remove deprecated visibility in favor of scope + $status->visibility = $visibility; + $status->scope = $visibility; + $status->entities = json_encode(['title' => $title]); + $status->save(); - $fractal = new \League\Fractal\Manager(); - $fractal->setSerializer(new \League\Fractal\Serializer\ArraySerializer()); - $s = new \League\Fractal\Resource\Item($status, new \App\Transformer\Api\StatusTransformer()); - return $fractal->createData($s)->toArray(); - } + return $status; + }); + + $fractal = new \League\Fractal\Manager; + $fractal->setSerializer(new \League\Fractal\Serializer\ArraySerializer); + $s = new \League\Fractal\Resource\Item($status, new \App\Transformer\Api\StatusTransformer); + + return $fractal->createData($s)->toArray(); + } } diff --git a/app/Http/Controllers/OAuth/OobAuthorizationController.php b/app/Http/Controllers/OAuth/OobAuthorizationController.php new file mode 100644 index 000000000..f69368b79 --- /dev/null +++ b/app/Http/Controllers/OAuth/OobAuthorizationController.php @@ -0,0 +1,99 @@ +assertValidAuthToken($request); + + $authRequest = $this->getAuthRequestFromSession($request); + $authRequest->setAuthorizationApproved(true); + + return $this->withErrorHandling(function () use ($authRequest) { + $response = $this->server->completeAuthorizationRequest($authRequest, new Psr7Response); + + if ($this->isOutOfBandRequest($authRequest)) { + $code = $this->extractAuthorizationCode($response); + return response()->json([ + 'code' => $code, + 'state' => $authRequest->getState() + ]); + } + + return $this->convertResponse($response); + }); + } + + /** + * Check if the request is an out-of-band OAuth request. + * + * @param \League\OAuth2\Server\RequestTypes\AuthorizationRequest $authRequest + * @return bool + */ + protected function isOutOfBandRequest($authRequest) + { + return $authRequest->getRedirectUri() === 'urn:ietf:wg:oauth:2.0:oob'; + } + + /** + * Extract the authorization code from the PSR-7 response. + * + * @param \Psr\Http\Message\ResponseInterface $response + * @return string + * @throws \League\OAuth2\Server\Exception\OAuthServerException + */ + protected function extractAuthorizationCode($response) + { + $location = $response->getHeader('Location')[0] ?? ''; + + if (empty($location)) { + throw OAuthServerException::serverError('Missing authorization code in response'); + } + + parse_str(parse_url($location, PHP_URL_QUERY), $params); + + if (!isset($params['code'])) { + throw OAuthServerException::serverError('Invalid authorization code format'); + } + + return $params['code']; + } + + /** + * Handle OAuth errors for both redirect and OOB flows. + * + * @param \Closure $callback + * @return \Illuminate\Http\Response + */ + protected function withErrorHandling($callback) + { + try { + return $callback(); + } catch (OAuthServerException $e) { + if ($this->isOutOfBandRequest($this->getAuthRequestFromSession(request()))) { + return response()->json([ + 'error' => $e->getErrorType(), + 'message' => $e->getMessage(), + 'hint' => $e->getHint() + ], $e->getHttpStatusCode()); + } + + return $this->convertResponse( + $e->generateHttpResponse(new Psr7Response) + ); + } + } +} diff --git a/app/Http/Controllers/PublicApiController.php b/app/Http/Controllers/PublicApiController.php index b0e2efc40..1c9781935 100644 --- a/app/Http/Controllers/PublicApiController.php +++ b/app/Http/Controllers/PublicApiController.php @@ -31,8 +31,8 @@ class PublicApiController extends Controller public function __construct() { - $this->fractal = new Fractal\Manager(); - $this->fractal->setSerializer(new ArraySerializer()); + $this->fractal = new Fractal\Manager; + $this->fractal->setSerializer(new ArraySerializer); } protected function getUserData($user) @@ -74,7 +74,7 @@ class PublicApiController extends Controller abort_if(! in_array($cached['visibility'], ['public', 'unlisted']), 403); $res = ['status' => $cached]; } else { - $item = new Fractal\Resource\Item($status, new StatusStatelessTransformer()); + $item = new Fractal\Resource\Item($status, new StatusStatelessTransformer); $res = [ 'status' => $this->fractal->createData($item)->toArray(), ]; @@ -141,7 +141,7 @@ class PublicApiController extends Controller $replies = $status->comments() ->whereNull('reblog_of_id') ->whereIn('scope', $scope) - ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') + ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') ->where('id', '>=', $request->min_id) ->orderBy('id', 'desc') ->paginate($limit); @@ -150,7 +150,7 @@ class PublicApiController extends Controller $replies = $status->comments() ->whereNull('reblog_of_id') ->whereIn('scope', $scope) - ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') + ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') ->where('id', '<=', $request->max_id) ->orderBy('id', 'desc') ->paginate($limit); @@ -159,12 +159,12 @@ class PublicApiController extends Controller $replies = Status::whereInReplyToId($status->id) ->whereNull('reblog_of_id') ->whereIn('scope', $scope) - ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'rendered', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') + ->select('id', 'caption', 'local', 'visibility', 'scope', 'is_nsfw', 'profile_id', 'in_reply_to_id', 'type', 'reply_count', 'created_at') ->orderBy('id', 'desc') ->paginate($limit); } - $resource = new Fractal\Resource\Collection($replies, new StatusStatelessTransformer(), 'data'); + $resource = new Fractal\Resource\Collection($replies, new StatusStatelessTransformer, 'data'); $resource->setPaginator(new IlluminatePaginatorAdapter($replies)); $res = $this->fractal->createData($resource)->toArray(); @@ -271,7 +271,6 @@ class PublicApiController extends Controller 'id', 'uri', 'caption', - 'rendered', 'profile_id', 'type', 'in_reply_to_id', @@ -405,7 +404,6 @@ class PublicApiController extends Controller 'id', 'uri', 'caption', - 'rendered', 'profile_id', 'type', 'in_reply_to_id', @@ -456,7 +454,6 @@ class PublicApiController extends Controller 'id', 'uri', 'caption', - 'rendered', 'profile_id', 'type', 'in_reply_to_id', diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index 9388d3abd..8e4296e5b 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -8,6 +8,7 @@ use App\Profile; use App\Services\WebfingerService; use App\Status; use App\Util\ActivityPub\Helpers; +use App\Util\Lexer\Autolink; use Auth; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; @@ -320,17 +321,21 @@ class SearchController extends Controller if (Status::whereUri($tag)->whereLocal(false)->exists()) { $item = Status::whereUri($tag)->first(); + if (! $item) { + return; + } $media = $item->firstMedia(); $url = null; if ($media) { $url = $media->remote_url; } + $content = $item->caption ? Autolink::create()->autolink($item->caption) : null; $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, + 'caption' => $content, 'thumb' => $url, 'timestamp' => $item->created_at->diffForHumans(), ]]; @@ -340,17 +345,21 @@ class SearchController extends Controller if (isset($remote['type']) && $remote['type'] == 'Note') { $item = Helpers::statusFetch($tag); + if (! $item) { + return; + } $media = $item->firstMedia(); $url = null; if ($media) { $url = $media->remote_url; } + $content = $item->caption ? Autolink::create()->autolink($item->caption) : null; $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, + 'caption' => $content, 'thumb' => $url, 'timestamp' => $item->created_at->diffForHumans(), ]]; diff --git a/app/Http/Controllers/Settings/ExportSettings.php b/app/Http/Controllers/Settings/ExportSettings.php index 91f8c9760..bbbc2e7f2 100644 --- a/app/Http/Controllers/Settings/ExportSettings.php +++ b/app/Http/Controllers/Settings/ExportSettings.php @@ -2,29 +2,27 @@ namespace App\Http\Controllers\Settings; -use App\AccountLog; -use App\Following; -use App\Report; use App\Status; -use App\UserFilter; -use Auth, Cookie, DB, Cache, Purify; -use Carbon\Carbon; -use Illuminate\Http\Request; -use App\Transformer\ActivityPub\{ - ProfileTransformer, - StatusTransformer -}; +use App\Transformer\ActivityPub\ProfileTransformer; use App\Transformer\Api\StatusTransformer as StatusApiTransformer; +use App\UserFilter; +use Auth; +use Cache; +use Illuminate\Http\Request; use League\Fractal; use League\Fractal\Serializer\ArraySerializer; -use League\Fractal\Pagination\IlluminatePaginatorAdapter; +use Storage; trait ExportSettings { - public function __construct() - { - $this->middleware('auth'); - } + private const CHUNK_SIZE = 1000; + + private const STORAGE_BASE = 'user_exports'; + + public function __construct() + { + $this->middleware('auth'); + } public function dataExport() { @@ -33,47 +31,146 @@ trait ExportSettings public function exportAccount() { - $data = Cache::remember('account:export:profile:actor:'.Auth::user()->profile->id, now()->addMinutes(60), function() { - $profile = Auth::user()->profile; - $fractal = new Fractal\Manager(); - $fractal->setSerializer(new ArraySerializer()); - $resource = new Fractal\Resource\Item($profile, new ProfileTransformer()); - return $fractal->createData($resource)->toArray(); - }); + $profile = Auth::user()->profile; + $fractal = new Fractal\Manager; + $fractal->setSerializer(new ArraySerializer); + $resource = new Fractal\Resource\Item($profile, new ProfileTransformer); - return response()->streamDownload(function () use ($data) { - echo json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); - }, 'account.json', [ - 'Content-Type' => 'application/json' - ]); + $data = $fractal->createData($resource)->toArray(); + + return response()->streamDownload(function () use ($data) { + echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + }, 'account.json', [ + 'Content-Type' => 'application/json', + ]); } public function exportFollowing() { - $data = Cache::remember('account:export:profile:following:'.Auth::user()->profile->id, now()->addMinutes(60), function() { - return Auth::user()->profile->following()->get()->map(function($i) { - return $i->url(); - }); - }); - return response()->streamDownload(function () use($data) { - echo $data; - }, 'following.json', [ - 'Content-Type' => 'application/json' - ]); + $profile = Auth::user()->profile; + $userId = Auth::id(); + + $userExportPath = 'user_exports/'.$userId; + $filename = 'pixelfed-following.json'; + $tempPath = $userExportPath.'/'.$filename; + + if (! Storage::exists($userExportPath)) { + Storage::makeDirectory($userExportPath); + } + + try { + Storage::put($tempPath, '['); + + $profile->following() + ->chunk(1000, function ($following) use ($tempPath) { + $urls = $following->map(function ($follow) { + return $follow->url(); + }); + + $json = json_encode($urls, + JSON_PRETTY_PRINT | + JSON_UNESCAPED_SLASHES | + JSON_UNESCAPED_UNICODE + ); + + $json = trim($json, '[]'); + if (Storage::size($tempPath) > 1) { + $json = ','.$json; + } + + Storage::append($tempPath, $json); + }); + + Storage::append($tempPath, ']'); + + return response()->stream( + function () use ($tempPath) { + $handle = fopen(Storage::path($tempPath), 'rb'); + while (! feof($handle)) { + echo fread($handle, 8192); + flush(); + } + fclose($handle); + + Storage::delete($tempPath); + }, + 200, + [ + 'Content-Type' => 'application/json', + 'Content-Disposition' => 'attachment; filename="pixelfed-following.json"', + ] + ); + + } catch (\Exception $e) { + if (Storage::exists($tempPath)) { + Storage::delete($tempPath); + } + throw $e; + } } public function exportFollowers() { - $data = Cache::remember('account:export:profile:followers:'.Auth::user()->profile->id, now()->addMinutes(60), function() { - return Auth::user()->profile->followers()->get()->map(function($i) { - return $i->url(); - }); - }); - return response()->streamDownload(function () use($data) { - echo $data; - }, 'followers.json', [ - 'Content-Type' => 'application/json' - ]); + $profile = Auth::user()->profile; + $userId = Auth::id(); + + $userExportPath = 'user_exports/'.$userId; + $filename = 'pixelfed-followers.json'; + $tempPath = $userExportPath.'/'.$filename; + + if (! Storage::exists($userExportPath)) { + Storage::makeDirectory($userExportPath); + } + + try { + Storage::put($tempPath, '['); + + $profile->followers() + ->chunk(1000, function ($followers) use ($tempPath) { + $urls = $followers->map(function ($follower) { + return $follower->url(); + }); + + $json = json_encode($urls, + JSON_PRETTY_PRINT | + JSON_UNESCAPED_SLASHES | + JSON_UNESCAPED_UNICODE + ); + + $json = trim($json, '[]'); + if (Storage::size($tempPath) > 1) { + $json = ','.$json; + } + + Storage::append($tempPath, $json); + }); + + Storage::append($tempPath, ']'); + + return response()->stream( + function () use ($tempPath) { + $handle = fopen(Storage::path($tempPath), 'rb'); + while (! feof($handle)) { + echo fread($handle, 8192); + flush(); + } + fclose($handle); + + Storage::delete($tempPath); + }, + 200, + [ + 'Content-Type' => 'application/json', + 'Content-Disposition' => 'attachment; filename="pixelfed-followers.json"', + ] + ); + + } catch (\Exception $e) { + if (Storage::exists($tempPath)) { + Storage::delete($tempPath); + } + throw $e; + } } public function exportMuteBlockList() @@ -82,63 +179,83 @@ trait ExportSettings $exists = UserFilter::select('id') ->whereUserId($profile->id) ->exists(); - if(!$exists) { + if (! $exists) { return redirect()->back(); } - $data = Cache::remember('account:export:profile:muteblocklist:'.Auth::user()->profile->id, now()->addMinutes(60), function() use($profile) { + $data = Cache::remember('account:export:profile:muteblocklist:'.Auth::user()->profile->id, now()->addMinutes(60), function () use ($profile) { return json_encode([ 'muted' => $profile->mutedProfileUrls(), - 'blocked' => $profile->blockedProfileUrls() + 'blocked' => $profile->blockedProfileUrls(), ], JSON_PRETTY_PRINT); }); - return response()->streamDownload(function () use($data) { + + return response()->streamDownload(function () use ($data) { echo $data; }, 'muted-and-blocked-accounts.json', [ - 'Content-Type' => 'application/json' - ]); + 'Content-Type' => 'application/json', + ]); } public function exportStatuses(Request $request) { - $this->validate($request, [ - 'type' => 'required|string|in:ap,api' - ]); - $limit = 500; + $profile = Auth::user()->profile; + $userId = Auth::id(); + $userExportPath = self::STORAGE_BASE.'/'.$userId; + $filename = 'pixelfed-statuses.json'; + $tempPath = $userExportPath.'/'.$filename; - $profile = Auth::user()->profile; - $type = 'ap'; + if (! Storage::exists($userExportPath)) { + Storage::makeDirectory($userExportPath); + } - $count = Status::select('id')->whereProfileId($profile->id)->count(); - if($count > $limit) { - // fire background job - return redirect('/settings/data-export')->with(['status' => 'You have more than '.$limit.' statuses, we do not support full account export yet.']); - } + Storage::put($tempPath, '['); + $fractal = new Fractal\Manager; + $fractal->setSerializer(new ArraySerializer); - $filename = 'outbox.json'; - if($type == 'ap') { - $data = Cache::remember('account:export:profile:statuses:ap:'.Auth::user()->profile->id, now()->addHours(1), function() { - $profile = Auth::user()->profile->statuses; - $fractal = new Fractal\Manager(); - $fractal->setSerializer(new ArraySerializer()); - $resource = new Fractal\Resource\Collection($profile, new StatusTransformer()); - return $fractal->createData($resource)->toArray(); - }); - } else { - $filename = 'api-statuses.json'; - $data = Cache::remember('account:export:profile:statuses:api:'.Auth::user()->profile->id, now()->addHours(1), function() { - $profile = Auth::user()->profile->statuses; - $fractal = new Fractal\Manager(); - $fractal->setSerializer(new ArraySerializer()); - $resource = new Fractal\Resource\Collection($profile, new StatusApiTransformer()); - return $fractal->createData($resource)->toArray(); - }); - } + try { + Status::whereProfileId($profile->id) + ->chunk(self::CHUNK_SIZE, function ($statuses) use ($fractal, $tempPath) { + $resource = new Fractal\Resource\Collection($statuses, new StatusApiTransformer); + $data = $fractal->createData($resource)->toArray(); - return response()->streamDownload(function () use ($data, $filename) { - echo json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); - }, $filename, [ - 'Content-Type' => 'application/json' - ]); + $json = json_encode($data, + JSON_PRETTY_PRINT | + JSON_UNESCAPED_SLASHES | + JSON_UNESCAPED_UNICODE + ); + + $json = trim($json, '[]'); + if (Storage::size($tempPath) > 1) { + $json = ','.$json; + } + + Storage::append($tempPath, $json); + }); + + Storage::append($tempPath, ']'); + + return response()->stream( + function () use ($tempPath) { + $handle = fopen(Storage::path($tempPath), 'rb'); + while (! feof($handle)) { + echo fread($handle, 8192); + flush(); + } + fclose($handle); + Storage::delete($tempPath); + }, + 200, + [ + 'Content-Type' => 'application/json', + 'Content-Disposition' => 'attachment; filename="pixelfed-statuses.json"', + ] + ); + + } catch (\Exception $e) { + if (Storage::exists($tempPath)) { + Storage::delete($tempPath); + } + throw $e; + } } - -} \ No newline at end of file +} diff --git a/app/Http/Controllers/StatusController.php b/app/Http/Controllers/StatusController.php index 14a5e5b7d..b523f8add 100644 --- a/app/Http/Controllers/StatusController.php +++ b/app/Http/Controllers/StatusController.php @@ -309,7 +309,7 @@ class StatusController extends Controller abort_if(! $statusAccount || isset($statusAccount['moved'], $statusAccount['moved']['id']), 422, 'Account moved'); $count = $status->reblogs_count; - + $defaultCaption = config_cache('database.default') === 'mysql' ? null : ""; $exists = Status::whereProfileId(Auth::user()->profile->id) ->whereReblogOfId($status->id) ->exists(); @@ -324,6 +324,8 @@ class StatusController extends Controller } } else { $share = new Status; + $share->caption = $defaultCaption; + $share->rendered = $defaultCaption; $share->profile_id = $profile->id; $share->reblog_of_id = $status->id; $share->in_reply_to_profile_id = $status->profile_id; diff --git a/app/Http/Controllers/Stories/StoryApiV1Controller.php b/app/Http/Controllers/Stories/StoryApiV1Controller.php index 5d0a15160..48599fc3a 100644 --- a/app/Http/Controllers/Stories/StoryApiV1Controller.php +++ b/app/Http/Controllers/Stories/StoryApiV1Controller.php @@ -281,7 +281,7 @@ class StoryApiV1Controller extends Controller $photo = $request->file('file'); $path = $this->storeMedia($photo, $user); - $story = new Story(); + $story = new Story; $story->duration = $request->input('duration', 3); $story->profile_id = $user->profile_id; $story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' : 'photo'; @@ -418,7 +418,6 @@ class StoryApiV1Controller extends Controller $status->type = 'story:reply'; $status->profile_id = $pid; $status->caption = $text; - $status->rendered = $text; $status->scope = 'direct'; $status->visibility = 'direct'; $status->in_reply_to_profile_id = $story->profile_id; diff --git a/app/Http/Controllers/StoryComposeController.php b/app/Http/Controllers/StoryComposeController.php index c8b0599a6..e02e2d219 100644 --- a/app/Http/Controllers/StoryComposeController.php +++ b/app/Http/Controllers/StoryComposeController.php @@ -54,7 +54,7 @@ class StoryComposeController extends Controller $photo = $request->file('file'); $path = $this->storePhoto($photo, $user); - $story = new Story(); + $story = new Story; $story->duration = 3; $story->profile_id = $user->profile_id; $story->type = Str::endsWith($photo->getMimeType(), 'mp4') ? 'video' : 'photo'; @@ -403,7 +403,6 @@ class StoryComposeController extends Controller $status->profile_id = $pid; $status->type = 'story:reaction'; $status->caption = $text; - $status->rendered = $text; $status->scope = 'direct'; $status->visibility = 'direct'; $status->in_reply_to_profile_id = $story->profile_id; @@ -477,7 +476,6 @@ class StoryComposeController extends Controller $status->type = 'story:reply'; $status->profile_id = $pid; $status->caption = $text; - $status->rendered = $text; $status->scope = 'direct'; $status->visibility = 'direct'; $status->in_reply_to_profile_id = $story->profile_id; diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index f44fd7ac4..145c629a3 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -12,6 +12,7 @@ class VerifyCsrfToken extends Middleware * @var array */ protected $except = [ - '/api/v1/*' + '/api/v1/*', + 'oauth/token' ]; } diff --git a/app/Http/Resources/StatusStateless.php b/app/Http/Resources/StatusStateless.php index df451cc53..0a7bbe8d4 100644 --- a/app/Http/Resources/StatusStateless.php +++ b/app/Http/Resources/StatusStateless.php @@ -2,18 +2,17 @@ namespace App\Http\Resources; -use Illuminate\Http\Resources\Json\JsonResource; -use Cache; +use App\Models\CustomEmoji; use App\Services\AccountService; use App\Services\HashidService; use App\Services\LikeService; use App\Services\MediaService; use App\Services\MediaTagService; -use App\Services\StatusHashtagService; -use App\Services\StatusLabelService; -use App\Services\StatusMentionService; use App\Services\PollService; -use App\Models\CustomEmoji; +use App\Services\StatusHashtagService; +use App\Services\StatusMentionService; +use App\Util\Lexer\Autolink; +use Illuminate\Http\Resources\Json\JsonResource; class StatusStateless extends JsonResource { @@ -28,49 +27,50 @@ class StatusStateless extends JsonResource $status = $this; $taggedPeople = MediaTagService::get($status->id); $poll = $status->type === 'poll' ? PollService::get($status->id) : null; + $autoLink = $status->caption ? Autolink::create()->autolink($status->caption) : null; return [ - '_v' => 1, - 'id' => (string) $status->id, + '_v' => 1, + 'id' => (string) $status->id, //'gid' => $status->group_id ? (string) $status->group_id : null, - 'shortcode' => HashidService::encode($status->id), - 'uri' => $status->url(), - 'url' => $status->url(), - 'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null, - 'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null, - 'reblog' => null, - 'content' => $status->rendered ?? $status->caption, - 'content_text' => $status->caption, - 'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)), - 'emojis' => CustomEmoji::scan($status->caption), - 'reblogs_count' => $status->reblogs_count ?? 0, - 'favourites_count' => $status->likes_count ?? 0, - 'reblogged' => null, - 'favourited' => null, - 'muted' => null, - 'sensitive' => (bool) $status->is_nsfw, - 'spoiler_text' => $status->cw_summary ?? '', - 'visibility' => $status->scope ?? $status->visibility, - 'application' => [ - 'name' => 'web', - 'website' => null - ], - 'language' => null, - 'mentions' => StatusMentionService::get($status->id), - 'pf_type' => $status->type ?? $status->setType(), - 'reply_count' => (int) $status->reply_count, - 'comments_disabled' => (bool) $status->comments_disabled, - 'thread' => false, - 'replies' => [], - 'parent' => [], - 'place' => $status->place, - 'local' => (bool) $status->local, - 'taggedPeople' => $taggedPeople, - 'liked_by' => LikeService::likedBy($status), - 'media_attachments' => MediaService::get($status->id), - 'account' => AccountService::get($status->profile_id, true), - 'tags' => StatusHashtagService::statusTags($status->id), - 'poll' => $poll + 'shortcode' => HashidService::encode($status->id), + 'uri' => $status->url(), + 'url' => $status->url(), + 'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null, + 'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null, + 'reblog' => null, + 'content' => $autoLink, + 'content_text' => $status->caption, + 'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)), + 'emojis' => CustomEmoji::scan($status->caption), + 'reblogs_count' => $status->reblogs_count ?? 0, + 'favourites_count' => $status->likes_count ?? 0, + 'reblogged' => null, + 'favourited' => null, + 'muted' => null, + 'sensitive' => (bool) $status->is_nsfw, + 'spoiler_text' => $status->cw_summary ?? '', + 'visibility' => $status->scope ?? $status->visibility, + 'application' => [ + 'name' => 'web', + 'website' => null, + ], + 'language' => null, + 'mentions' => StatusMentionService::get($status->id), + 'pf_type' => $status->type ?? $status->setType(), + 'reply_count' => (int) $status->reply_count, + 'comments_disabled' => (bool) $status->comments_disabled, + 'thread' => false, + 'replies' => [], + 'parent' => [], + 'place' => $status->place, + 'local' => (bool) $status->local, + 'taggedPeople' => $taggedPeople, + 'liked_by' => LikeService::likedBy($status), + 'media_attachments' => MediaService::get($status->id), + 'account' => AccountService::get($status->profile_id, true), + 'tags' => StatusHashtagService::statusTags($status->id), + 'poll' => $poll, ]; } } diff --git a/app/Jobs/GroupPipeline/NewStatusPipeline.php b/app/Jobs/GroupPipeline/NewStatusPipeline.php index 4d8eeca5c..d791d81a4 100644 --- a/app/Jobs/GroupPipeline/NewStatusPipeline.php +++ b/app/Jobs/GroupPipeline/NewStatusPipeline.php @@ -2,129 +2,122 @@ namespace App\Jobs\GroupPipeline; -use App\Notification; use App\Hashtag; use App\Mention; -use App\Profile; -use App\Status; -use App\StatusHashtag; -use App\Models\GroupPostHashtag; use App\Models\GroupPost; -use Cache; +use App\Models\GroupPostHashtag; +use App\Profile; +use App\Services\StatusService; +use App\Status; +use App\Util\Lexer\Autolink; +use App\Util\Lexer\Extractor; use DB; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Redis; -use App\Services\MediaStorageService; -use App\Services\NotificationService; -use App\Services\StatusService; -use App\Util\Lexer\Autolink; -use App\Util\Lexer\Extractor; class NewStatusPipeline implements ShouldQueue { - use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - protected $status; - protected $gp; - protected $tags; - protected $mentions; + protected $status; - public function __construct(Status $status, GroupPost $gp) - { - $this->status = $status; - $this->gp = $gp; - } + protected $gp; - public function handle() - { - $status = $this->status; + protected $tags; - $autolink = Autolink::create() - ->setAutolinkActiveUsersOnly(true) - ->setBaseHashPath("/groups/{$status->group_id}/topics/") - ->setBaseUserPath("/groups/{$status->group_id}/username/") - ->autolink($status->caption); + protected $mentions; + + public function __construct(Status $status, GroupPost $gp) + { + $this->status = $status; + $this->gp = $gp; + } + + public function handle() + { + $status = $this->status; + + $autolink = Autolink::create() + ->setAutolinkActiveUsersOnly(true) + ->setBaseHashPath("/groups/{$status->group_id}/topics/") + ->setBaseUserPath("/groups/{$status->group_id}/username/") + ->autolink($status->caption); $entities = Extractor::create()->extract($status->caption); + $status->entities = null; + $status->save(); - $autolink = str_replace('/discover/tags/', '/groups/' . $status->group_id . '/topics/', $autolink); + $this->tags = array_unique($entities['hashtags']); + $this->mentions = array_unique($entities['mentions']); - $status->rendered = nl2br($autolink); - $status->entities = null; - $status->save(); + if (count($this->tags)) { + $this->storeHashtags(); + } - $this->tags = array_unique($entities['hashtags']); - $this->mentions = array_unique($entities['mentions']); + if (count($this->mentions)) { + $this->storeMentions($this->mentions); + } + } - if(count($this->tags)) { - $this->storeHashtags(); - } + protected function storeHashtags() + { + $tags = $this->tags; + $status = $this->status; + $gp = $this->gp; - if(count($this->mentions)) { - $this->storeMentions($this->mentions); - } - } + foreach ($tags as $tag) { + if (mb_strlen($tag) > 124) { + continue; + } - protected function storeHashtags() - { - $tags = $this->tags; - $status = $this->status; - $gp = $this->gp; + DB::transaction(function () use ($status, $tag, $gp) { + $slug = str_slug($tag, '-', false); + $hashtag = Hashtag::firstOrCreate( + ['name' => $tag, 'slug' => $slug] + ); + GroupPostHashtag::firstOrCreate( + [ + 'group_id' => $status->group_id, + 'group_post_id' => $gp->id, + 'status_id' => $status->id, + 'hashtag_id' => $hashtag->id, + 'profile_id' => $status->profile_id, + ] + ); - foreach ($tags as $tag) { - if(mb_strlen($tag) > 124) { - continue; - } + }); + } - DB::transaction(function () use ($status, $tag, $gp) { - $slug = str_slug($tag, '-', false); - $hashtag = Hashtag::firstOrCreate( - ['name' => $tag, 'slug' => $slug] - ); - GroupPostHashtag::firstOrCreate( - [ - 'group_id' => $status->group_id, - 'group_post_id' => $gp->id, - 'status_id' => $status->id, - 'hashtag_id' => $hashtag->id, - 'profile_id' => $status->profile_id, - ] - ); + if (count($this->mentions)) { + $this->storeMentions(); + } + StatusService::del($status->id); + } - }); - } + protected function storeMentions() + { + $mentions = $this->mentions; + $status = $this->status; - if(count($this->mentions)) { - $this->storeMentions(); - } - StatusService::del($status->id); - } + foreach ($mentions as $mention) { + $mentioned = Profile::whereUsername($mention)->first(); - protected function storeMentions() - { - $mentions = $this->mentions; - $status = $this->status; + if (empty($mentioned) || ! isset($mentioned->id)) { + continue; + } - foreach ($mentions as $mention) { - $mentioned = Profile::whereUsername($mention)->first(); + DB::transaction(function () use ($status, $mentioned) { + $m = new Mention; + $m->status_id = $status->id; + $m->profile_id = $mentioned->id; + $m->save(); - if (empty($mentioned) || !isset($mentioned->id)) { - continue; - } - - DB::transaction(function () use ($status, $mentioned) { - $m = new Mention(); - $m->status_id = $status->id; - $m->profile_id = $mentioned->id; - $m->save(); - - MentionPipeline::dispatch($status, $m); - }); - } - StatusService::del($status->id); - } + MentionPipeline::dispatch($status, $m); + }); + } + StatusService::del($status->id); + } } diff --git a/app/Jobs/StatusPipeline/StatusEntityLexer.php b/app/Jobs/StatusPipeline/StatusEntityLexer.php index 4d19c7d8a..8fe767417 100644 --- a/app/Jobs/StatusPipeline/StatusEntityLexer.php +++ b/app/Jobs/StatusPipeline/StatusEntityLexer.php @@ -91,11 +91,6 @@ class StatusEntityLexer implements ShouldQueue public function storeEntities() { $this->storeHashtags(); - DB::transaction(function () { - $status = $this->status; - $status->rendered = nl2br($this->autolink); - $status->save(); - }); } public function storeHashtags() @@ -146,7 +141,7 @@ class StatusEntityLexer implements ShouldQueue } DB::transaction(function () use ($status, $mentioned) { - $m = new Mention(); + $m = new Mention; $m->status_id = $status->id; $m->profile_id = $mentioned->id; $m->save(); diff --git a/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php b/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php index 7ef7a3366..b216c0531 100644 --- a/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php +++ b/app/Jobs/StatusPipeline/StatusRemoteUpdatePipeline.php @@ -120,8 +120,7 @@ class StatusRemoteUpdatePipeline implements ShouldQueue protected function updateImmediateAttributes($status, $activity) { if (isset($activity['content'])) { - $status->caption = strip_tags($activity['content']); - $status->rendered = Purify::clean($activity['content']); + $status->caption = strip_tags(Purify::clean($activity['content'])); } if (isset($activity['sensitive'])) { diff --git a/app/Jobs/StatusPipeline/StatusTagsPipeline.php b/app/Jobs/StatusPipeline/StatusTagsPipeline.php index 003196e0d..895c3593d 100644 --- a/app/Jobs/StatusPipeline/StatusTagsPipeline.php +++ b/app/Jobs/StatusPipeline/StatusTagsPipeline.php @@ -2,27 +2,28 @@ namespace App\Jobs\StatusPipeline; +use App\Hashtag; +use App\Jobs\MentionPipeline\MentionPipeline; +use App\Mention; +use App\Services\AccountService; +use App\Services\CustomEmojiService; +use App\Services\StatusService; +use App\Services\TrendingHashtagService; +use App\StatusHashtag; +use App\Util\ActivityPub\Helpers; use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use App\Services\AccountService; -use App\Services\CustomEmojiService; -use App\Services\StatusService; -use App\Jobs\MentionPipeline\MentionPipeline; -use App\Mention; -use App\Hashtag; -use App\StatusHashtag; -use App\Services\TrendingHashtagService; -use App\Util\ActivityPub\Helpers; +use Illuminate\Support\Facades\DB; class StatusTagsPipeline implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $activity; + protected $status; /** @@ -46,92 +47,126 @@ class StatusTagsPipeline implements ShouldQueue $res = $this->activity; $status = $this->status; - if(isset($res['tag']['type'], $res['tag']['name'])) { + if (isset($res['tag']['type'], $res['tag']['name'])) { $res['tag'] = [$res['tag']]; } $tags = collect($res['tag']); // Emoji - $tags->filter(function($tag) { + $tags->filter(function ($tag) { return $tag && isset($tag['id'], $tag['icon'], $tag['name'], $tag['type']) && $tag['type'] == 'Emoji'; }) - ->map(function($tag) { - CustomEmojiService::import($tag['id'], $this->status->id); - }); + ->map(function ($tag) { + CustomEmojiService::import($tag['id'], $this->status->id); + }); // Hashtags - $tags->filter(function($tag) { + $tags->filter(function ($tag) { return $tag && $tag['type'] == 'Hashtag' && isset($tag['href'], $tag['name']); }) - ->map(function($tag) use($status) { - $name = substr($tag['name'], 0, 1) == '#' ? - substr($tag['name'], 1) : $tag['name']; + ->map(function ($tag) use ($status) { + $name = substr($tag['name'], 0, 1) == '#' ? + substr($tag['name'], 1) : $tag['name']; - $banned = TrendingHashtagService::getBannedHashtagNames(); + $banned = TrendingHashtagService::getBannedHashtagNames(); - if(count($banned)) { - if(in_array(strtolower($name), array_map('strtolower', $banned))) { - return; + if (count($banned)) { + if (in_array(strtolower($name), array_map('strtolower', $banned))) { + return; + } } - } - if(config('database.default') === 'pgsql') { - $hashtag = Hashtag::where('name', 'ilike', $name) - ->orWhere('slug', 'ilike', str_slug($name, '-', false)) - ->first(); + if (config('database.default') === 'pgsql') { + $hashtag = DB::transaction(function () use ($name) { + $baseSlug = str_slug($name, '-', false); + $slug = $baseSlug; + $counter = 1; - if(!$hashtag) { - $hashtag = Hashtag::updateOrCreate([ - 'slug' => str_slug($name, '-', false), - 'name' => $name - ]); + $existing = Hashtag::where('name', $name) + ->lockForUpdate() + ->first(); + + if ($existing) { + if ($existing->slug !== $slug) { + while (Hashtag::where('slug', $slug) + ->where('name', '!=', $name) + ->exists()) { + $slug = $baseSlug.'-'.$counter++; + } + $existing->slug = $slug; + $existing->save(); + } + + return $existing; + } + + while (Hashtag::where('slug', $slug)->exists()) { + $slug = $baseSlug.'-'.$counter++; + } + + return Hashtag::create([ + 'name' => $name, + 'slug' => $slug, + ]); + }); + } else { + $hashtag = DB::transaction(function () use ($name) { + $baseSlug = str_slug($name, '-', false); + $slug = $baseSlug; + $counter = 1; + + while (Hashtag::where('slug', $slug) + ->where('name', '!=', $name) + ->exists()) { + $slug = $baseSlug.'-'.$counter++; + } + + return Hashtag::updateOrCreate( + ['name' => $name], + ['slug' => $slug] + ); + }); } - } else { - $hashtag = Hashtag::updateOrCreate([ - 'slug' => str_slug($name, '-', false), - 'name' => $name + + StatusHashtag::firstOrCreate([ + 'status_id' => $status->id, + 'hashtag_id' => $hashtag->id, + 'profile_id' => $status->profile_id, + 'status_visibility' => $status->scope, ]); - } - - StatusHashtag::firstOrCreate([ - 'status_id' => $status->id, - 'hashtag_id' => $hashtag->id, - 'profile_id' => $status->profile_id, - 'status_visibility' => $status->scope - ]); - }); + }); // Mentions - $tags->filter(function($tag) { + $tags->filter(function ($tag) { return $tag && $tag['type'] == 'Mention' && isset($tag['href']) && substr($tag['href'], 0, 8) === 'https://'; }) - ->map(function($tag) use($status) { - if(Helpers::validateLocalUrl($tag['href'])) { - $parts = explode('/', $tag['href']); - if(!$parts) { - return; + ->map(function ($tag) use ($status) { + if (Helpers::validateLocalUrl($tag['href'])) { + $parts = explode('/', $tag['href']); + if (! $parts) { + return; + } + $pid = AccountService::usernameToId(end($parts)); + if (! $pid) { + return; + } + } else { + $acct = Helpers::profileFetch($tag['href']); + if (! $acct) { + return; + } + $pid = $acct->id; } - $pid = AccountService::usernameToId(end($parts)); - if(!$pid) { - return; - } - } else { - $acct = Helpers::profileFetch($tag['href']); - if(!$acct) { - return; - } - $pid = $acct->id; - } - $mention = new Mention; - $mention->status_id = $status->id; - $mention->profile_id = $pid; - $mention->save(); - MentionPipeline::dispatch($status, $mention); - }); + $mention = new Mention; + $mention->status_id = $status->id; + $mention->profile_id = $pid; + $mention->save(); + MentionPipeline::dispatch($status, $mention); + }); StatusService::refresh($status->id); } diff --git a/app/Media.php b/app/Media.php index 30a1b33bd..9ecc7b17e 100644 --- a/app/Media.php +++ b/app/Media.php @@ -22,6 +22,7 @@ class Media extends Model protected $casts = [ 'srcset' => 'array', 'deleted_at' => 'datetime', + 'skip_optimize' => 'boolean' ]; public function status() diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index b080b3b2f..a8abc99c7 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -2,81 +2,99 @@ namespace App\Providers; -use App\Observers\{ - AvatarObserver, - FollowerObserver, - HashtagFollowObserver, - LikeObserver, - NotificationObserver, - ModLogObserver, - ProfileObserver, - StatusHashtagObserver, - StatusObserver, - UserObserver, - UserFilterObserver, -}; -use App\{ - Avatar, - Follower, - HashtagFollow, - Like, - Notification, - ModLog, - Profile, - StatusHashtag, - Status, - User, - UserFilter -}; -use Auth, Horizon, URL; -use Illuminate\Support\Facades\Blade; -use Illuminate\Support\Facades\Schema; -use Illuminate\Support\ServiceProvider; -use Illuminate\Pagination\Paginator; -use Illuminate\Support\Facades\Validator; +use App\Avatar; +use App\Follower; +use App\HashtagFollow; +use App\Like; +use App\ModLog; +use App\Notification; +use App\Observers\AvatarObserver; +use App\Observers\FollowerObserver; +use App\Observers\HashtagFollowObserver; +use App\Observers\LikeObserver; +use App\Observers\ModLogObserver; +use App\Observers\NotificationObserver; +use App\Observers\ProfileObserver; +use App\Observers\StatusHashtagObserver; +use App\Observers\StatusObserver; +use App\Observers\UserFilterObserver; +use App\Observers\UserObserver; +use App\Profile; +use App\Services\AccountService; +use App\Status; +use App\StatusHashtag; +use App\User; +use App\UserFilter; +use Auth; +use Horizon; use Illuminate\Database\Eloquent\Model; +use Illuminate\Pagination\Paginator; +use Illuminate\Support\Facades\Gate; +use Illuminate\Support\Facades\Schema; +use Illuminate\Support\Facades\Validator; +use Illuminate\Support\ServiceProvider; +use Laravel\Pulse\Facades\Pulse; +use URL; class AppServiceProvider extends ServiceProvider { - /** - * Bootstrap any application services. - * - * @return void - */ - public function boot() - { - if(config('instance.force_https_urls', true)) { - URL::forceScheme('https'); - } + /** + * Bootstrap any application services. + * + * @return void + */ + public function boot() + { + if (config('instance.force_https_urls', true)) { + URL::forceScheme('https'); + } - Schema::defaultStringLength(191); - Paginator::useBootstrap(); - Avatar::observe(AvatarObserver::class); - Follower::observe(FollowerObserver::class); - HashtagFollow::observe(HashtagFollowObserver::class); - Like::observe(LikeObserver::class); - Notification::observe(NotificationObserver::class); - ModLog::observe(ModLogObserver::class); - Profile::observe(ProfileObserver::class); - StatusHashtag::observe(StatusHashtagObserver::class); - User::observe(UserObserver::class); + Schema::defaultStringLength(191); + Paginator::useBootstrap(); + Avatar::observe(AvatarObserver::class); + Follower::observe(FollowerObserver::class); + HashtagFollow::observe(HashtagFollowObserver::class); + Like::observe(LikeObserver::class); + Notification::observe(NotificationObserver::class); + ModLog::observe(ModLogObserver::class); + Profile::observe(ProfileObserver::class); + StatusHashtag::observe(StatusHashtagObserver::class); + User::observe(UserObserver::class); Status::observe(StatusObserver::class); - UserFilter::observe(UserFilterObserver::class); - Horizon::auth(function ($request) { - return Auth::check() && $request->user()->is_admin; - }); - Validator::includeUnvalidatedArrayKeys(); + UserFilter::observe(UserFilterObserver::class); + Horizon::auth(function ($request) { + return Auth::check() && $request->user()->is_admin; + }); + Validator::includeUnvalidatedArrayKeys(); - // Model::preventLazyLoading(true); - } + Gate::define('viewPulse', function (User $user) { + return $user->is_admin === 1; + }); - /** - * Register any application services. - * - * @return void - */ - public function register() - { - // - } + Pulse::user(function ($user) { + $acct = AccountService::get($user->profile_id, true); + + return $acct ? [ + 'name' => $acct['username'], + 'extra' => $user->email, + 'avatar' => $acct['avatar'], + ] : [ + 'name' => $user->username, + 'extra' => 'DELETED', + 'avatar' => '/storage/avatars/default.jpg', + ]; + }); + + // Model::preventLazyLoading(true); + } + + /** + * Register any application services. + * + * @return void + */ + public function register() + { + // + } } diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php index dfd4518c3..8bedbfd53 100644 --- a/app/Providers/AuthServiceProvider.php +++ b/app/Providers/AuthServiceProvider.php @@ -25,6 +25,7 @@ class AuthServiceProvider extends ServiceProvider public function boot() { if(config('pixelfed.oauth_enabled') == true) { + Passport::ignoreRoutes(); Passport::tokensExpireIn(now()->addDays(config('instance.oauth.token_expiration', 356))); Passport::refreshTokensExpireIn(now()->addDays(config('instance.oauth.refresh_expiration', 400))); Passport::enableImplicitGrant(); diff --git a/app/Services/AutolinkService.php b/app/Services/AutolinkService.php index f0f3278ff..c494ab151 100644 --- a/app/Services/AutolinkService.php +++ b/app/Services/AutolinkService.php @@ -2,53 +2,25 @@ namespace App\Services; -use Cache; use App\Profile; -use Illuminate\Support\Str; -use Illuminate\Support\Facades\Http; -use App\Util\Webfinger\WebfingerUrl; +use Cache; +use Purify; class AutolinkService { - const CACHE_KEY = 'pf:services:autolink:'; + const CACHE_KEY = 'pf:services:autolink:mue:'; - public static function mentionedUsernameExists($username) - { - $key = 'pf:services:autolink:userexists:' . hash('sha256', $username); + public static function mentionedUsernameExists($username) + { + if (str_starts_with($username, '@')) { + if (substr_count($username, '@') === 1) { + $username = substr($username, 1); + } + } + $name = Purify::clean(strtolower($username)); - return Cache::remember($key, 3600, function() use($username) { - $remote = Str::of($username)->contains('@'); - $profile = Profile::whereUsername($username)->first(); - if($profile) { - if($profile->domain != null) { - $instance = InstanceService::getByDomain($profile->domain); - if($instance && $instance->banned == true) { - return false; - } - } - return true; - } else { - if($remote) { - $parts = explode('@', $username); - $domain = last($parts); - $instance = InstanceService::getByDomain($domain); - - if($instance) { - if($instance->banned == true) { - return false; - } else { - $wf = WebfingerUrl::generateWebfingerUrl($username); - $res = Http::head($wf); - return $res->ok(); - } - } else { - $wf = WebfingerUrl::generateWebfingerUrl($username); - $res = Http::head($wf); - return $res->ok(); - } - } - } - return false; - }); - } + return Cache::remember(self::CACHE_KEY.base64_encode($name), 7200, function () use ($name) { + return Profile::where('username', $name)->exists(); + }); + } } diff --git a/app/Services/ImportService.php b/app/Services/ImportService.php index 4f85dabbe..b0191ba7b 100644 --- a/app/Services/ImportService.php +++ b/app/Services/ImportService.php @@ -14,7 +14,7 @@ class ImportService if($userId > 999999) { return; } - if($year < 9 || $year > 23) { + if($year < 9 || $year > (int) now()->addYear()->format('y')) { return; } if($month < 1 || $month > 12) { diff --git a/app/Services/Status/UpdateStatusService.php b/app/Services/Status/UpdateStatusService.php index d0a69c451..7ef4d440c 100644 --- a/app/Services/Status/UpdateStatusService.php +++ b/app/Services/Status/UpdateStatusService.php @@ -3,135 +3,133 @@ namespace App\Services\Status; use App\Media; -use App\ModLog; -use App\Status; use App\Models\StatusEdit; -use Purify; -use App\Util\Lexer\Autolink; +use App\ModLog; use App\Services\MediaService; use App\Services\MediaStorageService; use App\Services\StatusService; +use App\Status; +use Purify; class UpdateStatusService { - public static function call(Status $status, $attributes) - { - self::createPreviousEdit($status); - self::updateMediaAttachements($status, $attributes); - self::handleImmediateAttributes($status, $attributes); - self::createEdit($status, $attributes); + public static function call(Status $status, $attributes) + { + self::createPreviousEdit($status); + self::updateMediaAttachements($status, $attributes); + self::handleImmediateAttributes($status, $attributes); + self::createEdit($status, $attributes); - return StatusService::get($status->id); - } + return StatusService::get($status->id); + } - public static function updateMediaAttachements(Status $status, $attributes) - { - $count = $status->media()->count(); - if($count === 0 || $count === 1) { - return; - } + public static function updateMediaAttachements(Status $status, $attributes) + { + $count = $status->media()->count(); + if ($count === 0 || $count === 1) { + return; + } - $oids = $status->media()->orderBy('order')->pluck('id')->map(function($m) { return (string) $m; }); - $nids = collect($attributes['media_ids']); + $oids = $status->media()->orderBy('order')->pluck('id')->map(function ($m) { + return (string) $m; + }); + $nids = collect($attributes['media_ids']); - if($oids->toArray() === $nids->toArray()) { - return; - } + if ($oids->toArray() === $nids->toArray()) { + return; + } - foreach($oids->diff($nids)->values()->toArray() as $mid) { - $media = Media::find($mid); - if(!$media) { - continue; - } - $media->status_id = null; - $media->save(); - MediaStorageService::delete($media, true); - } + foreach ($oids->diff($nids)->values()->toArray() as $mid) { + $media = Media::find($mid); + if (! $media) { + continue; + } + $media->status_id = null; + $media->save(); + MediaStorageService::delete($media, true); + } - $nids->each(function($nid, $idx) { - $media = Media::find($nid); - if(!$media) { - return; - } - $media->order = $idx; - $media->save(); - }); - MediaService::del($status->id); - } + $nids->each(function ($nid, $idx) { + $media = Media::find($nid); + if (! $media) { + return; + } + $media->order = $idx; + $media->save(); + }); + MediaService::del($status->id); + } - public static function handleImmediateAttributes(Status $status, $attributes) - { - if(isset($attributes['status'])) { - $cleaned = Purify::clean($attributes['status']); - $status->caption = $cleaned; - $status->rendered = nl2br(Autolink::create()->autolink($cleaned)); - } else { - $status->caption = null; - $status->rendered = null; - } - if(isset($attributes['sensitive'])) { - if($status->is_nsfw != (bool) $attributes['sensitive'] && - (bool) $attributes['sensitive'] == false) - { - $exists = ModLog::whereObjectType('App\Status::class') - ->whereObjectId($status->id) - ->whereAction('admin.status.moderate') - ->exists(); - if(!$exists) { - $status->is_nsfw = (bool) $attributes['sensitive']; - } - } else { - $status->is_nsfw = (bool) $attributes['sensitive']; - } - } - if(isset($attributes['spoiler_text'])) { - $status->cw_summary = Purify::clean($attributes['spoiler_text']); - } else { - $status->cw_summary = null; - } - if(isset($attributes['location'])) { - if (isset($attributes['location']['id'])) { - $status->place_id = $attributes['location']['id']; - } else { - $status->place_id = null; - } - } - if($status->cw_summary && !$status->is_nsfw) { - $status->cw_summary = null; - } - $status->edited_at = now(); - $status->save(); - StatusService::del($status->id); - } + public static function handleImmediateAttributes(Status $status, $attributes) + { + if (isset($attributes['status'])) { + $cleaned = Purify::clean($attributes['status']); + $status->caption = $cleaned; + } else { + $status->caption = null; + } + if (isset($attributes['sensitive'])) { + if ($status->is_nsfw != (bool) $attributes['sensitive'] && + (bool) $attributes['sensitive'] == false) { + $exists = ModLog::whereObjectType('App\Status::class') + ->whereObjectId($status->id) + ->whereAction('admin.status.moderate') + ->exists(); + if (! $exists) { + $status->is_nsfw = (bool) $attributes['sensitive']; + } + } else { + $status->is_nsfw = (bool) $attributes['sensitive']; + } + } + if (isset($attributes['spoiler_text'])) { + $status->cw_summary = Purify::clean($attributes['spoiler_text']); + } else { + $status->cw_summary = null; + } + if (isset($attributes['location'])) { + if (isset($attributes['location']['id'])) { + $status->place_id = $attributes['location']['id']; + } else { + $status->place_id = null; + } + } + if ($status->cw_summary && ! $status->is_nsfw) { + $status->cw_summary = null; + } + $status->edited_at = now(); + $status->save(); + StatusService::del($status->id); + } - public static function createPreviousEdit(Status $status) - { - if(!$status->edits()->count()) { - StatusEdit::create([ - 'status_id' => $status->id, - 'profile_id' => $status->profile_id, - 'caption' => $status->caption, - 'spoiler_text' => $status->cw_summary, - 'is_nsfw' => $status->is_nsfw, - 'ordered_media_attachment_ids' => $status->media()->orderBy('order')->pluck('id')->toArray(), - 'created_at' => $status->created_at - ]); - } - } + public static function createPreviousEdit(Status $status) + { + if (! $status->edits()->count()) { + StatusEdit::create([ + 'status_id' => $status->id, + 'profile_id' => $status->profile_id, + 'caption' => $status->caption, + 'spoiler_text' => $status->cw_summary, + 'is_nsfw' => $status->is_nsfw, + 'ordered_media_attachment_ids' => $status->media()->orderBy('order')->pluck('id')->toArray(), + 'created_at' => $status->created_at, + ]); + } + } - public static function createEdit(Status $status, $attributes) - { - $cleaned = isset($attributes['status']) ? Purify::clean($attributes['status']) : null; - $spoiler_text = isset($attributes['spoiler_text']) ? Purify::clean($attributes['spoiler_text']) : null; - $sensitive = isset($attributes['sensitive']) ? $attributes['sensitive'] : null; - $mids = $status->media()->count() ? $status->media()->orderBy('order')->pluck('id')->toArray() : null; - StatusEdit::create([ - 'status_id' => $status->id, - 'profile_id' => $status->profile_id, - 'caption' => $cleaned, - 'spoiler_text' => $spoiler_text, - 'is_nsfw' => $sensitive, - 'ordered_media_attachment_ids' => $mids - ]); - } + public static function createEdit(Status $status, $attributes) + { + $cleaned = isset($attributes['status']) ? Purify::clean($attributes['status']) : null; + $spoiler_text = isset($attributes['spoiler_text']) ? Purify::clean($attributes['spoiler_text']) : null; + $sensitive = isset($attributes['sensitive']) ? $attributes['sensitive'] : null; + $mids = $status->media()->count() ? $status->media()->orderBy('order')->pluck('id')->toArray() : null; + StatusEdit::create([ + 'status_id' => $status->id, + 'profile_id' => $status->profile_id, + 'caption' => $cleaned, + 'spoiler_text' => $spoiler_text, + 'is_nsfw' => $sensitive, + 'ordered_media_attachment_ids' => $mids, + ]); + } } diff --git a/app/Services/StatusService.php b/app/Services/StatusService.php index 621574f09..de2f4d112 100644 --- a/app/Services/StatusService.php +++ b/app/Services/StatusService.php @@ -10,7 +10,7 @@ use League\Fractal\Serializer\ArraySerializer; class StatusService { - const CACHE_KEY = 'pf:services:status:'; + const CACHE_KEY = 'pf:services:status:v1.1:'; public static function key($id, $publicOnly = true) { diff --git a/app/Status.php b/app/Status.php index d665464ae..8b69c199c 100644 --- a/app/Status.php +++ b/app/Status.php @@ -308,46 +308,6 @@ class Status extends Model return $this->comments()->orderBy('created_at', 'desc')->take(3); } - public function toActivityPubObject() - { - if($this->local == false) { - return; - } - $profile = $this->profile; - $to = $this->scopeToAudience('to'); - $cc = $this->scopeToAudience('cc'); - return [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $this->permalink(), - 'type' => 'Create', - 'actor' => $profile->permalink(), - 'published' => str_replace('+00:00', 'Z', $this->created_at->format(DATE_RFC3339_EXTENDED)), - 'to' => $to, - 'cc' => $cc, - 'object' => [ - 'id' => $this->url(), - 'type' => 'Note', - 'summary' => null, - 'inReplyTo' => null, - 'published' => str_replace('+00:00', 'Z', $this->created_at->format(DATE_RFC3339_EXTENDED)), - 'url' => $this->url(), - 'attributedTo' => $this->profile->url(), - 'to' => $to, - 'cc' => $cc, - 'sensitive' => (bool) $this->is_nsfw, - 'content' => $this->rendered, - 'attachment' => $this->media->map(function($media) { - return [ - 'type' => 'Document', - 'mediaType' => $media->mime, - 'url' => $media->url(), - 'name' => null - ]; - })->toArray() - ] - ]; - } - public function scopeToAudience($audience) { if(!in_array($audience, ['to', 'cc']) || $this->local == false) { diff --git a/app/Transformer/ActivityPub/StatusTransformer.php b/app/Transformer/ActivityPub/StatusTransformer.php index f5d5ea531..86beb321c 100644 --- a/app/Transformer/ActivityPub/StatusTransformer.php +++ b/app/Transformer/ActivityPub/StatusTransformer.php @@ -2,59 +2,62 @@ namespace App\Transformer\ActivityPub; -use App\Status; -use League\Fractal; use App\Services\MediaService; +use App\Services\StatusService; +use App\Status; +use App\Util\Lexer\Autolink; +use League\Fractal; class StatusTransformer extends Fractal\TransformerAbstract { public function transform(Status $status) { + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : ''; + + $inReplyTo = null; + + if ($status->in_reply_to_id) { + $reply = StatusService::get($status->in_reply_to_id, true); + if ($reply && isset($reply['url'])) { + $inReplyTo = $reply['url']; + } + } + return [ - '@context' => [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', - [ - 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', - 'featured' => [ - 'https://pixelfed.org/ns#featured' => ['@type' => '@id'], - ], + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', + [ + 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', + 'featured' => [ + 'https://pixelfed.org/ns#featured' => ['@type' => '@id'], + ], + ], ], - ], - 'id' => $status->url(), - - // TODO: handle other types - 'type' => 'Note', - - // XXX: CW Title - 'summary' => null, - 'content' => $status->rendered ?? $status->caption, - 'inReplyTo' => null, - - // TODO: fix date format - 'published' => $status->created_at->toAtomString(), - 'url' => $status->url(), - 'attributedTo' => $status->profile->permalink(), - 'to' => [ - // TODO: handle proper scope - 'https://www.w3.org/ns/activitystreams#Public', - ], - 'cc' => [ - // TODO: add cc's - $status->profile->permalink('/followers'), - ], - 'sensitive' => (bool) $status->is_nsfw, - 'atomUri' => $status->url(), - 'inReplyToAtomUri' => null, - 'attachment' => MediaService::activitypub($status->id), - 'tag' => [], - 'location' => $status->place_id ? [ - 'type' => 'Place', - 'name' => $status->place->name, - 'longitude' => $status->place->long, - 'latitude' => $status->place->lat, - 'country' => $status->place->country + 'id' => $status->url(), + 'type' => 'Note', + 'summary' => null, + 'content' => $content, + 'inReplyTo' => $inReplyTo, + 'published' => $status->created_at->toAtomString(), + 'url' => $status->url(), + 'attributedTo' => $status->profile->permalink(), + 'to' => [ + 'https://www.w3.org/ns/activitystreams#Public', + ], + 'cc' => [ + $status->profile->permalink('/followers'), + ], + 'sensitive' => (bool) $status->is_nsfw, + 'attachment' => MediaService::activitypub($status->id), + 'tag' => [], + 'location' => $status->place_id ? [ + 'type' => 'Place', + 'name' => $status->place->name, + 'longitude' => $status->place->long, + 'latitude' => $status->place->lat, + 'country' => $status->place->country, ] : null, - ]; + ]; } } diff --git a/app/Transformer/ActivityPub/Verb/CreateNote.php b/app/Transformer/ActivityPub/Verb/CreateNote.php index 55fdfa8f4..cf2f0fb51 100644 --- a/app/Transformer/ActivityPub/Verb/CreateNote.php +++ b/app/Transformer/ActivityPub/Verb/CreateNote.php @@ -2,140 +2,144 @@ namespace App\Transformer\ActivityPub\Verb; -use App\Status; -use League\Fractal; use App\Models\CustomEmoji; +use App\Status; +use App\Util\Lexer\Autolink; use Illuminate\Support\Str; +use League\Fractal; class CreateNote extends Fractal\TransformerAbstract { - public function transform(Status $status) - { - $mentions = $status->mentions->map(function ($mention) { - $webfinger = $mention->emailUrl(); - $name = Str::startsWith($webfinger, '@') ? - $webfinger : - '@' . $webfinger; - return [ - 'type' => 'Mention', - 'href' => $mention->permalink(), - 'name' => $name - ]; - })->toArray(); + public function transform(Status $status) + { + $mentions = $status->mentions->map(function ($mention) { + $webfinger = $mention->emailUrl(); + $name = Str::startsWith($webfinger, '@') ? + $webfinger : + '@'.$webfinger; - if($status->in_reply_to_id != null) { - $parent = $status->parent()->profile; - if($parent) { - $webfinger = $parent->emailUrl(); - $name = Str::startsWith($webfinger, '@') ? - $webfinger : - '@' . $webfinger; - $reply = [ - 'type' => 'Mention', - 'href' => $parent->permalink(), - 'name' => $name - ]; - $mentions = array_merge($reply, $mentions); - } - } + return [ + 'type' => 'Mention', + 'href' => $mention->permalink(), + 'name' => $name, + ]; + })->toArray(); - $hashtags = $status->hashtags->map(function ($hashtag) { - return [ - 'type' => 'Hashtag', - 'href' => $hashtag->url(), - 'name' => "#{$hashtag->name}", - ]; - })->toArray(); + if ($status->in_reply_to_id != null) { + $parent = $status->parent()->profile; + if ($parent) { + $webfinger = $parent->emailUrl(); + $name = Str::startsWith($webfinger, '@') ? + $webfinger : + '@'.$webfinger; + $reply = [ + 'type' => 'Mention', + 'href' => $parent->permalink(), + 'name' => $name, + ]; + $mentions = array_merge($reply, $mentions); + } + } - $emojis = CustomEmoji::scan($status->caption, true) ?? []; - $emoji = array_merge($emojis, $mentions); - $tags = array_merge($emoji, $hashtags); + $hashtags = $status->hashtags->map(function ($hashtag) { + return [ + 'type' => 'Hashtag', + 'href' => $hashtag->url(), + 'name' => "#{$hashtag->name}", + ]; + })->toArray(); - return [ - '@context' => [ - 'https://w3id.org/security/v1', - 'https://www.w3.org/ns/activitystreams', - [ - 'Hashtag' => 'as:Hashtag', - 'sensitive' => 'as:sensitive', - 'schema' => 'http://schema.org/', - 'pixelfed' => 'http://pixelfed.org/ns#', - 'commentsEnabled' => [ - '@id' => 'pixelfed:commentsEnabled', - '@type' => 'schema:Boolean' - ], - 'capabilities' => [ - '@id' => 'pixelfed:capabilities', - '@container' => '@set' - ], - 'announce' => [ - '@id' => 'pixelfed:canAnnounce', - '@type' => '@id' - ], - 'like' => [ - '@id' => 'pixelfed:canLike', - '@type' => '@id' - ], - 'reply' => [ - '@id' => 'pixelfed:canReply', - '@type' => '@id' - ], - 'toot' => 'http://joinmastodon.org/ns#', - 'Emoji' => 'toot:Emoji', - 'blurhash' => 'toot:blurhash', - ] - ], - 'id' => $status->permalink(), - 'type' => 'Create', - 'actor' => $status->profile->permalink(), - 'published' => $status->created_at->toAtomString(), - 'to' => $status->scopeToAudience('to'), - 'cc' => $status->scopeToAudience('cc'), - 'object' => [ - 'id' => $status->url(), - 'type' => 'Note', - 'summary' => $status->is_nsfw ? $status->cw_summary : null, - 'content' => $status->rendered ?? $status->caption, - 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, - 'published' => $status->created_at->toAtomString(), - 'url' => $status->url(), - 'attributedTo' => $status->profile->permalink(), - 'to' => $status->scopeToAudience('to'), - 'cc' => $status->scopeToAudience('cc'), - 'sensitive' => (bool) $status->is_nsfw, - 'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) { - $res = [ - 'type' => $media->activityVerb(), - 'mediaType' => $media->mime, - 'url' => $media->url(), - 'name' => $media->caption, - ]; - if($media->blurhash) { - $res['blurhash'] = $media->blurhash; - } - if($media->width) { - $res['width'] = $media->width; - } - if($media->height) { - $res['height'] = $media->height; - } - return $res; - })->toArray(), - 'tag' => $tags, - 'commentsEnabled' => (bool) !$status->comments_disabled, - 'capabilities' => [ - 'announce' => 'https://www.w3.org/ns/activitystreams#Public', - 'like' => 'https://www.w3.org/ns/activitystreams#Public', - 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public' - ], - 'location' => $status->place_id ? [ - 'type' => 'Place', - 'name' => $status->place->name, - 'longitude' => $status->place->long, - 'latitude' => $status->place->lat, - 'country' => $status->place->country - ] : null, - ] - ]; - } + $emojis = CustomEmoji::scan($status->caption, true) ?? []; + $emoji = array_merge($emojis, $mentions); + $tags = array_merge($emoji, $hashtags); + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : ""; + + return [ + '@context' => [ + 'https://w3id.org/security/v1', + 'https://www.w3.org/ns/activitystreams', + [ + 'Hashtag' => 'as:Hashtag', + 'sensitive' => 'as:sensitive', + 'schema' => 'http://schema.org/', + 'pixelfed' => 'http://pixelfed.org/ns#', + 'commentsEnabled' => [ + '@id' => 'pixelfed:commentsEnabled', + '@type' => 'schema:Boolean', + ], + 'capabilities' => [ + '@id' => 'pixelfed:capabilities', + '@container' => '@set', + ], + 'announce' => [ + '@id' => 'pixelfed:canAnnounce', + '@type' => '@id', + ], + 'like' => [ + '@id' => 'pixelfed:canLike', + '@type' => '@id', + ], + 'reply' => [ + '@id' => 'pixelfed:canReply', + '@type' => '@id', + ], + 'toot' => 'http://joinmastodon.org/ns#', + 'Emoji' => 'toot:Emoji', + 'blurhash' => 'toot:blurhash', + ], + ], + 'id' => $status->permalink(), + 'type' => 'Create', + 'actor' => $status->profile->permalink(), + 'published' => $status->created_at->toAtomString(), + 'to' => $status->scopeToAudience('to'), + 'cc' => $status->scopeToAudience('cc'), + 'object' => [ + 'id' => $status->url(), + 'type' => 'Note', + 'summary' => $status->is_nsfw ? $status->cw_summary : null, + 'content' => $content, + 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, + 'published' => $status->created_at->toAtomString(), + 'url' => $status->url(), + 'attributedTo' => $status->profile->permalink(), + 'to' => $status->scopeToAudience('to'), + 'cc' => $status->scopeToAudience('cc'), + 'sensitive' => (bool) $status->is_nsfw, + 'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) { + $res = [ + 'type' => $media->activityVerb(), + 'mediaType' => $media->mime, + 'url' => $media->url(), + 'name' => $media->caption, + ]; + if ($media->blurhash) { + $res['blurhash'] = $media->blurhash; + } + if ($media->width) { + $res['width'] = $media->width; + } + if ($media->height) { + $res['height'] = $media->height; + } + + return $res; + })->toArray(), + 'tag' => $tags, + 'commentsEnabled' => (bool) ! $status->comments_disabled, + 'capabilities' => [ + 'announce' => 'https://www.w3.org/ns/activitystreams#Public', + 'like' => 'https://www.w3.org/ns/activitystreams#Public', + 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public', + ], + 'location' => $status->place_id ? [ + 'type' => 'Place', + 'name' => $status->place->name, + 'longitude' => $status->place->long, + 'latitude' => $status->place->lat, + 'country' => $status->place->country, + ] : null, + ], + ]; + } } diff --git a/app/Transformer/ActivityPub/Verb/Note.php b/app/Transformer/ActivityPub/Verb/Note.php index 1350641d4..bc34761cd 100644 --- a/app/Transformer/ActivityPub/Verb/Note.php +++ b/app/Transformer/ActivityPub/Verb/Note.php @@ -2,133 +2,137 @@ namespace App\Transformer\ActivityPub\Verb; -use App\Status; -use League\Fractal; use App\Models\CustomEmoji; +use App\Status; +use App\Util\Lexer\Autolink; use Illuminate\Support\Str; +use League\Fractal; class Note extends Fractal\TransformerAbstract { - public function transform(Status $status) - { + public function transform(Status $status) + { - $mentions = $status->mentions->map(function ($mention) { - $webfinger = $mention->emailUrl(); - $name = Str::startsWith($webfinger, '@') ? - $webfinger : - '@' . $webfinger; - return [ - 'type' => 'Mention', - 'href' => $mention->permalink(), - 'name' => $name - ]; - })->toArray(); + $mentions = $status->mentions->map(function ($mention) { + $webfinger = $mention->emailUrl(); + $name = Str::startsWith($webfinger, '@') ? + $webfinger : + '@'.$webfinger; - if($status->in_reply_to_id != null) { - $parent = $status->parent()->profile; - if($parent) { - $webfinger = $parent->emailUrl(); - $name = Str::startsWith($webfinger, '@') ? - $webfinger : - '@' . $webfinger; - $reply = [ - 'type' => 'Mention', - 'href' => $parent->permalink(), - 'name' => $name - ]; - array_push($mentions, $reply); - } - } - - $hashtags = $status->hashtags->map(function ($hashtag) { - return [ - 'type' => 'Hashtag', - 'href' => $hashtag->url(), - 'name' => "#{$hashtag->name}", - ]; - })->toArray(); + return [ + 'type' => 'Mention', + 'href' => $mention->permalink(), + 'name' => $name, + ]; + })->toArray(); - $emojis = CustomEmoji::scan($status->caption, true) ?? []; - $emoji = array_merge($emojis, $mentions); - $tags = array_merge($emoji, $hashtags); + if ($status->in_reply_to_id != null) { + $parent = $status->parent()->profile; + if ($parent) { + $webfinger = $parent->emailUrl(); + $name = Str::startsWith($webfinger, '@') ? + $webfinger : + '@'.$webfinger; + $reply = [ + 'type' => 'Mention', + 'href' => $parent->permalink(), + 'name' => $name, + ]; + array_push($mentions, $reply); + } + } - return [ - '@context' => [ - 'https://w3id.org/security/v1', - 'https://www.w3.org/ns/activitystreams', - [ - 'Hashtag' => 'as:Hashtag', - 'sensitive' => 'as:sensitive', - 'schema' => 'http://schema.org/', - 'pixelfed' => 'http://pixelfed.org/ns#', - 'commentsEnabled' => [ - '@id' => 'pixelfed:commentsEnabled', - '@type' => 'schema:Boolean' - ], - 'capabilities' => [ - '@id' => 'pixelfed:capabilities', - '@container' => '@set' - ], - 'announce' => [ - '@id' => 'pixelfed:canAnnounce', - '@type' => '@id' - ], - 'like' => [ - '@id' => 'pixelfed:canLike', - '@type' => '@id' - ], - 'reply' => [ - '@id' => 'pixelfed:canReply', - '@type' => '@id' - ], - 'toot' => 'http://joinmastodon.org/ns#', - 'Emoji' => 'toot:Emoji', - 'blurhash' => 'toot:blurhash', - ] - ], - 'id' => $status->url(), - 'type' => 'Note', - 'summary' => $status->is_nsfw ? $status->cw_summary : null, - 'content' => $status->rendered ?? $status->caption, - 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, - 'published' => $status->created_at->toAtomString(), - 'url' => $status->url(), - 'attributedTo' => $status->profile->permalink(), - 'to' => $status->scopeToAudience('to'), - 'cc' => $status->scopeToAudience('cc'), - 'sensitive' => (bool) $status->is_nsfw, - 'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) { - $res = [ - 'type' => $media->activityVerb(), - 'mediaType' => $media->mime, - 'url' => $media->url(), - 'name' => $media->caption, - ]; - if($media->blurhash) { - $res['blurhash'] = $media->blurhash; - } - if($media->width) { - $res['width'] = $media->width; - } - if($media->height) { - $res['height'] = $media->height; - } - return $res; - })->toArray(), - 'tag' => $tags, - 'commentsEnabled' => (bool) !$status->comments_disabled, - 'capabilities' => [ - 'announce' => 'https://www.w3.org/ns/activitystreams#Public', - 'like' => 'https://www.w3.org/ns/activitystreams#Public', - 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public' - ], - 'location' => $status->place_id ? [ - 'type' => 'Place', - 'name' => $status->place->name, - 'longitude' => $status->place->long, - 'latitude' => $status->place->lat, - 'country' => $status->place->country - ] : null, - ]; - } + $hashtags = $status->hashtags->map(function ($hashtag) { + return [ + 'type' => 'Hashtag', + 'href' => $hashtag->url(), + 'name' => "#{$hashtag->name}", + ]; + })->toArray(); + + $emojis = CustomEmoji::scan($status->caption, true) ?? []; + $emoji = array_merge($emojis, $mentions); + $tags = array_merge($emoji, $hashtags); + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : ""; + + return [ + '@context' => [ + 'https://w3id.org/security/v1', + 'https://www.w3.org/ns/activitystreams', + [ + 'Hashtag' => 'as:Hashtag', + 'sensitive' => 'as:sensitive', + 'schema' => 'http://schema.org/', + 'pixelfed' => 'http://pixelfed.org/ns#', + 'commentsEnabled' => [ + '@id' => 'pixelfed:commentsEnabled', + '@type' => 'schema:Boolean', + ], + 'capabilities' => [ + '@id' => 'pixelfed:capabilities', + '@container' => '@set', + ], + 'announce' => [ + '@id' => 'pixelfed:canAnnounce', + '@type' => '@id', + ], + 'like' => [ + '@id' => 'pixelfed:canLike', + '@type' => '@id', + ], + 'reply' => [ + '@id' => 'pixelfed:canReply', + '@type' => '@id', + ], + 'toot' => 'http://joinmastodon.org/ns#', + 'Emoji' => 'toot:Emoji', + 'blurhash' => 'toot:blurhash', + ], + ], + 'id' => $status->url(), + 'type' => 'Note', + 'summary' => $status->is_nsfw ? $status->cw_summary : null, + 'content' => $content, + 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, + 'published' => $status->created_at->toAtomString(), + 'url' => $status->url(), + 'attributedTo' => $status->profile->permalink(), + 'to' => $status->scopeToAudience('to'), + 'cc' => $status->scopeToAudience('cc'), + 'sensitive' => (bool) $status->is_nsfw, + 'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) { + $res = [ + 'type' => $media->activityVerb(), + 'mediaType' => $media->mime, + 'url' => $media->url(), + 'name' => $media->caption, + ]; + if ($media->blurhash) { + $res['blurhash'] = $media->blurhash; + } + if ($media->width) { + $res['width'] = $media->width; + } + if ($media->height) { + $res['height'] = $media->height; + } + + return $res; + })->toArray(), + 'tag' => $tags, + 'commentsEnabled' => (bool) ! $status->comments_disabled, + 'capabilities' => [ + 'announce' => 'https://www.w3.org/ns/activitystreams#Public', + 'like' => 'https://www.w3.org/ns/activitystreams#Public', + 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public', + ], + 'location' => $status->place_id ? [ + 'type' => 'Place', + 'name' => $status->place->name, + 'longitude' => $status->place->long, + 'latitude' => $status->place->lat, + 'country' => $status->place->country, + ] : null, + ]; + } } diff --git a/app/Transformer/ActivityPub/Verb/Question.php b/app/Transformer/ActivityPub/Verb/Question.php index fb9313fb1..3d53ebcb5 100644 --- a/app/Transformer/ActivityPub/Verb/Question.php +++ b/app/Transformer/ActivityPub/Verb/Question.php @@ -3,104 +3,107 @@ namespace App\Transformer\ActivityPub\Verb; use App\Status; -use League\Fractal; +use App\Util\Lexer\Autolink; use Illuminate\Support\Str; +use League\Fractal; class Question extends Fractal\TransformerAbstract { - public function transform(Status $status) - { - $mentions = $status->mentions->map(function ($mention) { - $webfinger = $mention->emailUrl(); - $name = Str::startsWith($webfinger, '@') ? - $webfinger : - '@' . $webfinger; - return [ - 'type' => 'Mention', - 'href' => $mention->permalink(), - 'name' => $name - ]; - })->toArray(); + public function transform(Status $status) + { + $mentions = $status->mentions->map(function ($mention) { + $webfinger = $mention->emailUrl(); + $name = Str::startsWith($webfinger, '@') ? + $webfinger : + '@'.$webfinger; - $hashtags = $status->hashtags->map(function ($hashtag) { - return [ - 'type' => 'Hashtag', - 'href' => $hashtag->url(), - 'name' => "#{$hashtag->name}", - ]; - })->toArray(); - $tags = array_merge($mentions, $hashtags); + return [ + 'type' => 'Mention', + 'href' => $mention->permalink(), + 'name' => $name, + ]; + })->toArray(); - return [ - '@context' => [ - 'https://w3id.org/security/v1', - 'https://www.w3.org/ns/activitystreams', - [ - 'Hashtag' => 'as:Hashtag', - 'sensitive' => 'as:sensitive', - 'schema' => 'http://schema.org/', - 'pixelfed' => 'http://pixelfed.org/ns#', - 'commentsEnabled' => [ - '@id' => 'pixelfed:commentsEnabled', - '@type' => 'schema:Boolean' - ], - 'capabilities' => [ - '@id' => 'pixelfed:capabilities', - '@container' => '@set' - ], - 'announce' => [ - '@id' => 'pixelfed:canAnnounce', - '@type' => '@id' - ], - 'like' => [ - '@id' => 'pixelfed:canLike', - '@type' => '@id' - ], - 'reply' => [ - '@id' => 'pixelfed:canReply', - '@type' => '@id' - ], - 'toot' => 'http://joinmastodon.org/ns#', - 'Emoji' => 'toot:Emoji' - ] - ], - 'id' => $status->url(), - 'type' => 'Question', - 'summary' => null, - 'content' => $status->rendered ?? $status->caption, - 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, - 'published' => $status->created_at->toAtomString(), - 'url' => $status->url(), - 'attributedTo' => $status->profile->permalink(), - 'to' => $status->scopeToAudience('to'), - 'cc' => $status->scopeToAudience('cc'), - 'sensitive' => (bool) $status->is_nsfw, - 'attachment' => [], - 'tag' => $tags, - 'commentsEnabled' => (bool) !$status->comments_disabled, - 'capabilities' => [ - 'announce' => 'https://www.w3.org/ns/activitystreams#Public', - 'like' => 'https://www.w3.org/ns/activitystreams#Public', - 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public' - ], - 'location' => $status->place_id ? [ - 'type' => 'Place', - 'name' => $status->place->name, - 'longitude' => $status->place->long, - 'latitude' => $status->place->lat, - 'country' => $status->place->country - ] : null, - 'endTime' => $status->poll->expires_at->toAtomString(), - 'oneOf' => collect($status->poll->poll_options)->map(function($option, $index) use($status) { - return [ - 'type' => 'Note', - 'name' => $option, - 'replies' => [ - 'type' => 'Collection', - 'totalItems' => $status->poll->cached_tallies[$index] - ] - ]; - }) - ]; - } + $hashtags = $status->hashtags->map(function ($hashtag) { + return [ + 'type' => 'Hashtag', + 'href' => $hashtag->url(), + 'name' => "#{$hashtag->name}", + ]; + })->toArray(); + $tags = array_merge($mentions, $hashtags); + $content = $status->caption ? Autolink::create()->autolink($status->caption) : null; + + return [ + '@context' => [ + 'https://w3id.org/security/v1', + 'https://www.w3.org/ns/activitystreams', + [ + 'Hashtag' => 'as:Hashtag', + 'sensitive' => 'as:sensitive', + 'schema' => 'http://schema.org/', + 'pixelfed' => 'http://pixelfed.org/ns#', + 'commentsEnabled' => [ + '@id' => 'pixelfed:commentsEnabled', + '@type' => 'schema:Boolean', + ], + 'capabilities' => [ + '@id' => 'pixelfed:capabilities', + '@container' => '@set', + ], + 'announce' => [ + '@id' => 'pixelfed:canAnnounce', + '@type' => '@id', + ], + 'like' => [ + '@id' => 'pixelfed:canLike', + '@type' => '@id', + ], + 'reply' => [ + '@id' => 'pixelfed:canReply', + '@type' => '@id', + ], + 'toot' => 'http://joinmastodon.org/ns#', + 'Emoji' => 'toot:Emoji', + ], + ], + 'id' => $status->url(), + 'type' => 'Question', + 'summary' => null, + 'content' => $content, + 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, + 'published' => $status->created_at->toAtomString(), + 'url' => $status->url(), + 'attributedTo' => $status->profile->permalink(), + 'to' => $status->scopeToAudience('to'), + 'cc' => $status->scopeToAudience('cc'), + 'sensitive' => (bool) $status->is_nsfw, + 'attachment' => [], + 'tag' => $tags, + 'commentsEnabled' => (bool) ! $status->comments_disabled, + 'capabilities' => [ + 'announce' => 'https://www.w3.org/ns/activitystreams#Public', + 'like' => 'https://www.w3.org/ns/activitystreams#Public', + 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public', + ], + 'location' => $status->place_id ? [ + 'type' => 'Place', + 'name' => $status->place->name, + 'longitude' => $status->place->long, + 'latitude' => $status->place->lat, + 'country' => $status->place->country, + ] : null, + 'endTime' => $status->poll->expires_at->toAtomString(), + 'oneOf' => collect($status->poll->poll_options)->map(function ($option, $index) use ($status) { + return [ + 'type' => 'Note', + 'name' => $option, + 'replies' => [ + 'type' => 'Collection', + 'totalItems' => $status->poll->cached_tallies[$index], + ], + ]; + }), + ]; + } } diff --git a/app/Transformer/ActivityPub/Verb/UpdateNote.php b/app/Transformer/ActivityPub/Verb/UpdateNote.php index bdbb20c45..4199f7230 100644 --- a/app/Transformer/ActivityPub/Verb/UpdateNote.php +++ b/app/Transformer/ActivityPub/Verb/UpdateNote.php @@ -2,132 +2,135 @@ namespace App\Transformer\ActivityPub\Verb; -use App\Status; -use League\Fractal; use App\Models\CustomEmoji; +use App\Status; +use App\Util\Lexer\Autolink; use Illuminate\Support\Str; +use League\Fractal; class UpdateNote extends Fractal\TransformerAbstract { - public function transform(Status $status) - { - $mentions = $status->mentions->map(function ($mention) { - $webfinger = $mention->emailUrl(); - $name = Str::startsWith($webfinger, '@') ? - $webfinger : - '@' . $webfinger; - return [ - 'type' => 'Mention', - 'href' => $mention->permalink(), - 'name' => $name - ]; - })->toArray(); + public function transform(Status $status) + { + $mentions = $status->mentions->map(function ($mention) { + $webfinger = $mention->emailUrl(); + $name = Str::startsWith($webfinger, '@') ? + $webfinger : + '@'.$webfinger; - if($status->in_reply_to_id != null) { - $parent = $status->parent()->profile; - if($parent) { - $webfinger = $parent->emailUrl(); - $name = Str::startsWith($webfinger, '@') ? - $webfinger : - '@' . $webfinger; - $reply = [ - 'type' => 'Mention', - 'href' => $parent->permalink(), - 'name' => $name - ]; - $mentions = array_merge($reply, $mentions); - } - } + return [ + 'type' => 'Mention', + 'href' => $mention->permalink(), + 'name' => $name, + ]; + })->toArray(); - $hashtags = $status->hashtags->map(function ($hashtag) { - return [ - 'type' => 'Hashtag', - 'href' => $hashtag->url(), - 'name' => "#{$hashtag->name}", - ]; - })->toArray(); + if ($status->in_reply_to_id != null) { + $parent = $status->parent()->profile; + if ($parent) { + $webfinger = $parent->emailUrl(); + $name = Str::startsWith($webfinger, '@') ? + $webfinger : + '@'.$webfinger; + $reply = [ + 'type' => 'Mention', + 'href' => $parent->permalink(), + 'name' => $name, + ]; + $mentions = array_merge($reply, $mentions); + } + } - $emojis = CustomEmoji::scan($status->caption, true) ?? []; - $emoji = array_merge($emojis, $mentions); - $tags = array_merge($emoji, $hashtags); + $hashtags = $status->hashtags->map(function ($hashtag) { + return [ + 'type' => 'Hashtag', + 'href' => $hashtag->url(), + 'name' => "#{$hashtag->name}", + ]; + })->toArray(); - $latestEdit = $status->edits()->latest()->first(); + $emojis = CustomEmoji::scan($status->caption, true) ?? []; + $emoji = array_merge($emojis, $mentions); + $tags = array_merge($emoji, $hashtags); - return [ - '@context' => [ - 'https://w3id.org/security/v1', - 'https://www.w3.org/ns/activitystreams', - [ - 'Hashtag' => 'as:Hashtag', - 'sensitive' => 'as:sensitive', - 'schema' => 'http://schema.org/', - 'pixelfed' => 'http://pixelfed.org/ns#', - 'commentsEnabled' => [ - '@id' => 'pixelfed:commentsEnabled', - '@type' => 'schema:Boolean' - ], - 'capabilities' => [ - '@id' => 'pixelfed:capabilities', - '@container' => '@set' - ], - 'announce' => [ - '@id' => 'pixelfed:canAnnounce', - '@type' => '@id' - ], - 'like' => [ - '@id' => 'pixelfed:canLike', - '@type' => '@id' - ], - 'reply' => [ - '@id' => 'pixelfed:canReply', - '@type' => '@id' - ], - 'toot' => 'http://joinmastodon.org/ns#', - 'Emoji' => 'toot:Emoji' - ] - ], - 'id' => $status->permalink('#updates/' . $latestEdit->id), - 'type' => 'Update', - 'actor' => $status->profile->permalink(), - 'published' => $latestEdit->created_at->toAtomString(), - 'to' => $status->scopeToAudience('to'), - 'cc' => $status->scopeToAudience('cc'), - 'object' => [ - 'id' => $status->url(), - 'type' => 'Note', - 'summary' => $status->is_nsfw ? $status->cw_summary : null, - 'content' => $status->rendered ?? $status->caption, - 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, - 'published' => $status->created_at->toAtomString(), - 'url' => $status->url(), - 'attributedTo' => $status->profile->permalink(), - 'to' => $status->scopeToAudience('to'), - 'cc' => $status->scopeToAudience('cc'), - 'sensitive' => (bool) $status->is_nsfw, - 'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) { - return [ - 'type' => $media->activityVerb(), - 'mediaType' => $media->mime, - 'url' => $media->url(), - 'name' => $media->caption, - ]; - })->toArray(), - 'tag' => $tags, - 'commentsEnabled' => (bool) !$status->comments_disabled, - 'updated' => $latestEdit->created_at->toAtomString(), - 'capabilities' => [ - 'announce' => 'https://www.w3.org/ns/activitystreams#Public', - 'like' => 'https://www.w3.org/ns/activitystreams#Public', - 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public' - ], - 'location' => $status->place_id ? [ - 'type' => 'Place', - 'name' => $status->place->name, - 'longitude' => $status->place->long, - 'latitude' => $status->place->lat, - 'country' => $status->place->country - ] : null, - ] - ]; - } + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : ""; + $latestEdit = $status->edits()->latest()->first(); + + return [ + '@context' => [ + 'https://w3id.org/security/v1', + 'https://www.w3.org/ns/activitystreams', + [ + 'Hashtag' => 'as:Hashtag', + 'sensitive' => 'as:sensitive', + 'schema' => 'http://schema.org/', + 'pixelfed' => 'http://pixelfed.org/ns#', + 'commentsEnabled' => [ + '@id' => 'pixelfed:commentsEnabled', + '@type' => 'schema:Boolean', + ], + 'capabilities' => [ + '@id' => 'pixelfed:capabilities', + '@container' => '@set', + ], + 'announce' => [ + '@id' => 'pixelfed:canAnnounce', + '@type' => '@id', + ], + 'like' => [ + '@id' => 'pixelfed:canLike', + '@type' => '@id', + ], + 'reply' => [ + '@id' => 'pixelfed:canReply', + '@type' => '@id', + ], + 'toot' => 'http://joinmastodon.org/ns#', + 'Emoji' => 'toot:Emoji', + ], + ], + 'id' => $status->permalink('#updates/'.$latestEdit->id), + 'type' => 'Update', + 'actor' => $status->profile->permalink(), + 'published' => $latestEdit->created_at->toAtomString(), + 'to' => $status->scopeToAudience('to'), + 'cc' => $status->scopeToAudience('cc'), + 'object' => [ + 'id' => $status->url(), + 'type' => 'Note', + 'summary' => $status->is_nsfw ? $status->cw_summary : null, + 'content' => $content, + 'inReplyTo' => $status->in_reply_to_id ? $status->parent()->url() : null, + 'published' => $status->created_at->toAtomString(), + 'url' => $status->url(), + 'attributedTo' => $status->profile->permalink(), + 'to' => $status->scopeToAudience('to'), + 'cc' => $status->scopeToAudience('cc'), + 'sensitive' => (bool) $status->is_nsfw, + 'attachment' => $status->media()->orderBy('order')->get()->map(function ($media) { + return [ + 'type' => $media->activityVerb(), + 'mediaType' => $media->mime, + 'url' => $media->url(), + 'name' => $media->caption, + ]; + })->toArray(), + 'tag' => $tags, + 'commentsEnabled' => (bool) ! $status->comments_disabled, + 'updated' => $latestEdit->created_at->toAtomString(), + 'capabilities' => [ + 'announce' => 'https://www.w3.org/ns/activitystreams#Public', + 'like' => 'https://www.w3.org/ns/activitystreams#Public', + 'reply' => $status->comments_disabled == true ? '[]' : 'https://www.w3.org/ns/activitystreams#Public', + ], + 'location' => $status->place_id ? [ + 'type' => 'Place', + 'name' => $status->place->name, + 'longitude' => $status->place->long, + 'latitude' => $status->place->lat, + 'country' => $status->place->country, + ] : null, + ], + ]; + } } diff --git a/app/Transformer/Api/Mastodon/v1/StatusTransformer.php b/app/Transformer/Api/Mastodon/v1/StatusTransformer.php index bfbc3d58b..16ff4cc30 100644 --- a/app/Transformer/Api/Mastodon/v1/StatusTransformer.php +++ b/app/Transformer/Api/Mastodon/v1/StatusTransformer.php @@ -2,48 +2,50 @@ namespace App\Transformer\Api\Mastodon\v1; -use App\Status; -use League\Fractal; -use Cache; use App\Services\MediaService; use App\Services\ProfileService; use App\Services\StatusHashtagService; +use App\Status; +use App\Util\Lexer\Autolink; +use League\Fractal; class StatusTransformer extends Fractal\TransformerAbstract { - public function transform(Status $status) - { - return [ - 'id' => (string) $status->id, - 'created_at' => $status->created_at->toJSON(), - 'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null, - 'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null, - 'sensitive' => (bool) $status->is_nsfw, - 'spoiler_text' => $status->cw_summary ?? '', - 'visibility' => $status->visibility ?? $status->scope, - 'language' => 'en', - 'uri' => $status->permalink(''), - 'url' => $status->url(), - 'replies_count' => $status->reply_count ?? 0, - 'reblogs_count' => $status->reblogs_count ?? 0, - 'favourites_count' => $status->likes_count ?? 0, - 'reblogged' => $status->shared(), - 'favourited' => $status->liked(), - 'muted' => false, - 'bookmarked' => false, - 'content' => $status->rendered ?? $status->caption ?? '', - 'reblog' => null, - 'application' => [ - 'name' => 'web', - 'website' => null - ], - 'mentions' => [], - 'emojis' => [], - 'card' => null, - 'poll' => null, - 'media_attachments' => MediaService::get($status->id), - 'account' => ProfileService::get($status->profile_id, true), - 'tags' => StatusHashtagService::statusTags($status->id), - ]; - } + public function transform(Status $status) + { + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : ""; + + return [ + 'id' => (string) $status->id, + 'created_at' => $status->created_at->toJSON(), + 'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null, + 'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null, + 'sensitive' => (bool) $status->is_nsfw, + 'spoiler_text' => $status->cw_summary ?? '', + 'visibility' => $status->visibility ?? $status->scope, + 'language' => 'en', + 'uri' => $status->permalink(''), + 'url' => $status->url(), + 'replies_count' => $status->reply_count ?? 0, + 'reblogs_count' => $status->reblogs_count ?? 0, + 'favourites_count' => $status->likes_count ?? 0, + 'reblogged' => $status->shared(), + 'favourited' => $status->liked(), + 'muted' => false, + 'bookmarked' => false, + 'content' => $content, + 'reblog' => null, + 'application' => [ + 'name' => 'web', + 'website' => null, + ], + 'mentions' => [], + 'emojis' => [], + 'card' => null, + 'poll' => null, + 'media_attachments' => MediaService::get($status->id), + 'account' => ProfileService::get($status->profile_id, true), + 'tags' => StatusHashtagService::statusTags($status->id), + ]; + } } diff --git a/app/Transformer/Api/StatusStatelessTransformer.php b/app/Transformer/Api/StatusStatelessTransformer.php index 3c2c02d60..9f52ab50a 100644 --- a/app/Transformer/Api/StatusStatelessTransformer.php +++ b/app/Transformer/Api/StatusStatelessTransformer.php @@ -2,76 +2,73 @@ namespace App\Transformer\Api; -use App\Status; -use League\Fractal; -use Cache; +use App\Models\CustomEmoji; use App\Services\AccountService; use App\Services\HashidService; use App\Services\LikeService; use App\Services\MediaService; use App\Services\MediaTagService; -use App\Services\StatusService; +use App\Services\PollService; use App\Services\StatusHashtagService; use App\Services\StatusLabelService; use App\Services\StatusMentionService; -use App\Services\PollService; -use App\Models\CustomEmoji; +use App\Services\StatusService; +use App\Status; use App\Util\Lexer\Autolink; +use League\Fractal; class StatusStatelessTransformer extends Fractal\TransformerAbstract { - public function transform(Status $status) - { - $taggedPeople = MediaTagService::get($status->id); - $poll = $status->type === 'poll' ? PollService::get($status->id) : null; - $rendered = config('exp.autolink') ? - ( $status->caption ? Autolink::create()->autolink($status->caption) : '' ) : - ( $status->rendered ?? $status->caption ); + public function transform(Status $status) + { + $taggedPeople = MediaTagService::get($status->id); + $poll = $status->type === 'poll' ? PollService::get($status->id) : null; + $rendered = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : ""; - return [ - '_v' => 1, - 'id' => (string) $status->id, - //'gid' => $status->group_id ? (string) $status->group_id : null, - 'shortcode' => HashidService::encode($status->id), - 'uri' => $status->url(), - 'url' => $status->url(), - 'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null, - 'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null, - 'reblog' => $status->reblog_of_id ? StatusService::get($status->reblog_of_id, false) : null, - 'content' => $rendered, - 'content_text' => $status->caption, - 'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)), - 'emojis' => CustomEmoji::scan($status->caption), - 'reblogs_count' => $status->reblogs_count ?? 0, - 'favourites_count' => $status->likes_count ?? 0, - 'reblogged' => null, - 'favourited' => null, - 'muted' => null, - 'sensitive' => (bool) $status->is_nsfw, - 'spoiler_text' => $status->cw_summary ?? '', - 'visibility' => $status->scope ?? $status->visibility, - 'application' => [ - 'name' => 'web', - 'website' => null - ], - 'language' => null, - 'mentions' => StatusMentionService::get($status->id), - 'pf_type' => $status->type ?? $status->setType(), - 'reply_count' => (int) $status->reply_count, - 'comments_disabled' => (bool) $status->comments_disabled, - 'thread' => false, - 'replies' => [], - 'parent' => [], - 'place' => $status->place, - 'local' => (bool) $status->local, - 'taggedPeople' => $taggedPeople, - 'label' => StatusLabelService::get($status), - 'liked_by' => LikeService::likedBy($status), - 'media_attachments' => MediaService::get($status->id), - 'account' => AccountService::get($status->profile_id, true), - 'tags' => StatusHashtagService::statusTags($status->id), - 'poll' => $poll, - 'edited_at' => $status->edited_at ? str_replace('+00:00', 'Z', $status->edited_at->format(DATE_RFC3339_EXTENDED)) : null, - ]; - } + return [ + '_v' => 1, + 'id' => (string) $status->id, + //'gid' => $status->group_id ? (string) $status->group_id : null, + 'shortcode' => HashidService::encode($status->id), + 'uri' => $status->url(), + 'url' => $status->url(), + 'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null, + 'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null, + 'reblog' => $status->reblog_of_id ? StatusService::get($status->reblog_of_id, false) : null, + 'content' => $rendered, + 'content_text' => $status->caption, + 'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)), + 'emojis' => CustomEmoji::scan($status->caption), + 'reblogs_count' => $status->reblogs_count ?? 0, + 'favourites_count' => $status->likes_count ?? 0, + 'reblogged' => null, + 'favourited' => null, + 'muted' => null, + 'sensitive' => (bool) $status->is_nsfw, + 'spoiler_text' => $status->cw_summary ?? '', + 'visibility' => $status->scope ?? $status->visibility, + 'application' => [ + 'name' => 'web', + 'website' => null, + ], + 'language' => null, + 'mentions' => StatusMentionService::get($status->id), + 'pf_type' => $status->type ?? $status->setType(), + 'reply_count' => (int) $status->reply_count, + 'comments_disabled' => (bool) $status->comments_disabled, + 'thread' => false, + 'replies' => [], + 'parent' => [], + 'place' => $status->place, + 'local' => (bool) $status->local, + 'taggedPeople' => $taggedPeople, + 'label' => StatusLabelService::get($status), + 'liked_by' => LikeService::likedBy($status), + 'media_attachments' => MediaService::get($status->id), + 'account' => AccountService::get($status->profile_id, true), + 'tags' => StatusHashtagService::statusTags($status->id), + 'poll' => $poll, + 'edited_at' => $status->edited_at ? str_replace('+00:00', 'Z', $status->edited_at->format(DATE_RFC3339_EXTENDED)) : null, + ]; + } } diff --git a/app/Transformer/Api/StatusTransformer.php b/app/Transformer/Api/StatusTransformer.php index 22a840ce0..4a6dc5d7d 100644 --- a/app/Transformer/Api/StatusTransformer.php +++ b/app/Transformer/Api/StatusTransformer.php @@ -2,80 +2,75 @@ namespace App\Transformer\Api; -use App\Like; -use App\Status; -use League\Fractal; -use Cache; +use App\Models\CustomEmoji; +use App\Services\BookmarkService; use App\Services\HashidService; use App\Services\LikeService; use App\Services\MediaService; use App\Services\MediaTagService; -use App\Services\StatusService; +use App\Services\PollService; +use App\Services\ProfileService; use App\Services\StatusHashtagService; use App\Services\StatusLabelService; use App\Services\StatusMentionService; -use App\Services\ProfileService; -use Illuminate\Support\Str; -use App\Services\PollService; -use App\Models\CustomEmoji; -use App\Services\BookmarkService; +use App\Services\StatusService; +use App\Status; use App\Util\Lexer\Autolink; +use League\Fractal; class StatusTransformer extends Fractal\TransformerAbstract { - public function transform(Status $status) - { - $pid = request()->user()->profile_id; - $taggedPeople = MediaTagService::get($status->id); - $poll = $status->type === 'poll' ? PollService::get($status->id, $pid) : null; - $rendered = config('exp.autolink') ? - ( $status->caption ? Autolink::create()->autolink($status->caption) : '' ) : - ( $status->rendered ?? $status->caption ); + public function transform(Status $status) + { + $pid = request()->user()->profile_id; + $taggedPeople = MediaTagService::get($status->id); + $poll = $status->type === 'poll' ? PollService::get($status->id, $pid) : null; + $content = $status->caption ? nl2br(Autolink::create()->autolink($status->caption)) : ''; - return [ - '_v' => 1, - 'id' => (string) $status->id, - 'shortcode' => HashidService::encode($status->id), - 'uri' => $status->url(), - 'url' => $status->url(), - 'in_reply_to_id' => (string) $status->in_reply_to_id, - 'in_reply_to_account_id' => (string) $status->in_reply_to_profile_id, - 'reblog' => $status->reblog_of_id ? StatusService::get($status->reblog_of_id) : null, - 'content' => $rendered, - 'content_text' => $status->caption, - 'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)), - 'emojis' => CustomEmoji::scan($status->caption), - 'reblogs_count' => 0, - 'favourites_count' => $status->likes_count ?? 0, - 'reblogged' => $status->shared(), - 'favourited' => $status->liked(), - 'muted' => null, - 'sensitive' => (bool) $status->is_nsfw, - 'spoiler_text' => $status->cw_summary ?? '', - 'visibility' => $status->scope ?? $status->visibility, - 'application' => [ - 'name' => 'web', - 'website' => null - ], - 'language' => null, - 'mentions' => StatusMentionService::get($status->id), - 'pf_type' => $status->type ?? $status->setType(), - 'reply_count' => (int) $status->reply_count, - 'comments_disabled' => (bool) $status->comments_disabled, - 'thread' => false, - 'replies' => [], - 'parent' => [], - 'place' => $status->place, - 'local' => (bool) $status->local, - 'taggedPeople' => $taggedPeople, - 'label' => StatusLabelService::get($status), - 'liked_by' => LikeService::likedBy($status), - 'media_attachments' => MediaService::get($status->id), - 'account' => ProfileService::get($status->profile_id, true), - 'tags' => StatusHashtagService::statusTags($status->id), - 'poll' => $poll, - 'bookmarked' => BookmarkService::get($pid, $status->id), - 'edited_at' => $status->edited_at ? str_replace('+00:00', 'Z', $status->edited_at->format(DATE_RFC3339_EXTENDED)) : null, - ]; - } + return [ + '_v' => 1, + 'id' => (string) $status->id, + 'shortcode' => HashidService::encode($status->id), + 'uri' => $status->url(), + 'url' => $status->url(), + 'in_reply_to_id' => $status->in_reply_to_id ? (string) $status->in_reply_to_id : null, + 'in_reply_to_account_id' => $status->in_reply_to_profile_id ? (string) $status->in_reply_to_profile_id : null, + 'reblog' => $status->reblog_of_id ? StatusService::get($status->reblog_of_id) : null, + 'content' => $content, + 'content_text' => $status->caption, + 'created_at' => str_replace('+00:00', 'Z', $status->created_at->format(DATE_RFC3339_EXTENDED)), + 'emojis' => CustomEmoji::scan($status->caption), + 'reblogs_count' => 0, + 'favourites_count' => $status->likes_count ?? 0, + 'reblogged' => $status->shared(), + 'favourited' => $status->liked(), + 'muted' => null, + 'sensitive' => (bool) $status->is_nsfw, + 'spoiler_text' => $status->cw_summary ?? '', + 'visibility' => $status->scope ?? $status->visibility, + 'application' => [ + 'name' => 'web', + 'website' => null, + ], + 'language' => null, + 'mentions' => StatusMentionService::get($status->id), + 'pf_type' => $status->type ?? $status->setType(), + 'reply_count' => (int) $status->reply_count, + 'comments_disabled' => (bool) $status->comments_disabled, + 'thread' => false, + 'replies' => [], + 'parent' => [], + 'place' => $status->place, + 'local' => (bool) $status->local, + 'taggedPeople' => $taggedPeople, + 'label' => StatusLabelService::get($status), + 'liked_by' => LikeService::likedBy($status), + 'media_attachments' => MediaService::get($status->id), + 'account' => ProfileService::get($status->profile_id, true), + 'tags' => StatusHashtagService::statusTags($status->id), + 'poll' => $poll, + 'bookmarked' => BookmarkService::get($pid, $status->id), + 'edited_at' => $status->edited_at ? str_replace('+00:00', 'Z', $status->edited_at->format(DATE_RFC3339_EXTENDED)) : null, + ]; + } } diff --git a/app/Util/ActivityPub/Helpers.php b/app/Util/ActivityPub/Helpers.php index 782404836..570d216ab 100644 --- a/app/Util/ActivityPub/Helpers.php +++ b/app/Util/ActivityPub/Helpers.php @@ -24,7 +24,6 @@ use App\Status; use App\Util\Media\License; use Cache; use Carbon\Carbon; -use Illuminate\Support\Str; use Illuminate\Validation\Rule; use League\Uri\Exceptions\UriException; use League\Uri\Uri; @@ -33,16 +32,32 @@ use Validator; class Helpers { - public static function validateObject($data) + private const PUBLIC_TIMELINE = 'https://www.w3.org/ns/activitystreams#Public'; + + private const CACHE_TTL = 14440; + + private const URL_CACHE_PREFIX = 'helpers:url:'; + + private const FETCH_CACHE_TTL = 15; + + private const LOCALHOST_DOMAINS = [ + 'localhost', + '127.0.0.1', + '::1', + 'broadcasthost', + 'ip6-localhost', + 'ip6-loopback', + ]; + + /** + * Validate an ActivityPub object + */ + public static function validateObject(array $data): bool { $verbs = ['Create', 'Announce', 'Like', 'Follow', 'Delete', 'Accept', 'Reject', 'Undo', 'Tombstone']; - $valid = Validator::make($data, [ - 'type' => [ - 'required', - 'string', - Rule::in($verbs), - ], + return Validator::make($data, [ + 'type' => ['required', 'string', Rule::in($verbs)], 'id' => 'required|string', 'actor' => 'required|string|url', 'object' => 'required', @@ -50,105 +65,88 @@ class Helpers 'object.attributedTo' => 'required_if:type,Create|url', 'published' => 'required_if:type,Create|date', ])->passes(); - - return $valid; } - public static function verifyAttachments($data) + /** + * Validate media attachments + */ + public static function verifyAttachments(array $data): bool { if (! isset($data['object']) || empty($data['object'])) { $data = ['object' => $data]; } $activity = $data['object']; - $mimeTypes = explode(',', config_cache('pixelfed.media_types')); - $mediaTypes = in_array('video/mp4', $mimeTypes) ? ['Document', 'Image', 'Video'] : ['Document', 'Image']; - - // Peertube - // $mediaTypes = in_array('video/mp4', $mimeTypes) ? ['Document', 'Image', 'Video', 'Link'] : ['Document', 'Image']; + $mediaTypes = in_array('video/mp4', $mimeTypes) ? + ['Document', 'Image', 'Video'] : + ['Document', 'Image']; if (! isset($activity['attachment']) || empty($activity['attachment'])) { return false; } - // peertube - // $attachment = is_array($activity['url']) ? - // collect($activity['url']) - // ->filter(function($media) { - // return $media['type'] == 'Link' && $media['mediaType'] == 'video/mp4'; - // }) - // ->take(1) - // ->values() - // ->toArray()[0] : $activity['attachment']; - - $attachment = $activity['attachment']; - - $valid = Validator::make($attachment, [ - '*.type' => [ - 'required', - 'string', - Rule::in($mediaTypes), - ], + return Validator::make($activity['attachment'], [ + '*.type' => ['required', 'string', Rule::in($mediaTypes)], '*.url' => 'required|url', - '*.mediaType' => [ - 'required', - 'string', - Rule::in($mimeTypes), - ], + '*.mediaType' => ['required', 'string', Rule::in($mimeTypes)], '*.name' => 'sometimes|nullable|string', '*.blurhash' => 'sometimes|nullable|string|min:6|max:164', '*.width' => 'sometimes|nullable|integer|min:1|max:5000', '*.height' => 'sometimes|nullable|integer|min:1|max:5000', ])->passes(); - - return $valid; } - public static function normalizeAudience($data, $localOnly = true) + /** + * Normalize ActivityPub audience + */ + public static function normalizeAudience(array $data, bool $localOnly = true): ?array { if (! isset($data['to'])) { - return; + return null; } - $audience = []; - $audience['to'] = []; - $audience['cc'] = []; - $scope = 'private'; + $audience = [ + 'to' => [], + 'cc' => [], + 'scope' => 'private', + ]; if (is_array($data['to']) && ! empty($data['to'])) { foreach ($data['to'] as $to) { - if ($to == 'https://www.w3.org/ns/activitystreams#Public') { - $scope = 'public'; + if ($to == self::PUBLIC_TIMELINE) { + $audience['scope'] = 'public'; continue; } $url = $localOnly ? self::validateLocalUrl($to) : self::validateUrl($to); - if ($url != false) { - array_push($audience['to'], $url); + if ($url) { + $audience['to'][] = $url; } } } if (is_array($data['cc']) && ! empty($data['cc'])) { foreach ($data['cc'] as $cc) { - if ($cc == 'https://www.w3.org/ns/activitystreams#Public') { - $scope = 'unlisted'; + if ($cc == self::PUBLIC_TIMELINE) { + $audience['scope'] = 'unlisted'; continue; } $url = $localOnly ? self::validateLocalUrl($cc) : self::validateUrl($cc); - if ($url != false) { - array_push($audience['cc'], $url); + if ($url) { + $audience['cc'][] = $url; } } } - $audience['scope'] = $scope; return $audience; } - public static function userInAudience($profile, $data) + /** + * Check if user is in audience + */ + public static function userInAudience(Profile $profile, array $data): bool { $audience = self::normalizeAudience($data); $url = $profile->permalink(); @@ -156,96 +154,167 @@ class Helpers return in_array($url, $audience['to']) || in_array($url, $audience['cc']); } - public static function validateUrl($url = null, $disableDNSCheck = false, $forceBanCheck = false) + /** + * Validate URL with various security and format checks + */ + public static function validateUrl(?string $url, bool $disableDNSCheck = false, bool $forceBanCheck = false): string|bool { - if (is_array($url) && ! empty($url)) { - $url = $url[0]; - } - if (! $url || strlen($url) === 0) { + if (! $normalizedUrl = self::normalizeUrl($url)) { return false; } + try { - $uri = Uri::new($url); + $uri = Uri::new($normalizedUrl); - if (! $uri) { - return false; - } - - if ($uri->getScheme() !== 'https') { + if (! self::isValidUri($uri)) { return false; } $host = $uri->getHost(); - - if (! $host || $host === '') { + if (! self::isValidHost($host)) { return false; } - if (! filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) { + if (! self::passesSecurityChecks($host, $disableDNSCheck, $forceBanCheck)) { return false; } - if (! str_contains($host, '.')) { - return false; - } - - $localhosts = [ - 'localhost', - '127.0.0.1', - '::1', - 'broadcasthost', - 'ip6-localhost', - 'ip6-loopback', - ]; - - if (in_array($host, $localhosts)) { - return false; - } - - if ($disableDNSCheck !== true && app()->environment() === 'production' && (bool) config('security.url.verify_dns')) { - $hash = hash('sha256', $host); - $key = "helpers:url:valid-dns:sha256-{$hash}"; - $domainValidDns = Cache::remember($key, 14440, function () use ($host) { - return DomainService::hasValidDns($host); - }); - if (! $domainValidDns) { - return false; - } - } - - if ($forceBanCheck || $disableDNSCheck !== true && app()->environment() === 'production') { - $bannedInstances = InstanceService::getBannedDomains(); - if (in_array($host, $bannedInstances)) { - return false; - } - } - return $uri->toString(); + } catch (UriException $e) { return false; } } - public static function validateLocalUrl($url) + /** + * Normalize URL input + */ + public static function normalizeUrl(?string $url): ?string + { + if (is_array($url) && ! empty($url)) { + $url = $url[0]; + } + + return (! $url || strlen($url) === 0) ? null : $url; + } + + /** + * Validate basic URI requirements + */ + public static function isValidUri(Uri $uri): bool + { + return $uri && $uri->getScheme() === 'https'; + } + + /** + * Validate host requirements + */ + public static function isValidHost(?string $host): bool + { + if (! $host || $host === '') { + return false; + } + + if (! filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) { + return false; + } + + if (! str_contains($host, '.')) { + return false; + } + + if (in_array($host, self::LOCALHOST_DOMAINS)) { + return false; + } + + return true; + } + + /** + * Check DNS and banned status if required + */ + public static function passesSecurityChecks(string $host, bool $disableDNSCheck, bool $forceBanCheck): bool + { + if ($disableDNSCheck !== true && self::shouldCheckDNS()) { + if (! self::hasValidDNS($host)) { + return false; + } + } + + if ($forceBanCheck || self::shouldCheckBans()) { + if (self::isHostBanned($host)) { + return false; + } + } + + return true; + } + + /** + * Check if DNS validation is required + */ + public static function shouldCheckDNS(): bool + { + return app()->environment() === 'production' && + (bool) config('security.url.verify_dns'); + } + + /** + * Validate domain DNS records + */ + public static function hasValidDNS(string $host): bool + { + $hash = hash('sha256', $host); + $key = self::URL_CACHE_PREFIX."valid-dns:sha256-{$hash}"; + + return Cache::remember($key, self::CACHE_TTL, function () use ($host) { + return DomainService::hasValidDns($host); + }); + } + + /** + * Check if domain bans should be validated + */ + public static function shouldCheckBans(): bool + { + return app()->environment() === 'production'; + } + + /** + * Check if host is in banned domains list + */ + public static function isHostBanned(string $host): bool + { + $bannedInstances = InstanceService::getBannedDomains(); + + return in_array($host, $bannedInstances); + } + + /** + * Validate local URL + */ + public static function validateLocalUrl(string $url): string|bool { $url = self::validateUrl($url); - if ($url == true) { + if ($url) { $domain = config('pixelfed.domain.app'); - $uri = Uri::new($url); $host = $uri->getHost(); + if (! $host || empty($host)) { return false; } - $url = strtolower($domain) === strtolower($host) ? $url : false; - return $url; + return strtolower($domain) === strtolower($host) ? $url : false; } return false; } - public static function zttpUserAgent() + /** + * Get user agent string + */ + public static function zttpUserAgent(): array { $version = config('pixelfed.version'); $url = config('app.url'); @@ -298,254 +367,248 @@ class Helpers return null; } - public static function statusFirstOrFetch($url, $replyTo = false) + public static function validateTimestamp($timestamp) { - $url = self::validateUrl($url); - if ($url == false) { - return; + try { + $date = Carbon::parse($timestamp); + $now = Carbon::now(); + $tenYearsAgo = $now->copy()->subYears(20); + $isMoreThanTenYearsOld = $date->lt($tenYearsAgo); + $tomorrow = $now->copy()->addDay(); + $isMoreThanOneDayFuture = $date->gt($tomorrow); + + return ! ($isMoreThanTenYearsOld || $isMoreThanOneDayFuture); + } catch (\Exception $e) { + return false; + } + } + + /** + * Fetch or create a status from URL + */ + public static function statusFirstOrFetch(string $url, bool $replyTo = false): ?Status + { + if (! $validUrl = self::validateUrl($url)) { + return null; } - $host = parse_url($url, PHP_URL_HOST); - $local = config('pixelfed.domain.app') == $host ? true : false; + if ($status = self::findExistingStatus($url)) { + return $status; + } - if ($local) { + return self::createStatusFromUrl($url, $replyTo); + } + + /** + * Find existing status by URL + */ + public static function findExistingStatus(string $url): ?Status + { + $host = parse_url($url, PHP_URL_HOST); + + if (self::isLocalDomain($host)) { $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']) - ->whereUri($url) - ->orWhere('object_url', $url) + return Status::whereNotIn('scope', ['draft', 'archived']) + ->where(function ($query) use ($url) { + $query->whereUri($url) + ->orWhere('object_url', $url); + }) ->first(); + } - if ($cached) { - return $cached; - } - + /** + * Create a new status from ActivityPub data + */ + public static function createStatusFromUrl(string $url, bool $replyTo): ?Status + { $res = self::fetchFromUrl($url); - if (! $res || empty($res) || isset($res['error']) || ! isset($res['@context']) || ! isset($res['published'])) { - return; + if (! $res || ! self::isValidStatusData($res)) { + return null; } - if (config('autospam.live_filters.enabled')) { - $filters = config('autospam.live_filters.filters'); - if (! empty($filters) && isset($res['content']) && ! empty($res['content']) && strlen($filters) > 3) { - $filters = array_map('trim', explode(',', $filters)); - $content = $res['content']; - foreach ($filters as $filter) { - $filter = trim(strtolower($filter)); - if (! $filter || ! strlen($filter)) { - continue; - } - if (str_contains(strtolower($content), $filter)) { - return; - } - } - } + if (! self::validateTimestamp($res['published'])) { + return null; } - if (isset($res['object'])) { - $activity = $res; - } else { - $activity = ['object' => $res]; + if (! self::passesContentFilters($res)) { + return null; } - $scope = 'private'; + $activity = isset($res['object']) ? $res : ['object' => $res]; - $cw = isset($res['sensitive']) ? (bool) $res['sensitive'] : false; - - if (isset($res['to']) == true) { - if (is_array($res['to']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['to'])) { - $scope = 'public'; - } - if (is_string($res['to']) && $res['to'] == 'https://www.w3.org/ns/activitystreams#Public') { - $scope = 'public'; - } + if (! $profile = self::getStatusProfile($activity)) { + return null; } - if (isset($res['cc']) == true) { - if (is_array($res['cc']) && in_array('https://www.w3.org/ns/activitystreams#Public', $res['cc'])) { - $scope = 'unlisted'; - } - if (is_string($res['cc']) && $res['cc'] == 'https://www.w3.org/ns/activitystreams#Public') { - $scope = 'unlisted'; - } + if (! self::validateStatusUrls($url, $activity)) { + return null; } - if (config('costar.enabled') == true) { - $blockedKeywords = config('costar.keyword.block'); - if ($blockedKeywords !== null) { - $keywords = config('costar.keyword.block'); - foreach ($keywords as $kw) { - if (Str::contains($res['content'], $kw) == true) { - return; - } - } - } - - $unlisted = config('costar.domain.unlisted'); - if (in_array(parse_url($url, PHP_URL_HOST), $unlisted) == true) { - $unlisted = true; - $scope = 'unlisted'; - } else { - $unlisted = false; - } - - $cwDomains = config('costar.domain.cw'); - if (in_array(parse_url($url, PHP_URL_HOST), $cwDomains) == true) { - $cw = true; - } - } - - $id = isset($res['id']) ? self::pluckval($res['id']) : self::pluckval($url); - $idDomain = parse_url($id, PHP_URL_HOST); - $urlDomain = parse_url($url, PHP_URL_HOST); - - if ($idDomain && $urlDomain && strtolower($idDomain) !== strtolower($urlDomain)) { - return; - } - - if (! self::validateUrl($id)) { - return; - } - - if (! isset($activity['object']['attributedTo'])) { - return; - } - - $attributedTo = is_string($activity['object']['attributedTo']) ? - $activity['object']['attributedTo'] : - (is_array($activity['object']['attributedTo']) ? - collect($activity['object']['attributedTo']) - ->filter(function ($o) { - return $o && isset($o['type']) && $o['type'] == 'Person'; - }) - ->pluck('id') - ->first() : null - ); - - if ($attributedTo) { - $actorDomain = parse_url($attributedTo, PHP_URL_HOST); - if (! self::validateUrl($attributedTo) || - $idDomain !== $actorDomain || - $actorDomain !== $urlDomain - ) { - return; - } - } - - if ($idDomain !== $urlDomain) { - return; - } - - $profile = self::profileFirstOrNew($attributedTo); - - if (! $profile) { - return; - } - - if (isset($activity['object']['inReplyTo']) && ! empty($activity['object']['inReplyTo']) || $replyTo == true) { - $reply_to = self::statusFirstOrFetch(self::pluckval($activity['object']['inReplyTo']), false); - if ($reply_to) { - $blocks = UserFilterService::blocks($reply_to->profile_id); - if (in_array($profile->id, $blocks)) { - return; - } - } - $reply_to = optional($reply_to)->id; - } else { - $reply_to = null; - } - $ts = self::pluckval($res['published']); - - if ($scope == 'public' && in_array($urlDomain, InstanceService::getUnlistedDomains())) { - $scope = 'unlisted'; - } - - if (in_array($urlDomain, InstanceService::getNsfwDomains())) { - $cw = true; - } + $reply_to = self::getReplyToId($activity, $profile, $replyTo); + $scope = self::getScope($activity, $url); + $cw = self::getSensitive($activity, $url); if ($res['type'] === 'Question') { - $status = self::storePoll( + return self::storePoll( $profile, $res, $url, - $ts, + $res['published'], $reply_to, $cw, $scope, - $id + $activity['id'] ?? $url ); - - return $status; - } else { - $status = self::storeStatus($url, $profile, $res); } - return $status; + return self::storeStatus($url, $profile, $res); } - public static function storeStatus($url, $profile, $activity) + /** + * Validate status data + */ + public static function isValidStatusData(?array $res): bool { - $originalUrl = $url; - $id = isset($activity['id']) ? self::pluckval($activity['id']) : self::pluckval($activity['url']); - $url = isset($activity['url']) && is_string($activity['url']) ? self::pluckval($activity['url']) : self::pluckval($id); - $idDomain = parse_url($id, PHP_URL_HOST); - $urlDomain = parse_url($url, PHP_URL_HOST); - $originalUrlDomain = parse_url($originalUrl, PHP_URL_HOST); - if (! self::validateUrl($id) || ! self::validateUrl($url)) { - return; + return $res && + ! empty($res) && + ! isset($res['error']) && + isset($res['@context']) && + isset($res['published']); + } + + /** + * Check if content passes filters + */ + public static function passesContentFilters(array $res): bool + { + if (! config('autospam.live_filters.enabled')) { + return true; } - if (strtolower($originalUrlDomain) !== strtolower($idDomain) || - strtolower($originalUrlDomain) !== strtolower($urlDomain)) { - return; + $filters = config('autospam.live_filters.filters'); + if (empty($filters) || ! isset($res['content']) || strlen($filters) <= 3) { + return true; } - $reply_to = self::getReplyTo($activity); + $filters = array_map('trim', explode(',', $filters)); + $content = strtolower($res['content']); - $ts = self::pluckval($activity['published']); - $scope = self::getScope($activity, $url); - $cw = self::getSensitive($activity, $url); - $pid = is_object($profile) ? $profile->id : (is_array($profile) ? $profile['id'] : null); - $isUnlisted = is_object($profile) ? $profile->unlisted : (is_array($profile) ? $profile['unlisted'] : false); - $commentsDisabled = isset($activity['commentsEnabled']) ? ! boolval($activity['commentsEnabled']) : false; - - if (! $pid) { - return; - } - - if ($scope == 'public') { - if ($isUnlisted == true) { - $scope = 'unlisted'; + foreach ($filters as $filter) { + $filter = trim(strtolower($filter)); + if ($filter && str_contains($content, $filter)) { + return false; } } - $status = Status::updateOrCreate( - [ - 'uri' => $url, - ], [ - 'profile_id' => $pid, - 'url' => $url, - 'object_url' => $id, - 'caption' => isset($activity['content']) ? Purify::clean(strip_tags($activity['content'])) : null, - 'rendered' => isset($activity['content']) ? Purify::clean($activity['content']) : null, - 'created_at' => Carbon::parse($ts)->tz('UTC'), - 'in_reply_to_id' => $reply_to, - 'local' => false, - 'is_nsfw' => $cw, - 'scope' => $scope, - 'visibility' => $scope, - 'cw_summary' => ($cw == true && isset($activity['summary']) ? - Purify::clean(strip_tags($activity['summary'])) : null), - 'comments_disabled' => $commentsDisabled, - ] - ); + return true; + } - if ($reply_to == null) { + /** + * Get profile for status + */ + public static function getStatusProfile(array $activity): ?Profile + { + if (! isset($activity['object']['attributedTo'])) { + return null; + } + + $attributedTo = self::extractAttributedTo($activity['object']['attributedTo']); + + return $attributedTo ? self::profileFirstOrNew($attributedTo) : null; + } + + /** + * Extract attributed to value + */ + public static function extractAttributedTo(string|array $attributedTo): ?string + { + if (is_string($attributedTo)) { + return $attributedTo; + } + + if (is_array($attributedTo)) { + return collect($attributedTo) + ->filter(fn ($o) => $o && isset($o['type']) && $o['type'] == 'Person') + ->pluck('id') + ->first(); + } + + return null; + } + + /** + * Validate status URLs match + */ + public static function validateStatusUrls(string $url, array $activity): bool + { + $id = isset($activity['id']) ? + self::pluckval($activity['id']) : + self::pluckval($url); + + $idDomain = parse_url($id, PHP_URL_HOST); + $urlDomain = parse_url($url, PHP_URL_HOST); + + return $idDomain && + $urlDomain && + strtolower($idDomain) === strtolower($urlDomain); + } + + /** + * Get reply-to status ID + */ + public static function getReplyToId(array $activity, Profile $profile, bool $replyTo): ?int + { + $inReplyTo = $activity['object']['inReplyTo'] ?? null; + + if (! $inReplyTo && ! $replyTo) { + return null; + } + + $reply = self::statusFirstOrFetch(self::pluckval($inReplyTo), false); + + if (! $reply) { + return null; + } + + $blocks = UserFilterService::blocks($reply->profile_id); + + return in_array($profile->id, $blocks) ? null : $reply->id; + } + + /** + * Store a new regular status + */ + public static function storeStatus(string $url, Profile $profile, array $activity): Status + { + $originalUrl = $url; + $id = self::getStatusId($activity, $url); + $url = self::getStatusUrl($activity, $id); + + if ((! isset($activity['type']) || + in_array($activity['type'], ['Create', 'Note'])) && + ! self::validateStatusDomains($originalUrl, $id, $url)) { + throw new \Exception('Invalid status domains'); + } + + $reply_to = self::getReplyTo($activity); + $ts = self::pluckval($activity['published']); + $scope = self::getScope($activity, $url); + $commentsDisabled = $activity['commentsEnabled'] ?? false; + $cw = self::getSensitive($activity, $url); + + if ($profile->unlisted) { + $scope = 'unlisted'; + } + + $status = self::createOrUpdateStatus($url, $profile, $id, $activity, $ts, $reply_to, $cw, $scope, $commentsDisabled); + + if ($reply_to === null) { self::importNoteAttachment($activity, $status); } else { if (isset($activity['attachment']) && ! empty($activity['attachment'])) { @@ -558,34 +621,136 @@ class Helpers StatusTagsPipeline::dispatch($activity, $status); } + self::handleStatusPostProcessing($status, $profile->id, $url); + + return $status; + } + + /** + * Get status ID from activity + */ + public static function getStatusId(array $activity, string $url): string + { + return isset($activity['id']) ? + self::pluckval($activity['id']) : + self::pluckval($url); + } + + /** + * Get status URL from activity + */ + public static function getStatusUrl(array $activity, string $id): string + { + return isset($activity['url']) && is_string($activity['url']) ? + self::pluckval($activity['url']) : + self::pluckval($id); + } + + /** + * Validate status domain consistency + */ + public static function validateStatusDomains(string $originalUrl, string $id, string $url): bool + { + if (! self::validateUrl($id) || ! self::validateUrl($url)) { + return false; + } + + $originalDomain = parse_url($originalUrl, PHP_URL_HOST); + $idDomain = parse_url($id, PHP_URL_HOST); + $urlDomain = parse_url($url, PHP_URL_HOST); + + return strtolower($originalDomain) === strtolower($idDomain) && + strtolower($originalDomain) === strtolower($urlDomain); + } + + /** + * Create or update status record + */ + public static function createOrUpdateStatus( + string $url, + Profile $profile, + string $id, + array $activity, + string $ts, + ?int $reply_to, + bool $cw, + string $scope, + bool $commentsDisabled + ): Status { + $caption = isset($activity['content']) ? + Purify::clean($activity['content']) : + ''; + + return Status::updateOrCreate( + ['uri' => $url], + [ + 'profile_id' => $profile->id, + 'url' => $url, + 'object_url' => $id, + 'caption' => strip_tags($caption), + 'rendered' => $caption, + 'created_at' => Carbon::parse($ts)->tz('UTC'), + 'in_reply_to_id' => $reply_to, + 'local' => false, + 'is_nsfw' => $cw, + 'scope' => $scope, + 'visibility' => $scope, + 'cw_summary' => ($cw && isset($activity['summary'])) ? + Purify::clean(strip_tags($activity['summary'])) : + null, + 'comments_disabled' => $commentsDisabled, + ] + ); + } + + /** + * Handle post-creation status processing + */ + public static function handleStatusPostProcessing(Status $status, int $profileId, string $url): void + { if (config('instance.timeline.network.cached') && - $status->in_reply_to_id === null && - $status->reblog_of_id === null && - in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) && - $status->created_at->gt(now()->subHours(config('instance.timeline.network.max_hours_old'))) && - (config('instance.hide_nsfw_on_public_feeds') == true ? $status->is_nsfw == false : true) + self::isEligibleForNetwork($status) ) { - $filteredDomains = collect(InstanceService::getBannedDomains()) - ->merge(InstanceService::getUnlistedDomains()) - ->unique() - ->values() - ->toArray(); + $urlDomain = parse_url($url, PHP_URL_HOST); + $filteredDomains = self::getFilteredDomains(); + if (! in_array($urlDomain, $filteredDomains)) { - if (! $isUnlisted) { - NetworkTimelineService::add($status->id); - } + NetworkTimelineService::add($status->id); } } - AccountStatService::incrementPostCount($pid); + AccountStatService::incrementPostCount($profileId); if ($status->in_reply_to_id === null && in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) ) { - FeedInsertRemotePipeline::dispatch($status->id, $pid)->onQueue('feed'); + FeedInsertRemotePipeline::dispatch($status->id, $profileId) + ->onQueue('feed'); } + } - return $status; + /** + * Check if status is eligible for network timeline + */ + public static function isEligibleForNetwork(Status $status): bool + { + return $status->in_reply_to_id === null && + $status->reblog_of_id === null && + in_array($status->type, ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']) && + $status->created_at->gt(now()->subHours(config('instance.timeline.network.max_hours_old'))) && + (config('instance.hide_nsfw_on_public_feeds') ? ! $status->is_nsfw : true); + } + + /** + * Get filtered domains list + */ + public static function getFilteredDomains(): array + { + return collect(InstanceService::getBannedDomains()) + ->merge(InstanceService::getUnlistedDomains()) + ->unique() + ->values() + ->toArray(); } public static function getSensitive($activity, $url) @@ -655,7 +820,7 @@ class Helpers return $scope; } - private static function storePoll($profile, $res, $url, $ts, $reply_to, $cw, $scope, $id) + public 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) { return; @@ -669,13 +834,14 @@ class Helpers return $option['replies']['totalItems'] ?? 0; })->toArray(); + $defaultCaption = ''; $status = new Status; $status->profile_id = $profile->id; $status->url = isset($res['url']) ? $res['url'] : $url; $status->uri = isset($res['url']) ? $res['url'] : $url; $status->object_url = $id; - $status->caption = strip_tags($res['content']); - $status->rendered = Purify::clean($res['content']); + $status->caption = strip_tags(Purify::clean($res['content'])) ?? $defaultCaption; + $status->rendered = Purify::clean($res['content'] ?? $defaultCaption); $status->created_at = Carbon::parse($ts)->tz('UTC'); $status->in_reply_to_id = null; $status->local = false; @@ -709,185 +875,430 @@ class Helpers return self::statusFirstOrFetch($url); } - public static function importNoteAttachment($data, Status $status) + /** + * Process and store note attachments + */ + public static function importNoteAttachment(array $data, Status $status): void { - if (self::verifyAttachments($data) == false) { - // \Log::info('importNoteAttachment::failedVerification.', [$data['id']]); + if (! self::verifyAttachments($data)) { $status->viewType(); return; } - $attachments = isset($data['object']) ? $data['object']['attachment'] : $data['attachment']; - // peertube - // if(!$attachments) { - // $obj = isset($data['object']) ? $data['object'] : $data; - // $attachments = is_array($obj['url']) ? $obj['url'] : null; - // } - $user = $status->profile; - $storagePath = MediaPathService::get($user, 2); - $allowed = explode(',', config_cache('pixelfed.media_types')); + + $attachments = self::getAttachments($data); + $profile = $status->profile; + $storagePath = MediaPathService::get($profile, 2); + $allowedTypes = explode(',', config_cache('pixelfed.media_types')); foreach ($attachments as $key => $media) { - $type = $media['mediaType']; - $url = $media['url']; - $valid = self::validateUrl($url); - if (in_array($type, $allowed) == false || $valid == false) { + if (! self::isValidAttachment($media, $allowedTypes)) { continue; } - $blurhash = isset($media['blurhash']) ? $media['blurhash'] : null; - $license = isset($media['license']) ? License::nameToId($media['license']) : null; - $caption = isset($media['name']) ? Purify::clean($media['name']) : null; - $width = isset($media['width']) ? $media['width'] : false; - $height = isset($media['height']) ? $media['height'] : false; - $media = new Media; - $media->blurhash = $blurhash; - $media->remote_media = true; - $media->status_id = $status->id; - $media->profile_id = $status->profile_id; - $media->user_id = null; - $media->media_path = $url; - $media->remote_url = $url; - $media->caption = $caption; - $media->order = $key + 1; - if ($width) { - $media->width = $width; - } - if ($height) { - $media->height = $height; - } - if ($license) { - $media->license = $license; - } - $media->mime = $type; - $media->version = 3; - $media->save(); - - if ((bool) config_cache('pixelfed.cloud_storage') == true) { - MediaStoragePipeline::dispatch($media); - } + $mediaModel = self::createMediaAttachment($media, $status, $key); + self::handleMediaStorage($mediaModel); } $status->viewType(); - } - public static function profileFirstOrNew($url) + /** + * Get attachments from ActivityPub data + */ + public static function getAttachments(array $data): array { - $url = self::validateUrl($url); - if ($url == false) { - return; + return isset($data['object']) ? + $data['object']['attachment'] : + $data['attachment']; + } + + /** + * Validate individual attachment + */ + public static function isValidAttachment(array $media, array $allowedTypes): bool + { + $type = $media['mediaType']; + $url = $media['url']; + + return in_array($type, $allowedTypes) && + self::validateUrl($url); + } + + /** + * Create media attachment record + */ + public static function createMediaAttachment(array $media, Status $status, int $key): Media + { + $mediaModel = new Media; + + self::setBasicMediaAttributes($mediaModel, $media, $status, $key); + self::setOptionalMediaAttributes($mediaModel, $media); + + $mediaModel->save(); + + return $mediaModel; + } + + /** + * Set basic media attributes + */ + public static function setBasicMediaAttributes(Media $media, array $data, Status $status, int $key): void + { + $media->remote_media = true; + $media->status_id = $status->id; + $media->profile_id = $status->profile_id; + $media->user_id = null; + $media->media_path = $data['url']; + $media->remote_url = $data['url']; + $media->mime = $data['mediaType']; + $media->version = 3; + $media->order = $key + 1; + } + + /** + * Set optional media attributes + */ + public static function setOptionalMediaAttributes(Media $media, array $data): void + { + $media->blurhash = $data['blurhash'] ?? null; + $media->caption = isset($data['name']) ? + Purify::clean($data['name']) : + null; + + if (isset($data['width'])) { + $media->width = $data['width']; } - $host = parse_url($url, PHP_URL_HOST); - $local = config('pixelfed.domain.app') == $host ? true : false; - - if ($local == true) { - $id = last(explode('/', $url)); - - return Profile::whereNull('status') - ->whereNull('domain') - ->whereUsername($id) - ->firstOrFail(); + if (isset($data['height'])) { + $media->height = $data['height']; } - if ($profile = Profile::whereRemoteUrl($url)->first()) { - if ($profile->last_fetched_at && $profile->last_fetched_at->lt(now()->subHours(24))) { - return self::profileUpdateOrCreate($url); + if (isset($data['license'])) { + $media->license = License::nameToId($data['license']); + } + } + + /** + * Handle media storage processing + */ + public static function handleMediaStorage(Media $media): void + { + if ((bool) config_cache('pixelfed.cloud_storage')) { + MediaStoragePipeline::dispatch($media); + } + } + + /** + * Validate attachment collection + */ + public static function validateAttachmentCollection(array $attachments, array $mediaTypes, array $mimeTypes): bool + { + return Validator::make($attachments, [ + '*.type' => [ + 'required', + 'string', + Rule::in($mediaTypes), + ], + '*.url' => 'required|url', + '*.mediaType' => [ + 'required', + 'string', + Rule::in($mimeTypes), + ], + '*.name' => 'sometimes|nullable|string', + '*.blurhash' => 'sometimes|nullable|string|min:6|max:164', + '*.width' => 'sometimes|nullable|integer|min:1|max:5000', + '*.height' => 'sometimes|nullable|integer|min:1|max:5000', + ])->passes(); + } + + /** + * Get supported media types + */ + public static function getSupportedMediaTypes(): array + { + $mimeTypes = explode(',', config_cache('pixelfed.media_types')); + + return in_array('video/mp4', $mimeTypes) ? + ['Document', 'Image', 'Video'] : + ['Document', 'Image']; + } + + /** + * Process specific media type attachment + */ + public static function processMediaTypeAttachment(array $media, Status $status, int $order): ?Media + { + if (! self::isValidMediaType($media)) { + return null; + } + + $mediaModel = new Media; + self::setMediaAttributes($mediaModel, $media, $status, $order); + $mediaModel->save(); + + return $mediaModel; + } + + /** + * Validate media type + */ + public static function isValidMediaType(array $media): bool + { + $requiredFields = ['mediaType', 'url']; + + foreach ($requiredFields as $field) { + if (! isset($media[$field]) || empty($media[$field])) { + return false; } + } + return true; + } + + /** + * Set media attributes + */ + public static function setMediaAttributes(Media $media, array $data, Status $status, int $order): void + { + $media->remote_media = true; + $media->status_id = $status->id; + $media->profile_id = $status->profile_id; + $media->user_id = null; + $media->media_path = $data['url']; + $media->remote_url = $data['url']; + $media->mime = $data['mediaType']; + $media->version = 3; + $media->order = $order; + + // Optional attributes + if (isset($data['blurhash'])) { + $media->blurhash = $data['blurhash']; + } + + if (isset($data['name'])) { + $media->caption = Purify::clean($data['name']); + } + + if (isset($data['width'])) { + $media->width = $data['width']; + } + + if (isset($data['height'])) { + $media->height = $data['height']; + } + + if (isset($data['license'])) { + $media->license = License::nameToId($data['license']); + } + } + + /** + * Fetch or create a profile from a URL + */ + public static function profileFirstOrNew(string $url): ?Profile + { + if (! $validatedUrl = self::validateUrl($url)) { + return null; + } + + $host = parse_url($validatedUrl, PHP_URL_HOST); + + if (self::isLocalDomain($host)) { + return self::getLocalProfile($validatedUrl); + } + + return self::getOrFetchRemoteProfile($validatedUrl); + } + + /** + * Check if domain is local + */ + public static function isLocalDomain(string $host): bool + { + return config('pixelfed.domain.app') == $host; + } + + /** + * Get local profile from URL + */ + public static function getLocalProfile(string $url): ?Profile + { + $username = last(explode('/', $url)); + + return Profile::whereNull('status') + ->whereNull('domain') + ->whereUsername($username) + ->firstOrFail(); + } + + /** + * Get existing or fetch new remote profile + */ + public static function getOrFetchRemoteProfile(string $url): ?Profile + { + $profile = Profile::whereRemoteUrl($url)->first(); + + if ($profile && ! self::needsFetch($profile)) { return $profile; } return self::profileUpdateOrCreate($url); } - public static function profileUpdateOrCreate($url, $movedToCheck = false) + /** + * Check if profile needs to be fetched + */ + public static function needsFetch(?Profile $profile): bool + { + return ! $profile?->last_fetched_at || + $profile->last_fetched_at->lt(now()->subHours(24)); + } + + /** + * Update or create a profile from ActivityPub data + */ + public static function profileUpdateOrCreate(string $url, bool $movedToCheck = false): ?Profile { - $movedToPid = null; $res = self::fetchProfileFromUrl($url); - if (! $res || isset($res['id']) == false) { - return; - } - if (! self::validateUrl($res['inbox'])) { - return; - } - if (! self::validateUrl($res['id'])) { - return; + + if (! $res || ! self::isValidProfileData($res, $url)) { + return null; } - if (ModeratedProfile::whereProfileUrl($res['id'])->whereIsBanned(true)->exists()) { - return; - } - - $urlDomain = parse_url($url, PHP_URL_HOST); $domain = parse_url($res['id'], PHP_URL_HOST); - if (strtolower($urlDomain) !== strtolower($domain)) { - return; + $username = self::extractUsername($res); + + if (! $username || self::isProfileBanned($res['id'])) { + return null; } - if (! isset($res['preferredUsername']) && ! isset($res['nickname'])) { - return; - } - // skip invalid usernames - if (! ctype_alnum($res['preferredUsername'])) { - $tmpUsername = str_replace(['_', '.', '-'], '', $res['preferredUsername']); - if (! ctype_alnum($tmpUsername)) { - return; - } - } - $username = (string) Purify::clean($res['preferredUsername'] ?? $res['nickname']); - if (empty($username)) { - return; - } - $remoteUsername = $username; + $webfinger = "@{$username}@{$domain}"; - - $instance = Instance::updateOrCreate([ - 'domain' => $domain, - ]); - if ($instance->wasRecentlyCreated == true) { - \App\Jobs\InstancePipeline\FetchNodeinfoPipeline::dispatch($instance)->onQueue('low'); - } - - if (! $movedToCheck && isset($res['movedTo']) && Helpers::validateUrl($res['movedTo'])) { - $movedTo = self::profileUpdateOrCreate($res['movedTo'], true); - if ($movedTo) { - $movedToPid = $movedTo->id; - } - } + $instance = self::getOrCreateInstance($domain); + $movedToPid = $movedToCheck ? null : self::handleMovedTo($res); $profile = Profile::updateOrCreate( [ 'domain' => strtolower($domain), 'username' => Purify::clean($webfinger), ], - [ - 'webfinger' => Purify::clean($webfinger), - 'key_id' => $res['publicKey']['id'], - 'remote_url' => $res['id'], - 'name' => isset($res['name']) ? Purify::clean($res['name']) : 'user', - 'bio' => isset($res['summary']) ? Purify::clean($res['summary']) : null, - 'sharedInbox' => isset($res['endpoints']) && isset($res['endpoints']['sharedInbox']) ? $res['endpoints']['sharedInbox'] : null, - 'inbox_url' => $res['inbox'], - 'outbox_url' => isset($res['outbox']) ? $res['outbox'] : null, - 'public_key' => $res['publicKey']['publicKeyPem'], - 'indexable' => isset($res['indexable']) && is_bool($res['indexable']) ? $res['indexable'] : false, - 'moved_to_profile_id' => $movedToPid, - ] + self::buildProfileData($res, $webfinger, $movedToPid) ); - if ($profile->last_fetched_at == null || - $profile->last_fetched_at->lt(now()->subMonths(3)) - ) { - RemoteAvatarFetch::dispatch($profile); - } - $profile->last_fetched_at = now(); - $profile->save(); + self::handleProfileAvatar($profile); return $profile; } - public static function profileFetch($url) + /** + * Validate profile data from ActivityPub + */ + public static function isValidProfileData(?array $res, string $url): bool + { + if (! $res || ! isset($res['id']) || ! isset($res['inbox'])) { + return false; + } + + if (! self::validateUrl($res['inbox']) || ! self::validateUrl($res['id'])) { + return false; + } + + $urlDomain = parse_url($url, PHP_URL_HOST); + $domain = parse_url($res['id'], PHP_URL_HOST); + + return strtolower($urlDomain) === strtolower($domain); + } + + /** + * Extract username from profile data + */ + public static function extractUsername(array $res): ?string + { + $username = $res['preferredUsername'] ?? $res['nickname'] ?? null; + + if (! $username || ! ctype_alnum(str_replace(['_', '.', '-'], '', $username))) { + return null; + } + + return Purify::clean($username); + } + + /** + * Check if profile is banned + */ + public static function isProfileBanned(string $profileUrl): bool + { + return ModeratedProfile::whereProfileUrl($profileUrl) + ->whereIsBanned(true) + ->exists(); + } + + /** + * Get or create federation instance + */ + public static function getOrCreateInstance(string $domain): Instance + { + $instance = Instance::updateOrCreate(['domain' => $domain]); + + if ($instance->wasRecentlyCreated) { + \App\Jobs\InstancePipeline\FetchNodeinfoPipeline::dispatch($instance) + ->onQueue('low'); + } + + return $instance; + } + + /** + * Handle moved profile references + */ + public static function handleMovedTo(array $res): ?int + { + if (! isset($res['movedTo']) || ! self::validateUrl($res['movedTo'])) { + return null; + } + + $movedTo = self::profileUpdateOrCreate($res['movedTo'], true); + + return $movedTo?->id; + } + + /** + * Build profile data array for database + */ + public static function buildProfileData(array $res, string $webfinger, ?int $movedToPid): array + { + return [ + 'webfinger' => Purify::clean($webfinger), + 'key_id' => $res['publicKey']['id'], + 'remote_url' => $res['id'], + 'name' => isset($res['name']) ? Purify::clean($res['name']) : 'user', + 'bio' => isset($res['summary']) ? Purify::clean($res['summary']) : null, + 'sharedInbox' => $res['endpoints']['sharedInbox'] ?? null, + 'inbox_url' => $res['inbox'], + 'outbox_url' => $res['outbox'] ?? null, + 'public_key' => $res['publicKey']['publicKeyPem'], + 'indexable' => $res['indexable'] ?? false, + 'moved_to_profile_id' => $movedToPid, + ]; + } + + /** + * Handle profile avatar updates + */ + public static function handleProfileAvatar(Profile $profile): void + { + if (! $profile->last_fetched_at || + $profile->last_fetched_at->lt(now()->subMonths(3)) + ) { + RemoteAvatarFetch::dispatch($profile); + } + + $profile->last_fetched_at = now(); + $profile->save(); + } + + public static function profileFetch($url): ?Profile { return self::profileFirstOrNew($url); } diff --git a/app/Util/ActivityPub/HttpSignature.php b/app/Util/ActivityPub/HttpSignature.php index e6834aaef..20071932b 100644 --- a/app/Util/ActivityPub/HttpSignature.php +++ b/app/Util/ActivityPub/HttpSignature.php @@ -71,9 +71,14 @@ class HttpSignature public static function instanceActorSign($url, $body = false, $addlHeaders = [], $method = 'post') { $keyId = config('app.url').'/i/actor#main-key'; - $privateKey = Cache::rememberForever(InstanceActor::PKI_PRIVATE, function () { - return InstanceActor::first()->private_key; - }); + if(config_cache('database.default') === 'mysql') { + $privateKey = Cache::rememberForever(InstanceActor::PKI_PRIVATE, function () { + return InstanceActor::first()->private_key; + }); + } else { + $privateKey = InstanceActor::first()?->private_key; + } + abort_if(!$privateKey || empty($privateKey), 400, 'Missing instance actor key, please run php artisan instance:actor'); if ($body) { $digest = self::_digest($body); } diff --git a/app/Util/ActivityPub/Inbox.php b/app/Util/ActivityPub/Inbox.php index dd9b1578b..e98b48c49 100644 --- a/app/Util/ActivityPub/Inbox.php +++ b/app/Util/ActivityPub/Inbox.php @@ -417,8 +417,8 @@ class Inbox return; } - $msg = $activity['content']; - $msgText = strip_tags($activity['content']); + $msg = Purify::clean($activity['content']); + $msgText = strip_tags($msg); if (Str::startsWith($msgText, '@'.$profile->username)) { $len = strlen('@'.$profile->username); @@ -438,7 +438,6 @@ class Inbox $status = new Status; $status->profile_id = $actor->id; $status->caption = $msgText; - $status->rendered = $msg; $status->visibility = 'direct'; $status->scope = 'direct'; $status->url = $activity['id']; @@ -1081,7 +1080,6 @@ class Inbox $status->uri = $url; $status->object_url = $url; $status->caption = $text; - $status->rendered = $text; $status->scope = 'direct'; $status->visibility = 'direct'; $status->in_reply_to_profile_id = $story->profile_id; @@ -1199,7 +1197,6 @@ class Inbox $status->profile_id = $actorProfile->id; $status->type = 'story:reply'; $status->caption = $text; - $status->rendered = $text; $status->url = $url; $status->uri = $url; $status->object_url = $url; diff --git a/composer.json b/composer.json index 26b975f41..7d3320ba6 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "laravel/helpers": "^1.1", "laravel/horizon": "^5.0", "laravel/passport": "^12.0", + "laravel/pulse": "^1.3", "laravel/tinker": "^2.9", "laravel/ui": "^4.2", "league/flysystem-aws-s3-v3": "^3.0", diff --git a/composer.lock b/composer.lock index 8819e991a..79aaa3a6f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "0035325cb0240e92fc378e49f76447bd", + "content-hash": "3bb2ed96bc8ff080f3415b9bcf6ac307", "packages": [ { "name": "aws/aws-crt-php", @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.325.5", + "version": "3.336.2", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "195d003c902a741de53008c839cbcebddbe1f326" + "reference": "954bfdfc048840ca34afe2a2e1cbcff6681989c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/195d003c902a741de53008c839cbcebddbe1f326", - "reference": "195d003c902a741de53008c839cbcebddbe1f326", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/954bfdfc048840ca34afe2a2e1cbcff6681989c4", + "reference": "954bfdfc048840ca34afe2a2e1cbcff6681989c4", "shasum": "" }, "require": { @@ -100,8 +100,8 @@ "nette/neon": "^2.3", "paragonie/random_compat": ">= 2", "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", - "psr/cache": "^1.0", - "psr/simple-cache": "^1.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "sebastian/comparator": "^1.2.3 || ^4.0", "yoast/phpunit-polyfills": "^1.0" }, @@ -154,9 +154,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.325.5" + "source": "https://github.com/aws/aws-sdk-php/tree/3.336.2" }, - "time": "2024-11-08T19:12:57+00:00" + "time": "2024-12-20T19:05:10+00:00" }, { "name": "bacon/bacon-qr-code", @@ -294,12 +294,12 @@ "type": "library", "extra": { "laravel": { - "providers": [ - "Buzz\\LaravelHCaptcha\\CaptchaServiceProvider" - ], "aliases": { "Captcha": "Buzz\\LaravelHCaptcha\\CaptchaFacade" - } + }, + "providers": [ + "Buzz\\LaravelHCaptcha\\CaptchaServiceProvider" + ] } }, "autoload": { @@ -808,29 +808,27 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "1.4.10 || 1.10.15", - "phpstan/phpstan-phpunit": "^1.0", + "doctrine/coding-standard": "^9 || ^12", + "phpstan/phpstan": "1.4.10 || 2.0.3", + "phpstan/phpstan-phpunit": "^1.0 || ^2", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "0.18.4", - "psr/log": "^1 || ^2 || ^3", - "vimeo/psalm": "4.30.0 || 5.12.0" + "psr/log": "^1 || ^2 || ^3" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -838,7 +836,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + "Doctrine\\Deprecations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -849,9 +847,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + "source": "https://github.com/doctrine/deprecations/tree/1.1.4" }, - "time": "2024-01-30T19:34:25+00:00" + "time": "2024-12-07T21:18:45+00:00" }, { "name": "doctrine/event-manager", @@ -1112,6 +1110,62 @@ ], "time": "2024-02-05T11:56:58+00:00" }, + { + "name": "doctrine/sql-formatter", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/sql-formatter.git", + "reference": "b784cbde727cf806721451dde40eff4fec3bbe86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/b784cbde727cf806721451dde40eff4fec3bbe86", + "reference": "b784cbde727cf806721451dde40eff4fec3bbe86", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "ergebnis/phpunit-slow-test-detector": "^2.14", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.24" + }, + "bin": [ + "bin/sql-formatter" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\SqlFormatter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Dorn", + "email": "jeremy@jeremydorn.com", + "homepage": "https://jeremydorn.com/" + } + ], + "description": "a PHP SQL highlighting library", + "homepage": "https://github.com/doctrine/sql-formatter/", + "keywords": [ + "highlight", + "sql" + ], + "support": { + "issues": "https://github.com/doctrine/sql-formatter/issues", + "source": "https://github.com/doctrine/sql-formatter/tree/1.5.1" + }, + "time": "2024-10-21T18:21:57+00:00" + }, { "name": "dragonmantank/cron-expression", "version": "v3.4.0", @@ -1430,16 +1484,16 @@ }, { "name": "firebase/php-jwt", - "version": "v6.10.1", + "version": "v6.10.2", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "500501c2ce893c824c801da135d02661199f60c5" + "reference": "30c19ed0f3264cb660ea496895cfb6ef7ee3653b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/500501c2ce893c824c801da135d02661199f60c5", - "reference": "500501c2ce893c824c801da135d02661199f60c5", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/30c19ed0f3264cb660ea496895cfb6ef7ee3653b", + "reference": "30c19ed0f3264cb660ea496895cfb6ef7ee3653b", "shasum": "" }, "require": { @@ -1487,9 +1541,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.10.1" + "source": "https://github.com/firebase/php-jwt/tree/v6.10.2" }, - "time": "2024-05-18T18:05:11+00:00" + "time": "2024-11-24T11:22:49+00:00" }, { "name": "fruitcake/php-cors", @@ -2065,16 +2119,16 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.4-dev" - }, "laravel": { - "providers": [ - "Intervention\\Image\\ImageServiceProvider" - ], "aliases": { "Image": "Intervention\\Image\\Facades\\Image" - } + }, + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "2.4-dev" } }, "autoload": { @@ -2121,20 +2175,20 @@ }, { "name": "jaybizzle/crawler-detect", - "version": "v1.2.121", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/JayBizzle/Crawler-Detect.git", - "reference": "40ecda6322d4163fe2c6e1dd47c574f580b8487f" + "reference": "be155e11613fa618aa18aee438955588d1092a47" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/40ecda6322d4163fe2c6e1dd47c574f580b8487f", - "reference": "40ecda6322d4163fe2c6e1dd47c574f580b8487f", + "url": "https://api.github.com/repos/JayBizzle/Crawler-Detect/zipball/be155e11613fa618aa18aee438955588d1092a47", + "reference": "be155e11613fa618aa18aee438955588d1092a47", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=7.1.0" }, "require-dev": { "phpunit/phpunit": "^4.8|^5.5|^6.5|^9.4" @@ -2167,9 +2221,9 @@ ], "support": { "issues": "https://github.com/JayBizzle/Crawler-Detect/issues", - "source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.2.121" + "source": "https://github.com/JayBizzle/Crawler-Detect/tree/v1.3.0" }, - "time": "2024-10-20T21:42:39+00:00" + "time": "2024-11-25T19:38:36+00:00" }, { "name": "jenssegers/agent", @@ -2378,23 +2432,23 @@ }, { "name": "laravel/framework", - "version": "v11.30.0", + "version": "v11.36.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "dff716442d9c229d716be82ccc9a7de52eb97193" + "reference": "df06f5163f4550641fdf349ebc04916a61135a64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/dff716442d9c229d716be82ccc9a7de52eb97193", - "reference": "dff716442d9c229d716be82ccc9a7de52eb97193", + "url": "https://api.github.com/repos/laravel/framework/zipball/df06f5163f4550641fdf349ebc04916a61135a64", + "reference": "df06f5163f4550641fdf349ebc04916a61135a64", "shasum": "" }, "require": { "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", - "dragonmantank/cron-expression": "^3.3.2", + "dragonmantank/cron-expression": "^3.4", "egulias/email-validator": "^3.2.1|^4.0", "ext-ctype": "*", "ext-filter": "*", @@ -2404,35 +2458,37 @@ "ext-session": "*", "ext-tokenizer": "*", "fruitcake/php-cors": "^1.3", - "guzzlehttp/guzzle": "^7.8", + "guzzlehttp/guzzle": "^7.8.2", "guzzlehttp/uri-template": "^1.0", "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", - "laravel/serializable-closure": "^1.3", - "league/commonmark": "^2.2.1", - "league/flysystem": "^3.8.0", + "laravel/serializable-closure": "^1.3|^2.0", + "league/commonmark": "^2.6", + "league/flysystem": "^3.25.1", + "league/flysystem-local": "^3.25.1", + "league/uri": "^7.5.1", "monolog/monolog": "^3.0", - "nesbot/carbon": "^2.72.2|^3.0", + "nesbot/carbon": "^2.72.2|^3.4", "nunomaduro/termwind": "^2.0", "php": "^8.2", "psr/container": "^1.1.1|^2.0.1", "psr/log": "^1.0|^2.0|^3.0", "psr/simple-cache": "^1.0|^2.0|^3.0", "ramsey/uuid": "^4.7", - "symfony/console": "^7.0", - "symfony/error-handler": "^7.0", - "symfony/finder": "^7.0", - "symfony/http-foundation": "^7.0", - "symfony/http-kernel": "^7.0", - "symfony/mailer": "^7.0", - "symfony/mime": "^7.0", - "symfony/polyfill-php83": "^1.28", - "symfony/process": "^7.0", - "symfony/routing": "^7.0", - "symfony/uid": "^7.0", - "symfony/var-dumper": "^7.0", + "symfony/console": "^7.0.3", + "symfony/error-handler": "^7.0.3", + "symfony/finder": "^7.0.3", + "symfony/http-foundation": "^7.2.0", + "symfony/http-kernel": "^7.0.3", + "symfony/mailer": "^7.0.3", + "symfony/mime": "^7.0.3", + "symfony/polyfill-php83": "^1.31", + "symfony/process": "^7.0.3", + "symfony/routing": "^7.0.3", + "symfony/uid": "^7.0.3", + "symfony/var-dumper": "^7.0.3", "tijsverkoyen/css-to-inline-styles": "^2.2.5", - "vlucas/phpdotenv": "^5.4.1", - "voku/portable-ascii": "^2.0" + "vlucas/phpdotenv": "^5.6.1", + "voku/portable-ascii": "^2.0.2" }, "conflict": { "mockery/mockery": "1.6.8", @@ -2482,29 +2538,32 @@ }, "require-dev": { "ably/ably-php": "^1.0", - "aws/aws-sdk-php": "^3.235.5", + "aws/aws-sdk-php": "^3.322.9", "ext-gmp": "*", - "fakerphp/faker": "^1.23", - "league/flysystem-aws-s3-v3": "^3.0", - "league/flysystem-ftp": "^3.0", - "league/flysystem-path-prefixing": "^3.3", - "league/flysystem-read-only": "^3.3", - "league/flysystem-sftp-v3": "^3.0", - "mockery/mockery": "^1.6", - "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "^9.5", - "pda/pheanstalk": "^5.0", + "fakerphp/faker": "^1.24", + "guzzlehttp/promises": "^2.0.3", + "guzzlehttp/psr7": "^2.4", + "league/flysystem-aws-s3-v3": "^3.25.1", + "league/flysystem-ftp": "^3.25.1", + "league/flysystem-path-prefixing": "^3.25.1", + "league/flysystem-read-only": "^3.25.1", + "league/flysystem-sftp-v3": "^3.25.1", + "mockery/mockery": "^1.6.10", + "orchestra/testbench-core": "^9.6", + "pda/pheanstalk": "^5.0.6", + "php-http/discovery": "^1.15", "phpstan/phpstan": "^1.11.5", - "phpunit/phpunit": "^10.5|^11.0", - "predis/predis": "^2.0.2", + "phpunit/phpunit": "^10.5.35|^11.3.6", + "predis/predis": "^2.3", "resend/resend-php": "^0.10.0", - "symfony/cache": "^7.0", - "symfony/http-client": "^7.0", - "symfony/psr-http-message-bridge": "^7.0" + "symfony/cache": "^7.0.3", + "symfony/http-client": "^7.0.3", + "symfony/psr-http-message-bridge": "^7.0.3", + "symfony/translation": "^7.0.3" }, "suggest": { "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", - "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).", "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", "ext-apcu": "Required to use the APC cache driver.", "ext-fileinfo": "Required to use the Filesystem class.", @@ -2518,16 +2577,16 @@ "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", "laravel/tinker": "Required to use the tinker console command (^2.0).", - "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).", - "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", - "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.3).", - "league/flysystem-read-only": "Required to use read-only disks (^3.3)", - "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.25.1).", + "league/flysystem-read-only": "Required to use read-only disks (^3.25.1)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", "mockery/mockery": "Required to use mocking (^1.6).", - "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", + "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", "phpunit/phpunit": "Required to use assertions and run tests (^10.5|^11.0).", - "predis/predis": "Required to use the predis connector (^2.0.2).", + "predis/predis": "Required to use the predis connector (^2.3).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", @@ -2546,6 +2605,7 @@ }, "autoload": { "files": [ + "src/Illuminate/Collections/functions.php", "src/Illuminate/Collections/helpers.php", "src/Illuminate/Events/functions.php", "src/Illuminate/Filesystem/functions.php", @@ -2583,20 +2643,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-10-30T15:00:34+00:00" + "time": "2024-12-17T22:32:08+00:00" }, { "name": "laravel/helpers", - "version": "v1.7.0", + "version": "v1.7.1", "source": { "type": "git", "url": "https://github.com/laravel/helpers.git", - "reference": "6caaa242a23bc39b4e3cf57304b5409260a7a346" + "reference": "f28907033d7edf8a0525cfb781ab30ce6d531c35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/helpers/zipball/6caaa242a23bc39b4e3cf57304b5409260a7a346", - "reference": "6caaa242a23bc39b4e3cf57304b5409260a7a346", + "url": "https://api.github.com/repos/laravel/helpers/zipball/f28907033d7edf8a0525cfb781ab30ce6d531c35", + "reference": "f28907033d7edf8a0525cfb781ab30ce6d531c35", "shasum": "" }, "require": { @@ -2638,22 +2698,22 @@ "laravel" ], "support": { - "source": "https://github.com/laravel/helpers/tree/v1.7.0" + "source": "https://github.com/laravel/helpers/tree/v1.7.1" }, - "time": "2023-11-30T14:09:05+00:00" + "time": "2024-11-26T14:56:25+00:00" }, { "name": "laravel/horizon", - "version": "v5.29.2", + "version": "v5.30.1", "source": { "type": "git", "url": "https://github.com/laravel/horizon.git", - "reference": "d9c39ce4e9050b33a2ff9d2cee21646a18d4cc92" + "reference": "77177646679ef2f2acf71d4d4b16036d18002040" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/horizon/zipball/d9c39ce4e9050b33a2ff9d2cee21646a18d4cc92", - "reference": "d9c39ce4e9050b33a2ff9d2cee21646a18d4cc92", + "url": "https://api.github.com/repos/laravel/horizon/zipball/77177646679ef2f2acf71d4d4b16036d18002040", + "reference": "77177646679ef2f2acf71d4d4b16036d18002040", "shasum": "" }, "require": { @@ -2684,16 +2744,16 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - }, "laravel": { - "providers": [ - "Laravel\\Horizon\\HorizonServiceProvider" - ], "aliases": { "Horizon": "Laravel\\Horizon\\Horizon" - } + }, + "providers": [ + "Laravel\\Horizon\\HorizonServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "5.x-dev" } }, "autoload": { @@ -2718,22 +2778,22 @@ ], "support": { "issues": "https://github.com/laravel/horizon/issues", - "source": "https://github.com/laravel/horizon/tree/v5.29.2" + "source": "https://github.com/laravel/horizon/tree/v5.30.1" }, - "time": "2024-10-16T21:36:57+00:00" + "time": "2024-12-13T14:08:51+00:00" }, { "name": "laravel/passport", - "version": "v12.3.0", + "version": "v12.3.1", "source": { "type": "git", "url": "https://github.com/laravel/passport.git", - "reference": "ca63a86697a4fa091c7dcabe88ebba91d97c785d" + "reference": "0d95ca9cc9c80bdf64d04dcf04542720e3d5d55c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/passport/zipball/ca63a86697a4fa091c7dcabe88ebba91d97c785d", - "reference": "ca63a86697a4fa091c7dcabe88ebba91d97c785d", + "url": "https://api.github.com/repos/laravel/passport/zipball/0d95ca9cc9c80bdf64d04dcf04542720e3d5d55c", + "reference": "0d95ca9cc9c80bdf64d04dcf04542720e3d5d55c", "shasum": "" }, "require": { @@ -2796,20 +2856,20 @@ "issues": "https://github.com/laravel/passport/issues", "source": "https://github.com/laravel/passport" }, - "time": "2024-08-05T13:44:51+00:00" + "time": "2024-11-11T20:15:28+00:00" }, { "name": "laravel/prompts", - "version": "v0.3.1", + "version": "v0.3.2", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "0f3848a445562dac376b27968f753c65e7e1036e" + "reference": "0e0535747c6b8d6d10adca8b68293cf4517abb0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/0f3848a445562dac376b27968f753c65e7e1036e", - "reference": "0f3848a445562dac376b27968f753c65e7e1036e", + "url": "https://api.github.com/repos/laravel/prompts/zipball/0e0535747c6b8d6d10adca8b68293cf4517abb0f", + "reference": "0e0535747c6b8d6d10adca8b68293cf4517abb0f", "shasum": "" }, "require": { @@ -2825,7 +2885,7 @@ "require-dev": { "illuminate/collections": "^10.0|^11.0", "mockery/mockery": "^1.5", - "pestphp/pest": "^2.3", + "pestphp/pest": "^2.3|^3.4", "phpstan/phpstan": "^1.11", "phpstan/phpstan-mockery": "^1.1" }, @@ -2853,38 +2913,125 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.3.1" + "source": "https://github.com/laravel/prompts/tree/v0.3.2" }, - "time": "2024-10-09T19:42:26+00:00" + "time": "2024-11-12T14:59:47+00:00" }, { - "name": "laravel/serializable-closure", - "version": "v1.3.5", + "name": "laravel/pulse", + "version": "v1.3.2", "source": { "type": "git", - "url": "https://github.com/laravel/serializable-closure.git", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c" + "url": "https://github.com/laravel/pulse.git", + "reference": "f0bf3959faa89c05fa211632b6d2665131b017fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", + "url": "https://api.github.com/repos/laravel/pulse/zipball/f0bf3959faa89c05fa211632b6d2665131b017fc", + "reference": "f0bf3959faa89c05fa211632b6d2665131b017fc", "shasum": "" }, "require": { - "php": "^7.3|^8.0" + "doctrine/sql-formatter": "^1.4.1", + "guzzlehttp/promises": "^1.0|^2.0", + "illuminate/auth": "^10.48.4|^11.0.8", + "illuminate/cache": "^10.48.4|^11.0.8", + "illuminate/config": "^10.48.4|^11.0.8", + "illuminate/console": "^10.48.4|^11.0.8", + "illuminate/contracts": "^10.48.4|^11.0.8", + "illuminate/database": "^10.48.4|^11.0.8", + "illuminate/events": "^10.48.4|^11.0.8", + "illuminate/http": "^10.48.4|^11.0.8", + "illuminate/queue": "^10.48.4|^11.0.8", + "illuminate/redis": "^10.48.4|^11.0.8", + "illuminate/routing": "^10.48.4|^11.0.8", + "illuminate/support": "^10.48.4|^11.0.8", + "illuminate/view": "^10.48.4|^11.0.8", + "livewire/livewire": "^3.4.9", + "nesbot/carbon": "^2.67|^3.0", + "php": "^8.1", + "symfony/console": "^6.0|^7.0" + }, + "conflict": { + "nunomaduro/collision": "<7.7.0" }, "require-dev": { - "illuminate/support": "^8.0|^9.0|^10.0|^11.0", - "nesbot/carbon": "^2.61|^3.0", - "pestphp/pest": "^1.21.3", - "phpstan/phpstan": "^1.8.2", - "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" + "guzzlehttp/guzzle": "^7.7", + "mockery/mockery": "^1.0", + "orchestra/testbench": "^8.23.1|^9.0", + "pestphp/pest": "^2.0", + "pestphp/pest-plugin-laravel": "^2.2", + "phpstan/phpstan": "^1.11", + "predis/predis": "^1.0|^2.0" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Pulse": "Laravel\\Pulse\\Facades\\Pulse" + }, + "providers": [ + "Laravel\\Pulse\\PulseServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Laravel\\Pulse\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "Laravel Pulse is a real-time application performance monitoring tool and dashboard for your Laravel application.", + "homepage": "https://github.com/laravel/pulse", + "keywords": [ + "laravel" + ], + "support": { + "issues": "https://github.com/laravel/pulse/issues", + "source": "https://github.com/laravel/pulse" + }, + "time": "2024-12-12T18:17:53+00:00" + }, + { + "name": "laravel/serializable-closure", + "version": "v2.0.1", + "source": { + "type": "git", + "url": "https://github.com/laravel/serializable-closure.git", + "reference": "613b2d4998f85564d40497e05e89cb6d9bd1cbe8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/613b2d4998f85564d40497e05e89cb6d9bd1cbe8", + "reference": "613b2d4998f85564d40497e05e89cb6d9bd1cbe8", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "illuminate/support": "^10.0|^11.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -2916,7 +3063,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2024-09-23T13:33:08+00:00" + "time": "2024-12-16T15:26:28+00:00" }, { "name": "laravel/tinker", @@ -2986,16 +3133,16 @@ }, { "name": "laravel/ui", - "version": "v4.5.2", + "version": "v4.6.0", "source": { "type": "git", "url": "https://github.com/laravel/ui.git", - "reference": "c75396f63268c95b053c8e4814eb70e0875e9628" + "reference": "a34609b15ae0c0512a0cf47a21695a2729cb7f93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/c75396f63268c95b053c8e4814eb70e0875e9628", - "reference": "c75396f63268c95b053c8e4814eb70e0875e9628", + "url": "https://api.github.com/repos/laravel/ui/zipball/a34609b15ae0c0512a0cf47a21695a2729cb7f93", + "reference": "a34609b15ae0c0512a0cf47a21695a2729cb7f93", "shasum": "" }, "require": { @@ -3043,9 +3190,9 @@ "ui" ], "support": { - "source": "https://github.com/laravel/ui/tree/v4.5.2" + "source": "https://github.com/laravel/ui/tree/v4.6.0" }, - "time": "2024-05-08T18:07:10+00:00" + "time": "2024-11-21T15:06:41+00:00" }, { "name": "lcobucci/clock", @@ -3186,16 +3333,16 @@ }, { "name": "league/commonmark", - "version": "2.5.3", + "version": "2.6.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "b650144166dfa7703e62a22e493b853b58d874b0" + "reference": "d150f911e0079e90ae3c106734c93137c184f932" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0", - "reference": "b650144166dfa7703e62a22e493b853b58d874b0", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d150f911e0079e90ae3c106734c93137c184f932", + "reference": "d150f911e0079e90ae3c106734c93137c184f932", "shasum": "" }, "require": { @@ -3220,8 +3367,9 @@ "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0 || ^7.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 || ^7.0", + "symfony/finder": "^5.3 | ^6.0 | ^7.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", "vimeo/psalm": "^4.24.0 || ^5.0.0" }, @@ -3231,7 +3379,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.6-dev" + "dev-main": "2.7-dev" } }, "autoload": { @@ -3288,7 +3436,7 @@ "type": "tidelift" } ], - "time": "2024-08-16T11:46:16+00:00" + "time": "2024-12-07T15:34:16+00:00" }, { "name": "league/config", @@ -3738,16 +3886,16 @@ }, { "name": "league/oauth2-server", - "version": "8.5.4", + "version": "8.5.5", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth2-server.git", - "reference": "ab7714d073844497fd222d5d0a217629089936bc" + "reference": "cc8778350f905667e796b3c2364a9d3bd7a73518" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/ab7714d073844497fd222d5d0a217629089936bc", - "reference": "ab7714d073844497fd222d5d0a217629089936bc", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/cc8778350f905667e796b3c2364a9d3bd7a73518", + "reference": "cc8778350f905667e796b3c2364a9d3bd7a73518", "shasum": "" }, "require": { @@ -3814,7 +3962,7 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth2-server/issues", - "source": "https://github.com/thephpleague/oauth2-server/tree/8.5.4" + "source": "https://github.com/thephpleague/oauth2-server/tree/8.5.5" }, "funding": [ { @@ -3822,24 +3970,24 @@ "type": "github" } ], - "time": "2023-08-25T22:35:12+00:00" + "time": "2024-12-20T23:06:10+00:00" }, { "name": "league/uri", - "version": "7.4.1", + "version": "7.5.1", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "bedb6e55eff0c933668addaa7efa1e1f2c417cc4" + "reference": "81fb5145d2644324614cc532b28efd0215bda430" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/bedb6e55eff0c933668addaa7efa1e1f2c417cc4", - "reference": "bedb6e55eff0c933668addaa7efa1e1f2c417cc4", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", + "reference": "81fb5145d2644324614cc532b28efd0215bda430", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.3", + "league/uri-interfaces": "^7.5", "php": "^8.1" }, "conflict": { @@ -3904,7 +4052,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.4.1" + "source": "https://github.com/thephpleague/uri/tree/7.5.1" }, "funding": [ { @@ -3912,20 +4060,20 @@ "type": "github" } ], - "time": "2024-03-23T07:42:40+00:00" + "time": "2024-12-08T08:40:02+00:00" }, { "name": "league/uri-interfaces", - "version": "7.4.1", + "version": "7.5.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "8d43ef5c841032c87e2de015972c06f3865ef718" + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/8d43ef5c841032c87e2de015972c06f3865ef718", - "reference": "8d43ef5c841032c87e2de015972c06f3865ef718", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", "shasum": "" }, "require": { @@ -3988,7 +4136,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.4.1" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" }, "funding": [ { @@ -3996,7 +4144,83 @@ "type": "github" } ], - "time": "2024-03-23T07:42:40+00:00" + "time": "2024-12-08T08:18:47+00:00" + }, + { + "name": "livewire/livewire", + "version": "v3.5.18", + "source": { + "type": "git", + "url": "https://github.com/livewire/livewire.git", + "reference": "62f0fa6b340a467c25baa590a567d9a134b357da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/livewire/livewire/zipball/62f0fa6b340a467c25baa590a567d9a134b357da", + "reference": "62f0fa6b340a467c25baa590a567d9a134b357da", + "shasum": "" + }, + "require": { + "illuminate/database": "^10.0|^11.0", + "illuminate/routing": "^10.0|^11.0", + "illuminate/support": "^10.0|^11.0", + "illuminate/validation": "^10.0|^11.0", + "laravel/prompts": "^0.1.24|^0.2|^0.3", + "league/mime-type-detection": "^1.9", + "php": "^8.1", + "symfony/console": "^6.0|^7.0", + "symfony/http-kernel": "^6.2|^7.0" + }, + "require-dev": { + "calebporzio/sushi": "^2.1", + "laravel/framework": "^10.15.0|^11.0", + "mockery/mockery": "^1.3.1", + "orchestra/testbench": "^8.21.0|^9.0", + "orchestra/testbench-dusk": "^8.24|^9.1", + "phpunit/phpunit": "^10.4", + "psy/psysh": "^0.11.22|^0.12" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "Livewire": "Livewire\\Livewire" + }, + "providers": [ + "Livewire\\LivewireServiceProvider" + ] + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Livewire\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Caleb Porzio", + "email": "calebporzio@gmail.com" + } + ], + "description": "A front-end framework for Laravel.", + "support": { + "issues": "https://github.com/livewire/livewire/issues", + "source": "https://github.com/livewire/livewire/tree/v3.5.18" + }, + "funding": [ + { + "url": "https://github.com/livewire", + "type": "github" + } + ], + "time": "2024-12-23T15:05:02+00:00" }, { "name": "minishlink/web-push", @@ -4129,16 +4353,16 @@ }, { "name": "monolog/monolog", - "version": "3.7.0", + "version": "3.8.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8" + "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8", - "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/aef6ee73a77a66e404dd6540934a9ef1b3c855b4", + "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4", "shasum": "" }, "require": { @@ -4158,12 +4382,14 @@ "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "^10.5.17", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", "predis/predis": "^1.1 || ^2", - "ruflin/elastica": "^7", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", "symfony/mailer": "^5.4 || ^6", "symfony/mime": "^5.4 || ^6" }, @@ -4214,7 +4440,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.7.0" + "source": "https://github.com/Seldaek/monolog/tree/3.8.1" }, "funding": [ { @@ -4226,7 +4452,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:40:51+00:00" + "time": "2024-12-05T17:15:07+00:00" }, { "name": "mtdowling/jmespath.php", @@ -4337,10 +4563,6 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.x-dev", - "dev-2.x": "2.x-dev" - }, "laravel": { "providers": [ "Carbon\\Laravel\\ServiceProvider" @@ -4350,6 +4572,10 @@ "includes": [ "extension.neon" ] + }, + "branch-alias": { + "dev-2.x": "2.x-dev", + "dev-master": "3.x-dev" } }, "autoload": { @@ -4608,31 +4834,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "42c84e4e8090766bbd6445d06cd6e57650626ea3" + "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/42c84e4e8090766bbd6445d06cd6e57650626ea3", - "reference": "42c84e4e8090766bbd6445d06cd6e57650626ea3", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/52915afe6a1044e8b9cee1bcff836fb63acf9cda", + "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.1.5" + "symfony/console": "^7.1.8" }, "require-dev": { - "illuminate/console": "^11.28.0", - "laravel/pint": "^1.18.1", + "illuminate/console": "^11.33.2", + "laravel/pint": "^1.18.2", "mockery/mockery": "^1.6.12", "pestphp/pest": "^2.36.0", - "phpstan/phpstan": "^1.12.6", + "phpstan/phpstan": "^1.12.11", "phpstan/phpstan-strict-rules": "^1.6.1", - "symfony/var-dumper": "^7.1.5", + "symfony/var-dumper": "^7.1.8", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -4675,7 +4901,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.2.0" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.0" }, "funding": [ { @@ -4691,7 +4917,7 @@ "type": "github" } ], - "time": "2024-10-15T16:15:16+00:00" + "time": "2024-11-21T10:39:51+00:00" }, { "name": "nyholm/psr7", @@ -4981,21 +5207,21 @@ }, { "name": "pbmedia/laravel-ffmpeg", - "version": "8.5.0", + "version": "8.6.0", "source": { "type": "git", "url": "https://github.com/protonemedia/laravel-ffmpeg.git", - "reference": "44f260839e68ce8c785d502f99b998729cdb5321" + "reference": "f14efc53e8a52b53a237a9910b32e795dafcf8bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/protonemedia/laravel-ffmpeg/zipball/44f260839e68ce8c785d502f99b998729cdb5321", - "reference": "44f260839e68ce8c785d502f99b998729cdb5321", + "url": "https://api.github.com/repos/protonemedia/laravel-ffmpeg/zipball/f14efc53e8a52b53a237a9910b32e795dafcf8bc", + "reference": "f14efc53e8a52b53a237a9910b32e795dafcf8bc", "shasum": "" }, "require": { "illuminate/contracts": "^10.0|^11.0", - "php": "^8.1|^8.2|^8.3", + "php": "^8.1|^8.2|^8.3|^8.4", "php-ffmpeg/php-ffmpeg": "^1.2", "ramsey/collection": "^2.0" }, @@ -5011,12 +5237,12 @@ "type": "library", "extra": { "laravel": { - "providers": [ - "ProtoneMedia\\LaravelFFMpeg\\Support\\ServiceProvider" - ], "aliases": { "FFMpeg": "ProtoneMedia\\LaravelFFMpeg\\Support\\FFMpeg" - } + }, + "providers": [ + "ProtoneMedia\\LaravelFFMpeg\\Support\\ServiceProvider" + ] } }, "autoload": { @@ -5047,7 +5273,7 @@ ], "support": { "issues": "https://github.com/protonemedia/laravel-ffmpeg/issues", - "source": "https://github.com/protonemedia/laravel-ffmpeg/tree/8.5.0" + "source": "https://github.com/protonemedia/laravel-ffmpeg/tree/8.6.0" }, "funding": [ { @@ -5055,25 +5281,25 @@ "type": "github" } ], - "time": "2024-03-12T11:20:32+00:00" + "time": "2024-11-12T16:12:23+00:00" }, { "name": "php-ffmpeg/php-ffmpeg", - "version": "v1.2.0", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/PHP-FFMpeg/PHP-FFMpeg.git", - "reference": "785a5ba05dd88b3b8146f85f18476b259b23917c" + "reference": "5e7b15710a8607e8a3a2d9fbe2c150a99b924fa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-FFMpeg/PHP-FFMpeg/zipball/785a5ba05dd88b3b8146f85f18476b259b23917c", - "reference": "785a5ba05dd88b3b8146f85f18476b259b23917c", + "url": "https://api.github.com/repos/PHP-FFMpeg/PHP-FFMpeg/zipball/5e7b15710a8607e8a3a2d9fbe2c150a99b924fa5", + "reference": "5e7b15710a8607e8a3a2d9fbe2c150a99b924fa5", "shasum": "" }, "require": { "evenement/evenement": "^3.0", - "php": "^8.0 || ^8.1 || ^8.2 || ^8.3", + "php": "^8.0 || ^8.1 || ^8.2 || ^8.3 || ^8.4", "psr/log": "^1.0 || ^2.0 || ^3.0", "spatie/temporary-directory": "^2.0", "symfony/cache": "^5.4 || ^6.0 || ^7.0", @@ -5081,7 +5307,7 @@ }, "require-dev": { "mockery/mockery": "^1.5", - "phpunit/phpunit": "^9.5.10" + "phpunit/phpunit": "^9.5.10 || ^10.0" }, "suggest": { "php-ffmpeg/extras": "A compilation of common audio & video drivers for PHP-FFMpeg" @@ -5142,9 +5368,9 @@ ], "support": { "issues": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/issues", - "source": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/tree/v1.2.0" + "source": "https://github.com/PHP-FFMpeg/PHP-FFMpeg/tree/v1.3.0" }, - "time": "2024-01-02T10:37:01+00:00" + "time": "2024-11-12T15:39:52+00:00" }, { "name": "phpoption/phpoption", @@ -5223,16 +5449,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "2.0.47", + "version": "2.0.48", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "b7d7d90ee7df7f33a664b4aea32d50a305d35adb" + "reference": "eaa7be704b8b93a6913b69eb7f645a59d7731b61" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/b7d7d90ee7df7f33a664b4aea32d50a305d35adb", - "reference": "b7d7d90ee7df7f33a664b4aea32d50a305d35adb", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/eaa7be704b8b93a6913b69eb7f645a59d7731b61", + "reference": "eaa7be704b8b93a6913b69eb7f645a59d7731b61", "shasum": "" }, "require": { @@ -5313,7 +5539,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/2.0.47" + "source": "https://github.com/phpseclib/phpseclib/tree/2.0.48" }, "funding": [ { @@ -5329,7 +5555,7 @@ "type": "tidelift" } ], - "time": "2024-02-26T04:55:38+00:00" + "time": "2024-12-14T21:03:54+00:00" }, { "name": "pixelfed/fractal", @@ -5511,16 +5737,16 @@ }, { "name": "predis/predis", - "version": "v2.2.2", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1" + "reference": "bac46bfdb78cd6e9c7926c697012aae740cb9ec9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1", - "reference": "b1d3255ed9ad4d7254f9f9bba386c99f4bb983d1", + "url": "https://api.github.com/repos/predis/predis/zipball/bac46bfdb78cd6e9c7926c697012aae740cb9ec9", + "reference": "bac46bfdb78cd6e9c7926c697012aae740cb9ec9", "shasum": "" }, "require": { @@ -5529,7 +5755,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^3.3", "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^8.0 || ~9.4.4" + "phpunit/phpunit": "^8.0 || ^9.4" }, "suggest": { "ext-relay": "Faster connection with in-memory caching (>=0.6.2)" @@ -5560,7 +5786,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v2.2.2" + "source": "https://github.com/predis/predis/tree/v2.3.0" }, "funding": [ { @@ -5568,7 +5794,7 @@ "type": "github" } ], - "time": "2023-09-13T16:42:03+00:00" + "time": "2024-11-21T20:00:02+00:00" }, { "name": "psr/cache", @@ -6033,16 +6259,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.4", + "version": "v0.12.7", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "2fd717afa05341b4f8152547f142cd2f130f6818" + "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/2fd717afa05341b4f8152547f142cd2f130f6818", - "reference": "2fd717afa05341b4f8152547f142cd2f130f6818", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", + "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", "shasum": "" }, "require": { @@ -6069,12 +6295,12 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-main": "0.12.x-dev" - }, "bamarni-bin": { "bin-links": false, "forward-command": false + }, + "branch-alias": { + "dev-main": "0.12.x-dev" } }, "autoload": { @@ -6106,9 +6332,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.4" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.7" }, - "time": "2024-06-10T01:18:23+00:00" + "time": "2024-12-10T01:58:33+00:00" }, { "name": "pusher/pusher-php-server", @@ -6455,16 +6681,16 @@ }, { "name": "spatie/db-dumper", - "version": "3.7.0", + "version": "3.7.1", "source": { "type": "git", "url": "https://github.com/spatie/db-dumper.git", - "reference": "22553ab8c34a9bb70645cb9bc2d9f236f3135705" + "reference": "55d4d6710e1ab18c1e7ce2b22b8ad4bea2a30016" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/db-dumper/zipball/22553ab8c34a9bb70645cb9bc2d9f236f3135705", - "reference": "22553ab8c34a9bb70645cb9bc2d9f236f3135705", + "url": "https://api.github.com/repos/spatie/db-dumper/zipball/55d4d6710e1ab18c1e7ce2b22b8ad4bea2a30016", + "reference": "55d4d6710e1ab18c1e7ce2b22b8ad4bea2a30016", "shasum": "" }, "require": { @@ -6502,7 +6728,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/db-dumper/tree/3.7.0" + "source": "https://github.com/spatie/db-dumper/tree/3.7.1" }, "funding": [ { @@ -6514,7 +6740,7 @@ "type": "github" } ], - "time": "2024-09-23T08:58:35+00:00" + "time": "2024-11-18T14:54:31+00:00" }, { "name": "spatie/image-optimizer", @@ -6740,16 +6966,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.16.5", + "version": "1.17.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "c7413972cf22ffdff97b68499c22baa04eddb6a2" + "reference": "9ab30fd24f677e5aa370ea4cf6b41c517d16cf85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/c7413972cf22ffdff97b68499c22baa04eddb6a2", - "reference": "c7413972cf22ffdff97b68499c22baa04eddb6a2", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/9ab30fd24f677e5aa370ea4cf6b41c517d16cf85", + "reference": "9ab30fd24f677e5aa370ea4cf6b41c517d16cf85", "shasum": "" }, "require": { @@ -6758,10 +6984,10 @@ }, "require-dev": { "mockery/mockery": "^1.5", - "orchestra/testbench": "^7.7|^8.0", - "pestphp/pest": "^1.22", - "phpunit/phpunit": "^9.5.24", - "spatie/pest-plugin-test-time": "^1.1" + "orchestra/testbench": "^7.7|^8.0|^9.0", + "pestphp/pest": "^1.22|^2", + "phpunit/phpunit": "^9.5.24|^10.5", + "spatie/pest-plugin-test-time": "^1.1|^2.2" }, "type": "library", "autoload": { @@ -6788,7 +7014,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.5" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.17.0" }, "funding": [ { @@ -6796,7 +7022,7 @@ "type": "github" } ], - "time": "2024-08-27T18:56:10+00:00" + "time": "2024-12-09T16:29:14+00:00" }, { "name": "spatie/laravel-signal-aware-command", @@ -6830,12 +7056,12 @@ "type": "library", "extra": { "laravel": { - "providers": [ - "Spatie\\SignalAwareCommand\\SignalAwareCommandServiceProvider" - ], "aliases": { "Signal": "Spatie\\SignalAwareCommand\\Facades\\Signal" - } + }, + "providers": [ + "Spatie\\SignalAwareCommand\\SignalAwareCommandServiceProvider" + ] } }, "autoload": { @@ -7067,16 +7293,16 @@ }, { "name": "symfony/cache", - "version": "v7.1.7", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "23b61c9592ee72233c31625f0ae805dd1571e928" + "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/23b61c9592ee72233c31625f0ae805dd1571e928", - "reference": "23b61c9592ee72233c31625f0ae805dd1571e928", + "url": "https://api.github.com/repos/symfony/cache/zipball/e7e983596b744c4539f31e79b0350a6cf5878a20", + "reference": "e7e983596b744c4539f31e79b0350a6cf5878a20", "shasum": "" }, "require": { @@ -7104,6 +7330,7 @@ "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0|^3.0", + "symfony/clock": "^6.4|^7.0", "symfony/config": "^6.4|^7.0", "symfony/dependency-injection": "^6.4|^7.0", "symfony/filesystem": "^6.4|^7.0", @@ -7144,7 +7371,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.1.7" + "source": "https://github.com/symfony/cache/tree/v7.2.1" }, "funding": [ { @@ -7160,20 +7387,20 @@ "type": "tidelift" } ], - "time": "2024-11-05T15:34:55+00:00" + "time": "2024-12-07T08:08:50+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "df6a1a44c890faded49a5fca33c2d5c5fd3c2197" + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/df6a1a44c890faded49a5fca33c2d5c5fd3c2197", - "reference": "df6a1a44c890faded49a5fca33c2d5c5fd3c2197", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", "shasum": "" }, "require": { @@ -7220,7 +7447,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1" }, "funding": [ { @@ -7236,20 +7463,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/clock", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "97bebc53548684c17ed696bc8af016880f0f098d" + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/97bebc53548684c17ed696bc8af016880f0f098d", - "reference": "97bebc53548684c17ed696bc8af016880f0f098d", + "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", "shasum": "" }, "require": { @@ -7294,7 +7521,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.1.6" + "source": "https://github.com/symfony/clock/tree/v7.2.0" }, "funding": [ { @@ -7310,20 +7537,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/console", - "version": "v7.1.7", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "3284aafcac338b6e86fd955ee4d794cbe434151a" + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/3284aafcac338b6e86fd955ee4d794cbe434151a", - "reference": "3284aafcac338b6e86fd955ee4d794cbe434151a", + "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", "shasum": "" }, "require": { @@ -7387,7 +7614,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.7" + "source": "https://github.com/symfony/console/tree/v7.2.1" }, "funding": [ { @@ -7403,20 +7630,20 @@ "type": "tidelift" } ], - "time": "2024-11-05T15:34:55+00:00" + "time": "2024-12-11T03:49:26+00:00" }, { "name": "symfony/css-selector", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "4aa4f6b3d6749c14d3aa815eef8226632e7bbc66" + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/4aa4f6b3d6749c14d3aa815eef8226632e7bbc66", - "reference": "4aa4f6b3d6749c14d3aa815eef8226632e7bbc66", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", "shasum": "" }, "require": { @@ -7452,7 +7679,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.1.6" + "source": "https://github.com/symfony/css-selector/tree/v7.2.0" }, "funding": [ { @@ -7468,20 +7695,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", "shasum": "" }, "require": { @@ -7519,7 +7746,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" }, "funding": [ { @@ -7535,20 +7762,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/error-handler", - "version": "v7.1.7", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "010e44661f4c6babaf8c4862fe68c24a53903342" + "reference": "6150b89186573046167796fa5f3f76601d5145f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/010e44661f4c6babaf8c4862fe68c24a53903342", - "reference": "010e44661f4c6babaf8c4862fe68c24a53903342", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/6150b89186573046167796fa5f3f76601d5145f8", + "reference": "6150b89186573046167796fa5f3f76601d5145f8", "shasum": "" }, "require": { @@ -7594,7 +7821,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.1.7" + "source": "https://github.com/symfony/error-handler/tree/v7.2.1" }, "funding": [ { @@ -7610,20 +7837,20 @@ "type": "tidelift" } ], - "time": "2024-11-05T15:34:55+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "87254c78dd50721cfd015b62277a8281c5589702" + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87254c78dd50721cfd015b62277a8281c5589702", - "reference": "87254c78dd50721cfd015b62277a8281c5589702", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", "shasum": "" }, "require": { @@ -7674,7 +7901,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.6" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.2.0" }, "funding": [ { @@ -7690,20 +7917,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", - "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", "shasum": "" }, "require": { @@ -7750,7 +7977,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" }, "funding": [ { @@ -7766,20 +7993,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/finder", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8" + "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/2cb89664897be33f78c65d3d2845954c8d7a43b8", - "reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8", + "url": "https://api.github.com/repos/symfony/finder/zipball/6de263e5868b9a137602dd1e33e4d48bfae99c49", + "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49", "shasum": "" }, "require": { @@ -7814,7 +8041,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.1.6" + "source": "https://github.com/symfony/finder/tree/v7.2.0" }, "funding": [ { @@ -7830,27 +8057,27 @@ "type": "tidelift" } ], - "time": "2024-10-01T08:31:23+00:00" + "time": "2024-10-23T06:56:12+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.14", + "version": "v6.4.16", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "05d88cbd816ad6e0202edd9a9963cb9d615b8826" + "reference": "60a113666fa67e598abace38e5f46a0954d8833d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/05d88cbd816ad6e0202edd9a9963cb9d615b8826", - "reference": "05d88cbd816ad6e0202edd9a9963cb9d615b8826", + "url": "https://api.github.com/repos/symfony/http-client/zipball/60a113666fa67e598abace38e5f46a0954d8833d", + "reference": "60a113666fa67e598abace38e5f46a0954d8833d", "shasum": "" }, "require": { "php": ">=8.1", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "^3.4.1", + "symfony/http-client-contracts": "~3.4.3|^3.5.1", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -7907,7 +8134,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.14" + "source": "https://github.com/symfony/http-client/tree/v6.4.16" }, "funding": [ { @@ -7923,20 +8150,20 @@ "type": "tidelift" } ], - "time": "2024-11-05T16:39:55+00:00" + "time": "2024-11-27T11:52:33+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.5.0", + "version": "v3.5.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "20414d96f391677bf80078aa55baece78b82647d" + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d", - "reference": "20414d96f391677bf80078aa55baece78b82647d", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/ee8d807ab20fcb51267fdace50fbe3494c31e645", + "reference": "ee8d807ab20fcb51267fdace50fbe3494c31e645", "shasum": "" }, "require": { @@ -7944,12 +8171,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -7985,7 +8212,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.2" }, "funding": [ { @@ -8001,35 +8228,36 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-12-07T08:49:48+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.1.7", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "5183b61657807099d98f3367bcccb850238b17a9" + "reference": "e88a66c3997859532bc2ddd6dd8f35aba2711744" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5183b61657807099d98f3367bcccb850238b17a9", - "reference": "5183b61657807099d98f3367bcccb850238b17a9", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e88a66c3997859532bc2ddd6dd8f35aba2711744", + "reference": "e88a66c3997859532bc2ddd6dd8f35aba2711744", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-mbstring": "~1.1", "symfony/polyfill-php83": "^1.27" }, "conflict": { "doctrine/dbal": "<3.6", - "symfony/cache": "<6.4" + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4|^7.0", + "symfony/cache": "^6.4.12|^7.1.5", "symfony/dependency-injection": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", @@ -8062,7 +8290,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.1.7" + "source": "https://github.com/symfony/http-foundation/tree/v7.2.0" }, "funding": [ { @@ -8078,20 +8306,20 @@ "type": "tidelift" } ], - "time": "2024-11-06T09:02:46+00:00" + "time": "2024-11-13T18:58:46+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.1.7", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "7f137cda31fd41e422edcdc01915f2c095b84399" + "reference": "d8ae58eecae44c8e66833e76cc50a4ad3c002d97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/7f137cda31fd41e422edcdc01915f2c095b84399", - "reference": "7f137cda31fd41e422edcdc01915f2c095b84399", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d8ae58eecae44c8e66833e76cc50a4ad3c002d97", + "reference": "d8ae58eecae44c8e66833e76cc50a4ad3c002d97", "shasum": "" }, "require": { @@ -8120,7 +8348,7 @@ "symfony/twig-bridge": "<6.4", "symfony/validator": "<6.4", "symfony/var-dumper": "<6.4", - "twig/twig": "<3.0.4" + "twig/twig": "<3.12" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" @@ -8148,7 +8376,7 @@ "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0", "symfony/var-exporter": "^6.4|^7.0", - "twig/twig": "^3.0.4" + "twig/twig": "^3.12" }, "type": "library", "autoload": { @@ -8176,7 +8404,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.1.7" + "source": "https://github.com/symfony/http-kernel/tree/v7.2.1" }, "funding": [ { @@ -8192,20 +8420,20 @@ "type": "tidelift" } ], - "time": "2024-11-06T09:54:34+00:00" + "time": "2024-12-11T12:09:10+00:00" }, { "name": "symfony/mailer", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "69c9948451fb3a6a4d47dc8261d1794734e76cdd" + "reference": "e4d358702fb66e4c8a2af08e90e7271a62de39cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/69c9948451fb3a6a4d47dc8261d1794734e76cdd", - "reference": "69c9948451fb3a6a4d47dc8261d1794734e76cdd", + "url": "https://api.github.com/repos/symfony/mailer/zipball/e4d358702fb66e4c8a2af08e90e7271a62de39cc", + "reference": "e4d358702fb66e4c8a2af08e90e7271a62de39cc", "shasum": "" }, "require": { @@ -8214,7 +8442,7 @@ "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", + "symfony/mime": "^7.2", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -8256,7 +8484,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.1.6" + "source": "https://github.com/symfony/mailer/tree/v7.2.0" }, "funding": [ { @@ -8272,7 +8500,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-11-25T15:21:05+00:00" }, { "name": "symfony/mailgun-mailer", @@ -8345,16 +8573,16 @@ }, { "name": "symfony/mime", - "version": "v7.1.6", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "caa1e521edb2650b8470918dfe51708c237f0598" + "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/caa1e521edb2650b8470918dfe51708c237f0598", - "reference": "caa1e521edb2650b8470918dfe51708c237f0598", + "url": "https://api.github.com/repos/symfony/mime/zipball/7f9617fcf15cb61be30f8b252695ed5e2bfac283", + "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283", "shasum": "" }, "require": { @@ -8409,7 +8637,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.1.6" + "source": "https://github.com/symfony/mime/tree/v7.2.1" }, "funding": [ { @@ -8425,7 +8653,7 @@ "type": "tidelift" } ], - "time": "2024-10-25T15:11:02+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/polyfill-ctype", @@ -8453,8 +8681,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -8529,8 +8757,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -8608,8 +8836,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -8690,8 +8918,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -8774,8 +9002,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -8848,8 +9076,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -8928,8 +9156,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9010,8 +9238,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9065,16 +9293,16 @@ }, { "name": "symfony/process", - "version": "v7.1.7", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "9b8a40b7289767aa7117e957573c2a535efe6585" + "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/9b8a40b7289767aa7117e957573c2a535efe6585", - "reference": "9b8a40b7289767aa7117e957573c2a535efe6585", + "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", + "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", "shasum": "" }, "require": { @@ -9106,7 +9334,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.7" + "source": "https://github.com/symfony/process/tree/v7.2.0" }, "funding": [ { @@ -9122,20 +9350,20 @@ "type": "tidelift" } ], - "time": "2024-11-06T09:25:12+00:00" + "time": "2024-11-06T14:24:19+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "f16471bb19f6685b9ccf0a2c03c213840ae68cd6" + "reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/f16471bb19f6685b9ccf0a2c03c213840ae68cd6", - "reference": "f16471bb19f6685b9ccf0a2c03c213840ae68cd6", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/03f2f72319e7acaf2a9f6fcbe30ef17eec51594f", + "reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f", "shasum": "" }, "require": { @@ -9189,7 +9417,7 @@ "psr-7" ], "support": { - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.1.6" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.2.0" }, "funding": [ { @@ -9205,20 +9433,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-26T08:57:56+00:00" }, { "name": "symfony/routing", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "66a2c469f6c22d08603235c46a20007c0701ea0a" + "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/66a2c469f6c22d08603235c46a20007c0701ea0a", - "reference": "66a2c469f6c22d08603235c46a20007c0701ea0a", + "url": "https://api.github.com/repos/symfony/routing/zipball/e10a2450fa957af6c448b9b93c9010a4e4c0725e", + "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e", "shasum": "" }, "require": { @@ -9270,7 +9498,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.1.6" + "source": "https://github.com/symfony/routing/tree/v7.2.0" }, "funding": [ { @@ -9286,20 +9514,20 @@ "type": "tidelift" } ], - "time": "2024-10-01T08:31:23+00:00" + "time": "2024-11-25T11:08:51+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", "shasum": "" }, "require": { @@ -9353,7 +9581,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" }, "funding": [ { @@ -9369,20 +9597,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/string", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "61b72d66bf96c360a727ae6232df5ac83c71f626" + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/61b72d66bf96c360a727ae6232df5ac83c71f626", - "reference": "61b72d66bf96c360a727ae6232df5ac83c71f626", + "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", "shasum": "" }, "require": { @@ -9440,7 +9668,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.6" + "source": "https://github.com/symfony/string/tree/v7.2.0" }, "funding": [ { @@ -9456,24 +9684,25 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-11-13T13:31:26+00:00" }, { "name": "symfony/translation", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "b9f72ab14efdb6b772f85041fa12f820dee8d55f" + "reference": "dc89e16b44048ceecc879054e5b7f38326ab6cc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/b9f72ab14efdb6b772f85041fa12f820dee8d55f", - "reference": "b9f72ab14efdb6b772f85041fa12f820dee8d55f", + "url": "https://api.github.com/repos/symfony/translation/zipball/dc89e16b44048ceecc879054e5b7f38326ab6cc5", + "reference": "dc89e16b44048ceecc879054e5b7f38326ab6cc5", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/translation-contracts": "^2.5|^3.0" }, @@ -9534,7 +9763,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.1.6" + "source": "https://github.com/symfony/translation/tree/v7.2.0" }, "funding": [ { @@ -9550,20 +9779,20 @@ "type": "tidelift" } ], - "time": "2024-09-28T12:35:13+00:00" + "time": "2024-11-12T20:47:56+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", - "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", "shasum": "" }, "require": { @@ -9612,7 +9841,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" }, "funding": [ { @@ -9628,20 +9857,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/uid", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "65befb3bb2d503bbffbd08c815aa38b472999917" + "reference": "2d294d0c48df244c71c105a169d0190bfb080426" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/65befb3bb2d503bbffbd08c815aa38b472999917", - "reference": "65befb3bb2d503bbffbd08c815aa38b472999917", + "url": "https://api.github.com/repos/symfony/uid/zipball/2d294d0c48df244c71c105a169d0190bfb080426", + "reference": "2d294d0c48df244c71c105a169d0190bfb080426", "shasum": "" }, "require": { @@ -9686,7 +9915,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.1.6" + "source": "https://github.com/symfony/uid/tree/v7.2.0" }, "funding": [ { @@ -9702,20 +9931,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.1.7", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "f6ea51f669760cacd7464bf7eaa0be87b8072db1" + "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/f6ea51f669760cacd7464bf7eaa0be87b8072db1", - "reference": "f6ea51f669760cacd7464bf7eaa0be87b8072db1", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c6a22929407dec8765d6e2b6ff85b800b245879c", + "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c", "shasum": "" }, "require": { @@ -9731,7 +9960,7 @@ "symfony/http-kernel": "^6.4|^7.0", "symfony/process": "^6.4|^7.0", "symfony/uid": "^6.4|^7.0", - "twig/twig": "^3.0.4" + "twig/twig": "^3.12" }, "bin": [ "Resources/bin/var-dump-server" @@ -9769,7 +9998,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.1.7" + "source": "https://github.com/symfony/var-dumper/tree/v7.2.0" }, "funding": [ { @@ -9785,20 +10014,20 @@ "type": "tidelift" } ], - "time": "2024-11-05T15:34:55+00:00" + "time": "2024-11-08T15:48:14+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "90173ef89c40e7c8c616653241048705f84130ef" + "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/90173ef89c40e7c8c616653241048705f84130ef", - "reference": "90173ef89c40e7c8c616653241048705f84130ef", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/1a6a89f95a46af0f142874c9d650a6358d13070d", + "reference": "1a6a89f95a46af0f142874c9d650a6358d13070d", "shasum": "" }, "require": { @@ -9845,7 +10074,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.1.6" + "source": "https://github.com/symfony/var-exporter/tree/v7.2.0" }, "funding": [ { @@ -9861,7 +10090,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-10-18T07:58:17+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -10002,16 +10231,16 @@ }, { "name": "voku/portable-ascii", - "version": "2.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "b56450eed252f6801410d810c8e1727224ae0743" + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743", - "reference": "b56450eed252f6801410d810c8e1727224ae0743", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", "shasum": "" }, "require": { @@ -10036,7 +10265,7 @@ "authors": [ { "name": "Lars Moelleken", - "homepage": "http://www.moelleken.org/" + "homepage": "https://www.moelleken.org/" } ], "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", @@ -10048,7 +10277,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.0.1" + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" }, "funding": [ { @@ -10072,7 +10301,7 @@ "type": "tidelift" } ], - "time": "2022-03-08T17:03:00+00:00" + "time": "2024-11-21T01:49:47+00:00" }, { "name": "web-token/jwt-core", @@ -10515,16 +10744,16 @@ "packages-dev": [ { "name": "fakerphp/faker", - "version": "v1.23.1", + "version": "v1.24.1", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", "shasum": "" }, "require": { @@ -10572,9 +10801,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" }, - "time": "2024-01-02T13:46:09+00:00" + "time": "2024-11-21T13:46:39+00:00" }, { "name": "filp/whoops", @@ -10700,16 +10929,16 @@ }, { "name": "laravel/pint", - "version": "v1.18.1", + "version": "v1.18.3", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9" + "reference": "cef51821608239040ab841ad6e1c6ae502ae3026" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/35c00c05ec43e6b46d295efc0f4386ceb30d50d9", - "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9", + "url": "https://api.github.com/repos/laravel/pint/zipball/cef51821608239040ab841ad6e1c6ae502ae3026", + "reference": "cef51821608239040ab841ad6e1c6ae502ae3026", "shasum": "" }, "require": { @@ -10720,13 +10949,13 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.64.0", - "illuminate/view": "^10.48.20", - "larastan/larastan": "^2.9.8", + "friendsofphp/php-cs-fixer": "^3.65.0", + "illuminate/view": "^10.48.24", + "larastan/larastan": "^2.9.11", "laravel-zero/framework": "^10.4.0", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^2.35.1" + "nunomaduro/termwind": "^1.17.0", + "pestphp/pest": "^2.36.0" }, "bin": [ "builds/pint" @@ -10762,20 +10991,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-09-24T17:22:50+00:00" + "time": "2024-11-26T15:34:00+00:00" }, { "name": "laravel/telescope", - "version": "v5.2.4", + "version": "v5.2.6", "source": { "type": "git", "url": "https://github.com/laravel/telescope.git", - "reference": "749369e996611d803e7c1b57929b482dd676008d" + "reference": "7ee46fbea8e3b01108575c8edf7377abddfe8bb9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/telescope/zipball/749369e996611d803e7c1b57929b482dd676008d", - "reference": "749369e996611d803e7c1b57929b482dd676008d", + "url": "https://api.github.com/repos/laravel/telescope/zipball/7ee46fbea8e3b01108575c8edf7377abddfe8bb9", + "reference": "7ee46fbea8e3b01108575c8edf7377abddfe8bb9", "shasum": "" }, "require": { @@ -10829,9 +11058,9 @@ ], "support": { "issues": "https://github.com/laravel/telescope/issues", - "source": "https://github.com/laravel/telescope/tree/v5.2.4" + "source": "https://github.com/laravel/telescope/tree/v5.2.6" }, - "time": "2024-10-29T15:35:13+00:00" + "time": "2024-11-25T20:34:58+00:00" }, { "name": "mockery/mockery", @@ -11193,16 +11422,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.7", + "version": "11.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca" + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca", - "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118", + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118", "shasum": "" }, "require": { @@ -11221,7 +11450,7 @@ "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^11.4.1" + "phpunit/phpunit": "^11.5.0" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -11259,7 +11488,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.8" }, "funding": [ { @@ -11267,7 +11496,7 @@ "type": "github" } ], - "time": "2024-10-09T06:21:38+00:00" + "time": "2024-12-11T12:34:27+00:00" }, { "name": "phpunit/php-file-iterator", @@ -11516,16 +11745,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.4.3", + "version": "11.5.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e8e8ed1854de5d36c088ec1833beae40d2dedd76" + "reference": "2b94d4f2450b9869fa64a46fd8a6a41997aef56a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e8e8ed1854de5d36c088ec1833beae40d2dedd76", - "reference": "e8e8ed1854de5d36c088ec1833beae40d2dedd76", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2b94d4f2450b9869fa64a46fd8a6a41997aef56a", + "reference": "2b94d4f2450b9869fa64a46fd8a6a41997aef56a", "shasum": "" }, "require": { @@ -11535,7 +11764,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.0", + "myclabs/deep-copy": "^1.12.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", @@ -11546,14 +11775,15 @@ "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", "sebastian/code-unit": "^3.0.1", - "sebastian/comparator": "^6.1.1", + "sebastian/comparator": "^6.2.1", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.0", - "sebastian/exporter": "^6.1.3", + "sebastian/exporter": "^6.3.0", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", "sebastian/type": "^5.1.0", - "sebastian/version": "^5.0.2" + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -11564,7 +11794,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "11.4-dev" + "dev-main": "11.5-dev" } }, "autoload": { @@ -11596,7 +11826,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.4.3" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.1" }, "funding": [ { @@ -11612,7 +11842,7 @@ "type": "tidelift" } ], - "time": "2024-10-28T13:07:50+00:00" + "time": "2024-12-11T10:52:48+00:00" }, { "name": "sebastian/cli-parser", @@ -11673,23 +11903,23 @@ }, { "name": "sebastian/code-unit", - "version": "3.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "6bb7d09d6623567178cf54126afa9c2310114268" + "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268", - "reference": "6bb7d09d6623567178cf54126afa9c2310114268", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", + "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.5" }, "type": "library", "extra": { @@ -11718,7 +11948,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.2" }, "funding": [ { @@ -11726,7 +11956,7 @@ "type": "github" } ], - "time": "2024-07-03T04:44:28+00:00" + "time": "2024-12-12T09:59:06+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -12052,16 +12282,16 @@ }, { "name": "sebastian/exporter", - "version": "6.1.3", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e" + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", - "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", "shasum": "" }, "require": { @@ -12070,7 +12300,7 @@ "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^11.2" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { @@ -12118,7 +12348,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/6.1.3" + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" }, "funding": [ { @@ -12126,7 +12356,7 @@ "type": "github" } ], - "time": "2024-07-03T04:56:19+00:00" + "time": "2024-12-05T09:17:50+00:00" }, { "name": "sebastian/global-state", @@ -12537,6 +12767,58 @@ ], "time": "2024-10-09T05:16:32+00:00" }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, { "name": "theseer/tokenizer", "version": "1.2.3", @@ -12590,7 +12872,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -12603,6 +12885,6 @@ "ext-mbstring": "*", "ext-openssl": "*" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/config/cache.php b/config/cache.php index 88129848e..db2925e13 100644 --- a/config/cache.php +++ b/config/cache.php @@ -85,11 +85,29 @@ return [ 'database' => env('REDIS_DATABASE', 0), ], + 'session' => [ + 'scheme' => env('REDIS_SCHEME', 'tcp'), + 'path' => env('REDIS_PATH'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', 6379), + 'database' => env('REDIS_DATABASE_SESSION', 1), + ], + + 'pulse' => [ + 'scheme' => env('REDIS_SCHEME', 'tcp'), + 'path' => env('REDIS_PATH'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', 6379), + 'database' => env('REDIS_DATABASE_PULSE', 2), + ], + ], 'redis:session' => [ 'driver' => 'redis', - 'connection' => 'default', + 'connection' => 'session', 'prefix' => 'pf_session', ], diff --git a/config/database.php b/config/database.php index 92d6a2ba2..2a3cf3393 100644 --- a/config/database.php +++ b/config/database.php @@ -143,6 +143,24 @@ return [ 'database' => env('REDIS_DATABASE', 0), ], + 'session' => [ + 'scheme' => env('REDIS_SCHEME', 'tcp'), + 'path' => env('REDIS_PATH'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', 6379), + 'database' => env('REDIS_DATABASE_SESSION', 1), + ], + + 'pulse' => [ + 'scheme' => env('REDIS_SCHEME', 'tcp'), + 'path' => env('REDIS_PATH'), + 'host' => env('REDIS_HOST', '127.0.0.1'), + 'password' => env('REDIS_PASSWORD', null), + 'port' => env('REDIS_PORT', 6379), + 'database' => env('REDIS_DATABASE_PULSE', 2), + ], + ], 'dbal' => [ diff --git a/config/media.php b/config/media.php index 46c1719db..5e3b32ae6 100644 --- a/config/media.php +++ b/config/media.php @@ -24,6 +24,10 @@ return [ ], ], + 'image_optimize' => [ + 'catch_unoptimized_media_hour_limit' => env('PF_CATCHUNOPTIMIZEDMEDIA', false), + ], + 'hls' => [ /* |-------------------------------------------------------------------------- diff --git a/config/pulse.php b/config/pulse.php new file mode 100644 index 000000000..7341ca7f0 --- /dev/null +++ b/config/pulse.php @@ -0,0 +1,236 @@ + env('PULSE_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | Pulse Path + |-------------------------------------------------------------------------- + | + | This is the path which the Pulse dashboard will be accessible from. Feel + | free to change this path to anything you'd like. Note that this won't + | affect the path of the internal API that is never exposed to users. + | + */ + + 'path' => env('PULSE_PATH', 'pulse'), + + /* + |-------------------------------------------------------------------------- + | Pulse Master Switch + |-------------------------------------------------------------------------- + | + | This configuration option may be used to completely disable all Pulse + | data recorders regardless of their individual configurations. This + | provides a single option to quickly disable all Pulse recording. + | + */ + + 'enabled' => env('PULSE_ENABLED', false), + + /* + |-------------------------------------------------------------------------- + | Pulse Storage Driver + |-------------------------------------------------------------------------- + | + | This configuration option determines which storage driver will be used + | while storing entries from Pulse's recorders. In addition, you also + | may provide any options to configure the selected storage driver. + | + */ + + 'storage' => [ + 'driver' => env('PULSE_STORAGE_DRIVER', 'database'), + + 'trim' => [ + 'keep' => env('PULSE_STORAGE_KEEP', '7 days'), + ], + + 'database' => [ + 'connection' => env('PULSE_DB_CONNECTION'), + 'chunk' => 1000, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Pulse Ingest Driver + |-------------------------------------------------------------------------- + | + | This configuration options determines the ingest driver that will be used + | to capture entries from Pulse's recorders. Ingest drivers are great to + | free up your request workers quickly by offloading the data storage. + | + */ + + 'ingest' => [ + 'driver' => env('PULSE_INGEST_DRIVER', 'storage'), + + 'buffer' => env('PULSE_INGEST_BUFFER', 5_000), + + 'trim' => [ + 'lottery' => [1, 1_000], + 'keep' => env('PULSE_INGEST_KEEP', '7 days'), + ], + + 'redis' => [ + 'connection' => env('PULSE_REDIS_CONNECTION'), + 'chunk' => 1000, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Pulse Cache Driver + |-------------------------------------------------------------------------- + | + | This configuration option determines the cache driver that will be used + | for various tasks, including caching dashboard results, establishing + | locks for events that should only occur on one server and signals. + | + */ + + 'cache' => env('PULSE_CACHE_DRIVER'), + + /* + |-------------------------------------------------------------------------- + | Pulse Route Middleware + |-------------------------------------------------------------------------- + | + | These middleware will be assigned to every Pulse route, giving you the + | chance to add your own middleware to this list or change any of the + | existing middleware. Of course, reasonable defaults are provided. + | + */ + + 'middleware' => [ + 'web', + Authorize::class, + ], + + /* + |-------------------------------------------------------------------------- + | Pulse Recorders + |-------------------------------------------------------------------------- + | + | The following array lists the "recorders" that will be registered with + | Pulse, along with their configuration. Recorders gather application + | event data from requests and tasks to pass to your ingest driver. + | + */ + + 'recorders' => [ + Recorders\CacheInteractions::class => [ + 'enabled' => env('PULSE_CACHE_INTERACTIONS_ENABLED', true), + 'sample_rate' => env('PULSE_CACHE_INTERACTIONS_SAMPLE_RATE', 1), + 'ignore' => [ + ...Pulse::defaultVendorCacheKeys(), + ], + 'groups' => [ + '/^job-exceptions:.*/' => 'job-exceptions:*', + // '/:\d+/' => ':*', + ], + ], + + Recorders\Exceptions::class => [ + 'enabled' => env('PULSE_EXCEPTIONS_ENABLED', true), + 'sample_rate' => env('PULSE_EXCEPTIONS_SAMPLE_RATE', 1), + 'location' => env('PULSE_EXCEPTIONS_LOCATION', true), + 'ignore' => [ + // '/^Package\\\\Exceptions\\\\/', + ], + ], + + Recorders\Queues::class => [ + 'enabled' => env('PULSE_QUEUES_ENABLED', true), + 'sample_rate' => env('PULSE_QUEUES_SAMPLE_RATE', 1), + 'ignore' => [ + // '/^Package\\\\Jobs\\\\/', + ], + ], + + Recorders\Servers::class => [ + 'server_name' => env('PULSE_SERVER_NAME', gethostname()), + 'directories' => explode(':', env('PULSE_SERVER_DIRECTORIES', '/')), + ], + + Recorders\SlowJobs::class => [ + 'enabled' => env('PULSE_SLOW_JOBS_ENABLED', true), + 'sample_rate' => env('PULSE_SLOW_JOBS_SAMPLE_RATE', 1), + 'threshold' => env('PULSE_SLOW_JOBS_THRESHOLD', 1000), + 'ignore' => [ + // '/^Package\\\\Jobs\\\\/', + ], + ], + + Recorders\SlowOutgoingRequests::class => [ + 'enabled' => env('PULSE_SLOW_OUTGOING_REQUESTS_ENABLED', true), + 'sample_rate' => env('PULSE_SLOW_OUTGOING_REQUESTS_SAMPLE_RATE', 1), + 'threshold' => env('PULSE_SLOW_OUTGOING_REQUESTS_THRESHOLD', 1000), + 'ignore' => [ + // '#^http://127\.0\.0\.1:13714#', // Inertia SSR... + ], + 'groups' => [ + // '#^https://api\.github\.com/repos/.*$#' => 'api.github.com/repos/*', + // '#^https?://([^/]*).*$#' => '\1', + // '#/\d+#' => '/*', + ], + ], + + Recorders\SlowQueries::class => [ + 'enabled' => env('PULSE_SLOW_QUERIES_ENABLED', true), + 'sample_rate' => env('PULSE_SLOW_QUERIES_SAMPLE_RATE', 1), + 'threshold' => env('PULSE_SLOW_QUERIES_THRESHOLD', 1000), + 'location' => env('PULSE_SLOW_QUERIES_LOCATION', true), + 'max_query_length' => env('PULSE_SLOW_QUERIES_MAX_QUERY_LENGTH'), + 'ignore' => [ + '/(["`])pulse_[\w]+?\1/', // Pulse tables... + '/(["`])telescope_[\w]+?\1/', // Telescope tables... + ], + ], + + Recorders\SlowRequests::class => [ + 'enabled' => env('PULSE_SLOW_REQUESTS_ENABLED', true), + 'sample_rate' => env('PULSE_SLOW_REQUESTS_SAMPLE_RATE', 1), + 'threshold' => env('PULSE_SLOW_REQUESTS_THRESHOLD', 1000), + 'ignore' => [ + '#^/'.env('PULSE_PATH', 'pulse').'$#', // Pulse dashboard... + '#^/telescope#', // Telescope dashboard... + ], + ], + + Recorders\UserJobs::class => [ + 'enabled' => env('PULSE_USER_JOBS_ENABLED', true), + 'sample_rate' => env('PULSE_USER_JOBS_SAMPLE_RATE', 1), + 'ignore' => [ + // '/^Package\\\\Jobs\\\\/', + ], + ], + + Recorders\UserRequests::class => [ + 'enabled' => env('PULSE_USER_REQUESTS_ENABLED', true), + 'sample_rate' => env('PULSE_USER_REQUESTS_SAMPLE_RATE', 1), + 'ignore' => [ + '#^/'.env('PULSE_PATH', 'pulse').'$#', // Pulse dashboard... + '#^/telescope#', // Telescope dashboard... + ], + ], + ], +]; diff --git a/config/services.php b/config/services.php index 3f73b64aa..a1e56ac99 100644 --- a/config/services.php +++ b/config/services.php @@ -17,6 +17,8 @@ return [ 'mailgun' => [ 'domain' => env('MAILGUN_DOMAIN'), 'secret' => env('MAILGUN_SECRET'), + 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), + 'scheme' => 'https', ], 'ses' => [ diff --git a/database/migrations/2018_09_30_051108_create_direct_messages_table.php b/database/migrations/2018_09_30_051108_create_direct_messages_table.php index 63b305f4d..97db01e3c 100644 --- a/database/migrations/2018_09_30_051108_create_direct_messages_table.php +++ b/database/migrations/2018_09_30_051108_create_direct_messages_table.php @@ -19,7 +19,7 @@ class CreateDirectMessagesTable extends Migration $table->bigInteger('from_id')->unsigned()->index(); $table->string('from_profile_ids')->nullable(); $table->boolean('group_message')->default(false); - $table->bigInteger('status_id')->unsigned()->integer(); + $table->bigInteger('status_id')->unsigned(); $table->unique(['to_id', 'from_id', 'status_id']); $table->timestamp('read_at')->nullable(); $table->timestamps(); diff --git a/database/migrations/2023_06_07_000001_create_pulse_tables.php b/database/migrations/2023_06_07_000001_create_pulse_tables.php new file mode 100644 index 000000000..5d194e2c9 --- /dev/null +++ b/database/migrations/2023_06_07_000001_create_pulse_tables.php @@ -0,0 +1,84 @@ +shouldRun()) { + return; + } + + Schema::create('pulse_values', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('timestamp'); + $table->string('type'); + $table->mediumText('key'); + match ($this->driver()) { + 'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'), + 'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'), + 'sqlite' => $table->string('key_hash'), + }; + $table->mediumText('value'); + + $table->index('timestamp'); // For trimming... + $table->index('type'); // For fast lookups and purging... + $table->unique(['type', 'key_hash']); // For data integrity and upserts... + }); + + Schema::create('pulse_entries', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('timestamp'); + $table->string('type'); + $table->mediumText('key'); + match ($this->driver()) { + 'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'), + 'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'), + 'sqlite' => $table->string('key_hash'), + }; + $table->bigInteger('value')->nullable(); + + $table->index('timestamp'); // For trimming... + $table->index('type'); // For purging... + $table->index('key_hash'); // For mapping... + $table->index(['timestamp', 'type', 'key_hash', 'value']); // For aggregate queries... + }); + + Schema::create('pulse_aggregates', function (Blueprint $table) { + $table->id(); + $table->unsignedInteger('bucket'); + $table->unsignedMediumInteger('period'); + $table->string('type'); + $table->mediumText('key'); + match ($this->driver()) { + 'mariadb', 'mysql' => $table->char('key_hash', 16)->charset('binary')->virtualAs('unhex(md5(`key`))'), + 'pgsql' => $table->uuid('key_hash')->storedAs('md5("key")::uuid'), + 'sqlite' => $table->string('key_hash'), + }; + $table->string('aggregate'); + $table->decimal('value', 20, 2); + $table->unsignedInteger('count')->nullable(); + + $table->unique(['bucket', 'period', 'type', 'aggregate', 'key_hash']); // Force "on duplicate update"... + $table->index(['period', 'bucket']); // For trimming... + $table->index('type'); // For purging... + $table->index(['period', 'type', 'aggregate', 'bucket']); // For aggregate queries... + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('pulse_values'); + Schema::dropIfExists('pulse_entries'); + Schema::dropIfExists('pulse_aggregates'); + } +}; diff --git a/database/migrations/2024_05_20_062706_update_group_posts_table.php b/database/migrations/2024_05_20_062706_update_group_posts_table.php index 99f272be9..828727395 100644 --- a/database/migrations/2024_05_20_062706_update_group_posts_table.php +++ b/database/migrations/2024_05_20_062706_update_group_posts_table.php @@ -2,6 +2,7 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; return new class extends Migration @@ -12,6 +13,9 @@ return new class extends Migration public function up(): void { Schema::table('group_posts', function (Blueprint $table) { + if (DB::getDriverName() === 'sqlite') { + $table->dropUnique(['status_id']); + } $table->dropColumn('status_id'); $table->dropColumn('reply_child_id'); $table->dropColumn('in_reply_to_id'); diff --git a/docker-compose.yml b/docker-compose.yml index 2a805eb7c..ea61e595e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: # # See: https://github.com/nginx-proxy/nginx-proxy/tree/main/docs proxy: - image: nginxproxy/nginx-proxy:1.4 + image: "nginxproxy/nginx-proxy:${DOCKER_PROXY_VERSION}" container_name: "${DOCKER_ALL_CONTAINER_NAME_PREFIX}-proxy" restart: unless-stopped profiles: diff --git a/funding.json b/funding.json new file mode 100644 index 000000000..507d8441e --- /dev/null +++ b/funding.json @@ -0,0 +1,68 @@ +{ + "version": "v1.0.0", + "entity": { + "type": "individual", + "role": "owner", + "name": "Daniel Supernault", + "email": "danielsupernault@gmail.com", + "phone": "", + "description": "I'm the developer behind Pixelfed, an open-source, federated photo-sharing platform that prioritizes privacy, community, and creativity. With a passion for building ethical alternatives to mainstream social networks, I have championed decentralized technologies and empowered users to share their stories without compromising their digital autonomy. As the driving force behind Pixelfed, I combine innovative development with a thoughtful approach to user experience, fostering an online space that feels personal, authentic, and inclusive.", + "webpageUrl": { + "url": "https://github.com/pixelfed/pixelfed" + } + }, + "projects": [ + { + "guid": "pixelfed", + "name": "Pixelfed", + "description": "Pixelfed is a free, open-source photo-sharing platform designed to put users in control of their content. Built on the principles of decentralization, Pixelfed offers a refreshing alternative to traditional social media, prioritizing privacy, community, and ethical design. Whether you're an artist, photographer, or someone who loves sharing moments, Pixelfed lets you connect and create without intrusive ads, algorithms, or data exploitation. Fully federated and part of the Fediverse, Pixelfed empowers users to join or host their own instances while still connecting with a global network of creatives and communities. It's not just a platform—it's a movement toward a better, more user-centered internet.", + "webpageUrl": { + "url": "https://github.com/pixelfed/pixelfed" + }, + "repositoryUrl": { + "url": "https://github.com/pixelfed/pixelfed" + }, + "licenses": ["spdx:AGPL-3.0"], + "tags": ["activitypub", "fediverse", "laravel", "pixelfed"] + } + ], + "funding": { + "channels": [ + { + "guid": "github-sponsors", + "type": "payment-provider", + "address": "https://github.com/sponsors/dansup", + "description": "Sponsor me through Github." + }, + { + "guid": "paypal-sponsors", + "type": "payment-provider", + "address": "https://www.paypal.com/paypalme/dansup", + "description": "Sponsor me through Paypal." + } + ], + "plans": [ + { + "guid": "developer-time", + "status": "active", + "name": "Developer compensation", + "description": "This will cover the cost of one developer working part-time on the projects.", + "amount": 0, + "currency": "USD", + "frequency": "monthly", + "channels": ["github-sponsors", "paypal-sponsors"] + }, + { + "guid": "support-plan", + "status": "active", + "name": "Support plan", + "description": "Pay anything you wish/can to show your support for the projects.", + "amount": 0, + "currency": "USD", + "frequency": "one-time", + "channels": ["github-sponsors", "paypal-sponsors"] + } + ], + "history": [] + } +} diff --git a/package-lock.json b/package-lock.json index ec29866a7..2c64259da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,6 +44,7 @@ "vue-loading-overlay": "^3.3.3", "vue-timeago": "^5.1.2", "vue-tribute": "^1.0.7", + "webgl-media-editor": "^0.0.1", "zuck.js": "^1.6.0" }, "devDependencies": { @@ -97,9 +98,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", - "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", "engines": { "node": ">=6.9.0" } @@ -142,12 +143,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -167,18 +168,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.25.9.tgz", - "integrity": "sha512-C47lC7LIDCnz0h4vai/tpNOI95tCd5ZT3iBt/DBH5lXKHZsyNQv18yf1wIIg2ntiQNgmAvA+DgZ82iW8Qdym8g==", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-compilation-targets": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", @@ -231,12 +220,12 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.9.tgz", - "integrity": "sha512-ORPNZ3h6ZRkOyAa/SaHU+XsLZr0UQzRwuDQ0cczIA17nAzZ+85G5cVkOJIj7QavLZGSe8QXUmNFxSZzjcZF9bw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", + "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", - "regexpu-core": "^6.1.1", + "regexpu-core": "^6.2.0", "semver": "^6.3.1" }, "engines": { @@ -255,9 +244,9 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", - "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", + "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", "@babel/helper-plugin-utils": "^7.22.5", @@ -360,18 +349,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-simple-access": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", - "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", - "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", @@ -434,11 +411,11 @@ } }, "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -827,11 +804,10 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.25.9.tgz", - "integrity": "sha512-KRhdhlVk2nObA5AYa7QMgTMTVJdfHprfpAk4DjZVtllqRg9qarilstTKEhpVjyt+Npi8ThRyiV8176Am3CodPA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", + "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.25.9", "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { @@ -958,13 +934,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.25.9.tgz", - "integrity": "sha512-dwh2Ol1jWwL2MgkCzUSOvfmKElqQcuswAZypBSUsScMXvgdT8Ekq5YA6TtqpTVWH+4903NmboMuH1o9i8Rxlyg==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz", + "integrity": "sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==", "dependencies": { - "@babel/helper-module-transforms": "^7.25.9", - "@babel/helper-plugin-utils": "^7.25.9", - "@babel/helper-simple-access": "^7.25.9" + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helper-plugin-utils": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -1510,15 +1485,15 @@ } }, "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", + "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1527,9 +1502,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" @@ -1909,9 +1884,9 @@ } }, "node_modules/@glidejs/glide": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/@glidejs/glide/-/glide-3.6.2.tgz", - "integrity": "sha512-oXw7In0IZV69PC0PChQakY+yh+UnqIb5+zfVuEIzub6Kkfl1foo7TAhr2PZXPzihOG9YS57t8wvdzBFEZ0aPVA==" + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@glidejs/glide/-/glide-3.7.1.tgz", + "integrity": "sha512-8he0pZpLSqTesSFYiuWdhBmtdlYSPWxfPUCKtkkiX6ZmT8UdMdmoFPtJaOwLBXv4p2JiGxqbuPdiRGGo2e/htQ==" }, "node_modules/@hcaptcha/vue-hcaptcha": { "version": "1.3.0", @@ -1922,9 +1897,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", - "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -2599,11 +2574,11 @@ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==" }, "node_modules/@types/node": { - "version": "22.9.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz", - "integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==", + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", "dependencies": { - "undici-types": "~6.19.8" + "undici-types": "~6.20.0" } }, "node_modules/@types/node-forge": { @@ -2947,9 +2922,9 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==" }, "node_modules/@zip.js/zip.js": { - "version": "2.7.53", - "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.53.tgz", - "integrity": "sha512-G6Bl5wN9EXXVaTUIox71vIX5Z454zEBe+akKpV4m1tUboIctT5h7ID3QXCJd/Lfy2rSvmkTmZIucf1jGRR4f5A==", + "version": "2.7.54", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.54.tgz", + "integrity": "sha512-qMrJVg2hoEsZJjMJez9yI2+nZlBUxgYzGV3mqcb2B/6T1ihXp0fWBDYlVHlHquuorgNUQP5a8qSmX6HF5rFJNg==", "engines": { "bun": ">=0.7.0", "deno": ">=1.0.0", @@ -3165,9 +3140,9 @@ } }, "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" }, "node_modules/assert": { "version": "1.5.1", @@ -3234,9 +3209,9 @@ } }, "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", "dev": true, "dependencies": { "follow-redirects": "^1.15.6", @@ -3244,6 +3219,11 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==" + }, "node_modules/babel-helper-vue-jsx-merge-props": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-2.0.3.tgz", @@ -3268,12 +3248,12 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", - "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", + "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", "dependencies": { "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.2", + "@babel/helper-define-polyfill-provider": "^0.6.3", "semver": "^6.3.1" }, "peerDependencies": { @@ -3301,11 +3281,11 @@ } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", - "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", + "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2" + "@babel/helper-define-polyfill-provider": "^0.6.3" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -3374,9 +3354,9 @@ } }, "node_modules/bigpicture": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/bigpicture/-/bigpicture-2.6.2.tgz", - "integrity": "sha512-IZmRDr7ZSJLDtDvOP/dfqvJhMBqV/tGQAl3UgvkaCzePIkHYlYV/uCg9349E4RPF3ng7QM+eqFZ2CY7tE6pLMA==" + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/bigpicture/-/bigpicture-2.6.3.tgz", + "integrity": "sha512-Zmnca4YpQn3iygBNSxiyILBVk/a0nvdn3BAmNyZ3KLG5aasDYg+f0oEBqsbohL2YPS5UPqrk3NZH2zXSSHDKIg==" }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -3558,10 +3538,24 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/bonjour-service": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", - "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", + "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" @@ -3705,9 +3699,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "funding": [ { "type": "opencollective", @@ -3723,9 +3717,9 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { @@ -3782,15 +3776,41 @@ } }, "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dependencies": { + "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -3828,9 +3848,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001679", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001679.tgz", - "integrity": "sha512-j2YqID/YwpLnKzCmBOS4tlZdWprXm3ZmQLBH9ZBXFOhoxLA46fwyBvx6toCBWBmnuwUY/qB3kEU6gFx8qgCroA==", + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "funding": [ { "type": "opencollective", @@ -3968,12 +3988,15 @@ } }, "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", + "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" } }, "node_modules/clean-css": { @@ -4290,9 +4313,9 @@ } }, "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" }, "node_modules/create-hash": { "version": "1.2.0", @@ -4367,9 +4390,9 @@ } }, "node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", + "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", "dev": true, "dependencies": { "nice-try": "^1.0.4", @@ -4652,9 +4675,9 @@ "dev": true }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dependencies": { "ms": "^2.1.3" }, @@ -4828,9 +4851,9 @@ } }, "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" }, "node_modules/dir-glob": { "version": "3.0.1", @@ -4969,20 +4992,33 @@ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.5.55", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.55.tgz", - "integrity": "sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==" + "version": "1.5.75", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.75.tgz", + "integrity": "sha512-Lf3++DumRE/QmweGjU+ZcKqQ+3bKkU/qjaKYhIJKEOhgIO9Xs6IiAQFkfFoj+RhgDk4LUeNsLo6plExHqSyu6Q==" }, "node_modules/elliptic": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.0.tgz", - "integrity": "sha512-dpwoQcLc/2WLQvJvLRHKZ+f9FgOdjnq11rurqwekGQygGPsYSK29OMMD2WalatiqQ+XGFDglTNixpPfI+lpaAA==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -4994,9 +5030,9 @@ } }, "node_modules/elliptic/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -5028,9 +5064,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.17.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "version": "5.18.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -5072,12 +5108,9 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "engines": { "node": ">= 0.4" } @@ -5095,6 +5128,17 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==" }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-object-assign": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", @@ -5263,9 +5307,9 @@ } }, "node_modules/execa/node_modules/cross-spawn": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", - "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -5325,9 +5369,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -5348,7 +5392,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -5363,6 +5407,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/debug": { @@ -5378,6 +5426,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/express/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -5742,15 +5804,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -5775,6 +5842,12 @@ "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" }, + "node_modules/gl-matrix": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.3.tgz", + "integrity": "sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==", + "license": "MIT" + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5847,11 +5920,11 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5891,21 +5964,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "engines": { "node": ">= 0.4" }, @@ -5914,15 +5976,15 @@ } }, "node_modules/hash-base": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "integrity": "sha512-EeeoJKjTyt868liAlVmcv2ZsUfGHlE3Q+BICOXcZiwN3osr5Q/zFGYmTJpoIzuaSTAwndFy+GqhEwlU4L3j4Ow==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1" }, "engines": { - "node": ">=4" + "node": ">= 0.10" } }, "node_modules/hash-sum": { @@ -5959,9 +6021,9 @@ } }, "node_modules/hls.js": { - "version": "1.5.17", - "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.17.tgz", - "integrity": "sha512-wA66nnYFvQa1o4DO/BFgLNRKnBTVXpNeldGRBJ2Y0SvFtdwvFKCbqa9zhHoZLoxHhZ+jYsj3aIBkWQQCPNOhMw==" + "version": "1.5.18", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.5.18.tgz", + "integrity": "sha512-znxR+2jecWluu/0KOBqUcvVyAB5tLff10vjMGrpAlz1eFY+ZhF1bY3r82V+Bk7WJdk03iTjtja9KFFz5BrqjSA==" }, "node_modules/hmac-drbg": { "version": "1.0.1", @@ -6292,9 +6354,9 @@ } }, "node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz", + "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==", "dev": true }, "node_modules/import-fresh": { @@ -6423,9 +6485,9 @@ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "node_modules/is-core-module": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.0.tgz", + "integrity": "sha512-urTSINYfAYgcbLb0yDQ6egFm6h3Mo1DcF9EkyXSRjjzdHbsulg01qhwWuXdOoUBuTkbQ80KDboXa0vFJ+BDH+g==", "dependencies": { "hasown": "^2.0.2" }, @@ -6623,9 +6685,9 @@ "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" }, "node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "bin": { "jsesc": "bin/jsesc" }, @@ -6690,9 +6752,9 @@ } }, "node_modules/laravel-echo": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.16.1.tgz", - "integrity": "sha512-++Ylb6M3ariC9Rk5WE5gZjj6wcEV5kvLF8b+geJ5/rRIfdoOA+eG6b9qJPrarMD9rY28Apx+l3eelIrCc2skVg==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.17.1.tgz", + "integrity": "sha512-ORWc4vDfnBj/Oe5ThZ5kYyGItRjLDqAQUyhD/7UhehUOqc+s5x9HEBjtMVludNMP6VuXw6t7Uxt8bp63kaTofg==", "dev": true, "engines": { "node": ">=10" @@ -6975,6 +7037,14 @@ "semver": "bin/semver.js" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/md5": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", @@ -7082,9 +7152,9 @@ } }, "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" }, "node_modules/mime": { "version": "1.6.0", @@ -7240,9 +7310,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", @@ -7395,9 +7465,9 @@ } }, "node_modules/node-gyp-build": { - "version": "4.8.2", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", - "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "optional": true, "bin": { "node-gyp-build": "bin.js", @@ -7474,9 +7544,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -7555,13 +7625,15 @@ } }, "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", "object-keys": "^1.1.1" }, "engines": { @@ -7817,9 +7889,9 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/path-type": { "version": "4.0.0", @@ -7902,9 +7974,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { "type": "opencollective", @@ -7921,7 +7993,7 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -8180,12 +8252,12 @@ } }, "node_modules/postcss-modules-local-by-default": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", - "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", + "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "dependencies": { "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" }, "engines": { @@ -8195,12 +8267,24 @@ "postcss": "^8.1.0" } }, - "node_modules/postcss-modules-scope": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", - "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", "dependencies": { - "postcss-selector-parser": "^6.0.4" + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", + "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", + "dependencies": { + "postcss-selector-parser": "^7.0.0" }, "engines": { "node": "^10 || ^12 || >= 14" @@ -8209,6 +8293,18 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/postcss-modules-values": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", @@ -8558,9 +8654,9 @@ } }, "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==" }, "node_modules/pump": { "version": "3.0.2", @@ -8594,9 +8690,9 @@ "dev": true }, "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "version": "6.13.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz", + "integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==", "dependencies": { "side-channel": "^1.0.6" }, @@ -8795,14 +8891,14 @@ "dev": true }, "node_modules/regexpu-core": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.1.1.tgz", - "integrity": "sha512-k67Nb9jvwJcJmVpw0jPttR1/zVfnKf8Km0IPatrU/zJ5XeG3+Slx0xLXs9HByJSzXzrlz5EDvN6yLNMDc2qdnw==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", + "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.0", "regjsgen": "^0.8.0", - "regjsparser": "^0.11.0", + "regjsparser": "^0.12.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.1.0" }, @@ -8816,9 +8912,9 @@ "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==" }, "node_modules/regjsparser": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.11.2.tgz", - "integrity": "sha512-3OGZZ4HoLJkkAZx/48mTXJNlmqTGOzc0o9OWQPuWpkOlXXPbyN6OafCcoXUnBqE2D3f/T5L+pWc1kdEmnfnRsA==", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", + "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", "dependencies": { "jsesc": "~3.0.2" }, @@ -8826,6 +8922,17 @@ "regjsparser": "bin/parser" } }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", @@ -8864,17 +8971,20 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dependencies": { - "is-core-module": "^2.13.0", + "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -9035,13 +9145,13 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.80.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.6.tgz", - "integrity": "sha512-ccZgdHNiBF1NHBsWvacvT5rju3y1d/Eu+8Ex6c21nHp2lZGLBEtuwc415QfiI1PJa1TpCo3iXwwSRjRpn2Ckjg==", + "version": "1.83.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.83.0.tgz", + "integrity": "sha512-qsSxlayzoOjdvXMVLkzF84DJFc2HZEL/rFyGIKbbilYtAvlCxyuzUeff9LawTn4btVnLKg75Z8MMr1lxU1lfGw==", "dev": true, "dependencies": { "chokidar": "^4.0.0", - "immutable": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -9093,9 +9203,9 @@ } }, "node_modules/sass/node_modules/chokidar": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", - "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, "dependencies": { "readdirp": "^4.0.1" @@ -9370,9 +9480,12 @@ } }, "node_modules/shell-quote": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", - "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -9383,14 +9496,65 @@ "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==" }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -9668,9 +9832,9 @@ } }, "node_modules/streamx": { - "version": "2.20.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.1.tgz", - "integrity": "sha512-uTa0mU6WUC65iUvzKH4X9hEdvSW7rbPxPtwfWiLMSj3qTdQbAiUboZTxauKfpFuGIGa1C2BYijZ7wgdUXICJhA==", + "version": "2.21.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.21.1.tgz", + "integrity": "sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==", "dependencies": { "fast-fifo": "^1.3.2", "queue-tick": "^1.0.1", @@ -9895,9 +10059,9 @@ } }, "node_modules/terser": { - "version": "5.36.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", - "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", + "version": "5.37.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz", + "integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -9912,15 +10076,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.10", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", - "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.11.tgz", + "integrity": "sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.20", + "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.26.0" + "schema-utils": "^4.3.0", + "serialize-javascript": "^6.0.2", + "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" @@ -9944,14 +10108,46 @@ } } }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 10.13.0" @@ -9967,9 +10163,21 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, "node_modules/text-decoder": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.1.tgz", - "integrity": "sha512-x9v3H/lTKIJKQQe7RPQkLfKAnc9lUTkWDypIQgTzPJAq+5/GCDHonmshfvlsNSj58yyshbIJJDLmU15qNERrXQ==" + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } }, "node_modules/thunky": { "version": "1.1.0", @@ -10048,6 +10256,12 @@ "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "dev": true }, + "node_modules/twgl.js": { + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/twgl.js/-/twgl.js-5.5.4.tgz", + "integrity": "sha512-6kFOmijOpmblTN9CCwOTCxK4lPg7rCyQjLuub6EMOlEp89Ex6yUcsMjsmH7andNPL2NE3XmHdqHeP5gVKKPhxw==", + "license": "MIT" + }, "node_modules/twitter-text": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/twitter-text/-/twitter-text-2.0.5.tgz", @@ -10077,9 +10291,9 @@ } }, "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.1", @@ -10535,21 +10749,33 @@ "node": ">= 8" } }, + "node_modules/webgl-media-editor": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/webgl-media-editor/-/webgl-media-editor-0.0.1.tgz", + "integrity": "sha512-TxnuRl3rpWa1Cia/pn+vh+0iz3yDNwzsrnRGJ61YkdZAYuimu2afBivSHv0RK73hKza6Y/YoRCkuEcsFmtxPNw==", + "license": "AGPL-3.0-only", + "dependencies": { + "cropperjs": "^1.6.2", + "gl-matrix": "^3.4.3", + "throttle-debounce": "^5.0.2", + "twgl.js": "^5.5.4" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/webpack": { - "version": "5.96.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz", - "integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==", + "version": "5.97.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", + "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.6", - "@webassemblyjs/ast": "^1.12.1", - "@webassemblyjs/wasm-edit": "^1.12.1", - "@webassemblyjs/wasm-parser": "^1.12.1", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.14.0", "browserslist": "^4.24.0", "chrome-trace-event": "^1.0.2", @@ -10632,9 +10858,9 @@ } }, "node_modules/webpack-cli/node_modules/cross-spawn": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.5.tgz", - "integrity": "sha512-ZVJrKKYunU38/76t0RMOulHOnUcbU9GbpWKAOZ0mhjr7CX6FVrH+4FrAapSOekrgFQ3f/8gwMEuIft0aKq6Hug==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -10739,9 +10965,9 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/webpack-dev-middleware/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -10749,7 +10975,7 @@ "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", @@ -10846,9 +11072,9 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", - "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", + "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -10856,7 +11082,7 @@ "ajv-keywords": "^5.1.0" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10.13.0" }, "funding": { "type": "opencollective", diff --git a/package.json b/package.json index 7724f040c..691972ced 100644 --- a/package.json +++ b/package.json @@ -71,6 +71,7 @@ "vue-loading-overlay": "^3.3.3", "vue-timeago": "^5.1.2", "vue-tribute": "^1.0.7", + "webgl-media-editor": "^0.0.1", "zuck.js": "^1.6.0" }, "collective": { diff --git a/public/_lang/af.json b/public/_lang/af.json index 1acb08636..fc91f9371 100644 Binary files a/public/_lang/af.json and b/public/_lang/af.json differ diff --git a/public/_lang/ar.json b/public/_lang/ar.json index d88d5693b..a4dcab05b 100644 Binary files a/public/_lang/ar.json and b/public/_lang/ar.json differ diff --git a/public/_lang/bn.json b/public/_lang/bn.json index 1acb08636..d98e3dc1f 100644 Binary files a/public/_lang/bn.json and b/public/_lang/bn.json differ diff --git a/public/_lang/bs.json b/public/_lang/bs.json index 1acb08636..fc91f9371 100644 Binary files a/public/_lang/bs.json and b/public/_lang/bs.json differ diff --git a/public/_lang/ca.json b/public/_lang/ca.json index c12749db0..390c83f01 100644 Binary files a/public/_lang/ca.json and b/public/_lang/ca.json differ diff --git a/public/_lang/cs.json b/public/_lang/cs.json index fcf926ddf..245f50b88 100644 Binary files a/public/_lang/cs.json and b/public/_lang/cs.json differ diff --git a/public/_lang/cy.json b/public/_lang/cy.json index d21b67253..a544ff1a2 100644 Binary files a/public/_lang/cy.json and b/public/_lang/cy.json differ diff --git a/public/_lang/da.json b/public/_lang/da.json index 1acb08636..370ab2d03 100644 Binary files a/public/_lang/da.json and b/public/_lang/da.json differ diff --git a/public/_lang/de.json b/public/_lang/de.json index 653f352dc..048814f67 100644 Binary files a/public/_lang/de.json and b/public/_lang/de.json differ diff --git a/public/_lang/el.json b/public/_lang/el.json index a28e536a0..7d03b00b7 100644 Binary files a/public/_lang/el.json and b/public/_lang/el.json differ diff --git a/public/_lang/es.json b/public/_lang/es.json index a5b24ea1e..fcbe4b17f 100644 Binary files a/public/_lang/es.json and b/public/_lang/es.json differ diff --git a/public/_lang/eu.json b/public/_lang/eu.json index 4561c0e7f..1608126f1 100644 Binary files a/public/_lang/eu.json and b/public/_lang/eu.json differ diff --git a/public/_lang/fa.json b/public/_lang/fa.json index 8033bad73..1b18e4c6d 100644 Binary files a/public/_lang/fa.json and b/public/_lang/fa.json differ diff --git a/public/_lang/fi.json b/public/_lang/fi.json index 1acb08636..1f9fa01d6 100644 Binary files a/public/_lang/fi.json and b/public/_lang/fi.json differ diff --git a/public/_lang/fr.json b/public/_lang/fr.json index 925028a2c..bf9af15af 100644 Binary files a/public/_lang/fr.json and b/public/_lang/fr.json differ diff --git a/public/_lang/gd.json b/public/_lang/gd.json index 112a79ded..420f637d8 100644 Binary files a/public/_lang/gd.json and b/public/_lang/gd.json differ diff --git a/public/_lang/gl.json b/public/_lang/gl.json index 68b685273..43902057f 100644 Binary files a/public/_lang/gl.json and b/public/_lang/gl.json differ diff --git a/public/_lang/he.json b/public/_lang/he.json index c434c9eb7..ceefac458 100644 Binary files a/public/_lang/he.json and b/public/_lang/he.json differ diff --git a/public/_lang/hi.json b/public/_lang/hi.json index 1acb08636..9ca7e7d23 100644 Binary files a/public/_lang/hi.json and b/public/_lang/hi.json differ diff --git a/public/_lang/hr.json b/public/_lang/hr.json index 1acb08636..fc91f9371 100644 Binary files a/public/_lang/hr.json and b/public/_lang/hr.json differ diff --git a/public/_lang/hu.json b/public/_lang/hu.json index 2db23adba..c58426a4e 100644 Binary files a/public/_lang/hu.json and b/public/_lang/hu.json differ diff --git a/public/_lang/id.json b/public/_lang/id.json index 37fca65dd..554eb7487 100644 Binary files a/public/_lang/id.json and b/public/_lang/id.json differ diff --git a/public/_lang/it.json b/public/_lang/it.json index 5e30d2e69..3cd1e2ced 100644 Binary files a/public/_lang/it.json and b/public/_lang/it.json differ diff --git a/public/_lang/ja.json b/public/_lang/ja.json index f52fbe6d3..cd6ca79d7 100644 Binary files a/public/_lang/ja.json and b/public/_lang/ja.json differ diff --git a/public/_lang/ko.json b/public/_lang/ko.json index 456929703..c0dca1379 100644 Binary files a/public/_lang/ko.json and b/public/_lang/ko.json differ diff --git a/public/_lang/me.json b/public/_lang/me.json index 1acb08636..fc91f9371 100644 Binary files a/public/_lang/me.json and b/public/_lang/me.json differ diff --git a/public/_lang/mk.json b/public/_lang/mk.json index 1acb08636..fc91f9371 100644 Binary files a/public/_lang/mk.json and b/public/_lang/mk.json differ diff --git a/public/_lang/nl.json b/public/_lang/nl.json index 9d49929a1..d8d468338 100644 Binary files a/public/_lang/nl.json and b/public/_lang/nl.json differ diff --git a/public/_lang/no.json b/public/_lang/no.json index 1acb08636..a9a97c18f 100644 Binary files a/public/_lang/no.json and b/public/_lang/no.json differ diff --git a/public/_lang/oc.json b/public/_lang/oc.json index 0fca07e45..ab65f4408 100644 Binary files a/public/_lang/oc.json and b/public/_lang/oc.json differ diff --git a/public/_lang/pl.json b/public/_lang/pl.json index fbbcf029e..34ebf7647 100644 Binary files a/public/_lang/pl.json and b/public/_lang/pl.json differ diff --git a/public/_lang/pt.json b/public/_lang/pt.json index b98b0a5d1..f3eb9a64f 100644 Binary files a/public/_lang/pt.json and b/public/_lang/pt.json differ diff --git a/public/_lang/ro.json b/public/_lang/ro.json index 1acb08636..22b6299ce 100644 Binary files a/public/_lang/ro.json and b/public/_lang/ro.json differ diff --git a/public/_lang/ru.json b/public/_lang/ru.json index 81e62649a..2cad80bf8 100644 Binary files a/public/_lang/ru.json and b/public/_lang/ru.json differ diff --git a/public/_lang/sk.json b/public/_lang/sk.json index 31f53469d..cd7536be4 100644 Binary files a/public/_lang/sk.json and b/public/_lang/sk.json differ diff --git a/public/_lang/sr.json b/public/_lang/sr.json index 1acb08636..b52728000 100644 Binary files a/public/_lang/sr.json and b/public/_lang/sr.json differ diff --git a/public/_lang/sv.json b/public/_lang/sv.json index 1acb08636..36badbb9d 100644 Binary files a/public/_lang/sv.json and b/public/_lang/sv.json differ diff --git a/public/_lang/th.json b/public/_lang/th.json index 5445b0116..9da33b30b 100644 Binary files a/public/_lang/th.json and b/public/_lang/th.json differ diff --git a/public/_lang/tr.json b/public/_lang/tr.json index 274abb51d..49d1ce183 100644 Binary files a/public/_lang/tr.json and b/public/_lang/tr.json differ diff --git a/public/_lang/vi.json b/public/_lang/vi.json index 3f76b862f..a0433015a 100644 Binary files a/public/_lang/vi.json and b/public/_lang/vi.json differ diff --git a/public/_lang/zh.json b/public/_lang/zh.json index 1acb08636..d0d6c25c8 100644 Binary files a/public/_lang/zh.json and b/public/_lang/zh.json differ diff --git a/public/js/changelog.bundle.7fc2ee6c4475458c.js b/public/js/changelog.bundle.d40f01eba00c9885.js similarity index 100% rename from public/js/changelog.bundle.7fc2ee6c4475458c.js rename to public/js/changelog.bundle.d40f01eba00c9885.js diff --git a/public/js/compose.chunk.b06beb250e24db17.js b/public/js/compose.chunk.b06beb250e24db17.js new file mode 100644 index 000000000..ccd32b23e Binary files /dev/null and b/public/js/compose.chunk.b06beb250e24db17.js differ diff --git a/public/js/compose.chunk.e1f297b242137d23.js.LICENSE.txt b/public/js/compose.chunk.b06beb250e24db17.js.LICENSE.txt similarity index 100% rename from public/js/compose.chunk.e1f297b242137d23.js.LICENSE.txt rename to public/js/compose.chunk.b06beb250e24db17.js.LICENSE.txt diff --git a/public/js/compose.chunk.e1f297b242137d23.js b/public/js/compose.chunk.e1f297b242137d23.js deleted file mode 100644 index 91ed42f8b..000000000 Binary files a/public/js/compose.chunk.e1f297b242137d23.js and /dev/null differ diff --git a/public/js/compose.js b/public/js/compose.js index 358d724f3..8e1ab44ca 100644 Binary files a/public/js/compose.js and b/public/js/compose.js differ diff --git a/public/js/daci.chunk.3ed914c15dec4ff4.js b/public/js/daci.chunk.61b540b1630f8445.js similarity index 100% rename from public/js/daci.chunk.3ed914c15dec4ff4.js rename to public/js/daci.chunk.61b540b1630f8445.js diff --git a/public/js/discover.chunk.2986d7e977f5188a.js b/public/js/discover.chunk.00d9b5656d32080e.js similarity index 100% rename from public/js/discover.chunk.2986d7e977f5188a.js rename to public/js/discover.chunk.00d9b5656d32080e.js diff --git a/public/js/discover~findfriends.chunk.84758c764668a02c.js b/public/js/discover~findfriends.chunk.6d494abb9e464081.js similarity index 100% rename from public/js/discover~findfriends.chunk.84758c764668a02c.js rename to public/js/discover~findfriends.chunk.6d494abb9e464081.js diff --git a/public/js/discover~hashtag.bundle.93ce902dca5b65e3.js b/public/js/discover~hashtag.bundle.93ce902dca5b65e3.js new file mode 100644 index 000000000..556e69eb8 Binary files /dev/null and b/public/js/discover~hashtag.bundle.93ce902dca5b65e3.js differ diff --git a/public/js/discover~hashtag.bundle.db1d86f9e9dcb79a.js b/public/js/discover~hashtag.bundle.db1d86f9e9dcb79a.js deleted file mode 100644 index b6c51b20b..000000000 Binary files a/public/js/discover~hashtag.bundle.db1d86f9e9dcb79a.js and /dev/null differ diff --git a/public/js/discover~memories.chunk.3b45432a80b08e9b.js b/public/js/discover~memories.chunk.9541b66de9d5d907.js similarity index 100% rename from public/js/discover~memories.chunk.3b45432a80b08e9b.js rename to public/js/discover~memories.chunk.9541b66de9d5d907.js diff --git a/public/js/discover~myhashtags.chunk.67fd16950ee21ad8.js b/public/js/discover~myhashtags.chunk.e2ca0db60346d0c2.js similarity index 100% rename from public/js/discover~myhashtags.chunk.67fd16950ee21ad8.js rename to public/js/discover~myhashtags.chunk.e2ca0db60346d0c2.js diff --git a/public/js/discover~serverfeed.chunk.93bc564867eaa7c3.js b/public/js/discover~serverfeed.chunk.138d9d53d1debac1.js similarity index 100% rename from public/js/discover~serverfeed.chunk.93bc564867eaa7c3.js rename to public/js/discover~serverfeed.chunk.138d9d53d1debac1.js diff --git a/public/js/discover~settings.chunk.950c11c918a541b0.js b/public/js/discover~settings.chunk.b1b5642ccef06123.js similarity index 100% rename from public/js/discover~settings.chunk.950c11c918a541b0.js rename to public/js/discover~settings.chunk.b1b5642ccef06123.js diff --git a/public/js/dms.chunk.b7e970fb49da0199.js b/public/js/dms.chunk.1a2a644df5c78346.js similarity index 100% rename from public/js/dms.chunk.b7e970fb49da0199.js rename to public/js/dms.chunk.1a2a644df5c78346.js diff --git a/public/js/dms~message.chunk.011f31232754f650.js b/public/js/dms~message.chunk.4e68bb824f396d86.js similarity index 100% rename from public/js/dms~message.chunk.011f31232754f650.js rename to public/js/dms~message.chunk.4e68bb824f396d86.js diff --git a/public/js/error404.bundle.ad885ef6f9b2c101.js b/public/js/error404.bundle.e2f43f5006962e80.js similarity index 100% rename from public/js/error404.bundle.ad885ef6f9b2c101.js rename to public/js/error404.bundle.e2f43f5006962e80.js diff --git a/public/js/group.create.0d645a1de271e28d.js b/public/js/group.create.72c3a1e5c1dc00dc.js similarity index 100% rename from public/js/group.create.0d645a1de271e28d.js rename to public/js/group.create.72c3a1e5c1dc00dc.js diff --git a/public/js/groups-page-about.06576420562628e3.js b/public/js/groups-page-about.76a616aa7e1a367b.js similarity index 100% rename from public/js/groups-page-about.06576420562628e3.js rename to public/js/groups-page-about.76a616aa7e1a367b.js diff --git a/public/js/groups-page-media.f611a51e684c48ef.js b/public/js/groups-page-media.056a7bbc46b79034.js similarity index 100% rename from public/js/groups-page-media.f611a51e684c48ef.js rename to public/js/groups-page-media.056a7bbc46b79034.js diff --git a/public/js/groups-page-members.bfdefdd66058e838.js b/public/js/groups-page-members.a8ea4f209fcbe238.js similarity index 100% rename from public/js/groups-page-members.bfdefdd66058e838.js rename to public/js/groups-page-members.a8ea4f209fcbe238.js diff --git a/public/js/groups-page-topics.431ebaf843ca9b16.js b/public/js/groups-page-topics.f69667c933f7d122.js similarity index 100% rename from public/js/groups-page-topics.431ebaf843ca9b16.js rename to public/js/groups-page-topics.f69667c933f7d122.js diff --git a/public/js/groups-page.53eccead9512c61f.js b/public/js/groups-page.d484dab549a033ca.js similarity index 100% rename from public/js/groups-page.53eccead9512c61f.js rename to public/js/groups-page.d484dab549a033ca.js diff --git a/public/js/groups-post.639cb121bdc6f4a7.js b/public/js/groups-post.4c3d4860b029bbaf.js similarity index 100% rename from public/js/groups-post.639cb121bdc6f4a7.js rename to public/js/groups-post.4c3d4860b029bbaf.js diff --git a/public/js/groups-profile.3b11ffa46ae76520.js b/public/js/groups-profile.1bb8be935d1f108a.js similarity index 100% rename from public/js/groups-profile.3b11ffa46ae76520.js rename to public/js/groups-profile.1bb8be935d1f108a.js diff --git a/public/js/home.chunk.c362371940daf318.js b/public/js/home.chunk.acf96f52790bffa6.js similarity index 99% rename from public/js/home.chunk.c362371940daf318.js rename to public/js/home.chunk.acf96f52790bffa6.js index 4ebd0feb2..bfc47a564 100644 Binary files a/public/js/home.chunk.c362371940daf318.js and b/public/js/home.chunk.acf96f52790bffa6.js differ diff --git a/public/js/home.chunk.c362371940daf318.js.LICENSE.txt b/public/js/home.chunk.acf96f52790bffa6.js.LICENSE.txt similarity index 100% rename from public/js/home.chunk.c362371940daf318.js.LICENSE.txt rename to public/js/home.chunk.acf96f52790bffa6.js.LICENSE.txt diff --git a/public/js/i18n.bundle.882da44b752e4e1a.js b/public/js/i18n.bundle.951c52d1740442f6.js similarity index 100% rename from public/js/i18n.bundle.882da44b752e4e1a.js rename to public/js/i18n.bundle.951c52d1740442f6.js diff --git a/public/js/landing.js b/public/js/landing.js index 3069baa87..5d9df030e 100644 Binary files a/public/js/landing.js and b/public/js/landing.js differ diff --git a/public/js/manifest.js b/public/js/manifest.js index 38f8d015d..196fbee81 100644 Binary files a/public/js/manifest.js and b/public/js/manifest.js differ diff --git a/public/js/notifications.chunk.8c41265737b2568a.js b/public/js/notifications.chunk.a882b19f4469ed55.js similarity index 100% rename from public/js/notifications.chunk.8c41265737b2568a.js rename to public/js/notifications.chunk.a882b19f4469ed55.js diff --git a/public/js/post.chunk.5f457aeaa4ae598c.js b/public/js/post.chunk.0757a6f1391ea041.js similarity index 99% rename from public/js/post.chunk.5f457aeaa4ae598c.js rename to public/js/post.chunk.0757a6f1391ea041.js index 8b86545da..9e69d8bfc 100644 Binary files a/public/js/post.chunk.5f457aeaa4ae598c.js and b/public/js/post.chunk.0757a6f1391ea041.js differ diff --git a/public/js/post.chunk.5f457aeaa4ae598c.js.LICENSE.txt b/public/js/post.chunk.0757a6f1391ea041.js.LICENSE.txt similarity index 100% rename from public/js/post.chunk.5f457aeaa4ae598c.js.LICENSE.txt rename to public/js/post.chunk.0757a6f1391ea041.js.LICENSE.txt diff --git a/public/js/profile.chunk.9e77e21e157a47c5.js b/public/js/profile.chunk.5a5ea597e5286899.js similarity index 100% rename from public/js/profile.chunk.9e77e21e157a47c5.js rename to public/js/profile.chunk.5a5ea597e5286899.js diff --git a/public/js/profile~followers.bundle.f26ee6ed6ced9aa7.js b/public/js/profile~followers.bundle.a595bbddf5b02150.js similarity index 100% rename from public/js/profile~followers.bundle.f26ee6ed6ced9aa7.js rename to public/js/profile~followers.bundle.a595bbddf5b02150.js diff --git a/public/js/profile~following.bundle.4ac5466dca6ca1c4.js b/public/js/profile~following.bundle.1c64fbecaa81a368.js similarity index 100% rename from public/js/profile~following.bundle.4ac5466dca6ca1c4.js rename to public/js/profile~following.bundle.1c64fbecaa81a368.js diff --git a/public/js/spa.js b/public/js/spa.js index 5c1950c77..bd016167c 100644 Binary files a/public/js/spa.js and b/public/js/spa.js differ diff --git a/public/js/vendor.js b/public/js/vendor.js index 1753e176e..344264c2e 100644 Binary files a/public/js/vendor.js and b/public/js/vendor.js differ diff --git a/public/js/vendor.js.LICENSE.txt b/public/js/vendor.js.LICENSE.txt index 83e24868a..1632173a3 100644 --- a/public/js/vendor.js.LICENSE.txt +++ b/public/js/vendor.js.LICENSE.txt @@ -1,3 +1,7 @@ +/* @license twgl.js 5.5.4 Copyright (c) 2015, Gregg Tavares All Rights Reserved. +Available via the MIT license. +see: http://github.com/greggman/twgl.js for details */ + /*! * Bootstrap v4.6.2 (https://getbootstrap.com/) * Copyright 2011-2022 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) @@ -41,7 +45,7 @@ */ /*! - * Glide.js v3.6.2 + * Glide.js v3.7.1 * (c) 2013-2024 Jędrzej Chałubek (https://github.com/jedrzejchalubek/) * Released under the MIT License. */ diff --git a/public/mix-manifest.json b/public/mix-manifest.json index fa05d8a82..55c0b502e 100644 Binary files a/public/mix-manifest.json and b/public/mix-manifest.json differ diff --git a/resources/assets/components/Hashtag.vue b/resources/assets/components/Hashtag.vue index 01094a85b..62665c1db 100644 --- a/resources/assets/components/Hashtag.vue +++ b/resources/assets/components/Hashtag.vue @@ -1,325 +1,336 @@ -- #{{ hashtag.name }} -
-- {{ formatCount(hashtag.count) }} Posts -
-+ #{{ hashtag.name }} +
++ {{ formatCount(hashtag.count) }} Posts +
+{{ $t('hashtags.emptyFeed') }}
-{{ $t('hashtags.emptyFeed') }}
+