diff --git a/src/Auth/Permissions.php b/src/Auth/Permissions.php index 4a4d6840271..7d20b524f22 100644 --- a/src/Auth/Permissions.php +++ b/src/Auth/Permissions.php @@ -2,12 +2,34 @@ namespace Statamic\Auth; +use Facades\Statamic\Auth\CorePermissions; + class Permissions { + protected $extensions = []; protected $permissions = []; protected $groups = []; protected $pendingGroup = null; + public function boot() + { + $early = $this->permissions; + $this->permissions = []; + + CorePermissions::boot(); + + foreach ($this->extensions as $callback) { + $callback($this); + } + + $this->permissions = array_merge($this->permissions, $early); + } + + public function extend($callback) + { + $this->extensions[] = $callback; + } + public function make(string $value) { $permission = (new Permission)->value($value); diff --git a/src/CP/Utilities/CoreUtilities.php b/src/CP/Utilities/CoreUtilities.php index 432d9c601da..0c51043e372 100644 --- a/src/CP/Utilities/CoreUtilities.php +++ b/src/CP/Utilities/CoreUtilities.php @@ -16,7 +16,7 @@ class CoreUtilities { public static function boot() { - Utility::make('cache') + Utility::register('cache') ->action([CacheController::class, 'index']) ->title(__('Cache Manager')) ->icon('cache') @@ -26,28 +26,25 @@ public static function boot() ->routes(function ($router) { $router->post('cache/{cache}', [CacheController::class, 'clear'])->name('clear'); $router->post('cache/{cache}/warm', [CacheController::class, 'warm'])->name('warm'); - }) - ->register(); + }); - Utility::make('phpinfo') + Utility::register('phpinfo') ->action(PhpInfoController::class) ->title(__('PHP Info')) ->icon('php') ->description(__('statamic::messages.phpinfo_utility_description')) - ->docsUrl(Statamic::docsUrl('utilities/phpinfo')) - ->register(); + ->docsUrl(Statamic::docsUrl('utilities/phpinfo')); - Utility::make('search') + Utility::register('search') ->view('statamic::utilities.search') ->title(__('Search')) ->icon('search-utility') ->description(__('statamic::messages.search_utility_description')) ->routes(function ($router) { $router->post('/', [UpdateSearchController::class, 'update'])->name('update'); - }) - ->register(); + }); - Utility::make('email') + Utility::register('email') ->view('statamic::utilities.email') ->title(__('Email')) ->icon('email-utility') @@ -55,10 +52,9 @@ public static function boot() ->docsUrl(Statamic::docsUrl('utilities/email')) ->routes(function ($router) { $router->post('/', [EmailController::class, 'send']); - }) - ->register(); + }); - Utility::make('licensing') + Utility::register('licensing') ->action([LicensingController::class, 'show']) ->title(__('Licensing')) ->icon('licensing') @@ -66,11 +62,10 @@ public static function boot() ->docsUrl(Statamic::docsUrl('licensing')) ->routes(function ($router) { $router->get('refresh', [LicensingController::class, 'refresh'])->name('refresh'); - }) - ->register(); + }); if (config('statamic.git.enabled') && Statamic::pro()) { - Utility::make('git') + Utility::register('git') ->action([GitController::class, 'index']) ->title('Git') ->icon('git') @@ -78,8 +73,7 @@ public static function boot() ->docsUrl(Statamic::docsUrl('utilities/git')) ->routes(function ($router) { $router->post('/', [GitController::class, 'commit'])->name('commit'); - }) - ->register(); + }); } } } diff --git a/src/CP/Utilities/Utility.php b/src/CP/Utilities/Utility.php index 3224c3a9b87..795d6b4c400 100644 --- a/src/CP/Utilities/Utility.php +++ b/src/CP/Utilities/Utility.php @@ -104,6 +104,7 @@ public function routes(Closure $routes = null) return $this->fluentlyGetOrSet('routes')->args(func_get_args()); } + /** @deprecated */ public function register() { \Statamic\Facades\Utility::push($this); diff --git a/src/CP/Utilities/UtilityRepository.php b/src/CP/Utilities/UtilityRepository.php index 6aaad1c56cf..7818c0fcec3 100644 --- a/src/CP/Utilities/UtilityRepository.php +++ b/src/CP/Utilities/UtilityRepository.php @@ -2,27 +2,55 @@ namespace Statamic\CP\Utilities; +use Facades\Statamic\CP\Utilities\CoreUtilities; use Illuminate\Support\Facades\Route; use Statamic\Facades\User; class UtilityRepository { protected $utilities; + protected $extensions = []; public function __construct() { $this->utilities = collect([]); } + public function boot() + { + CoreUtilities::boot(); + + foreach ($this->extensions as $callback) { + $callback($this); + } + } + + public function extend($callback) + { + $this->extensions[] = $callback; + } + public function make($handle) { return (new Utility)->handle($handle); } - public function push(Utility $utility) + public function register($utility) { + if (! $utility instanceof Utility) { + $utility = $this->make($utility); + } + $this->utilities[$utility->handle()] = $utility; + return $utility; + } + + /** @deprecated */ + public function push(Utility $utility) + { + $this->register($utility); + return $this; } @@ -45,6 +73,8 @@ public function find($handle) public function routes() { + $this->boot(); + Route::namespace('\\')->prefix('utilities')->name('utilities.')->group(function () { $this->all()->each(function ($utility) { if ($utility->action()) { diff --git a/src/Facades/Utility.php b/src/Facades/Utility.php index 5fe75d770e6..2ecfd1bad41 100644 --- a/src/Facades/Utility.php +++ b/src/Facades/Utility.php @@ -7,7 +7,7 @@ /** * @method static mixed|null make($handle) - * @method static self push(Utility $utility) + * @method static \Statamic\CP\Utilities\Utility register($utility) * @method static mixed all() * @method static mixed authorized() * @method static mixed find($handle) diff --git a/src/Http/Middleware/CP/BootPermissions.php b/src/Http/Middleware/CP/BootPermissions.php new file mode 100644 index 00000000000..ee3d00c45bc --- /dev/null +++ b/src/Http/Middleware/CP/BootPermissions.php @@ -0,0 +1,16 @@ +pendingSection = $handle; - $permissions(); + $permissions($this); $this->pendingSection = null; } diff --git a/src/Providers/AuthServiceProvider.php b/src/Providers/AuthServiceProvider.php index 4d05eb238e4..1a8f3132d28 100755 --- a/src/Providers/AuthServiceProvider.php +++ b/src/Providers/AuthServiceProvider.php @@ -2,7 +2,6 @@ namespace Statamic\Providers; -use Facades\Statamic\Auth\CorePermissions; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Gate; use Illuminate\Support\ServiceProvider; @@ -90,10 +89,6 @@ public function boot() return optional(User::fromUser($user))->hasPermission($ability) === true ? true : null; }); - $this->app->booted(function () { - CorePermissions::boot(); - }); - foreach ($this->policies as $key => $policy) { Gate::policy($key, $policy); } diff --git a/src/Providers/CpServiceProvider.php b/src/Providers/CpServiceProvider.php index 7d9c11c92d6..b17afe4bfd4 100644 --- a/src/Providers/CpServiceProvider.php +++ b/src/Providers/CpServiceProvider.php @@ -6,7 +6,6 @@ use Illuminate\Support\Facades\Blade; use Illuminate\Support\Facades\View; use Illuminate\Support\ServiceProvider; -use Statamic\CP\Utilities\CoreUtilities; use Statamic\CP\Utilities\UtilityRepository; use Statamic\Extensions\Translation\Loader; use Statamic\Extensions\Translation\Translator; @@ -33,8 +32,6 @@ public function boot() View::composer(NavComposer::VIEWS, NavComposer::class); View::composer(CustomLogoComposer::VIEWS, CustomLogoComposer::class); - CoreUtilities::boot(); - Blade::directive('cp_svg', function ($expression) { return ""; }); @@ -83,7 +80,9 @@ protected function registerMiddlewareGroups() $router->middlewareGroup('statamic.cp.authenticated', [ \Statamic\Http\Middleware\CP\Authorize::class, \Statamic\Http\Middleware\CP\Localize::class, + \Statamic\Http\Middleware\CP\BootPermissions::class, \Statamic\Http\Middleware\CP\BootPreferences::class, + \Statamic\Http\Middleware\CP\BootUtilities::class, \Statamic\Http\Middleware\CP\CountUsers::class, \Statamic\Http\Middleware\DeleteTemporaryFileUploads::class, ]); diff --git a/tests/CP/Utilities/UtilityRepositoryTest.php b/tests/CP/Utilities/UtilityRepositoryTest.php new file mode 100644 index 00000000000..fdbbce1f70c --- /dev/null +++ b/tests/CP/Utilities/UtilityRepositoryTest.php @@ -0,0 +1,96 @@ +assertInstanceOf(Collection::class, $utilities->all()); + $this->assertCount(0, $utilities->all()); + + $utility = $utilities->make('one'); + $this->assertCount(0, $utilities->all()); + + $utilities->$registerMethod($utility); + $this->assertEquals(['one' => $utility], $utilities->all()->all()); + $this->assertEquals($utility, $utilities->find('one')); + } + + public function registerMethodProvider() + { + return [ + 'register' => ['register'], + 'push' => ['push'], // @deprecated + ]; + } + + /** @test */ + public function it_registers_a_utility_via_a_string() + { + $utilities = new UtilityRepository; + + $utility = $utilities->register('one'); + + $this->assertInstanceOf(Utility::class, $utility); + $this->assertEquals('one', $utility->handle()); + $this->assertCount(1, $utilities->all()); + $this->assertEquals(['one' => $utility], $utilities->all()->all()); + } + + /** @test */ + public function it_defers_registration_until_boot_using_extend_method() + { + $utilities = new UtilityRepository; + $callbackRan = false; + + $utilities->extend(function ($arg) use ($utilities, &$callbackRan) { + $this->assertEquals($utilities, $arg); + $callbackRan = true; + }); + + $this->assertFalse($callbackRan); + + $utilities->boot(); + + $this->assertTrue($callbackRan); + } + + /** @test */ + public function booting_more_than_once_just_updates_the_utilities() + { + // This makes sure that booting a second time doesn't duplicate + // any utilities. It should just update/replace the existing ones. + // We boot once early so that routes can get registered, and + // then again after the user's locale preference is set so + // that the translations for labels etc use the right language. + + $utilities = new UtilityRepository; + + $utilities->extend(function ($utilities) { + $utilities->register('test') + ->title(__('and')); // using a translation that will likely never change. + }); + + $utilities->boot(); + + $this->assertEquals(['and'], $utilities->all()->map->title()->values()->all()); + + app()->setLocale('fr'); + + $utilities->boot(); + + $this->assertEquals(['et'], $utilities->all()->map->title()->values()->all()); + } +} diff --git a/tests/Permissions/PermissionsTest.php b/tests/Permissions/PermissionsTest.php index 71c28e05a07..8d97acab786 100644 --- a/tests/Permissions/PermissionsTest.php +++ b/tests/Permissions/PermissionsTest.php @@ -99,6 +99,42 @@ public function any_permissions_registered_within_a_group_callback_will_belong_t $this->assertEquals('foo', $four->group()); } + /** @test */ + public function it_defers_registration_until_boot_using_extend_method() + { + $permissions = new Permissions; + $callbackRan = false; + + $permissions->extend(function ($arg) use ($permissions, &$callbackRan) { + $this->assertEquals($permissions, $arg); + $callbackRan = true; + }); + + $this->assertFalse($callbackRan); + + $permissions->boot(); + + $this->assertTrue($callbackRan); + } + + /** @test */ + public function it_places_any_permissions_registered_early_without_extend_callback_at_the_end() + { + $permissions = new Permissions; + $permissions->register('one'); + $permissions->register('two'); + + $permissions->extend(function ($preference) { + $preference->register('three'); + }); + + $permissions->boot(); + + $names = $permissions->all()->keys()->all(); + + $this->assertEquals(['three', 'one', 'two'], $names); + } + /** @test */ public function it_makes_a_tree() {