From a961552c23101bcf260aaf930a8362277852f67b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 5 Aug 2025 13:39:30 +0100 Subject: [PATCH 01/48] Commands: Updated create admin comment to accept extra flags Added flags to target changes to the first default admin user, and to generate a password. This is related to #4575. --- app/Console/Commands/CreateAdminCommand.php | 93 ++++++++++++++++----- app/Users/UserRepo.php | 19 ++++- 2 files changed, 90 insertions(+), 22 deletions(-) diff --git a/app/Console/Commands/CreateAdminCommand.php b/app/Console/Commands/CreateAdminCommand.php index 82b6e50aa..ce619e05d 100644 --- a/app/Console/Commands/CreateAdminCommand.php +++ b/app/Console/Commands/CreateAdminCommand.php @@ -21,7 +21,9 @@ class CreateAdminCommand extends Command {--email= : The email address for the new admin user} {--name= : The name of the new admin user} {--password= : The password to assign to the new admin user} - {--external-auth-id= : The external authentication system id for the new admin user (SAML2/LDAP/OIDC)}'; + {--external-auth-id= : The external authentication system id for the new admin user (SAML2/LDAP/OIDC)} + {--generate-password : Generate a random password for the new admin user} + {--first-admin : Indicate if this should set/update the details of the initial admin user}'; /** * The console command description. @@ -35,23 +37,9 @@ class CreateAdminCommand extends Command */ public function handle(UserRepo $userRepo): int { - $details = $this->snakeCaseOptions(); - - if (empty($details['email'])) { - $details['email'] = $this->ask('Please specify an email address for the new admin user'); - } - - if (empty($details['name'])) { - $details['name'] = $this->ask('Please specify a name for the new admin user'); - } - - if (empty($details['password'])) { - if (empty($details['external_auth_id'])) { - $details['password'] = $this->ask('Please specify a password for the new admin user (8 characters min)'); - } else { - $details['password'] = Str::random(32); - } - } + $firstAdminOnly = $this->option('first-admin'); + $shouldGeneratePassword = $this->option('generate-password'); + $details = $this->gatherDetails($shouldGeneratePassword); $validator = Validator::make($details, [ 'email' => ['required', 'email', 'min:5', new Unique('users', 'email')], @@ -68,16 +56,81 @@ class CreateAdminCommand extends Command return 1; } + $adminRole = Role::getSystemRole('admin'); + + if ($firstAdminOnly) { + $handled = $this->handleFirstAdminIfExists($userRepo, $details, $shouldGeneratePassword, $adminRole); + if ($handled) { + return 0; + } + } + $user = $userRepo->createWithoutActivity($validator->validated()); - $user->attachRole(Role::getSystemRole('admin')); + $user->attachRole($adminRole); $user->email_confirmed = true; $user->save(); - $this->info("Admin account with email \"{$user->email}\" successfully created!"); + if ($shouldGeneratePassword) { + $this->line($details['password']); + } else { + $this->info("Admin account with email \"{$user->email}\" successfully created!"); + } return 0; } + /** + * Handle updates to the first admin if exists. + * Returns true if the action has been handled (user updated or already a non-default admin user) otherwise + * returns false if no action has been taken, and we therefore need to proceed with a normal account creation. + */ + protected function handleFirstAdminIfExists(UserRepo $userRepo, array $data, bool $generatePassword, Role $adminRole): bool + { + $defaultAdmin = $userRepo->getByEmail('admin@admin.com'); + if ($defaultAdmin && $defaultAdmin->hasSystemRole('admin')) { + $userRepo->updateWithoutActivity($defaultAdmin, $data, true); + if ($generatePassword) { + $this->line($data['password']); + } else { + $this->info("The default admin user has been updated with the provided details!"); + } + + return true; + } else if ($adminRole->users()->count() > 0) { + $this->warn('Non-default admin user already exists. Skipping creation of new admin user.'); + return true; + } + + return false; + } + + protected function gatherDetails(bool $generatePassword): array + { + $details = $this->snakeCaseOptions(); + + if (empty($details['email'])) { + $details['email'] = $this->ask('Please specify an email address for the new admin user'); + } + + if (empty($details['name'])) { + $details['name'] = $this->ask('Please specify a name for the new admin user'); + } + + if (empty($details['password'])) { + if (empty($details['external_auth_id'])) { + if ($generatePassword) { + $details['password'] = Str::random(32); + } else { + $details['password'] = $this->ask('Please specify a password for the new admin user (8 characters min)'); + } + } else { + $details['password'] = Str::random(32); + } + } + + return $details; + } + protected function snakeCaseOptions(): array { $returnOpts = []; diff --git a/app/Users/UserRepo.php b/app/Users/UserRepo.php index 5c8ace8fa..d24f7002e 100644 --- a/app/Users/UserRepo.php +++ b/app/Users/UserRepo.php @@ -100,13 +100,13 @@ class UserRepo } /** - * Update the given user with the given data. + * Update the given user with the given data, but do not create an activity. * * @param array{name: ?string, email: ?string, external_auth_id: ?string, password: ?string, roles: ?array, language: ?string} $data * * @throws UserUpdateException */ - public function update(User $user, array $data, bool $manageUsersAllowed): User + public function updateWithoutActivity(User $user, array $data, bool $manageUsersAllowed): User { if (!empty($data['name'])) { $user->name = $data['name']; @@ -134,6 +134,21 @@ class UserRepo } $user->save(); + + return $user; + } + + /** + * Update the given user with the given data. + * + * @param array{name: ?string, email: ?string, external_auth_id: ?string, password: ?string, roles: ?array, language: ?string} $data + * + * @throws UserUpdateException + */ + public function update(User $user, array $data, bool $manageUsersAllowed): User + { + $user = $this->updateWithoutActivity($user, $data, $manageUsersAllowed); + Activity::add(ActivityType::USER_UPDATE, $user); return $user; From 2eefbd21c1cdb6a3b32529fd6db10f9bbfd4b9da Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 5 Aug 2025 16:43:06 +0100 Subject: [PATCH 02/48] Commands: Added testing for initial admin changes - Also changed first-admin to initial. - Updated initial handling to not require email/name to be passed, using defaults instead. - Adds missing existing email use check. --- app/Console/Commands/CreateAdminCommand.php | 53 ++++-- tests/Commands/CreateAdminCommandTest.php | 185 +++++++++++++++++++- 2 files changed, 212 insertions(+), 26 deletions(-) diff --git a/app/Console/Commands/CreateAdminCommand.php b/app/Console/Commands/CreateAdminCommand.php index ce619e05d..520f0822a 100644 --- a/app/Console/Commands/CreateAdminCommand.php +++ b/app/Console/Commands/CreateAdminCommand.php @@ -8,7 +8,6 @@ use Illuminate\Console\Command; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use Illuminate\Validation\Rules\Password; -use Illuminate\Validation\Rules\Unique; class CreateAdminCommand extends Command { @@ -23,7 +22,7 @@ class CreateAdminCommand extends Command {--password= : The password to assign to the new admin user} {--external-auth-id= : The external authentication system id for the new admin user (SAML2/LDAP/OIDC)} {--generate-password : Generate a random password for the new admin user} - {--first-admin : Indicate if this should set/update the details of the initial admin user}'; + {--initial : Indicate if this should set/update the details of the initial admin user}'; /** * The console command description. @@ -37,12 +36,12 @@ class CreateAdminCommand extends Command */ public function handle(UserRepo $userRepo): int { - $firstAdminOnly = $this->option('first-admin'); + $initialAdminOnly = $this->option('initial'); $shouldGeneratePassword = $this->option('generate-password'); - $details = $this->gatherDetails($shouldGeneratePassword); + $details = $this->gatherDetails($shouldGeneratePassword, $initialAdminOnly); $validator = Validator::make($details, [ - 'email' => ['required', 'email', 'min:5', new Unique('users', 'email')], + 'email' => ['required', 'email', 'min:5'], 'name' => ['required', 'min:2'], 'password' => ['required_without:external_auth_id', Password::default()], 'external_auth_id' => ['required_without:password'], @@ -58,13 +57,20 @@ class CreateAdminCommand extends Command $adminRole = Role::getSystemRole('admin'); - if ($firstAdminOnly) { - $handled = $this->handleFirstAdminIfExists($userRepo, $details, $shouldGeneratePassword, $adminRole); - if ($handled) { - return 0; + if ($initialAdminOnly) { + $handled = $this->handleInitialAdminIfExists($userRepo, $details, $shouldGeneratePassword, $adminRole); + if ($handled !== null) { + return $handled ? 0 : 1; } } + $emailUsed = $userRepo->getByEmail($details['email']) !== null; + if ($emailUsed) { + $this->error("Could not create admin account."); + $this->error("An account with the email address \"{$details['email']}\" already exists."); + return 1; + } + $user = $userRepo->createWithoutActivity($validator->validated()); $user->attachRole($adminRole); $user->email_confirmed = true; @@ -80,14 +86,19 @@ class CreateAdminCommand extends Command } /** - * Handle updates to the first admin if exists. - * Returns true if the action has been handled (user updated or already a non-default admin user) otherwise - * returns false if no action has been taken, and we therefore need to proceed with a normal account creation. + * Handle updates to the original admin account if it exists. + * Returns true if it's been successfully handled, false if unsuccessful, or null if not handled. */ - protected function handleFirstAdminIfExists(UserRepo $userRepo, array $data, bool $generatePassword, Role $adminRole): bool + protected function handleInitialAdminIfExists(UserRepo $userRepo, array $data, bool $generatePassword, Role $adminRole): bool|null { $defaultAdmin = $userRepo->getByEmail('admin@admin.com'); if ($defaultAdmin && $defaultAdmin->hasSystemRole('admin')) { + if ($defaultAdmin->email !== $data['email'] && $userRepo->getByEmail($data['email']) !== null) { + $this->error("Could not create admin account."); + $this->error("An account with the email address \"{$data['email']}\" already exists."); + return false; + } + $userRepo->updateWithoutActivity($defaultAdmin, $data, true); if ($generatePassword) { $this->line($data['password']); @@ -101,19 +112,27 @@ class CreateAdminCommand extends Command return true; } - return false; + return null; } - protected function gatherDetails(bool $generatePassword): array + protected function gatherDetails(bool $generatePassword, bool $initialAdmin): array { $details = $this->snakeCaseOptions(); if (empty($details['email'])) { - $details['email'] = $this->ask('Please specify an email address for the new admin user'); + if ($initialAdmin) { + $details['email'] = 'admin@example.com'; + } else { + $details['email'] = $this->ask('Please specify an email address for the new admin user'); + } } if (empty($details['name'])) { - $details['name'] = $this->ask('Please specify a name for the new admin user'); + if ($initialAdmin) { + $details['name'] = 'Admin'; + } else { + $details['name'] = $this->ask('Please specify a name for the new admin user'); + } } if (empty($details['password'])) { diff --git a/tests/Commands/CreateAdminCommandTest.php b/tests/Commands/CreateAdminCommandTest.php index 95a39c497..fa1329ad3 100644 --- a/tests/Commands/CreateAdminCommandTest.php +++ b/tests/Commands/CreateAdminCommandTest.php @@ -2,8 +2,11 @@ namespace Tests\Commands; +use BookStack\Users\Models\Role; use BookStack\Users\Models\User; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Hash; use Tests\TestCase; class CreateAdminCommandTest extends TestCase @@ -11,14 +14,14 @@ class CreateAdminCommandTest extends TestCase public function test_standard_command_usage() { $this->artisan('bookstack:create-admin', [ - '--email' => 'admintest@example.com', - '--name' => 'Admin Test', + '--email' => 'admintest@example.com', + '--name' => 'Admin Test', '--password' => 'testing-4', ])->assertExitCode(0); $this->assertDatabaseHas('users', [ 'email' => 'admintest@example.com', - 'name' => 'Admin Test', + 'name' => 'Admin Test', ]); /** @var User $user */ @@ -30,14 +33,14 @@ class CreateAdminCommandTest extends TestCase public function test_providing_external_auth_id() { $this->artisan('bookstack:create-admin', [ - '--email' => 'admintest@example.com', - '--name' => 'Admin Test', + '--email' => 'admintest@example.com', + '--name' => 'Admin Test', '--external-auth-id' => 'xX_admin_Xx', ])->assertExitCode(0); $this->assertDatabaseHas('users', [ - 'email' => 'admintest@example.com', - 'name' => 'Admin Test', + 'email' => 'admintest@example.com', + 'name' => 'Admin Test', 'external_auth_id' => 'xX_admin_Xx', ]); @@ -50,14 +53,178 @@ class CreateAdminCommandTest extends TestCase { $this->artisan('bookstack:create-admin', [ '--email' => 'admintest@example.com', - '--name' => 'Admin Test', + '--name' => 'Admin Test', ])->expectsQuestion('Please specify a password for the new admin user (8 characters min)', 'hunter2000') ->assertExitCode(0); $this->assertDatabaseHas('users', [ 'email' => 'admintest@example.com', - 'name' => 'Admin Test', + 'name' => 'Admin Test', ]); $this->assertTrue(Auth::attempt(['email' => 'admintest@example.com', 'password' => 'hunter2000'])); } + + public function test_generate_password_option() + { + $this->withoutMockingConsoleOutput() + ->artisan('bookstack:create-admin', [ + '--email' => 'admintest@example.com', + '--name' => 'Admin Test', + '--generate-password' => true, + ]); + + $output = trim(Artisan::output()); + $this->assertMatchesRegularExpression('/^[a-zA-Z0-9]{32}$/', $output); + + $user = User::query()->where('email', '=', 'admintest@example.com')->first(); + $this->assertTrue(Hash::check($output, $user->password)); + } + + public function test_initial_option_updates_default_admin() + { + $defaultAdmin = User::query()->where('email', '=', 'admin@admin.com')->first(); + + $this->artisan('bookstack:create-admin', [ + '--email' => 'firstadmin@example.com', + '--name' => 'Admin Test', + '--password' => 'testing-7', + '--initial' => true, + ])->expectsOutput('The default admin user has been updated with the provided details!') + ->assertExitCode(0); + + $defaultAdmin->refresh(); + + $this->assertEquals('firstadmin@example.com', $defaultAdmin->email); + } + + public function test_initial_option_does_not_update_if_only_non_default_admin_exists() + { + $defaultAdmin = User::query()->where('email', '=', 'admin@admin.com')->first(); + $defaultAdmin->email = 'testadmin@example.com'; + $defaultAdmin->save(); + + $this->artisan('bookstack:create-admin', [ + '--email' => 'firstadmin@example.com', + '--name' => 'Admin Test', + '--password' => 'testing-7', + '--initial' => true, + ])->expectsOutput('Non-default admin user already exists. Skipping creation of new admin user.') + ->assertExitCode(0); + + $defaultAdmin->refresh(); + + $this->assertEquals('testadmin@example.com', $defaultAdmin->email); + } + + public function test_initial_option_updates_creates_new_admin_if_none_exists() + { + $adminRole = Role::getSystemRole('admin'); + $adminRole->users()->delete(); + $this->assertEquals(0, $adminRole->users()->count()); + + $this->artisan('bookstack:create-admin', [ + '--email' => 'firstadmin@example.com', + '--name' => 'My initial admin', + '--password' => 'testing-7', + '--initial' => true, + ])->expectsOutput("Admin account with email \"firstadmin@example.com\" successfully created!") + ->assertExitCode(0); + + $this->assertEquals(1, $adminRole->users()->count()); + $this->assertDatabaseHas('users', [ + 'email' => 'firstadmin@example.com', + 'name' => 'My initial admin', + ]); + } + + public function test_initial_rerun_does_not_error_but_skips() + { + $adminRole = Role::getSystemRole('admin'); + $adminRole->users()->delete(); + + $this->artisan('bookstack:create-admin', [ + '--email' => 'firstadmin@example.com', + '--name' => 'My initial admin', + '--password' => 'testing-7', + '--initial' => true, + ])->expectsOutput("Admin account with email \"firstadmin@example.com\" successfully created!") + ->assertExitCode(0); + + $this->artisan('bookstack:create-admin', [ + '--email' => 'firstadmin@example.com', + '--name' => 'My initial admin', + '--password' => 'testing-7', + '--initial' => true, + ])->expectsOutput("Non-default admin user already exists. Skipping creation of new admin user.") + ->assertExitCode(0); + } + + public function test_initial_option_creation_errors_if_email_already_exists() + { + $adminRole = Role::getSystemRole('admin'); + $adminRole->users()->delete(); + $editor = $this->users->editor(); + + $this->artisan('bookstack:create-admin', [ + '--email' => $editor->email, + '--name' => 'My initial admin', + '--password' => 'testing-7', + '--initial' => true, + ])->expectsOutput("Could not create admin account.") + ->expectsOutput("An account with the email address \"{$editor->email}\" already exists.") + ->assertExitCode(1); + } + + public function test_initial_option_updating_errors_if_email_already_exists() + { + $editor = $this->users->editor(); + $defaultAdmin = User::query()->where('email', '=', 'admin@admin.com')->first(); + $this->assertNotNull($defaultAdmin); + + $this->artisan('bookstack:create-admin', [ + '--email' => $editor->email, + '--name' => 'My initial admin', + '--password' => 'testing-7', + '--initial' => true, + ])->expectsOutput("Could not create admin account.") + ->expectsOutput("An account with the email address \"{$editor->email}\" already exists.") + ->assertExitCode(1); + } + + public function test_initial_option_does_not_require_name_or_email_to_be_passed() + { + $adminRole = Role::getSystemRole('admin'); + $adminRole->users()->delete(); + $this->assertEquals(0, $adminRole->users()->count()); + + $this->artisan('bookstack:create-admin', [ + '--generate-password' => true, + '--initial' => true, + ])->assertExitCode(0); + + $this->assertEquals(1, $adminRole->users()->count()); + $this->assertDatabaseHas('users', [ + 'email' => 'admin@example.com', + 'name' => 'Admin', + ]); + } + + public function test_initial_option_updating_existing_user_with_generate_password_only_outputs_password() + { + $defaultAdmin = User::query()->where('email', '=', 'admin@admin.com')->first(); + + $this->withoutMockingConsoleOutput() + ->artisan('bookstack:create-admin', [ + '--email' => 'firstadmin@example.com', + '--name' => 'Admin Test', + '--generate-password' => true, + '--initial' => true, + ]); + + $output = Artisan::output(); + $this->assertMatchesRegularExpression('/^[a-zA-Z0-9]{32}$/', $output); + + $defaultAdmin->refresh(); + $this->assertEquals('firstadmin@example.com', $defaultAdmin->email); + } } From f36e6fb929d50a2713d1b01f064f2f087188bb69 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 7 Aug 2025 13:16:49 +0100 Subject: [PATCH 03/48] Commands: Updated create admin skip return Return status for skipped --initial creation will now return 2, so that it can be identified seperate from a creation and from an error. --- app/Console/Commands/CreateAdminCommand.php | 12 ++++++------ tests/Commands/CreateAdminCommandTest.php | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/Console/Commands/CreateAdminCommand.php b/app/Console/Commands/CreateAdminCommand.php index 520f0822a..bf72553f7 100644 --- a/app/Console/Commands/CreateAdminCommand.php +++ b/app/Console/Commands/CreateAdminCommand.php @@ -60,7 +60,7 @@ class CreateAdminCommand extends Command if ($initialAdminOnly) { $handled = $this->handleInitialAdminIfExists($userRepo, $details, $shouldGeneratePassword, $adminRole); if ($handled !== null) { - return $handled ? 0 : 1; + return $handled; } } @@ -87,16 +87,16 @@ class CreateAdminCommand extends Command /** * Handle updates to the original admin account if it exists. - * Returns true if it's been successfully handled, false if unsuccessful, or null if not handled. + * Returns an int return status if handled, otherwise returns null if not handled (new user to be created). */ - protected function handleInitialAdminIfExists(UserRepo $userRepo, array $data, bool $generatePassword, Role $adminRole): bool|null + protected function handleInitialAdminIfExists(UserRepo $userRepo, array $data, bool $generatePassword, Role $adminRole): int|null { $defaultAdmin = $userRepo->getByEmail('admin@admin.com'); if ($defaultAdmin && $defaultAdmin->hasSystemRole('admin')) { if ($defaultAdmin->email !== $data['email'] && $userRepo->getByEmail($data['email']) !== null) { $this->error("Could not create admin account."); $this->error("An account with the email address \"{$data['email']}\" already exists."); - return false; + return 1; } $userRepo->updateWithoutActivity($defaultAdmin, $data, true); @@ -106,10 +106,10 @@ class CreateAdminCommand extends Command $this->info("The default admin user has been updated with the provided details!"); } - return true; + return 0; } else if ($adminRole->users()->count() > 0) { $this->warn('Non-default admin user already exists. Skipping creation of new admin user.'); - return true; + return 2; } return null; diff --git a/tests/Commands/CreateAdminCommandTest.php b/tests/Commands/CreateAdminCommandTest.php index fa1329ad3..f389dd942 100644 --- a/tests/Commands/CreateAdminCommandTest.php +++ b/tests/Commands/CreateAdminCommandTest.php @@ -109,7 +109,7 @@ class CreateAdminCommandTest extends TestCase '--password' => 'testing-7', '--initial' => true, ])->expectsOutput('Non-default admin user already exists. Skipping creation of new admin user.') - ->assertExitCode(0); + ->assertExitCode(2); $defaultAdmin->refresh(); @@ -156,7 +156,7 @@ class CreateAdminCommandTest extends TestCase '--password' => 'testing-7', '--initial' => true, ])->expectsOutput("Non-default admin user already exists. Skipping creation of new admin user.") - ->assertExitCode(0); + ->assertExitCode(2); } public function test_initial_option_creation_errors_if_email_already_exists() From cdbac63b401a74ca53a307e78e86da4b02b33c6e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 30 Aug 2025 11:10:11 +0100 Subject: [PATCH 04/48] Framework: Updated to Laravel 12 --- app/Config/cache.php | 2 +- app/Config/database.php | 6 +- composer.json | 4 +- composer.lock | 392 +++++++++++++++++++++++++++++----------- 4 files changed, 288 insertions(+), 116 deletions(-) diff --git a/app/Config/cache.php b/app/Config/cache.php index 9a0be8eab..01c822a65 100644 --- a/app/Config/cache.php +++ b/app/Config/cache.php @@ -85,6 +85,6 @@ return [ | */ - 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_') . '_cache_'), + 'prefix' => env('CACHE_PREFIX', 'bookstack_cache_'), ]; diff --git a/app/Config/database.php b/app/Config/database.php index 8d38a86df..5edafa777 100644 --- a/app/Config/database.php +++ b/app/Config/database.php @@ -75,7 +75,7 @@ return [ 'collation' => 'utf8mb4_unicode_ci', // Prefixes are only semi-supported and may be unstable // since they are not tested as part of our automated test suite. - // If used, the prefix should not be changed otherwise you will likely receive errors. + // If used, the prefix should not be changed; otherwise you will likely receive errors. 'prefix' => env('DB_TABLE_PREFIX', ''), 'prefix_indexes' => true, 'strict' => false, @@ -103,9 +103,7 @@ return [ ], // Migration Repository Table - // This table keeps track of all the migrations that have already run for - // your application. Using this information, we can determine which of - // the migrations on disk haven't actually been run in the database. + // This table keeps track of all the migrations that have already run for the application. 'migrations' => 'migrations', // Redis configuration to use if set diff --git a/composer.json b/composer.json index 2ea1d802b..52fc4581b 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "guzzlehttp/guzzle": "^7.4", "intervention/image": "^3.5", "knplabs/knp-snappy": "^1.5", - "laravel/framework": "^v11.37", + "laravel/framework": "^v12.26.4", "laravel/socialite": "^5.10", "laravel/tinker": "^2.8", "league/commonmark": "^2.3", @@ -44,7 +44,7 @@ "fakerphp/faker": "^1.21", "itsgoingd/clockwork": "^5.1", "mockery/mockery": "^1.5", - "nunomaduro/collision": "^8.1", + "nunomaduro/collision": "^8.6", "larastan/larastan": "^v3.0", "phpunit/phpunit": "^11.5", "squizlabs/php_codesniffer": "^3.7", diff --git a/composer.lock b/composer.lock index fe7bfbb38..2c7d2cede 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": "b7695cb9945ec550970c67da96934daf", + "content-hash": "e5783454db9e549054438a448bcc9570", "packages": [ { "name": "aws/aws-crt-php", @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.356.6", + "version": "3.356.8", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "079d1936bef94cb7d76113b62e993f2996db6e24" + "reference": "3efa8c62c11fedb17b90f60b2d3a9f815b406e63" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/079d1936bef94cb7d76113b62e993f2996db6e24", - "reference": "079d1936bef94cb7d76113b62e993f2996db6e24", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3efa8c62c11fedb17b90f60b2d3a9f815b406e63", + "reference": "3efa8c62c11fedb17b90f60b2d3a9f815b406e63", "shasum": "" }, "require": { @@ -153,9 +153,9 @@ "support": { "forum": "https://github.com/aws/aws-sdk-php/discussions", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.356.6" + "source": "https://github.com/aws/aws-sdk-php/tree/3.356.8" }, - "time": "2025-08-27T18:06:10+00:00" + "time": "2025-08-29T18:06:18+00:00" }, { "name": "bacon/bacon-qr-code", @@ -213,16 +213,16 @@ }, { "name": "brick/math", - "version": "0.12.3", + "version": "0.13.1", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba" + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/866551da34e9a618e64a819ee1e01c20d8a588ba", - "reference": "866551da34e9a618e64a819ee1e01c20d8a588ba", + "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", "shasum": "" }, "require": { @@ -261,7 +261,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.12.3" + "source": "https://github.com/brick/math/tree/0.13.1" }, "funding": [ { @@ -269,7 +269,7 @@ "type": "github" } ], - "time": "2025-02-28T13:11:00+00:00" + "time": "2025-03-29T13:50:30+00:00" }, { "name": "carbonphp/carbon-doctrine-types", @@ -1739,20 +1739,20 @@ }, { "name": "laravel/framework", - "version": "v11.45.2", + "version": "v12.26.4", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "d134bf11e2208c0c5bd488cf19e612ca176b820a" + "reference": "085a367a32ba86fcfa647bfc796098ae6f795b09" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/d134bf11e2208c0c5bd488cf19e612ca176b820a", - "reference": "d134bf11e2208c0c5bd488cf19e612ca176b820a", + "url": "https://api.github.com/repos/laravel/framework/zipball/085a367a32ba86fcfa647bfc796098ae6f795b09", + "reference": "085a367a32ba86fcfa647bfc796098ae6f795b09", "shasum": "" }, "require": { - "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", + "brick/math": "^0.11|^0.12|^0.13", "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.4", @@ -1767,32 +1767,34 @@ "fruitcake/php-cors": "^1.3", "guzzlehttp/guzzle": "^7.8.2", "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", + "laravel/prompts": "^0.3.0", "laravel/serializable-closure": "^1.3|^2.0", "league/commonmark": "^2.7", "league/flysystem": "^3.25.1", "league/flysystem-local": "^3.25.1", "league/uri": "^7.5.1", "monolog/monolog": "^3.0", - "nesbot/carbon": "^2.72.6|^3.8.4", + "nesbot/carbon": "^3.8.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.3", - "symfony/error-handler": "^7.0.3", - "symfony/finder": "^7.0.3", + "symfony/console": "^7.2.0", + "symfony/error-handler": "^7.2.0", + "symfony/finder": "^7.2.0", "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", + "symfony/http-kernel": "^7.2.0", + "symfony/mailer": "^7.2.0", + "symfony/mime": "^7.2.0", + "symfony/polyfill-php83": "^1.33", + "symfony/polyfill-php84": "^1.33", + "symfony/polyfill-php85": "^1.33", + "symfony/process": "^7.2.0", + "symfony/routing": "^7.2.0", + "symfony/uid": "^7.2.0", + "symfony/var-dumper": "^7.2.0", "tijsverkoyen/css-to-inline-styles": "^2.2.5", "vlucas/phpdotenv": "^5.6.1", "voku/portable-ascii": "^2.0.2" @@ -1856,17 +1858,17 @@ "league/flysystem-read-only": "^3.25.1", "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", - "orchestra/testbench-core": "^9.16.0", - "pda/pheanstalk": "^5.0.6", + "orchestra/testbench-core": "^10.6.3", + "pda/pheanstalk": "^5.0.6|^7.0.0", "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", - "phpunit/phpunit": "^10.5.35|^11.3.6|^12.0.1", - "predis/predis": "^2.3", + "phpunit/phpunit": "^10.5.35|^11.5.3|^12.0.1", + "predis/predis": "^2.3|^3.0", "resend/resend-php": "^0.10.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" + "symfony/cache": "^7.2.0", + "symfony/http-client": "^7.2.0", + "symfony/psr-http-message-bridge": "^7.2.0", + "symfony/translation": "^7.2.0" }, "suggest": { "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", @@ -1892,22 +1894,22 @@ "mockery/mockery": "Required to use mocking (^1.6).", "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.35|^11.3.6|^12.0.1).", - "predis/predis": "Required to use the predis connector (^2.3).", + "phpunit/phpunit": "Required to use assertions and run tests (^10.5.35|^11.5.3|^12.0.1).", + "predis/predis": "Required to use the predis connector (^2.3|^3.0).", "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).", - "symfony/cache": "Required to PSR-6 cache bridge (^7.0).", - "symfony/filesystem": "Required to enable support for relative symbolic links (^7.0).", - "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.0).", - "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.0).", - "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.0).", - "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.0)." + "symfony/cache": "Required to PSR-6 cache bridge (^7.2).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^7.2).", + "symfony/http-client": "Required to enable support for the Symfony API mail transports (^7.2).", + "symfony/mailgun-mailer": "Required to enable support for the Mailgun mail transport (^7.2).", + "symfony/postmark-mailer": "Required to enable support for the Postmark mail transport (^7.2).", + "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^7.2)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "11.x-dev" + "dev-master": "12.x-dev" } }, "autoload": { @@ -1950,7 +1952,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-08-13T20:28:00+00:00" + "time": "2025-08-29T14:15:53+00:00" }, { "name": "laravel/prompts", @@ -5403,16 +5405,16 @@ }, { "name": "symfony/console", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1" + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1", - "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1", + "url": "https://api.github.com/repos/symfony/console/zipball/cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", + "reference": "cb0102a1c5ac3807cf3fdf8bea96007df7fdbea7", "shasum": "" }, "require": { @@ -5477,7 +5479,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.2" + "source": "https://github.com/symfony/console/tree/v7.3.3" }, "funding": [ { @@ -5497,7 +5499,7 @@ "type": "tidelift" } ], - "time": "2025-07-30T17:13:41+00:00" + "time": "2025-08-25T06:35:40+00:00" }, { "name": "symfony/css-selector", @@ -5714,16 +5716,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", - "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/b7dc69e71de420ac04bc9ab830cf3ffebba48191", + "reference": "b7dc69e71de420ac04bc9ab830cf3ffebba48191", "shasum": "" }, "require": { @@ -5774,7 +5776,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.3.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.3" }, "funding": [ { @@ -5785,12 +5787,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-22T09:11:45+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -5938,16 +5944,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6" + "reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6877c122b3a6cc3695849622720054f6e6fa5fa6", - "reference": "6877c122b3a6cc3695849622720054f6e6fa5fa6", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/7475561ec27020196c49bb7c4f178d33d7d3dc00", + "reference": "7475561ec27020196c49bb7c4f178d33d7d3dc00", "shasum": "" }, "require": { @@ -5997,7 +6003,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.3.2" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.3" }, "funding": [ { @@ -6017,20 +6023,20 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:47:49+00:00" + "time": "2025-08-20T08:04:18+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c" + "reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/6ecc895559ec0097e221ed2fd5eb44d5fede083c", - "reference": "6ecc895559ec0097e221ed2fd5eb44d5fede083c", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/72c304de37e1a1cec6d5d12b81187ebd4850a17b", + "reference": "72c304de37e1a1cec6d5d12b81187ebd4850a17b", "shasum": "" }, "require": { @@ -6115,7 +6121,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.3.2" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.3" }, "funding": [ { @@ -6135,20 +6141,20 @@ "type": "tidelift" } ], - "time": "2025-07-31T10:45:04+00:00" + "time": "2025-08-29T08:23:45+00:00" }, { "name": "symfony/mailer", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b" + "reference": "a32f3f45f1990db8c4341d5122a7d3a381c7e575" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/d43e84d9522345f96ad6283d5dfccc8c1cfc299b", - "reference": "d43e84d9522345f96ad6283d5dfccc8c1cfc299b", + "url": "https://api.github.com/repos/symfony/mailer/zipball/a32f3f45f1990db8c4341d5122a7d3a381c7e575", + "reference": "a32f3f45f1990db8c4341d5122a7d3a381c7e575", "shasum": "" }, "require": { @@ -6199,7 +6205,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.2" + "source": "https://github.com/symfony/mailer/tree/v7.3.3" }, "funding": [ { @@ -6219,7 +6225,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:36:08+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "symfony/mime", @@ -6895,6 +6901,166 @@ ], "time": "2025-07-08T02:45:35+00:00" }, + { + "name": "symfony/polyfill-php84", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php84.git", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", + "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php84\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-24T13:30:11+00:00" + }, + { + "name": "symfony/polyfill-php85", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php85.git", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "reference": "d4e5fcd4ab3d998ab16c0db48e6cbb9a01993f91", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php85\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php85/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-06-23T16:12:55+00:00" + }, { "name": "symfony/polyfill-uuid", "version": "v1.33.0", @@ -6980,16 +7146,16 @@ }, { "name": "symfony/process", - "version": "v7.3.0", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", - "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "url": "https://api.github.com/repos/symfony/process/zipball/32241012d521e2e8a9d713adb0812bb773b907f1", + "reference": "32241012d521e2e8a9d713adb0812bb773b907f1", "shasum": "" }, "require": { @@ -7021,7 +7187,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.3.0" + "source": "https://github.com/symfony/process/tree/v7.3.3" }, "funding": [ { @@ -7032,12 +7198,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-17T09:11:12+00:00" + "time": "2025-08-18T09:42:54+00:00" }, { "name": "symfony/routing", @@ -7209,16 +7379,16 @@ }, { "name": "symfony/string", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", - "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", + "url": "https://api.github.com/repos/symfony/string/zipball/17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", + "reference": "17a426cce5fd1f0901fefa9b2a490d0038fd3c9c", "shasum": "" }, "require": { @@ -7276,7 +7446,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.3.2" + "source": "https://github.com/symfony/string/tree/v7.3.3" }, "funding": [ { @@ -7296,20 +7466,20 @@ "type": "tidelift" } ], - "time": "2025-07-10T08:47:49+00:00" + "time": "2025-08-25T06:35:40+00:00" }, { "name": "symfony/translation", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "81b48f4daa96272efcce9c7a6c4b58e629df3c90" + "reference": "e0837b4cbcef63c754d89a4806575cada743a38d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/81b48f4daa96272efcce9c7a6c4b58e629df3c90", - "reference": "81b48f4daa96272efcce9c7a6c4b58e629df3c90", + "url": "https://api.github.com/repos/symfony/translation/zipball/e0837b4cbcef63c754d89a4806575cada743a38d", + "reference": "e0837b4cbcef63c754d89a4806575cada743a38d", "shasum": "" }, "require": { @@ -7376,7 +7546,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.3.2" + "source": "https://github.com/symfony/translation/tree/v7.3.3" }, "funding": [ { @@ -7396,7 +7566,7 @@ "type": "tidelift" } ], - "time": "2025-07-30T17:31:46+00:00" + "time": "2025-08-01T21:02:37+00:00" }, { "name": "symfony/translation-contracts", @@ -7552,16 +7722,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.3.2", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "53205bea27450dc5c65377518b3275e126d45e75" + "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/53205bea27450dc5c65377518b3275e126d45e75", - "reference": "53205bea27450dc5c65377518b3275e126d45e75", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", + "reference": "34d8d4c4b9597347306d1ec8eb4e1319b1e6986f", "shasum": "" }, "require": { @@ -7615,7 +7785,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.2" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.3" }, "funding": [ { @@ -7635,7 +7805,7 @@ "type": "tidelift" } ], - "time": "2025-07-29T20:02:46+00:00" + "time": "2025-08-13T11:49:31+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -10319,16 +10489,16 @@ }, { "name": "symfony/dom-crawler", - "version": "v7.3.1", + "version": "v7.3.3", "source": { "type": "git", "url": "https://github.com/symfony/dom-crawler.git", - "reference": "8b2ee2e06ab99fa5f067b6699296d4e35c156bb9" + "reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/8b2ee2e06ab99fa5f067b6699296d4e35c156bb9", - "reference": "8b2ee2e06ab99fa5f067b6699296d4e35c156bb9", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/efa076ea0eeff504383ff0dcf827ea5ce15690ba", + "reference": "efa076ea0eeff504383ff0dcf827ea5ce15690ba", "shasum": "" }, "require": { @@ -10366,7 +10536,7 @@ "description": "Eases DOM navigation for HTML and XML documents", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dom-crawler/tree/v7.3.1" + "source": "https://github.com/symfony/dom-crawler/tree/v7.3.3" }, "funding": [ { @@ -10377,12 +10547,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-06-15T10:07:06+00:00" + "time": "2025-08-06T20:13:54+00:00" }, { "name": "theseer/tokenizer", From 64b06bcf618455f348648aeac2cf1f295726e955 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 30 Aug 2025 11:47:22 +0100 Subject: [PATCH 05/48] Packages: Updated predis --- composer.json | 2 +- composer.lock | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/composer.json b/composer.json index 52fc4581b..2285a22cb 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,7 @@ "onelogin/php-saml": "^4.0", "phpseclib/phpseclib": "^3.0", "pragmarx/google2fa": "^8.0", - "predis/predis": "^2.1", + "predis/predis": "^3.2", "socialiteproviders/discord": "^4.1", "socialiteproviders/gitlab": "^4.1", "socialiteproviders/microsoft-azure": "^5.1", diff --git a/composer.lock b/composer.lock index 2c7d2cede..1dbb75203 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": "e5783454db9e549054438a448bcc9570", + "content-hash": "506bcb9f80a5fd736b79c8e138efafa0", "packages": [ { "name": "aws/aws-crt-php", @@ -4105,26 +4105,27 @@ }, { "name": "predis/predis", - "version": "v2.4.0", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/predis/predis.git", - "reference": "f49e13ee3a2a825631562aa0223ac922ec5d058b" + "reference": "9e9deec4dfd3ebf65d32eb368f498c646ba2ecd8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/predis/predis/zipball/f49e13ee3a2a825631562aa0223ac922ec5d058b", - "reference": "f49e13ee3a2a825631562aa0223ac922ec5d058b", + "url": "https://api.github.com/repos/predis/predis/zipball/9e9deec4dfd3ebf65d32eb368f498c646ba2ecd8", + "reference": "9e9deec4dfd3ebf65d32eb368f498c646ba2ecd8", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.2 || ^8.0", + "psr/http-message": "^1.0|^2.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.3", "phpstan/phpstan": "^1.9", "phpunit/phpcov": "^6.0 || ^8.0", - "phpunit/phpunit": "^8.0 || ^9.4" + "phpunit/phpunit": "^8.0 || ~9.4.4" }, "suggest": { "ext-relay": "Faster connection with in-memory caching (>=0.6.2)" @@ -4155,7 +4156,7 @@ ], "support": { "issues": "https://github.com/predis/predis/issues", - "source": "https://github.com/predis/predis/tree/v2.4.0" + "source": "https://github.com/predis/predis/tree/v3.2.0" }, "funding": [ { @@ -4163,7 +4164,7 @@ "type": "github" } ], - "time": "2025-04-30T15:16:02+00:00" + "time": "2025-08-06T06:41:24+00:00" }, { "name": "psr/clock", From a27ce6e9154387ac24ad3d3df3321ddb19dd7592 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 30 Aug 2025 22:18:09 +0100 Subject: [PATCH 06/48] Packages: Updated npm packages Spent way too many hours debugging through issues from jsdom changes. --- package-lock.json | 3179 ++++++++++------- package.json | 46 +- .../wysiwyg/lexical/core/LexicalSelection.ts | 2 + .../core/__tests__/unit/LexicalEditor.test.ts | 4 +- .../core/__tests__/unit/LexicalNode.test.ts | 2 +- .../lexical/core/__tests__/utils/index.ts | 22 + .../__tests__/unit/LexicalElementNode.test.ts | 13 +- .../unit/LexicalHeadlessEditor.test.ts | 8 +- .../__tests__/unit/LexicalSelection.test.ts | 31 +- .../selection/__tests__/utils/index.ts | 5 +- .../__tests__/unit/LexicalTableNode.test.ts | 2 +- 11 files changed, 2054 insertions(+), 1260 deletions(-) diff --git a/package-lock.json b/package-lock.json index 079e39770..fb6a1d4fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,47 +5,47 @@ "packages": { "": { "dependencies": { - "@codemirror/commands": "^6.8.0", + "@codemirror/commands": "^6.8.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.9", - "@codemirror/lang-javascript": "^6.2.3", - "@codemirror/lang-json": "^6.0.1", - "@codemirror/lang-markdown": "^6.3.2", - "@codemirror/lang-php": "^6.0.1", + "@codemirror/lang-javascript": "^6.2.4", + "@codemirror/lang-json": "^6.0.2", + "@codemirror/lang-markdown": "^6.3.4", + "@codemirror/lang-php": "^6.0.2", "@codemirror/lang-xml": "^6.1.0", - "@codemirror/language": "^6.10.8", - "@codemirror/legacy-modes": "^6.4.3", + "@codemirror/language": "^6.11.3", + "@codemirror/legacy-modes": "^6.5.1", "@codemirror/state": "^6.5.2", - "@codemirror/theme-one-dark": "^6.1.2", - "@codemirror/view": "^6.36.3", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.38.1", "@lezer/highlight": "^1.2.1", "@ssddanbrown/codemirror-lang-smarty": "^1.0.0", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", - "@types/jest": "^29.5.14", - "codemirror": "^6.0.1", - "idb-keyval": "^6.2.1", + "@types/jest": "^30.0.0", + "codemirror": "^6.0.2", + "idb-keyval": "^6.2.2", "markdown-it": "^14.1.0", "markdown-it-task-lists": "^2.1.1", "snabbdom": "^3.6.2", "sortablejs": "^1.15.6" }, "devDependencies": { - "@eslint/js": "^9.21.0", - "@lezer/generator": "^1.7.2", + "@eslint/js": "^9.34.0", + "@lezer/generator": "^1.8.0", "@types/markdown-it": "^14.1.2", "@types/sortablejs": "^1.15.8", "chokidar-cli": "^3.0", - "esbuild": "^0.25.0", - "eslint": "^9.21.0", - "eslint-plugin-import": "^2.31.0", - "jest": "^29.7.0", - "jest-environment-jsdom": "^29.7.0", - "livereload": "^0.9.3", + "esbuild": "^0.25.9", + "eslint": "^9.34.0", + "eslint-plugin-import": "^2.32.0", + "jest": "^30.1.1", + "jest-environment-jsdom": "^30.1.1", + "livereload": "^0.10.3", "npm-run-all": "^4.1.5", - "sass": "^1.85.0", - "ts-jest": "^29.2.6", + "sass": "^1.91.0", + "ts-jest": "^29.4.1", "ts-node": "^10.9.2", - "typescript": "5.7.*" + "typescript": "5.9.*" } }, "node_modules/@ampproject/remapping": { @@ -62,6 +62,27 @@ "node": ">=6.0.0" } }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -536,9 +557,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.1.tgz", - "integrity": "sha512-x0LvFTekgSX+83TI28Y9wYPUfzrnl2aT5+5QLnO6v7mSJYtEEevuDRN0F0uSHRk1G1IWZC43o00Y0xDDrpBGPQ==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -636,9 +657,9 @@ } }, "node_modules/@codemirror/lang-markdown": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.3.3.tgz", - "integrity": "sha512-1fn1hQAPWlSSMCvnF810AkhWpNLkJpl66CRfIy3vVl20Sl4NwChkorCHqpMtNbXr1EuMJsrDnhEpjZxKZ2UX3A==", + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.3.4.tgz", + "integrity": "sha512-fBm0BO03azXnTAsxhONDYHi/qWSI+uSEIpzKM7h/bkIc9fHnFp9y7KTMXKON0teNT97pFhc1a9DQTtWBYEZ7ug==", "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.7.1", @@ -678,9 +699,9 @@ } }, "node_modules/@codemirror/language": { - "version": "6.11.2", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz", - "integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==", + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", + "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", @@ -779,10 +800,159 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", + "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", - "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", + "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", "cpu": [ "ppc64" ], @@ -797,9 +967,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", - "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", + "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", "cpu": [ "arm" ], @@ -814,9 +984,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", - "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", + "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", "cpu": [ "arm64" ], @@ -831,9 +1001,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", - "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", + "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", "cpu": [ "x64" ], @@ -848,9 +1018,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", - "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", + "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", "cpu": [ "arm64" ], @@ -865,9 +1035,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", - "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", + "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", "cpu": [ "x64" ], @@ -882,9 +1052,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", - "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", + "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", "cpu": [ "arm64" ], @@ -899,9 +1069,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", - "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", + "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", "cpu": [ "x64" ], @@ -916,9 +1086,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", - "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", + "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", "cpu": [ "arm" ], @@ -933,9 +1103,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", - "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", + "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", "cpu": [ "arm64" ], @@ -950,9 +1120,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", - "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", + "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", "cpu": [ "ia32" ], @@ -967,9 +1137,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", - "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", + "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", "cpu": [ "loong64" ], @@ -984,9 +1154,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", - "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", + "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", "cpu": [ "mips64el" ], @@ -1001,9 +1171,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", - "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", + "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", "cpu": [ "ppc64" ], @@ -1018,9 +1188,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", - "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", + "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", "cpu": [ "riscv64" ], @@ -1035,9 +1205,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", - "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", + "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", "cpu": [ "s390x" ], @@ -1052,9 +1222,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", - "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", + "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", "cpu": [ "x64" ], @@ -1069,9 +1239,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", - "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", + "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", "cpu": [ "arm64" ], @@ -1086,9 +1256,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", - "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", + "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", "cpu": [ "x64" ], @@ -1103,9 +1273,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", - "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", + "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", "cpu": [ "arm64" ], @@ -1120,9 +1290,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", - "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", + "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", "cpu": [ "x64" ], @@ -1137,9 +1307,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", - "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", + "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", "cpu": [ "arm64" ], @@ -1154,9 +1324,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", - "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", + "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", "cpu": [ "x64" ], @@ -1171,9 +1341,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", - "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", + "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", "cpu": [ "arm64" ], @@ -1188,9 +1358,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", - "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", + "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", "cpu": [ "ia32" ], @@ -1205,9 +1375,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", - "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", + "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", "cpu": [ "x64" ], @@ -1279,9 +1449,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", - "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1289,9 +1459,9 @@ } }, "node_modules/@eslint/core": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", - "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1326,9 +1496,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.31.0.tgz", - "integrity": "sha512-LOm5OVt7D4qiKCqoiPbA7LWmI+tbw1VbTUowBcUMgQSuM6poJufkFkYDcQpo5KfgD39TnNySV26QjOh7VFpSyw==", + "version": "9.34.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.34.0.tgz", + "integrity": "sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw==", "dev": true, "license": "MIT", "engines": { @@ -1349,13 +1519,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", - "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.1", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { @@ -1428,6 +1598,80 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1546,61 +1790,61 @@ } }, "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.1.1.tgz", + "integrity": "sha512-f7TGqR1k4GtN5pyFrKmq+ZVndesiwLU33yDpJIGMS9aW+j6hKjue7ljeAdznBsH9kAnxUWe2Y+Y3fLV/FJt3gA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.0.5", "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "jest-message-util": "30.1.0", + "jest-util": "30.0.5", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.1.1.tgz", + "integrity": "sha512-3ncU9peZ3D2VdgRkdZtUceTrDgX5yiDRwAFjtxNfU22IiZrpVWlv/FogzDLYSJQptQGfFo3PcHK86a2oG6WUGg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/console": "30.1.1", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.1.1", + "@jest/test-result": "30.1.1", + "@jest/transform": "30.1.1", + "@jest/types": "30.0.5", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.0.5", + "jest-config": "30.1.1", + "jest-haste-map": "30.1.0", + "jest-message-util": "30.1.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.1.0", + "jest-resolve-dependencies": "30.1.1", + "jest-runner": "30.1.1", + "jest-runtime": "30.1.1", + "jest-snapshot": "30.1.1", + "jest-util": "30.0.5", + "jest-validate": "30.1.0", + "jest-watcher": "30.1.1", + "micromatch": "^4.0.8", + "pretty-format": "30.0.5", + "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1611,116 +1855,174 @@ } } }, + "node_modules/@jest/diff-sequences": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", + "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.1.1.tgz", + "integrity": "sha512-yWHbU+3j7ehQE+NRpnxRvHvpUhoohIjMePBbIr8lfe0cWVb0WeTf80DNux1GPJa18CDHiIU5DtksGUfxcDE+Rw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/fake-timers": "30.1.1", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-mock": "^29.7.0" + "jest-mock": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/environment-jsdom-abstract": { + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/@jest/environment-jsdom-abstract/-/environment-jsdom-abstract-30.1.1.tgz", + "integrity": "sha512-d7pP9SeIOI6qnrNIS/ds1hlS9jpqh8EywHK0dALSLODZKo2QEGnDNvnPvhRKI0FHWDnE2EMl8CDTP0jM9lhlOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "30.1.1", + "@jest/fake-timers": "30.1.1", + "@jest/types": "30.0.5", + "@types/jsdom": "^21.1.7", + "@types/node": "*", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } } }, "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.1.1.tgz", + "integrity": "sha512-3vHIHsF+qd3D8FU2c7U5l3rg1fhDwAYcGyHyZAi94YIlTwcJ+boNhRyJf373cl4wxbOX+0Q7dF40RTrTFTSuig==", "dev": true, "license": "MIT", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "expect": "30.1.1", + "jest-snapshot": "30.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.1.1.tgz", + "integrity": "sha512-5YUHr27fpJ64dnvtu+tt11ewATynrHkGYD+uSFgRr8V2eFJis/vEXgToyLwccIwqBihVfz9jwio+Zr1ab1Zihw==", "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "@jest/get-type": "30.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.1.1.tgz", + "integrity": "sha512-fK/25dNgBNYPw3eLi2CRs57g1H04qBAFNMsUY3IRzkfx/m4THe0E1zF+yGQBOMKKc2XQVdc9EYbJ4hEm7/2UtA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", + "@jest/types": "30.0.5", + "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-message-util": "30.1.0", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.1.1.tgz", + "integrity": "sha512-NNUUkHT2TU/xztZl6r1UXvJL+zvCwmZsQDmK69fVHHcB9fBtlu3FInnzOve/ZoyKnWY8JXWJNT+Lkmu1+ubXUA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "@jest/environment": "30.1.1", + "@jest/expect": "30.1.1", + "@jest/types": "30.0.5", + "jest-mock": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.1.1.tgz", + "integrity": "sha512-Hb2Bq80kahOC6Sv2waEaH1rEU6VdFcM6WHaRBWQF9tf30+nJHxhl/Upbgo9+25f0mOgbphxvbwSMjSgy9gW/FA==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", + "@jest/console": "30.1.1", + "@jest/test-result": "30.1.1", + "@jest/transform": "30.1.1", + "@jest/types": "30.0.5", + "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", + "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", + "jest-message-util": "30.1.0", + "jest-util": "30.0.5", + "jest-worker": "30.1.0", "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", + "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1732,106 +2034,123 @@ } }, "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@sinclair/typebox": "^0.34.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.1.1.tgz", + "integrity": "sha512-TkVBc9wuN22TT8hESRFmjjg/xIMu7z0J3UDYtIRydzCqlLPTB7jK1DDBKdnTUZ4zL3z3rnPpzV6rL1Uzh87sXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.5", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.1.1.tgz", + "integrity": "sha512-bMdj7fNu8iZuBPSnbVir5ezvWmVo4jrw7xDE+A33Yb3ENCoiJK9XgOLgal+rJ9XSKjsL7aPUMIo87zhN7I5o2w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "@jest/console": "30.1.1", + "@jest/types": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.1.1.tgz", + "integrity": "sha512-yruRdLXSA3HYD/MTNykgJ6VYEacNcXDFRMqKVAwlYegmxICUiT/B++CNuhJnYJzKYks61iYnjVsMwbUqmmAYJg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", + "@jest/test-result": "30.1.1", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.1.0", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.1.1.tgz", + "integrity": "sha512-PHIA2AbAASBfk6evkNifvmx9lkOSkmvaQoO6VSpuL8+kQqDMHeDoJ7RU3YP1wWAMD7AyQn9UL5iheuFYCC4lqQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", + "@babel/core": "^7.27.4", + "@jest/types": "30.0.5", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.0", + "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.1.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.0.5", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "write-file-atomic": "^5.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", + "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jridgewell/gen-mapping": { @@ -1993,6 +2312,19 @@ "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", "license": "MIT" }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", @@ -2303,6 +2635,30 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pkgr/core": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", + "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/pkgr" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -2311,9 +2667,9 @@ "license": "MIT" }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "version": "0.34.41", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", + "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", "license": "MIT" }, "node_modules/@sinonjs/commons": { @@ -2327,13 +2683,13 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "version": "13.0.5", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", + "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "@sinonjs/commons": "^3.0.1" } }, "node_modules/@ssddanbrown/codemirror-lang-smarty": { @@ -2353,16 +2709,6 @@ "@lezer/lr": "^1.0.0" } }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -2391,6 +2737,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.0.tgz", + "integrity": "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2427,13 +2784,13 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.20.7" + "@babel/types": "^7.28.2" } }, "node_modules/@types/estree": { @@ -2443,16 +2800,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -2478,19 +2825,19 @@ } }, "node_modules/@types/jest": { - "version": "29.5.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", - "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", "license": "MIT", "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" + "expect": "^30.0.0", + "pretty-format": "^30.0.0" } }, "node_modules/@types/jsdom": { - "version": "20.0.1", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", - "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "version": "21.1.7", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", + "integrity": "sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==", "dev": true, "license": "MIT", "dependencies": { @@ -2582,13 +2929,281 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "license": "MIT" }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead", + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true, - "license": "BSD-3-Clause" + "license": "ISC" + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/acorn": { "version": "8.15.0", @@ -2603,17 +3218,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-globals": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", - "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.1.0", - "acorn-walk": "^8.0.2" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", @@ -2638,16 +3242,13 @@ } }, "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", - "dependencies": { - "debug": "4" - }, "engines": { - "node": ">= 6.0.0" + "node": ">= 14" } }, "node_modules/ajv": { @@ -2684,13 +3285,16 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { @@ -2857,13 +3461,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -2874,13 +3471,6 @@ "node": ">= 0.4" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2898,75 +3488,57 @@ } }, "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.1.1.tgz", + "integrity": "sha512-1bZfC/V03qBCzASvZpNFhx3Ouj6LgOd4KFJm4br/fYOS+tSSvVCE61QmcAVbMTwq/GoB7KN4pzGMoyr9cMxSvQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", + "@jest/transform": "30.1.1", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.0", + "babel-preset-jest": "30.0.1", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.8.0" + "@babel/core": "^7.11.0" } }, "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz", + "integrity": "sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", "test-exclude": "^6.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" + "node": ">=12" } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz", + "integrity": "sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "@types/babel__core": "^7.20.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/babel-preset-current-node-syntax": { @@ -2997,20 +3569,20 @@ } }, "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz", + "integrity": "sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" + "babel-plugin-jest-hoist": "30.0.1", + "babel-preset-current-node-syntax": "^1.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.11.0" } }, "node_modules/balanced-match": { @@ -3281,9 +3853,9 @@ } }, "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.0.tgz", + "integrity": "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==", "funding": [ { "type": "github", @@ -3296,9 +3868,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz", + "integrity": "sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==", "dev": true, "license": "MIT" }, @@ -3388,19 +3960,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3415,28 +3974,6 @@ "dev": true, "license": "MIT" }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -3465,46 +4002,32 @@ "node": ">= 8" } }, - "node_modules/cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true, - "license": "MIT" - }, "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", "dev": true, "license": "MIT", "dependencies": { - "cssom": "~0.3.6" + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true, - "license": "MIT" - }, "node_modules/data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", "dev": true, "license": "MIT", "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/data-view-buffer": { @@ -3664,16 +4187,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -3708,15 +4221,6 @@ "node": ">=0.3.1" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -3730,20 +4234,6 @@ "node": ">=0.10.0" } }, - "node_modules/domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "license": "MIT", - "dependencies": { - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3759,21 +4249,12 @@ "node": ">= 0.4" } }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, "node_modules/electron-to-chromium": { "version": "1.5.190", @@ -3974,9 +4455,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.8", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", - "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", + "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3987,32 +4468,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.8", - "@esbuild/android-arm": "0.25.8", - "@esbuild/android-arm64": "0.25.8", - "@esbuild/android-x64": "0.25.8", - "@esbuild/darwin-arm64": "0.25.8", - "@esbuild/darwin-x64": "0.25.8", - "@esbuild/freebsd-arm64": "0.25.8", - "@esbuild/freebsd-x64": "0.25.8", - "@esbuild/linux-arm": "0.25.8", - "@esbuild/linux-arm64": "0.25.8", - "@esbuild/linux-ia32": "0.25.8", - "@esbuild/linux-loong64": "0.25.8", - "@esbuild/linux-mips64el": "0.25.8", - "@esbuild/linux-ppc64": "0.25.8", - "@esbuild/linux-riscv64": "0.25.8", - "@esbuild/linux-s390x": "0.25.8", - "@esbuild/linux-x64": "0.25.8", - "@esbuild/netbsd-arm64": "0.25.8", - "@esbuild/netbsd-x64": "0.25.8", - "@esbuild/openbsd-arm64": "0.25.8", - "@esbuild/openbsd-x64": "0.25.8", - "@esbuild/openharmony-arm64": "0.25.8", - "@esbuild/sunos-x64": "0.25.8", - "@esbuild/win32-arm64": "0.25.8", - "@esbuild/win32-ia32": "0.25.8", - "@esbuild/win32-x64": "0.25.8" + "@esbuild/aix-ppc64": "0.25.9", + "@esbuild/android-arm": "0.25.9", + "@esbuild/android-arm64": "0.25.9", + "@esbuild/android-x64": "0.25.9", + "@esbuild/darwin-arm64": "0.25.9", + "@esbuild/darwin-x64": "0.25.9", + "@esbuild/freebsd-arm64": "0.25.9", + "@esbuild/freebsd-x64": "0.25.9", + "@esbuild/linux-arm": "0.25.9", + "@esbuild/linux-arm64": "0.25.9", + "@esbuild/linux-ia32": "0.25.9", + "@esbuild/linux-loong64": "0.25.9", + "@esbuild/linux-mips64el": "0.25.9", + "@esbuild/linux-ppc64": "0.25.9", + "@esbuild/linux-riscv64": "0.25.9", + "@esbuild/linux-s390x": "0.25.9", + "@esbuild/linux-x64": "0.25.9", + "@esbuild/netbsd-arm64": "0.25.9", + "@esbuild/netbsd-x64": "0.25.9", + "@esbuild/openbsd-arm64": "0.25.9", + "@esbuild/openbsd-x64": "0.25.9", + "@esbuild/openharmony-arm64": "0.25.9", + "@esbuild/sunos-x64": "0.25.9", + "@esbuild/win32-arm64": "0.25.9", + "@esbuild/win32-ia32": "0.25.9", + "@esbuild/win32-x64": "0.25.9" } }, "node_modules/escalade": { @@ -4038,43 +4519,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, "node_modules/eslint": { - "version": "9.31.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.31.0.tgz", - "integrity": "sha512-QldCVh/ztyKJJZLr4jXNUByx3gR+TDYZCRXEktiZoUR3PGy4qCmSbkxcIle8GEwGpb5JBZazlaJ/CxLidXdEbQ==", + "version": "9.34.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.34.0.tgz", + "integrity": "sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", - "@eslint/config-helpers": "^0.3.0", - "@eslint/core": "^0.15.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.31.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/js": "9.34.0", + "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -4360,29 +4819,31 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.1.1.tgz", + "integrity": "sha512-OKe7cdic4qbfWd/CcgwJvvCrNX2KWfuMZee9AfJHL1gTYmvqjBjZG1a2NwfhspBzxzlXwsN75WWpKTYfsJpBxg==", "license": "MIT", "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" + "@jest/expect-utils": "30.1.1", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.1.1", + "jest-message-util": "30.1.0", + "jest-mock": "30.0.5", + "jest-util": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/fast-deep-equal": { @@ -4429,39 +4890,6 @@ "node": ">=16.0.0" } }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4528,21 +4956,34 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { - "node": ">= 6" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/fs.realpath": { @@ -4709,22 +5150,21 @@ } }, "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4743,6 +5183,32 @@ "node": ">= 6" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", @@ -4792,6 +5258,28 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -4893,16 +5381,16 @@ "license": "ISC" }, "node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-encoding": "^2.0.0" + "whatwg-encoding": "^3.1.1" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/html-escaper": { @@ -4913,32 +5401,31 @@ "license": "MIT" }, "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "agent-base": "^7.1.0", + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { - "agent-base": "6", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/human-signals": { @@ -5588,24 +6075,24 @@ } }, "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "istanbul-lib-coverage": "^3.0.0" }, "engines": { "node": ">=10" } }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5616,42 +6103,39 @@ "node": ">=8" } }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "license": "Apache-2.0", + "license": "BlueOak-1.0.0", "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" + "@isaacs/cliui": "^8.0.2" }, - "bin": { - "jake": "bin/cli.js" + "funding": { + "url": "https://github.com/sponsors/isaacs" }, - "engines": { - "node": ">=10" + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" } }, "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.1.1.tgz", + "integrity": "sha512-yC3JvpP/ZcAZX5rYCtXO/g9k6VTCQz0VFE2v1FpxytWzUqfDtu0XL/pwnNvptzYItvGwomh1ehomRNMOyhCJKw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" + "@jest/core": "30.1.1", + "@jest/types": "30.0.5", + "import-local": "^3.2.0", + "jest-cli": "30.1.1" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -5663,76 +6147,75 @@ } }, "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.5.tgz", + "integrity": "sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", + "execa": "^5.1.1", + "jest-util": "30.0.5", "p-limit": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.1.1.tgz", + "integrity": "sha512-M3Vd4x5wD7eSJspuTvRF55AkOOBndRxgW3gqQBDlFvbH3X+ASdi8jc+EqXEeAFd/UHulVYIlC4XKJABOhLw6UA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.1.1", + "@jest/expect": "30.1.1", + "@jest/test-result": "30.1.1", + "@jest/types": "30.0.5", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.1.0", + "jest-matcher-utils": "30.1.1", + "jest-message-util": "30.1.0", + "jest-runtime": "30.1.1", + "jest-snapshot": "30.1.1", + "jest-util": "30.0.5", "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", + "pretty-format": "30.0.5", + "pure-rand": "^7.0.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.1.1.tgz", + "integrity": "sha512-xm9llxuh5OoI5KZaYzlMhklryHBwg9LZy/gEaaMlXlxb+cZekGNzukU0iblbDo3XOBuN6N0CgK4ykgNRYSEb6g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" + "@jest/core": "30.1.1", + "@jest/test-result": "30.1.1", + "@jest/types": "30.0.5", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.1.1", + "jest-util": "30.0.5", + "jest-validate": "30.1.0", + "yargs": "^17.7.2" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -5743,6 +6226,16 @@ } } }, + "node_modules/jest-cli/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/jest-cli/node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -5790,6 +6283,19 @@ "node": ">=8" } }, + "node_modules/jest-cli/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-cli/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -5838,117 +6344,120 @@ } }, "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.1.1.tgz", + "integrity": "sha512-xuPGUGDw+9fPPnGmddnLnHS/mhKUiJOW7K65vErYmglEPKq65NKwSRchkQ7iv6gqjs2l+YNEsAtbsplxozdOWg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.1.1", + "@jest/types": "30.0.5", + "babel-jest": "30.1.1", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-circus": "30.1.1", + "jest-docblock": "30.0.1", + "jest-environment-node": "30.1.1", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.1.0", + "jest-runner": "30.1.1", + "jest-util": "30.0.5", + "jest-validate": "30.1.0", + "micromatch": "^4.0.8", "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", + "pretty-format": "30.0.5", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "@types/node": "*", + "esbuild-register": ">=3.4.0", "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "esbuild-register": { + "optional": true + }, "ts-node": { "optional": true } } }, "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.1.1.tgz", + "integrity": "sha512-LUU2Gx8EhYxpdzTR6BmjL1ifgOAQJQELTHOiPv9KITaKjZvJ9Jmgigx01tuZ49id37LorpGc9dPBPlXTboXScw==", "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/diff-sequences": "30.0.1", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.0.1.tgz", + "integrity": "sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==", "dev": true, "license": "MIT", "dependencies": { - "detect-newline": "^3.0.0" + "detect-newline": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.1.0.tgz", + "integrity": "sha512-A+9FKzxPluqogNahpCv04UJvcZ9B3HamqpDNWNKDjtxVRYB8xbZLFuCr8JAJFpNp83CA0anGQFlpQna9Me+/tQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "@jest/types": "30.0.5", + "chalk": "^4.1.2", + "jest-util": "30.0.5", + "pretty-format": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-jsdom": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", - "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-30.1.1.tgz", + "integrity": "sha512-fInyXsHSuPaERmRiub4V6jl6KERXowGqY8AISJrXZjOq7vdP46qecm+GnTngjcUPeHFqrxp1PfP0XuFfKTzA2A==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/jsdom": "^20.0.0", + "@jest/environment": "30.1.1", + "@jest/environment-jsdom-abstract": "30.1.1", + "@types/jsdom": "^21.1.7", "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0", - "jsdom": "^20.0.0" + "jsdom": "^26.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "canvas": "^2.5.0" + "canvas": "^3.0.0" }, "peerDependenciesMeta": { "canvas": { @@ -5957,120 +6466,110 @@ } }, "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.1.1.tgz", + "integrity": "sha512-IaMoaA6saxnJimqCppUDqKck+LKM0Jg+OxyMUIvs1yGd2neiC22o8zXo90k04+tO+49OmgMR4jTgM5e4B0S62Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.1.1", + "@jest/fake-timers": "30.1.1", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-mock": "30.0.5", + "jest-util": "30.0.5", + "jest-validate": "30.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.1.0.tgz", + "integrity": "sha512-JLeM84kNjpRkggcGpQLsV7B8W4LNUWz7oDNVnY1Vjj22b5/fAb3kk3htiD+4Na8bmJmjJR7rBtS2Rmq/NEcADg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", + "@jest/types": "30.0.5", "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.0.5", + "jest-worker": "30.1.0", + "micromatch": "^4.0.8", "walker": "^1.0.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "optionalDependencies": { - "fsevents": "^2.3.2" + "fsevents": "^2.3.3" } }, "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.1.0.tgz", + "integrity": "sha512-AoFvJzwxK+4KohH60vRuHaqXfWmeBATFZpzpmzNmYTtmRMiyGPVhkXpBqxUQunw+dQB48bDf4NpUs6ivVbRv1g==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "pretty-format": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.1.1.tgz", + "integrity": "sha512-SuH2QVemK48BNTqReti6FtjsMPFsSOD/ZzRxU1TttR7RiRsRSe78d03bb4Cx6D4bQC/80Q8U4VnaaAH9FlbZ9w==", "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.1.1", + "pretty-format": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.1.0.tgz", + "integrity": "sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.0.5", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.0.5", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", + "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.0.5", "@types/node": "*", - "jest-util": "^29.7.0" + "jest-util": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-pnp-resolver": { @@ -6092,147 +6591,147 @@ } }, "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.1.0.tgz", + "integrity": "sha512-hASe7D/wRtZw8Cm607NrlF7fi3HWC5wmA5jCVc2QjQAB2pTwP9eVZILGEi6OeSLNUtE1zb04sXRowsdh5CUjwA==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.1.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.0.5", + "jest-validate": "30.1.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.1.1.tgz", + "integrity": "sha512-tRtaaoH8Ws1Gn1o/9pedt19dvVgr81WwdmvJSP9Ow3amOUOP2nN9j94u5jC9XlIfa2Q1FQKIWWQwL4ajqsjCGQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.1.1.tgz", + "integrity": "sha512-ATe6372SOfJvCRExtCAr06I4rGujwFdKg44b6i7/aOgFnULwjxzugJ0Y4AnG+jeSeQi8dU7R6oqLGmsxRUbErQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/console": "30.1.1", + "@jest/environment": "30.1.1", + "@jest/test-result": "30.1.1", + "@jest/transform": "30.1.1", + "@jest/types": "30.0.5", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.0.1", + "jest-environment-node": "30.1.1", + "jest-haste-map": "30.1.0", + "jest-leak-detector": "30.1.0", + "jest-message-util": "30.1.0", + "jest-resolve": "30.1.0", + "jest-runtime": "30.1.1", + "jest-util": "30.0.5", + "jest-watcher": "30.1.1", + "jest-worker": "30.1.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.1.1.tgz", + "integrity": "sha512-7sOyR0Oekw4OesQqqBHuYJRB52QtXiq0NNgLRzVogiMSxKCMiliUd6RrXHCnG5f12Age/ggidCBiQftzcA9XKw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.1.1", + "@jest/fake-timers": "30.1.1", + "@jest/globals": "30.1.1", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.1.1", + "@jest/transform": "30.1.1", + "@jest/types": "30.0.5", "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.1.0", + "jest-message-util": "30.1.0", + "jest-mock": "30.0.5", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.1.0", + "jest-snapshot": "30.1.1", + "jest-util": "30.0.5", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.1.1.tgz", + "integrity": "sha512-7/iBEzoJqEt2TjkQY+mPLHP8cbPhLReZVkkxjTMzIzoTC4cZufg7HzKo/n9cIkXKj2LG0x3mmBHsZto+7TOmFg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.1.1", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.1.1", + "@jest/transform": "30.1.1", + "@jest/types": "30.0.5", + "babel-preset-current-node-syntax": "^1.1.0", + "chalk": "^4.1.2", + "expect": "30.1.1", + "graceful-fs": "^4.2.11", + "jest-diff": "30.1.1", + "jest-matcher-utils": "30.1.1", + "jest-message-util": "30.1.0", + "jest-util": "30.0.5", + "pretty-format": "30.0.5", + "semver": "^7.7.2", + "synckit": "^0.11.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-snapshot/node_modules/semver": { @@ -6249,38 +6748,50 @@ } }, "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", + "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.0.5", "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.1.0.tgz", + "integrity": "sha512-7P3ZlCFW/vhfQ8pE7zW6Oi4EzvuB4sgR72Q1INfW9m0FGo0GADYlPwIkf4CyPq7wq85g+kPMtPOHNAdWHeBOaA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", + "@jest/get-type": "30.1.0", + "@jest/types": "30.0.5", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "^29.7.0" + "pretty-format": "30.0.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-validate/node_modules/camelcase": { @@ -6297,39 +6808,40 @@ } }, "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "version": "30.1.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.1.1.tgz", + "integrity": "sha512-CrAQ73LlaS6KGQQw6NBi71g7qvP7scy+4+2c0jKX6+CWaYg85lZiig5nQQVTsS5a5sffNPL3uxXnaE9d7v9eQg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/test-result": "30.1.1", + "@jest/types": "30.0.5", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" + "jest-util": "30.0.5", + "string-length": "^4.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.1.0.tgz", + "integrity": "sha512-uvWcSjlwAAgIu133Tt77A05H7RIk3Ho8tZL50bQM2AkvLdluw9NG48lRCl3Dt+MOH719n/0nnb5YxUwcuJiKRA==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-util": "^29.7.0", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.0.5", "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "supports-color": "^8.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker/node_modules/supports-color": { @@ -6368,44 +6880,38 @@ } }, "node_modules/jsdom": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", - "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "dev": true, "license": "MIT", "dependencies": { - "abab": "^2.0.6", - "acorn": "^8.8.1", - "acorn-globals": "^7.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.4.2", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.2", - "parse5": "^7.1.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.11.0", - "xml-name-validator": "^4.0.0" + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" }, "peerDependencies": { - "canvas": "^2.5.0" + "canvas": "^3.0.0" }, "peerDependenciesMeta": { "canvas": { @@ -6484,16 +6990,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -6535,16 +7031,16 @@ } }, "node_modules/livereload": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.3.tgz", - "integrity": "sha512-q7Z71n3i4X0R9xthAryBdNGVGAO2R5X+/xXpmKeuPMrteg+W2U8VusTKV3YiJbXZwKsOlFlHe+go6uSNjfxrZw==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.10.3.tgz", + "integrity": "sha512-llSb8HrtSH7ByPFMc8WTTeW3oy++smwgSA8JVGzEn8KiDPESq6jt1M4ZKKkhKTrhn2wvUOadQq4ip10E5daZ3w==", "dev": true, "license": "MIT", "dependencies": { - "chokidar": "^3.5.0", - "livereload-js": "^3.3.1", - "opts": ">= 1.2.0", - "ws": "^7.4.3" + "chokidar": "^4.0.3", + "livereload-js": "^4.0.2", + "opts": "^2.0.2", + "ws": "^8.4.3" }, "bin": { "livereload": "bin/livereload.js" @@ -6554,32 +7050,40 @@ } }, "node_modules/livereload-js": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.4.1.tgz", - "integrity": "sha512-5MP0uUeVCec89ZbNOT/i97Mc+q3SxXmiUGhRFOTmhrGPn//uWVQdCvcLJDy64MSBR5MidFdOR7B9viumoavy6g==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-4.0.2.tgz", + "integrity": "sha512-Fy7VwgQNiOkynYyNBTo3v9hQUhcW5pFAheJN148+DTgpShjsy/22pLHKKwDK5v0kOsZsJBK+6q1PMgLvRmrwFQ==", "dev": true, "license": "MIT" }, - "node_modules/livereload/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "node_modules/livereload/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/livereload/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.3.0" + "node": ">= 14.18.0" }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/load-json-file": { @@ -6790,29 +7294,6 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -6846,6 +7327,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -6853,6 +7344,22 @@ "dev": true, "license": "MIT" }, + "node_modules/napi-postinstall": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", + "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -6860,6 +7367,13 @@ "dev": true, "license": "MIT" }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -7113,9 +7627,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", - "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "version": "2.2.21", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.21.tgz", + "integrity": "sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==", "dev": true, "license": "MIT" }, @@ -7327,6 +7841,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -7422,6 +7943,30 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", @@ -7576,17 +8121,17 @@ } }, "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", + "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -7601,33 +8146,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -7648,9 +8166,9 @@ } }, "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", "dev": true, "funding": [ { @@ -7664,13 +8182,6 @@ ], "license": "MIT" }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true, - "license": "MIT" - }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -7766,13 +8277,6 @@ "dev": true, "license": "ISC" }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" - }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -7827,15 +8331,12 @@ "node": ">=4" } }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } + "license": "MIT" }, "node_modules/safe-array-concat": { "version": "1.1.3", @@ -7900,9 +8401,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.89.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz", - "integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==", + "version": "1.91.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.91.0.tgz", + "integrity": "sha512-aFOZHGf+ur+bp1bCHZ+u8otKGh77ZtmFyXDo4tlYvT7PWql41Kwd8wdkPqhhT+h2879IVblcHFglIMofsFd1EA==", "dev": true, "license": "MIT", "dependencies": { @@ -8148,13 +8649,6 @@ "dev": true, "license": "ISC" }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -8302,6 +8796,29 @@ "node": ">=10" } }, + "node_modules/string-length/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-length/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -8317,6 +8834,62 @@ "node": ">=6" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string-width/node_modules/ansi-regex": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", @@ -8419,6 +8992,23 @@ } }, "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", @@ -8431,6 +9021,16 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -8502,6 +9102,22 @@ "dev": true, "license": "MIT" }, + "node_modules/synckit": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", + "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@pkgr/core": "^0.2.9" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/synckit" + } + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -8517,6 +9133,48 @@ "node": ">=8" } }, + "node_modules/test-exclude/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -8537,44 +9195,41 @@ } }, "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" + "tldts": "^6.1.32" }, "engines": { - "node": ">=6" + "node": ">=16" } }, "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", "dev": true, "license": "MIT", "dependencies": { - "punycode": "^2.1.1" + "punycode": "^2.3.1" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/ts-jest": { - "version": "29.4.0", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.0.tgz", - "integrity": "sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==", + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.1.tgz", + "integrity": "sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", - "ejs": "^3.1.10", "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", @@ -8724,6 +9379,14 @@ "node": ">=4" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8839,9 +9502,9 @@ } }, "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", "bin": { @@ -8858,6 +9521,20 @@ "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "license": "MIT" }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -8883,14 +9560,39 @@ "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", "license": "MIT" }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "engines": { - "node": ">= 4.0.0" + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "node_modules/update-browserslist-db": { @@ -8934,17 +9636,6 @@ "punycode": "^2.1.0" } }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -8985,16 +9676,16 @@ "license": "MIT" }, "node_modules/w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", "dev": true, "license": "MIT", "dependencies": { - "xml-name-validator": "^4.0.0" + "xml-name-validator": "^5.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/walker": { @@ -9018,40 +9709,40 @@ } }, "node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", "dev": true, "license": "MIT", "dependencies": { "iconv-lite": "0.6.3" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", "dev": true, "license": "MIT", "dependencies": { - "tr46": "^3.0.0", + "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/which": { @@ -9176,6 +9867,13 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", @@ -9191,6 +9889,80 @@ "node": ">=6" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", @@ -9252,17 +10024,30 @@ "license": "ISC" }, "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ws": { @@ -9288,13 +10073,13 @@ } }, "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/xmlchars": { diff --git a/package.json b/package.json index 151338d8c..9ec94acc2 100644 --- a/package.json +++ b/package.json @@ -19,43 +19,43 @@ "test": "jest" }, "devDependencies": { - "@eslint/js": "^9.21.0", - "@lezer/generator": "^1.7.2", + "@eslint/js": "^9.34.0", + "@lezer/generator": "^1.8.0", "@types/markdown-it": "^14.1.2", "@types/sortablejs": "^1.15.8", "chokidar-cli": "^3.0", - "esbuild": "^0.25.0", - "eslint": "^9.21.0", - "eslint-plugin-import": "^2.31.0", - "jest": "^29.7.0", - "jest-environment-jsdom": "^29.7.0", - "livereload": "^0.9.3", + "esbuild": "^0.25.9", + "eslint": "^9.34.0", + "eslint-plugin-import": "^2.32.0", + "jest": "^30.1.1", + "jest-environment-jsdom": "^30.1.1", + "livereload": "^0.10.3", "npm-run-all": "^4.1.5", - "sass": "^1.85.0", - "ts-jest": "^29.2.6", + "sass": "^1.91.0", + "ts-jest": "^29.4.1", "ts-node": "^10.9.2", - "typescript": "5.7.*" + "typescript": "5.9.*" }, "dependencies": { - "@codemirror/commands": "^6.8.0", + "@codemirror/commands": "^6.8.1", "@codemirror/lang-css": "^6.3.1", "@codemirror/lang-html": "^6.4.9", - "@codemirror/lang-javascript": "^6.2.3", - "@codemirror/lang-json": "^6.0.1", - "@codemirror/lang-markdown": "^6.3.2", - "@codemirror/lang-php": "^6.0.1", + "@codemirror/lang-javascript": "^6.2.4", + "@codemirror/lang-json": "^6.0.2", + "@codemirror/lang-markdown": "^6.3.4", + "@codemirror/lang-php": "^6.0.2", "@codemirror/lang-xml": "^6.1.0", - "@codemirror/language": "^6.10.8", - "@codemirror/legacy-modes": "^6.4.3", + "@codemirror/language": "^6.11.3", + "@codemirror/legacy-modes": "^6.5.1", "@codemirror/state": "^6.5.2", - "@codemirror/theme-one-dark": "^6.1.2", - "@codemirror/view": "^6.36.3", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.38.1", "@lezer/highlight": "^1.2.1", "@ssddanbrown/codemirror-lang-smarty": "^1.0.0", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", - "@types/jest": "^29.5.14", - "codemirror": "^6.0.1", - "idb-keyval": "^6.2.1", + "@types/jest": "^30.0.0", + "codemirror": "^6.0.2", + "idb-keyval": "^6.2.2", "markdown-it": "^14.1.0", "markdown-it-task-lists": "^2.1.1", "snabbdom": "^3.6.2", diff --git a/resources/js/wysiwyg/lexical/core/LexicalSelection.ts b/resources/js/wysiwyg/lexical/core/LexicalSelection.ts index 7051336d5..36e2db547 100644 --- a/resources/js/wysiwyg/lexical/core/LexicalSelection.ts +++ b/resources/js/wysiwyg/lexical/core/LexicalSelection.ts @@ -2708,6 +2708,8 @@ export function updateDOMSelection( const range = document.createRange(); range.selectNode(selectionTarget); selectionRect = range.getBoundingClientRect(); + } else if (selectionTarget instanceof Range) { + selectionRect = selectionTarget.getBoundingClientRect(); } else { selectionRect = selectionTarget.getBoundingClientRect(); } diff --git a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts index a54d33ca4..f195974d0 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts @@ -2510,8 +2510,8 @@ describe('LexicalEditor tests', () => { ); }); - expect(onError).toBeCalledWith(updateError); - expect(textListener).toBeCalledWith('Hello\n\nworld'); + expect(onError).toHaveBeenCalledWith(updateError); + expect(textListener).toHaveBeenCalledWith('Hello\n\nworld'); expect(updateListener.mock.lastCall[0].prevEditorState).toBe(editorState); }); diff --git a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalNode.test.ts b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalNode.test.ts index fcf666213..33c59bf5f 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalNode.test.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/unit/LexicalNode.test.ts @@ -138,7 +138,7 @@ describe('LexicalNode tests', () => { const validNode = new TextNode(textNode.__text, textNode.__key); expect(textNode.getLatest()).toBe(textNode); expect(validNode.getLatest()).toBe(textNode); - expect(() => new TestNode(textNode.__key)).toThrowError( + expect(() => new TestNode(textNode.__key)).toThrow( /TestNode.*re-use key.*TextNode/, ); }); diff --git a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts index fd87877ee..00c5ec796 100644 --- a/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts +++ b/resources/js/wysiwyg/lexical/core/__tests__/utils/index.ts @@ -864,4 +864,26 @@ export function dispatchEditorMouseClick(editor: LexicalEditor, clientX: number, }); dom?.dispatchEvent(event); editor.commitUpdates(); +} + +export function patchRange() { + const RangePrototype = Object.getPrototypeOf(document.createRange()); + RangePrototype.getBoundingClientRect = function (): DOMRect { + const rect = { + bottom: 0, + height: 0, + left: 0, + right: 0, + top: 0, + width: 0, + x: 0, + y: 0, + }; + return { + ...rect, + toJSON() { + return rect; + }, + }; + }; } \ No newline at end of file diff --git a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalElementNode.test.ts b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalElementNode.test.ts index 6e3a3861a..0bc7646c9 100644 --- a/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalElementNode.test.ts +++ b/resources/js/wysiwyg/lexical/core/nodes/__tests__/unit/LexicalElementNode.test.ts @@ -18,10 +18,12 @@ import { } from 'lexical'; import { - $createTestElementNode, - createTestEditor, + $createTestElementNode, + createTestEditor, patchRange, } from '../../../__tests__/utils'; +patchRange(); + describe('LexicalElementNode tests', () => { let container: HTMLElement; @@ -54,6 +56,7 @@ describe('LexicalElementNode tests', () => { editor = createTestEditor(); editor.setRootElement(root); + root.focus(); // Insert initial block await update(() => { @@ -63,11 +66,11 @@ describe('LexicalElementNode tests', () => { // Prevent text nodes from combining. text2.setMode('segmented'); const text3 = $createTextNode('Baz'); - // Some operations require a selection to exist, hence - // we make a selection in the setup code. - text.select(0, 0); block.append(text, text2, text3); $getRoot().append(block); + // Some operations require a selection to exist, hence + // we make a selection in the setup code. + text.select(0, 0); }); } diff --git a/resources/js/wysiwyg/lexical/headless/__tests__/unit/LexicalHeadlessEditor.test.ts b/resources/js/wysiwyg/lexical/headless/__tests__/unit/LexicalHeadlessEditor.test.ts index c03f1bdb2..cee414919 100644 --- a/resources/js/wysiwyg/lexical/headless/__tests__/unit/LexicalHeadlessEditor.test.ts +++ b/resources/js/wysiwyg/lexical/headless/__tests__/unit/LexicalHeadlessEditor.test.ts @@ -146,12 +146,12 @@ describe('LexicalHeadlessEditor', () => { editor.dispatchCommand(CONTROLLED_TEXT_INSERTION_COMMAND, 'foo'); }); - expect(onUpdate).toBeCalled(); - expect(onCommand).toBeCalledWith('foo', expect.anything()); - expect(onTransform).toBeCalledWith( + expect(onUpdate).toHaveBeenCalled(); + expect(onCommand).toHaveBeenCalledWith('foo', expect.anything()); + expect(onTransform).toHaveBeenCalledWith( expect.objectContaining({__type: 'paragraph'}), ); - expect(onTextContent).toBeCalledWith('Helloworld'); + expect(onTextContent).toHaveBeenCalledWith('Helloworld'); }); it('can preserve selection for pending editor state (within update loop)', async () => { diff --git a/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelection.test.ts b/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelection.test.ts index cc09d1735..14c8b5121 100644 --- a/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelection.test.ts +++ b/resources/js/wysiwyg/lexical/selection/__tests__/unit/LexicalSelection.test.ts @@ -37,12 +37,12 @@ import { TextNode, } from 'lexical'; import { - $assertRangeSelection, - $createTestDecoratorNode, - $createTestElementNode, - createTestEditor, - initializeClipboard, - invariant, + $assertRangeSelection, + $createTestDecoratorNode, + $createTestElementNode, + createTestEditor, + initializeClipboard, + invariant, patchRange, } from 'lexical/__tests__/utils'; import { @@ -91,24 +91,7 @@ jest.mock('lexical/shared/environment', () => { return {...originalModule, IS_FIREFOX: true}; }); -Range.prototype.getBoundingClientRect = function (): DOMRect { - const rect = { - bottom: 0, - height: 0, - left: 0, - right: 0, - top: 0, - width: 0, - x: 0, - y: 0, - }; - return { - ...rect, - toJSON() { - return rect; - }, - }; -}; +patchRange(); describe('LexicalSelection tests', () => { let container: HTMLElement; diff --git a/resources/js/wysiwyg/lexical/selection/__tests__/utils/index.ts b/resources/js/wysiwyg/lexical/selection/__tests__/utils/index.ts index 84c82edec..39d4a04bd 100644 --- a/resources/js/wysiwyg/lexical/selection/__tests__/utils/index.ts +++ b/resources/js/wysiwyg/lexical/selection/__tests__/utils/index.ts @@ -422,9 +422,6 @@ export function setNativeSelection( range.setEnd(focusNode, focusOffset); domSelection.removeAllRanges(); domSelection.addRange(range); - Promise.resolve().then(() => { - document.dispatchEvent(new Event('selectionchange')); - }); } export function setNativeSelectionWithPaths( @@ -647,6 +644,8 @@ export async function applySelectionInputs( editor: LexicalEditor, ) { const rootElement = editor.getRootElement()!; + // Set initial focus as if we're in the editor + rootElement.focus(); for (let i = 0; i < inputs.length; i++) { const input = inputs[i]; diff --git a/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableNode.test.ts b/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableNode.test.ts index 2879decda..560d316fe 100644 --- a/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableNode.test.ts +++ b/resources/js/wysiwyg/lexical/table/__tests__/unit/LexicalTableNode.test.ts @@ -114,7 +114,7 @@ describe('LexicalTableNode tests', () => { }); // Make sure paragraph is inserted inside empty cells expect(testEnv.innerHTML).toBe( - `

Hello there

General Kenobi!

Lexical is nice


`, + `

Hello there

General Kenobi!

Lexical is nice


`, ); }); From 5ea4e1e935624e060ad2b4e257a76539ec1a4746 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 2 Sep 2025 10:20:10 +0100 Subject: [PATCH 07/48] Maintenance: Removed unused comments text column Has been redundant and unused for a about a year now. Closes #4821 --- ..._02_111542_remove_comments_text_column.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 database/migrations/2025_09_02_111542_remove_comments_text_column.php diff --git a/database/migrations/2025_09_02_111542_remove_comments_text_column.php b/database/migrations/2025_09_02_111542_remove_comments_text_column.php new file mode 100644 index 000000000..8caa05e54 --- /dev/null +++ b/database/migrations/2025_09_02_111542_remove_comments_text_column.php @@ -0,0 +1,28 @@ +dropColumn('text'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('comments', function (Blueprint $table) { + $table->longText('text')->nullable(); + }); + } +}; From 1e34954554d672446693bcc74bf7f9aae1936802 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 2 Sep 2025 11:10:47 +0100 Subject: [PATCH 08/48] Maintenance: Continued work towards PHPstan level 2 Updated html description code to be behind a proper interface. Set new convention for mode traits/interfaces. --- app/Entities/Models/Book.php | 5 +-- app/Entities/Models/Bookshelf.php | 4 +-- app/Entities/Models/Chapter.php | 4 +-- ...CoverImage.php => CoverImageInterface.php} | 2 +- .../{Deletable.php => DeletableInterface.php} | 2 +- app/Entities/Models/Deletion.php | 2 +- app/Entities/Models/Entity.php | 2 +- app/Entities/Models/HasHtmlDescription.php | 21 ----------- .../Models/HtmlDescriptionInterface.php | 17 +++++++++ app/Entities/Models/HtmlDescriptionTrait.php | 35 +++++++++++++++++++ app/Entities/Queries/PageQueries.php | 3 ++ app/Entities/Repos/BaseRepo.php | 20 +++++------ app/Entities/Tools/Cloner.php | 4 +-- app/Entities/Tools/PageIncludeParser.php | 7 ++-- app/Entities/Tools/TrashCan.php | 4 +-- app/References/ReferenceUpdater.php | 13 ++++--- app/Sorting/SortRuleController.php | 2 +- .../Controllers/ImageGalleryApiController.php | 4 +-- 18 files changed, 94 insertions(+), 57 deletions(-) rename app/Entities/Models/{HasCoverImage.php => CoverImageInterface.php} (92%) rename app/Entities/Models/{Deletable.php => DeletableInterface.php} (90%) delete mode 100644 app/Entities/Models/HasHtmlDescription.php create mode 100644 app/Entities/Models/HtmlDescriptionInterface.php create mode 100644 app/Entities/Models/HtmlDescriptionTrait.php diff --git a/app/Entities/Models/Book.php b/app/Entities/Models/Book.php index ede4fc7d5..88e3b85ba 100644 --- a/app/Entities/Models/Book.php +++ b/app/Entities/Models/Book.php @@ -26,10 +26,10 @@ use Illuminate\Support\Collection; * @property ?Page $defaultTemplate * @property ?SortRule $sortRule */ -class Book extends Entity implements HasCoverImage +class Book extends Entity implements CoverImageInterface, HtmlDescriptionInterface { use HasFactory; - use HasHtmlDescription; + use HtmlDescriptionTrait; public float $searchFactor = 1.2; @@ -111,6 +111,7 @@ class Book extends Entity implements HasCoverImage /** * Get all chapters within this book. + * @return HasMany */ public function chapters(): HasMany { diff --git a/app/Entities/Models/Bookshelf.php b/app/Entities/Models/Bookshelf.php index 9ffa0ea9c..5b403d9c0 100644 --- a/app/Entities/Models/Bookshelf.php +++ b/app/Entities/Models/Bookshelf.php @@ -8,10 +8,10 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; -class Bookshelf extends Entity implements HasCoverImage +class Bookshelf extends Entity implements CoverImageInterface, HtmlDescriptionInterface { use HasFactory; - use HasHtmlDescription; + use HtmlDescriptionTrait; protected $table = 'bookshelves'; diff --git a/app/Entities/Models/Chapter.php b/app/Entities/Models/Chapter.php index 088d199da..03e819c06 100644 --- a/app/Entities/Models/Chapter.php +++ b/app/Entities/Models/Chapter.php @@ -14,10 +14,10 @@ use Illuminate\Support\Collection; * @property ?int $default_template_id * @property ?Page $defaultTemplate */ -class Chapter extends BookChild +class Chapter extends BookChild implements HtmlDescriptionInterface { use HasFactory; - use HasHtmlDescription; + use HtmlDescriptionTrait; public float $searchFactor = 1.2; diff --git a/app/Entities/Models/HasCoverImage.php b/app/Entities/Models/CoverImageInterface.php similarity index 92% rename from app/Entities/Models/HasCoverImage.php rename to app/Entities/Models/CoverImageInterface.php index f665efce6..5f781fe02 100644 --- a/app/Entities/Models/HasCoverImage.php +++ b/app/Entities/Models/CoverImageInterface.php @@ -4,7 +4,7 @@ namespace BookStack\Entities\Models; use Illuminate\Database\Eloquent\Relations\BelongsTo; -interface HasCoverImage +interface CoverImageInterface { /** * Get the cover image for this item. diff --git a/app/Entities/Models/Deletable.php b/app/Entities/Models/DeletableInterface.php similarity index 90% rename from app/Entities/Models/Deletable.php rename to app/Entities/Models/DeletableInterface.php index a2c7fad81..f771d9c69 100644 --- a/app/Entities/Models/Deletable.php +++ b/app/Entities/Models/DeletableInterface.php @@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Relations\MorphMany; * A model that can be deleted in a manner that deletions * are tracked to be part of the recycle bin system. */ -interface Deletable +interface DeletableInterface { public function deletions(): MorphMany; } diff --git a/app/Entities/Models/Deletion.php b/app/Entities/Models/Deletion.php index a73437c94..55589f61e 100644 --- a/app/Entities/Models/Deletion.php +++ b/app/Entities/Models/Deletion.php @@ -13,7 +13,7 @@ use Illuminate\Database\Eloquent\Relations\MorphTo; * @property int $deleted_by * @property string $deletable_type * @property int $deletable_id - * @property Deletable $deletable + * @property DeletableInterface $deletable */ class Deletion extends Model implements Loggable { diff --git a/app/Entities/Models/Entity.php b/app/Entities/Models/Entity.php index 1ef4e618d..46b29f93b 100644 --- a/app/Entities/Models/Entity.php +++ b/app/Entities/Models/Entity.php @@ -49,7 +49,7 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @method static Builder withLastView() * @method static Builder withViewCount() */ -abstract class Entity extends Model implements Sluggable, Favouritable, Viewable, Deletable, Loggable +abstract class Entity extends Model implements Sluggable, Favouritable, Viewable, DeletableInterface, Loggable { use SoftDeletes; use HasCreatorAndUpdater; diff --git a/app/Entities/Models/HasHtmlDescription.php b/app/Entities/Models/HasHtmlDescription.php deleted file mode 100644 index c9f08616d..000000000 --- a/app/Entities/Models/HasHtmlDescription.php +++ /dev/null @@ -1,21 +0,0 @@ -description_html ?: '

' . nl2br(e($this->description)) . '

'; - return HtmlContentFilter::removeScriptsFromHtmlString($html); - } -} diff --git a/app/Entities/Models/HtmlDescriptionInterface.php b/app/Entities/Models/HtmlDescriptionInterface.php new file mode 100644 index 000000000..ffe7f0c5f --- /dev/null +++ b/app/Entities/Models/HtmlDescriptionInterface.php @@ -0,0 +1,17 @@ +description_html ?: '

' . nl2br(e($this->description)) . '

'; + if ($raw) { + return $html; + } + + return HtmlContentFilter::removeScriptsFromHtmlString($html); + } + + public function setDescriptionHtml(string $html, string|null $plaintext = null): void + { + $this->description_html = $html; + + if ($plaintext !== null) { + $this->description = $plaintext; + } + + if (empty($html) && !empty($plaintext)) { + $this->description_html = $this->descriptionHtml(); + } + } +} diff --git a/app/Entities/Queries/PageQueries.php b/app/Entities/Queries/PageQueries.php index 06298f470..f821ee86a 100644 --- a/app/Entities/Queries/PageQueries.php +++ b/app/Entities/Queries/PageQueries.php @@ -66,6 +66,9 @@ class PageQueries implements ProvidesEntityQueries }); } + /** + * @return Builder + */ public function visibleForList(): Builder { return $this->start() diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index ac5a44e67..f4d05d8af 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -7,8 +7,9 @@ use BookStack\Entities\Models\Book; use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; -use BookStack\Entities\Models\HasCoverImage; -use BookStack\Entities\Models\HasHtmlDescription; +use BookStack\Entities\Models\CoverImageInterface; +use BookStack\Entities\Models\HtmlDescriptionInterface; +use BookStack\Entities\Models\HtmlDescriptionTrait; use BookStack\Entities\Queries\PageQueries; use BookStack\Exceptions\ImageUploadException; use BookStack\References\ReferenceStore; @@ -88,7 +89,7 @@ class BaseRepo /** * Update the given items' cover image, or clear it. * - * @param Entity&HasCoverImage $entity + * @param Entity&CoverImageInterface $entity * * @throws ImageUploadException * @throws \Exception @@ -150,18 +151,17 @@ class BaseRepo protected function updateDescription(Entity $entity, array $input): void { - if (!in_array(HasHtmlDescription::class, class_uses($entity))) { + if (!($entity instanceof HtmlDescriptionInterface)) { return; } - /** @var HasHtmlDescription $entity */ if (isset($input['description_html'])) { - $entity->description_html = HtmlDescriptionFilter::filterFromString($input['description_html']); - $entity->description = html_entity_decode(strip_tags($input['description_html'])); + $entity->setDescriptionHtml( + HtmlDescriptionFilter::filterFromString($input['description_html']), + html_entity_decode(strip_tags($input['description_html'])) + ); } else if (isset($input['description'])) { - $entity->description = $input['description']; - $entity->description_html = ''; - $entity->description_html = $entity->descriptionHtml(); + $entity->setDescriptionHtml('', $input['description']); } } } diff --git a/app/Entities/Tools/Cloner.php b/app/Entities/Tools/Cloner.php index 2be6083e3..87aa770c0 100644 --- a/app/Entities/Tools/Cloner.php +++ b/app/Entities/Tools/Cloner.php @@ -7,7 +7,7 @@ use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; -use BookStack\Entities\Models\HasCoverImage; +use BookStack\Entities\Models\CoverImageInterface; use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Repos\ChapterRepo; @@ -105,7 +105,7 @@ class Cloner $inputData['tags'] = $this->entityTagsToInputArray($entity); // Add a cover to the data if existing on the original entity - if ($entity instanceof HasCoverImage) { + if ($entity instanceof CoverImageInterface) { $cover = $entity->cover()->first(); if ($cover) { $inputData['image'] = $this->imageToUploadedFile($cover); diff --git a/app/Entities/Tools/PageIncludeParser.php b/app/Entities/Tools/PageIncludeParser.php index e0b89f158..329a7633f 100644 --- a/app/Entities/Tools/PageIncludeParser.php +++ b/app/Entities/Tools/PageIncludeParser.php @@ -7,7 +7,6 @@ use Closure; use DOMDocument; use DOMElement; use DOMNode; -use DOMText; class PageIncludeParser { @@ -159,7 +158,7 @@ class PageIncludeParser /** * Splits the given $parentNode at the location of the $domNode within it. - * Attempts replicate the original $parentNode, moving some of their parent + * Attempts to replicate the original $parentNode, moving some of their parent * children in where needed, before adding the $domNode between. */ protected function splitNodeAtChildNode(DOMElement $parentNode, DOMNode $domNode): void @@ -171,6 +170,10 @@ class PageIncludeParser } $parentClone = $parentNode->cloneNode(); + if (!($parentClone instanceof DOMElement)) { + return; + } + $parentNode->parentNode->insertBefore($parentClone, $parentNode); $parentClone->removeAttribute('id'); diff --git a/app/Entities/Tools/TrashCan.php b/app/Entities/Tools/TrashCan.php index 5e8a93719..d457d4f48 100644 --- a/app/Entities/Tools/TrashCan.php +++ b/app/Entities/Tools/TrashCan.php @@ -8,7 +8,7 @@ use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Deletion; use BookStack\Entities\Models\Entity; -use BookStack\Entities\Models\HasCoverImage; +use BookStack\Entities\Models\CoverImageInterface; use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\EntityQueries; use BookStack\Exceptions\NotifyException; @@ -398,7 +398,7 @@ class TrashCan $entity->referencesTo()->delete(); $entity->referencesFrom()->delete(); - if ($entity instanceof HasCoverImage && $entity->cover()->exists()) { + if ($entity instanceof CoverImageInterface && $entity->cover()->exists()) { $imageService = app()->make(ImageService::class); $imageService->destroy($entity->cover()->first()); } diff --git a/app/References/ReferenceUpdater.php b/app/References/ReferenceUpdater.php index db355f211..5f1d711e9 100644 --- a/app/References/ReferenceUpdater.php +++ b/app/References/ReferenceUpdater.php @@ -4,7 +4,8 @@ namespace BookStack\References; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Entity; -use BookStack\Entities\Models\HasHtmlDescription; +use BookStack\Entities\Models\HtmlDescriptionInterface; +use BookStack\Entities\Models\HtmlDescriptionTrait; use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\RevisionRepo; use BookStack\Util\HtmlDocument; @@ -61,20 +62,18 @@ class ReferenceUpdater { if ($entity instanceof Page) { $this->updateReferencesWithinPage($entity, $oldLink, $newLink); - return; } - if (in_array(HasHtmlDescription::class, class_uses($entity))) { + if ($entity instanceof HtmlDescriptionInterface) { $this->updateReferencesWithinDescription($entity, $oldLink, $newLink); } } - protected function updateReferencesWithinDescription(Entity $entity, string $oldLink, string $newLink): void + protected function updateReferencesWithinDescription(Entity&HtmlDescriptionInterface $entity, string $oldLink, string $newLink): void { - /** @var HasHtmlDescription&Entity $entity */ $entity = (clone $entity)->refresh(); - $html = $this->updateLinksInHtml($entity->description_html ?: '', $oldLink, $newLink); - $entity->description_html = $html; + $html = $this->updateLinksInHtml($entity->descriptionHtml(true) ?: '', $oldLink, $newLink); + $entity->setDescriptionHtml($html); $entity->save(); } diff --git a/app/Sorting/SortRuleController.php b/app/Sorting/SortRuleController.php index 96b8e8ef5..a124ffa9c 100644 --- a/app/Sorting/SortRuleController.php +++ b/app/Sorting/SortRuleController.php @@ -29,7 +29,7 @@ class SortRuleController extends Controller $operations = SortRuleOperation::fromSequence($request->input('sequence')); if (count($operations) === 0) { - return redirect()->withInput()->withErrors(['sequence' => 'No operations set.']); + return redirect('/settings/sorting/rules/new')->withInput()->withErrors(['sequence' => 'No operations set.']); } $rule = new SortRule(); diff --git a/app/Uploads/Controllers/ImageGalleryApiController.php b/app/Uploads/Controllers/ImageGalleryApiController.php index 6d4657a7a..d2a750c45 100644 --- a/app/Uploads/Controllers/ImageGalleryApiController.php +++ b/app/Uploads/Controllers/ImageGalleryApiController.php @@ -146,10 +146,10 @@ class ImageGalleryApiController extends ApiController $data['content']['html'] = "
id}\">
"; $data['content']['markdown'] = $data['content']['html']; } else { - $escapedDisplayThumb = htmlentities($image->thumbs['display']); + $escapedDisplayThumb = htmlentities($image->getAttribute('thumbs')['display']); $data['content']['html'] = "\"{$escapedName}\""; $mdEscapedName = str_replace(']', '', str_replace('[', '', $image->name)); - $mdEscapedThumb = str_replace(']', '', str_replace('[', '', $image->thumbs['display'])); + $mdEscapedThumb = str_replace(']', '', str_replace('[', '', $image->getAttribute('thumbs')['display'])); $data['content']['markdown'] = "![{$mdEscapedName}]({$mdEscapedThumb})"; } From cee23de6c5350101e16e1aead59e0219e9e2771e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 2 Sep 2025 16:02:52 +0100 Subject: [PATCH 09/48] Maintenance: Reached PHPstan level 2 Reworked some stuff around slugs to use interface in a better way. Also standardised phpdoc to use @return instead of @returns --- app/Access/Saml2Service.php | 2 +- app/Access/SocialDriverManager.php | 2 +- app/Activity/Models/Comment.php | 7 +++-- app/Activity/WatchLevels.php | 4 +-- app/App/Model.php | 2 +- .../{Sluggable.php => SluggableInterface.php} | 5 +-- .../Controllers/RecycleBinApiController.php | 19 ++++++++---- app/Entities/Models/Book.php | 3 +- app/Entities/Models/Bookshelf.php | 1 + app/Entities/Models/Chapter.php | 4 +-- app/Entities/Models/Entity.php | 31 ++++++++++++++++--- app/Entities/Queries/BookQueries.php | 3 ++ app/Entities/Queries/BookshelfQueries.php | 3 ++ app/Entities/Queries/EntityQueries.php | 2 +- .../Queries/ProvidesEntityQueries.php | 3 +- app/Entities/Repos/BaseRepo.php | 6 ++-- app/Entities/Tools/SlugGenerator.php | 12 +++---- app/Permissions/PermissionApplicator.php | 15 ++++----- app/References/CrossLinkParser.php | 2 +- app/Search/SearchIndex.php | 12 +++---- app/Sorting/BookSorter.php | 2 +- app/Theming/ThemeEvents.php | 12 +++---- app/Uploads/Attachment.php | 3 +- app/Uploads/Image.php | 3 +- app/Uploads/ImageService.php | 2 +- app/Uploads/ImageStorageDisk.php | 2 +- app/Users/Controllers/UserApiController.php | 2 +- app/Users/Models/HasCreatorAndUpdater.php | 5 +++ app/Users/Models/HasOwner.php | 19 ------------ app/Users/Models/OwnableInterface.php | 8 +++++ app/Users/Models/Role.php | 1 + app/Users/Models/User.php | 6 ++-- phpcs.xml | 2 +- phpstan.neon.dist | 2 +- 34 files changed, 118 insertions(+), 89 deletions(-) rename app/App/{Sluggable.php => SluggableInterface.php} (75%) delete mode 100644 app/Users/Models/HasOwner.php create mode 100644 app/Users/Models/OwnableInterface.php diff --git a/app/Access/Saml2Service.php b/app/Access/Saml2Service.php index bb7e9b572..106a7a229 100644 --- a/app/Access/Saml2Service.php +++ b/app/Access/Saml2Service.php @@ -51,7 +51,7 @@ class Saml2Service * Returns the SAML2 request ID, and the URL to redirect the user to. * * @throws Error - * @returns array{url: string, id: ?string} + * @return array{url: string, id: ?string} */ public function logout(User $user): array { diff --git a/app/Access/SocialDriverManager.php b/app/Access/SocialDriverManager.php index dafc0e82d..efafab560 100644 --- a/app/Access/SocialDriverManager.php +++ b/app/Access/SocialDriverManager.php @@ -55,7 +55,7 @@ class SocialDriverManager /** * Gets the names of the active social drivers, keyed by driver id. - * @returns array + * @return array */ public function getActive(): array { diff --git a/app/Activity/Models/Comment.php b/app/Activity/Models/Comment.php index 91cea4fe0..6b05a9bab 100644 --- a/app/Activity/Models/Comment.php +++ b/app/Activity/Models/Comment.php @@ -4,6 +4,8 @@ namespace BookStack\Activity\Models; use BookStack\App\Model; use BookStack\Users\Models\HasCreatorAndUpdater; +use BookStack\Users\Models\OwnableInterface; +use BookStack\Users\Models\User; use BookStack\Util\HtmlContentFilter; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -17,12 +19,10 @@ use Illuminate\Database\Eloquent\Relations\MorphTo; * @property int $local_id * @property string $entity_type * @property int $entity_id - * @property int $created_by - * @property int $updated_by * @property string $content_ref * @property bool $archived */ -class Comment extends Model implements Loggable +class Comment extends Model implements Loggable, OwnableInterface { use HasFactory; use HasCreatorAndUpdater; @@ -39,6 +39,7 @@ class Comment extends Model implements Loggable /** * Get the parent comment this is in reply to (if existing). + * @return BelongsTo */ public function parent(): BelongsTo { diff --git a/app/Activity/WatchLevels.php b/app/Activity/WatchLevels.php index de3c5e122..edbece2d3 100644 --- a/app/Activity/WatchLevels.php +++ b/app/Activity/WatchLevels.php @@ -36,7 +36,7 @@ class WatchLevels /** * Get all the possible values as an option_name => value array. - * @returns array + * @return array */ public static function all(): array { @@ -50,7 +50,7 @@ class WatchLevels /** * Get the watch options suited for the given entity. - * @returns array + * @return array */ public static function allSuitedFor(Entity $entity): array { diff --git a/app/App/Model.php b/app/App/Model.php index 8de5a2762..e1c7511c1 100644 --- a/app/App/Model.php +++ b/app/App/Model.php @@ -8,7 +8,7 @@ class Model extends EloquentModel { /** * Provides public access to get the raw attribute value from the model. - * Used in areas where no mutations are required but performance is critical. + * Used in areas where no mutations are required, but performance is critical. * * @return mixed */ diff --git a/app/App/Sluggable.php b/app/App/SluggableInterface.php similarity index 75% rename from app/App/Sluggable.php rename to app/App/SluggableInterface.php index f8da2e265..96af49cd3 100644 --- a/app/App/Sluggable.php +++ b/app/App/SluggableInterface.php @@ -5,11 +5,8 @@ namespace BookStack\App; /** * Assigned to models that can have slugs. * Must have the below properties. - * - * @property int $id - * @property string $name */ -interface Sluggable +interface SluggableInterface { /** * Regenerate the slug for this model. diff --git a/app/Entities/Controllers/RecycleBinApiController.php b/app/Entities/Controllers/RecycleBinApiController.php index bf22d7dcd..fdc24ddf8 100644 --- a/app/Entities/Controllers/RecycleBinApiController.php +++ b/app/Entities/Controllers/RecycleBinApiController.php @@ -6,10 +6,10 @@ use BookStack\Entities\Models\Book; use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Deletion; +use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\DeletionRepo; use BookStack\Http\ApiController; -use Closure; -use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Relations\HasMany; class RecycleBinApiController extends ApiController { @@ -40,7 +40,7 @@ class RecycleBinApiController extends ApiController 'updated_at', 'deletable_type', 'deletable_id', - ], [Closure::fromCallable([$this, 'listFormatter'])]); + ], [$this->listFormatter(...)]); } /** @@ -72,7 +72,6 @@ class RecycleBinApiController extends ApiController protected function listFormatter(Deletion $deletion) { $deletable = $deletion->deletable; - $withTrashedQuery = fn (Builder $query) => $query->withTrashed(); if ($deletable instanceof BookChild) { $parent = $deletable->getParent(); @@ -81,11 +80,19 @@ class RecycleBinApiController extends ApiController } if ($deletable instanceof Book || $deletable instanceof Chapter) { - $countsToLoad = ['pages' => $withTrashedQuery]; + $countsToLoad = ['pages' => static::withTrashedQuery(...)]; if ($deletable instanceof Book) { - $countsToLoad['chapters'] = $withTrashedQuery; + $countsToLoad['chapters'] = static::withTrashedQuery(...); } $deletable->loadCount($countsToLoad); } } + + /** + * @param HasMany $query + */ + protected static function withTrashedQuery(HasMany $query): void + { + $query->withTrashed(); + } } diff --git a/app/Entities/Models/Book.php b/app/Entities/Models/Book.php index 88e3b85ba..5f54e0f6a 100644 --- a/app/Entities/Models/Book.php +++ b/app/Entities/Models/Book.php @@ -95,6 +95,7 @@ class Book extends Entity implements CoverImageInterface, HtmlDescriptionInterfa /** * Get all pages within this book. + * @return HasMany */ public function pages(): HasMany { @@ -111,7 +112,7 @@ class Book extends Entity implements CoverImageInterface, HtmlDescriptionInterfa /** * Get all chapters within this book. - * @return HasMany + * @return HasMany */ public function chapters(): HasMany { diff --git a/app/Entities/Models/Bookshelf.php b/app/Entities/Models/Bookshelf.php index 5b403d9c0..9ae52abcb 100644 --- a/app/Entities/Models/Bookshelf.php +++ b/app/Entities/Models/Bookshelf.php @@ -70,6 +70,7 @@ class Bookshelf extends Entity implements CoverImageInterface, HtmlDescriptionIn /** * Get the cover image of the shelf. + * @return BelongsTo */ public function cover(): BelongsTo { diff --git a/app/Entities/Models/Chapter.php b/app/Entities/Models/Chapter.php index 03e819c06..d70a49e7a 100644 --- a/app/Entities/Models/Chapter.php +++ b/app/Entities/Models/Chapter.php @@ -27,7 +27,7 @@ class Chapter extends BookChild implements HtmlDescriptionInterface /** * Get the pages that this chapter contains. * - * @return HasMany + * @return HasMany */ public function pages(string $dir = 'ASC'): HasMany { @@ -60,7 +60,7 @@ class Chapter extends BookChild implements HtmlDescriptionInterface /** * Get the visible pages in this chapter. - * @returns Collection + * @return Collection */ public function getVisiblePages(): Collection { diff --git a/app/Entities/Models/Entity.php b/app/Entities/Models/Entity.php index 46b29f93b..31511aa83 100644 --- a/app/Entities/Models/Entity.php +++ b/app/Entities/Models/Entity.php @@ -12,7 +12,7 @@ use BookStack\Activity\Models\View; use BookStack\Activity\Models\Viewable; use BookStack\Activity\Models\Watch; use BookStack\App\Model; -use BookStack\App\Sluggable; +use BookStack\App\SluggableInterface; use BookStack\Entities\Tools\SlugGenerator; use BookStack\Permissions\JointPermissionBuilder; use BookStack\Permissions\Models\EntityPermission; @@ -22,7 +22,8 @@ use BookStack\References\Reference; use BookStack\Search\SearchIndex; use BookStack\Search\SearchTerm; use BookStack\Users\Models\HasCreatorAndUpdater; -use BookStack\Users\Models\HasOwner; +use BookStack\Users\Models\OwnableInterface; +use BookStack\Users\Models\User; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; @@ -43,17 +44,23 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property Carbon $deleted_at * @property int $created_by * @property int $updated_by + * @property int $owned_by * @property Collection $tags * * @method static Entity|Builder visible() * @method static Builder withLastView() * @method static Builder withViewCount() */ -abstract class Entity extends Model implements Sluggable, Favouritable, Viewable, DeletableInterface, Loggable +abstract class Entity extends Model implements + SluggableInterface, + Favouritable, + Viewable, + DeletableInterface, + OwnableInterface, + Loggable { use SoftDeletes; use HasCreatorAndUpdater; - use HasOwner; /** * @var string - Name of property where the main text content is found @@ -200,6 +207,20 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable return $this->morphMany(JointPermission::class, 'entity'); } + /** + * Get the user who owns this entity. + * @return BelongsTo + */ + public function ownedBy(): BelongsTo + { + return $this->belongsTo(User::class, 'owned_by'); + } + + public function getOwnerFieldName(): string + { + return 'owned_by'; + } + /** * Get the related delete records for this entity. */ @@ -318,7 +339,7 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable */ public function refreshSlug(): string { - $this->slug = app()->make(SlugGenerator::class)->generate($this); + $this->slug = app()->make(SlugGenerator::class)->generate($this, $this->name); return $this->slug; } diff --git a/app/Entities/Queries/BookQueries.php b/app/Entities/Queries/BookQueries.php index 534640621..5ff22252c 100644 --- a/app/Entities/Queries/BookQueries.php +++ b/app/Entities/Queries/BookQueries.php @@ -13,6 +13,9 @@ class BookQueries implements ProvidesEntityQueries 'created_at', 'updated_at', 'image_id', 'owned_by', ]; + /** + * @return Builder + */ public function start(): Builder { return Book::query(); diff --git a/app/Entities/Queries/BookshelfQueries.php b/app/Entities/Queries/BookshelfQueries.php index 19717fb7c..874bc7f2f 100644 --- a/app/Entities/Queries/BookshelfQueries.php +++ b/app/Entities/Queries/BookshelfQueries.php @@ -13,6 +13,9 @@ class BookshelfQueries implements ProvidesEntityQueries 'created_at', 'updated_at', 'image_id', 'owned_by', ]; + /** + * @return Builder + */ public function start(): Builder { return Bookshelf::query(); diff --git a/app/Entities/Queries/EntityQueries.php b/app/Entities/Queries/EntityQueries.php index 36dc6c0bc..0d2cd7acf 100644 --- a/app/Entities/Queries/EntityQueries.php +++ b/app/Entities/Queries/EntityQueries.php @@ -35,6 +35,7 @@ class EntityQueries /** * Start a query of visible entities of the given type, * suitable for listing display. + * @return Builder */ public function visibleForList(string $entityType): Builder { @@ -44,7 +45,6 @@ class EntityQueries protected function getQueriesForType(string $type): ProvidesEntityQueries { - /** @var ?ProvidesEntityQueries $queries */ $queries = match ($type) { 'page' => $this->pages, 'chapter' => $this->chapters, diff --git a/app/Entities/Queries/ProvidesEntityQueries.php b/app/Entities/Queries/ProvidesEntityQueries.php index 611d0ae52..1f8a71ae2 100644 --- a/app/Entities/Queries/ProvidesEntityQueries.php +++ b/app/Entities/Queries/ProvidesEntityQueries.php @@ -22,13 +22,14 @@ interface ProvidesEntityQueries public function start(): Builder; /** - * Find the entity of the given ID, or return null if not found. + * Find the entity of the given ID or return null if not found. */ public function findVisibleById(int $id): ?Entity; /** * Start a query for items that are visible, with selection * configured for list display of this item. + * @return Builder */ public function visibleForList(): Builder; } diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index f4d05d8af..bfc01a58d 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -89,12 +89,10 @@ class BaseRepo /** * Update the given items' cover image, or clear it. * - * @param Entity&CoverImageInterface $entity - * * @throws ImageUploadException * @throws \Exception */ - public function updateCoverImage($entity, ?UploadedFile $coverImage, bool $removeImage = false) + public function updateCoverImage(Entity&CoverImageInterface $entity, ?UploadedFile $coverImage, bool $removeImage = false) { if ($coverImage) { $imageType = $entity->coverImageTypeKey(); @@ -106,7 +104,7 @@ class BaseRepo if ($removeImage) { $this->imageRepo->destroyImage($entity->cover()->first()); - $entity->image_id = 0; + $entity->cover()->dissociate(); $entity->save(); } } diff --git a/app/Entities/Tools/SlugGenerator.php b/app/Entities/Tools/SlugGenerator.php index 5df300bb0..fb9123187 100644 --- a/app/Entities/Tools/SlugGenerator.php +++ b/app/Entities/Tools/SlugGenerator.php @@ -3,7 +3,7 @@ namespace BookStack\Entities\Tools; use BookStack\App\Model; -use BookStack\App\Sluggable; +use BookStack\App\SluggableInterface; use BookStack\Entities\Models\BookChild; use Illuminate\Support\Str; @@ -13,9 +13,9 @@ class SlugGenerator * Generate a fresh slug for the given entity. * The slug will be generated so that it doesn't conflict within the same parent item. */ - public function generate(Sluggable $model): string + public function generate(SluggableInterface&Model $model, string $slugSource): string { - $slug = $this->formatNameAsSlug($model->name); + $slug = $this->formatNameAsSlug($slugSource); while ($this->slugInUse($slug, $model)) { $slug .= '-' . Str::random(3); } @@ -24,7 +24,7 @@ class SlugGenerator } /** - * Format a name as a url slug. + * Format a name as a URL slug. */ protected function formatNameAsSlug(string $name): string { @@ -39,10 +39,8 @@ class SlugGenerator /** * Check if a slug is already in-use for this * type of model within the same parent. - * - * @param Sluggable&Model $model */ - protected function slugInUse(string $slug, Sluggable $model): bool + protected function slugInUse(string $slug, SluggableInterface&Model $model): bool { $query = $model->newQuery()->where('slug', '=', $slug); diff --git a/app/Permissions/PermissionApplicator.php b/app/Permissions/PermissionApplicator.php index ce4a543fd..e59b8ab67 100644 --- a/app/Permissions/PermissionApplicator.php +++ b/app/Permissions/PermissionApplicator.php @@ -7,8 +7,7 @@ use BookStack\Entities\EntityProvider; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; use BookStack\Permissions\Models\EntityPermission; -use BookStack\Users\Models\HasCreatorAndUpdater; -use BookStack\Users\Models\HasOwner; +use BookStack\Users\Models\OwnableInterface; use BookStack\Users\Models\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\Builder as QueryBuilder; @@ -24,10 +23,8 @@ class PermissionApplicator /** * Checks if an entity has a restriction set upon it. - * - * @param Model&(HasCreatorAndUpdater|HasOwner) $ownable */ - public function checkOwnableUserAccess(Model $ownable, string $permission): bool + public function checkOwnableUserAccess(Model&OwnableInterface $ownable, string $permission): bool { $explodedPermission = explode('-', $permission); $action = $explodedPermission[1] ?? $explodedPermission[0]; @@ -39,7 +36,7 @@ class PermissionApplicator $allRolePermission = $user->can($fullPermission . '-all'); $ownRolePermission = $user->can($fullPermission . '-own'); $nonJointPermissions = ['restrictions', 'image', 'attachment', 'comment']; - $ownerField = ($ownable instanceof Entity) ? 'owned_by' : 'created_by'; + $ownerField = $ownable->getOwnerFieldName(); $ownableFieldVal = $ownable->getAttribute($ownerField); if (is_null($ownableFieldVal)) { @@ -49,11 +46,15 @@ class PermissionApplicator $isOwner = $user->id === $ownableFieldVal; $hasRolePermission = $allRolePermission || ($isOwner && $ownRolePermission); - // Handle non entity specific jointPermissions + // Handle non-entity-specific jointPermissions if (in_array($explodedPermission[0], $nonJointPermissions)) { return $hasRolePermission; } + if (!($ownable instanceof Entity)) { + return false; + } + $hasApplicableEntityPermissions = $this->hasEntityPermission($ownable, $userRoleIds, $action); return is_null($hasApplicableEntityPermissions) ? $hasRolePermission : $hasApplicableEntityPermissions; diff --git a/app/References/CrossLinkParser.php b/app/References/CrossLinkParser.php index 1fd4c1b3e..3fb00be84 100644 --- a/app/References/CrossLinkParser.php +++ b/app/References/CrossLinkParser.php @@ -48,7 +48,7 @@ class CrossLinkParser /** * Get a list of href values from the given document. * - * @returns string[] + * @return string[] */ protected function getLinksFromContent(string $html): array { diff --git a/app/Search/SearchIndex.php b/app/Search/SearchIndex.php index 844e3584b..117d069ea 100644 --- a/app/Search/SearchIndex.php +++ b/app/Search/SearchIndex.php @@ -119,7 +119,7 @@ class SearchIndex * Create a scored term array from the given text, where the keys are the terms * and the values are their scores. * - * @returns array + * @return array */ protected function generateTermScoreMapFromText(string $text, float $scoreAdjustment = 1): array { @@ -136,7 +136,7 @@ class SearchIndex * Create a scored term array from the given HTML, where the keys are the terms * and the values are their scores. * - * @returns array + * @return array */ protected function generateTermScoreMapFromHtml(string $html): array { @@ -177,7 +177,7 @@ class SearchIndex * * @param Tag[] $tags * - * @returns array + * @return array */ protected function generateTermScoreMapFromTags(array $tags): array { @@ -199,7 +199,7 @@ class SearchIndex * For the given text, return an array where the keys are the unique term words * and the values are the frequency of that term. * - * @returns array + * @return array */ protected function textToTermCountMap(string $text): array { @@ -243,7 +243,7 @@ class SearchIndex * For the given entity, Generate an array of term data details. * Is the raw term data, not instances of SearchTerm models. * - * @returns array{term: string, score: float, entity_id: int, entity_type: string}[] + * @return array{term: string, score: float, entity_id: int, entity_type: string}[] */ protected function entityToTermDataArray(Entity $entity): array { @@ -279,7 +279,7 @@ class SearchIndex * * @param array[] ...$scoreMaps * - * @returns array + * @return array */ protected function mergeTermScoreMaps(...$scoreMaps): array { diff --git a/app/Sorting/BookSorter.php b/app/Sorting/BookSorter.php index cf41a6a94..e627d66fd 100644 --- a/app/Sorting/BookSorter.php +++ b/app/Sorting/BookSorter.php @@ -78,7 +78,7 @@ class BookSorter * Sort the books content using the given sort map. * Returns a list of books that were involved in the operation. * - * @returns Book[] + * @return Book[] */ public function sortUsingMap(BookSortMap $sortMap): array { diff --git a/app/Theming/ThemeEvents.php b/app/Theming/ThemeEvents.php index 2d4900c96..44630acae 100644 --- a/app/Theming/ThemeEvents.php +++ b/app/Theming/ThemeEvents.php @@ -62,7 +62,7 @@ class ThemeEvents * * @param string $authSystem * @param array $userData - * @returns bool|null + * @return bool|null */ const AUTH_PRE_REGISTER = 'auth_pre_register'; @@ -83,7 +83,7 @@ class ThemeEvents * If the listener returns a non-null value, that will be used as an environment instead. * * @param \League\CommonMark\Environment\Environment $environment - * @returns \League\CommonMark\Environment\Environment|null + * @return \League\CommonMark\Environment\Environment|null */ const COMMONMARK_ENVIRONMENT_CONFIGURE = 'commonmark_environment_configure'; @@ -96,7 +96,7 @@ class ThemeEvents * * @param array $idTokenData * @param array $accessTokenData - * @returns array|null + * @return array|null */ const OIDC_ID_TOKEN_PRE_VALIDATE = 'oidc_id_token_pre_validate'; @@ -142,7 +142,7 @@ class ThemeEvents * Return values, if provided, will be used as a new response to use. * * @param \Illuminate\Http\Request $request - * @returns \Illuminate\Http\Response|null + * @return \Illuminate\Http\Response|null */ const WEB_MIDDLEWARE_BEFORE = 'web_middleware_before'; @@ -154,7 +154,7 @@ class ThemeEvents * * @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\BinaryFileResponse $response - * @returns \Illuminate\Http\Response|null + * @return \Illuminate\Http\Response|null */ const WEB_MIDDLEWARE_AFTER = 'web_middleware_after'; @@ -173,7 +173,7 @@ class ThemeEvents * @param string|\BookStack\Activity\Models\Loggable $detail * @param \BookStack\Users\Models\User $initiator * @param int $initiatedTime - * @returns array|null + * @return array|null */ const WEBHOOK_CALL_BEFORE = 'webhook_call_before'; } diff --git a/app/Uploads/Attachment.php b/app/Uploads/Attachment.php index 57d7cb334..05227243a 100644 --- a/app/Uploads/Attachment.php +++ b/app/Uploads/Attachment.php @@ -8,6 +8,7 @@ use BookStack\Entities\Models\Page; use BookStack\Permissions\Models\JointPermission; use BookStack\Permissions\PermissionApplicator; use BookStack\Users\Models\HasCreatorAndUpdater; +use BookStack\Users\Models\OwnableInterface; use BookStack\Users\Models\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -27,7 +28,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; * * @method static Entity|Builder visible() */ -class Attachment extends Model +class Attachment extends Model implements OwnableInterface { use HasCreatorAndUpdater; use HasFactory; diff --git a/app/Uploads/Image.php b/app/Uploads/Image.php index 0a267a644..ec8f8be0f 100644 --- a/app/Uploads/Image.php +++ b/app/Uploads/Image.php @@ -7,6 +7,7 @@ use BookStack\Entities\Models\Page; use BookStack\Permissions\Models\JointPermission; use BookStack\Permissions\PermissionApplicator; use BookStack\Users\Models\HasCreatorAndUpdater; +use BookStack\Users\Models\OwnableInterface; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -21,7 +22,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany; * @property int $created_by * @property int $updated_by */ -class Image extends Model +class Image extends Model implements OwnableInterface { use HasFactory; use HasCreatorAndUpdater; diff --git a/app/Uploads/ImageService.php b/app/Uploads/ImageService.php index a8f144517..458f0102d 100644 --- a/app/Uploads/ImageService.php +++ b/app/Uploads/ImageService.php @@ -138,7 +138,7 @@ class ImageService * Get the raw data content from an image. * * @throws Exception - * @returns ?resource + * @return ?resource */ public function getImageStream(Image $image): mixed { diff --git a/app/Uploads/ImageStorageDisk.php b/app/Uploads/ImageStorageDisk.php index f2667d993..36d6721de 100644 --- a/app/Uploads/ImageStorageDisk.php +++ b/app/Uploads/ImageStorageDisk.php @@ -61,7 +61,7 @@ class ImageStorageDisk /** * Get a stream to the file at the given path. - * @returns ?resource + * @return ?resource */ public function stream(string $path): mixed { diff --git a/app/Users/Controllers/UserApiController.php b/app/Users/Controllers/UserApiController.php index bb2570b31..351893b03 100644 --- a/app/Users/Controllers/UserApiController.php +++ b/app/Users/Controllers/UserApiController.php @@ -81,7 +81,7 @@ class UserApiController extends ApiController return $this->apiListingResponse($users, [ 'id', 'name', 'slug', 'email', 'external_auth_id', 'created_at', 'updated_at', 'last_activity_at', - ], [Closure::fromCallable([$this, 'listFormatter'])]); + ], [$this->listFormatter(...)]); } /** diff --git a/app/Users/Models/HasCreatorAndUpdater.php b/app/Users/Models/HasCreatorAndUpdater.php index 9fb24ead9..bb05de11a 100644 --- a/app/Users/Models/HasCreatorAndUpdater.php +++ b/app/Users/Models/HasCreatorAndUpdater.php @@ -27,4 +27,9 @@ trait HasCreatorAndUpdater { return $this->belongsTo(User::class, 'updated_by'); } + + public function getOwnerFieldName(): string + { + return 'created_by'; + } } diff --git a/app/Users/Models/HasOwner.php b/app/Users/Models/HasOwner.php deleted file mode 100644 index eb830ef6f..000000000 --- a/app/Users/Models/HasOwner.php +++ /dev/null @@ -1,19 +0,0 @@ -belongsTo(User::class, 'owned_by'); - } -} diff --git a/app/Users/Models/OwnableInterface.php b/app/Users/Models/OwnableInterface.php new file mode 100644 index 000000000..8f738487f --- /dev/null +++ b/app/Users/Models/OwnableInterface.php @@ -0,0 +1,8 @@ + */ public function permissions(): BelongsToMany { diff --git a/app/Users/Models/User.php b/app/Users/Models/User.php index 0d437418b..b6989ce2d 100644 --- a/app/Users/Models/User.php +++ b/app/Users/Models/User.php @@ -10,7 +10,7 @@ use BookStack\Activity\Models\Loggable; use BookStack\Activity\Models\Watch; use BookStack\Api\ApiToken; use BookStack\App\Model; -use BookStack\App\Sluggable; +use BookStack\App\SluggableInterface; use BookStack\Entities\Tools\SlugGenerator; use BookStack\Translation\LocaleDefinition; use BookStack\Translation\LocaleManager; @@ -47,7 +47,7 @@ use Illuminate\Support\Collection; * @property Collection $mfaValues * @property ?Image $avatar */ -class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable, Sluggable +class User extends Model implements AuthenticatableContract, CanResetPasswordContract, Loggable, SluggableInterface { use HasFactory; use Authenticatable; @@ -372,7 +372,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon */ public function refreshSlug(): string { - $this->slug = app()->make(SlugGenerator::class)->generate($this); + $this->slug = app()->make(SlugGenerator::class)->generate($this, $this->name); return $this->slug; } diff --git a/phpcs.xml b/phpcs.xml index 8d4c6b702..8337d5aac 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -14,7 +14,7 @@ - + ./tests/* diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 0f2021383..9dfd9d29e 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -7,7 +7,7 @@ parameters: - app # The level 8 is the highest level - level: 1 + level: 2 phpVersion: min: 80200 From e05ec7da361027126e538bbe5908d0bfdf1ede4a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 3 Sep 2025 10:47:45 +0100 Subject: [PATCH 10/48] Maintenance: Addressed a range of phpstan level 3 issues --- app/Access/ExternalBaseUserProvider.php | 24 +--- .../Guards/ExternalBaseSessionGuard.php | 111 ++++-------------- app/Activity/Models/Comment.php | 2 +- app/App/Providers/AuthServiceProvider.php | 4 +- app/App/Providers/EventServiceProvider.php | 2 +- .../Controllers/PageRevisionController.php | 2 +- app/Entities/Models/PageRevision.php | 2 +- app/Exceptions/Handler.php | 4 +- app/Exports/ImportRepo.php | 3 + .../ZipExports/Models/ZipExportAttachment.php | 6 +- .../ZipExports/Models/ZipExportBook.php | 6 +- .../ZipExports/Models/ZipExportChapter.php | 6 +- .../ZipExports/Models/ZipExportImage.php | 6 +- .../ZipExports/Models/ZipExportModel.php | 4 +- .../ZipExports/Models/ZipExportPage.php | 6 +- .../ZipExports/Models/ZipExportTag.php | 6 +- app/Http/Kernel.php | 6 +- app/Http/Middleware/EncryptCookies.php | 2 +- .../PreventRequestsDuringMaintenance.php | 2 +- app/Http/Middleware/TrimStrings.php | 2 +- app/Http/Middleware/TrustProxies.php | 2 +- app/Http/Middleware/VerifyCsrfToken.php | 2 +- app/Search/SearchOptionSet.php | 3 + app/Search/SearchRunner.php | 2 +- app/Users/Models/User.php | 13 +- app/Util/OutOfMemoryHandler.php | 7 +- 26 files changed, 84 insertions(+), 151 deletions(-) diff --git a/app/Access/ExternalBaseUserProvider.php b/app/Access/ExternalBaseUserProvider.php index 2b5ddfbf3..2165fd459 100644 --- a/app/Access/ExternalBaseUserProvider.php +++ b/app/Access/ExternalBaseUserProvider.php @@ -2,33 +2,18 @@ namespace BookStack\Access; +use BookStack\Users\Models\User; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\UserProvider; -use Illuminate\Database\Eloquent\Model; class ExternalBaseUserProvider implements UserProvider { - public function __construct( - protected string $model - ) { - } - - /** - * Create a new instance of the model. - */ - public function createModel(): Model - { - $class = '\\' . ltrim($this->model, '\\'); - - return new $class(); - } - /** * Retrieve a user by their unique identifier. */ public function retrieveById(mixed $identifier): ?Authenticatable { - return $this->createModel()->newQuery()->find($identifier); + return User::query()->find($identifier); } /** @@ -59,10 +44,7 @@ class ExternalBaseUserProvider implements UserProvider */ public function retrieveByCredentials(array $credentials): ?Authenticatable { - // Search current user base by looking up a uid - $model = $this->createModel(); - - return $model->newQuery() + return User::query() ->where('external_auth_id', $credentials['external_auth_id']) ->first(); } diff --git a/app/Access/Guards/ExternalBaseSessionGuard.php b/app/Access/Guards/ExternalBaseSessionGuard.php index 2d15422a5..899e93c36 100644 --- a/app/Access/Guards/ExternalBaseSessionGuard.php +++ b/app/Access/Guards/ExternalBaseSessionGuard.php @@ -4,7 +4,7 @@ namespace BookStack\Access\Guards; use BookStack\Access\RegistrationService; use Illuminate\Auth\GuardHelpers; -use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; +use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\StatefulGuard; use Illuminate\Contracts\Auth\UserProvider; use Illuminate\Contracts\Session\Session; @@ -24,43 +24,31 @@ class ExternalBaseSessionGuard implements StatefulGuard * The name of the Guard. Typically "session". * * Corresponds to guard name in authentication configuration. - * - * @var string */ - protected $name; + protected readonly string $name; /** * The user we last attempted to retrieve. - * - * @var \Illuminate\Contracts\Auth\Authenticatable */ - protected $lastAttempted; + protected Authenticatable $lastAttempted; /** * The session used by the guard. - * - * @var \Illuminate\Contracts\Session\Session */ - protected $session; + protected Session $session; /** * Indicates if the logout method has been called. - * - * @var bool */ - protected $loggedOut = false; + protected bool $loggedOut = false; /** * Service to handle common registration actions. - * - * @var RegistrationService */ - protected $registrationService; + protected RegistrationService $registrationService; /** * Create a new authentication guard. - * - * @return void */ public function __construct(string $name, UserProvider $provider, Session $session, RegistrationService $registrationService) { @@ -72,13 +60,11 @@ class ExternalBaseSessionGuard implements StatefulGuard /** * Get the currently authenticated user. - * - * @return \Illuminate\Contracts\Auth\Authenticatable|null */ - public function user() + public function user(): Authenticatable|null { if ($this->loggedOut) { - return; + return null; } // If we've already retrieved the user for the current request we can just @@ -101,13 +87,11 @@ class ExternalBaseSessionGuard implements StatefulGuard /** * Get the ID for the currently authenticated user. - * - * @return int|null */ - public function id() + public function id(): int|null { if ($this->loggedOut) { - return; + return null; } return $this->user() @@ -117,12 +101,8 @@ class ExternalBaseSessionGuard implements StatefulGuard /** * Log a user into the application without sessions or cookies. - * - * @param array $credentials - * - * @return bool */ - public function once(array $credentials = []) + public function once(array $credentials = []): bool { if ($this->validate($credentials)) { $this->setUser($this->lastAttempted); @@ -135,12 +115,8 @@ class ExternalBaseSessionGuard implements StatefulGuard /** * Log the given user ID into the application without sessions or cookies. - * - * @param mixed $id - * - * @return \Illuminate\Contracts\Auth\Authenticatable|false */ - public function onceUsingId($id) + public function onceUsingId($id): Authenticatable|false { if (!is_null($user = $this->provider->retrieveById($id))) { $this->setUser($user); @@ -153,38 +129,26 @@ class ExternalBaseSessionGuard implements StatefulGuard /** * Validate a user's credentials. - * - * @param array $credentials - * - * @return bool */ - public function validate(array $credentials = []) + public function validate(array $credentials = []): bool { return false; } /** * Attempt to authenticate a user using the given credentials. - * - * @param array $credentials - * @param bool $remember - * - * @return bool + * @param bool $remember */ - public function attempt(array $credentials = [], $remember = false) + public function attempt(array $credentials = [], $remember = false): bool { return false; } /** * Log the given user ID into the application. - * - * @param mixed $id * @param bool $remember - * - * @return \Illuminate\Contracts\Auth\Authenticatable|false */ - public function loginUsingId($id, $remember = false) + public function loginUsingId(mixed $id, $remember = false): Authenticatable|false { // Always return false as to disable this method, // Logins should route through LoginService. @@ -194,12 +158,9 @@ class ExternalBaseSessionGuard implements StatefulGuard /** * Log a user into the application. * - * @param \Illuminate\Contracts\Auth\Authenticatable $user - * @param bool $remember - * - * @return void + * @param bool $remember */ - public function login(AuthenticatableContract $user, $remember = false) + public function login(Authenticatable $user, $remember = false): void { $this->updateSession($user->getAuthIdentifier()); @@ -208,12 +169,8 @@ class ExternalBaseSessionGuard implements StatefulGuard /** * Update the session with the given ID. - * - * @param string $id - * - * @return void */ - protected function updateSession($id) + protected function updateSession(string|int $id): void { $this->session->put($this->getName(), $id); @@ -222,10 +179,8 @@ class ExternalBaseSessionGuard implements StatefulGuard /** * Log the user out of the application. - * - * @return void */ - public function logout() + public function logout(): void { $this->clearUserDataFromStorage(); @@ -239,62 +194,48 @@ class ExternalBaseSessionGuard implements StatefulGuard /** * Remove the user data from the session and cookies. - * - * @return void */ - protected function clearUserDataFromStorage() + protected function clearUserDataFromStorage(): void { $this->session->remove($this->getName()); } /** * Get the last user we attempted to authenticate. - * - * @return \Illuminate\Contracts\Auth\Authenticatable */ - public function getLastAttempted() + public function getLastAttempted(): Authenticatable { return $this->lastAttempted; } /** * Get a unique identifier for the auth session value. - * - * @return string */ - public function getName() + public function getName(): string { return 'login_' . $this->name . '_' . sha1(static::class); } /** * Determine if the user was authenticated via "remember me" cookie. - * - * @return bool */ - public function viaRemember() + public function viaRemember(): bool { return false; } /** * Return the currently cached user. - * - * @return \Illuminate\Contracts\Auth\Authenticatable|null */ - public function getUser() + public function getUser(): Authenticatable|null { return $this->user; } /** * Set the current user. - * - * @param \Illuminate\Contracts\Auth\Authenticatable $user - * - * @return $this */ - public function setUser(AuthenticatableContract $user) + public function setUser(Authenticatable $user): self { $this->user = $user; diff --git a/app/Activity/Models/Comment.php b/app/Activity/Models/Comment.php index 6b05a9bab..0cb83d61e 100644 --- a/app/Activity/Models/Comment.php +++ b/app/Activity/Models/Comment.php @@ -39,7 +39,7 @@ class Comment extends Model implements Loggable, OwnableInterface /** * Get the parent comment this is in reply to (if existing). - * @return BelongsTo + * @return BelongsTo */ public function parent(): BelongsTo { diff --git a/app/App/Providers/AuthServiceProvider.php b/app/App/Providers/AuthServiceProvider.php index 23c339079..6a8162521 100644 --- a/app/App/Providers/AuthServiceProvider.php +++ b/app/App/Providers/AuthServiceProvider.php @@ -59,8 +59,8 @@ class AuthServiceProvider extends ServiceProvider */ public function register(): void { - Auth::provider('external-users', function ($app, array $config) { - return new ExternalBaseUserProvider($config['model']); + Auth::provider('external-users', function () { + return new ExternalBaseUserProvider(); }); // Bind and provide the default system user as a singleton to the app instance when needed. diff --git a/app/App/Providers/EventServiceProvider.php b/app/App/Providers/EventServiceProvider.php index 34ab7cfef..60e78efe0 100644 --- a/app/App/Providers/EventServiceProvider.php +++ b/app/App/Providers/EventServiceProvider.php @@ -15,7 +15,7 @@ class EventServiceProvider extends ServiceProvider /** * The event listener mappings for the application. * - * @var array> + * @var array> */ protected $listen = [ SocialiteWasCalled::class => [ diff --git a/app/Entities/Controllers/PageRevisionController.php b/app/Entities/Controllers/PageRevisionController.php index 4985c39f3..c43eea08b 100644 --- a/app/Entities/Controllers/PageRevisionController.php +++ b/app/Entities/Controllers/PageRevisionController.php @@ -98,7 +98,7 @@ class PageRevisionController extends Controller throw new NotFoundException(); } - $prev = $revision->getPrevious(); + $prev = $revision->getPreviousRevision(); $prevContent = $prev->html ?? ''; $diff = Diff::excecute($prevContent, $revision->html); diff --git a/app/Entities/Models/PageRevision.php b/app/Entities/Models/PageRevision.php index 10ff6d901..1a6c980e1 100644 --- a/app/Entities/Models/PageRevision.php +++ b/app/Entities/Models/PageRevision.php @@ -60,7 +60,7 @@ class PageRevision extends Model implements Loggable /** * Get the previous revision for the same page if existing. */ - public function getPrevious(): ?PageRevision + public function getPreviousRevision(): ?PageRevision { $id = static::newQuery()->where('page_id', '=', $this->page_id) ->where('id', '<', $this->id) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 61e126327..b8c3fa192 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -94,7 +94,7 @@ class Handler extends ExceptionHandler * If the callable returns a response, this response will be returned * to the request upon error. */ - public function prepareForOutOfMemory(callable $onOutOfMemory) + public function prepareForOutOfMemory(callable $onOutOfMemory): void { $this->onOutOfMemory = $onOutOfMemory; } @@ -102,7 +102,7 @@ class Handler extends ExceptionHandler /** * Forget the current out of memory handler, if existing. */ - public function forgetOutOfMemoryHandler() + public function forgetOutOfMemoryHandler(): void { $this->onOutOfMemory = null; } diff --git a/app/Exports/ImportRepo.php b/app/Exports/ImportRepo.php index e030a88d2..896af903a 100644 --- a/app/Exports/ImportRepo.php +++ b/app/Exports/ImportRepo.php @@ -39,6 +39,9 @@ class ImportRepo return $this->queryVisible()->get(); } + /** + * @return Builder + */ public function queryVisible(): Builder { $query = Import::query(); diff --git a/app/Exports/ZipExports/Models/ZipExportAttachment.php b/app/Exports/ZipExports/Models/ZipExportAttachment.php index 4f5b2f236..97995738f 100644 --- a/app/Exports/ZipExports/Models/ZipExportAttachment.php +++ b/app/Exports/ZipExports/Models/ZipExportAttachment.php @@ -6,7 +6,7 @@ use BookStack\Exports\ZipExports\ZipExportFiles; use BookStack\Exports\ZipExports\ZipValidationHelper; use BookStack\Uploads\Attachment; -class ZipExportAttachment extends ZipExportModel +final class ZipExportAttachment extends ZipExportModel { public ?int $id = null; public string $name; @@ -52,9 +52,9 @@ class ZipExportAttachment extends ZipExportModel return $context->validateData($data, $rules); } - public static function fromArray(array $data): self + public static function fromArray(array $data): static { - $model = new self(); + $model = new static(); $model->id = $data['id'] ?? null; $model->name = $data['name']; diff --git a/app/Exports/ZipExports/Models/ZipExportBook.php b/app/Exports/ZipExports/Models/ZipExportBook.php index 39176ded4..6c51ea337 100644 --- a/app/Exports/ZipExports/Models/ZipExportBook.php +++ b/app/Exports/ZipExports/Models/ZipExportBook.php @@ -8,7 +8,7 @@ use BookStack\Entities\Models\Page; use BookStack\Exports\ZipExports\ZipExportFiles; use BookStack\Exports\ZipExports\ZipValidationHelper; -class ZipExportBook extends ZipExportModel +final class ZipExportBook extends ZipExportModel { public ?int $id = null; public string $name; @@ -101,9 +101,9 @@ class ZipExportBook extends ZipExportModel return $errors; } - public static function fromArray(array $data): self + public static function fromArray(array $data): static { - $model = new self(); + $model = new static(); $model->id = $data['id'] ?? null; $model->name = $data['name']; diff --git a/app/Exports/ZipExports/Models/ZipExportChapter.php b/app/Exports/ZipExports/Models/ZipExportChapter.php index bf2dc78f8..260191a3e 100644 --- a/app/Exports/ZipExports/Models/ZipExportChapter.php +++ b/app/Exports/ZipExports/Models/ZipExportChapter.php @@ -7,7 +7,7 @@ use BookStack\Entities\Models\Page; use BookStack\Exports\ZipExports\ZipExportFiles; use BookStack\Exports\ZipExports\ZipValidationHelper; -class ZipExportChapter extends ZipExportModel +final class ZipExportChapter extends ZipExportModel { public ?int $id = null; public string $name; @@ -79,9 +79,9 @@ class ZipExportChapter extends ZipExportModel return $errors; } - public static function fromArray(array $data): self + public static function fromArray(array $data): static { - $model = new self(); + $model = new static(); $model->id = $data['id'] ?? null; $model->name = $data['name']; diff --git a/app/Exports/ZipExports/Models/ZipExportImage.php b/app/Exports/ZipExports/Models/ZipExportImage.php index e0e7d1198..4c71af0c3 100644 --- a/app/Exports/ZipExports/Models/ZipExportImage.php +++ b/app/Exports/ZipExports/Models/ZipExportImage.php @@ -7,7 +7,7 @@ use BookStack\Exports\ZipExports\ZipValidationHelper; use BookStack\Uploads\Image; use Illuminate\Validation\Rule; -class ZipExportImage extends ZipExportModel +final class ZipExportImage extends ZipExportModel { public ?int $id = null; public string $name; @@ -43,9 +43,9 @@ class ZipExportImage extends ZipExportModel return $context->validateData($data, $rules); } - public static function fromArray(array $data): self + public static function fromArray(array $data): static { - $model = new self(); + $model = new static(); $model->id = $data['id'] ?? null; $model->name = $data['name']; diff --git a/app/Exports/ZipExports/Models/ZipExportModel.php b/app/Exports/ZipExports/Models/ZipExportModel.php index d3a8c3567..38001c628 100644 --- a/app/Exports/ZipExports/Models/ZipExportModel.php +++ b/app/Exports/ZipExports/Models/ZipExportModel.php @@ -30,12 +30,12 @@ abstract class ZipExportModel implements JsonSerializable /** * Decode the array of data into this export model. */ - abstract public static function fromArray(array $data): self; + abstract public static function fromArray(array $data): static; /** * Decode an array of array data into an array of export models. * @param array[] $data - * @return self[] + * @return static[] */ public static function fromManyArray(array $data): array { diff --git a/app/Exports/ZipExports/Models/ZipExportPage.php b/app/Exports/ZipExports/Models/ZipExportPage.php index 097443df0..6de7f9446 100644 --- a/app/Exports/ZipExports/Models/ZipExportPage.php +++ b/app/Exports/ZipExports/Models/ZipExportPage.php @@ -7,7 +7,7 @@ use BookStack\Entities\Tools\PageContent; use BookStack\Exports\ZipExports\ZipExportFiles; use BookStack\Exports\ZipExports\ZipValidationHelper; -class ZipExportPage extends ZipExportModel +final class ZipExportPage extends ZipExportModel { public ?int $id = null; public string $name; @@ -86,9 +86,9 @@ class ZipExportPage extends ZipExportModel return $errors; } - public static function fromArray(array $data): self + public static function fromArray(array $data): static { - $model = new self(); + $model = new static(); $model->id = $data['id'] ?? null; $model->name = $data['name']; diff --git a/app/Exports/ZipExports/Models/ZipExportTag.php b/app/Exports/ZipExports/Models/ZipExportTag.php index 6b4720fca..8ac7f3c4d 100644 --- a/app/Exports/ZipExports/Models/ZipExportTag.php +++ b/app/Exports/ZipExports/Models/ZipExportTag.php @@ -5,7 +5,7 @@ namespace BookStack\Exports\ZipExports\Models; use BookStack\Activity\Models\Tag; use BookStack\Exports\ZipExports\ZipValidationHelper; -class ZipExportTag extends ZipExportModel +final class ZipExportTag extends ZipExportModel { public string $name; public ?string $value = null; @@ -39,9 +39,9 @@ class ZipExportTag extends ZipExportModel return $context->validateData($data, $rules); } - public static function fromArray(array $data): self + public static function fromArray(array $data): static { - $model = new self(); + $model = new static(); $model->name = $data['name']; $model->value = $data['value'] ?? null; diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 30714e2ac..00bf8cbe1 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -9,6 +9,8 @@ class Kernel extends HttpKernel /** * The application's global HTTP middleware stack. * These middleware are run during every request to your application. + * + * @var list */ protected $middleware = [ \BookStack\Http\Middleware\PreventRequestsDuringMaintenance::class, @@ -21,7 +23,7 @@ class Kernel extends HttpKernel /** * The application's route middleware groups. * - * @var array + * @var array> */ protected $middlewareGroups = [ 'web' => [ @@ -47,7 +49,7 @@ class Kernel extends HttpKernel /** * The application's middleware aliases. * - * @var array + * @var array */ protected $middlewareAliases = [ 'auth' => \BookStack\Http\Middleware\Authenticate::class, diff --git a/app/Http/Middleware/EncryptCookies.php b/app/Http/Middleware/EncryptCookies.php index 484c15ae8..2d7d5d9d4 100644 --- a/app/Http/Middleware/EncryptCookies.php +++ b/app/Http/Middleware/EncryptCookies.php @@ -9,7 +9,7 @@ class EncryptCookies extends Middleware /** * The names of the cookies that should not be encrypted. * - * @var array + * @var array */ protected $except = [ // diff --git a/app/Http/Middleware/PreventRequestsDuringMaintenance.php b/app/Http/Middleware/PreventRequestsDuringMaintenance.php index dfb9592e1..c1a02c8cc 100644 --- a/app/Http/Middleware/PreventRequestsDuringMaintenance.php +++ b/app/Http/Middleware/PreventRequestsDuringMaintenance.php @@ -9,7 +9,7 @@ class PreventRequestsDuringMaintenance extends Middleware /** * The URIs that should be reachable while maintenance mode is enabled. * - * @var array + * @var array */ protected $except = [ // diff --git a/app/Http/Middleware/TrimStrings.php b/app/Http/Middleware/TrimStrings.php index cbdc88fb9..ec5725d67 100644 --- a/app/Http/Middleware/TrimStrings.php +++ b/app/Http/Middleware/TrimStrings.php @@ -9,7 +9,7 @@ class TrimStrings extends Middleware /** * The names of the attributes that should not be trimmed. * - * @var array + * @var array */ protected $except = [ 'password', diff --git a/app/Http/Middleware/TrustProxies.php b/app/Http/Middleware/TrustProxies.php index 0fe0247b8..ecf6cb18a 100644 --- a/app/Http/Middleware/TrustProxies.php +++ b/app/Http/Middleware/TrustProxies.php @@ -11,7 +11,7 @@ class TrustProxies extends Middleware /** * The trusted proxies for this application. * - * @var array + * @var array|string|null */ protected $proxies; diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 804a22bc0..7c8689b8b 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -16,7 +16,7 @@ class VerifyCsrfToken extends Middleware /** * The URIs that should be excluded from CSRF verification. * - * @var array + * @var array */ protected $except = [ 'saml2/*', diff --git a/app/Search/SearchOptionSet.php b/app/Search/SearchOptionSet.php index bd5e5a5b2..844d145e6 100644 --- a/app/Search/SearchOptionSet.php +++ b/app/Search/SearchOptionSet.php @@ -14,6 +14,9 @@ class SearchOptionSet */ protected array $options = []; + /** + * @param T[] $options + */ public function __construct(array $options = []) { $this->options = $options; diff --git a/app/Search/SearchRunner.php b/app/Search/SearchRunner.php index 9716f8053..c14d1c642 100644 --- a/app/Search/SearchRunner.php +++ b/app/Search/SearchRunner.php @@ -285,7 +285,7 @@ class SearchRunner * * @param array $termCounts * - * @return array + * @return array */ protected function rawTermCountsToAdjustments(array $termCounts): array { diff --git a/app/Users/Models/User.php b/app/Users/Models/User.php index b6989ce2d..f83e12088 100644 --- a/app/Users/Models/User.php +++ b/app/Users/Models/User.php @@ -26,6 +26,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Collection; @@ -64,7 +65,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon /** * The attributes that are mass assignable. * - * @var array + * @var list */ protected $fillable = ['name', 'email']; @@ -73,7 +74,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon /** * The attributes excluded from the model's JSON form. * - * @var array + * @var list */ protected $hidden = [ 'password', 'remember_token', 'system_name', 'email_confirmed', 'external_auth_id', 'email', @@ -118,14 +119,10 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon /** * The roles that belong to the user. * - * @return BelongsToMany + * @return BelongsToMany */ - public function roles() + public function roles(): BelongsToMany { - if ($this->id === 0) { - return; - } - return $this->belongsToMany(Role::class); } diff --git a/app/Util/OutOfMemoryHandler.php b/app/Util/OutOfMemoryHandler.php index 88e9581f4..6632d22a5 100644 --- a/app/Util/OutOfMemoryHandler.php +++ b/app/Util/OutOfMemoryHandler.php @@ -42,7 +42,7 @@ class OutOfMemoryHandler } /** - * Forget the handler so no action is taken place on out of memory. + * Forget the handler, so no action is taken place on out of memory. */ public function forget(): void { @@ -53,6 +53,11 @@ class OutOfMemoryHandler protected function getHandler(): Handler { + /** + * We want to resolve our specific BookStack handling via the set app handler + * singleton, but phpstan will only infer based on the interface. + * @phpstan-ignore return.type + */ return app()->make(ExceptionHandler::class); } } From 318b486e0b7601a00dc02bd1adcef22066bfef5d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 3 Sep 2025 15:18:49 +0100 Subject: [PATCH 11/48] Maintenance: Finished changes to meet phpstan level 3 --- .../Guards/AsyncExternalBaseSessionGuard.php | 20 ++++-------- app/Access/Guards/LdapSessionGuard.php | 6 +--- app/Access/LoginService.php | 10 +++--- app/Entities/Queries/BookQueries.php | 3 ++ app/Entities/Queries/BookshelfQueries.php | 3 ++ app/Entities/Queries/ChapterQueries.php | 3 ++ app/Entities/Queries/PageQueries.php | 6 ++++ .../Queries/ProvidesEntityQueries.php | 7 +++-- app/Entities/Tools/PageIncludeParser.php | 6 ++-- app/Exceptions/Handler.php | 31 +++++++------------ app/Permissions/EntityPermissionEvaluator.php | 4 +-- 11 files changed, 48 insertions(+), 51 deletions(-) diff --git a/app/Access/Guards/AsyncExternalBaseSessionGuard.php b/app/Access/Guards/AsyncExternalBaseSessionGuard.php index ab982a6b0..b66fbe95e 100644 --- a/app/Access/Guards/AsyncExternalBaseSessionGuard.php +++ b/app/Access/Guards/AsyncExternalBaseSessionGuard.php @@ -3,23 +3,18 @@ namespace BookStack\Access\Guards; /** - * Saml2 Session Guard. + * External Auth Session Guard. * - * The saml2 login process is async in nature meaning it does not fit very well - * into the default laravel 'Guard' auth flow. Instead most of the logic is done - * via the Saml2 controller & Saml2Service. This class provides a safer, thin - * version of SessionGuard. + * The login process for external auth (SAML2/OIDC) is async in nature, meaning it does not fit very well + * into the default laravel 'Guard' auth flow. Instead, most of the logic is done via the relevant + * controller and services. This class provides a safer, thin version of SessionGuard. */ class AsyncExternalBaseSessionGuard extends ExternalBaseSessionGuard { /** * Validate a user's credentials. - * - * @param array $credentials - * - * @return bool */ - public function validate(array $credentials = []) + public function validate(array $credentials = []): bool { return false; } @@ -27,12 +22,9 @@ class AsyncExternalBaseSessionGuard extends ExternalBaseSessionGuard /** * Attempt to authenticate a user using the given credentials. * - * @param array $credentials * @param bool $remember - * - * @return bool */ - public function attempt(array $credentials = [], $remember = false) + public function attempt(array $credentials = [], $remember = false): bool { return false; } diff --git a/app/Access/Guards/LdapSessionGuard.php b/app/Access/Guards/LdapSessionGuard.php index bd020f70b..9455d530d 100644 --- a/app/Access/Guards/LdapSessionGuard.php +++ b/app/Access/Guards/LdapSessionGuard.php @@ -35,13 +35,9 @@ class LdapSessionGuard extends ExternalBaseSessionGuard /** * Validate a user's credentials. * - * @param array $credentials - * * @throws LdapException - * - * @return bool */ - public function validate(array $credentials = []) + public function validate(array $credentials = []): bool { $userDetails = $this->ldapService->getUserDetails($credentials['username']); diff --git a/app/Access/LoginService.php b/app/Access/LoginService.php index 6607969af..76ae1938d 100644 --- a/app/Access/LoginService.php +++ b/app/Access/LoginService.php @@ -95,7 +95,7 @@ class LoginService { $value = session()->get(self::LAST_LOGIN_ATTEMPTED_SESSION_KEY); if (!$value) { - return ['user_id' => null, 'method' => null]; + return ['user_id' => null, 'method' => null, 'remember' => false]; } [$id, $method, $remember, $time] = explode(':', $value); @@ -103,18 +103,18 @@ class LoginService if ($time < $hourAgo) { $this->clearLastLoginAttempted(); - return ['user_id' => null, 'method' => null]; + return ['user_id' => null, 'method' => null, 'remember' => false]; } return ['user_id' => $id, 'method' => $method, 'remember' => boolval($remember)]; } /** - * Set the last login attempted user. + * Set the last login-attempted user. * Must be only used when credentials are correct and a login could be - * achieved but a secondary factor has stopped the login. + * achieved, but a secondary factor has stopped the login. */ - protected function setLastLoginAttemptedForUser(User $user, string $method, bool $remember) + protected function setLastLoginAttemptedForUser(User $user, string $method, bool $remember): void { session()->put( self::LAST_LOGIN_ATTEMPTED_SESSION_KEY, diff --git a/app/Entities/Queries/BookQueries.php b/app/Entities/Queries/BookQueries.php index 5ff22252c..2492f8131 100644 --- a/app/Entities/Queries/BookQueries.php +++ b/app/Entities/Queries/BookQueries.php @@ -6,6 +6,9 @@ use BookStack\Entities\Models\Book; use BookStack\Exceptions\NotFoundException; use Illuminate\Database\Eloquent\Builder; +/** + * @implements ProvidesEntityQueries + */ class BookQueries implements ProvidesEntityQueries { protected static array $listAttributes = [ diff --git a/app/Entities/Queries/BookshelfQueries.php b/app/Entities/Queries/BookshelfQueries.php index 874bc7f2f..842011a87 100644 --- a/app/Entities/Queries/BookshelfQueries.php +++ b/app/Entities/Queries/BookshelfQueries.php @@ -6,6 +6,9 @@ use BookStack\Entities\Models\Bookshelf; use BookStack\Exceptions\NotFoundException; use Illuminate\Database\Eloquent\Builder; +/** + * @implements ProvidesEntityQueries + */ class BookshelfQueries implements ProvidesEntityQueries { protected static array $listAttributes = [ diff --git a/app/Entities/Queries/ChapterQueries.php b/app/Entities/Queries/ChapterQueries.php index 53c5bc9d8..9bf0ff65b 100644 --- a/app/Entities/Queries/ChapterQueries.php +++ b/app/Entities/Queries/ChapterQueries.php @@ -6,6 +6,9 @@ use BookStack\Entities\Models\Chapter; use BookStack\Exceptions\NotFoundException; use Illuminate\Database\Eloquent\Builder; +/** + * @implements ProvidesEntityQueries + */ class ChapterQueries implements ProvidesEntityQueries { protected static array $listAttributes = [ diff --git a/app/Entities/Queries/PageQueries.php b/app/Entities/Queries/PageQueries.php index f821ee86a..ee7b201bc 100644 --- a/app/Entities/Queries/PageQueries.php +++ b/app/Entities/Queries/PageQueries.php @@ -6,6 +6,9 @@ use BookStack\Entities\Models\Page; use BookStack\Exceptions\NotFoundException; use Illuminate\Database\Eloquent\Builder; +/** + * @implements ProvidesEntityQueries + */ class PageQueries implements ProvidesEntityQueries { protected static array $contentAttributes = [ @@ -18,6 +21,9 @@ class PageQueries implements ProvidesEntityQueries 'template', 'text', 'created_at', 'updated_at', 'priority', 'owned_by', ]; + /** + * @return Builder + */ public function start(): Builder { return Page::query(); diff --git a/app/Entities/Queries/ProvidesEntityQueries.php b/app/Entities/Queries/ProvidesEntityQueries.php index 1f8a71ae2..79fc64b3a 100644 --- a/app/Entities/Queries/ProvidesEntityQueries.php +++ b/app/Entities/Queries/ProvidesEntityQueries.php @@ -7,17 +7,20 @@ use Illuminate\Database\Eloquent\Builder; /** * Interface for our classes which provide common queries for our - * entity objects. Ideally all queries for entities should run through + * entity objects. Ideally, all queries for entities should run through * these classes. * Any added methods should return a builder instances to allow extension * via building on the query, unless the method starts with 'find' * in which case an entity object should be returned. * (nullable unless it's a *OrFail method). + * + * @template TModel of Entity */ interface ProvidesEntityQueries { /** * Start a new query for this entity type. + * @return Builder */ public function start(): Builder; @@ -29,7 +32,7 @@ interface ProvidesEntityQueries /** * Start a query for items that are visible, with selection * configured for list display of this item. - * @return Builder + * @return Builder */ public function visibleForList(): Builder; } diff --git a/app/Entities/Tools/PageIncludeParser.php b/app/Entities/Tools/PageIncludeParser.php index 329a7633f..af7ed4fc6 100644 --- a/app/Entities/Tools/PageIncludeParser.php +++ b/app/Entities/Tools/PageIncludeParser.php @@ -13,8 +13,8 @@ class PageIncludeParser protected static string $includeTagRegex = "/{{@\s?([0-9].*?)}}/"; /** - * Elements to clean up and remove if left empty after a parsing operation. - * @var DOMElement[] + * Nodes to clean up and remove if left empty after a parsing operation. + * @var DOMNode[] */ protected array $toCleanup = []; @@ -206,7 +206,7 @@ class PageIncludeParser } /** - * Cleanup after a parse operation. + * Clean up after a parse operation. * Removes stranded elements we may have left during the parse. */ protected function cleanup(): void diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index b8c3fa192..08d326ad8 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -2,7 +2,6 @@ namespace BookStack\Exceptions; -use Exception; use Illuminate\Auth\AuthenticationException; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; @@ -12,6 +11,7 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Validation\ValidationException; use Symfony\Component\ErrorHandler\Error\FatalError; +use Symfony\Component\HttpFoundation\Response as SymfonyResponse; use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; use Throwable; @@ -20,7 +20,7 @@ class Handler extends ExceptionHandler /** * A list of the exception types that are not reported. * - * @var array> + * @var array> */ protected $dontReport = [ NotFoundException::class, @@ -50,11 +50,11 @@ class Handler extends ExceptionHandler /** * Report or log an exception. * - * @param \Throwable $exception - * - * @throws \Throwable + * @param Throwable $exception * * @return void + *@throws Throwable + * */ public function report(Throwable $exception) { @@ -64,12 +64,9 @@ class Handler extends ExceptionHandler /** * Render an exception into an HTTP response. * - * @param \Illuminate\Http\Request $request - * @param Exception $e - * - * @return \Illuminate\Http\Response + * @param Request $request */ - public function render($request, Throwable $e) + public function render($request, Throwable $e): SymfonyResponse { if ($e instanceof FatalError && str_contains($e->getMessage(), 'bytes exhausted (tried to allocate') && $this->onOutOfMemory) { $response = call_user_func($this->onOutOfMemory); @@ -152,12 +149,9 @@ class Handler extends ExceptionHandler /** * Convert an authentication exception into an unauthenticated response. * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Auth\AuthenticationException $exception - * - * @return \Illuminate\Http\Response + * @param Request $request */ - protected function unauthenticated($request, AuthenticationException $exception) + protected function unauthenticated($request, AuthenticationException $exception): SymfonyResponse { if ($request->expectsJson()) { return response()->json(['error' => 'Unauthenticated.'], 401); @@ -169,12 +163,9 @@ class Handler extends ExceptionHandler /** * Convert a validation exception into a JSON response. * - * @param \Illuminate\Http\Request $request - * @param \Illuminate\Validation\ValidationException $exception - * - * @return \Illuminate\Http\JsonResponse + * @param Request $request */ - protected function invalidJson($request, ValidationException $exception) + protected function invalidJson($request, ValidationException $exception): JsonResponse { return response()->json($exception->errors(), $exception->status); } diff --git a/app/Permissions/EntityPermissionEvaluator.php b/app/Permissions/EntityPermissionEvaluator.php index 98ec03306..30c97b700 100644 --- a/app/Permissions/EntityPermissionEvaluator.php +++ b/app/Permissions/EntityPermissionEvaluator.php @@ -30,7 +30,7 @@ class EntityPermissionEvaluator } /** - * @param array> $permitsByType + * @param array> $permitsByType */ protected function evaluatePermitsByType(array $permitsByType): ?int { @@ -50,7 +50,7 @@ class EntityPermissionEvaluator /** * @param string[] $typeIdChain * @param array $permissionMapByTypeId - * @return array> + * @return array> */ protected function collapseAndCategorisePermissions(array $typeIdChain, array $permissionMapByTypeId): array { From 7d1c31620213599fdc07ce5cda776d91f5642afc Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 3 Sep 2025 15:42:50 +0100 Subject: [PATCH 12/48] Maintenance: Updated larastan target level, fixed issues from tests --- app/Access/Guards/ExternalBaseSessionGuard.php | 2 +- app/Console/Commands/UpdateUrlCommand.php | 2 +- app/Entities/Controllers/RecycleBinApiController.php | 7 ++++--- phpstan.neon.dist | 2 +- tests/Entity/CommentStoreTest.php | 1 - 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/Access/Guards/ExternalBaseSessionGuard.php b/app/Access/Guards/ExternalBaseSessionGuard.php index 899e93c36..91239599b 100644 --- a/app/Access/Guards/ExternalBaseSessionGuard.php +++ b/app/Access/Guards/ExternalBaseSessionGuard.php @@ -30,7 +30,7 @@ class ExternalBaseSessionGuard implements StatefulGuard /** * The user we last attempted to retrieve. */ - protected Authenticatable $lastAttempted; + protected Authenticatable|null $lastAttempted; /** * The session used by the guard. diff --git a/app/Console/Commands/UpdateUrlCommand.php b/app/Console/Commands/UpdateUrlCommand.php index e155878d3..71f0b92fe 100644 --- a/app/Console/Commands/UpdateUrlCommand.php +++ b/app/Console/Commands/UpdateUrlCommand.php @@ -52,7 +52,7 @@ class UpdateUrlCommand extends Command 'page_revisions' => ['html', 'text', 'markdown'], 'images' => ['url'], 'settings' => ['value'], - 'comments' => ['html', 'text'], + 'comments' => ['html'], ]; foreach ($columnsToUpdateByTable as $table => $columns) { diff --git a/app/Entities/Controllers/RecycleBinApiController.php b/app/Entities/Controllers/RecycleBinApiController.php index fdc24ddf8..89bd3f41a 100644 --- a/app/Entities/Controllers/RecycleBinApiController.php +++ b/app/Entities/Controllers/RecycleBinApiController.php @@ -9,6 +9,7 @@ use BookStack\Entities\Models\Deletion; use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\DeletionRepo; use BookStack\Http\ApiController; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\HasMany; class RecycleBinApiController extends ApiController @@ -69,7 +70,7 @@ class RecycleBinApiController extends ApiController /** * Load some related details for the deletion listing. */ - protected function listFormatter(Deletion $deletion) + protected function listFormatter(Deletion $deletion): void { $deletable = $deletion->deletable; @@ -89,9 +90,9 @@ class RecycleBinApiController extends ApiController } /** - * @param HasMany $query + * @param Builder $query */ - protected static function withTrashedQuery(HasMany $query): void + protected static function withTrashedQuery(Builder $query): void { $query->withTrashed(); } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 9dfd9d29e..72189222f 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -7,7 +7,7 @@ parameters: - app # The level 8 is the highest level - level: 2 + level: 3 phpVersion: min: 80200 diff --git a/tests/Entity/CommentStoreTest.php b/tests/Entity/CommentStoreTest.php index c5fe4ce50..de093a3a6 100644 --- a/tests/Entity/CommentStoreTest.php +++ b/tests/Entity/CommentStoreTest.php @@ -27,7 +27,6 @@ class CommentStoreTest extends TestCase 'local_id' => 1, 'entity_id' => $page->id, 'entity_type' => Page::newModelInstance()->getMorphClass(), - 'text' => null, 'parent_id' => 2, ]); From 579c1bf4241270f96f527101f89a2c90c9b9cb6b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 4 Sep 2025 15:06:58 +0100 Subject: [PATCH 13/48] Timezones: Seperated out store & display timezones to two options --- .env.example.complete | 8 +++- .../Providers/ViewTweaksServiceProvider.php | 13 +++++++ app/Config/app.php | 8 ++-- app/Util/DateFormatter.php | 25 +++++++++++++ resources/views/entities/meta.blade.php | 8 ++-- tests/Util/DateFormatterTest.php | 37 +++++++++++++++++++ 6 files changed, 90 insertions(+), 9 deletions(-) create mode 100644 app/Util/DateFormatter.php create mode 100644 tests/Util/DateFormatterTest.php diff --git a/.env.example.complete b/.env.example.complete index 25687aaac..18e7bd00d 100644 --- a/.env.example.complete +++ b/.env.example.complete @@ -36,10 +36,14 @@ APP_LANG=en # APP_LANG will be used if such a header is not provided. APP_AUTO_LANG_PUBLIC=true -# Application timezone -# Used where dates are displayed such as on exported content. +# Application timezones +# The first option is used to determine what timezone is used for date storage. +# Leaving that as "UTC" is advised. +# The second option is used to set the timezone which will be used for date +# formatting and display. This defaults to the "APP_TIMEZONE" value. # Valid timezone values can be found here: https://www.php.net/manual/en/timezones.php APP_TIMEZONE=UTC +APP_DISPLAY_TIMEZONE=UTC # Application theme # Used to specific a themes/ folder where BookStack UI diff --git a/app/App/Providers/ViewTweaksServiceProvider.php b/app/App/Providers/ViewTweaksServiceProvider.php index 7115dcb51..6771e513f 100644 --- a/app/App/Providers/ViewTweaksServiceProvider.php +++ b/app/App/Providers/ViewTweaksServiceProvider.php @@ -3,6 +3,7 @@ namespace BookStack\App\Providers; use BookStack\Entities\BreadcrumbsViewComposer; +use BookStack\Util\DateFormatter; use Illuminate\Pagination\Paginator; use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\View; @@ -10,6 +11,15 @@ use Illuminate\Support\ServiceProvider; class ViewTweaksServiceProvider extends ServiceProvider { + public function register() + { + $this->app->singleton(DateFormatter::class, function ($app) { + return new DateFormatter( + $app['config']->get('app.display_timezone'), + ); + }); + } + /** * Bootstrap services. */ @@ -21,6 +31,9 @@ class ViewTweaksServiceProvider extends ServiceProvider // View Composers View::composer('entities.breadcrumbs', BreadcrumbsViewComposer::class); + // View Globals + View::share('dates', $this->app->make(DateFormatter::class)); + // Custom blade view directives Blade::directive('icon', function ($expression) { return "toHtml(); ?>"; diff --git a/app/Config/app.php b/app/Config/app.php index b96d0bdb7..40e542d3e 100644 --- a/app/Config/app.php +++ b/app/Config/app.php @@ -70,8 +70,8 @@ return [ // A list of the sources/hostnames that can be reached by application SSR calls. // This is used wherever users can provide URLs/hosts in-platform, like for webhooks. // Host-specific functionality (usually controlled via other options) like auth - // or user avatars for example, won't use this list. - // Space seperated if multiple. Can use '*' as a wildcard. + // or user avatars, for example, won't use this list. + // Space separated if multiple. Can use '*' as a wildcard. // Values will be compared prefix-matched, case-insensitive, against called SSR urls. // Defaults to allow all hosts. 'ssr_hosts' => env('ALLOWED_SSR_HOSTS', '*'), @@ -80,8 +80,10 @@ return [ // Integer value between 0 (IP hidden) to 4 (Full IP usage) 'ip_address_precision' => env('IP_ADDRESS_PRECISION', 4), - // Application timezone for back-end date functions. + // Application timezone for stored date/time values. 'timezone' => env('APP_TIMEZONE', 'UTC'), + // Application timezone for displayed date/time values in the UI. + 'display_timezone' => env('APP_DISPLAY_TIMEZONE', env('APP_TIMEZONE', 'UTC')), // Default locale to use // A default variant is also stored since Laravel can overwrite diff --git a/app/Util/DateFormatter.php b/app/Util/DateFormatter.php new file mode 100644 index 000000000..489ed54e2 --- /dev/null +++ b/app/Util/DateFormatter.php @@ -0,0 +1,25 @@ +clone()->setTimezone($this->displayTimezone); + + return $withDisplayTimezone->format('Y-m-d H:i:s T'); + } + + public function relative(Carbon $date): string + { + return $date->diffForHumans(); + } +} diff --git a/resources/views/entities/meta.blade.php b/resources/views/entities/meta.blade.php index 9d3c4b956..c9d301aa3 100644 --- a/resources/views/entities/meta.blade.php +++ b/resources/views/entities/meta.blade.php @@ -31,7 +31,7 @@ @icon('star')
{!! trans('entities.meta_created_name', [ - 'timeLength' => ''.$entity->created_at->diffForHumans() . '', + 'timeLength' => ''. $dates->relative($entity->created_at) . '', 'user' => "".e($entity->createdBy->name). "" ]) !!}
@@ -39,7 +39,7 @@ @else
@icon('star') - {{ trans('entities.meta_created', ['timeLength' => $entity->created_at->diffForHumans()]) }} + {{ trans('entities.meta_created', ['timeLength' => $dates->relative($entity->created_at)]) }}
@endif @@ -48,7 +48,7 @@ @icon('edit')
{!! trans('entities.meta_updated_name', [ - 'timeLength' => '' . $entity->updated_at->diffForHumans() .'', + 'timeLength' => '' . $dates->relative($entity->updated_at) .'', 'user' => "".e($entity->updatedBy->name). "" ]) !!}
@@ -56,7 +56,7 @@ @elseif (!$entity->isA('revision'))
@icon('edit') - {{ trans('entities.meta_updated', ['timeLength' => $entity->updated_at->diffForHumans()]) }} + {{ trans('entities.meta_updated', ['timeLength' => $dates->relative($entity->updated_at)]) }}
@endif diff --git a/tests/Util/DateFormatterTest.php b/tests/Util/DateFormatterTest.php new file mode 100644 index 000000000..c9004b5a5 --- /dev/null +++ b/tests/Util/DateFormatterTest.php @@ -0,0 +1,37 @@ +isoWithTimezone($dateTime); + $this->assertEquals('2020-06-01 13:00:00 BST', $result); + } + + public function test_iso_with_timezone_works_from_non_utc_dates() + { + $formatter = new DateFormatter('Asia/Shanghai'); + $dateTime = new Carbon('2025-06-10 15:25:00', 'America/New_York'); + + $result = $formatter->isoWithTimezone($dateTime); + $this->assertEquals('2025-06-11 03:25:00 CST', $result); + } + + public function test_relative() + { + $formatter = new DateFormatter('Europe/London'); + $dateTime = (new Carbon('now', 'UTC'))->subMinutes(50); + + $result = $formatter->relative($dateTime); + $this->assertEquals('50 minutes ago', $result); + } +} From 36cb243d5e47afbdf6c7c847bed5c34454abfa48 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 4 Sep 2025 16:11:35 +0100 Subject: [PATCH 14/48] Timezones: Updated date displays to use consistent formats --- app/Entities/Tools/PageEditActivity.php | 19 ++--- app/Util/DateFormatter.php | 7 +- phpunit.xml | 2 + resources/views/comments/comment.blade.php | 84 +++++++++++-------- .../views/common/activity-item.blade.php | 2 +- resources/views/entities/grid-item.blade.php | 4 +- resources/views/entities/list-item.blade.php | 4 +- resources/views/entities/meta.blade.php | 8 +- resources/views/exports/import-show.blade.php | 2 +- .../views/exports/parts/import.blade.php | 2 +- resources/views/exports/parts/meta.blade.php | 4 +- .../pages/parts/image-manager-form.blade.php | 8 +- .../pages/parts/revisions-index-row.blade.php | 4 +- .../parts/template-manager-list.blade.php | 2 +- .../views/settings/webhooks/edit.blade.php | 12 ++- .../views/users/api-tokens/edit.blade.php | 8 +- .../users/parts/users-list-item.blade.php | 2 +- resources/views/users/profile.blade.php | 2 +- tests/Exports/HtmlExportTest.php | 4 +- tests/Util/DateFormatterTest.php | 4 +- 20 files changed, 100 insertions(+), 84 deletions(-) diff --git a/app/Entities/Tools/PageEditActivity.php b/app/Entities/Tools/PageEditActivity.php index 646b200f1..22f89bf62 100644 --- a/app/Entities/Tools/PageEditActivity.php +++ b/app/Entities/Tools/PageEditActivity.php @@ -4,19 +4,15 @@ namespace BookStack\Entities\Tools; use BookStack\Entities\Models\Page; use BookStack\Entities\Models\PageRevision; +use BookStack\Util\DateFormatter; use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; class PageEditActivity { - protected Page $page; - - /** - * PageEditActivity constructor. - */ - public function __construct(Page $page) - { - $this->page = $page; + public function __construct( + protected Page $page + ) { } /** @@ -50,11 +46,9 @@ class PageEditActivity /** * Get any editor clash warning messages to show for the given draft revision. * - * @param PageRevision|Page $draft - * * @return string[] */ - public function getWarningMessagesForDraft($draft): array + public function getWarningMessagesForDraft(Page|PageRevision $draft): array { $warnings = []; @@ -82,7 +76,8 @@ class PageEditActivity */ public function getEditingActiveDraftMessage(PageRevision $draft): string { - $message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $draft->updated_at->diffForHumans()]); + $formatter = resolve(DateFormatter::class); + $message = trans('entities.pages_editing_draft_notification', ['timeDiff' => $formatter->relative($draft->updated_at)]); if ($draft->page->updated_at->timestamp <= $draft->updated_at->timestamp) { return $message; } diff --git a/app/Util/DateFormatter.php b/app/Util/DateFormatter.php index 489ed54e2..c6e60bd53 100644 --- a/app/Util/DateFormatter.php +++ b/app/Util/DateFormatter.php @@ -3,6 +3,7 @@ namespace BookStack\Util; use Carbon\Carbon; +use Carbon\CarbonInterface; class DateFormatter { @@ -11,15 +12,15 @@ class DateFormatter ) { } - public function isoWithTimezone(Carbon $date): string + public function absolute(Carbon $date): string { $withDisplayTimezone = $date->clone()->setTimezone($this->displayTimezone); return $withDisplayTimezone->format('Y-m-d H:i:s T'); } - public function relative(Carbon $date): string + public function relative(Carbon $date, bool $includeSuffix = true): string { - return $date->diffForHumans(); + return $date->diffForHumans(null, $includeSuffix ? null : CarbonInterface::DIFF_ABSOLUTE); } } diff --git a/phpunit.xml b/phpunit.xml index a8e725d41..8a7ab9cb7 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -16,6 +16,8 @@ + + diff --git a/resources/views/comments/comment.blade.php b/resources/views/comments/comment.blade.php index d70a8c1d9..67aac7203 100644 --- a/resources/views/comments/comment.blade.php +++ b/resources/views/comments/comment.blade.php @@ -14,7 +14,8 @@
@if ($comment->createdBy)
- {{ $comment->createdBy->name }} + {{ $comment->createdBy->name }}
@endif
@@ -23,50 +24,55 @@ @else {{ trans('common.deleted_user') }} @endif -  {{ trans('entities.comment_created', ['createDiff' => $comment->created_at->diffForHumans() ]) }} +  {{ trans('entities.comment_created', ['createDiff' => $dates->relative($comment->created_at) ]) }} @if($comment->isUpdated()) - + {{ trans('entities.comment_updated_indicator') }} @endif
@if(!$readOnly && (userCan('comment-create-all') || userCan('comment-update', $comment) || userCan('comment-delete', $comment))) -
- @if(userCan('comment-create-all')) - - @endif - @if(!$comment->parent_id && (userCan('comment-update', $comment) || userCan('comment-delete', $comment))) - - @endif - @if(userCan('comment-update', $comment)) - - @endif - @if(userCan('comment-delete', $comment)) - - @endif - +
+ @if(userCan('comment-create-all')) + + @endif + @if(!$comment->parent_id && (userCan('comment-update', $comment) || userCan('comment-delete', $comment))) + + @endif + @if(userCan('comment-update', $comment)) + + @endif + @if(userCan('comment-delete', $comment)) + + @endif +  •  -
+
@endif
@@ -76,7 +82,8 @@
@if ($comment->parent_id)

- @icon('reply'){{ trans('entities.comment_in_reply_to', ['commentId' => '#' . $comment->parent_id]) }} + @icon('reply'){{ trans('entities.comment_in_reply_to', ['commentId' => '#' . $comment->parent_id]) }}

@endif @if($comment->content_ref) @@ -86,7 +93,8 @@ option:page-comment-reference:view-comment-text="{{ trans('entities.comment_view') }}" option:page-comment-reference:jump-to-thread-text="{{ trans('entities.comment_jump_to_thread') }}" option:page-comment-reference:close-text="{{ trans('common.close') }}" - href="#">@icon('bookmark'){{ trans('entities.comment_reference') }} {{ trans('entities.comment_reference_outdated') }} + href="#">@icon('bookmark'){{ trans('entities.comment_reference') }} + {{ trans('entities.comment_reference_outdated') }}
@endif {!! $commentHtml !!} @@ -95,10 +103,12 @@ @if(!$readOnly && userCan('comment-update', $comment)) diff --git a/resources/views/common/activity-item.blade.php b/resources/views/common/activity-item.blade.php index 1c970084f..1d3c7bd75 100644 --- a/resources/views/common/activity-item.blade.php +++ b/resources/views/common/activity-item.blade.php @@ -26,5 +26,5 @@
- @icon('time'){{ $activity->created_at->diffForHumans() }} + @icon('time'){{ $dates->relative($activity->created_at) }} diff --git a/resources/views/entities/grid-item.blade.php b/resources/views/entities/grid-item.blade.php index ee31b53f2..17c54e263 100644 --- a/resources/views/entities/grid-item.blade.php +++ b/resources/views/entities/grid-item.blade.php @@ -10,7 +10,7 @@

{{ $entity->getExcerpt(130) }}

\ No newline at end of file diff --git a/resources/views/entities/list-item.blade.php b/resources/views/entities/list-item.blade.php index 2fadef191..5174de431 100644 --- a/resources/views/entities/list-item.blade.php +++ b/resources/views/entities/list-item.blade.php @@ -27,9 +27,9 @@ @endif @if(($showUpdatedBy ?? false) && $entity->relationLoaded('updatedBy') && $entity->updatedBy) - + {!! trans('entities.meta_updated_name', [ - 'timeLength' => $entity->updated_at->diffForHumans(), + 'timeLength' => $dates->relative($entity->updated_at), 'user' => e($entity->updatedBy->name) ]) !!} diff --git a/resources/views/entities/meta.blade.php b/resources/views/entities/meta.blade.php index c9d301aa3..060c197a4 100644 --- a/resources/views/entities/meta.blade.php +++ b/resources/views/entities/meta.blade.php @@ -31,7 +31,7 @@ @icon('star')
{!! trans('entities.meta_created_name', [ - 'timeLength' => ''. $dates->relative($entity->created_at) . '', + 'timeLength' => ''. $dates->relative($entity->created_at) . '', 'user' => "".e($entity->createdBy->name). "" ]) !!}
@@ -39,7 +39,7 @@ @else
@icon('star') - {{ trans('entities.meta_created', ['timeLength' => $dates->relative($entity->created_at)]) }} + {{ trans('entities.meta_created', ['timeLength' => $dates->relative($entity->created_at)]) }}
@endif @@ -48,7 +48,7 @@ @icon('edit')
{!! trans('entities.meta_updated_name', [ - 'timeLength' => '' . $dates->relative($entity->updated_at) .'', + 'timeLength' => '' . $dates->relative($entity->updated_at) .'', 'user' => "".e($entity->updatedBy->name). "" ]) !!}
@@ -56,7 +56,7 @@ @elseif (!$entity->isA('revision'))
@icon('edit') - {{ trans('entities.meta_updated', ['timeLength' => $dates->relative($entity->updated_at)]) }} + {{ trans('entities.meta_updated', ['timeLength' => $dates->relative($entity->updated_at)]) }}
@endif diff --git a/resources/views/exports/import-show.blade.php b/resources/views/exports/import-show.blade.php index a28b79bb3..1c46b7a0b 100644 --- a/resources/views/exports/import-show.blade.php +++ b/resources/views/exports/import-show.blade.php @@ -26,7 +26,7 @@
{{ trans('entities.import_size', ['size' => $import->getSizeString()]) }}
-
{{ trans('entities.import_uploaded_at', ['relativeTime' => $import->created_at->diffForHumans()]) }}
+
{{ trans('entities.import_uploaded_at', ['relativeTime' => $dates->relative($import->created_at)]) }}
@if($import->createdBy)
{{ trans('entities.import_uploaded_by') }} diff --git a/resources/views/exports/parts/import.blade.php b/resources/views/exports/parts/import.blade.php index 2f7659c46..dc287370e 100644 --- a/resources/views/exports/parts/import.blade.php +++ b/resources/views/exports/parts/import.blade.php @@ -5,6 +5,6 @@
{{ $import->getSizeString() }}
-
@icon('time'){{ $import->created_at->diffForHumans() }}
+
@icon('time'){{ $dates->relative($import->created_at) }}
\ No newline at end of file diff --git a/resources/views/exports/parts/meta.blade.php b/resources/views/exports/parts/meta.blade.php index d4128898b..00117f4a1 100644 --- a/resources/views/exports/parts/meta.blade.php +++ b/resources/views/exports/parts/meta.blade.php @@ -4,13 +4,13 @@ @endif @icon('star'){!! trans('entities.meta_created' . ($entity->createdBy ? '_name' : ''), [ - 'timeLength' => $entity->created_at->isoFormat('D MMMM Y HH:mm:ss'), + 'timeLength' => $dates->absolute($entity->created_at), 'user' => e($entity->createdBy->name ?? ''), ]) !!}
@icon('edit'){!! trans('entities.meta_updated' . ($entity->updatedBy ? '_name' : ''), [ - 'timeLength' => $entity->updated_at->isoFormat('D MMMM Y HH:mm:ss'), + 'timeLength' => $dates->absolute($entity->updated_at), 'user' => e($entity->updatedBy->name ?? '') ]) !!} \ No newline at end of file diff --git a/resources/views/pages/parts/image-manager-form.blade.php b/resources/views/pages/parts/image-manager-form.blade.php index bd84e247d..452a0aaa1 100644 --- a/resources/views/pages/parts/image-manager-form.blade.php +++ b/resources/views/pages/parts/image-manager-form.blade.php @@ -94,12 +94,12 @@

-
- @icon('star') {{ trans('components.image_uploaded', ['uploadedDate' => $image->created_at->diffForHumans()]) }} +
+ @icon('star') {{ trans('components.image_uploaded', ['uploadedDate' => $dates->relative($image->created_at)]) }}
@if($image->created_at->valueOf() !== $image->updated_at->valueOf()) -
- @icon('edit') {{ trans('components.image_updated', ['updateDate' => $image->updated_at->diffForHumans()]) }} +
+ @icon('edit') {{ trans('components.image_updated', ['updateDate' => $dates->relative($image->updated_at)]) }}
@endif @if($image->createdBy) diff --git a/resources/views/pages/parts/revisions-index-row.blade.php b/resources/views/pages/parts/revisions-index-row.blade.php index 48bea5b57..19b924763 100644 --- a/resources/views/pages/parts/revisions-index-row.blade.php +++ b/resources/views/pages/parts/revisions-index-row.blade.php @@ -17,8 +17,8 @@ @if($revision->createdBy) {{ $revision->createdBy->name }} @else {{ trans('common.deleted_user') }} @endif
- {{ $revision->created_at->isoFormat('D MMMM Y HH:mm:ss') }} - ({{ $revision->created_at->diffForHumans() }}) + {{ $dates->absolute($revision->created_at) }} + ({{ $dates->relative($revision->created_at) }})
diff --git a/resources/views/pages/parts/template-manager-list.blade.php b/resources/views/pages/parts/template-manager-list.blade.php index f2f70c142..990583a71 100644 --- a/resources/views/pages/parts/template-manager-list.blade.php +++ b/resources/views/pages/parts/template-manager-list.blade.php @@ -6,7 +6,7 @@ draggable="true" template-id="{{ $template->id }}">
{{ $template->name }}
-
{{ trans('entities.meta_updated', ['timeLength' => $template->updated_at->diffForHumans()]) }}
+
{{ trans('entities.meta_updated', ['timeLength' => $dates->relative($template->updated_at)]) }}
diff --git a/resources/views/users/api-tokens/edit.blade.php b/resources/views/users/api-tokens/edit.blade.php index aa3e49ded..3a1ff49d3 100644 --- a/resources/views/users/api-tokens/edit.blade.php +++ b/resources/views/users/api-tokens/edit.blade.php @@ -42,12 +42,12 @@
- - {{ trans('settings.user_api_token_created', ['timeAgo' => $token->created_at->diffForHumans()]) }} + + {{ trans('settings.user_api_token_created', ['timeAgo' => $dates->relative($token->created_at)]) }}
- - {{ trans('settings.user_api_token_updated', ['timeAgo' => $token->created_at->diffForHumans()]) }} + + {{ trans('settings.user_api_token_updated', ['timeAgo' => $dates->relative($token->created_at)]) }}
diff --git a/resources/views/users/parts/users-list-item.blade.php b/resources/views/users/parts/users-list-item.blade.php index dc7c9f272..9c7ecd147 100644 --- a/resources/views/users/parts/users-list-item.blade.php +++ b/resources/views/users/parts/users-list-item.blade.php @@ -20,7 +20,7 @@ @if($user->last_activity_at) {{ trans('settings.users_latest_activity') }}
- {{ $user->last_activity_at->diffForHumans() }} + {{ $dates->relative($user->last_activity_at) }} @endif
diff --git a/resources/views/users/profile.blade.php b/resources/views/users/profile.blade.php index a8be8a4c1..9879091a5 100644 --- a/resources/views/users/profile.blade.php +++ b/resources/views/users/profile.blade.php @@ -23,7 +23,7 @@

{{ $user->name }}

- {{ trans('entities.profile_user_for_x', ['time' => $user->created_at->diffForHumans(null, true)]) }} + {{ trans('entities.profile_user_for_x', ['time' => $dates->relative($user->created_at, false)]) }}

diff --git a/tests/Exports/HtmlExportTest.php b/tests/Exports/HtmlExportTest.php index 069cf2801..e039fb2cc 100644 --- a/tests/Exports/HtmlExportTest.php +++ b/tests/Exports/HtmlExportTest.php @@ -99,9 +99,9 @@ class HtmlExportTest extends TestCase $page = $this->entities->page(); $resp = $this->asEditor()->get($page->getUrl('/export/html')); - $resp->assertSee($page->created_at->isoFormat('D MMMM Y HH:mm:ss')); + $resp->assertSee($page->created_at->format('Y-m-d H:i:s T')); $resp->assertDontSee($page->created_at->diffForHumans()); - $resp->assertSee($page->updated_at->isoFormat('D MMMM Y HH:mm:ss')); + $resp->assertSee($page->updated_at->format('Y-m-d H:i:s T')); $resp->assertDontSee($page->updated_at->diffForHumans()); } diff --git a/tests/Util/DateFormatterTest.php b/tests/Util/DateFormatterTest.php index c9004b5a5..1c0a458e0 100644 --- a/tests/Util/DateFormatterTest.php +++ b/tests/Util/DateFormatterTest.php @@ -13,7 +13,7 @@ class DateFormatterTest extends TestCase $formatter = new DateFormatter('Europe/London'); $dateTime = new Carbon('2020-06-01 12:00:00', 'UTC'); - $result = $formatter->isoWithTimezone($dateTime); + $result = $formatter->absolute($dateTime); $this->assertEquals('2020-06-01 13:00:00 BST', $result); } @@ -22,7 +22,7 @@ class DateFormatterTest extends TestCase $formatter = new DateFormatter('Asia/Shanghai'); $dateTime = new Carbon('2025-06-10 15:25:00', 'America/New_York'); - $result = $formatter->isoWithTimezone($dateTime); + $result = $formatter->absolute($dateTime); $this->assertEquals('2025-06-11 03:25:00 CST', $result); } From c8716df284e74fad780ed8d36b23669d725b164a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 8 Sep 2025 15:59:25 +0100 Subject: [PATCH 15/48] Permissions: Removed unused role-perm columns, added permission enum Updated main permission check methods to support our new enum. --- app/App/helpers.php | 3 +- app/Entities/Tools/PermissionsUpdater.php | 11 +- app/Permissions/Models/EntityPermission.php | 2 - app/Permissions/Models/RolePermission.php | 1 - app/Permissions/Permission.php | 151 ++++++++++++++++++ app/Permissions/PermissionApplicator.php | 16 +- app/Users/Models/Role.php | 3 +- app/Users/Models/User.php | 21 ++- ...25_09_02_111542_remove_unused_columns.php} | 5 + tests/Helpers/PermissionsProvider.php | 5 +- 10 files changed, 190 insertions(+), 28 deletions(-) create mode 100644 app/Permissions/Permission.php rename database/migrations/{2025_09_02_111542_remove_comments_text_column.php => 2025_09_02_111542_remove_unused_columns.php} (76%) diff --git a/app/App/helpers.php b/app/App/helpers.php index 2305c2d72..0e26d9974 100644 --- a/app/App/helpers.php +++ b/app/App/helpers.php @@ -3,6 +3,7 @@ use BookStack\App\AppVersion; use BookStack\App\Model; use BookStack\Facades\Theme; +use BookStack\Permissions\Permission; use BookStack\Permissions\PermissionApplicator; use BookStack\Settings\SettingService; use BookStack\Users\Models\User; @@ -39,7 +40,7 @@ function user(): User * Check if the current user has a permission. If an ownable element * is passed in the jointPermissions are checked against that particular item. */ -function userCan(string $permission, ?Model $ownable = null): bool +function userCan(string|Permission $permission, ?Model $ownable = null): bool { if (is_null($ownable)) { return user()->can($permission); diff --git a/app/Entities/Tools/PermissionsUpdater.php b/app/Entities/Tools/PermissionsUpdater.php index 9f3b8f952..4ca53982a 100644 --- a/app/Entities/Tools/PermissionsUpdater.php +++ b/app/Entities/Tools/PermissionsUpdater.php @@ -8,6 +8,7 @@ use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Entity; use BookStack\Facades\Activity; use BookStack\Permissions\Models\EntityPermission; +use BookStack\Permissions\Permission; use BookStack\Users\Models\Role; use BookStack\Users\Models\User; use Illuminate\Http\Request; @@ -93,8 +94,9 @@ class PermissionsUpdater foreach ($permissions as $roleId => $info) { $entityPermissionData = ['role_id' => $roleId]; - foreach (EntityPermission::PERMISSIONS as $permission) { - $entityPermissionData[$permission] = (($info[$permission] ?? false) === "true"); + foreach (Permission::genericForEntity() as $permission) { + $permName = $permission->value; + $entityPermissionData[$permName] = (($info[$permName] ?? false) === "true"); } $formatted[] = $entityPermissionData; } @@ -108,8 +110,9 @@ class PermissionsUpdater foreach ($permissions as $requestPermissionData) { $entityPermissionData = ['role_id' => $requestPermissionData['role_id']]; - foreach (EntityPermission::PERMISSIONS as $permission) { - $entityPermissionData[$permission] = boolval($requestPermissionData[$permission] ?? false); + foreach (Permission::genericForEntity() as $permission) { + $permName = $permission->value; + $entityPermissionData[$permName] = boolval($requestPermissionData[$permName] ?? false); } $formatted[] = $entityPermissionData; } diff --git a/app/Permissions/Models/EntityPermission.php b/app/Permissions/Models/EntityPermission.php index 1ef9c2886..efad2ba39 100644 --- a/app/Permissions/Models/EntityPermission.php +++ b/app/Permissions/Models/EntityPermission.php @@ -18,8 +18,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; */ class EntityPermission extends Model { - public const PERMISSIONS = ['view', 'create', 'update', 'delete']; - protected $fillable = ['role_id', 'view', 'create', 'update', 'delete']; public $timestamps = false; protected $hidden = ['entity_id', 'entity_type', 'id']; diff --git a/app/Permissions/Models/RolePermission.php b/app/Permissions/Models/RolePermission.php index d43313ea0..67b1c55f5 100644 --- a/app/Permissions/Models/RolePermission.php +++ b/app/Permissions/Models/RolePermission.php @@ -9,7 +9,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; /** * @property int $id * @property string $name - * @property string $display_name */ class RolePermission extends Model { diff --git a/app/Permissions/Permission.php b/app/Permissions/Permission.php new file mode 100644 index 000000000..539bbe808 --- /dev/null +++ b/app/Permissions/Permission.php @@ -0,0 +1,151 @@ +value; + $explodedPermission = explode('-', $permissionName); $action = $explodedPermission[1] ?? $explodedPermission[0]; - $fullPermission = count($explodedPermission) > 1 ? $permission : $ownable->getMorphClass() . '-' . $permission; + $fullPermission = count($explodedPermission) > 1 ? $permissionName : $ownable->getMorphClass() . '-' . $permissionName; $user = $this->currentUser(); $userRoleIds = $this->getCurrentUserRoleIds(); @@ -235,8 +236,13 @@ class PermissionApplicator */ protected function ensureValidEntityAction(string $action): void { - if (!in_array($action, EntityPermission::PERMISSIONS)) { - throw new InvalidArgumentException('Action should be a simple entity permission action, not a role permission'); + $allowed = Permission::genericForEntity(); + foreach ($allowed as $permission) { + if ($permission->value === $action) { + return; + } } + + throw new InvalidArgumentException('Action should be a simple entity permission action, not a role permission'); } } diff --git a/app/Users/Models/Role.php b/app/Users/Models/Role.php index 912ae81c9..92be22458 100644 --- a/app/Users/Models/Role.php +++ b/app/Users/Models/Role.php @@ -57,7 +57,8 @@ class Role extends Model implements Loggable */ public function permissions(): BelongsToMany { - return $this->belongsToMany(RolePermission::class, 'permission_role', 'role_id', 'permission_id'); + return $this->belongsToMany(RolePermission::class, 'permission_role', 'role_id', 'permission_id') + ->select(['id', 'name']); } /** diff --git a/app/Users/Models/User.php b/app/Users/Models/User.php index f83e12088..0017653b5 100644 --- a/app/Users/Models/User.php +++ b/app/Users/Models/User.php @@ -12,6 +12,7 @@ use BookStack\Api\ApiToken; use BookStack\App\Model; use BookStack\App\SluggableInterface; use BookStack\Entities\Tools\SlugGenerator; +use BookStack\Permissions\Permission; use BookStack\Translation\LocaleDefinition; use BookStack\Translation\LocaleManager; use BookStack\Uploads\Image; @@ -26,7 +27,6 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Notifications\Notifiable; use Illuminate\Support\Collection; @@ -156,8 +156,9 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon /** * Check if the user has a particular permission. */ - public function can(string $permissionName): bool + public function can(string|Permission $permission): bool { + $permissionName = is_string($permission) ? $permission : $permission->value; return $this->permissions()->contains($permissionName); } @@ -181,9 +182,9 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon } /** - * Clear any cached permissions on this instance. + * Clear any cached permissions in this instance. */ - public function clearPermissionCache() + public function clearPermissionCache(): void { $this->permissions = null; } @@ -191,7 +192,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon /** * Attach a role to this user. */ - public function attachRole(Role $role) + public function attachRole(Role $role): void { $this->roles()->attach($role->id); $this->unsetRelation('roles'); @@ -207,15 +208,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon /** * Check if the user has a social account, - * If a driver is passed it checks for that single account type. - * - * @param bool|string $socialDriver - * - * @return bool + * If a driver is passed, it checks for that single account type. */ - public function hasSocialAccount($socialDriver = false) + public function hasSocialAccount(string $socialDriver = ''): bool { - if ($socialDriver === false) { + if (empty($socialDriver)) { return $this->socialAccounts()->count() > 0; } diff --git a/database/migrations/2025_09_02_111542_remove_comments_text_column.php b/database/migrations/2025_09_02_111542_remove_unused_columns.php similarity index 76% rename from database/migrations/2025_09_02_111542_remove_comments_text_column.php rename to database/migrations/2025_09_02_111542_remove_unused_columns.php index 8caa05e54..1ec2dfdf9 100644 --- a/database/migrations/2025_09_02_111542_remove_comments_text_column.php +++ b/database/migrations/2025_09_02_111542_remove_unused_columns.php @@ -14,6 +14,11 @@ return new class extends Migration Schema::table('comments', function (Blueprint $table) { $table->dropColumn('text'); }); + + Schema::table('role_permissions', function (Blueprint $table) { + $table->dropColumn('display_name'); + $table->dropColumn('description'); + }); } /** diff --git a/tests/Helpers/PermissionsProvider.php b/tests/Helpers/PermissionsProvider.php index cb036fe97..c9ae30919 100644 --- a/tests/Helpers/PermissionsProvider.php +++ b/tests/Helpers/PermissionsProvider.php @@ -5,6 +5,7 @@ namespace Tests\Helpers; use BookStack\Entities\Models\Entity; use BookStack\Permissions\Models\EntityPermission; use BookStack\Permissions\Models\RolePermission; +use BookStack\Permissions\Permission; use BookStack\Settings\SettingService; use BookStack\Users\Models\Role; use BookStack\Users\Models\User; @@ -139,8 +140,8 @@ class PermissionsProvider protected function actionListToEntityPermissionData(array $actionList, int $roleId = 0): array { $permissionData = ['role_id' => $roleId]; - foreach (EntityPermission::PERMISSIONS as $possibleAction) { - $permissionData[$possibleAction] = in_array($possibleAction, $actionList); + foreach (Permission::genericForEntity() as $permission) { + $permissionData[$permission->value] = in_array($permission->value, $actionList); } return $permissionData; From 5fc11d46d5115b96a2571fa371671c26900fab8f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 8 Sep 2025 16:15:42 +0100 Subject: [PATCH 16/48] Permissions: Added enum usage to controller helpers Also fixed various missing types or spelling/formatting points. Added down action for role_permission table changes in migration. --- app/Http/Controller.php | 27 +++++++++---------- app/Users/Controllers/RoleApiController.php | 13 ++++----- ...025_09_02_111542_remove_unused_columns.php | 5 ++++ 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/app/Http/Controller.php b/app/Http/Controller.php index 7f2134dd8..5d3be4951 100644 --- a/app/Http/Controller.php +++ b/app/Http/Controller.php @@ -6,6 +6,7 @@ use BookStack\Activity\Models\Loggable; use BookStack\App\Model; use BookStack\Exceptions\NotifyException; use BookStack\Facades\Activity; +use BookStack\Permissions\Permission; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Http\JsonResponse; @@ -27,10 +28,9 @@ abstract class Controller extends BaseController } /** - * Stops the application and shows a permission error if - * the application is in demo mode. + * Stops the application and shows a permission error if the application is in demo mode. */ - protected function preventAccessInDemoMode() + protected function preventAccessInDemoMode(): void { if (config('app.env') === 'demo') { $this->showPermissionError(); @@ -40,14 +40,13 @@ abstract class Controller extends BaseController /** * Adds the page title into the view. */ - public function setPageTitle(string $title) + public function setPageTitle(string $title): void { view()->share('pageTitle', $title); } /** - * On a permission error redirect to home and display. - * the error as a notification. + * On a permission error redirect to home and display the error as a notification. * * @throws NotifyException */ @@ -61,7 +60,7 @@ abstract class Controller extends BaseController /** * Checks that the current user has the given permission otherwise throw an exception. */ - protected function checkPermission(string $permission): void + protected function checkPermission(string|Permission $permission): void { if (!user() || !user()->can($permission)) { $this->showPermissionError(); @@ -81,7 +80,7 @@ abstract class Controller extends BaseController /** * Check the current user's permissions against an ownable item otherwise throw an exception. */ - protected function checkOwnablePermission(string $permission, Model $ownable, string $redirectLocation = '/'): void + protected function checkOwnablePermission(string|Permission $permission, Model $ownable, string $redirectLocation = '/'): void { if (!userCan($permission, $ownable)) { $this->showPermissionError($redirectLocation); @@ -92,7 +91,7 @@ abstract class Controller extends BaseController * Check if a user has a permission or bypass the permission * check if the given callback resolves true. */ - protected function checkPermissionOr(string $permission, callable $callback): void + protected function checkPermissionOr(string|Permission $permission, callable $callback): void { if ($callback() !== true) { $this->checkPermission($permission); @@ -103,7 +102,7 @@ abstract class Controller extends BaseController * Check if the current user has a permission or bypass if the provided user * id matches the current user. */ - protected function checkPermissionOrCurrentUser(string $permission, int $userId): void + protected function checkPermissionOrCurrentUser(string|Permission $permission, int $userId): void { $this->checkPermissionOr($permission, function () use ($userId) { return $userId === user()->id; @@ -111,7 +110,7 @@ abstract class Controller extends BaseController } /** - * Send back a json error message. + * Send back a JSON error message. */ protected function jsonError(string $messageText = '', int $statusCode = 500): JsonResponse { @@ -127,7 +126,7 @@ abstract class Controller extends BaseController } /** - * Show a positive, successful notification to the user on next view load. + * Show a positive, successful notification to the user on the next view load. */ protected function showSuccessNotification(string $message): void { @@ -135,7 +134,7 @@ abstract class Controller extends BaseController } /** - * Show a warning notification to the user on next view load. + * Show a warning notification to the user on the next view load. */ protected function showWarningNotification(string $message): void { @@ -143,7 +142,7 @@ abstract class Controller extends BaseController } /** - * Show an error notification to the user on next view load. + * Show an error notification to the user on the next view load. */ protected function showErrorNotification(string $message): void { diff --git a/app/Users/Controllers/RoleApiController.php b/app/Users/Controllers/RoleApiController.php index 2f3638cd3..6d87c9e27 100644 --- a/app/Users/Controllers/RoleApiController.php +++ b/app/Users/Controllers/RoleApiController.php @@ -10,8 +10,6 @@ use Illuminate\Support\Facades\DB; class RoleApiController extends ApiController { - protected PermissionsRepo $permissionsRepo; - protected array $fieldsToExpose = [ 'display_name', 'description', 'mfa_enforced', 'external_auth_id', 'created_at', 'updated_at', ]; @@ -35,10 +33,9 @@ class RoleApiController extends ApiController ] ]; - public function __construct(PermissionsRepo $permissionsRepo) - { - $this->permissionsRepo = $permissionsRepo; - + public function __construct( + protected PermissionsRepo $permissionsRepo + ) { // Checks for all endpoints in this controller $this->middleware(function ($request, $next) { $this->checkPermission('user-roles-manage'); @@ -125,9 +122,9 @@ class RoleApiController extends ApiController } /** - * Format the given role model for single-result display. + * Format the given role model for a single-result display. */ - protected function singleFormatter(Role $role) + protected function singleFormatter(Role $role): void { $role->load('users:id,name,slug'); $role->unsetRelation('permissions'); diff --git a/database/migrations/2025_09_02_111542_remove_unused_columns.php b/database/migrations/2025_09_02_111542_remove_unused_columns.php index 1ec2dfdf9..3a36e5ad6 100644 --- a/database/migrations/2025_09_02_111542_remove_unused_columns.php +++ b/database/migrations/2025_09_02_111542_remove_unused_columns.php @@ -29,5 +29,10 @@ return new class extends Migration Schema::table('comments', function (Blueprint $table) { $table->longText('text')->nullable(); }); + + Schema::table('role_permissions', function (Blueprint $table) { + $table->string('display_name')->nullable(); + $table->string('description')->nullable(); + }); } }; From 33a0237f875170e4750ea537604307dd9d5a0bde Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 8 Sep 2025 18:14:38 +0100 Subject: [PATCH 17/48] Permissions: Updated usage of controller methods to use enum --- app/Access/LoginService.php | 3 +- .../Controllers/AuditLogApiController.php | 5 +- .../Controllers/AuditLogController.php | 5 +- .../Controllers/CommentController.php | 17 ++--- app/Activity/Controllers/WatchController.php | 3 +- .../Handlers/BaseNotificationHandler.php | 3 +- app/Activity/Tools/UserEntityWatchOptions.php | 3 +- app/Api/ApiTokenGuard.php | 3 +- app/Api/UserApiTokenController.php | 13 ++-- app/App/helpers.php | 2 +- .../Controllers/BookApiController.php | 7 +- app/Entities/Controllers/BookController.php | 31 ++++----- .../Controllers/BookshelfApiController.php | 7 +- .../Controllers/BookshelfController.php | 15 +++-- .../Controllers/ChapterApiController.php | 9 +-- .../Controllers/ChapterController.php | 32 +++++---- .../Controllers/PageApiController.php | 9 +-- app/Entities/Controllers/PageController.php | 38 +++++------ .../Controllers/PageRevisionController.php | 5 +- .../Controllers/RecycleBinApiController.php | 5 +- .../Controllers/RecycleBinController.php | 5 +- app/Entities/Tools/PageContent.php | 3 +- app/Http/Middleware/ApiAuthenticate.php | 3 +- .../Middleware/CheckUserHasPermission.php | 7 +- .../ContentPermissionApiController.php | 4 +- app/Permissions/Permission.php | 66 +++++++------------ app/Permissions/PermissionsController.php | 20 +++--- app/Settings/MaintenanceController.php | 9 +-- app/Settings/SettingController.php | 5 +- app/Sorting/BookSortController.php | 5 +- .../Controllers/AttachmentApiController.php | 13 ++-- .../Controllers/AttachmentController.php | 32 ++++----- .../Controllers/DrawioImageController.php | 3 +- .../Controllers/GalleryImageController.php | 3 +- app/Uploads/Controllers/ImageController.php | 11 ++-- .../Controllers/ImageGalleryApiController.php | 11 ++-- app/Users/Controllers/RoleApiController.php | 3 +- app/Users/Controllers/RoleController.php | 15 +++-- .../Controllers/UserAccountController.php | 5 +- app/Users/Controllers/UserApiController.php | 4 +- app/Users/Controllers/UserController.php | 15 +++-- resources/views/home/books.blade.php | 2 +- resources/views/home/shelves.blade.php | 2 +- 43 files changed, 235 insertions(+), 226 deletions(-) diff --git a/app/Access/LoginService.php b/app/Access/LoginService.php index 76ae1938d..c81e95572 100644 --- a/app/Access/LoginService.php +++ b/app/Access/LoginService.php @@ -9,6 +9,7 @@ use BookStack\Exceptions\LoginAttemptInvalidUserException; use BookStack\Exceptions\StoppedAuthenticationException; use BookStack\Facades\Activity; use BookStack\Facades\Theme; +use BookStack\Permissions\Permission; use BookStack\Theming\ThemeEvents; use BookStack\Users\Models\User; use Exception; @@ -50,7 +51,7 @@ class LoginService Theme::dispatch(ThemeEvents::AUTH_LOGIN, $method, $user); // Authenticate on all session guards if a likely admin - if ($user->can('users-manage') && $user->can('user-roles-manage')) { + if ($user->can(Permission::UsersManage) && $user->can(Permission::UserRolesManage)) { $guards = ['standard', 'ldap', 'saml2', 'oidc']; foreach ($guards as $guard) { auth($guard)->login($user); diff --git a/app/Activity/Controllers/AuditLogApiController.php b/app/Activity/Controllers/AuditLogApiController.php index 650d17446..0cb4d9cb6 100644 --- a/app/Activity/Controllers/AuditLogApiController.php +++ b/app/Activity/Controllers/AuditLogApiController.php @@ -4,6 +4,7 @@ namespace BookStack\Activity\Controllers; use BookStack\Activity\Models\Activity; use BookStack\Http\ApiController; +use BookStack\Permissions\Permission; class AuditLogApiController extends ApiController { @@ -16,8 +17,8 @@ class AuditLogApiController extends ApiController */ public function list() { - $this->checkPermission('settings-manage'); - $this->checkPermission('users-manage'); + $this->checkPermission(Permission::SettingsManage); + $this->checkPermission(Permission::UsersManage); $query = Activity::query()->with(['user']); diff --git a/app/Activity/Controllers/AuditLogController.php b/app/Activity/Controllers/AuditLogController.php index 66ca30197..c4f9b91ed 100644 --- a/app/Activity/Controllers/AuditLogController.php +++ b/app/Activity/Controllers/AuditLogController.php @@ -5,6 +5,7 @@ namespace BookStack\Activity\Controllers; use BookStack\Activity\ActivityType; use BookStack\Activity\Models\Activity; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\Sorting\SortUrl; use BookStack\Util\SimpleListOptions; use Illuminate\Http\Request; @@ -13,8 +14,8 @@ class AuditLogController extends Controller { public function index(Request $request) { - $this->checkPermission('settings-manage'); - $this->checkPermission('users-manage'); + $this->checkPermission(Permission::SettingsManage); + $this->checkPermission(Permission::UsersManage); $sort = $request->get('sort', 'activity_date'); $order = $request->get('order', 'desc'); diff --git a/app/Activity/Controllers/CommentController.php b/app/Activity/Controllers/CommentController.php index 479d57c4d..fd5463dff 100644 --- a/app/Activity/Controllers/CommentController.php +++ b/app/Activity/Controllers/CommentController.php @@ -7,6 +7,7 @@ use BookStack\Activity\Tools\CommentTree; use BookStack\Activity\Tools\CommentTreeNode; use BookStack\Entities\Queries\PageQueries; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use Illuminate\Http\Request; use Illuminate\Validation\ValidationException; @@ -42,7 +43,7 @@ class CommentController extends Controller } // Create a new comment. - $this->checkPermission('comment-create-all'); + $this->checkPermission(Permission::CommentCreateAll); $contentRef = $input['content_ref'] ?? ''; $comment = $this->commentRepo->create($page, $input['html'], $input['parent_id'] ?? null, $contentRef); @@ -64,8 +65,8 @@ class CommentController extends Controller ]); $comment = $this->commentRepo->getById($commentId); - $this->checkOwnablePermission('page-view', $comment->entity); - $this->checkOwnablePermission('comment-update', $comment); + $this->checkOwnablePermission(Permission::PageView, $comment->entity); + $this->checkOwnablePermission(Permission::CommentUpdate, $comment); $comment = $this->commentRepo->update($comment, $input['html']); @@ -81,8 +82,8 @@ class CommentController extends Controller public function archive(int $id) { $comment = $this->commentRepo->getById($id); - $this->checkOwnablePermission('page-view', $comment->entity); - if (!userCan('comment-update', $comment) && !userCan('comment-delete', $comment)) { + $this->checkOwnablePermission(Permission::PageView, $comment->entity); + if (!userCan(Permission::CommentUpdate, $comment) && !userCan(Permission::CommentDelete, $comment)) { $this->showPermissionError(); } @@ -101,8 +102,8 @@ class CommentController extends Controller public function unarchive(int $id) { $comment = $this->commentRepo->getById($id); - $this->checkOwnablePermission('page-view', $comment->entity); - if (!userCan('comment-update', $comment) && !userCan('comment-delete', $comment)) { + $this->checkOwnablePermission(Permission::PageView, $comment->entity); + if (!userCan(Permission::CommentUpdate, $comment) && !userCan(Permission::CommentDelete, $comment)) { $this->showPermissionError(); } @@ -121,7 +122,7 @@ class CommentController extends Controller public function destroy(int $id) { $comment = $this->commentRepo->getById($id); - $this->checkOwnablePermission('comment-delete', $comment); + $this->checkOwnablePermission(Permission::CommentDelete, $comment); $this->commentRepo->delete($comment); diff --git a/app/Activity/Controllers/WatchController.php b/app/Activity/Controllers/WatchController.php index 5df75da39..b77a893ea 100644 --- a/app/Activity/Controllers/WatchController.php +++ b/app/Activity/Controllers/WatchController.php @@ -5,13 +5,14 @@ namespace BookStack\Activity\Controllers; use BookStack\Activity\Tools\UserEntityWatchOptions; use BookStack\Entities\Tools\MixedEntityRequestHelper; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use Illuminate\Http\Request; class WatchController extends Controller { public function update(Request $request, MixedEntityRequestHelper $entityHelper) { - $this->checkPermission('receive-notifications'); + $this->checkPermission(Permission::ReceiveNotifications); $this->preventGuestAccess(); $requestData = $this->validate($request, array_merge([ diff --git a/app/Activity/Notifications/Handlers/BaseNotificationHandler.php b/app/Activity/Notifications/Handlers/BaseNotificationHandler.php index 3a9b0c1dc..bc6f2e22f 100644 --- a/app/Activity/Notifications/Handlers/BaseNotificationHandler.php +++ b/app/Activity/Notifications/Handlers/BaseNotificationHandler.php @@ -5,6 +5,7 @@ namespace BookStack\Activity\Notifications\Handlers; use BookStack\Activity\Models\Loggable; use BookStack\Activity\Notifications\Messages\BaseActivityNotification; use BookStack\Entities\Models\Entity; +use BookStack\Permissions\Permission; use BookStack\Permissions\PermissionApplicator; use BookStack\Users\Models\User; use Illuminate\Support\Facades\Log; @@ -26,7 +27,7 @@ abstract class BaseNotificationHandler implements NotificationHandler } // Prevent sending of the user does not have notification permissions - if (!$user->can('receive-notifications')) { + if (!$user->can(Permission::ReceiveNotifications)) { continue; } diff --git a/app/Activity/Tools/UserEntityWatchOptions.php b/app/Activity/Tools/UserEntityWatchOptions.php index 559d7903d..8e5f70758 100644 --- a/app/Activity/Tools/UserEntityWatchOptions.php +++ b/app/Activity/Tools/UserEntityWatchOptions.php @@ -7,6 +7,7 @@ use BookStack\Activity\WatchLevels; use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; +use BookStack\Permissions\Permission; use BookStack\Users\Models\User; use Illuminate\Database\Eloquent\Builder; @@ -22,7 +23,7 @@ class UserEntityWatchOptions public function canWatch(): bool { - return $this->user->can('receive-notifications') && !$this->user->isGuest(); + return $this->user->can(Permission::ReceiveNotifications) && !$this->user->isGuest(); } public function getWatchLevel(): string diff --git a/app/Api/ApiTokenGuard.php b/app/Api/ApiTokenGuard.php index 6302884a9..9f4537b29 100644 --- a/app/Api/ApiTokenGuard.php +++ b/app/Api/ApiTokenGuard.php @@ -4,6 +4,7 @@ namespace BookStack\Api; use BookStack\Access\LoginService; use BookStack\Exceptions\ApiAuthException; +use BookStack\Permissions\Permission; use Illuminate\Auth\GuardHelpers; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\Guard; @@ -146,7 +147,7 @@ class ApiTokenGuard implements Guard throw new ApiAuthException(trans('errors.api_user_token_expired'), 403); } - if (!$token->user->can('access-api')) { + if (!$token->user->can(Permission::AccessApi)) { throw new ApiAuthException(trans('errors.api_user_no_api_permission'), 403); } } diff --git a/app/Api/UserApiTokenController.php b/app/Api/UserApiTokenController.php index 3606e8260..2ca9e2235 100644 --- a/app/Api/UserApiTokenController.php +++ b/app/Api/UserApiTokenController.php @@ -4,6 +4,7 @@ namespace BookStack\Api; use BookStack\Activity\ActivityType; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\Users\Models\User; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; @@ -16,8 +17,8 @@ class UserApiTokenController extends Controller */ public function create(Request $request, int $userId) { - $this->checkPermission('access-api'); - $this->checkPermissionOrCurrentUser('users-manage', $userId); + $this->checkPermission(Permission::AccessApi); + $this->checkPermissionOrCurrentUser(Permission::UsersManage, $userId); $this->updateContext($request); $user = User::query()->findOrFail($userId); @@ -35,8 +36,8 @@ class UserApiTokenController extends Controller */ public function store(Request $request, int $userId) { - $this->checkPermission('access-api'); - $this->checkPermissionOrCurrentUser('users-manage', $userId); + $this->checkPermission(Permission::AccessApi); + $this->checkPermissionOrCurrentUser(Permission::UsersManage, $userId); $this->validate($request, [ 'name' => ['required', 'max:250'], @@ -143,8 +144,8 @@ class UserApiTokenController extends Controller */ protected function checkPermissionAndFetchUserToken(int $userId, int $tokenId): array { - $this->checkPermissionOr('users-manage', function () use ($userId) { - return $userId === user()->id && userCan('access-api'); + $this->checkPermissionOr(Permission::UsersManage, function () use ($userId) { + return $userId === user()->id && userCan(Permission::AccessApi); }); $user = User::query()->findOrFail($userId); diff --git a/app/App/helpers.php b/app/App/helpers.php index 0e26d9974..0e357e36a 100644 --- a/app/App/helpers.php +++ b/app/App/helpers.php @@ -56,7 +56,7 @@ function userCan(string|Permission $permission, ?Model $ownable = null): bool * Check if the current user can perform the given action on any items in the system. * Can be provided the class name of an entity to filter ability to that specific entity type. */ -function userCanOnAny(string $action, string $entityClass = ''): bool +function userCanOnAny(string|Permission $action, string $entityClass = ''): bool { $permissions = app()->make(PermissionApplicator::class); diff --git a/app/Entities/Controllers/BookApiController.php b/app/Entities/Controllers/BookApiController.php index a617ee2da..5baea163f 100644 --- a/app/Entities/Controllers/BookApiController.php +++ b/app/Entities/Controllers/BookApiController.php @@ -11,6 +11,7 @@ use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Tools\BookContents; use BookStack\Http\ApiController; +use BookStack\Permissions\Permission; use Illuminate\Http\Request; use Illuminate\Validation\ValidationException; @@ -47,7 +48,7 @@ class BookApiController extends ApiController */ public function create(Request $request) { - $this->checkPermission('book-create-all'); + $this->checkPermission(Permission::BookCreateAll); $requestData = $this->validate($request, $this->rules()['create']); $book = $this->bookRepo->create($requestData); @@ -92,7 +93,7 @@ class BookApiController extends ApiController public function update(Request $request, string $id) { $book = $this->queries->findVisibleByIdOrFail(intval($id)); - $this->checkOwnablePermission('book-update', $book); + $this->checkOwnablePermission(Permission::BookUpdate, $book); $requestData = $this->validate($request, $this->rules()['update']); $book = $this->bookRepo->update($book, $requestData); @@ -109,7 +110,7 @@ class BookApiController extends ApiController public function delete(string $id) { $book = $this->queries->findVisibleByIdOrFail(intval($id)); - $this->checkOwnablePermission('book-delete', $book); + $this->checkOwnablePermission(Permission::BookDelete, $book); $this->bookRepo->destroy($book); diff --git a/app/Entities/Controllers/BookController.php b/app/Entities/Controllers/BookController.php index 5d3d67f64..cbf7ffb79 100644 --- a/app/Entities/Controllers/BookController.php +++ b/app/Entities/Controllers/BookController.php @@ -17,6 +17,7 @@ use BookStack\Exceptions\ImageUploadException; use BookStack\Exceptions\NotFoundException; use BookStack\Facades\Activity; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\References\ReferenceFetcher; use BookStack\Util\DatabaseTransaction; use BookStack\Util\SimpleListOptions; @@ -73,12 +74,12 @@ class BookController extends Controller */ public function create(?string $shelfSlug = null) { - $this->checkPermission('book-create-all'); + $this->checkPermission(Permission::BookCreateAll); $bookshelf = null; if ($shelfSlug !== null) { $bookshelf = $this->shelfQueries->findVisibleBySlugOrFail($shelfSlug); - $this->checkOwnablePermission('bookshelf-update', $bookshelf); + $this->checkOwnablePermission(Permission::BookshelfUpdate, $bookshelf); } $this->setPageTitle(trans('entities.books_create')); @@ -96,7 +97,7 @@ class BookController extends Controller */ public function store(Request $request, ?string $shelfSlug = null) { - $this->checkPermission('book-create-all'); + $this->checkPermission(Permission::BookCreateAll); $validated = $this->validate($request, [ 'name' => ['required', 'string', 'max:255'], 'description_html' => ['string', 'max:2000'], @@ -108,7 +109,7 @@ class BookController extends Controller $bookshelf = null; if ($shelfSlug !== null) { $bookshelf = $this->shelfQueries->findVisibleBySlugOrFail($shelfSlug); - $this->checkOwnablePermission('bookshelf-update', $bookshelf); + $this->checkOwnablePermission(Permission::BookshelfUpdate, $bookshelf); } $book = $this->bookRepo->create($validated); @@ -154,7 +155,7 @@ class BookController extends Controller public function edit(string $slug) { $book = $this->queries->findVisibleBySlugOrFail($slug); - $this->checkOwnablePermission('book-update', $book); + $this->checkOwnablePermission(Permission::BookUpdate, $book); $this->setPageTitle(trans('entities.books_edit_named', ['bookName' => $book->getShortName()])); return view('books.edit', ['book' => $book, 'current' => $book]); @@ -170,7 +171,7 @@ class BookController extends Controller public function update(Request $request, string $slug) { $book = $this->queries->findVisibleBySlugOrFail($slug); - $this->checkOwnablePermission('book-update', $book); + $this->checkOwnablePermission(Permission::BookUpdate, $book); $validated = $this->validate($request, [ 'name' => ['required', 'string', 'max:255'], @@ -197,7 +198,7 @@ class BookController extends Controller public function showDelete(string $bookSlug) { $book = $this->queries->findVisibleBySlugOrFail($bookSlug); - $this->checkOwnablePermission('book-delete', $book); + $this->checkOwnablePermission(Permission::BookDelete, $book); $this->setPageTitle(trans('entities.books_delete_named', ['bookName' => $book->getShortName()])); return view('books.delete', ['book' => $book, 'current' => $book]); @@ -211,7 +212,7 @@ class BookController extends Controller public function destroy(string $bookSlug) { $book = $this->queries->findVisibleBySlugOrFail($bookSlug); - $this->checkOwnablePermission('book-delete', $book); + $this->checkOwnablePermission(Permission::BookDelete, $book); $this->bookRepo->destroy($book); @@ -226,7 +227,7 @@ class BookController extends Controller public function showCopy(string $bookSlug) { $book = $this->queries->findVisibleBySlugOrFail($bookSlug); - $this->checkOwnablePermission('book-view', $book); + $this->checkOwnablePermission(Permission::BookView, $book); session()->flashInput(['name' => $book->name]); @@ -243,8 +244,8 @@ class BookController extends Controller public function copy(Request $request, Cloner $cloner, string $bookSlug) { $book = $this->queries->findVisibleBySlugOrFail($bookSlug); - $this->checkOwnablePermission('book-view', $book); - $this->checkPermission('book-create-all'); + $this->checkOwnablePermission(Permission::BookView, $book); + $this->checkPermission(Permission::BookCreateAll); $newName = $request->get('name') ?: $book->name; $bookCopy = $cloner->cloneBook($book, $newName); @@ -259,10 +260,10 @@ class BookController extends Controller public function convertToShelf(HierarchyTransformer $transformer, string $bookSlug) { $book = $this->queries->findVisibleBySlugOrFail($bookSlug); - $this->checkOwnablePermission('book-update', $book); - $this->checkOwnablePermission('book-delete', $book); - $this->checkPermission('bookshelf-create-all'); - $this->checkPermission('book-create-all'); + $this->checkOwnablePermission(Permission::BookUpdate, $book); + $this->checkOwnablePermission(Permission::BookDelete, $book); + $this->checkPermission(Permission::BookshelfCreateAll); + $this->checkPermission(Permission::BookCreateAll); $shelf = (new DatabaseTransaction(function () use ($book, $transformer) { return $transformer->transformBookToShelf($book); diff --git a/app/Entities/Controllers/BookshelfApiController.php b/app/Entities/Controllers/BookshelfApiController.php index b512f2d05..f4bd394a9 100644 --- a/app/Entities/Controllers/BookshelfApiController.php +++ b/app/Entities/Controllers/BookshelfApiController.php @@ -6,6 +6,7 @@ use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Queries\BookshelfQueries; use BookStack\Entities\Repos\BookshelfRepo; use BookStack\Http\ApiController; +use BookStack\Permissions\Permission; use Exception; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Http\Request; @@ -45,7 +46,7 @@ class BookshelfApiController extends ApiController */ public function create(Request $request) { - $this->checkPermission('bookshelf-create-all'); + $this->checkPermission(Permission::BookshelfCreateAll); $requestData = $this->validate($request, $this->rules()['create']); $bookIds = $request->get('books', []); @@ -84,7 +85,7 @@ class BookshelfApiController extends ApiController public function update(Request $request, string $id) { $shelf = $this->queries->findVisibleByIdOrFail(intval($id)); - $this->checkOwnablePermission('bookshelf-update', $shelf); + $this->checkOwnablePermission(Permission::BookshelfUpdate, $shelf); $requestData = $this->validate($request, $this->rules()['update']); $bookIds = $request->get('books', null); @@ -103,7 +104,7 @@ class BookshelfApiController extends ApiController public function delete(string $id) { $shelf = $this->queries->findVisibleByIdOrFail(intval($id)); - $this->checkOwnablePermission('bookshelf-delete', $shelf); + $this->checkOwnablePermission(Permission::BookshelfDelete, $shelf); $this->bookshelfRepo->destroy($shelf); diff --git a/app/Entities/Controllers/BookshelfController.php b/app/Entities/Controllers/BookshelfController.php index 6cedd23e7..f47742ffa 100644 --- a/app/Entities/Controllers/BookshelfController.php +++ b/app/Entities/Controllers/BookshelfController.php @@ -11,6 +11,7 @@ use BookStack\Entities\Tools\ShelfContext; use BookStack\Exceptions\ImageUploadException; use BookStack\Exceptions\NotFoundException; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\References\ReferenceFetcher; use BookStack\Util\SimpleListOptions; use Exception; @@ -68,7 +69,7 @@ class BookshelfController extends Controller */ public function create() { - $this->checkPermission('bookshelf-create-all'); + $this->checkPermission(Permission::BookshelfCreateAll); $books = $this->bookQueries->visibleForList()->orderBy('name')->get(['name', 'id', 'slug', 'created_at', 'updated_at']); $this->setPageTitle(trans('entities.shelves_create')); @@ -83,7 +84,7 @@ class BookshelfController extends Controller */ public function store(Request $request) { - $this->checkPermission('bookshelf-create-all'); + $this->checkPermission(Permission::BookshelfCreateAll); $validated = $this->validate($request, [ 'name' => ['required', 'string', 'max:255'], 'description_html' => ['string', 'max:2000'], @@ -105,7 +106,7 @@ class BookshelfController extends Controller public function show(Request $request, ActivityQueries $activities, string $slug) { $shelf = $this->queries->findVisibleBySlugOrFail($slug); - $this->checkOwnablePermission('bookshelf-view', $shelf); + $this->checkOwnablePermission(Permission::BookshelfView, $shelf); $listOptions = SimpleListOptions::fromRequest($request, 'shelf_books')->withSortOptions([ 'default' => trans('common.sort_default'), @@ -143,7 +144,7 @@ class BookshelfController extends Controller public function edit(string $slug) { $shelf = $this->queries->findVisibleBySlugOrFail($slug); - $this->checkOwnablePermission('bookshelf-update', $shelf); + $this->checkOwnablePermission(Permission::BookshelfUpdate, $shelf); $shelfBookIds = $shelf->books()->get(['id'])->pluck('id'); $books = $this->bookQueries->visibleForList() @@ -169,7 +170,7 @@ class BookshelfController extends Controller public function update(Request $request, string $slug) { $shelf = $this->queries->findVisibleBySlugOrFail($slug); - $this->checkOwnablePermission('bookshelf-update', $shelf); + $this->checkOwnablePermission(Permission::BookshelfUpdate, $shelf); $validated = $this->validate($request, [ 'name' => ['required', 'string', 'max:255'], 'description_html' => ['string', 'max:2000'], @@ -195,7 +196,7 @@ class BookshelfController extends Controller public function showDelete(string $slug) { $shelf = $this->queries->findVisibleBySlugOrFail($slug); - $this->checkOwnablePermission('bookshelf-delete', $shelf); + $this->checkOwnablePermission(Permission::BookshelfDelete, $shelf); $this->setPageTitle(trans('entities.shelves_delete_named', ['name' => $shelf->getShortName()])); @@ -210,7 +211,7 @@ class BookshelfController extends Controller public function destroy(string $slug) { $shelf = $this->queries->findVisibleBySlugOrFail($slug); - $this->checkOwnablePermission('bookshelf-delete', $shelf); + $this->checkOwnablePermission(Permission::BookshelfDelete, $shelf); $this->shelfRepo->destroy($shelf); diff --git a/app/Entities/Controllers/ChapterApiController.php b/app/Entities/Controllers/ChapterApiController.php index 6ba2e9fd2..80eab7bb8 100644 --- a/app/Entities/Controllers/ChapterApiController.php +++ b/app/Entities/Controllers/ChapterApiController.php @@ -9,6 +9,7 @@ use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Repos\ChapterRepo; use BookStack\Exceptions\PermissionsException; use BookStack\Http\ApiController; +use BookStack\Permissions\Permission; use Exception; use Illuminate\Http\Request; @@ -65,7 +66,7 @@ class ChapterApiController extends ApiController $bookId = $request->get('book_id'); $book = $this->entityQueries->books->findVisibleByIdOrFail(intval($bookId)); - $this->checkOwnablePermission('chapter-create', $book); + $this->checkOwnablePermission(Permission::ChapterCreate, $book); $chapter = $this->chapterRepo->create($requestData, $book); @@ -101,10 +102,10 @@ class ChapterApiController extends ApiController { $requestData = $this->validate($request, $this->rules()['update']); $chapter = $this->queries->findVisibleByIdOrFail(intval($id)); - $this->checkOwnablePermission('chapter-update', $chapter); + $this->checkOwnablePermission(Permission::ChapterUpdate, $chapter); if ($request->has('book_id') && $chapter->book_id !== intval($requestData['book_id'])) { - $this->checkOwnablePermission('chapter-delete', $chapter); + $this->checkOwnablePermission(Permission::ChapterDelete, $chapter); try { $this->chapterRepo->move($chapter, "book:{$requestData['book_id']}"); @@ -129,7 +130,7 @@ class ChapterApiController extends ApiController public function delete(string $id) { $chapter = $this->queries->findVisibleByIdOrFail(intval($id)); - $this->checkOwnablePermission('chapter-delete', $chapter); + $this->checkOwnablePermission(Permission::ChapterDelete, $chapter); $this->chapterRepo->destroy($chapter); diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 677745500..9335e0a70 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -17,6 +17,7 @@ use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\NotifyException; use BookStack\Exceptions\PermissionsException; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\References\ReferenceFetcher; use BookStack\Util\DatabaseTransaction; use Illuminate\Http\Request; @@ -39,7 +40,7 @@ class ChapterController extends Controller public function create(string $bookSlug) { $book = $this->entityQueries->books->findVisibleBySlugOrFail($bookSlug); - $this->checkOwnablePermission('chapter-create', $book); + $this->checkOwnablePermission(Permission::ChapterCreate, $book); $this->setPageTitle(trans('entities.chapters_create')); @@ -64,7 +65,7 @@ class ChapterController extends Controller ]); $book = $this->entityQueries->books->findVisibleBySlugOrFail($bookSlug); - $this->checkOwnablePermission('chapter-create', $book); + $this->checkOwnablePermission(Permission::ChapterCreate, $book); $chapter = $this->chapterRepo->create($validated, $book); @@ -77,7 +78,6 @@ class ChapterController extends Controller public function show(string $bookSlug, string $chapterSlug) { $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); - $this->checkOwnablePermission('chapter-view', $chapter); $sidebarTree = (new BookContents($chapter->book))->getTree(); $pages = $this->entityQueries->pages->visibleForChapterList($chapter->id)->get(); @@ -106,7 +106,7 @@ class ChapterController extends Controller public function edit(string $bookSlug, string $chapterSlug) { $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); - $this->checkOwnablePermission('chapter-update', $chapter); + $this->checkOwnablePermission(Permission::ChapterUpdate, $chapter); $this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()])); @@ -128,7 +128,7 @@ class ChapterController extends Controller ]); $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); - $this->checkOwnablePermission('chapter-update', $chapter); + $this->checkOwnablePermission(Permission::ChapterUpdate, $chapter); $this->chapterRepo->update($chapter, $validated); @@ -143,7 +143,7 @@ class ChapterController extends Controller public function showDelete(string $bookSlug, string $chapterSlug) { $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); - $this->checkOwnablePermission('chapter-delete', $chapter); + $this->checkOwnablePermission(Permission::ChapterDelete, $chapter); $this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()])); @@ -159,7 +159,7 @@ class ChapterController extends Controller public function destroy(string $bookSlug, string $chapterSlug) { $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); - $this->checkOwnablePermission('chapter-delete', $chapter); + $this->checkOwnablePermission(Permission::ChapterDelete, $chapter); $this->chapterRepo->destroy($chapter); @@ -175,8 +175,8 @@ class ChapterController extends Controller { $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()])); - $this->checkOwnablePermission('chapter-update', $chapter); - $this->checkOwnablePermission('chapter-delete', $chapter); + $this->checkOwnablePermission(Permission::ChapterUpdate, $chapter); + $this->checkOwnablePermission(Permission::ChapterDelete, $chapter); return view('chapters.move', [ 'chapter' => $chapter, @@ -192,8 +192,8 @@ class ChapterController extends Controller public function move(Request $request, string $bookSlug, string $chapterSlug) { $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); - $this->checkOwnablePermission('chapter-update', $chapter); - $this->checkOwnablePermission('chapter-delete', $chapter); + $this->checkOwnablePermission(Permission::ChapterUpdate, $chapter); + $this->checkOwnablePermission(Permission::ChapterDelete, $chapter); $entitySelection = $request->get('entity_selection', null); if ($entitySelection === null || $entitySelection === '') { @@ -221,7 +221,6 @@ class ChapterController extends Controller public function showCopy(string $bookSlug, string $chapterSlug) { $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); - $this->checkOwnablePermission('chapter-view', $chapter); session()->flashInput(['name' => $chapter->name]); @@ -240,7 +239,6 @@ class ChapterController extends Controller public function copy(Request $request, Cloner $cloner, string $bookSlug, string $chapterSlug) { $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); - $this->checkOwnablePermission('chapter-view', $chapter); $entitySelection = $request->get('entity_selection') ?: null; $newParentBook = $entitySelection ? $this->entityQueries->findVisibleByStringIdentifier($entitySelection) : $chapter->getParent(); @@ -251,7 +249,7 @@ class ChapterController extends Controller return redirect($chapter->getUrl('/copy')); } - $this->checkOwnablePermission('chapter-create', $newParentBook); + $this->checkOwnablePermission(Permission::ChapterCreate, $newParentBook); $newName = $request->get('name') ?: $chapter->name; $chapterCopy = $cloner->cloneChapter($chapter, $newParentBook, $newName); @@ -266,9 +264,9 @@ class ChapterController extends Controller public function convertToBook(HierarchyTransformer $transformer, string $bookSlug, string $chapterSlug) { $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); - $this->checkOwnablePermission('chapter-update', $chapter); - $this->checkOwnablePermission('chapter-delete', $chapter); - $this->checkPermission('book-create-all'); + $this->checkOwnablePermission(Permission::ChapterUpdate, $chapter); + $this->checkOwnablePermission(Permission::ChapterDelete, $chapter); + $this->checkPermission(Permission::BookCreateAll); $book = (new DatabaseTransaction(function () use ($chapter, $transformer) { return $transformer->transformChapterToBook($chapter); diff --git a/app/Entities/Controllers/PageApiController.php b/app/Entities/Controllers/PageApiController.php index 8fcba3dc6..033c19a7a 100644 --- a/app/Entities/Controllers/PageApiController.php +++ b/app/Entities/Controllers/PageApiController.php @@ -7,6 +7,7 @@ use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Repos\PageRepo; use BookStack\Exceptions\PermissionsException; use BookStack\Http\ApiController; +use BookStack\Permissions\Permission; use Exception; use Illuminate\Http\Request; @@ -76,7 +77,7 @@ class PageApiController extends ApiController } else { $parent = $this->entityQueries->books->findVisibleByIdOrFail(intval($request->get('book_id'))); } - $this->checkOwnablePermission('page-create', $parent); + $this->checkOwnablePermission(Permission::PageCreate, $parent); $draft = $this->pageRepo->getNewDraftPage($parent); $this->pageRepo->publishDraft($draft, $request->only(array_keys($this->rules['create']))); @@ -116,7 +117,7 @@ class PageApiController extends ApiController $requestData = $this->validate($request, $this->rules['update']); $page = $this->queries->findVisibleByIdOrFail($id); - $this->checkOwnablePermission('page-update', $page); + $this->checkOwnablePermission(Permission::PageUpdate, $page); $parent = null; if ($request->has('chapter_id')) { @@ -126,7 +127,7 @@ class PageApiController extends ApiController } if ($parent && !$parent->matches($page->getParent())) { - $this->checkOwnablePermission('page-delete', $page); + $this->checkOwnablePermission(Permission::PageDelete, $page); try { $this->pageRepo->move($page, $parent->getType() . ':' . $parent->id); @@ -151,7 +152,7 @@ class PageApiController extends ApiController public function delete(string $id) { $page = $this->queries->findVisibleByIdOrFail($id); - $this->checkOwnablePermission('page-delete', $page); + $this->checkOwnablePermission(Permission::PageDelete, $page); $this->pageRepo->destroy($page); diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index de3aed7d9..d4bde300b 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -20,6 +20,7 @@ use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\NotifyException; use BookStack\Exceptions\PermissionsException; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\References\ReferenceFetcher; use Exception; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -50,7 +51,7 @@ class PageController extends Controller $parent = $this->entityQueries->books->findVisibleBySlugOrFail($bookSlug); } - $this->checkOwnablePermission('page-create', $parent); + $this->checkOwnablePermission(Permission::PageCreate, $parent); // Redirect to draft edit screen if signed in if ($this->isSignedIn()) { @@ -82,7 +83,7 @@ class PageController extends Controller $parent = $this->entityQueries->books->findVisibleBySlugOrFail($bookSlug); } - $this->checkOwnablePermission('page-create', $parent); + $this->checkOwnablePermission(Permission::PageCreate, $parent); $page = $this->pageRepo->getNewDraftPage($parent); $this->pageRepo->publishDraft($page, [ @@ -100,7 +101,7 @@ class PageController extends Controller public function editDraft(Request $request, string $bookSlug, int $pageId) { $draft = $this->queries->findVisibleByIdOrFail($pageId); - $this->checkOwnablePermission('page-create', $draft->getParent()); + $this->checkOwnablePermission(Permission::PageCreate, $draft->getParent()); $editorData = new PageEditorData($draft, $this->entityQueries, $request->query('editor', '')); $this->setPageTitle(trans('entities.pages_edit_draft')); @@ -120,7 +121,7 @@ class PageController extends Controller 'name' => ['required', 'string', 'max:255'], ]); $draftPage = $this->queries->findVisibleByIdOrFail($pageId); - $this->checkOwnablePermission('page-create', $draftPage->getParent()); + $this->checkOwnablePermission(Permission::PageCreate, $draftPage->getParent()); $page = $this->pageRepo->publishDraft($draftPage, $request->all()); @@ -148,8 +149,6 @@ class PageController extends Controller return redirect($page->getUrl()); } - $this->checkOwnablePermission('page-view', $page); - $pageContent = (new PageContent($page)); $page->html = $pageContent->render(); $pageNav = $pageContent->getNavigation($page->html); @@ -197,7 +196,7 @@ class PageController extends Controller public function edit(Request $request, string $bookSlug, string $pageSlug) { $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); - $this->checkOwnablePermission('page-update', $page, $page->getUrl()); + $this->checkOwnablePermission(Permission::PageUpdate, $page, $page->getUrl()); $editorData = new PageEditorData($page, $this->entityQueries, $request->query('editor', '')); if ($editorData->getWarnings()) { @@ -221,7 +220,7 @@ class PageController extends Controller 'name' => ['required', 'string', 'max:255'], ]); $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); - $this->checkOwnablePermission('page-update', $page); + $this->checkOwnablePermission(Permission::PageUpdate, $page); $this->pageRepo->update($page, $request->all()); @@ -236,7 +235,7 @@ class PageController extends Controller public function saveDraft(Request $request, int $pageId) { $page = $this->queries->findVisibleByIdOrFail($pageId); - $this->checkOwnablePermission('page-update', $page); + $this->checkOwnablePermission(Permission::PageUpdate, $page); if (!$this->isSignedIn()) { return $this->jsonError(trans('errors.guests_cannot_save_drafts'), 500); @@ -273,7 +272,7 @@ class PageController extends Controller public function showDelete(string $bookSlug, string $pageSlug) { $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); - $this->checkOwnablePermission('page-delete', $page); + $this->checkOwnablePermission(Permission::PageDelete, $page); $this->setPageTitle(trans('entities.pages_delete_named', ['pageName' => $page->getShortName()])); $usedAsTemplate = $this->entityQueries->books->start()->where('default_template_id', '=', $page->id)->count() > 0 || @@ -295,7 +294,7 @@ class PageController extends Controller public function showDeleteDraft(string $bookSlug, int $pageId) { $page = $this->queries->findVisibleByIdOrFail($pageId); - $this->checkOwnablePermission('page-update', $page); + $this->checkOwnablePermission(Permission::PageUpdate, $page); $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName' => $page->getShortName()])); $usedAsTemplate = $this->entityQueries->books->start()->where('default_template_id', '=', $page->id)->count() > 0 || @@ -318,7 +317,7 @@ class PageController extends Controller public function destroy(string $bookSlug, string $pageSlug) { $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); - $this->checkOwnablePermission('page-delete', $page); + $this->checkOwnablePermission(Permission::PageDelete, $page); $parent = $page->getParent(); $this->pageRepo->destroy($page); @@ -337,7 +336,7 @@ class PageController extends Controller $page = $this->queries->findVisibleByIdOrFail($pageId); $book = $page->book; $chapter = $page->chapter; - $this->checkOwnablePermission('page-update', $page); + $this->checkOwnablePermission(Permission::PageUpdate, $page); $this->pageRepo->destroy($page); @@ -384,8 +383,8 @@ class PageController extends Controller public function showMove(string $bookSlug, string $pageSlug) { $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); - $this->checkOwnablePermission('page-update', $page); - $this->checkOwnablePermission('page-delete', $page); + $this->checkOwnablePermission(Permission::PageUpdate, $page); + $this->checkOwnablePermission(Permission::PageDelete, $page); return view('pages.move', [ 'book' => $page->book, @@ -402,8 +401,8 @@ class PageController extends Controller public function move(Request $request, string $bookSlug, string $pageSlug) { $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); - $this->checkOwnablePermission('page-update', $page); - $this->checkOwnablePermission('page-delete', $page); + $this->checkOwnablePermission(Permission::PageUpdate, $page); + $this->checkOwnablePermission(Permission::PageDelete, $page); $entitySelection = $request->get('entity_selection', null); if ($entitySelection === null || $entitySelection === '') { @@ -431,7 +430,6 @@ class PageController extends Controller public function showCopy(string $bookSlug, string $pageSlug) { $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); - $this->checkOwnablePermission('page-view', $page); session()->flashInput(['name' => $page->name]); return view('pages.copy', [ @@ -449,7 +447,7 @@ class PageController extends Controller public function copy(Request $request, Cloner $cloner, string $bookSlug, string $pageSlug) { $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); - $this->checkOwnablePermission('page-view', $page); + $this->checkOwnablePermission(Permission::PageView, $page); $entitySelection = $request->get('entity_selection') ?: null; $newParent = $entitySelection ? $this->entityQueries->findVisibleByStringIdentifier($entitySelection) : $page->getParent(); @@ -460,7 +458,7 @@ class PageController extends Controller return redirect($page->getUrl('/copy')); } - $this->checkOwnablePermission('page-create', $newParent); + $this->checkOwnablePermission(Permission::PageCreate, $newParent); $newName = $request->get('name') ?: $page->name; $pageCopy = $cloner->clonePage($page, $newParent, $newName); diff --git a/app/Entities/Controllers/PageRevisionController.php b/app/Entities/Controllers/PageRevisionController.php index c43eea08b..35f1e8daf 100644 --- a/app/Entities/Controllers/PageRevisionController.php +++ b/app/Entities/Controllers/PageRevisionController.php @@ -11,6 +11,7 @@ use BookStack\Entities\Tools\PageContent; use BookStack\Exceptions\NotFoundException; use BookStack\Facades\Activity; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\Util\SimpleListOptions; use Illuminate\Http\Request; use Ssddanbrown\HtmlDiff\Diff; @@ -124,7 +125,7 @@ class PageRevisionController extends Controller public function restore(string $bookSlug, string $pageSlug, int $revisionId) { $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); - $this->checkOwnablePermission('page-update', $page); + $this->checkOwnablePermission(Permission::PageUpdate, $page); $page = $this->pageRepo->restoreRevision($page, $revisionId); @@ -139,7 +140,7 @@ class PageRevisionController extends Controller public function destroy(string $bookSlug, string $pageSlug, int $revId) { $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); - $this->checkOwnablePermission('page-delete', $page); + $this->checkOwnablePermission(Permission::PageDelete, $page); $revision = $page->revisions()->where('id', '=', $revId)->first(); if ($revision === null) { diff --git a/app/Entities/Controllers/RecycleBinApiController.php b/app/Entities/Controllers/RecycleBinApiController.php index 89bd3f41a..614685136 100644 --- a/app/Entities/Controllers/RecycleBinApiController.php +++ b/app/Entities/Controllers/RecycleBinApiController.php @@ -9,6 +9,7 @@ use BookStack\Entities\Models\Deletion; use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\DeletionRepo; use BookStack\Http\ApiController; +use BookStack\Permissions\Permission; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -17,8 +18,8 @@ class RecycleBinApiController extends ApiController public function __construct() { $this->middleware(function ($request, $next) { - $this->checkPermission('settings-manage'); - $this->checkPermission('restrictions-manage-all'); + $this->checkPermission(Permission::SettingsManage); + $this->checkPermission(Permission::RestrictionsManageAll); return $next($request); }); diff --git a/app/Entities/Controllers/RecycleBinController.php b/app/Entities/Controllers/RecycleBinController.php index d11dde4dd..f3c2b6a01 100644 --- a/app/Entities/Controllers/RecycleBinController.php +++ b/app/Entities/Controllers/RecycleBinController.php @@ -8,6 +8,7 @@ use BookStack\Entities\Models\Entity; use BookStack\Entities\Repos\DeletionRepo; use BookStack\Entities\Tools\TrashCan; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; class RecycleBinController extends Controller { @@ -20,8 +21,8 @@ class RecycleBinController extends Controller public function __construct() { $this->middleware(function ($request, $next) { - $this->checkPermission('settings-manage'); - $this->checkPermission('restrictions-manage-all'); + $this->checkPermission(Permission::SettingsManage); + $this->checkPermission(Permission::RestrictionsManageAll); return $next($request); }); diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index d2f5de65c..4b1d77db7 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -7,6 +7,7 @@ use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Tools\Markdown\MarkdownToHtml; use BookStack\Exceptions\ImageUploadException; use BookStack\Facades\Theme; +use BookStack\Permissions\Permission; use BookStack\Theming\ThemeEvents; use BookStack\Uploads\ImageRepo; use BookStack\Uploads\ImageService; @@ -122,7 +123,7 @@ class PageContent $imageInfo = $this->parseBase64ImageUri($uri); // Validate user has permission to create images - if (!$updater->can('image-create-all')) { + if (!$updater->can(Permission::ImageCreateAll)) { return ''; } diff --git a/app/Http/Middleware/ApiAuthenticate.php b/app/Http/Middleware/ApiAuthenticate.php index 5f3ad3168..15b5a325a 100644 --- a/app/Http/Middleware/ApiAuthenticate.php +++ b/app/Http/Middleware/ApiAuthenticate.php @@ -3,6 +3,7 @@ namespace BookStack\Http\Middleware; use BookStack\Exceptions\ApiAuthException; +use BookStack\Permissions\Permission; use Closure; use Illuminate\Http\Request; @@ -51,7 +52,7 @@ class ApiAuthenticate */ protected function sessionUserHasApiAccess(): bool { - $hasApiPermission = user()->can('access-api'); + $hasApiPermission = user()->can(Permission::AccessApi); return $hasApiPermission && user()->hasAppAccess(); } diff --git a/app/Http/Middleware/CheckUserHasPermission.php b/app/Http/Middleware/CheckUserHasPermission.php index b5678e734..ea4ff652a 100644 --- a/app/Http/Middleware/CheckUserHasPermission.php +++ b/app/Http/Middleware/CheckUserHasPermission.php @@ -2,6 +2,7 @@ namespace BookStack\Http\Middleware; +use BookStack\Permissions\Permission; use Closure; use Illuminate\Http\Request; @@ -10,13 +11,9 @@ class CheckUserHasPermission /** * Handle an incoming request. * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @param string $permission - * * @return mixed */ - public function handle($request, Closure $next, $permission) + public function handle(Request $request, Closure $next, string|Permission $permission) { if (!user()->can($permission)) { return $this->errorResponse($request); diff --git a/app/Permissions/ContentPermissionApiController.php b/app/Permissions/ContentPermissionApiController.php index bddbc2c7d..0720cc478 100644 --- a/app/Permissions/ContentPermissionApiController.php +++ b/app/Permissions/ContentPermissionApiController.php @@ -51,7 +51,7 @@ class ContentPermissionApiController extends ApiController $entity = $this->entities->get($contentType) ->newQuery()->scopes(['visible'])->findOrFail($contentId); - $this->checkOwnablePermission('restrictions-manage', $entity); + $this->checkOwnablePermission(Permission::RestrictionsManage, $entity); return response()->json($this->formattedPermissionDataForEntity($entity)); } @@ -71,7 +71,7 @@ class ContentPermissionApiController extends ApiController $entity = $this->entities->get($contentType) ->newQuery()->scopes(['visible'])->findOrFail($contentId); - $this->checkOwnablePermission('restrictions-manage', $entity); + $this->checkOwnablePermission(Permission::RestrictionsManage, $entity); $data = $this->validate($request, $this->rules()['update']); $this->permissionsUpdater->updateFromApiRequestData($entity, $data); diff --git a/app/Permissions/Permission.php b/app/Permissions/Permission.php index 539bbe808..492ca2621 100644 --- a/app/Permissions/Permission.php +++ b/app/Permissions/Permission.php @@ -10,6 +10,9 @@ namespace BookStack\Permissions; * We use and still allow the string values in usage to allow for compatibility with scenarios where * users have customised their instance with additional permissions via the theme system. * This enum primarily exists for alignment within the codebase. + * + * Permissions with all/own suffixes may also be represented as a higher-level alias without the own/all + * suffix, which are used and assessed in the permission system logic. */ enum Permission: string { @@ -26,6 +29,7 @@ enum Permission: string case ContentImport = 'content-import'; case EditorChange = 'editor-change'; case ReceiveNotifications = 'receive-notifications'; + case RestrictionsManage = 'restrictions-manage'; case RestrictionsManageAll = 'restrictions-manage-all'; case RestrictionsManageOwn = 'restrictions-manage-own'; case SettingsManage = 'settings-manage'; @@ -33,33 +37,46 @@ enum Permission: string case UserRolesManage = 'user-roles-manage'; case UsersManage = 'users-manage'; - // Entity permissions - // Each has 'all' or 'own' in it's RolePermission, with the base non-suffix name being used - // in actual checking logic, with the permission system handling the assessment of the underlying RolePermission. + // Non-entity content permissions case AttachmentCreate = 'attachment-create'; case AttachmentCreateAll = 'attachment-create-all'; case AttachmentCreateOwn = 'attachment-create-own'; - case AttachmentDelete = 'attachment-delete'; case AttachmentDeleteAll = 'attachment-delete-all'; case AttachmentDeleteOwn = 'attachment-delete-own'; - case AttachmentUpdate = 'attachment-update'; case AttachmentUpdateAll = 'attachment-update-all'; case AttachmentUpdateOwn = 'attachment-update-own'; + case CommentCreate = 'comment-create'; + case CommentCreateAll = 'comment-create-all'; + case CommentCreateOwn = 'comment-create-own'; + case CommentDelete = 'comment-delete'; + case CommentDeleteAll = 'comment-delete-all'; + case CommentDeleteOwn = 'comment-delete-own'; + case CommentUpdate = 'comment-update'; + case CommentUpdateAll = 'comment-update-all'; + case CommentUpdateOwn = 'comment-update-own'; + + case ImageCreateAll = 'image-create-all'; + case ImageCreateOwn = 'image-create-own'; + case ImageDelete = 'image-delete'; + case ImageDeleteAll = 'image-delete-all'; + case ImageDeleteOwn = 'image-delete-own'; + case ImageUpdate = 'image-update'; + case ImageUpdateAll = 'image-update-all'; + case ImageUpdateOwn = 'image-update-own'; + + // Entity content permissions case BookCreate = 'book-create'; case BookCreateAll = 'book-create-all'; case BookCreateOwn = 'book-create-own'; - case BookDelete = 'book-delete'; case BookDeleteAll = 'book-delete-all'; case BookDeleteOwn = 'book-delete-own'; - case BookUpdate = 'book-update'; case BookUpdateAll = 'book-update-all'; case BookUpdateOwn = 'book-update-own'; - case BookView = 'book-view'; case BookViewAll = 'book-view-all'; case BookViewOwn = 'book-view-own'; @@ -67,15 +84,12 @@ enum Permission: string case BookshelfCreate = 'bookshelf-create'; case BookshelfCreateAll = 'bookshelf-create-all'; case BookshelfCreateOwn = 'bookshelf-create-own'; - case BookshelfDelete = 'bookshelf-delete'; case BookshelfDeleteAll = 'bookshelf-delete-all'; case BookshelfDeleteOwn = 'bookshelf-delete-own'; - case BookshelfUpdate = 'bookshelf-update'; case BookshelfUpdateAll = 'bookshelf-update-all'; case BookshelfUpdateOwn = 'bookshelf-update-own'; - case BookshelfView = 'bookshelf-view'; case BookshelfViewAll = 'bookshelf-view-all'; case BookshelfViewOwn = 'bookshelf-view-own'; @@ -83,55 +97,25 @@ enum Permission: string case ChapterCreate = 'chapter-create'; case ChapterCreateAll = 'chapter-create-all'; case ChapterCreateOwn = 'chapter-create-own'; - case ChapterDelete = 'chapter-delete'; case ChapterDeleteAll = 'chapter-delete-all'; case ChapterDeleteOwn = 'chapter-delete-own'; - case ChapterUpdate = 'chapter-update'; case ChapterUpdateAll = 'chapter-update-all'; case ChapterUpdateOwn = 'chapter-update-own'; - case ChapterView = 'chapter-view'; case ChapterViewAll = 'chapter-view-all'; case ChapterViewOwn = 'chapter-view-own'; - case CommentCreate = 'comment-create'; - case CommentCreateAll = 'comment-create-all'; - case CommentCreateOwn = 'comment-create-own'; - - case CommentDelete = 'comment-delete'; - case CommentDeleteAll = 'comment-delete-all'; - case CommentDeleteOwn = 'comment-delete-own'; - - case CommentUpdate = 'comment-update'; - case CommentUpdateAll = 'comment-update-all'; - case CommentUpdateOwn = 'comment-update-own'; - - case ImageCreate = 'image-create'; - case ImageCreateAll = 'image-create-all'; - case ImageCreateOwn = 'image-create-own'; - - case ImageDelete = 'image-delete'; - case ImageDeleteAll = 'image-delete-all'; - case ImageDeleteOwn = 'image-delete-own'; - - case ImageUpdate = 'image-update'; - case ImageUpdateAll = 'image-update-all'; - case ImageUpdateOwn = 'image-update-own'; - case PageCreate = 'page-create'; case PageCreateAll = 'page-create-all'; case PageCreateOwn = 'page-create-own'; - case PageDelete = 'page-delete'; case PageDeleteAll = 'page-delete-all'; case PageDeleteOwn = 'page-delete-own'; - case PageUpdate = 'page-update'; case PageUpdateAll = 'page-update-all'; case PageUpdateOwn = 'page-update-own'; - case PageView = 'page-view'; case PageViewAll = 'page-view-all'; case PageViewOwn = 'page-view-own'; diff --git a/app/Permissions/PermissionsController.php b/app/Permissions/PermissionsController.php index 9dcfe242e..d93d9e718 100644 --- a/app/Permissions/PermissionsController.php +++ b/app/Permissions/PermissionsController.php @@ -24,7 +24,7 @@ class PermissionsController extends Controller public function showForPage(string $bookSlug, string $pageSlug) { $page = $this->queries->pages->findVisibleBySlugsOrFail($bookSlug, $pageSlug); - $this->checkOwnablePermission('restrictions-manage', $page); + $this->checkOwnablePermission(Permission::RestrictionsManage, $page); $this->setPageTitle(trans('entities.pages_permissions')); return view('pages.permissions', [ @@ -39,7 +39,7 @@ class PermissionsController extends Controller public function updateForPage(Request $request, string $bookSlug, string $pageSlug) { $page = $this->queries->pages->findVisibleBySlugsOrFail($bookSlug, $pageSlug); - $this->checkOwnablePermission('restrictions-manage', $page); + $this->checkOwnablePermission(Permission::RestrictionsManage, $page); (new DatabaseTransaction(function () use ($page, $request) { $this->permissionsUpdater->updateFromPermissionsForm($page, $request); @@ -56,7 +56,7 @@ class PermissionsController extends Controller public function showForChapter(string $bookSlug, string $chapterSlug) { $chapter = $this->queries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); - $this->checkOwnablePermission('restrictions-manage', $chapter); + $this->checkOwnablePermission(Permission::RestrictionsManage, $chapter); $this->setPageTitle(trans('entities.chapters_permissions')); return view('chapters.permissions', [ @@ -71,7 +71,7 @@ class PermissionsController extends Controller public function updateForChapter(Request $request, string $bookSlug, string $chapterSlug) { $chapter = $this->queries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); - $this->checkOwnablePermission('restrictions-manage', $chapter); + $this->checkOwnablePermission(Permission::RestrictionsManage, $chapter); (new DatabaseTransaction(function () use ($chapter, $request) { $this->permissionsUpdater->updateFromPermissionsForm($chapter, $request); @@ -88,7 +88,7 @@ class PermissionsController extends Controller public function showForBook(string $slug) { $book = $this->queries->books->findVisibleBySlugOrFail($slug); - $this->checkOwnablePermission('restrictions-manage', $book); + $this->checkOwnablePermission(Permission::RestrictionsManage, $book); $this->setPageTitle(trans('entities.books_permissions')); return view('books.permissions', [ @@ -103,7 +103,7 @@ class PermissionsController extends Controller public function updateForBook(Request $request, string $slug) { $book = $this->queries->books->findVisibleBySlugOrFail($slug); - $this->checkOwnablePermission('restrictions-manage', $book); + $this->checkOwnablePermission(Permission::RestrictionsManage, $book); (new DatabaseTransaction(function () use ($book, $request) { $this->permissionsUpdater->updateFromPermissionsForm($book, $request); @@ -120,7 +120,7 @@ class PermissionsController extends Controller public function showForShelf(string $slug) { $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug); - $this->checkOwnablePermission('restrictions-manage', $shelf); + $this->checkOwnablePermission(Permission::RestrictionsManage, $shelf); $this->setPageTitle(trans('entities.shelves_permissions')); return view('shelves.permissions', [ @@ -135,7 +135,7 @@ class PermissionsController extends Controller public function updateForShelf(Request $request, string $slug) { $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug); - $this->checkOwnablePermission('restrictions-manage', $shelf); + $this->checkOwnablePermission(Permission::RestrictionsManage, $shelf); (new DatabaseTransaction(function () use ($shelf, $request) { $this->permissionsUpdater->updateFromPermissionsForm($shelf, $request); @@ -152,7 +152,7 @@ class PermissionsController extends Controller public function copyShelfPermissionsToBooks(string $slug) { $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug); - $this->checkOwnablePermission('restrictions-manage', $shelf); + $this->checkOwnablePermission(Permission::RestrictionsManage, $shelf); $updateCount = (new DatabaseTransaction(function () use ($shelf) { return $this->permissionsUpdater->updateBookPermissionsFromShelf($shelf); @@ -168,7 +168,7 @@ class PermissionsController extends Controller */ public function formRowForRole(string $entityType, string $roleId) { - $this->checkPermissionOr('restrictions-manage-all', fn() => userCan('restrictions-manage-own')); + $this->checkPermissionOr(Permission::RestrictionsManageAll, fn() => userCan(Permission::RestrictionsManageOwn)); $role = Role::query()->findOrFail($roleId); diff --git a/app/Settings/MaintenanceController.php b/app/Settings/MaintenanceController.php index ac9dd20cc..b2b2226bf 100644 --- a/app/Settings/MaintenanceController.php +++ b/app/Settings/MaintenanceController.php @@ -6,6 +6,7 @@ use BookStack\Activity\ActivityType; use BookStack\App\AppVersion; use BookStack\Entities\Tools\TrashCan; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\References\ReferenceStore; use BookStack\Uploads\ImageService; use Illuminate\Http\Request; @@ -17,7 +18,7 @@ class MaintenanceController extends Controller */ public function index(TrashCan $trashCan) { - $this->checkPermission('settings-manage'); + $this->checkPermission(Permission::SettingsManage); $this->setPageTitle(trans('settings.maint')); // Recycle bin details @@ -34,7 +35,7 @@ class MaintenanceController extends Controller */ public function cleanupImages(Request $request, ImageService $imageService) { - $this->checkPermission('settings-manage'); + $this->checkPermission(Permission::SettingsManage); $this->logActivity(ActivityType::MAINTENANCE_ACTION_RUN, 'cleanup-images'); $checkRevisions = !($request->get('ignore_revisions', 'false') === 'true'); @@ -62,7 +63,7 @@ class MaintenanceController extends Controller */ public function sendTestEmail() { - $this->checkPermission('settings-manage'); + $this->checkPermission(Permission::SettingsManage); $this->logActivity(ActivityType::MAINTENANCE_ACTION_RUN, 'send-test-email'); try { @@ -81,7 +82,7 @@ class MaintenanceController extends Controller */ public function regenerateReferences(ReferenceStore $referenceStore) { - $this->checkPermission('settings-manage'); + $this->checkPermission(Permission::SettingsManage); $this->logActivity(ActivityType::MAINTENANCE_ACTION_RUN, 'regenerate-references'); try { diff --git a/app/Settings/SettingController.php b/app/Settings/SettingController.php index 3b7ba74d5..f89bc6dab 100644 --- a/app/Settings/SettingController.php +++ b/app/Settings/SettingController.php @@ -5,6 +5,7 @@ namespace BookStack\Settings; use BookStack\Activity\ActivityType; use BookStack\App\AppVersion; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\Users\Models\User; use Illuminate\Http\Request; @@ -24,7 +25,7 @@ class SettingController extends Controller public function category(string $category) { $this->ensureCategoryExists($category); - $this->checkPermission('settings-manage'); + $this->checkPermission(Permission::SettingsManage); $this->setPageTitle(trans('settings.settings')); return view('settings.categories.' . $category, [ @@ -41,7 +42,7 @@ class SettingController extends Controller { $this->ensureCategoryExists($category); $this->preventAccessInDemoMode(); - $this->checkPermission('settings-manage'); + $this->checkPermission(Permission::SettingsManage); $this->validate($request, [ 'app_logo' => ['nullable', ...$this->getImageValidationRules()], 'app_icon' => ['nullable', ...$this->getImageValidationRules()], diff --git a/app/Sorting/BookSortController.php b/app/Sorting/BookSortController.php index d70d0e656..7e2ee5465 100644 --- a/app/Sorting/BookSortController.php +++ b/app/Sorting/BookSortController.php @@ -7,6 +7,7 @@ use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Tools\BookContents; use BookStack\Facades\Activity; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\Util\DatabaseTransaction; use Illuminate\Http\Request; @@ -23,7 +24,7 @@ class BookSortController extends Controller public function show(string $bookSlug) { $book = $this->queries->findVisibleBySlugOrFail($bookSlug); - $this->checkOwnablePermission('book-update', $book); + $this->checkOwnablePermission(Permission::BookUpdate, $book); $bookChildren = (new BookContents($book))->getTree(false); @@ -51,7 +52,7 @@ class BookSortController extends Controller public function update(Request $request, BookSorter $sorter, string $bookSlug) { $book = $this->queries->findVisibleBySlugOrFail($bookSlug); - $this->checkOwnablePermission('book-update', $book); + $this->checkOwnablePermission(Permission::BookUpdate, $book); $loggedActivityForBook = false; // Sort via map diff --git a/app/Uploads/Controllers/AttachmentApiController.php b/app/Uploads/Controllers/AttachmentApiController.php index 87e00257c..b47d6ff8d 100644 --- a/app/Uploads/Controllers/AttachmentApiController.php +++ b/app/Uploads/Controllers/AttachmentApiController.php @@ -5,6 +5,7 @@ namespace BookStack\Uploads\Controllers; use BookStack\Entities\Queries\PageQueries; use BookStack\Exceptions\FileUploadException; use BookStack\Http\ApiController; +use BookStack\Permissions\Permission; use BookStack\Uploads\Attachment; use BookStack\Uploads\AttachmentService; use Exception; @@ -45,12 +46,12 @@ class AttachmentApiController extends ApiController */ public function create(Request $request) { - $this->checkPermission('attachment-create-all'); + $this->checkPermission(Permission::AttachmentCreateAll); $requestData = $this->validate($request, $this->rules()['create']); $pageId = $request->get('uploaded_to'); $page = $this->pageQueries->findVisibleByIdOrFail($pageId); - $this->checkOwnablePermission('page-update', $page); + $this->checkOwnablePermission(Permission::PageUpdate, $page); if ($request->hasFile('file')) { $uploadedFile = $request->file('file'); @@ -137,9 +138,9 @@ class AttachmentApiController extends ApiController $attachment->uploaded_to = $requestData['uploaded_to']; } - $this->checkOwnablePermission('page-view', $page); - $this->checkOwnablePermission('page-update', $page); - $this->checkOwnablePermission('attachment-update', $attachment); + $this->checkOwnablePermission(Permission::PageView, $page); + $this->checkOwnablePermission(Permission::PageUpdate, $page); + $this->checkOwnablePermission(Permission::AttachmentUpdate, $attachment); if ($request->hasFile('file')) { $uploadedFile = $request->file('file'); @@ -160,7 +161,7 @@ class AttachmentApiController extends ApiController { /** @var Attachment $attachment */ $attachment = Attachment::visible()->findOrFail($id); - $this->checkOwnablePermission('attachment-delete', $attachment); + $this->checkOwnablePermission(Permission::AttachmentDelete, $attachment); $this->attachmentService->deleteFile($attachment); diff --git a/app/Uploads/Controllers/AttachmentController.php b/app/Uploads/Controllers/AttachmentController.php index 809cdfa58..0886193e4 100644 --- a/app/Uploads/Controllers/AttachmentController.php +++ b/app/Uploads/Controllers/AttachmentController.php @@ -7,6 +7,7 @@ use BookStack\Entities\Repos\PageRepo; use BookStack\Exceptions\FileUploadException; use BookStack\Exceptions\NotFoundException; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\Uploads\Attachment; use BookStack\Uploads\AttachmentService; use Exception; @@ -40,8 +41,8 @@ class AttachmentController extends Controller $pageId = $request->get('uploaded_to'); $page = $this->pageQueries->findVisibleByIdOrFail($pageId); - $this->checkPermission('attachment-create-all'); - $this->checkOwnablePermission('page-update', $page); + $this->checkPermission(Permission::AttachmentCreateAll); + $this->checkOwnablePermission(Permission::PageUpdate, $page); $uploadedFile = $request->file('file'); @@ -67,9 +68,9 @@ class AttachmentController extends Controller /** @var Attachment $attachment */ $attachment = Attachment::query()->findOrFail($attachmentId); - $this->checkOwnablePermission('view', $attachment->page); - $this->checkOwnablePermission('page-update', $attachment->page); - $this->checkOwnablePermission('attachment-create', $attachment); + $this->checkOwnablePermission(Permission::PageView, $attachment->page); + $this->checkOwnablePermission(Permission::PageUpdate, $attachment->page); + $this->checkOwnablePermission(Permission::AttachmentUpdate, $attachment); $uploadedFile = $request->file('file'); @@ -90,8 +91,8 @@ class AttachmentController extends Controller /** @var Attachment $attachment */ $attachment = Attachment::query()->findOrFail($attachmentId); - $this->checkOwnablePermission('page-update', $attachment->page); - $this->checkOwnablePermission('attachment-create', $attachment); + $this->checkOwnablePermission(Permission::PageUpdate, $attachment->page); + $this->checkOwnablePermission(Permission::AttachmentCreate, $attachment); return view('attachments.manager-edit-form', [ 'attachment' => $attachment, @@ -118,9 +119,9 @@ class AttachmentController extends Controller ]), 422); } - $this->checkOwnablePermission('page-view', $attachment->page); - $this->checkOwnablePermission('page-update', $attachment->page); - $this->checkOwnablePermission('attachment-update', $attachment); + $this->checkOwnablePermission(Permission::PageView, $attachment->page); + $this->checkOwnablePermission(Permission::PageUpdate, $attachment->page); + $this->checkOwnablePermission(Permission::AttachmentUpdate, $attachment); $attachment = $this->attachmentService->updateFile($attachment, [ 'name' => $request->get('attachment_edit_name'), @@ -156,8 +157,8 @@ class AttachmentController extends Controller $page = $this->pageQueries->findVisibleByIdOrFail($pageId); - $this->checkPermission('attachment-create-all'); - $this->checkOwnablePermission('page-update', $page); + $this->checkPermission(Permission::AttachmentCreateAll); + $this->checkOwnablePermission(Permission::PageUpdate, $page); $attachmentName = $request->get('attachment_link_name'); $link = $request->get('attachment_link_url'); @@ -176,7 +177,6 @@ class AttachmentController extends Controller public function listForPage(int $pageId) { $page = $this->pageQueries->findVisibleByIdOrFail($pageId); - $this->checkOwnablePermission('page-view', $page); return view('attachments.manager-list', [ 'attachments' => $page->attachments->all(), @@ -195,7 +195,7 @@ class AttachmentController extends Controller 'order' => ['required', 'array'], ]); $page = $this->pageQueries->findVisibleByIdOrFail($pageId); - $this->checkOwnablePermission('page-update', $page); + $this->checkOwnablePermission(Permission::PageUpdate, $page); $attachmentOrder = $request->get('order'); $this->attachmentService->updateFileOrderWithinPage($attachmentOrder, $pageId); @@ -220,7 +220,7 @@ class AttachmentController extends Controller throw new NotFoundException(trans('errors.attachment_not_found')); } - $this->checkOwnablePermission('page-view', $page); + $this->checkOwnablePermission(Permission::PageView, $page); if ($attachment->external) { return redirect($attachment->path); @@ -246,7 +246,7 @@ class AttachmentController extends Controller { /** @var Attachment $attachment */ $attachment = Attachment::query()->findOrFail($attachmentId); - $this->checkOwnablePermission('attachment-delete', $attachment); + $this->checkOwnablePermission(Permission::AttachmentDelete, $attachment); $this->attachmentService->deleteFile($attachment); return response()->json(['message' => trans('entities.attachments_deleted')]); diff --git a/app/Uploads/Controllers/DrawioImageController.php b/app/Uploads/Controllers/DrawioImageController.php index 6293da4f7..aff27b3b1 100644 --- a/app/Uploads/Controllers/DrawioImageController.php +++ b/app/Uploads/Controllers/DrawioImageController.php @@ -4,6 +4,7 @@ namespace BookStack\Uploads\Controllers; use BookStack\Exceptions\ImageUploadException; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\Uploads\ImageRepo; use BookStack\Uploads\ImageResizer; use BookStack\Util\OutOfMemoryHandler; @@ -57,7 +58,7 @@ class DrawioImageController extends Controller 'uploaded_to' => ['required', 'integer'], ]); - $this->checkPermission('image-create-all'); + $this->checkPermission(Permission::ImageCreateAll); $imageBase64Data = $request->get('image'); try { diff --git a/app/Uploads/Controllers/GalleryImageController.php b/app/Uploads/Controllers/GalleryImageController.php index 1bc9da2d7..745efcde8 100644 --- a/app/Uploads/Controllers/GalleryImageController.php +++ b/app/Uploads/Controllers/GalleryImageController.php @@ -4,6 +4,7 @@ namespace BookStack\Uploads\Controllers; use BookStack\Exceptions\ImageUploadException; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\Uploads\ImageRepo; use BookStack\Uploads\ImageResizer; use BookStack\Util\OutOfMemoryHandler; @@ -52,7 +53,7 @@ class GalleryImageController extends Controller */ public function create(Request $request) { - $this->checkPermission('image-create-all'); + $this->checkPermission(Permission::ImageCreateAll); try { $this->validate($request, [ diff --git a/app/Uploads/Controllers/ImageController.php b/app/Uploads/Controllers/ImageController.php index c68ffdf6b..da67639c1 100644 --- a/app/Uploads/Controllers/ImageController.php +++ b/app/Uploads/Controllers/ImageController.php @@ -6,6 +6,7 @@ use BookStack\Exceptions\ImageUploadException; use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\NotifyException; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\Uploads\Image; use BookStack\Uploads\ImageRepo; use BookStack\Uploads\ImageResizer; @@ -50,7 +51,7 @@ class ImageController extends Controller $image = $this->imageRepo->getById($id); $this->checkImagePermission($image); - $this->checkOwnablePermission('image-update', $image); + $this->checkOwnablePermission(Permission::ImageUpdate, $image); $image = $this->imageRepo->updateImageDetails($image, $data); @@ -71,7 +72,7 @@ class ImageController extends Controller $image = $this->imageRepo->getById($id); $this->checkImagePermission($image); - $this->checkOwnablePermission('image-update', $image); + $this->checkOwnablePermission(Permission::ImageUpdate, $image); $file = $request->file('file'); new OutOfMemoryHandler(function () { @@ -125,7 +126,7 @@ class ImageController extends Controller public function destroy(string $id) { $image = $this->imageRepo->getById($id); - $this->checkOwnablePermission('image-delete', $image); + $this->checkOwnablePermission(Permission::ImageDelete, $image); $this->checkImagePermission($image); $this->imageRepo->destroyImage($image); @@ -140,7 +141,7 @@ class ImageController extends Controller { $image = $this->imageRepo->getById($id); $this->checkImagePermission($image); - $this->checkOwnablePermission('image-update', $image); + $this->checkOwnablePermission(Permission::ImageUpdate, $image); new OutOfMemoryHandler(function () { return $this->jsonError(trans('errors.image_thumbnail_memory_limit')); @@ -163,7 +164,7 @@ class ImageController extends Controller $relatedPage = $image->getPage(); if ($relatedPage) { - $this->checkOwnablePermission('page-view', $relatedPage); + $this->checkOwnablePermission(Permission::PageView, $relatedPage); } } } diff --git a/app/Uploads/Controllers/ImageGalleryApiController.php b/app/Uploads/Controllers/ImageGalleryApiController.php index d2a750c45..59696dc9a 100644 --- a/app/Uploads/Controllers/ImageGalleryApiController.php +++ b/app/Uploads/Controllers/ImageGalleryApiController.php @@ -4,6 +4,7 @@ namespace BookStack\Uploads\Controllers; use BookStack\Entities\Queries\PageQueries; use BookStack\Http\ApiController; +use BookStack\Permissions\Permission; use BookStack\Uploads\Image; use BookStack\Uploads\ImageRepo; use BookStack\Uploads\ImageResizer; @@ -65,7 +66,7 @@ class ImageGalleryApiController extends ApiController */ public function create(Request $request) { - $this->checkPermission('image-create-all'); + $this->checkPermission(Permission::ImageCreateAll); $data = $this->validate($request, $this->rules()['create']); $page = $this->pageQueries->findVisibleByIdOrFail($data['uploaded_to']); @@ -102,8 +103,8 @@ class ImageGalleryApiController extends ApiController { $data = $this->validate($request, $this->rules()['update']); $image = $this->imageRepo->getById($id); - $this->checkOwnablePermission('page-view', $image->getPage()); - $this->checkOwnablePermission('image-update', $image); + $this->checkOwnablePermission(Permission::PageView, $image->getPage()); + $this->checkOwnablePermission(Permission::ImageUpdate, $image); $this->imageRepo->updateImageDetails($image, $data); if (isset($data['image'])) { @@ -121,8 +122,8 @@ class ImageGalleryApiController extends ApiController public function delete(string $id) { $image = $this->imageRepo->getById($id); - $this->checkOwnablePermission('page-view', $image->getPage()); - $this->checkOwnablePermission('image-delete', $image); + $this->checkOwnablePermission(Permission::PageView, $image->getPage()); + $this->checkOwnablePermission(Permission::ImageDelete, $image); $this->imageRepo->destroyImage($image); return response('', 204); diff --git a/app/Users/Controllers/RoleApiController.php b/app/Users/Controllers/RoleApiController.php index 6d87c9e27..93ecc549b 100644 --- a/app/Users/Controllers/RoleApiController.php +++ b/app/Users/Controllers/RoleApiController.php @@ -3,6 +3,7 @@ namespace BookStack\Users\Controllers; use BookStack\Http\ApiController; +use BookStack\Permissions\Permission; use BookStack\Permissions\PermissionsRepo; use BookStack\Users\Models\Role; use Illuminate\Http\Request; @@ -38,7 +39,7 @@ class RoleApiController extends ApiController ) { // Checks for all endpoints in this controller $this->middleware(function ($request, $next) { - $this->checkPermission('user-roles-manage'); + $this->checkPermission(Permission::UserRolesManage); return $next($request); }); diff --git a/app/Users/Controllers/RoleController.php b/app/Users/Controllers/RoleController.php index 0a7fdcc9b..549f6e0ac 100644 --- a/app/Users/Controllers/RoleController.php +++ b/app/Users/Controllers/RoleController.php @@ -4,6 +4,7 @@ namespace BookStack\Users\Controllers; use BookStack\Exceptions\PermissionsException; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\Permissions\PermissionsRepo; use BookStack\Users\Models\Role; use BookStack\Users\Queries\RolesAllPaginatedAndSorted; @@ -23,7 +24,7 @@ class RoleController extends Controller */ public function index(Request $request) { - $this->checkPermission('user-roles-manage'); + $this->checkPermission(Permission::UserRolesManage); $listOptions = SimpleListOptions::fromRequest($request, 'roles')->withSortOptions([ 'display_name' => trans('common.sort_name'), @@ -49,7 +50,7 @@ class RoleController extends Controller */ public function create(Request $request) { - $this->checkPermission('user-roles-manage'); + $this->checkPermission(Permission::UserRolesManage); /** @var ?Role $role */ $role = null; @@ -71,7 +72,7 @@ class RoleController extends Controller */ public function store(Request $request) { - $this->checkPermission('user-roles-manage'); + $this->checkPermission(Permission::UserRolesManage); $data = $this->validate($request, [ 'display_name' => ['required', 'min:3', 'max:180'], 'description' => ['max:180'], @@ -92,7 +93,7 @@ class RoleController extends Controller */ public function edit(string $id) { - $this->checkPermission('user-roles-manage'); + $this->checkPermission(Permission::UserRolesManage); $role = $this->permissionsRepo->getRoleById($id); $this->setPageTitle(trans('settings.role_edit')); @@ -105,7 +106,7 @@ class RoleController extends Controller */ public function update(Request $request, string $id) { - $this->checkPermission('user-roles-manage'); + $this->checkPermission(Permission::UserRolesManage); $data = $this->validate($request, [ 'display_name' => ['required', 'min:3', 'max:180'], 'description' => ['max:180'], @@ -127,7 +128,7 @@ class RoleController extends Controller */ public function showDelete(string $id) { - $this->checkPermission('user-roles-manage'); + $this->checkPermission(Permission::UserRolesManage); $role = $this->permissionsRepo->getRoleById($id); $roles = $this->permissionsRepo->getAllRolesExcept($role); $blankRole = $role->newInstance(['display_name' => trans('settings.role_delete_no_migration')]); @@ -146,7 +147,7 @@ class RoleController extends Controller */ public function delete(Request $request, string $id) { - $this->checkPermission('user-roles-manage'); + $this->checkPermission(Permission::UserRolesManage); try { $migrateRoleId = intval($request->get('migrate_role_id') ?: "0"); diff --git a/app/Users/Controllers/UserAccountController.php b/app/Users/Controllers/UserAccountController.php index 708a91e9d..54ca69c5d 100644 --- a/app/Users/Controllers/UserAccountController.php +++ b/app/Users/Controllers/UserAccountController.php @@ -4,6 +4,7 @@ namespace BookStack\Users\Controllers; use BookStack\Access\SocialDriverManager; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\Permissions\PermissionApplicator; use BookStack\Settings\UserNotificationPreferences; use BookStack\Settings\UserShortcutMap; @@ -122,7 +123,7 @@ class UserAccountController extends Controller */ public function showNotifications(PermissionApplicator $permissions) { - $this->checkPermission('receive-notifications'); + $this->checkPermission(Permission::ReceiveNotifications); $preferences = (new UserNotificationPreferences(user())); @@ -145,7 +146,7 @@ class UserAccountController extends Controller public function updateNotifications(Request $request) { $this->preventAccessInDemoMode(); - $this->checkPermission('receive-notifications'); + $this->checkPermission(Permission::ReceiveNotifications); $data = $this->validate($request, [ 'preferences' => ['required', 'array'], 'preferences.*' => ['required', 'string'], diff --git a/app/Users/Controllers/UserApiController.php b/app/Users/Controllers/UserApiController.php index 351893b03..1efc82500 100644 --- a/app/Users/Controllers/UserApiController.php +++ b/app/Users/Controllers/UserApiController.php @@ -4,9 +4,9 @@ namespace BookStack\Users\Controllers; use BookStack\Exceptions\UserUpdateException; use BookStack\Http\ApiController; +use BookStack\Permissions\Permission; use BookStack\Users\Models\User; use BookStack\Users\UserRepo; -use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; use Illuminate\Validation\Rules\Password; @@ -26,7 +26,7 @@ class UserApiController extends ApiController // Checks for all endpoints in this controller $this->middleware(function ($request, $next) { - $this->checkPermission('users-manage'); + $this->checkPermission(Permission::UsersManage); $this->preventAccessInDemoMode(); return $next($request); diff --git a/app/Users/Controllers/UserController.php b/app/Users/Controllers/UserController.php index c6e4326e9..494221b14 100644 --- a/app/Users/Controllers/UserController.php +++ b/app/Users/Controllers/UserController.php @@ -7,6 +7,7 @@ use BookStack\Access\UserInviteException; use BookStack\Exceptions\ImageUploadException; use BookStack\Exceptions\UserUpdateException; use BookStack\Http\Controller; +use BookStack\Permissions\Permission; use BookStack\Uploads\ImageRepo; use BookStack\Users\Models\Role; use BookStack\Users\Queries\UsersAllPaginatedAndSorted; @@ -32,7 +33,7 @@ class UserController extends Controller */ public function index(Request $request) { - $this->checkPermission('users-manage'); + $this->checkPermission(Permission::UsersManage); $listOptions = SimpleListOptions::fromRequest($request, 'users')->withSortOptions([ 'name' => trans('common.sort_name'), @@ -58,7 +59,7 @@ class UserController extends Controller */ public function create() { - $this->checkPermission('users-manage'); + $this->checkPermission(Permission::UsersManage); $authMethod = config('auth.method'); $roles = Role::query()->orderBy('display_name', 'asc')->get(); $this->setPageTitle(trans('settings.users_add_new')); @@ -73,7 +74,7 @@ class UserController extends Controller */ public function store(Request $request) { - $this->checkPermission('users-manage'); + $this->checkPermission(Permission::UsersManage); $authMethod = config('auth.method'); $sendInvite = ($request->get('send_invite', 'false') === 'true'); @@ -111,7 +112,7 @@ class UserController extends Controller */ public function edit(int $id, SocialDriverManager $socialDriverManager) { - $this->checkPermission('users-manage'); + $this->checkPermission(Permission::UsersManage); $user = $this->userRepo->getById($id); $user->load(['apiTokens', 'mfaValues']); @@ -141,7 +142,7 @@ class UserController extends Controller public function update(Request $request, int $id) { $this->preventAccessInDemoMode(); - $this->checkPermission('users-manage'); + $this->checkPermission(Permission::UsersManage); $validated = $this->validate($request, [ 'name' => ['min:1', 'max:100'], @@ -182,7 +183,7 @@ class UserController extends Controller */ public function delete(int $id) { - $this->checkPermission('users-manage'); + $this->checkPermission(Permission::UsersManage); $user = $this->userRepo->getById($id); $this->setPageTitle(trans('settings.users_delete_named', ['userName' => $user->name])); @@ -198,7 +199,7 @@ class UserController extends Controller public function destroy(Request $request, int $id) { $this->preventAccessInDemoMode(); - $this->checkPermission('users-manage'); + $this->checkPermission(Permission::UsersManage); $user = $this->userRepo->getById($id); $newOwnerId = intval($request->get('new_owner_id')) ?: null; diff --git a/resources/views/home/books.blade.php b/resources/views/home/books.blade.php index a2f2bf796..e0d2aeb1c 100644 --- a/resources/views/home/books.blade.php +++ b/resources/views/home/books.blade.php @@ -12,7 +12,7 @@
{{ trans('common.actions') }}