From a191e113d7c491d6e94207ba4cd4b15c87c02cfa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 17:15:02 +0000 Subject: [PATCH] deploy: cd72e0e0dd1470143acc796f34ef3a8913cba2d3 --- .cloveralls.yml | 6 - .gitignore | 15 - .nojekyll | 0 .travis.yml | 58 -- 404.html | 14 + CHANGELOG.md | 3 - LICENSE | 27 - README.md | 68 -- UPGRADE.md | 54 -- assets/css/styles.69f2a557.css | 1 + ...uards-9421d2ccbded4218039d3c55d9e07d64.png | Bin assets/js/01a85c17.8dd5da0a.js | 1 + assets/js/02def4a7.53c9a7f1.js | 1 + assets/js/0e384e19.3d824450.js | 1 + assets/js/17896441.ab15fc7f.js | 1 + assets/js/1c81fc78.f1f88d0c.js | 1 + assets/js/1f391b9e.a27a432b.js | 1 + assets/js/22465cd7.9e45649f.js | 1 + assets/js/237.7663bed8.js | 1 + assets/js/266b150c.c169f2f7.js | 1 + assets/js/378f56c0.f10e3e52.js | 1 + assets/js/393be207.8c825263.js | 1 + assets/js/3b8c55ea.95bbb53a.js | 1 + assets/js/4a900e2d.a37ef9b5.js | 1 + assets/js/4b8919da.4dc42bc8.js | 1 + assets/js/4e6224b1.3600afef.js | 1 + assets/js/533.11304ac5.js | 1 + assets/js/5b3ddbaa.579400ac.js | 1 + assets/js/5b72c13b.e8b2bfc0.js | 1 + assets/js/5e95c892.1dd55255.js | 1 + assets/js/6875c492.f1af0c91.js | 1 + assets/js/72e14192.a1fe92a6.js | 1 + assets/js/747.847fa25b.js | 1 + assets/js/809dac3e.133b40d5.js | 1 + assets/js/814f3328.d5988fa5.js | 1 + assets/js/8b5d4ccc.1ace6756.js | 1 + assets/js/935f2afb.6562d556.js | 1 + assets/js/93a10038.26f7b70f.js | 1 + assets/js/983b303c.20d2d832.js | 1 + assets/js/9e4087bc.bddafc5d.js | 1 + assets/js/a6185ec0.7643b318.js | 1 + assets/js/a6aa9e1f.aac55eda.js | 1 + assets/js/a7bd4aaa.fd305e17.js | 1 + assets/js/a94703ab.8b0ccadb.js | 1 + assets/js/b5cb822e.5f84e9da.js | 1 + assets/js/b9207088.c1f4a27e.js | 1 + assets/js/c4f5d8e4.00ce20a8.js | 1 + assets/js/ccc49370.c41daa2c.js | 1 + assets/js/d9e16301.5f01f94d.js | 1 + assets/js/e252ba15.a3bf887f.js | 1 + assets/js/ee696d42.cd7325d3.js | 1 + assets/js/f01051f8.63b05f50.js | 1 + assets/js/f6027d55.272e2251.js | 1 + assets/js/main.452d3fbd.js | 2 + assets/js/main.452d3fbd.js.LICENSE.txt | 64 ++ assets/js/runtime~main.705cea34.js | 1 + blog/archive/index.html | 14 + blog/atom.xml | 41 + blog/index.html | 16 + blog/new-documentation/index.html | 15 + blog/rss.xml | 35 + blog/tags/index.html | 14 + blog/tags/laminas/index.html | 16 + blog/tags/lmcrbacmvc/index.html | 15 + blog/tags/php/index.html | 16 + blog/welcome/index.html | 15 + composer.json | 76 -- config/lmc_rbac.global.php.dist | 132 --- config/module.config.php | 90 -- data/FlatRole.php.dist | 114 --- data/HierarchicalRole.php.dist | 146 ---- data/Permission.php.dist | 69 -- data/README.md | 14 - docs/01. Introduction.md | 57 -- docs/02. Quick Start.md | 139 ---- docs/03. Role providers.md | 200 ----- docs/04. Guards.md | 481 ----------- docs/05. Strategies.md | 155 ---- docs/06. Using the Authorization Service.md | 275 ------- docs/07. Cookbook.md | 779 ------------------ docs/README.md | 48 -- docs/cookbook/index.html | 149 ++++ docs/guards/index.html | 180 ++++ docs/images/workflow-without-guards.png | Bin 8905 -> 0 bytes docs/installation/index.html | 43 + docs/intro/index.html | 54 ++ docs/quick-start/index.html | 66 ++ docs/role-providers/index.html | 70 ++ docs/strategies/index.html | 55 ++ docs/support/index.html | 22 + .../index.html | 79 ++ img/LMC-logo.png | Bin 0 -> 3694 bytes img/LMC-social-card.jpg | Bin 0 -> 25971 bytes img/docusaurus.png | Bin 0 -> 5142 bytes img/favicon.ico | Bin 0 -> 5430 bytes index.html | 14 + markdown-page/index.html | 15 + phpunit.xml.dist | 11 - sitemap.xml | 1 + src/Assertion/AssertionInterface.php | 43 - src/Assertion/AssertionPluginManager.php | 64 -- src/Collector/RbacCollector.php | 251 ------ src/Exception/ExceptionInterface.php | 29 - src/Exception/InvalidArgumentException.php | 31 - src/Exception/RoleNotFoundException.php | 35 - src/Exception/RuntimeException.php | 31 - src/Exception/UnauthorizedException.php | 35 - .../UnauthorizedExceptionInterface.php | 29 - src/Factory/AssertionPluginManagerFactory.php | 56 -- .../AuthenticationIdentityProviderFactory.php | 57 -- .../AuthorizationServiceDelegatorFactory.php | 73 -- src/Factory/AuthorizationServiceFactory.php | 72 -- src/Factory/ControllerGuardFactory.php | 90 -- .../ControllerPermissionsGuardFactory.php | 90 -- src/Factory/GuardPluginManagerFactory.php | 58 -- src/Factory/GuardsFactory.php | 71 -- src/Factory/HasRoleViewHelperFactory.php | 57 -- src/Factory/IsGrantedPluginFactory.php | 57 -- src/Factory/IsGrantedViewHelperFactory.php | 57 -- src/Factory/ModuleOptionsFactory.php | 53 -- .../ObjectRepositoryRoleProviderFactory.php | 101 --- src/Factory/RbacFactory.php | 51 -- src/Factory/RedirectStrategyFactory.php | 59 -- .../RoleProviderPluginManagerFactory.php | 55 -- src/Factory/RoleServiceFactory.php | 85 -- src/Factory/RouteGuardFactory.php | 90 -- src/Factory/RoutePermissionsGuardFactory.php | 91 -- src/Factory/UnauthorizedStrategyFactory.php | 56 -- src/Guard/AbstractGuard.php | 80 -- src/Guard/ControllerGuard.php | 129 --- src/Guard/ControllerPermissionsGuard.php | 141 ---- src/Guard/GuardInterface.php | 61 -- src/Guard/GuardPluginManager.php | 81 -- src/Guard/ProtectionPolicyTrait.php | 54 -- src/Guard/RouteGuard.php | 109 --- src/Guard/RoutePermissionsGuard.php | 137 --- .../AuthenticationIdentityProvider.php | 53 -- src/Identity/IdentityInterface.php | 37 - src/Identity/IdentityProviderInterface.php | 35 - .../AuthorizationServiceInitializer.php | 58 -- src/Module.php | 61 -- src/Mvc/Controller/Plugin/IsGranted.php | 58 -- src/Options/ModuleOptions.php | 287 ------- src/Options/RedirectStrategyOptions.php | 148 ---- src/Options/UnauthorizedStrategyOptions.php | 53 -- src/Permission/PermissionInterface.php | 35 - src/Role/InMemoryRoleProvider.php | 121 --- src/Role/ObjectRepositoryRoleProvider.php | 100 --- src/Role/RoleProviderInterface.php | 41 - src/Role/RoleProviderPluginManager.php | 80 -- src/Service/AuthorizationService.php | 164 ---- .../AuthorizationServiceAwareInterface.php | 35 - .../AuthorizationServiceAwareTrait.php | 58 -- src/Service/AuthorizationServiceInterface.php | 39 - src/Service/RoleService.php | 229 ----- src/View/Helper/HasRole.php | 55 -- src/View/Helper/IsGranted.php | 58 -- src/View/Strategy/AbstractStrategy.php | 47 -- src/View/Strategy/RedirectStrategy.php | 107 --- src/View/Strategy/UnauthorizedStrategy.php | 84 -- tests/Asset/DummyGuard.php | 35 - tests/Asset/FlatRole.php | 89 -- tests/Asset/HierarchicalRole.php | 96 --- tests/Asset/MockRoleWithPermissionMethod.php | 22 - .../Asset/MockRoleWithPermissionProperty.php | 19 - .../MockRoleWithPermissionTraversable.php | 25 - tests/Asset/Permission.php | 83 -- tests/Asset/SimpleAssertion.php | 48 -- tests/Bootstrap.php | 45 - tests/Collector/RbacCollectorTest.php | 327 -------- .../AssertionPluginManagerFactoryTest.php | 58 -- ...henticationIdentityProviderFactoryTest.php | 42 - .../AuthorizationServiceDelegatorTest.php | 155 ---- .../AuthorizationServiceFactoryTest.php | 55 -- tests/Factory/ControllerGuardFactoryTest.php | 64 -- .../ControllerPermissionsGuardFactoryTest.php | 65 -- .../Factory/GuardPluginManagerFactoryTest.php | 44 - tests/Factory/GuardsFactoryTest.php | 98 --- .../Factory/HasRoleViewHelperFactoryTest.php | 48 -- tests/Factory/IsGrantedPluginFactoryTest.php | 47 -- .../IsGrantedViewHelperFactoryTest.php | 47 -- tests/Factory/ModuleOptionsFactoryTest.php | 41 - ...bjectRepositoryRoleProviderFactoryTest.php | 121 --- tests/Factory/RbacFactoryTest.php | 39 - tests/Factory/RedirectStrategyFactoryTest.php | 58 -- .../RoleProviderPluginManagerFactoryTest.php | 44 - tests/Factory/RoleServiceFactoryTest.php | 141 ---- tests/Factory/RouteGuardFactoryTest.php | 64 -- .../RoutePermissionsGuardFactoryTest.php | 64 -- .../UnauthorizedStrategyFactoryTest.php | 50 -- tests/Guard/AbstractGuardTest.php | 66 -- tests/Guard/ControllerGuardTest.php | 537 ------------ .../Guard/ControllerPermissionsGuardTest.php | 523 ------------ tests/Guard/GuardPluginManagerTest.php | 98 --- tests/Guard/ProtectionPolicyTraitTest.php | 35 - tests/Guard/RouteGuardTest.php | 477 ----------- tests/Guard/RoutePermissionsGuardTest.php | 452 ---------- .../AuthenticationIdentityProviderTest.php | 54 -- tests/Initializer/AuthorizationAwareFake.php | 32 - .../AuthorizationServiceInitializerTest.php | 52 -- tests/ModuleTest.php | 57 -- tests/Mvc/Controller/Plugin/IsGrantedTest.php | 41 - tests/Options/ModuleOptionsTest.php | 89 -- tests/Options/RedirectStrategyOptionsTest.php | 55 -- .../UnauthorizedStrategyOptionsTest.php | 43 - tests/Role/InMemoryRoleProviderTest.php | 82 -- .../Role/ObjectRepositoryRoleProviderTest.php | 173 ---- tests/Role/RoleProviderPluginManagerTest.php | 44 - .../AuthorizationServiceAwareTraitTest.php | 36 - tests/Service/AuthorizationServiceTest.php | 254 ------ tests/Service/RoleServiceTest.php | 311 ------- tests/TestConfiguration.php.dist | 33 - tests/Util/ServiceManagerFactory.php | 77 -- tests/View/Helper/HasRoleTest.php | 59 -- tests/View/Helper/IsGrantedTest.php | 55 -- tests/View/Strategy/RedirectStrategyTest.php | 204 ----- .../Strategy/UnauthorizedStrategyTest.php | 120 --- tests/testing.config.php | 51 -- view/error/403.phtml | 15 - .../toolbar/lmc-rbac.phtml | 102 --- 220 files changed, 1069 insertions(+), 14140 deletions(-) delete mode 100644 .cloveralls.yml delete mode 100644 .gitignore create mode 100644 .nojekyll delete mode 100644 .travis.yml create mode 100644 404.html delete mode 100644 CHANGELOG.md delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 UPGRADE.md create mode 100644 assets/css/styles.69f2a557.css rename docs/images/workflow-with-guards.png => assets/images/workflow-with-guards-9421d2ccbded4218039d3c55d9e07d64.png (100%) create mode 100644 assets/js/01a85c17.8dd5da0a.js create mode 100644 assets/js/02def4a7.53c9a7f1.js create mode 100644 assets/js/0e384e19.3d824450.js create mode 100644 assets/js/17896441.ab15fc7f.js create mode 100644 assets/js/1c81fc78.f1f88d0c.js create mode 100644 assets/js/1f391b9e.a27a432b.js create mode 100644 assets/js/22465cd7.9e45649f.js create mode 100644 assets/js/237.7663bed8.js create mode 100644 assets/js/266b150c.c169f2f7.js create mode 100644 assets/js/378f56c0.f10e3e52.js create mode 100644 assets/js/393be207.8c825263.js create mode 100644 assets/js/3b8c55ea.95bbb53a.js create mode 100644 assets/js/4a900e2d.a37ef9b5.js create mode 100644 assets/js/4b8919da.4dc42bc8.js create mode 100644 assets/js/4e6224b1.3600afef.js create mode 100644 assets/js/533.11304ac5.js create mode 100644 assets/js/5b3ddbaa.579400ac.js create mode 100644 assets/js/5b72c13b.e8b2bfc0.js create mode 100644 assets/js/5e95c892.1dd55255.js create mode 100644 assets/js/6875c492.f1af0c91.js create mode 100644 assets/js/72e14192.a1fe92a6.js create mode 100644 assets/js/747.847fa25b.js create mode 100644 assets/js/809dac3e.133b40d5.js create mode 100644 assets/js/814f3328.d5988fa5.js create mode 100644 assets/js/8b5d4ccc.1ace6756.js create mode 100644 assets/js/935f2afb.6562d556.js create mode 100644 assets/js/93a10038.26f7b70f.js create mode 100644 assets/js/983b303c.20d2d832.js create mode 100644 assets/js/9e4087bc.bddafc5d.js create mode 100644 assets/js/a6185ec0.7643b318.js create mode 100644 assets/js/a6aa9e1f.aac55eda.js create mode 100644 assets/js/a7bd4aaa.fd305e17.js create mode 100644 assets/js/a94703ab.8b0ccadb.js create mode 100644 assets/js/b5cb822e.5f84e9da.js create mode 100644 assets/js/b9207088.c1f4a27e.js create mode 100644 assets/js/c4f5d8e4.00ce20a8.js create mode 100644 assets/js/ccc49370.c41daa2c.js create mode 100644 assets/js/d9e16301.5f01f94d.js create mode 100644 assets/js/e252ba15.a3bf887f.js create mode 100644 assets/js/ee696d42.cd7325d3.js create mode 100644 assets/js/f01051f8.63b05f50.js create mode 100644 assets/js/f6027d55.272e2251.js create mode 100644 assets/js/main.452d3fbd.js create mode 100644 assets/js/main.452d3fbd.js.LICENSE.txt create mode 100644 assets/js/runtime~main.705cea34.js create mode 100644 blog/archive/index.html create mode 100644 blog/atom.xml create mode 100644 blog/index.html create mode 100644 blog/new-documentation/index.html create mode 100644 blog/rss.xml create mode 100644 blog/tags/index.html create mode 100644 blog/tags/laminas/index.html create mode 100644 blog/tags/lmcrbacmvc/index.html create mode 100644 blog/tags/php/index.html create mode 100644 blog/welcome/index.html delete mode 100644 composer.json delete mode 100644 config/lmc_rbac.global.php.dist delete mode 100644 config/module.config.php delete mode 100644 data/FlatRole.php.dist delete mode 100644 data/HierarchicalRole.php.dist delete mode 100644 data/Permission.php.dist delete mode 100644 data/README.md delete mode 100644 docs/01. Introduction.md delete mode 100644 docs/02. Quick Start.md delete mode 100644 docs/03. Role providers.md delete mode 100644 docs/04. Guards.md delete mode 100644 docs/05. Strategies.md delete mode 100644 docs/06. Using the Authorization Service.md delete mode 100644 docs/07. Cookbook.md delete mode 100644 docs/README.md create mode 100644 docs/cookbook/index.html create mode 100644 docs/guards/index.html delete mode 100644 docs/images/workflow-without-guards.png create mode 100644 docs/installation/index.html create mode 100644 docs/intro/index.html create mode 100644 docs/quick-start/index.html create mode 100644 docs/role-providers/index.html create mode 100644 docs/strategies/index.html create mode 100644 docs/support/index.html create mode 100644 docs/using-the-authorization-service/index.html create mode 100644 img/LMC-logo.png create mode 100644 img/LMC-social-card.jpg create mode 100644 img/docusaurus.png create mode 100644 img/favicon.ico create mode 100644 index.html create mode 100644 markdown-page/index.html delete mode 100644 phpunit.xml.dist create mode 100644 sitemap.xml delete mode 100644 src/Assertion/AssertionInterface.php delete mode 100644 src/Assertion/AssertionPluginManager.php delete mode 100644 src/Collector/RbacCollector.php delete mode 100644 src/Exception/ExceptionInterface.php delete mode 100644 src/Exception/InvalidArgumentException.php delete mode 100644 src/Exception/RoleNotFoundException.php delete mode 100644 src/Exception/RuntimeException.php delete mode 100644 src/Exception/UnauthorizedException.php delete mode 100644 src/Exception/UnauthorizedExceptionInterface.php delete mode 100644 src/Factory/AssertionPluginManagerFactory.php delete mode 100644 src/Factory/AuthenticationIdentityProviderFactory.php delete mode 100644 src/Factory/AuthorizationServiceDelegatorFactory.php delete mode 100644 src/Factory/AuthorizationServiceFactory.php delete mode 100644 src/Factory/ControllerGuardFactory.php delete mode 100644 src/Factory/ControllerPermissionsGuardFactory.php delete mode 100644 src/Factory/GuardPluginManagerFactory.php delete mode 100644 src/Factory/GuardsFactory.php delete mode 100644 src/Factory/HasRoleViewHelperFactory.php delete mode 100644 src/Factory/IsGrantedPluginFactory.php delete mode 100644 src/Factory/IsGrantedViewHelperFactory.php delete mode 100644 src/Factory/ModuleOptionsFactory.php delete mode 100644 src/Factory/ObjectRepositoryRoleProviderFactory.php delete mode 100644 src/Factory/RbacFactory.php delete mode 100644 src/Factory/RedirectStrategyFactory.php delete mode 100644 src/Factory/RoleProviderPluginManagerFactory.php delete mode 100644 src/Factory/RoleServiceFactory.php delete mode 100644 src/Factory/RouteGuardFactory.php delete mode 100644 src/Factory/RoutePermissionsGuardFactory.php delete mode 100644 src/Factory/UnauthorizedStrategyFactory.php delete mode 100644 src/Guard/AbstractGuard.php delete mode 100644 src/Guard/ControllerGuard.php delete mode 100644 src/Guard/ControllerPermissionsGuard.php delete mode 100644 src/Guard/GuardInterface.php delete mode 100644 src/Guard/GuardPluginManager.php delete mode 100644 src/Guard/ProtectionPolicyTrait.php delete mode 100644 src/Guard/RouteGuard.php delete mode 100644 src/Guard/RoutePermissionsGuard.php delete mode 100644 src/Identity/AuthenticationIdentityProvider.php delete mode 100644 src/Identity/IdentityInterface.php delete mode 100644 src/Identity/IdentityProviderInterface.php delete mode 100644 src/Initializer/AuthorizationServiceInitializer.php delete mode 100644 src/Module.php delete mode 100644 src/Mvc/Controller/Plugin/IsGranted.php delete mode 100644 src/Options/ModuleOptions.php delete mode 100644 src/Options/RedirectStrategyOptions.php delete mode 100644 src/Options/UnauthorizedStrategyOptions.php delete mode 100644 src/Permission/PermissionInterface.php delete mode 100644 src/Role/InMemoryRoleProvider.php delete mode 100644 src/Role/ObjectRepositoryRoleProvider.php delete mode 100644 src/Role/RoleProviderInterface.php delete mode 100644 src/Role/RoleProviderPluginManager.php delete mode 100644 src/Service/AuthorizationService.php delete mode 100644 src/Service/AuthorizationServiceAwareInterface.php delete mode 100644 src/Service/AuthorizationServiceAwareTrait.php delete mode 100644 src/Service/AuthorizationServiceInterface.php delete mode 100644 src/Service/RoleService.php delete mode 100644 src/View/Helper/HasRole.php delete mode 100644 src/View/Helper/IsGranted.php delete mode 100644 src/View/Strategy/AbstractStrategy.php delete mode 100644 src/View/Strategy/RedirectStrategy.php delete mode 100644 src/View/Strategy/UnauthorizedStrategy.php delete mode 100644 tests/Asset/DummyGuard.php delete mode 100644 tests/Asset/FlatRole.php delete mode 100644 tests/Asset/HierarchicalRole.php delete mode 100644 tests/Asset/MockRoleWithPermissionMethod.php delete mode 100644 tests/Asset/MockRoleWithPermissionProperty.php delete mode 100644 tests/Asset/MockRoleWithPermissionTraversable.php delete mode 100644 tests/Asset/Permission.php delete mode 100644 tests/Asset/SimpleAssertion.php delete mode 100644 tests/Bootstrap.php delete mode 100644 tests/Collector/RbacCollectorTest.php delete mode 100644 tests/Factory/AssertionPluginManagerFactoryTest.php delete mode 100644 tests/Factory/AuthenticationIdentityProviderFactoryTest.php delete mode 100644 tests/Factory/AuthorizationServiceDelegatorTest.php delete mode 100644 tests/Factory/AuthorizationServiceFactoryTest.php delete mode 100644 tests/Factory/ControllerGuardFactoryTest.php delete mode 100644 tests/Factory/ControllerPermissionsGuardFactoryTest.php delete mode 100644 tests/Factory/GuardPluginManagerFactoryTest.php delete mode 100644 tests/Factory/GuardsFactoryTest.php delete mode 100644 tests/Factory/HasRoleViewHelperFactoryTest.php delete mode 100644 tests/Factory/IsGrantedPluginFactoryTest.php delete mode 100644 tests/Factory/IsGrantedViewHelperFactoryTest.php delete mode 100644 tests/Factory/ModuleOptionsFactoryTest.php delete mode 100644 tests/Factory/ObjectRepositoryRoleProviderFactoryTest.php delete mode 100644 tests/Factory/RbacFactoryTest.php delete mode 100644 tests/Factory/RedirectStrategyFactoryTest.php delete mode 100644 tests/Factory/RoleProviderPluginManagerFactoryTest.php delete mode 100644 tests/Factory/RoleServiceFactoryTest.php delete mode 100644 tests/Factory/RouteGuardFactoryTest.php delete mode 100644 tests/Factory/RoutePermissionsGuardFactoryTest.php delete mode 100644 tests/Factory/UnauthorizedStrategyFactoryTest.php delete mode 100644 tests/Guard/AbstractGuardTest.php delete mode 100644 tests/Guard/ControllerGuardTest.php delete mode 100644 tests/Guard/ControllerPermissionsGuardTest.php delete mode 100644 tests/Guard/GuardPluginManagerTest.php delete mode 100644 tests/Guard/ProtectionPolicyTraitTest.php delete mode 100644 tests/Guard/RouteGuardTest.php delete mode 100644 tests/Guard/RoutePermissionsGuardTest.php delete mode 100644 tests/Identity/AuthenticationIdentityProviderTest.php delete mode 100644 tests/Initializer/AuthorizationAwareFake.php delete mode 100644 tests/Initializer/AuthorizationServiceInitializerTest.php delete mode 100644 tests/ModuleTest.php delete mode 100644 tests/Mvc/Controller/Plugin/IsGrantedTest.php delete mode 100644 tests/Options/ModuleOptionsTest.php delete mode 100644 tests/Options/RedirectStrategyOptionsTest.php delete mode 100644 tests/Options/UnauthorizedStrategyOptionsTest.php delete mode 100644 tests/Role/InMemoryRoleProviderTest.php delete mode 100644 tests/Role/ObjectRepositoryRoleProviderTest.php delete mode 100644 tests/Role/RoleProviderPluginManagerTest.php delete mode 100644 tests/Service/AuthorizationServiceAwareTraitTest.php delete mode 100644 tests/Service/AuthorizationServiceTest.php delete mode 100644 tests/Service/RoleServiceTest.php delete mode 100644 tests/TestConfiguration.php.dist delete mode 100644 tests/Util/ServiceManagerFactory.php delete mode 100644 tests/View/Helper/HasRoleTest.php delete mode 100644 tests/View/Helper/IsGrantedTest.php delete mode 100644 tests/View/Strategy/RedirectStrategyTest.php delete mode 100644 tests/View/Strategy/UnauthorizedStrategyTest.php delete mode 100644 tests/testing.config.php delete mode 100644 view/error/403.phtml delete mode 100644 view/laminas-developer-tools/toolbar/lmc-rbac.phtml diff --git a/.cloveralls.yml b/.cloveralls.yml deleted file mode 100644 index 3b4fbe8f..00000000 --- a/.cloveralls.yml +++ /dev/null @@ -1,6 +0,0 @@ -# Set folder to clover (must be the same on travis) -coverage_clover: build/logs/clover.xml -# Same folder here -json_path: build/logs/coveralls-upload.json -# Don't touch that -service_name: travis-ci diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 33024c3b..00000000 --- a/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -vendor -.idea -build -.phpunit* -composer.lock -#ignoring PHPEclipse project files and directories -/.settings -/.project -/.buildpath -#ignoring PHPStorm project files -/.idea -#ignoring VSCode project files -/.vscode -#ignoring NetBeans project files -/nbproject diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e5270c36..00000000 --- a/.travis.yml +++ /dev/null @@ -1,58 +0,0 @@ -language: php - -cache: - directories: - - $HOME/.composer/cache - -env: - global: - - COMPOSER_ARGS="--no-interaction" - - XDEBUG_MODE=coverage - -matrix: - fast_finish: true - include: - - php: 7.4 - env: - - DEPS=lowest - - php: 7.4 - env: - - DEPS=latest - - php: 8.0 - env: - - DEPS=lowest - - php: 8.0 - env: - - DEPS=latest - - CS_CHECK=true - - TEST_COVERAGE=true - - php: 8.1 - env: - - DEPS=lowest - - php: 8.1 - env: - - DEPS=latest - -before_install: - - if [[ $TEST_COVERAGE != 'true' ]]; then phpenv config-rm xdebug.ini || return 0 ; fi - -install: - - travis_retry composer install $COMPOSER_ARGS --ignore-platform-reqs - - if [[ $LEGACY_DEPS != '' ]]; then travis_retry composer update $COMPOSER_ARGS --with-dependencies $LEGACY_DEPS ; fi - - if [[ $DEPS == 'latest' ]]; then travis_retry composer update $COMPOSER_ARGS; fi - - if [[ $DEPS == 'lowest' ]]; then travis_retry composer update --prefer-lowest --prefer-stable $COMPOSER_ARGS; fi - - stty cols 120 && composer show - -before_script: - - mkdir -p ./build/logs - -script: - - if [[ $TEST_COVERAGE == 'true' ]]; then composer test-coverage ; else composer test ; fi - - composer cs-check - -after_success: - - composer upload-coverage - -notifications: - email: true - diff --git a/404.html b/404.html new file mode 100644 index 00000000..09f8d933 --- /dev/null +++ b/404.html @@ -0,0 +1,14 @@ + + + + + +Page Not Found | LmcRbacMvc + + + + + +
Skip to main content

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

+ + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 767b1809..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -# CHANGELOG - -For changelog, please refer to the [releases](https://github.com/LM-Commons/LmcRbacMvc/releases) page. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 7519bc3c..00000000 --- a/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2013, ZF-Commons Contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - - Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. - - Neither the name of the ZF-Commons nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md deleted file mode 100644 index 8b8ab380..00000000 --- a/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# LmcRbacMvc - -[![Version](https://poser.pugx.org/lm-commons/lmc-rbac-mvc/version)](//packagist.org/packages/lm-commons/lmc-rbac-mvc) -[![Total Downloads](https://poser.pugx.org/lm-commons/lmc-rbac-mvc/downloads)](//packagist.org/packages/lm-commons/lmc-rbac-mvc) -[![License](https://poser.pugx.org/lm-commons/lmc-rbac-mvc/license)](//packagist.org/packages/lm-commons/lmc-rbac-mvc) -[![Master Branch Build Status](https://travis-ci.com/LM-Commons/LmcRbacMvc.svg?branch=master)](http://travis-ci.com/LM-Commons/LmcRbac) -[![Gitter](https://badges.gitter.im/LM-Commons/community.svg)](https://gitter.im/LM-Commons/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) -[![Coverage Status](https://coveralls.io/repos/github/LM-Commons/LmcRbacMvc/badge.svg?branch=master)](https://coveralls.io/github/LM-Commons/LmcRbacMvc?branch=master) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/LM-Commons/LmcRbacMvc/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/LM-Commons/LmcRbacMvc/?branch=master) - -Role-based access control module to provide additional features on top of Zend\Permissions\Rbac - -Based on [ZF-Commons/zfc-rbac](https://github.com/ZF-Commons/zfc-rbac) v2.6.x. If you are looking for the Laminas version of zfc-rbac v3, please use [LM-Commons/LmcRbac](https://github.com/LM-Commons/LmcRbac). - -### Important Notes: - -This version has breaking changes with respect to ZfcRbac v2. See the [Upgrade](#upgrade) section for details. - - -## Requirements - -- PHP 7.2 or higher -- [Zf-fr/Rbac component v1](https://github.com/zf-fr/rbac): this is actually a prototype for the ZF3 Rbac component. -- [Laminas Components 2.x | 3.x or higher](http://www.github.com/laminas) - -> - -## Optional - -- [DoctrineModule](https://github.com/doctrine/DoctrineModule): if you want to use some built-in role and permission providers. -- [Laminas\DeveloperTools](https://github.com/laminas/Laminas\DeveloperTools): if you want to have useful stats added to -the Laminas Developer toolbar. - -## Upgrade - -LmcRbac introduces breaking changes from zfcrbac v2: -- The namespace has been changed from `ZfcRbac` to `LmcRbacMvc`. -- The key `zfc_rbac` in autoload and module config files has been replaced -by the `lmc_rbac` key. - -You can find an [upgrade guide](UPGRADE.md) to quickly upgrade your application from major versions of ZfcRbac. - -## Installation - -LmcRbacMvc only officially supports installation through Composer. For Composer documentation, please refer to -[getcomposer.org](http://getcomposer.org/). - -Install the module: - -```sh -$ php composer.phar require lm-commons/lmc-rbac-mvc:^3.0 -``` -This will install a Laminas MVC equivalent of zfc-rbac 2.6.3. - -Enable the module by adding `LmcRbacMvc` key to your `application.config.php` or `modules.config.php` file. Customize the module by copy-pasting -the `lmc_rbac.global.php.dist` file to your `config/autoload` folder. - -## Documentation - -The official documentation is available in the [/docs](/docs) folder. - -You can also find some Doctrine entities in the [/data](/data) folder that will help you to more quickly take advantage -of LmcRbac. - -## Support - -- File issues at https://github.com/LM-Commons/LmcRbacMvc/issues. -- Ask questions in the [LM-Commons gitter](https://gitter.im/LM-Commons/community) chat. diff --git a/UPGRADE.md b/UPGRADE.md deleted file mode 100644 index 823842e1..00000000 --- a/UPGRADE.md +++ /dev/null @@ -1,54 +0,0 @@ -# Upgrade guide - -## From zfc-rbac v2.x to LmcRbacMvc v3.0 - -- [BC] The namespace has been changed to `LmcRbacMvc` -- [BC] The `zfc_rbac` configuration key has been changed to `lmc_rbac` -- Requires PHP 7.2 or later -- Requires Laminas MVC components 3.x or later -- Uses PSR-4 autoload - - -## Previous zfc-rbac versions - -### From v2.2 to v2.3 - -- No BC - -### From v2.1 to v2.2 - -- [Potential BC] To simplify unit tests, we have introduced a new `AuthorizationServiceInterface` that the -`AuthorizationService` now implements. We didn't touch any interface (such as `AssertionInterface`) that rely explicitly -on typehinting the `AuthorizationService` to avoid big BC. However, we have updated the view helper and controller -plugins to use the interface instead. This can lead to a BC if you created subclasses of those plugins (which is -not a typical use case). If this is the case, just change `AuthorizationService` to `AuthorizationServiceInterface`. - -### From v2.0 to v2.1 - -- [Potential BC] A potential BC have been introduced in v2.1 to respect interfaces of RBAC component more strictly. -However there is great chance that you have nothing to do. Now, ZfcRbac no longer cast permissions to string before -passing it to your "hasPermission" method in the Role entity. If you used to call `isGranted` using a string permission, -like this: `isGranted('myPermission')`, then you have nothing to do. However, if you are passing a `PermissionInterface` -object, you will now receive this object instead of a string. It's up to you to getting the name from your permission. - -### From v1 to v2 - -Here are the major breaking changes from ZfcRbac 1 to ZfcRbac 2: - -- [BC] Dependency to the ZF2 RBAC component has been replaced in favour of a ZF3 prototype which fixes a lot -of design issues. -- [BC] ZfcRbac no longer accepts multiple role providers. Therefore, the option `role_providers` has been renamed -to `role_provider` -- [BC] Permission providers are gone (hence, the options `permission_providers` as well as `permission_manager` should -be removed). Instead, roles now embed all the necessary information -- [BC] The `redirect_to_route` option for the `RedirectStrategy` is gone. Instead, we now have two options: -`redirect_to_route_connected` and `redirect_to_route_disconnected`. This solves an issue when people used to have -a guard on `login` for non-authenticated users only, which leaded to circular redirections. -- [BC] The default protection policy is now `POLICY_ALLOW`. `POLICY_DENY` was way too restrictive and annoying to -work with by default. -- [BC] `isGranted` method of the AuthorizationService no longer accepts an assertion as a second parameter. Instead, -the AuthorizationService now has an assertion map, that allows to map an assertion to a permission. This allows to -inject dependencies into assertions, as well as making the use of assertions much more transparent. -- [BC] Each assertions now receive the whole `AuthorizationService` instead of the current identity. This allows to -support use cases where an assertion needs to check another permission. -- [BC] Entity schema for hierarchical role have changed and no longer require to implement `RecursiveIterator`. Please have a look at the new schema in the `data` folder. diff --git a/assets/css/styles.69f2a557.css b/assets/css/styles.69f2a557.css new file mode 100644 index 00000000..9d0d7cfd --- /dev/null +++ b/assets/css/styles.69f2a557.css @@ -0,0 +1 @@ +.col,.container{padding:0 var(--ifm-spacing-horizontal);width:100%}.markdown>h2,.markdown>h3,.markdown>h4,.markdown>h5,.markdown>h6{margin-bottom:calc(var(--ifm-heading-vertical-rhythm-bottom)*var(--ifm-leading))}.markdown li,body{word-wrap:break-word}body,ol ol,ol ul,ul ol,ul ul{margin:0}pre,table{overflow:auto}blockquote,pre{margin:0 0 var(--ifm-spacing-vertical)}.breadcrumbs__link,.button{transition-timing-function:var(--ifm-transition-timing-default)}.button,code{vertical-align:middle}.button--outline.button--active,.button--outline:active,.button--outline:hover,:root{--ifm-button-color:var(--ifm-font-color-base-inverse)}.menu__link:hover,a{transition:color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.navbar--dark,:root{--ifm-navbar-link-hover-color:var(--ifm-color-primary)}.menu,.navbar-sidebar{overflow-x:hidden}:root,html[data-theme=dark]{--ifm-color-emphasis-500:var(--ifm-color-gray-500)}.toggleButton_gllP,html{-webkit-tap-highlight-color:transparent}.clean-list,.containsTaskList_mC6p,.details_lb9f>summary,.dropdown__menu,.menu__list{list-style:none}:root{--ifm-color-scheme:light;--ifm-dark-value:10%;--ifm-darker-value:15%;--ifm-darkest-value:30%;--ifm-light-value:15%;--ifm-lighter-value:30%;--ifm-lightest-value:50%;--ifm-contrast-background-value:90%;--ifm-contrast-foreground-value:70%;--ifm-contrast-background-dark-value:70%;--ifm-contrast-foreground-dark-value:90%;--ifm-color-primary:#3578e5;--ifm-color-secondary:#ebedf0;--ifm-color-success:#00a400;--ifm-color-info:#54c7ec;--ifm-color-warning:#ffba00;--ifm-color-danger:#fa383e;--ifm-color-primary-dark:#306cce;--ifm-color-primary-darker:#2d66c3;--ifm-color-primary-darkest:#2554a0;--ifm-color-primary-light:#538ce9;--ifm-color-primary-lighter:#72a1ed;--ifm-color-primary-lightest:#9abcf2;--ifm-color-primary-contrast-background:#ebf2fc;--ifm-color-primary-contrast-foreground:#102445;--ifm-color-secondary-dark:#d4d5d8;--ifm-color-secondary-darker:#c8c9cc;--ifm-color-secondary-darkest:#a4a6a8;--ifm-color-secondary-light:#eef0f2;--ifm-color-secondary-lighter:#f1f2f5;--ifm-color-secondary-lightest:#f5f6f8;--ifm-color-secondary-contrast-background:#fdfdfe;--ifm-color-secondary-contrast-foreground:#474748;--ifm-color-success-dark:#009400;--ifm-color-success-darker:#008b00;--ifm-color-success-darkest:#007300;--ifm-color-success-light:#26b226;--ifm-color-success-lighter:#4dbf4d;--ifm-color-success-lightest:#80d280;--ifm-color-success-contrast-background:#e6f6e6;--ifm-color-success-contrast-foreground:#003100;--ifm-color-info-dark:#4cb3d4;--ifm-color-info-darker:#47a9c9;--ifm-color-info-darkest:#3b8ba5;--ifm-color-info-light:#6ecfef;--ifm-color-info-lighter:#87d8f2;--ifm-color-info-lightest:#aae3f6;--ifm-color-info-contrast-background:#eef9fd;--ifm-color-info-contrast-foreground:#193c47;--ifm-color-warning-dark:#e6a700;--ifm-color-warning-darker:#d99e00;--ifm-color-warning-darkest:#b38200;--ifm-color-warning-light:#ffc426;--ifm-color-warning-lighter:#ffcf4d;--ifm-color-warning-lightest:#ffdd80;--ifm-color-warning-contrast-background:#fff8e6;--ifm-color-warning-contrast-foreground:#4d3800;--ifm-color-danger-dark:#e13238;--ifm-color-danger-darker:#d53035;--ifm-color-danger-darkest:#af272b;--ifm-color-danger-light:#fb565b;--ifm-color-danger-lighter:#fb7478;--ifm-color-danger-lightest:#fd9c9f;--ifm-color-danger-contrast-background:#ffebec;--ifm-color-danger-contrast-foreground:#4b1113;--ifm-color-white:#fff;--ifm-color-black:#000;--ifm-color-gray-0:var(--ifm-color-white);--ifm-color-gray-100:#f5f6f7;--ifm-color-gray-200:#ebedf0;--ifm-color-gray-300:#dadde1;--ifm-color-gray-400:#ccd0d5;--ifm-color-gray-500:#bec3c9;--ifm-color-gray-600:#8d949e;--ifm-color-gray-700:#606770;--ifm-color-gray-800:#444950;--ifm-color-gray-900:#1c1e21;--ifm-color-gray-1000:var(--ifm-color-black);--ifm-color-emphasis-0:var(--ifm-color-gray-0);--ifm-color-emphasis-100:var(--ifm-color-gray-100);--ifm-color-emphasis-200:var(--ifm-color-gray-200);--ifm-color-emphasis-300:var(--ifm-color-gray-300);--ifm-color-emphasis-400:var(--ifm-color-gray-400);--ifm-color-emphasis-600:var(--ifm-color-gray-600);--ifm-color-emphasis-700:var(--ifm-color-gray-700);--ifm-color-emphasis-800:var(--ifm-color-gray-800);--ifm-color-emphasis-900:var(--ifm-color-gray-900);--ifm-color-emphasis-1000:var(--ifm-color-gray-1000);--ifm-color-content:var(--ifm-color-emphasis-900);--ifm-color-content-inverse:var(--ifm-color-emphasis-0);--ifm-color-content-secondary:#525860;--ifm-background-color:#0000;--ifm-background-surface-color:var(--ifm-color-content-inverse);--ifm-global-border-width:1px;--ifm-global-radius:0.4rem;--ifm-hover-overlay:#0000000d;--ifm-font-color-base:var(--ifm-color-content);--ifm-font-color-base-inverse:var(--ifm-color-content-inverse);--ifm-font-color-secondary:var(--ifm-color-content-secondary);--ifm-font-family-base:system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--ifm-font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--ifm-font-size-base:100%;--ifm-font-weight-light:300;--ifm-font-weight-normal:400;--ifm-font-weight-semibold:500;--ifm-font-weight-bold:700;--ifm-font-weight-base:var(--ifm-font-weight-normal);--ifm-line-height-base:1.65;--ifm-global-spacing:1rem;--ifm-spacing-vertical:var(--ifm-global-spacing);--ifm-spacing-horizontal:var(--ifm-global-spacing);--ifm-transition-fast:200ms;--ifm-transition-slow:400ms;--ifm-transition-timing-default:cubic-bezier(0.08,0.52,0.52,1);--ifm-global-shadow-lw:0 1px 2px 0 #0000001a;--ifm-global-shadow-md:0 5px 40px #0003;--ifm-global-shadow-tl:0 12px 28px 0 #0003,0 2px 4px 0 #0000001a;--ifm-z-index-dropdown:100;--ifm-z-index-fixed:200;--ifm-z-index-overlay:400;--ifm-container-width:1140px;--ifm-container-width-xl:1320px;--ifm-code-background:#f6f7f8;--ifm-code-border-radius:var(--ifm-global-radius);--ifm-code-font-size:90%;--ifm-code-padding-horizontal:0.1rem;--ifm-code-padding-vertical:0.1rem;--ifm-pre-background:var(--ifm-code-background);--ifm-pre-border-radius:var(--ifm-code-border-radius);--ifm-pre-color:inherit;--ifm-pre-line-height:1.45;--ifm-pre-padding:1rem;--ifm-heading-color:inherit;--ifm-heading-margin-top:0;--ifm-heading-margin-bottom:var(--ifm-spacing-vertical);--ifm-heading-font-family:var(--ifm-font-family-base);--ifm-heading-font-weight:var(--ifm-font-weight-bold);--ifm-heading-line-height:1.25;--ifm-h1-font-size:2rem;--ifm-h2-font-size:1.5rem;--ifm-h3-font-size:1.25rem;--ifm-h4-font-size:1rem;--ifm-h5-font-size:0.875rem;--ifm-h6-font-size:0.85rem;--ifm-image-alignment-padding:1.25rem;--ifm-leading-desktop:1.25;--ifm-leading:calc(var(--ifm-leading-desktop)*1rem);--ifm-list-left-padding:2rem;--ifm-list-margin:1rem;--ifm-list-item-margin:0.25rem;--ifm-list-paragraph-margin:1rem;--ifm-table-cell-padding:0.75rem;--ifm-table-background:#0000;--ifm-table-stripe-background:#00000008;--ifm-table-border-width:1px;--ifm-table-border-color:var(--ifm-color-emphasis-300);--ifm-table-head-background:inherit;--ifm-table-head-color:inherit;--ifm-table-head-font-weight:var(--ifm-font-weight-bold);--ifm-table-cell-color:inherit;--ifm-link-color:var(--ifm-color-primary);--ifm-link-decoration:none;--ifm-link-hover-color:var(--ifm-link-color);--ifm-link-hover-decoration:underline;--ifm-paragraph-margin-bottom:var(--ifm-leading);--ifm-blockquote-font-size:var(--ifm-font-size-base);--ifm-blockquote-border-left-width:2px;--ifm-blockquote-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-blockquote-padding-vertical:0;--ifm-blockquote-shadow:none;--ifm-blockquote-color:var(--ifm-color-emphasis-800);--ifm-blockquote-border-color:var(--ifm-color-emphasis-300);--ifm-hr-background-color:var(--ifm-color-emphasis-500);--ifm-hr-height:1px;--ifm-hr-margin-vertical:1.5rem;--ifm-scrollbar-size:7px;--ifm-scrollbar-track-background-color:#f1f1f1;--ifm-scrollbar-thumb-background-color:silver;--ifm-scrollbar-thumb-hover-background-color:#a7a7a7;--ifm-alert-background-color:inherit;--ifm-alert-border-color:inherit;--ifm-alert-border-radius:var(--ifm-global-radius);--ifm-alert-border-width:0px;--ifm-alert-border-left-width:5px;--ifm-alert-color:var(--ifm-font-color-base);--ifm-alert-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-alert-padding-vertical:var(--ifm-spacing-vertical);--ifm-alert-shadow:var(--ifm-global-shadow-lw);--ifm-avatar-intro-margin:1rem;--ifm-avatar-intro-alignment:inherit;--ifm-avatar-photo-size:3rem;--ifm-badge-background-color:inherit;--ifm-badge-border-color:inherit;--ifm-badge-border-radius:var(--ifm-global-radius);--ifm-badge-border-width:var(--ifm-global-border-width);--ifm-badge-color:var(--ifm-color-white);--ifm-badge-padding-horizontal:calc(var(--ifm-spacing-horizontal)*0.5);--ifm-badge-padding-vertical:calc(var(--ifm-spacing-vertical)*0.25);--ifm-breadcrumb-border-radius:1.5rem;--ifm-breadcrumb-spacing:0.5rem;--ifm-breadcrumb-color-active:var(--ifm-color-primary);--ifm-breadcrumb-item-background-active:var(--ifm-hover-overlay);--ifm-breadcrumb-padding-horizontal:0.8rem;--ifm-breadcrumb-padding-vertical:0.4rem;--ifm-breadcrumb-size-multiplier:1;--ifm-breadcrumb-separator:url('data:image/svg+xml;utf8,');--ifm-breadcrumb-separator-filter:none;--ifm-breadcrumb-separator-size:0.5rem;--ifm-breadcrumb-separator-size-multiplier:1.25;--ifm-button-background-color:inherit;--ifm-button-border-color:var(--ifm-button-background-color);--ifm-button-border-width:var(--ifm-global-border-width);--ifm-button-font-weight:var(--ifm-font-weight-bold);--ifm-button-padding-horizontal:1.5rem;--ifm-button-padding-vertical:0.375rem;--ifm-button-size-multiplier:1;--ifm-button-transition-duration:var(--ifm-transition-fast);--ifm-button-border-radius:calc(var(--ifm-global-radius)*var(--ifm-button-size-multiplier));--ifm-button-group-spacing:2px;--ifm-card-background-color:var(--ifm-background-surface-color);--ifm-card-border-radius:calc(var(--ifm-global-radius)*2);--ifm-card-horizontal-spacing:var(--ifm-global-spacing);--ifm-card-vertical-spacing:var(--ifm-global-spacing);--ifm-toc-border-color:var(--ifm-color-emphasis-300);--ifm-toc-link-color:var(--ifm-color-content-secondary);--ifm-toc-padding-vertical:0.5rem;--ifm-toc-padding-horizontal:0.5rem;--ifm-dropdown-background-color:var(--ifm-background-surface-color);--ifm-dropdown-font-weight:var(--ifm-font-weight-semibold);--ifm-dropdown-link-color:var(--ifm-font-color-base);--ifm-dropdown-hover-background-color:var(--ifm-hover-overlay);--ifm-footer-background-color:var(--ifm-color-emphasis-100);--ifm-footer-color:inherit;--ifm-footer-link-color:var(--ifm-color-emphasis-700);--ifm-footer-link-hover-color:var(--ifm-color-primary);--ifm-footer-link-horizontal-spacing:0.5rem;--ifm-footer-padding-horizontal:calc(var(--ifm-spacing-horizontal)*2);--ifm-footer-padding-vertical:calc(var(--ifm-spacing-vertical)*2);--ifm-footer-title-color:inherit;--ifm-footer-logo-max-width:min(30rem,90vw);--ifm-hero-background-color:var(--ifm-background-surface-color);--ifm-hero-text-color:var(--ifm-color-emphasis-800);--ifm-menu-color:var(--ifm-color-emphasis-700);--ifm-menu-color-active:var(--ifm-color-primary);--ifm-menu-color-background-active:var(--ifm-hover-overlay);--ifm-menu-color-background-hover:var(--ifm-hover-overlay);--ifm-menu-link-padding-horizontal:0.75rem;--ifm-menu-link-padding-vertical:0.375rem;--ifm-menu-link-sublist-icon:url('data:image/svg+xml;utf8,');--ifm-menu-link-sublist-icon-filter:none;--ifm-navbar-background-color:var(--ifm-background-surface-color);--ifm-navbar-height:3.75rem;--ifm-navbar-item-padding-horizontal:0.75rem;--ifm-navbar-item-padding-vertical:0.25rem;--ifm-navbar-link-color:var(--ifm-font-color-base);--ifm-navbar-link-active-color:var(--ifm-link-color);--ifm-navbar-padding-horizontal:var(--ifm-spacing-horizontal);--ifm-navbar-padding-vertical:calc(var(--ifm-spacing-vertical)*0.5);--ifm-navbar-shadow:var(--ifm-global-shadow-lw);--ifm-navbar-search-input-background-color:var(--ifm-color-emphasis-200);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-800);--ifm-navbar-search-input-placeholder-color:var(--ifm-color-emphasis-500);--ifm-navbar-search-input-icon:url('data:image/svg+xml;utf8,');--ifm-navbar-sidebar-width:83vw;--ifm-pagination-border-radius:var(--ifm-global-radius);--ifm-pagination-color-active:var(--ifm-color-primary);--ifm-pagination-font-size:1rem;--ifm-pagination-item-active-background:var(--ifm-hover-overlay);--ifm-pagination-page-spacing:0.2em;--ifm-pagination-padding-horizontal:calc(var(--ifm-spacing-horizontal)*1);--ifm-pagination-padding-vertical:calc(var(--ifm-spacing-vertical)*0.25);--ifm-pagination-nav-border-radius:var(--ifm-global-radius);--ifm-pagination-nav-color-hover:var(--ifm-color-primary);--ifm-pills-color-active:var(--ifm-color-primary);--ifm-pills-color-background-active:var(--ifm-hover-overlay);--ifm-pills-spacing:0.125rem;--ifm-tabs-color:var(--ifm-font-color-secondary);--ifm-tabs-color-active:var(--ifm-color-primary);--ifm-tabs-color-active-border:var(--ifm-tabs-color-active);--ifm-tabs-padding-horizontal:1rem;--ifm-tabs-padding-vertical:1rem;--docusaurus-progress-bar-color:var(--ifm-color-primary);--ifm-color-primary:#2e8555;--ifm-color-primary-dark:#29784c;--ifm-color-primary-darker:#277148;--ifm-color-primary-darkest:#205d3b;--ifm-color-primary-light:#33925d;--ifm-color-primary-lighter:#359962;--ifm-color-primary-lightest:#3cad6e;--ifm-code-font-size:95%;--docusaurus-highlighted-code-line-bg:#0000001a;--docusaurus-announcement-bar-height:auto;--docusaurus-collapse-button-bg:#0000;--docusaurus-collapse-button-bg-hover:#0000001a;--doc-sidebar-width:300px;--doc-sidebar-hidden-width:30px;--docusaurus-tag-list-border:var(--ifm-color-emphasis-300)}.badge--danger,.badge--info,.badge--primary,.badge--secondary,.badge--success,.badge--warning{--ifm-badge-border-color:var(--ifm-badge-background-color)}.button--link,.button--outline{--ifm-button-background-color:#0000}*{box-sizing:border-box}html{-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:100%;text-size-adjust:100%;background-color:var(--ifm-background-color);color:var(--ifm-font-color-base);color-scheme:var(--ifm-color-scheme);font:var(--ifm-font-size-base)/var(--ifm-line-height-base) var(--ifm-font-family-base);text-rendering:optimizelegibility}iframe{border:0;color-scheme:auto}.container{margin:0 auto;max-width:var(--ifm-container-width)}.container--fluid{max-width:inherit}.row{display:flex;flex-wrap:wrap;margin:0 calc(var(--ifm-spacing-horizontal)*-1)}.margin-bottom--none,.margin-vert--none,.markdown>:last-child{margin-bottom:0!important}.margin-top--none,.margin-vert--none{margin-top:0!important}.row--no-gutters{margin-left:0;margin-right:0}.margin-horiz--none,.margin-right--none{margin-right:0!important}.row--no-gutters>.col{padding-left:0;padding-right:0}.row--align-top{align-items:flex-start}.row--align-bottom{align-items:flex-end}.menuExternalLink_NmtK,.row--align-center{align-items:center}.row--align-stretch{align-items:stretch}.row--align-baseline{align-items:baseline}.col{--ifm-col-width:100%;flex:1 0;margin-left:0;max-width:var(--ifm-col-width)}.padding-bottom--none,.padding-vert--none{padding-bottom:0!important}.padding-top--none,.padding-vert--none{padding-top:0!important}.padding-horiz--none,.padding-left--none{padding-left:0!important}.padding-horiz--none,.padding-right--none{padding-right:0!important}.col[class*=col--]{flex:0 0 var(--ifm-col-width)}.col--1{--ifm-col-width:8.33333%}.col--offset-1{margin-left:8.33333%}.col--2{--ifm-col-width:16.66667%}.col--offset-2{margin-left:16.66667%}.col--3{--ifm-col-width:25%}.col--offset-3{margin-left:25%}.col--4{--ifm-col-width:33.33333%}.col--offset-4{margin-left:33.33333%}.col--5{--ifm-col-width:41.66667%}.col--offset-5{margin-left:41.66667%}.col--6{--ifm-col-width:50%}.col--offset-6{margin-left:50%}.col--7{--ifm-col-width:58.33333%}.col--offset-7{margin-left:58.33333%}.col--8{--ifm-col-width:66.66667%}.col--offset-8{margin-left:66.66667%}.col--9{--ifm-col-width:75%}.col--offset-9{margin-left:75%}.col--10{--ifm-col-width:83.33333%}.col--offset-10{margin-left:83.33333%}.col--11{--ifm-col-width:91.66667%}.col--offset-11{margin-left:91.66667%}.col--12{--ifm-col-width:100%}.col--offset-12{margin-left:100%}.margin-horiz--none,.margin-left--none{margin-left:0!important}.margin--none{margin:0!important}.margin-bottom--xs,.margin-vert--xs{margin-bottom:.25rem!important}.margin-top--xs,.margin-vert--xs{margin-top:.25rem!important}.margin-horiz--xs,.margin-left--xs{margin-left:.25rem!important}.margin-horiz--xs,.margin-right--xs{margin-right:.25rem!important}.margin--xs{margin:.25rem!important}.margin-bottom--sm,.margin-vert--sm{margin-bottom:.5rem!important}.margin-top--sm,.margin-vert--sm{margin-top:.5rem!important}.margin-horiz--sm,.margin-left--sm{margin-left:.5rem!important}.margin-horiz--sm,.margin-right--sm{margin-right:.5rem!important}.margin--sm{margin:.5rem!important}.margin-bottom--md,.margin-vert--md{margin-bottom:1rem!important}.margin-top--md,.margin-vert--md{margin-top:1rem!important}.margin-horiz--md,.margin-left--md{margin-left:1rem!important}.margin-horiz--md,.margin-right--md{margin-right:1rem!important}.margin--md{margin:1rem!important}.margin-bottom--lg,.margin-vert--lg{margin-bottom:2rem!important}.margin-top--lg,.margin-vert--lg{margin-top:2rem!important}.margin-horiz--lg,.margin-left--lg{margin-left:2rem!important}.margin-horiz--lg,.margin-right--lg{margin-right:2rem!important}.margin--lg{margin:2rem!important}.margin-bottom--xl,.margin-vert--xl{margin-bottom:5rem!important}.margin-top--xl,.margin-vert--xl{margin-top:5rem!important}.margin-horiz--xl,.margin-left--xl{margin-left:5rem!important}.margin-horiz--xl,.margin-right--xl{margin-right:5rem!important}.margin--xl{margin:5rem!important}.padding--none{padding:0!important}.padding-bottom--xs,.padding-vert--xs{padding-bottom:.25rem!important}.padding-top--xs,.padding-vert--xs{padding-top:.25rem!important}.padding-horiz--xs,.padding-left--xs{padding-left:.25rem!important}.padding-horiz--xs,.padding-right--xs{padding-right:.25rem!important}.padding--xs{padding:.25rem!important}.padding-bottom--sm,.padding-vert--sm{padding-bottom:.5rem!important}.padding-top--sm,.padding-vert--sm{padding-top:.5rem!important}.padding-horiz--sm,.padding-left--sm{padding-left:.5rem!important}.padding-horiz--sm,.padding-right--sm{padding-right:.5rem!important}.padding--sm{padding:.5rem!important}.padding-bottom--md,.padding-vert--md{padding-bottom:1rem!important}.padding-top--md,.padding-vert--md{padding-top:1rem!important}.padding-horiz--md,.padding-left--md{padding-left:1rem!important}.padding-horiz--md,.padding-right--md{padding-right:1rem!important}.padding--md{padding:1rem!important}.padding-bottom--lg,.padding-vert--lg{padding-bottom:2rem!important}.padding-top--lg,.padding-vert--lg{padding-top:2rem!important}.padding-horiz--lg,.padding-left--lg{padding-left:2rem!important}.padding-horiz--lg,.padding-right--lg{padding-right:2rem!important}.padding--lg{padding:2rem!important}.padding-bottom--xl,.padding-vert--xl{padding-bottom:5rem!important}.padding-top--xl,.padding-vert--xl{padding-top:5rem!important}.padding-horiz--xl,.padding-left--xl{padding-left:5rem!important}.padding-horiz--xl,.padding-right--xl{padding-right:5rem!important}.padding--xl{padding:5rem!important}code{background-color:var(--ifm-code-background);border:.1rem solid #0000001a;border-radius:var(--ifm-code-border-radius);font-family:var(--ifm-font-family-monospace);font-size:var(--ifm-code-font-size);padding:var(--ifm-code-padding-vertical) var(--ifm-code-padding-horizontal)}a code{color:inherit}pre{background-color:var(--ifm-pre-background);border-radius:var(--ifm-pre-border-radius);color:var(--ifm-pre-color);font:var(--ifm-code-font-size)/var(--ifm-pre-line-height) var(--ifm-font-family-monospace);padding:var(--ifm-pre-padding)}pre code{background-color:initial;border:none;font-size:100%;line-height:inherit;padding:0}kbd{background-color:var(--ifm-color-emphasis-0);border:1px solid var(--ifm-color-emphasis-400);border-radius:.2rem;box-shadow:inset 0 -1px 0 var(--ifm-color-emphasis-400);color:var(--ifm-color-emphasis-800);font:80% var(--ifm-font-family-monospace);padding:.15rem .3rem}h1,h2,h3,h4,h5,h6{color:var(--ifm-heading-color);font-family:var(--ifm-heading-font-family);font-weight:var(--ifm-heading-font-weight);line-height:var(--ifm-heading-line-height);margin:var(--ifm-heading-margin-top) 0 var(--ifm-heading-margin-bottom) 0}h1{font-size:var(--ifm-h1-font-size)}h2{font-size:var(--ifm-h2-font-size)}h3{font-size:var(--ifm-h3-font-size)}h4{font-size:var(--ifm-h4-font-size)}h5{font-size:var(--ifm-h5-font-size)}h6{font-size:var(--ifm-h6-font-size)}img{max-width:100%}img[align=right]{padding-left:var(--image-alignment-padding)}img[align=left]{padding-right:var(--image-alignment-padding)}.markdown{--ifm-h1-vertical-rhythm-top:3;--ifm-h2-vertical-rhythm-top:2;--ifm-h3-vertical-rhythm-top:1.5;--ifm-heading-vertical-rhythm-top:1.25;--ifm-h1-vertical-rhythm-bottom:1.25;--ifm-heading-vertical-rhythm-bottom:1}.markdown:after,.markdown:before{content:"";display:table}.markdown:after{clear:both}.markdown h1:first-child{--ifm-h1-font-size:3rem;margin-bottom:calc(var(--ifm-h1-vertical-rhythm-bottom)*var(--ifm-leading))}.markdown>h2{--ifm-h2-font-size:2rem;margin-top:calc(var(--ifm-h2-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h3{--ifm-h3-font-size:1.5rem;margin-top:calc(var(--ifm-h3-vertical-rhythm-top)*var(--ifm-leading))}.markdown>h4,.markdown>h5,.markdown>h6{margin-top:calc(var(--ifm-heading-vertical-rhythm-top)*var(--ifm-leading))}.markdown>p,.markdown>pre,.markdown>ul{margin-bottom:var(--ifm-leading)}.markdown li>p{margin-top:var(--ifm-list-paragraph-margin)}.markdown li+li{margin-top:var(--ifm-list-item-margin)}ol,ul{margin:0 0 var(--ifm-list-margin);padding-left:var(--ifm-list-left-padding)}ol ol,ul ol{list-style-type:lower-roman}ol ol ol,ol ul ol,ul ol ol,ul ul ol{list-style-type:lower-alpha}table{border-collapse:collapse;display:block;margin-bottom:var(--ifm-spacing-vertical)}table thead tr{border-bottom:2px solid var(--ifm-table-border-color)}table thead,table tr:nth-child(2n){background-color:var(--ifm-table-stripe-background)}table tr{background-color:var(--ifm-table-background);border-top:var(--ifm-table-border-width) solid var(--ifm-table-border-color)}table td,table th{border:var(--ifm-table-border-width) solid var(--ifm-table-border-color);padding:var(--ifm-table-cell-padding)}table th{background-color:var(--ifm-table-head-background);color:var(--ifm-table-head-color);font-weight:var(--ifm-table-head-font-weight)}table td{color:var(--ifm-table-cell-color)}strong{font-weight:var(--ifm-font-weight-bold)}a{color:var(--ifm-link-color);text-decoration:var(--ifm-link-decoration)}a:hover{color:var(--ifm-link-hover-color);text-decoration:var(--ifm-link-hover-decoration)}.button:hover,.text--no-decoration,.text--no-decoration:hover,a:not([href]){text-decoration:none}p{margin:0 0 var(--ifm-paragraph-margin-bottom)}blockquote{border-left:var(--ifm-blockquote-border-left-width) solid var(--ifm-blockquote-border-color);box-shadow:var(--ifm-blockquote-shadow);color:var(--ifm-blockquote-color);font-size:var(--ifm-blockquote-font-size);padding:var(--ifm-blockquote-padding-vertical) var(--ifm-blockquote-padding-horizontal)}blockquote>:first-child{margin-top:0}blockquote>:last-child{margin-bottom:0}hr{background-color:var(--ifm-hr-background-color);border:0;height:var(--ifm-hr-height);margin:var(--ifm-hr-margin-vertical) 0}.shadow--lw{box-shadow:var(--ifm-global-shadow-lw)!important}.shadow--md{box-shadow:var(--ifm-global-shadow-md)!important}.shadow--tl{box-shadow:var(--ifm-global-shadow-tl)!important}.text--primary,.wordWrapButtonEnabled_EoeP .wordWrapButtonIcon_Bwma{color:var(--ifm-color-primary)}.text--secondary{color:var(--ifm-color-secondary)}.text--success{color:var(--ifm-color-success)}.text--info{color:var(--ifm-color-info)}.text--warning{color:var(--ifm-color-warning)}.text--danger{color:var(--ifm-color-danger)}.text--center{text-align:center}.text--left{text-align:left}.text--justify{text-align:justify}.text--right{text-align:right}.text--capitalize{text-transform:capitalize}.text--lowercase{text-transform:lowercase}.admonitionHeading_Gvgb,.alert__heading,.text--uppercase{text-transform:uppercase}.text--light{font-weight:var(--ifm-font-weight-light)}.text--normal{font-weight:var(--ifm-font-weight-normal)}.text--semibold{font-weight:var(--ifm-font-weight-semibold)}.text--bold{font-weight:var(--ifm-font-weight-bold)}.text--italic{font-style:italic}.text--truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text--break{word-wrap:break-word!important;word-break:break-word!important}.clean-btn{background:none;border:none;color:inherit;cursor:pointer;font-family:inherit;padding:0}.alert,.alert .close{color:var(--ifm-alert-foreground-color)}.clean-list{padding-left:0}.alert--primary{--ifm-alert-background-color:var(--ifm-color-primary-contrast-background);--ifm-alert-background-color-highlight:#3578e526;--ifm-alert-foreground-color:var(--ifm-color-primary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-primary-dark)}.alert--secondary{--ifm-alert-background-color:var(--ifm-color-secondary-contrast-background);--ifm-alert-background-color-highlight:#ebedf026;--ifm-alert-foreground-color:var(--ifm-color-secondary-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-secondary-dark)}.alert--success{--ifm-alert-background-color:var(--ifm-color-success-contrast-background);--ifm-alert-background-color-highlight:#00a40026;--ifm-alert-foreground-color:var(--ifm-color-success-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-success-dark)}.alert--info{--ifm-alert-background-color:var(--ifm-color-info-contrast-background);--ifm-alert-background-color-highlight:#54c7ec26;--ifm-alert-foreground-color:var(--ifm-color-info-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-info-dark)}.alert--warning{--ifm-alert-background-color:var(--ifm-color-warning-contrast-background);--ifm-alert-background-color-highlight:#ffba0026;--ifm-alert-foreground-color:var(--ifm-color-warning-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-warning-dark)}.alert--danger{--ifm-alert-background-color:var(--ifm-color-danger-contrast-background);--ifm-alert-background-color-highlight:#fa383e26;--ifm-alert-foreground-color:var(--ifm-color-danger-contrast-foreground);--ifm-alert-border-color:var(--ifm-color-danger-dark)}.alert{--ifm-code-background:var(--ifm-alert-background-color-highlight);--ifm-link-color:var(--ifm-alert-foreground-color);--ifm-link-hover-color:var(--ifm-alert-foreground-color);--ifm-link-decoration:underline;--ifm-tabs-color:var(--ifm-alert-foreground-color);--ifm-tabs-color-active:var(--ifm-alert-foreground-color);--ifm-tabs-color-active-border:var(--ifm-alert-border-color);background-color:var(--ifm-alert-background-color);border:var(--ifm-alert-border-width) solid var(--ifm-alert-border-color);border-left-width:var(--ifm-alert-border-left-width);border-radius:var(--ifm-alert-border-radius);box-shadow:var(--ifm-alert-shadow);padding:var(--ifm-alert-padding-vertical) var(--ifm-alert-padding-horizontal)}.alert__heading{align-items:center;display:flex;font:700 var(--ifm-h5-font-size)/var(--ifm-heading-line-height) var(--ifm-heading-font-family);margin-bottom:.5rem}.alert__icon{display:inline-flex;margin-right:.4em}.alert__icon svg{fill:var(--ifm-alert-foreground-color);stroke:var(--ifm-alert-foreground-color);stroke-width:0}.alert .close{margin:calc(var(--ifm-alert-padding-vertical)*-1) calc(var(--ifm-alert-padding-horizontal)*-1) 0 0;opacity:.75}.alert .close:focus,.alert .close:hover{opacity:1}.alert a{text-decoration-color:var(--ifm-alert-border-color)}.alert a:hover{text-decoration-thickness:2px}.avatar{column-gap:var(--ifm-avatar-intro-margin);display:flex}.avatar__photo{border-radius:50%;display:block;height:var(--ifm-avatar-photo-size);overflow:hidden;width:var(--ifm-avatar-photo-size)}.card--full-height,.navbar__logo img,body,html{height:100%}.avatar__photo--sm{--ifm-avatar-photo-size:2rem}.avatar__photo--lg{--ifm-avatar-photo-size:4rem}.avatar__photo--xl{--ifm-avatar-photo-size:6rem}.avatar__intro{display:flex;flex:1 1;flex-direction:column;justify-content:center;text-align:var(--ifm-avatar-intro-alignment)}.badge,.breadcrumbs__item,.breadcrumbs__link,.button,.dropdown>.navbar__link:after{display:inline-block}.avatar__name{font:700 var(--ifm-h4-font-size)/var(--ifm-heading-line-height) var(--ifm-font-family-base)}.avatar__subtitle{margin-top:.25rem}.avatar--vertical{--ifm-avatar-intro-alignment:center;--ifm-avatar-intro-margin:0.5rem;align-items:center;flex-direction:column}.badge{background-color:var(--ifm-badge-background-color);border:var(--ifm-badge-border-width) solid var(--ifm-badge-border-color);border-radius:var(--ifm-badge-border-radius);color:var(--ifm-badge-color);font-size:75%;font-weight:var(--ifm-font-weight-bold);line-height:1;padding:var(--ifm-badge-padding-vertical) var(--ifm-badge-padding-horizontal)}.badge--primary{--ifm-badge-background-color:var(--ifm-color-primary)}.badge--secondary{--ifm-badge-background-color:var(--ifm-color-secondary);color:var(--ifm-color-black)}.breadcrumbs__link,.button.button--secondary.button--outline:not(.button--active):not(:hover){color:var(--ifm-font-color-base)}.badge--success{--ifm-badge-background-color:var(--ifm-color-success)}.badge--info{--ifm-badge-background-color:var(--ifm-color-info)}.badge--warning{--ifm-badge-background-color:var(--ifm-color-warning)}.badge--danger{--ifm-badge-background-color:var(--ifm-color-danger)}.breadcrumbs{margin-bottom:0;padding-left:0}.breadcrumbs__item:not(:last-child):after{background:var(--ifm-breadcrumb-separator) center;content:" ";display:inline-block;filter:var(--ifm-breadcrumb-separator-filter);height:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier));margin:0 var(--ifm-breadcrumb-spacing);opacity:.5;width:calc(var(--ifm-breadcrumb-separator-size)*var(--ifm-breadcrumb-size-multiplier)*var(--ifm-breadcrumb-separator-size-multiplier))}.breadcrumbs__item--active .breadcrumbs__link{background:var(--ifm-breadcrumb-item-background-active);color:var(--ifm-breadcrumb-color-active)}.breadcrumbs__link{border-radius:var(--ifm-breadcrumb-border-radius);font-size:calc(1rem*var(--ifm-breadcrumb-size-multiplier));padding:calc(var(--ifm-breadcrumb-padding-vertical)*var(--ifm-breadcrumb-size-multiplier)) calc(var(--ifm-breadcrumb-padding-horizontal)*var(--ifm-breadcrumb-size-multiplier));transition-duration:var(--ifm-transition-fast);transition-property:background,color}.breadcrumbs__link:any-link:hover,.breadcrumbs__link:link:hover,.breadcrumbs__link:visited:hover,area[href].breadcrumbs__link:hover{background:var(--ifm-breadcrumb-item-background-active);text-decoration:none}.breadcrumbs--sm{--ifm-breadcrumb-size-multiplier:0.8}.breadcrumbs--lg{--ifm-breadcrumb-size-multiplier:1.2}.button{background-color:var(--ifm-button-background-color);border:var(--ifm-button-border-width) solid var(--ifm-button-border-color);border-radius:var(--ifm-button-border-radius);cursor:pointer;font-size:calc(.875rem*var(--ifm-button-size-multiplier));font-weight:var(--ifm-button-font-weight);line-height:1.5;padding:calc(var(--ifm-button-padding-vertical)*var(--ifm-button-size-multiplier)) calc(var(--ifm-button-padding-horizontal)*var(--ifm-button-size-multiplier));text-align:center;transition-duration:var(--ifm-button-transition-duration);transition-property:color,background,border-color;-webkit-user-select:none;user-select:none;white-space:nowrap}.button,.button:hover{color:var(--ifm-button-color)}.button--outline{--ifm-button-color:var(--ifm-button-border-color)}.button--outline:hover{--ifm-button-background-color:var(--ifm-button-border-color)}.button--link{--ifm-button-border-color:#0000;color:var(--ifm-link-color);text-decoration:var(--ifm-link-decoration)}.button--link.button--active,.button--link:active,.button--link:hover{color:var(--ifm-link-hover-color);text-decoration:var(--ifm-link-hover-decoration)}.button.disabled,.button:disabled,.button[disabled]{opacity:.65;pointer-events:none}.button--sm{--ifm-button-size-multiplier:0.8}.button--lg{--ifm-button-size-multiplier:1.35}.button--block{display:block;width:100%}.button.button--secondary{color:var(--ifm-color-gray-900)}:where(.button--primary){--ifm-button-background-color:var(--ifm-color-primary);--ifm-button-border-color:var(--ifm-color-primary)}:where(.button--primary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-primary-dark);--ifm-button-border-color:var(--ifm-color-primary-dark)}.button--primary.button--active,.button--primary:active{--ifm-button-background-color:var(--ifm-color-primary-darker);--ifm-button-border-color:var(--ifm-color-primary-darker)}:where(.button--secondary){--ifm-button-background-color:var(--ifm-color-secondary);--ifm-button-border-color:var(--ifm-color-secondary)}:where(.button--secondary):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-secondary-dark);--ifm-button-border-color:var(--ifm-color-secondary-dark)}.button--secondary.button--active,.button--secondary:active{--ifm-button-background-color:var(--ifm-color-secondary-darker);--ifm-button-border-color:var(--ifm-color-secondary-darker)}:where(.button--success){--ifm-button-background-color:var(--ifm-color-success);--ifm-button-border-color:var(--ifm-color-success)}:where(.button--success):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-success-dark);--ifm-button-border-color:var(--ifm-color-success-dark)}.button--success.button--active,.button--success:active{--ifm-button-background-color:var(--ifm-color-success-darker);--ifm-button-border-color:var(--ifm-color-success-darker)}:where(.button--info){--ifm-button-background-color:var(--ifm-color-info);--ifm-button-border-color:var(--ifm-color-info)}:where(.button--info):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-info-dark);--ifm-button-border-color:var(--ifm-color-info-dark)}.button--info.button--active,.button--info:active{--ifm-button-background-color:var(--ifm-color-info-darker);--ifm-button-border-color:var(--ifm-color-info-darker)}:where(.button--warning){--ifm-button-background-color:var(--ifm-color-warning);--ifm-button-border-color:var(--ifm-color-warning)}:where(.button--warning):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-warning-dark);--ifm-button-border-color:var(--ifm-color-warning-dark)}.button--warning.button--active,.button--warning:active{--ifm-button-background-color:var(--ifm-color-warning-darker);--ifm-button-border-color:var(--ifm-color-warning-darker)}:where(.button--danger){--ifm-button-background-color:var(--ifm-color-danger);--ifm-button-border-color:var(--ifm-color-danger)}:where(.button--danger):not(.button--outline):hover{--ifm-button-background-color:var(--ifm-color-danger-dark);--ifm-button-border-color:var(--ifm-color-danger-dark)}.button--danger.button--active,.button--danger:active{--ifm-button-background-color:var(--ifm-color-danger-darker);--ifm-button-border-color:var(--ifm-color-danger-darker)}.button-group{display:inline-flex;gap:var(--ifm-button-group-spacing)}.button-group>.button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.button-group>.button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.button-group--block{display:flex;justify-content:stretch}.button-group--block>.button{flex-grow:1}.card{background-color:var(--ifm-card-background-color);border-radius:var(--ifm-card-border-radius);box-shadow:var(--ifm-global-shadow-lw);display:flex;flex-direction:column;overflow:hidden}.card__image{padding-top:var(--ifm-card-vertical-spacing)}.card__image:first-child{padding-top:0}.card__body,.card__footer,.card__header{padding:var(--ifm-card-vertical-spacing) var(--ifm-card-horizontal-spacing)}.card__body:not(:last-child),.card__footer:not(:last-child),.card__header:not(:last-child){padding-bottom:0}.card__body>:last-child,.card__footer>:last-child,.card__header>:last-child{margin-bottom:0}.card__footer{margin-top:auto}.table-of-contents{font-size:.8rem;margin-bottom:0;padding:var(--ifm-toc-padding-vertical) 0}.table-of-contents,.table-of-contents ul{list-style:none;padding-left:var(--ifm-toc-padding-horizontal)}.table-of-contents li{margin:var(--ifm-toc-padding-vertical) var(--ifm-toc-padding-horizontal)}.table-of-contents__left-border{border-left:1px solid var(--ifm-toc-border-color)}.table-of-contents__link{color:var(--ifm-toc-link-color);display:block}.table-of-contents__link--active,.table-of-contents__link--active code,.table-of-contents__link:hover,.table-of-contents__link:hover code{color:var(--ifm-color-primary);text-decoration:none}.close{color:var(--ifm-color-black);float:right;font-size:1.5rem;font-weight:var(--ifm-font-weight-bold);line-height:1;opacity:.5;padding:1rem;transition:opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.close:hover{opacity:.7}.close:focus,.theme-code-block-highlighted-line .codeLineNumber_Tfdd:before{opacity:.8}.dropdown{display:inline-flex;font-weight:var(--ifm-dropdown-font-weight);position:relative;vertical-align:top}.dropdown--hoverable:hover .dropdown__menu,.dropdown--show .dropdown__menu{opacity:1;pointer-events:all;transform:translateY(-1px);visibility:visible}#nprogress,.dropdown__menu,.navbar__item.dropdown .navbar__link:not([href]){pointer-events:none}.dropdown--right .dropdown__menu{left:inherit;right:0}.dropdown--nocaret .navbar__link:after{content:none!important}.dropdown__menu{background-color:var(--ifm-dropdown-background-color);border-radius:var(--ifm-global-radius);box-shadow:var(--ifm-global-shadow-md);left:0;max-height:80vh;min-width:10rem;opacity:0;overflow-y:auto;padding:.5rem;position:absolute;top:calc(100% - var(--ifm-navbar-item-padding-vertical) + .3rem);transform:translateY(-.625rem);transition-duration:var(--ifm-transition-fast);transition-property:opacity,transform,visibility;transition-timing-function:var(--ifm-transition-timing-default);visibility:hidden;z-index:var(--ifm-z-index-dropdown)}.sidebar_re4s,.tableOfContents_bqdL{max-height:calc(100vh - var(--ifm-navbar-height) - 2rem)}.menu__caret,.menu__link,.menu__list-item-collapsible{border-radius:.25rem;transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.dropdown__link{border-radius:.25rem;color:var(--ifm-dropdown-link-color);display:block;font-size:.875rem;margin-top:.2rem;padding:.25rem .5rem;white-space:nowrap}.dropdown__link--active,.dropdown__link:hover{background-color:var(--ifm-dropdown-hover-background-color);color:var(--ifm-dropdown-link-color);text-decoration:none}.dropdown__link--active,.dropdown__link--active:hover{--ifm-dropdown-link-color:var(--ifm-link-color)}.dropdown>.navbar__link:after{border-color:currentcolor #0000;border-style:solid;border-width:.4em .4em 0;content:"";margin-left:.3em;position:relative;top:2px;transform:translateY(-50%)}.footer{background-color:var(--ifm-footer-background-color);color:var(--ifm-footer-color);padding:var(--ifm-footer-padding-vertical) var(--ifm-footer-padding-horizontal)}.footer--dark{--ifm-footer-background-color:#303846;--ifm-footer-color:var(--ifm-footer-link-color);--ifm-footer-link-color:var(--ifm-color-secondary);--ifm-footer-title-color:var(--ifm-color-white)}.footer__links{margin-bottom:1rem}.footer__link-item{color:var(--ifm-footer-link-color);line-height:2}.footer__link-item:hover{color:var(--ifm-footer-link-hover-color)}.footer__link-separator{margin:0 var(--ifm-footer-link-horizontal-spacing)}.footer__logo{margin-top:1rem;max-width:var(--ifm-footer-logo-max-width)}.footer__title{color:var(--ifm-footer-title-color);font:700 var(--ifm-h4-font-size)/var(--ifm-heading-line-height) var(--ifm-font-family-base);margin-bottom:var(--ifm-heading-margin-bottom)}.menu,.navbar__link{font-weight:var(--ifm-font-weight-semibold)}.docItemContainer_Djhp article>:first-child,.docItemContainer_Djhp header+*,.footer__item{margin-top:0}.admonitionContent_BuS1>:last-child,.collapsibleContent_i85q p:last-child,.details_lb9f>summary>p:last-child,.footer__items{margin-bottom:0}.codeBlockStandalone_MEMb,[type=checkbox]{padding:0}.hero{align-items:center;background-color:var(--ifm-hero-background-color);color:var(--ifm-hero-text-color);display:flex;padding:4rem 2rem}.hero--primary{--ifm-hero-background-color:var(--ifm-color-primary);--ifm-hero-text-color:var(--ifm-font-color-base-inverse)}.hero--dark{--ifm-hero-background-color:#303846;--ifm-hero-text-color:var(--ifm-color-white)}.hero__title,.title_f1Hy{font-size:3rem}.hero__subtitle{font-size:1.5rem}.menu__list{margin:0;padding-left:0}.menu__caret,.menu__link{padding:var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal)}.menu__list .menu__list{flex:0 0 100%;margin-top:.25rem;padding-left:var(--ifm-menu-link-padding-horizontal)}.menu__list-item:not(:first-child){margin-top:.25rem}.menu__list-item--collapsed .menu__list{height:0;overflow:hidden}.details_lb9f[data-collapsed=false].isBrowser_bmU9>summary:before,.details_lb9f[open]:not(.isBrowser_bmU9)>summary:before,.menu__list-item--collapsed .menu__caret:before,.menu__list-item--collapsed .menu__link--sublist:after{transform:rotate(90deg)}.menu__list-item-collapsible{display:flex;flex-wrap:wrap;position:relative}.menu__caret:hover,.menu__link:hover,.menu__list-item-collapsible--active,.menu__list-item-collapsible:hover{background:var(--ifm-menu-color-background-hover)}.menu__list-item-collapsible .menu__link--active,.menu__list-item-collapsible .menu__link:hover{background:none!important}.menu__caret,.menu__link{align-items:center;display:flex}.menu__link{color:var(--ifm-menu-color);flex:1;line-height:1.25}.menu__link:hover{color:var(--ifm-menu-color);text-decoration:none}.menu__caret:before,.menu__link--sublist-caret:after{content:"";height:1.25rem;transform:rotate(180deg);transition:transform var(--ifm-transition-fast) linear;width:1.25rem;filter:var(--ifm-menu-link-sublist-icon-filter)}.menu__link--sublist-caret:after{background:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem;margin-left:auto;min-width:1.25rem}.menu__link--active,.menu__link--active:hover{color:var(--ifm-menu-color-active)}.navbar__brand,.navbar__link{color:var(--ifm-navbar-link-color)}.menu__link--active:not(.menu__link--sublist){background-color:var(--ifm-menu-color-background-active)}.menu__caret:before{background:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem}.navbar--dark,html[data-theme=dark]{--ifm-menu-link-sublist-icon-filter:invert(100%) sepia(94%) saturate(17%) hue-rotate(223deg) brightness(104%) contrast(98%)}.navbar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-navbar-shadow);height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical) var(--ifm-navbar-padding-horizontal)}.navbar,.navbar>.container,.navbar>.container-fluid{display:flex}.navbar--fixed-top{position:sticky;top:0;z-index:var(--ifm-z-index-fixed)}.navbar-sidebar,.navbar-sidebar__backdrop{bottom:0;opacity:0;position:fixed;transition-duration:var(--ifm-transition-fast);transition-timing-function:ease-in-out;left:0;top:0;visibility:hidden}.navbar__inner{display:flex;flex-wrap:wrap;justify-content:space-between;width:100%}.navbar__brand{align-items:center;display:flex;margin-right:1rem;min-width:0}.navbar__brand:hover{color:var(--ifm-navbar-link-hover-color);text-decoration:none}.announcementBarContent_xLdY,.navbar__title{flex:1 1 auto}.navbar__toggle{display:none;margin-right:.5rem}.navbar__logo{flex:0 0 auto;height:2rem;margin-right:.5rem}.navbar__items{align-items:center;display:flex;flex:1;min-width:0}.navbar__items--center{flex:0 0 auto}.navbar__items--center .navbar__brand{margin:0}.navbar__items--center+.navbar__items--right{flex:1}.navbar__items--right{flex:0 0 auto;justify-content:flex-end}.navbar__items--right>:last-child{padding-right:0}.navbar__item{display:inline-block;padding:var(--ifm-navbar-item-padding-vertical) var(--ifm-navbar-item-padding-horizontal)}.navbar__link--active,.navbar__link:hover{color:var(--ifm-navbar-link-hover-color);text-decoration:none}.navbar--dark,.navbar--primary{--ifm-menu-color:var(--ifm-color-gray-300);--ifm-navbar-link-color:var(--ifm-color-gray-100);--ifm-navbar-search-input-background-color:#ffffff1a;--ifm-navbar-search-input-placeholder-color:#ffffff80;color:var(--ifm-color-white)}.navbar--dark{--ifm-navbar-background-color:#242526;--ifm-menu-color-background-active:#ffffff0d;--ifm-navbar-search-input-color:var(--ifm-color-white)}.navbar--primary{--ifm-navbar-background-color:var(--ifm-color-primary);--ifm-navbar-link-hover-color:var(--ifm-color-white);--ifm-menu-color-active:var(--ifm-color-white);--ifm-navbar-search-input-color:var(--ifm-color-emphasis-500)}.navbar__search-input{-webkit-appearance:none;appearance:none;background:var(--ifm-navbar-search-input-background-color) var(--ifm-navbar-search-input-icon) no-repeat .75rem center/1rem 1rem;border:none;border-radius:2rem;color:var(--ifm-navbar-search-input-color);cursor:text;display:inline-block;font-size:.9rem;height:2rem;padding:0 .5rem 0 2.25rem;width:12.5rem}.navbar__search-input::placeholder{color:var(--ifm-navbar-search-input-placeholder-color)}.navbar-sidebar{background-color:var(--ifm-navbar-background-color);box-shadow:var(--ifm-global-shadow-md);transform:translate3d(-100%,0,0);transition-property:opacity,visibility,transform;width:var(--ifm-navbar-sidebar-width)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar__items{transform:translateZ(0)}.navbar-sidebar--show .navbar-sidebar,.navbar-sidebar--show .navbar-sidebar__backdrop{opacity:1;visibility:visible}.navbar-sidebar__backdrop{background-color:#0009;right:0;transition-property:opacity,visibility}.navbar-sidebar__brand{align-items:center;box-shadow:var(--ifm-navbar-shadow);display:flex;flex:1;height:var(--ifm-navbar-height);padding:var(--ifm-navbar-padding-vertical) var(--ifm-navbar-padding-horizontal)}.navbar-sidebar__items{display:flex;height:calc(100% - var(--ifm-navbar-height));transition:transform var(--ifm-transition-fast) ease-in-out}.navbar-sidebar__items--show-secondary{transform:translate3d(calc((var(--ifm-navbar-sidebar-width))*-1),0,0)}.navbar-sidebar__item{flex-shrink:0;padding:.5rem;width:calc(var(--ifm-navbar-sidebar-width))}.navbar-sidebar__back{background:var(--ifm-menu-color-background-active);font-size:15px;font-weight:var(--ifm-button-font-weight);margin:0 0 .2rem -.5rem;padding:.6rem 1.5rem;position:relative;text-align:left;top:-.5rem;width:calc(100% + 1rem)}.navbar-sidebar__close{display:flex;margin-left:auto}.pagination{column-gap:var(--ifm-pagination-page-spacing);display:flex;font-size:var(--ifm-pagination-font-size);padding-left:0}.pagination--sm{--ifm-pagination-font-size:0.8rem;--ifm-pagination-padding-horizontal:0.8rem;--ifm-pagination-padding-vertical:0.2rem}.pagination--lg{--ifm-pagination-font-size:1.2rem;--ifm-pagination-padding-horizontal:1.2rem;--ifm-pagination-padding-vertical:0.3rem}.pagination__item{display:inline-flex}.pagination__item>span{padding:var(--ifm-pagination-padding-vertical)}.pagination__item--active .pagination__link{color:var(--ifm-pagination-color-active)}.pagination__item--active .pagination__link,.pagination__item:not(.pagination__item--active):hover .pagination__link{background:var(--ifm-pagination-item-active-background)}.pagination__item--disabled,.pagination__item[disabled]{opacity:.25;pointer-events:none}.pagination__link{border-radius:var(--ifm-pagination-border-radius);color:var(--ifm-font-color-base);display:inline-block;padding:var(--ifm-pagination-padding-vertical) var(--ifm-pagination-padding-horizontal);transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pagination__link:hover,.sidebarItemLink_mo7H:hover{text-decoration:none}.pagination-nav{grid-gap:var(--ifm-spacing-horizontal);display:grid;gap:var(--ifm-spacing-horizontal);grid-template-columns:repeat(2,1fr)}.pagination-nav__link{border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-pagination-nav-border-radius);display:block;height:100%;line-height:var(--ifm-heading-line-height);padding:var(--ifm-global-spacing);transition:border-color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.pagination-nav__link:hover{border-color:var(--ifm-pagination-nav-color-hover);text-decoration:none}.pagination-nav__link--next{grid-column:2/3;text-align:right}.pagination-nav__label{font-size:var(--ifm-h4-font-size);font-weight:var(--ifm-heading-font-weight);word-break:break-word}.pagination-nav__link--prev .pagination-nav__label:before{content:"« "}.pagination-nav__link--next .pagination-nav__label:after{content:" »"}.pagination-nav__sublabel{color:var(--ifm-color-content-secondary);font-size:var(--ifm-h5-font-size);font-weight:var(--ifm-font-weight-semibold);margin-bottom:.25rem}.pills__item,.sidebarItemTitle_pO2u,.tabs{font-weight:var(--ifm-font-weight-bold)}.pills{display:flex;gap:var(--ifm-pills-spacing);padding-left:0}.pills__item{border-radius:.5rem;cursor:pointer;display:inline-block;padding:.25rem 1rem;transition:background var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.tabs,:not(.containsTaskList_mC6p>li)>.containsTaskList_mC6p{padding-left:0}.pills__item--active{color:var(--ifm-pills-color-active)}.pills__item--active,.pills__item:not(.pills__item--active):hover{background:var(--ifm-pills-color-background-active)}.pills--block{justify-content:stretch}.pills--block .pills__item{flex-grow:1;text-align:center}.tabs{color:var(--ifm-tabs-color);display:flex;margin-bottom:0;overflow-x:auto}.tabs__item{border-bottom:3px solid #0000;border-radius:var(--ifm-global-radius);cursor:pointer;display:inline-flex;padding:var(--ifm-tabs-padding-vertical) var(--ifm-tabs-padding-horizontal);transition:background-color var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.tabs__item--active{border-bottom-color:var(--ifm-tabs-color-active-border);border-bottom-left-radius:0;border-bottom-right-radius:0;color:var(--ifm-tabs-color-active)}.tabs__item:hover{background-color:var(--ifm-hover-overlay)}.tabs--block{justify-content:stretch}.tabs--block .tabs__item{flex-grow:1;justify-content:center}html[data-theme=dark]{--ifm-color-scheme:dark;--ifm-color-emphasis-0:var(--ifm-color-gray-1000);--ifm-color-emphasis-100:var(--ifm-color-gray-900);--ifm-color-emphasis-200:var(--ifm-color-gray-800);--ifm-color-emphasis-300:var(--ifm-color-gray-700);--ifm-color-emphasis-400:var(--ifm-color-gray-600);--ifm-color-emphasis-600:var(--ifm-color-gray-400);--ifm-color-emphasis-700:var(--ifm-color-gray-300);--ifm-color-emphasis-800:var(--ifm-color-gray-200);--ifm-color-emphasis-900:var(--ifm-color-gray-100);--ifm-color-emphasis-1000:var(--ifm-color-gray-0);--ifm-background-color:#1b1b1d;--ifm-background-surface-color:#242526;--ifm-hover-overlay:#ffffff0d;--ifm-color-content:#e3e3e3;--ifm-color-content-secondary:#fff;--ifm-breadcrumb-separator-filter:invert(64%) sepia(11%) saturate(0%) hue-rotate(149deg) brightness(99%) contrast(95%);--ifm-code-background:#ffffff1a;--ifm-scrollbar-track-background-color:#444;--ifm-scrollbar-thumb-background-color:#686868;--ifm-scrollbar-thumb-hover-background-color:#7a7a7a;--ifm-table-stripe-background:#ffffff12;--ifm-toc-border-color:var(--ifm-color-emphasis-200);--ifm-color-primary-contrast-background:#102445;--ifm-color-primary-contrast-foreground:#ebf2fc;--ifm-color-secondary-contrast-background:#474748;--ifm-color-secondary-contrast-foreground:#fdfdfe;--ifm-color-success-contrast-background:#003100;--ifm-color-success-contrast-foreground:#e6f6e6;--ifm-color-info-contrast-background:#193c47;--ifm-color-info-contrast-foreground:#eef9fd;--ifm-color-warning-contrast-background:#4d3800;--ifm-color-warning-contrast-foreground:#fff8e6;--ifm-color-danger-contrast-background:#4b1113;--ifm-color-danger-contrast-foreground:#ffebec}#nprogress .bar{background:var(--docusaurus-progress-bar-color);height:2px;left:0;position:fixed;top:0;width:100%;z-index:1031}#nprogress .peg{box-shadow:0 0 10px var(--docusaurus-progress-bar-color),0 0 5px var(--docusaurus-progress-bar-color);height:100%;opacity:1;position:absolute;right:0;transform:rotate(3deg) translateY(-4px);width:100px}[data-theme=dark]{--ifm-color-primary:#25c2a0;--ifm-color-primary-dark:#21af90;--ifm-color-primary-darker:#1fa588;--ifm-color-primary-darkest:#1a8870;--ifm-color-primary-light:#29d5b0;--ifm-color-primary-lighter:#32d8b4;--ifm-color-primary-lightest:#4fddbf;--docusaurus-highlighted-code-line-bg:#0000004d}.backToTopButton_sjWU{background-color:var(--ifm-color-emphasis-200);border-radius:50%;bottom:1.3rem;box-shadow:var(--ifm-global-shadow-lw);height:3rem;opacity:0;position:fixed;right:1.3rem;transform:scale(0);transition:all var(--ifm-transition-fast) var(--ifm-transition-timing-default);visibility:hidden;width:3rem;z-index:calc(var(--ifm-z-index-fixed) - 1)}.backToTopButton_sjWU:after{background-color:var(--ifm-color-emphasis-1000);content:" ";display:inline-block;height:100%;-webkit-mask:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem no-repeat;mask:var(--ifm-menu-link-sublist-icon) 50%/2rem 2rem no-repeat;width:100%}.backToTopButtonShow_xfvO{opacity:1;transform:scale(1);visibility:visible}.skipToContent_fXgn{background-color:var(--ifm-background-surface-color);color:var(--ifm-color-emphasis-900);left:100%;padding:calc(var(--ifm-global-spacing)/2) var(--ifm-global-spacing);position:fixed;top:1rem;z-index:calc(var(--ifm-z-index-fixed) + 1)}.skipToContent_fXgn:focus{box-shadow:var(--ifm-global-shadow-md);left:1rem}.closeButton_CVFx{line-height:0;padding:0}.content_knG7{font-size:85%;padding:5px 0;text-align:center}.content_knG7 a{color:inherit;text-decoration:underline}.announcementBar_mb4j{align-items:center;background-color:var(--ifm-color-white);border-bottom:1px solid var(--ifm-color-emphasis-100);color:var(--ifm-color-black);display:flex;height:var(--docusaurus-announcement-bar-height)}#__docusaurus-base-url-issue-banner-container,.docSidebarContainer_YfHR,.sidebarLogo_isFc,.themedComponent_mlkZ,[data-theme=dark] .lightToggleIcon_pyhR,[data-theme=light] .darkToggleIcon_wfgR,html[data-announcement-bar-initially-dismissed=true] .announcementBar_mb4j{display:none}.announcementBarPlaceholder_vyr4{flex:0 0 10px}.announcementBarClose_gvF7{align-self:stretch;flex:0 0 30px}.toggle_vylO{height:2rem;width:2rem}.toggleButton_gllP{align-items:center;border-radius:50%;display:flex;height:100%;justify-content:center;transition:background var(--ifm-transition-fast);width:100%}.toggleButton_gllP:hover{background:var(--ifm-color-emphasis-200)}.toggleButtonDisabled_aARS{cursor:not-allowed}.darkNavbarColorModeToggle_X3D1:hover{background:var(--ifm-color-gray-800)}[data-theme=dark] .themedComponent--dark_xIcU,[data-theme=light] .themedComponent--light_NVdE,html:not([data-theme]) .themedComponent--light_NVdE{display:initial}[data-theme=dark]:root{--docusaurus-collapse-button-bg:#ffffff0d;--docusaurus-collapse-button-bg-hover:#ffffff1a}.collapseSidebarButton_PEFL{display:none;margin:0}.iconExternalLink_nPIU{margin-left:.3rem}.docMainContainer_TBSr,.docRoot_UBD9{display:flex;width:100%}.docsWrapper_hBAB{display:flex;flex:1 0 auto}.dropdownNavbarItemMobile_S0Fm{cursor:pointer}.iconLanguage_nlXk{margin-right:5px;vertical-align:text-bottom}@supports selector(:has(*)){.navbarSearchContainer_Bca1:not(:has(>*)){display:none}}.navbarHideable_m1mJ{transition:transform var(--ifm-transition-fast) ease}.navbarHidden_jGov{transform:translate3d(0,calc(-100% - 2px),0)}.errorBoundaryError_a6uf{color:red;white-space:pre-wrap}.errorBoundaryFallback_VBag{color:red;padding:.55rem}.footerLogoLink_BH7S{opacity:.5;transition:opacity var(--ifm-transition-fast) var(--ifm-transition-timing-default)}.footerLogoLink_BH7S:hover,.hash-link:focus,:hover>.hash-link{opacity:1}body:not(.navigation-with-keyboard) :not(input):focus{outline:0}.anchorWithStickyNavbar_LWe7{scroll-margin-top:calc(var(--ifm-navbar-height) + .5rem)}.anchorWithHideOnScrollNavbar_WYt5{scroll-margin-top:.5rem}.hash-link{opacity:0;padding-left:.5rem;transition:opacity var(--ifm-transition-fast);-webkit-user-select:none;user-select:none}.hash-link:before{content:"#"}.mainWrapper_z2l0{display:flex;flex:1 0 auto;flex-direction:column}.docusaurus-mt-lg{margin-top:3rem}#__docusaurus{display:flex;flex-direction:column;min-height:100%}.sidebar_re4s{overflow-y:auto;position:sticky;top:calc(var(--ifm-navbar-height) + 2rem)}.sidebarItemTitle_pO2u{font-size:var(--ifm-h3-font-size)}.container_mt6G,.sidebarItemList_Yudw{font-size:.9rem}.sidebarItem__DBe{margin-top:.7rem}.sidebarItemLink_mo7H{color:var(--ifm-font-color-base);display:block}.sidebarItemLinkActive_I1ZP{color:var(--ifm-color-primary)!important}.buttonGroup__atx button,.codeBlockContainer_Ckt0{background:var(--prism-background-color);color:var(--prism-color)}.authorCol_Hf19{flex-grow:1!important;max-width:inherit!important}.imageOnlyAuthorRow_pa_O{display:flex;flex-flow:row wrap}.buttons_AeoN,.features_t9lD{align-items:center;display:flex}.imageOnlyAuthorCol_G86a{margin-left:.3rem;margin-right:.3rem}.features_t9lD{padding:2rem 0;width:100%}.featureSvg_GfXr{height:200px;width:200px}.heroBanner_qdFl{overflow:hidden;padding:4rem 0;position:relative;text-align:center}.buttons_AeoN{justify-content:center}.codeBlockContainer_Ckt0{border-radius:var(--ifm-code-border-radius);box-shadow:var(--ifm-global-shadow-lw);margin-bottom:var(--ifm-leading)}.codeBlockContent_biex{border-radius:inherit;direction:ltr;position:relative}.codeBlockTitle_Ktv7{border-bottom:1px solid var(--ifm-color-emphasis-300);border-top-left-radius:inherit;border-top-right-radius:inherit;font-size:var(--ifm-code-font-size);font-weight:500;padding:.75rem var(--ifm-pre-padding)}.codeBlock_bY9V{--ifm-pre-background:var(--prism-background-color);margin:0;padding:0}.codeBlockTitle_Ktv7+.codeBlockContent_biex .codeBlock_bY9V{border-top-left-radius:0;border-top-right-radius:0}.codeBlockLines_e6Vv{float:left;font:inherit;min-width:100%;padding:var(--ifm-pre-padding)}.codeBlockLinesWithNumbering_o6Pm{display:table;padding:var(--ifm-pre-padding) 0}.buttonGroup__atx{column-gap:.2rem;display:flex;position:absolute;right:calc(var(--ifm-pre-padding)/2);top:calc(var(--ifm-pre-padding)/2)}.buttonGroup__atx button{align-items:center;border:1px solid var(--ifm-color-emphasis-300);border-radius:var(--ifm-global-radius);display:flex;line-height:0;opacity:0;padding:.4rem;transition:opacity var(--ifm-transition-fast) ease-in-out}.buttonGroup__atx button:focus-visible,.buttonGroup__atx button:hover{opacity:1!important}.theme-code-block:hover .buttonGroup__atx button{opacity:.4}.iconEdit_Z9Sw{margin-right:.3em;vertical-align:sub}:where(:root){--docusaurus-highlighted-code-line-bg:#484d5b}:where([data-theme=dark]){--docusaurus-highlighted-code-line-bg:#646464}.theme-code-block-highlighted-line{background-color:var(--docusaurus-highlighted-code-line-bg);display:block;margin:0 calc(var(--ifm-pre-padding)*-1);padding:0 var(--ifm-pre-padding)}.codeLine_lJS_{counter-increment:a;display:table-row}.codeLineNumber_Tfdd{background:var(--ifm-pre-background);display:table-cell;left:0;overflow-wrap:normal;padding:0 var(--ifm-pre-padding);position:sticky;text-align:right;width:1%}.codeLineNumber_Tfdd:before{content:counter(a);opacity:.4}.codeLineContent_feaV{padding-right:var(--ifm-pre-padding)}.tag_zVej{border:1px solid var(--docusaurus-tag-list-border);transition:border var(--ifm-transition-fast)}.tag_zVej:hover{--docusaurus-tag-list-border:var(--ifm-link-color);text-decoration:none}.tagRegular_sFm0{border-radius:var(--ifm-global-radius);font-size:90%;padding:.2rem .5rem .3rem}.tagWithCount_h2kH{align-items:center;border-left:0;display:flex;padding:0 .5rem 0 1rem;position:relative}.tagWithCount_h2kH:after,.tagWithCount_h2kH:before{border:1px solid var(--docusaurus-tag-list-border);content:"";position:absolute;top:50%;transition:inherit}.tagWithCount_h2kH:before{border-bottom:0;border-right:0;height:1.18rem;right:100%;transform:translate(50%,-50%) rotate(-45deg);width:1.18rem}.tagWithCount_h2kH:after{border-radius:50%;height:.5rem;left:0;transform:translateY(-50%);width:.5rem}.tagWithCount_h2kH span{background:var(--ifm-color-secondary);border-radius:var(--ifm-global-radius);color:var(--ifm-color-black);font-size:.7rem;line-height:1.2;margin-left:.3rem;padding:.1rem .4rem}.tag_Nnez{display:inline-block;margin:.5rem .5rem 0 1rem}.theme-code-block:hover .copyButtonCopied_obH4{opacity:1!important}.copyButtonIcons_eSgA{height:1.125rem;position:relative;width:1.125rem}.copyButtonIcon_y97N,.copyButtonSuccessIcon_LjdS{fill:currentColor;height:inherit;left:0;opacity:inherit;position:absolute;top:0;transition:all var(--ifm-transition-fast) ease;width:inherit}.copyButtonSuccessIcon_LjdS{color:#00d600;left:50%;opacity:0;top:50%;transform:translate(-50%,-50%) scale(.33)}.copyButtonCopied_obH4 .copyButtonIcon_y97N{opacity:0;transform:scale(.33)}.copyButtonCopied_obH4 .copyButtonSuccessIcon_LjdS{opacity:1;transform:translate(-50%,-50%) scale(1);transition-delay:75ms}.tags_jXut{display:inline}.tag_QGVx{display:inline-block;margin:0 .4rem .5rem 0}.lastUpdated_vwxv{font-size:smaller;font-style:italic;margin-top:.2rem}.tocCollapsibleButton_TO0P{align-items:center;display:flex;font-size:inherit;justify-content:space-between;padding:.4rem .8rem;width:100%}.tocCollapsibleButton_TO0P:after{background:var(--ifm-menu-link-sublist-icon) 50% 50%/2rem 2rem no-repeat;content:"";filter:var(--ifm-menu-link-sublist-icon-filter);height:1.25rem;transform:rotate(180deg);transition:transform var(--ifm-transition-fast);width:1.25rem}.tocCollapsibleButtonExpanded_MG3E:after,.tocCollapsibleExpanded_sAul{transform:none}.tocCollapsible_ETCw{background-color:var(--ifm-menu-color-background-active);border-radius:var(--ifm-global-radius);margin:1rem 0}.tocCollapsibleContent_vkbj>ul{border-left:none;border-top:1px solid var(--ifm-color-emphasis-300);font-size:15px;padding:.2rem 0}.tocCollapsibleContent_vkbj ul li{margin:.4rem .8rem}.tocCollapsibleContent_vkbj a{display:block}.wordWrapButtonIcon_Bwma{height:1.2rem;width:1.2rem}.details_lb9f{--docusaurus-details-summary-arrow-size:0.38rem;--docusaurus-details-transition:transform 200ms ease;--docusaurus-details-decoration-color:grey}.details_lb9f>summary{cursor:pointer;padding-left:1rem;position:relative}.details_lb9f>summary::-webkit-details-marker{display:none}.details_lb9f>summary:before{border-color:#0000 #0000 #0000 var(--docusaurus-details-decoration-color);border-style:solid;border-width:var(--docusaurus-details-summary-arrow-size);content:"";left:0;position:absolute;top:.45rem;transform:rotate(0);transform-origin:calc(var(--docusaurus-details-summary-arrow-size)/2) 50%;transition:var(--docusaurus-details-transition)}.collapsibleContent_i85q{border-top:1px solid var(--docusaurus-details-decoration-color);margin-top:1rem;padding-top:1rem}.details_b_Ee{--docusaurus-details-decoration-color:var(--ifm-alert-border-color);--docusaurus-details-transition:transform var(--ifm-transition-fast) ease;border:1px solid var(--ifm-alert-border-color);margin:0 0 var(--ifm-spacing-vertical)}.img_ev3q{height:auto}.admonition_xJq3{margin-bottom:1em}.admonitionHeading_Gvgb{font:var(--ifm-heading-font-weight) var(--ifm-h5-font-size)/var(--ifm-heading-line-height) var(--ifm-heading-font-family)}.admonitionHeading_Gvgb:not(:last-child){margin-bottom:.3rem}.admonitionHeading_Gvgb code{text-transform:none}.admonitionIcon_Rf37{display:inline-block;margin-right:.4em;vertical-align:middle}.admonitionIcon_Rf37 svg{fill:var(--ifm-alert-foreground-color);display:inline-block;height:1.6em;width:1.6em}.blogPostFooterDetailsFull_mRVl{flex-direction:column}.tableOfContents_bqdL{overflow-y:auto;position:sticky;top:calc(var(--ifm-navbar-height) + 1rem)}.breadcrumbHomeIcon_YNFT{height:1.1rem;position:relative;top:1px;vertical-align:top;width:1.1rem}.breadcrumbsContainer_Z_bl{--ifm-breadcrumb-size-multiplier:0.8;margin-bottom:.8rem}.mdxPageWrapper_j9I6{justify-content:center}@media (min-width:997px){.collapseSidebarButton_PEFL,.expandButton_TmdG{background-color:var(--docusaurus-collapse-button-bg)}:root{--docusaurus-announcement-bar-height:30px}.announcementBarClose_gvF7,.announcementBarPlaceholder_vyr4{flex-basis:50px}.collapseSidebarButton_PEFL{border:1px solid var(--ifm-toc-border-color);border-radius:0;bottom:0;display:block!important;height:40px;position:sticky}.collapseSidebarButtonIcon_kv0_{margin-top:4px;transform:rotate(180deg)}.expandButtonIcon_i1dp,[dir=rtl] .collapseSidebarButtonIcon_kv0_{transform:rotate(0)}.collapseSidebarButton_PEFL:focus,.collapseSidebarButton_PEFL:hover,.expandButton_TmdG:focus,.expandButton_TmdG:hover{background-color:var(--docusaurus-collapse-button-bg-hover)}.menuHtmlItem_M9Kj{padding:var(--ifm-menu-link-padding-vertical) var(--ifm-menu-link-padding-horizontal)}.menu_SIkG{flex-grow:1;padding:.5rem}@supports (scrollbar-gutter:stable){.menu_SIkG{padding:.5rem 0 .5rem .5rem;scrollbar-gutter:stable}}.menuWithAnnouncementBar_GW3s{margin-bottom:var(--docusaurus-announcement-bar-height)}.sidebar_njMd{display:flex;flex-direction:column;height:100%;padding-top:var(--ifm-navbar-height);width:var(--doc-sidebar-width)}.sidebarWithHideableNavbar_wUlq{padding-top:0}.sidebarHidden_VK0M{opacity:0;visibility:hidden}.sidebarLogo_isFc{align-items:center;color:inherit!important;display:flex!important;margin:0 var(--ifm-navbar-padding-horizontal);max-height:var(--ifm-navbar-height);min-height:var(--ifm-navbar-height);text-decoration:none!important}.sidebarLogo_isFc img{height:2rem;margin-right:.5rem}.expandButton_TmdG{align-items:center;display:flex;height:100%;justify-content:center;position:absolute;right:0;top:0;transition:background-color var(--ifm-transition-fast) ease;width:100%}[dir=rtl] .expandButtonIcon_i1dp{transform:rotate(180deg)}.docSidebarContainer_YfHR{border-right:1px solid var(--ifm-toc-border-color);-webkit-clip-path:inset(0);clip-path:inset(0);display:block;margin-top:calc(var(--ifm-navbar-height)*-1);transition:width var(--ifm-transition-fast) ease;width:var(--doc-sidebar-width);will-change:width}.docSidebarContainerHidden_DPk8{cursor:pointer;width:var(--doc-sidebar-hidden-width)}.sidebarViewport_aRkj{height:100%;max-height:100vh;position:sticky;top:0}.docMainContainer_TBSr{flex-grow:1;max-width:calc(100% - var(--doc-sidebar-width))}.docMainContainerEnhanced_lQrH{max-width:calc(100% - var(--doc-sidebar-hidden-width))}.docItemWrapperEnhanced_JWYK{max-width:calc(var(--ifm-container-width) + var(--doc-sidebar-width))!important}.navbarSearchContainer_Bca1{padding:var(--ifm-navbar-item-padding-vertical) var(--ifm-navbar-item-padding-horizontal)}.lastUpdated_vwxv{text-align:right}.tocMobile_ITEo{display:none}.docItemCol_VOVn{max-width:75%!important}}@media (min-width:1440px){.container{max-width:var(--ifm-container-width-xl)}}@media (max-width:996px){.col{--ifm-col-width:100%;flex-basis:var(--ifm-col-width);margin-left:0}.footer{--ifm-footer-padding-horizontal:0}.colorModeToggle_DEke,.footer__link-separator,.navbar__item,.sidebar_re4s,.tableOfContents_bqdL{display:none}.footer__col{margin-bottom:calc(var(--ifm-spacing-vertical)*3)}.footer__link-item{display:block}.hero{padding-left:0;padding-right:0}.navbar>.container,.navbar>.container-fluid{padding:0}.navbar__toggle{display:inherit}.navbar__search-input{width:9rem}.pills--block,.tabs--block{flex-direction:column}.navbarSearchContainer_Bca1{position:absolute;right:var(--ifm-navbar-padding-horizontal)}.docItemContainer_F8PC{padding:0 .3rem}}@media screen and (max-width:996px){.heroBanner_qdFl{padding:2rem}}@media (max-width:576px){.markdown h1:first-child{--ifm-h1-font-size:2rem}.markdown>h2{--ifm-h2-font-size:1.5rem}.markdown>h3{--ifm-h3-font-size:1.25rem}.title_f1Hy{font-size:2rem}}@media (hover:hover){.backToTopButton_sjWU:hover{background-color:var(--ifm-color-emphasis-300)}}@media (pointer:fine){.thin-scrollbar{scrollbar-width:thin}.thin-scrollbar::-webkit-scrollbar{height:var(--ifm-scrollbar-size);width:var(--ifm-scrollbar-size)}.thin-scrollbar::-webkit-scrollbar-track{background:var(--ifm-scrollbar-track-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb{background:var(--ifm-scrollbar-thumb-background-color);border-radius:10px}.thin-scrollbar::-webkit-scrollbar-thumb:hover{background:var(--ifm-scrollbar-thumb-hover-background-color)}}@media (prefers-reduced-motion:reduce){:root{--ifm-transition-fast:0ms;--ifm-transition-slow:0ms}}@media print{.announcementBar_mb4j,.footer,.menu,.navbar,.pagination-nav,.table-of-contents,.tocMobile_ITEo{display:none}.tabs{page-break-inside:avoid}.codeBlockLines_e6Vv{white-space:pre-wrap}} \ No newline at end of file diff --git a/docs/images/workflow-with-guards.png b/assets/images/workflow-with-guards-9421d2ccbded4218039d3c55d9e07d64.png similarity index 100% rename from docs/images/workflow-with-guards.png rename to assets/images/workflow-with-guards-9421d2ccbded4218039d3c55d9e07d64.png diff --git a/assets/js/01a85c17.8dd5da0a.js b/assets/js/01a85c17.8dd5da0a.js new file mode 100644 index 00000000..0125ab59 --- /dev/null +++ b/assets/js/01a85c17.8dd5da0a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[209],{6535:(e,s,t)=>{t.d(s,{A:()=>v});var a=t(6540),i=t(4164),r=t(781),l=t(4581),n=t(8774),c=t(1312),o=t(6347),m=t(9169);function d(e){const{pathname:s}=(0,o.zy)();return(0,a.useMemo)((()=>e.filter((e=>function(e,s){return!(e.unlisted&&!(0,m.ys)(e.permalink,s))}(e,s)))),[e,s])}const u={sidebar:"sidebar_re4s",sidebarItemTitle:"sidebarItemTitle_pO2u",sidebarItemList:"sidebarItemList_Yudw",sidebarItem:"sidebarItem__DBe",sidebarItemLink:"sidebarItemLink_mo7H",sidebarItemLinkActive:"sidebarItemLinkActive_I1ZP"};var g=t(4848);function h(e){let{sidebar:s}=e;const t=d(s.items);return(0,g.jsx)("aside",{className:"col col--3",children:(0,g.jsxs)("nav",{className:(0,i.A)(u.sidebar,"thin-scrollbar"),"aria-label":(0,c.T)({id:"theme.blog.sidebar.navAriaLabel",message:"Blog recent posts navigation",description:"The ARIA label for recent posts in the blog sidebar"}),children:[(0,g.jsx)("div",{className:(0,i.A)(u.sidebarItemTitle,"margin-bottom--md"),children:s.title}),(0,g.jsx)("ul",{className:(0,i.A)(u.sidebarItemList,"clean-list"),children:t.map((e=>(0,g.jsx)("li",{className:u.sidebarItem,children:(0,g.jsx)(n.A,{isNavLink:!0,to:e.permalink,className:u.sidebarItemLink,activeClassName:u.sidebarItemLinkActive,children:e.title})},e.permalink)))})]})})}var b=t(5600);function p(e){let{sidebar:s}=e;const t=d(s.items);return(0,g.jsx)("ul",{className:"menu__list",children:t.map((e=>(0,g.jsx)("li",{className:"menu__list-item",children:(0,g.jsx)(n.A,{isNavLink:!0,to:e.permalink,className:"menu__link",activeClassName:"menu__link--active",children:e.title})},e.permalink)))})}function j(e){return(0,g.jsx)(b.GX,{component:p,props:e})}function x(e){let{sidebar:s}=e;const t=(0,l.l)();return s?.items.length?"mobile"===t?(0,g.jsx)(j,{sidebar:s}):(0,g.jsx)(h,{sidebar:s}):null}function v(e){const{sidebar:s,toc:t,children:a,...l}=e,n=s&&s.items.length>0;return(0,g.jsx)(r.A,{...l,children:(0,g.jsx)("div",{className:"container margin-vert--lg",children:(0,g.jsxs)("div",{className:"row",children:[(0,g.jsx)(x,{sidebar:s}),(0,g.jsx)("main",{className:(0,i.A)("col",{"col--7":n,"col--9 col--offset-1":!n}),itemScope:!0,itemType:"https://schema.org/Blog",children:a}),t&&(0,g.jsx)("div",{className:"col col--2",children:t})]})})})}},9158:(e,s,t)=>{t.r(s),t.d(s,{default:()=>p});t(6540);var a=t(4164),i=t(1312);const r=()=>(0,i.T)({id:"theme.tags.tagsPageTitle",message:"Tags",description:"The title of the tag list page"});var l=t(1003),n=t(7559),c=t(6535),o=t(6133),m=t(1107);const d={tag:"tag_Nnez"};var u=t(4848);function g(e){let{letterEntry:s}=e;return(0,u.jsxs)("article",{children:[(0,u.jsx)(m.A,{as:"h2",id:s.letter,children:s.letter}),(0,u.jsx)("ul",{className:"padding--none",children:s.tags.map((e=>(0,u.jsx)("li",{className:d.tag,children:(0,u.jsx)(o.A,{...e})},e.permalink)))}),(0,u.jsx)("hr",{})]})}function h(e){let{tags:s}=e;const t=function(e){const s={};return Object.values(e).forEach((e=>{const t=function(e){return e[0].toUpperCase()}(e.label);s[t]??=[],s[t].push(e)})),Object.entries(s).sort(((e,s)=>{let[t]=e,[a]=s;return t.localeCompare(a)})).map((e=>{let[s,t]=e;return{letter:s,tags:t.sort(((e,s)=>e.label.localeCompare(s.label)))}}))}(s);return(0,u.jsx)("section",{className:"margin-vert--lg",children:t.map((e=>(0,u.jsx)(g,{letterEntry:e},e.letter)))})}var b=t(1463);function p(e){let{tags:s,sidebar:t}=e;const i=r();return(0,u.jsxs)(l.e3,{className:(0,a.A)(n.G.wrapper.blogPages,n.G.page.blogTagsListPage),children:[(0,u.jsx)(l.be,{title:i}),(0,u.jsx)(b.A,{tag:"blog_tags_list"}),(0,u.jsxs)(c.A,{sidebar:t,children:[(0,u.jsx)(m.A,{as:"h1",children:i}),(0,u.jsx)(h,{tags:s})]})]})}},6133:(e,s,t)=>{t.d(s,{A:()=>n});t(6540);var a=t(4164),i=t(8774);const r={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var l=t(4848);function n(e){let{permalink:s,label:t,count:n}=e;return(0,l.jsxs)(i.A,{href:s,className:(0,a.A)(r.tag,n?r.tagWithCount:r.tagRegular),children:[t,n&&(0,l.jsx)("span",{children:n})]})}}}]); \ No newline at end of file diff --git a/assets/js/02def4a7.53c9a7f1.js b/assets/js/02def4a7.53c9a7f1.js new file mode 100644 index 00000000..7b2ec53d --- /dev/null +++ b/assets/js/02def4a7.53c9a7f1.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[770],{1966:s=>{s.exports=JSON.parse('{"name":"docusaurus-plugin-content-docs","id":"default"}')}}]); \ No newline at end of file diff --git a/assets/js/0e384e19.3d824450.js b/assets/js/0e384e19.3d824450.js new file mode 100644 index 00000000..5b59c2f4 --- /dev/null +++ b/assets/js/0e384e19.3d824450.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[976],{1512:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>c,contentTitle:()=>a,default:()=>h,frontMatter:()=>t,metadata:()=>r,toc:()=>l});var o=i(4848),s=i(8453);const t={sidebar_position:1},a="Introduction",r={id:"intro",title:"Introduction",description:"LmcRbacMvc is a role-based access control Laminas MVC module to provide additional features on top of Laminas\\Permissions\\Rbac",source:"@site/docs/intro.md",sourceDirName:".",slug:"/intro",permalink:"/lmc-rbac-mvc/docs/intro",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/intro.md",tags:[],version:"current",sidebarPosition:1,frontMatter:{sidebar_position:1},sidebar:"tutorialSidebar",next:{title:"Requirements and Installation",permalink:"/lmc-rbac-mvc/docs/installation"}},c={},l=[{value:"Why should I use an authorization module?",id:"why-should-i-use-an-authorization-module",level:2},{value:"What is the Rbac model?",id:"what-is-the-rbac-model",level:2},{value:"How can I integrate LmcRbacMvc into my application?",id:"how-can-i-integrate-lmcrbacmvc-into-my-application",level:2}];function d(e){const n={a:"a",admonition:"admonition",code:"code",em:"em",h1:"h1",h2:"h2",li:"li",p:"p",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.h1,{id:"introduction",children:"Introduction"}),"\n",(0,o.jsx)(n.p,{children:"LmcRbacMvc is a role-based access control Laminas MVC module to provide additional features on top of Laminas\\Permissions\\Rbac"}),"\n",(0,o.jsxs)(n.p,{children:["LmcRbacMvc is part of the ",(0,o.jsx)(n.a,{href:"https://lm-commons.github.io",children:"LM-Commons"})," series of community developed packages for Laminas."]}),"\n",(0,o.jsx)(n.p,{children:"LM-Commons is a GitHub organization dedicated to the collaborative\nand community-driven long-term maintenance of packages & libraries based on the Laminas MVC and Components."}),"\n",(0,o.jsxs)(n.admonition,{type:"tip",children:[(0,o.jsx)(n.p,{children:(0,o.jsx)(n.strong,{children:"Important Note:"})}),(0,o.jsxs)(n.p,{children:["If you are migrating from ZfcRbac v2, there are breaking changes to take into account. See the ",(0,o.jsx)(n.a,{href:"/lmc-rbac-mvc/docs/installation#upgrade",children:"Upgrade"})," section for details."]})]}),"\n",(0,o.jsx)(n.h2,{id:"why-should-i-use-an-authorization-module",children:"Why should I use an authorization module?"}),"\n",(0,o.jsxs)(n.p,{children:["The authorization part of an application is an essential aspect of securing your application.\nWhile the ",(0,o.jsx)(n.em,{children:"authentication"})," part tells you who is using your website, the ",(0,o.jsx)(n.em,{children:"authorization"})," answers if the given identity has the permission to\nperform specific actions."]}),"\n",(0,o.jsx)(n.h2,{id:"what-is-the-rbac-model",children:"What is the Rbac model?"}),"\n",(0,o.jsxs)(n.p,{children:["Rbac stands for ",(0,o.jsx)(n.strong,{children:"role-based access control"}),". We use a very simple (albeit powerful) implementation of this model\nthrough the use of the ",(0,o.jsx)(n.a,{href:"https://github.com/zf-fr/rbac",children:"zf-fr/rbac"})," library."]}),"\n",(0,o.jsx)(n.p,{children:"The basic idea of Rbac is to use roles and permissions:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.strong,{children:"Users"})," can have one or many ",(0,o.jsx)(n.strong,{children:"Roles"})]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.strong,{children:"Roles"})," request access to ",(0,o.jsx)(n.strong,{children:"Permissions"})]}),"\n",(0,o.jsxs)(n.li,{children:[(0,o.jsx)(n.strong,{children:"Permissions"})," are granted to ",(0,o.jsx)(n.strong,{children:"Roles"})]}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"By default, LmcRbacMvc can be used for two kinds of Rbac model:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:"Flat RBAC model: in this model, roles cannot have children. This is ideal for smaller applications, as it is easier\nto understand, and the database design is simpler (no need for a join table)."}),"\n",(0,o.jsx)(n.li,{children:"Hierarchical RBAC model: in this model, roles can have child roles. When evaluating if a given role has a\npermission, this model also checks recursively if any of its child roles also have the permission."}),"\n"]}),"\n",(0,o.jsx)(n.h2,{id:"how-can-i-integrate-lmcrbacmvc-into-my-application",children:"How can I integrate LmcRbacMvc into my application?"}),"\n",(0,o.jsx)(n.p,{children:"LmcRbacMvc offers multiple ways to protect your application:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["Using ",(0,o.jsx)(n.strong,{children:"Guards"}),': these classes act as "firewalls" that block access to routes and/or controllers. Guards are usually\nconfigured using PHP arrays, and are executed early in the MVC dispatch process. Typically this happens right after\nthe route has been matched.']}),"\n",(0,o.jsxs)(n.li,{children:["Using ",(0,o.jsx)(n.strong,{children:"AuthorizationService"}),": a complementary method is to use the ",(0,o.jsx)(n.code,{children:"AuthorizationService"})," class and inject it into your\nservice classes to protect them from unwanted access."]}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"While it is advised to use both methods to make your application even more secure, this is completely optional and you\ncan choose either of them independently."}),"\n",(0,o.jsx)(n.p,{children:"To find out about how you can easily make your existing application more secure, please refer to the following section:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:(0,o.jsx)(n.a,{href:"/lmc-rbac-mvc/docs/cookbook#a-real-world-application",children:"Cookbook: A real world example"})}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(d,{...e})}):d(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>a,x:()=>r});var o=i(6540);const s={},t=o.createContext(s);function a(e){const n=o.useContext(t);return o.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),o.createElement(t.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/17896441.ab15fc7f.js b/assets/js/17896441.ab15fc7f.js new file mode 100644 index 00000000..127cf9da --- /dev/null +++ b/assets/js/17896441.ab15fc7f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[401],{5022:(e,t,n)=>{n.r(t),n.d(t,{default:()=>de});var s=n(6540),a=n(1003),i=n(9532),l=n(4848);const o=s.createContext(null);function r(e){let{children:t,content:n}=e;const a=function(e){return(0,s.useMemo)((()=>({metadata:e.metadata,frontMatter:e.frontMatter,assets:e.assets,contentTitle:e.contentTitle,toc:e.toc})),[e])}(n);return(0,l.jsx)(o.Provider,{value:a,children:t})}function c(){const e=(0,s.useContext)(o);if(null===e)throw new i.dV("DocProvider");return e}function d(){const{metadata:e,frontMatter:t,assets:n}=c();return(0,l.jsx)(a.be,{title:e.title,description:e.description,keywords:t.keywords,image:n.image??t.image})}var u=n(4164),m=n(4581),h=n(1312),v=n(9022);function x(e){const{previous:t,next:n}=e;return(0,l.jsxs)("nav",{className:"pagination-nav docusaurus-mt-lg","aria-label":(0,h.T)({id:"theme.docs.paginator.navAriaLabel",message:"Docs pages",description:"The ARIA label for the docs pagination"}),children:[t&&(0,l.jsx)(v.A,{...t,subLabel:(0,l.jsx)(h.A,{id:"theme.docs.paginator.previous",description:"The label used to navigate to the previous doc",children:"Previous"})}),n&&(0,l.jsx)(v.A,{...n,subLabel:(0,l.jsx)(h.A,{id:"theme.docs.paginator.next",description:"The label used to navigate to the next doc",children:"Next"}),isNext:!0})]})}function p(){const{metadata:e}=c();return(0,l.jsx)(x,{previous:e.previous,next:e.next})}var b=n(4586),g=n(8774),f=n(4070),j=n(7559),A=n(5597),L=n(2252);const N={unreleased:function(e){let{siteTitle:t,versionMetadata:n}=e;return(0,l.jsx)(h.A,{id:"theme.docs.versions.unreleasedVersionLabel",description:"The label used to tell the user that he's browsing an unreleased doc version",values:{siteTitle:t,versionLabel:(0,l.jsx)("b",{children:n.label})},children:"This is unreleased documentation for {siteTitle} {versionLabel} version."})},unmaintained:function(e){let{siteTitle:t,versionMetadata:n}=e;return(0,l.jsx)(h.A,{id:"theme.docs.versions.unmaintainedVersionLabel",description:"The label used to tell the user that he's browsing an unmaintained doc version",values:{siteTitle:t,versionLabel:(0,l.jsx)("b",{children:n.label})},children:"This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained."})}};function C(e){const t=N[e.versionMetadata.banner];return(0,l.jsx)(t,{...e})}function _(e){let{versionLabel:t,to:n,onClick:s}=e;return(0,l.jsx)(h.A,{id:"theme.docs.versions.latestVersionSuggestionLabel",description:"The label used to tell the user to check the latest version",values:{versionLabel:t,latestVersionLink:(0,l.jsx)("b",{children:(0,l.jsx)(g.A,{to:n,onClick:s,children:(0,l.jsx)(h.A,{id:"theme.docs.versions.latestVersionLinkLabel",description:"The label used for the latest version suggestion link label",children:"latest version"})})})},children:"For up-to-date documentation, see the {latestVersionLink} ({versionLabel})."})}function T(e){let{className:t,versionMetadata:n}=e;const{siteConfig:{title:s}}=(0,b.A)(),{pluginId:a}=(0,f.vT)({failfast:!0}),{savePreferredVersionName:i}=(0,A.g1)(a),{latestDocSuggestion:o,latestVersionSuggestion:r}=(0,f.HW)(a),c=o??(d=r).docs.find((e=>e.id===d.mainDocId));var d;return(0,l.jsxs)("div",{className:(0,u.A)(t,j.G.docs.docVersionBanner,"alert alert--warning margin-bottom--md"),role:"alert",children:[(0,l.jsx)("div",{children:(0,l.jsx)(C,{siteTitle:s,versionMetadata:n})}),(0,l.jsx)("div",{className:"margin-top--md",children:(0,l.jsx)(_,{versionLabel:r.label,to:c.path,onClick:()=>i(r.name)})})]})}function k(e){let{className:t}=e;const n=(0,L.r)();return n.banner?(0,l.jsx)(T,{className:t,versionMetadata:n}):null}function H(e){let{className:t}=e;const n=(0,L.r)();return n.badge?(0,l.jsx)("span",{className:(0,u.A)(t,j.G.docs.docVersionBadge,"badge badge--secondary"),children:(0,l.jsx)(h.A,{id:"theme.docs.versionBadge.label",values:{versionLabel:n.label},children:"Version: {versionLabel}"})}):null}function U(e){let{lastUpdatedAt:t,formattedLastUpdatedAt:n}=e;return(0,l.jsx)(h.A,{id:"theme.lastUpdated.atDate",description:"The words used to describe on which date a page has been last updated",values:{date:(0,l.jsx)("b",{children:(0,l.jsx)("time",{dateTime:new Date(1e3*t).toISOString(),children:n})})},children:" on {date}"})}function y(e){let{lastUpdatedBy:t}=e;return(0,l.jsx)(h.A,{id:"theme.lastUpdated.byUser",description:"The words used to describe by who the page has been last updated",values:{user:(0,l.jsx)("b",{children:t})},children:" by {user}"})}function w(e){let{lastUpdatedAt:t,formattedLastUpdatedAt:n,lastUpdatedBy:s}=e;return(0,l.jsxs)("span",{className:j.G.common.lastUpdated,children:[(0,l.jsx)(h.A,{id:"theme.lastUpdated.lastUpdatedAtBy",description:"The sentence used to display when a page has been last updated, and by who",values:{atDate:t&&n?(0,l.jsx)(U,{lastUpdatedAt:t,formattedLastUpdatedAt:n}):"",byUser:s?(0,l.jsx)(y,{lastUpdatedBy:s}):""},children:"Last updated{atDate}{byUser}"}),!1]})}var M=n(1943),B=n(2053);const E={lastUpdated:"lastUpdated_vwxv"};function I(e){return(0,l.jsx)("div",{className:(0,u.A)(j.G.docs.docFooterTagsRow,"row margin-bottom--sm"),children:(0,l.jsx)("div",{className:"col",children:(0,l.jsx)(B.A,{...e})})})}function O(e){let{editUrl:t,lastUpdatedAt:n,lastUpdatedBy:s,formattedLastUpdatedAt:a}=e;return(0,l.jsxs)("div",{className:(0,u.A)(j.G.docs.docFooterEditMetaRow,"row"),children:[(0,l.jsx)("div",{className:"col",children:t&&(0,l.jsx)(M.A,{editUrl:t})}),(0,l.jsx)("div",{className:(0,u.A)("col",E.lastUpdated),children:(n||s)&&(0,l.jsx)(w,{lastUpdatedAt:n,formattedLastUpdatedAt:a,lastUpdatedBy:s})})]})}function V(){const{metadata:e}=c(),{editUrl:t,lastUpdatedAt:n,formattedLastUpdatedAt:s,lastUpdatedBy:a,tags:i}=e,o=i.length>0,r=!!(t||n||a);return o||r?(0,l.jsxs)("footer",{className:(0,u.A)(j.G.docs.docFooter,"docusaurus-mt-lg"),children:[o&&(0,l.jsx)(I,{tags:i}),r&&(0,l.jsx)(O,{editUrl:t,lastUpdatedAt:n,lastUpdatedBy:a,formattedLastUpdatedAt:s})]}):null}var S=n(1422),G=n(5195);const P={tocCollapsibleButton:"tocCollapsibleButton_TO0P",tocCollapsibleButtonExpanded:"tocCollapsibleButtonExpanded_MG3E"};function R(e){let{collapsed:t,...n}=e;return(0,l.jsx)("button",{type:"button",...n,className:(0,u.A)("clean-btn",P.tocCollapsibleButton,!t&&P.tocCollapsibleButtonExpanded,n.className),children:(0,l.jsx)(h.A,{id:"theme.TOCCollapsible.toggleButtonLabel",description:"The label used by the button on the collapsible TOC component",children:"On this page"})})}const D={tocCollapsible:"tocCollapsible_ETCw",tocCollapsibleContent:"tocCollapsibleContent_vkbj",tocCollapsibleExpanded:"tocCollapsibleExpanded_sAul"};function F(e){let{toc:t,className:n,minHeadingLevel:s,maxHeadingLevel:a}=e;const{collapsed:i,toggleCollapsed:o}=(0,S.u)({initialState:!0});return(0,l.jsxs)("div",{className:(0,u.A)(D.tocCollapsible,!i&&D.tocCollapsibleExpanded,n),children:[(0,l.jsx)(R,{collapsed:i,onClick:o}),(0,l.jsx)(S.N,{lazy:!0,className:D.tocCollapsibleContent,collapsed:i,children:(0,l.jsx)(G.A,{toc:t,minHeadingLevel:s,maxHeadingLevel:a})})]})}const z={tocMobile:"tocMobile_ITEo"};function q(){const{toc:e,frontMatter:t}=c();return(0,l.jsx)(F,{toc:e,minHeadingLevel:t.toc_min_heading_level,maxHeadingLevel:t.toc_max_heading_level,className:(0,u.A)(j.G.docs.docTocMobile,z.tocMobile)})}var W=n(7763);function $(){const{toc:e,frontMatter:t}=c();return(0,l.jsx)(W.A,{toc:e,minHeadingLevel:t.toc_min_heading_level,maxHeadingLevel:t.toc_max_heading_level,className:j.G.docs.docTocDesktop})}var Z=n(1107),Q=n(5533);function X(e){let{children:t}=e;const n=function(){const{metadata:e,frontMatter:t,contentTitle:n}=c();return t.hide_title||void 0!==n?null:e.title}();return(0,l.jsxs)("div",{className:(0,u.A)(j.G.docs.docMarkdown,"markdown"),children:[n&&(0,l.jsx)("header",{children:(0,l.jsx)(Z.A,{as:"h1",children:n})}),(0,l.jsx)(Q.A,{children:t})]})}var Y=n(1754),J=n(9169),K=n(6025);function ee(e){return(0,l.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,l.jsx)("path",{d:"M10 19v-5h4v5c0 .55.45 1 1 1h3c.55 0 1-.45 1-1v-7h1.7c.46 0 .68-.57.33-.87L12.67 3.6c-.38-.34-.96-.34-1.34 0l-8.36 7.53c-.34.3-.13.87.33.87H5v7c0 .55.45 1 1 1h3c.55 0 1-.45 1-1z",fill:"currentColor"})})}const te={breadcrumbHomeIcon:"breadcrumbHomeIcon_YNFT"};function ne(){const e=(0,K.A)("/");return(0,l.jsx)("li",{className:"breadcrumbs__item",children:(0,l.jsx)(g.A,{"aria-label":(0,h.T)({id:"theme.docs.breadcrumbs.home",message:"Home page",description:"The ARIA label for the home page in the breadcrumbs"}),className:"breadcrumbs__link",href:e,children:(0,l.jsx)(ee,{className:te.breadcrumbHomeIcon})})})}const se={breadcrumbsContainer:"breadcrumbsContainer_Z_bl"};function ae(e){let{children:t,href:n,isLast:s}=e;const a="breadcrumbs__link";return s?(0,l.jsx)("span",{className:a,itemProp:"name",children:t}):n?(0,l.jsx)(g.A,{className:a,href:n,itemProp:"item",children:(0,l.jsx)("span",{itemProp:"name",children:t})}):(0,l.jsx)("span",{className:a,children:t})}function ie(e){let{children:t,active:n,index:s,addMicrodata:a}=e;return(0,l.jsxs)("li",{...a&&{itemScope:!0,itemProp:"itemListElement",itemType:"https://schema.org/ListItem"},className:(0,u.A)("breadcrumbs__item",{"breadcrumbs__item--active":n}),children:[t,(0,l.jsx)("meta",{itemProp:"position",content:String(s+1)})]})}function le(){const e=(0,Y.OF)(),t=(0,J.Dt)();return e?(0,l.jsx)("nav",{className:(0,u.A)(j.G.docs.docBreadcrumbs,se.breadcrumbsContainer),"aria-label":(0,h.T)({id:"theme.docs.breadcrumbs.navAriaLabel",message:"Breadcrumbs",description:"The ARIA label for the breadcrumbs"}),children:(0,l.jsxs)("ul",{className:"breadcrumbs",itemScope:!0,itemType:"https://schema.org/BreadcrumbList",children:[t&&(0,l.jsx)(ne,{}),e.map(((t,n)=>{const s=n===e.length-1,a="category"===t.type&&t.linkUnlisted?void 0:t.href;return(0,l.jsx)(ie,{active:s,index:n,addMicrodata:!!a,children:(0,l.jsx)(ae,{href:a,isLast:s,children:t.label})},n)}))]})}):null}var oe=n(996);const re={docItemContainer:"docItemContainer_Djhp",docItemCol:"docItemCol_VOVn"};function ce(e){let{children:t}=e;const n=function(){const{frontMatter:e,toc:t}=c(),n=(0,m.l)(),s=e.hide_table_of_contents,a=!s&&t.length>0;return{hidden:s,mobile:a?(0,l.jsx)(q,{}):void 0,desktop:!a||"desktop"!==n&&"ssr"!==n?void 0:(0,l.jsx)($,{})}}(),{metadata:{unlisted:s}}=c();return(0,l.jsxs)("div",{className:"row",children:[(0,l.jsxs)("div",{className:(0,u.A)("col",!n.hidden&&re.docItemCol),children:[s&&(0,l.jsx)(oe.A,{}),(0,l.jsx)(k,{}),(0,l.jsxs)("div",{className:re.docItemContainer,children:[(0,l.jsxs)("article",{children:[(0,l.jsx)(le,{}),(0,l.jsx)(H,{}),n.mobile,(0,l.jsx)(X,{children:t}),(0,l.jsx)(V,{})]}),(0,l.jsx)(p,{})]})]}),n.desktop&&(0,l.jsx)("div",{className:"col col--3",children:n.desktop})]})}function de(e){const t=`docs-doc-id-${e.content.metadata.id}`,n=e.content;return(0,l.jsx)(r,{content:e.content,children:(0,l.jsxs)(a.e3,{className:t,children:[(0,l.jsx)(d,{}),(0,l.jsx)(ce,{children:(0,l.jsx)(n,{})})]})})}},1943:(e,t,n)=>{n.d(t,{A:()=>d});n(6540);var s=n(1312),a=n(7559),i=n(8774),l=n(4164);const o={iconEdit:"iconEdit_Z9Sw"};var r=n(4848);function c(e){let{className:t,...n}=e;return(0,r.jsx)("svg",{fill:"currentColor",height:"20",width:"20",viewBox:"0 0 40 40",className:(0,l.A)(o.iconEdit,t),"aria-hidden":"true",...n,children:(0,r.jsx)("g",{children:(0,r.jsx)("path",{d:"m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"})})})}function d(e){let{editUrl:t}=e;return(0,r.jsxs)(i.A,{to:t,className:a.G.common.editThisPage,children:[(0,r.jsx)(c,{}),(0,r.jsx)(s.A,{id:"theme.common.editThisPage",description:"The link label to edit the current page",children:"Edit this page"})]})}},9022:(e,t,n)=>{n.d(t,{A:()=>l});n(6540);var s=n(4164),a=n(8774),i=n(4848);function l(e){const{permalink:t,title:n,subLabel:l,isNext:o}=e;return(0,i.jsxs)(a.A,{className:(0,s.A)("pagination-nav__link",o?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t,children:[l&&(0,i.jsx)("div",{className:"pagination-nav__sublabel",children:l}),(0,i.jsx)("div",{className:"pagination-nav__label",children:n})]})}},7763:(e,t,n)=>{n.d(t,{A:()=>c});n(6540);var s=n(4164),a=n(5195);const i={tableOfContents:"tableOfContents_bqdL",docItemContainer:"docItemContainer_F8PC"};var l=n(4848);const o="table-of-contents__link toc-highlight",r="table-of-contents__link--active";function c(e){let{className:t,...n}=e;return(0,l.jsx)("div",{className:(0,s.A)(i.tableOfContents,"thin-scrollbar",t),children:(0,l.jsx)(a.A,{...n,linkClassName:o,linkActiveClassName:r})})}},5195:(e,t,n)=>{n.d(t,{A:()=>x});var s=n(6540),a=n(6342);function i(e){const t=e.map((e=>({...e,parentIndex:-1,children:[]}))),n=Array(7).fill(-1);t.forEach(((e,t)=>{const s=n.slice(2,e.level);e.parentIndex=Math.max(...s),n[e.level]=t}));const s=[];return t.forEach((e=>{const{parentIndex:n,...a}=e;n>=0?t[n].children.push(a):s.push(a)})),s}function l(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:s}=e;return t.flatMap((e=>{const t=l({toc:e.children,minHeadingLevel:n,maxHeadingLevel:s});return function(e){return e.level>=n&&e.level<=s}(e)?[{...e,children:t}]:t}))}function o(e){const t=e.getBoundingClientRect();return t.top===t.bottom?o(e.parentNode):t}function r(e,t){let{anchorTopOffset:n}=t;const s=e.find((e=>o(e).top>=n));if(s){return function(e){return e.top>0&&e.bottom{e.current=t?0:document.querySelector(".navbar").clientHeight}),[t]),e}function d(e){const t=(0,s.useRef)(void 0),n=c();(0,s.useEffect)((()=>{if(!e)return()=>{};const{linkClassName:s,linkActiveClassName:a,minHeadingLevel:i,maxHeadingLevel:l}=e;function o(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(s),o=function(e){let{minHeadingLevel:t,maxHeadingLevel:n}=e;const s=[];for(let a=t;a<=n;a+=1)s.push(`h${a}.anchor`);return Array.from(document.querySelectorAll(s.join()))}({minHeadingLevel:i,maxHeadingLevel:l}),c=r(o,{anchorTopOffset:n.current}),d=e.find((e=>c&&c.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e)));e.forEach((e=>{!function(e,n){n?(t.current&&t.current!==e&&t.current.classList.remove(a),e.classList.add(a),t.current=e):e.classList.remove(a)}(e,e===d)}))}return document.addEventListener("scroll",o),document.addEventListener("resize",o),o(),()=>{document.removeEventListener("scroll",o),document.removeEventListener("resize",o)}}),[e,n])}var u=n(8774),m=n(4848);function h(e){let{toc:t,className:n,linkClassName:s,isChild:a}=e;return t.length?(0,m.jsx)("ul",{className:a?void 0:n,children:t.map((e=>(0,m.jsxs)("li",{children:[(0,m.jsx)(u.A,{to:`#${e.id}`,className:s??void 0,dangerouslySetInnerHTML:{__html:e.value}}),(0,m.jsx)(h,{isChild:!0,toc:e.children,className:n,linkClassName:s})]},e.id)))}):null}const v=s.memo(h);function x(e){let{toc:t,className:n="table-of-contents table-of-contents__left-border",linkClassName:o="table-of-contents__link",linkActiveClassName:r,minHeadingLevel:c,maxHeadingLevel:u,...h}=e;const x=(0,a.p)(),p=c??x.tableOfContents.minHeadingLevel,b=u??x.tableOfContents.maxHeadingLevel,g=function(e){let{toc:t,minHeadingLevel:n,maxHeadingLevel:a}=e;return(0,s.useMemo)((()=>l({toc:i(t),minHeadingLevel:n,maxHeadingLevel:a})),[t,n,a])}({toc:t,minHeadingLevel:p,maxHeadingLevel:b});return d((0,s.useMemo)((()=>{if(o&&r)return{linkClassName:o,linkActiveClassName:r,minHeadingLevel:p,maxHeadingLevel:b}}),[o,r,p,b])),(0,m.jsx)(v,{toc:g,className:n,linkClassName:o,...h})}},6133:(e,t,n)=>{n.d(t,{A:()=>o});n(6540);var s=n(4164),a=n(8774);const i={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var l=n(4848);function o(e){let{permalink:t,label:n,count:o}=e;return(0,l.jsxs)(a.A,{href:t,className:(0,s.A)(i.tag,o?i.tagWithCount:i.tagRegular),children:[n,o&&(0,l.jsx)("span",{children:o})]})}},2053:(e,t,n)=>{n.d(t,{A:()=>r});n(6540);var s=n(4164),a=n(1312),i=n(6133);const l={tags:"tags_jXut",tag:"tag_QGVx"};var o=n(4848);function r(e){let{tags:t}=e;return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)("b",{children:(0,o.jsx)(a.A,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list",children:"Tags:"})}),(0,o.jsx)("ul",{className:(0,s.A)(l.tags,"padding--none","margin-left--sm"),children:t.map((e=>{let{label:t,permalink:n}=e;return(0,o.jsx)("li",{className:l.tag,children:(0,o.jsx)(i.A,{label:t,permalink:n})},n)}))})]})}},996:(e,t,n)=>{n.d(t,{A:()=>h});n(6540);var s=n(4164),a=n(1312),i=n(5260),l=n(4848);function o(){return(0,l.jsx)(a.A,{id:"theme.unlistedContent.title",description:"The unlisted content banner title",children:"Unlisted page"})}function r(){return(0,l.jsx)(a.A,{id:"theme.unlistedContent.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function c(){return(0,l.jsx)(i.A,{children:(0,l.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}var d=n(7559),u=n(7293);function m(e){let{className:t}=e;return(0,l.jsx)(u.A,{type:"caution",title:(0,l.jsx)(o,{}),className:(0,s.A)(t,d.G.common.unlistedBanner),children:(0,l.jsx)(r,{})})}function h(e){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(c,{}),(0,l.jsx)(m,{...e})]})}}}]); \ No newline at end of file diff --git a/assets/js/1c81fc78.f1f88d0c.js b/assets/js/1c81fc78.f1f88d0c.js new file mode 100644 index 00000000..d6427577 --- /dev/null +++ b/assets/js/1c81fc78.f1f88d0c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[982],{953:s=>{s.exports=JSON.parse('{"permalink":"/lmc-rbac-mvc/blog/tags/laminas","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/1f391b9e.a27a432b.js b/assets/js/1f391b9e.a27a432b.js new file mode 100644 index 00000000..09a85348 --- /dev/null +++ b/assets/js/1f391b9e.a27a432b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[61],{7973:(e,n,t)=>{t.r(n),t.d(n,{default:()=>u});t(6540);var i=t(4164),a=t(1003),s=t(7559),l=t(781),r=t(5533),c=t(7763),o=t(996);const d={mdxPageWrapper:"mdxPageWrapper_j9I6"};var m=t(4848);function u(e){const{content:n}=e,{metadata:{title:t,description:u,frontMatter:f,unlisted:v},assets:h}=n,{keywords:g,wrapperClassName:x,hide_table_of_contents:p}=f,L=h.image??f.image;return(0,m.jsx)(a.e3,{className:(0,i.A)(x??s.G.wrapper.mdxPages,s.G.page.mdxPage),children:(0,m.jsxs)(l.A,{children:[(0,m.jsx)(a.be,{title:t,description:u,keywords:g,image:L}),(0,m.jsx)("main",{className:"container container--fluid margin-vert--lg",children:(0,m.jsxs)("div",{className:(0,i.A)("row",d.mdxPageWrapper),children:[(0,m.jsxs)("div",{className:(0,i.A)("col",!p&&"col--8"),children:[v&&(0,m.jsx)(o.A,{}),(0,m.jsx)("article",{children:(0,m.jsx)(r.A,{children:(0,m.jsx)(n,{})})})]}),!p&&n.toc.length>0&&(0,m.jsx)("div",{className:"col col--2",children:(0,m.jsx)(c.A,{toc:n.toc,minHeadingLevel:f.toc_min_heading_level,maxHeadingLevel:f.toc_max_heading_level})})]})})]})})}},7763:(e,n,t)=>{t.d(n,{A:()=>o});t(6540);var i=t(4164),a=t(5195);const s={tableOfContents:"tableOfContents_bqdL",docItemContainer:"docItemContainer_F8PC"};var l=t(4848);const r="table-of-contents__link toc-highlight",c="table-of-contents__link--active";function o(e){let{className:n,...t}=e;return(0,l.jsx)("div",{className:(0,i.A)(s.tableOfContents,"thin-scrollbar",n),children:(0,l.jsx)(a.A,{...t,linkClassName:r,linkActiveClassName:c})})}},5195:(e,n,t)=>{t.d(n,{A:()=>h});var i=t(6540),a=t(6342);function s(e){const n=e.map((e=>({...e,parentIndex:-1,children:[]}))),t=Array(7).fill(-1);n.forEach(((e,n)=>{const i=t.slice(2,e.level);e.parentIndex=Math.max(...i),t[e.level]=n}));const i=[];return n.forEach((e=>{const{parentIndex:t,...a}=e;t>=0?n[t].children.push(a):i.push(a)})),i}function l(e){let{toc:n,minHeadingLevel:t,maxHeadingLevel:i}=e;return n.flatMap((e=>{const n=l({toc:e.children,minHeadingLevel:t,maxHeadingLevel:i});return function(e){return e.level>=t&&e.level<=i}(e)?[{...e,children:n}]:n}))}function r(e){const n=e.getBoundingClientRect();return n.top===n.bottom?r(e.parentNode):n}function c(e,n){let{anchorTopOffset:t}=n;const i=e.find((e=>r(e).top>=t));if(i){return function(e){return e.top>0&&e.bottom{e.current=n?0:document.querySelector(".navbar").clientHeight}),[n]),e}function d(e){const n=(0,i.useRef)(void 0),t=o();(0,i.useEffect)((()=>{if(!e)return()=>{};const{linkClassName:i,linkActiveClassName:a,minHeadingLevel:s,maxHeadingLevel:l}=e;function r(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(i),r=function(e){let{minHeadingLevel:n,maxHeadingLevel:t}=e;const i=[];for(let a=n;a<=t;a+=1)i.push(`h${a}.anchor`);return Array.from(document.querySelectorAll(i.join()))}({minHeadingLevel:s,maxHeadingLevel:l}),o=c(r,{anchorTopOffset:t.current}),d=e.find((e=>o&&o.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e)));e.forEach((e=>{!function(e,t){t?(n.current&&n.current!==e&&n.current.classList.remove(a),e.classList.add(a),n.current=e):e.classList.remove(a)}(e,e===d)}))}return document.addEventListener("scroll",r),document.addEventListener("resize",r),r(),()=>{document.removeEventListener("scroll",r),document.removeEventListener("resize",r)}}),[e,t])}var m=t(8774),u=t(4848);function f(e){let{toc:n,className:t,linkClassName:i,isChild:a}=e;return n.length?(0,u.jsx)("ul",{className:a?void 0:t,children:n.map((e=>(0,u.jsxs)("li",{children:[(0,u.jsx)(m.A,{to:`#${e.id}`,className:i??void 0,dangerouslySetInnerHTML:{__html:e.value}}),(0,u.jsx)(f,{isChild:!0,toc:e.children,className:t,linkClassName:i})]},e.id)))}):null}const v=i.memo(f);function h(e){let{toc:n,className:t="table-of-contents table-of-contents__left-border",linkClassName:r="table-of-contents__link",linkActiveClassName:c,minHeadingLevel:o,maxHeadingLevel:m,...f}=e;const h=(0,a.p)(),g=o??h.tableOfContents.minHeadingLevel,x=m??h.tableOfContents.maxHeadingLevel,p=function(e){let{toc:n,minHeadingLevel:t,maxHeadingLevel:a}=e;return(0,i.useMemo)((()=>l({toc:s(n),minHeadingLevel:t,maxHeadingLevel:a})),[n,t,a])}({toc:n,minHeadingLevel:g,maxHeadingLevel:x});return d((0,i.useMemo)((()=>{if(r&&c)return{linkClassName:r,linkActiveClassName:c,minHeadingLevel:g,maxHeadingLevel:x}}),[r,c,g,x])),(0,u.jsx)(v,{toc:p,className:t,linkClassName:r,...f})}},996:(e,n,t)=>{t.d(n,{A:()=>f});t(6540);var i=t(4164),a=t(1312),s=t(5260),l=t(4848);function r(){return(0,l.jsx)(a.A,{id:"theme.unlistedContent.title",description:"The unlisted content banner title",children:"Unlisted page"})}function c(){return(0,l.jsx)(a.A,{id:"theme.unlistedContent.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function o(){return(0,l.jsx)(s.A,{children:(0,l.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}var d=t(7559),m=t(7293);function u(e){let{className:n}=e;return(0,l.jsx)(m.A,{type:"caution",title:(0,l.jsx)(r,{}),className:(0,i.A)(n,d.G.common.unlistedBanner),children:(0,l.jsx)(c,{})})}function f(e){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(o,{}),(0,l.jsx)(u,{...e})]})}}}]); \ No newline at end of file diff --git a/assets/js/22465cd7.9e45649f.js b/assets/js/22465cd7.9e45649f.js new file mode 100644 index 00000000..0dc1fc54 --- /dev/null +++ b/assets/js/22465cd7.9e45649f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[166],{5823:(e,r,n)=>{n.r(r),n.d(r,{assets:()=>l,contentTitle:()=>s,default:()=>h,frontMatter:()=>t,metadata:()=>c,toc:()=>d});var o=n(4848),i=n(8453);const t={sidebar_position:4},s="Role providers",c={id:"role-providers",title:"Role providers",description:"In this section, you will learn:",source:"@site/docs/role-providers.md",sourceDirName:".",slug:"/role-providers",permalink:"/lmc-rbac-mvc/docs/role-providers",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/role-providers.md",tags:[],version:"current",sidebarPosition:4,frontMatter:{sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"Quick Start",permalink:"/lmc-rbac-mvc/docs/quick-start"},next:{title:"Guards",permalink:"/lmc-rbac-mvc/docs/guards"}},l={},d=[{value:"What are role providers?",id:"what-are-role-providers",level:2},{value:"Identity providers?",id:"identity-providers",level:2},{value:"Create your own identity provider",id:"create-your-own-identity-provider",level:3},{value:"Built-in role providers",id:"built-in-role-providers",level:2},{value:"InMemoryRoleProvider",id:"inmemoryroleprovider",level:3},{value:"ObjectRepositoryRoleProvider",id:"objectrepositoryroleprovider",level:3},{value:"Creating custom role providers",id:"creating-custom-role-providers",level:2}];function a(e){const r={code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",ul:"ul",...(0,i.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(r.h1,{id:"role-providers",children:"Role providers"}),"\n",(0,o.jsx)(r.p,{children:"In this section, you will learn:"}),"\n",(0,o.jsxs)(r.ul,{children:["\n",(0,o.jsx)(r.li,{children:"What are role providers"}),"\n",(0,o.jsx)(r.li,{children:"What are identity providers"}),"\n",(0,o.jsx)(r.li,{children:"How to use and configure built-in providers"}),"\n",(0,o.jsx)(r.li,{children:"How to create custom role providers"}),"\n"]}),"\n",(0,o.jsx)(r.h2,{id:"what-are-role-providers",children:"What are role providers?"}),"\n",(0,o.jsxs)(r.p,{children:["A role provider is an object that returns a list of roles. Each role provider must implement the\n",(0,o.jsx)(r.code,{children:"LmcRbacMvc\\Role\\RoleProviderInterface"})," interface. The only required method is ",(0,o.jsx)(r.code,{children:"getRoles"}),", and must return an array\nof ",(0,o.jsx)(r.code,{children:"Rbac\\Role\\RoleInterface"})," objects."]}),"\n",(0,o.jsx)(r.p,{children:"Roles can come from one of many sources: in memory, from a file, from a database... However, please note that\nyou can specify only one role provider per application. The reason is that having multiple role providers makes\nthe workflow harder and can lead to security problems that are very hard to spot."}),"\n",(0,o.jsx)(r.h2,{id:"identity-providers",children:"Identity providers?"}),"\n",(0,o.jsxs)(r.p,{children:["Identity providers return the current identity. Most of the time, this means the logged in user. LmcRbacMvc comes with a\ndefault identity provider (",(0,o.jsx)(r.code,{children:"LmcRbacMvc\\Identity\\AuthenticationIdentityProvider"}),") that uses the\n",(0,o.jsx)(r.code,{children:"Laminas\\Authentication\\AuthenticationService"})," service."]}),"\n",(0,o.jsx)(r.h3,{id:"create-your-own-identity-provider",children:"Create your own identity provider"}),"\n",(0,o.jsxs)(r.p,{children:["If you want to implement your own identity provider, create a new class that implements\n",(0,o.jsx)(r.code,{children:"LmcRbacMvc\\Identity\\IdentityProviderInterface"})," class. Then, change the ",(0,o.jsx)(r.code,{children:"identity_provider"})," option in LmcRbacMvc config,\nas shown below:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'identity_provider' => 'MyCustomIdentityProvider'\n ]\n];\n"})}),"\n",(0,o.jsx)(r.p,{children:"The identity provider is automatically pulled from the service manager."}),"\n",(0,o.jsx)(r.h2,{id:"built-in-role-providers",children:"Built-in role providers"}),"\n",(0,o.jsxs)(r.p,{children:["LmcRbacMvc comes with two built-in role providers: ",(0,o.jsx)(r.code,{children:"InMemoryRoleProvider"})," and ",(0,o.jsx)(r.code,{children:"ObjectRepositoryRoleProvider"}),". A role\nprovider must be added to the ",(0,o.jsx)(r.code,{children:"role_provider"})," subkey:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n // Role provider config here!\n ]\n ]\n];\n"})}),"\n",(0,o.jsx)(r.h3,{id:"inmemoryroleprovider",children:"InMemoryRoleProvider"}),"\n",(0,o.jsx)(r.p,{children:"This provider is ideal for small/medium sites with few roles/permissions. All the data is specified in a simple\nPHP file, so you never hit a database."}),"\n",(0,o.jsx)(r.p,{children:"Here is an example of the format you need to use:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\InMemoryRoleProvider' => [\n 'admin' => [\n 'children' => ['member'],\n 'permissions' => ['article.delete']\n ],\n 'member' => [\n 'children' => ['guest'],\n 'permissions' => ['article.edit', 'article.archive']\n ],\n 'guest' => [\n 'permissions' => ['article.read']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,o.jsxs)(r.p,{children:["The ",(0,o.jsx)(r.code,{children:"children"})," and ",(0,o.jsx)(r.code,{children:"permissions"})," subkeys are entirely optional. Internally, the ",(0,o.jsx)(r.code,{children:"InMemoryRoleProvider"})," creates\neither a ",(0,o.jsx)(r.code,{children:"Rbac\\Role\\Role"})," object if the role does not have any children, or a ",(0,o.jsx)(r.code,{children:"Rbac\\Role\\HierarchicalRole"})," if\nthe role has at least one child."]}),"\n",(0,o.jsx)(r.p,{children:"If you are more confident with flat RBAC, the previous config can be re-written to remove any inheritence between roles:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\InMemoryRoleProvider' => [\n 'admin' => [\n 'permissions' => [\n 'article.delete',\n 'article.edit',\n 'article.archive',\n 'article.read'\n ]\n ],\n 'member' => [\n 'permissions' => [\n 'article.edit',\n 'article.archive',\n 'article.read'\n ]\n ],\n 'guest' => [\n 'permissions' => ['article.read']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,o.jsx)(r.h3,{id:"objectrepositoryroleprovider",children:"ObjectRepositoryRoleProvider"}),"\n",(0,o.jsxs)(r.p,{children:["This provider fetches roles from the database using ",(0,o.jsx)(r.code,{children:"Doctrine\\Common\\Persistence\\ObjectRepository"})," interface."]}),"\n",(0,o.jsxs)(r.p,{children:["You can configure this provider by giving an object repository service name that is fetched from the service manager\nusing the ",(0,o.jsx)(r.code,{children:"object_repository"})," key:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\ObjectRepositoryRoleProvider' => [\n 'object_repository' => 'App\\Repository\\RoleRepository',\n 'role_name_property' => 'name'\n ]\n ]\n ]\n];\n"})}),"\n",(0,o.jsxs)(r.p,{children:["Or you can specify the ",(0,o.jsx)(r.code,{children:"object_manager"})," and ",(0,o.jsx)(r.code,{children:"class_name"})," options:"]}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\ObjectRepositoryRoleProvider' => [\n 'object_manager' => 'doctrine.entitymanager.orm_default',\n 'class_name' => 'App\\Entity\\Role',\n 'role_name_property' => 'name'\n ]\n ]\n ]\n];\n"})}),"\n",(0,o.jsxs)(r.p,{children:["In both cases, you need to specify the ",(0,o.jsx)(r.code,{children:"role_name_property"})," value, which is the name of the entity's property\nthat holds the actual role name. This is used internally to only load the identity roles, instead of loading\nthe whole table every time."]}),"\n",(0,o.jsxs)(r.p,{children:["Please note that your entity fetched from the table MUST implement the ",(0,o.jsx)(r.code,{children:"Rbac\\Role\\RoleInterface"})," interface."]}),"\n",(0,o.jsx)(r.h2,{id:"creating-custom-role-providers",children:"Creating custom role providers"}),"\n",(0,o.jsxs)(r.p,{children:["To create a custom role provider, you first need to create a class that implements the ",(0,o.jsx)(r.code,{children:"LmcRbacMvc\\Role\\RoleProviderInterface"}),"\ninterface."]}),"\n",(0,o.jsx)(r.p,{children:"Then, you need to add it to the role provider manager:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider_manager' => [\n 'factories' => [\n 'Application\\Role\\CustomRoleProvider' => 'Application\\Factory\\CustomRoleProviderFactory'\n ]\n ] \n ]\n];\n"})}),"\n",(0,o.jsx)(r.p,{children:"You can now use it like any other role provider:"}),"\n",(0,o.jsx)(r.pre,{children:(0,o.jsx)(r.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n 'Application\\Role\\CustomRoleProvider' => [\n // Options\n ]\n ]\n ]\n];\n"})})]})}function h(e={}){const{wrapper:r}={...(0,i.R)(),...e.components};return r?(0,o.jsx)(r,{...e,children:(0,o.jsx)(a,{...e})}):a(e)}},8453:(e,r,n)=>{n.d(r,{R:()=>s,x:()=>c});var o=n(6540);const i={},t=o.createContext(i);function s(e){const r=o.useContext(t);return o.useMemo((function(){return"function"==typeof e?e(r):{...r,...e}}),[r,e])}function c(e){let r;return r=e.disableParentContext?"function"==typeof e.components?e.components(i):e.components||i:s(e.components),o.createElement(t.Provider,{value:r},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/237.7663bed8.js b/assets/js/237.7663bed8.js new file mode 100644 index 00000000..8bc8b4a3 --- /dev/null +++ b/assets/js/237.7663bed8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[237],{3363:(e,t,n)=>{n.d(t,{A:()=>a});n(6540);var i=n(4164),o=n(1312),s=n(1107),r=n(4848);function a(e){let{className:t}=e;return(0,r.jsx)("main",{className:(0,i.A)("container margin-vert--xl",t),children:(0,r.jsx)("div",{className:"row",children:(0,r.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,r.jsx)(s.A,{as:"h1",className:"hero__title",children:(0,r.jsx)(o.A,{id:"theme.NotFound.title",description:"The title of the 404 page",children:"Page Not Found"})}),(0,r.jsx)("p",{children:(0,r.jsx)(o.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page",children:"We could not find what you were looking for."})}),(0,r.jsx)("p",{children:(0,r.jsx)(o.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page",children:"Please contact the owner of the site that linked you to the original URL and let them know their link is broken."})})]})})})}},2237:(e,t,n)=>{n.r(t),n.d(t,{default:()=>d});n(6540);var i=n(1312),o=n(1003),s=n(781),r=n(3363),a=n(4848);function d(){const e=(0,i.T)({id:"theme.NotFound.title",message:"Page Not Found"});return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(o.be,{title:e}),(0,a.jsx)(s.A,{children:(0,a.jsx)(r.A,{})})]})}}}]); \ No newline at end of file diff --git a/assets/js/266b150c.c169f2f7.js b/assets/js/266b150c.c169f2f7.js new file mode 100644 index 00000000..04b0f354 --- /dev/null +++ b/assets/js/266b150c.c169f2f7.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[346],{5614:e=>{e.exports=JSON.parse('{"permalink":"/lmc-rbac-mvc/blog","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/378f56c0.f10e3e52.js b/assets/js/378f56c0.f10e3e52.js new file mode 100644 index 00000000..209d88e1 --- /dev/null +++ b/assets/js/378f56c0.f10e3e52.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[639],{6060:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>i,contentTitle:()=>r,default:()=>u,frontMatter:()=>a,metadata:()=>m,toc:()=>s});var c=n(4848),o=n(8453);const a={slug:"new-documentation",title:"New documentation",authors:["ericr"],tags:["laminas","PHP","lmcrbacmvc"]},r=void 0,m={permalink:"/lmc-rbac-mvc/blog/new-documentation",editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2024-02-22-New-documentation.md",source:"@site/blog/2024-02-22-New-documentation.md",title:"New documentation",description:"This the new documentation site dedicated to the LmcRbacMvc module.",date:"2024-02-22T00:00:00.000Z",formattedDate:"February 22, 2024",tags:[{label:"laminas",permalink:"/lmc-rbac-mvc/blog/tags/laminas"},{label:"PHP",permalink:"/lmc-rbac-mvc/blog/tags/php"},{label:"lmcrbacmvc",permalink:"/lmc-rbac-mvc/blog/tags/lmcrbacmvc"}],readingTime:.11,hasTruncateMarker:!1,authors:[{name:"Eric Richer",title:"LM-Commons Administrator",url:"https://github.com/visto9259",imageURL:"https://github.com/visto9259.png",key:"ericr"}],frontMatter:{slug:"new-documentation",title:"New documentation",authors:["ericr"],tags:["laminas","PHP","lmcrbacmvc"]},unlisted:!1,nextItem:{title:"Welcome",permalink:"/lmc-rbac-mvc/blog/welcome"}},i={authorsImageUrls:[void 0]},s=[];function l(t){const e={p:"p",...(0,o.R)(),...t.components};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(e.p,{children:"This the new documentation site dedicated to the LmcRbacMvc module."}),"\n",(0,c.jsx)(e.p,{children:"There are no changes to the code, just improvements in the documentation."})]})}function u(t={}){const{wrapper:e}={...(0,o.R)(),...t.components};return e?(0,c.jsx)(e,{...t,children:(0,c.jsx)(l,{...t})}):l(t)}},8453:(t,e,n)=>{n.d(e,{R:()=>r,x:()=>m});var c=n(6540);const o={},a=c.createContext(o);function r(t){const e=c.useContext(a);return c.useMemo((function(){return"function"==typeof t?t(e):{...e,...t}}),[e,t])}function m(t){let e;return e=t.disableParentContext?"function"==typeof t.components?t.components(o):t.components||o:r(t.components),c.createElement(a.Provider,{value:e},t.children)}}}]); \ No newline at end of file diff --git a/assets/js/393be207.8c825263.js b/assets/js/393be207.8c825263.js new file mode 100644 index 00000000..0564ed72 --- /dev/null +++ b/assets/js/393be207.8c825263.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[134],{6602:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>p,contentTitle:()=>s,default:()=>l,frontMatter:()=>r,metadata:()=>c,toc:()=>d});var o=t(4848),a=t(8453);const r={title:"Markdown page example"},s="Markdown page example",c={type:"mdx",permalink:"/lmc-rbac-mvc/markdown-page",source:"@site/src/pages/markdown-page.md",title:"Markdown page example",description:"You don't need React to write simple standalone pages.",frontMatter:{title:"Markdown page example"},unlisted:!1},p={},d=[];function i(e){const n={h1:"h1",p:"p",...(0,a.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(n.h1,{id:"markdown-page-example",children:"Markdown page example"}),"\n",(0,o.jsx)(n.p,{children:"You don't need React to write simple standalone pages."})]})}function l(e={}){const{wrapper:n}={...(0,a.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(i,{...e})}):i(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>s,x:()=>c});var o=t(6540);const a={},r=o.createContext(a);function s(e){const n=o.useContext(r);return o.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(a):e.components||a:s(e.components),o.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/3b8c55ea.95bbb53a.js b/assets/js/3b8c55ea.95bbb53a.js new file mode 100644 index 00000000..cbee10f0 --- /dev/null +++ b/assets/js/3b8c55ea.95bbb53a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[803],{3668:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>l,default:()=>h,frontMatter:()=>s,metadata:()=>r,toc:()=>c});var t=i(4848),o=i(8453);const s={sidebar_position:2,sidebar_label:"Requirements and Installation"},l="Requirements and Installation",r={id:"installation",title:"Requirements and Installation",description:"Requirements",source:"@site/docs/installation.md",sourceDirName:".",slug:"/installation",permalink:"/lmc-rbac-mvc/docs/installation",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/installation.md",tags:[],version:"current",sidebarPosition:2,frontMatter:{sidebar_position:2,sidebar_label:"Requirements and Installation"},sidebar:"tutorialSidebar",previous:{title:"Introduction",permalink:"/lmc-rbac-mvc/docs/intro"},next:{title:"Quick Start",permalink:"/lmc-rbac-mvc/docs/quick-start"}},a={},c=[{value:"Requirements",id:"requirements",level:2},{value:"Optional",id:"optional",level:2},{value:"Installation",id:"installation",level:2},{value:"Upgrade",id:"upgrade",level:2}];function d(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",li:"li",p:"p",pre:"pre",ul:"ul",...(0,o.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"requirements-and-installation",children:"Requirements and Installation"}),"\n",(0,t.jsx)(n.h2,{id:"requirements",children:"Requirements"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"PHP 7.4 or higher"}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"https://github.com/zf-fr/rbac",children:"Zf-fr/Rbac component v1"}),": this is actually a prototype for the ZF3 Rbac component."]}),"\n",(0,t.jsx)(n.li,{children:(0,t.jsx)(n.a,{href:"http://www.github.com/laminas",children:"Laminas Components 2.x | 3.x or higher"})}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"optional",children:"Optional"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"https://github.com/doctrine/DoctrineModule",children:"DoctrineModule"}),": if you want to use some built-in role and permission providers."]}),"\n",(0,t.jsxs)(n.li,{children:[(0,t.jsx)(n.a,{href:"https://github.com/laminas/Laminas%5CDeveloperTools",children:"Laminas\\DeveloperTools"}),": if you want to have useful stats added to\nthe Laminas Developer toolbar."]}),"\n"]}),"\n",(0,t.jsx)(n.h2,{id:"installation",children:"Installation"}),"\n",(0,t.jsxs)(n.p,{children:["LmcRbacMvc only officially supports installation through Composer. For Composer documentation, please refer to\n",(0,t.jsx)(n.a,{href:"http://getcomposer.org/",children:"getcomposer.org"}),"."]}),"\n",(0,t.jsx)(n.p,{children:"Install the module:"}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-sh",children:"$ composer require lm-commons/lmc-rbac-mvc:^3.0\n"})}),"\n",(0,t.jsxs)(n.p,{children:["Enable the module by adding ",(0,t.jsx)(n.code,{children:"LmcRbacMvc"})," key to your ",(0,t.jsx)(n.code,{children:"application.config.php"})," or ",(0,t.jsx)(n.code,{children:"modules.config.php"})," file. Customize the module by copy-pasting\nthe ",(0,t.jsx)(n.code,{children:"lmc_rbac.global.php.dist"})," file to your ",(0,t.jsx)(n.code,{children:"config/autoload"})," folder."]}),"\n",(0,t.jsx)(n.h2,{id:"upgrade",children:"Upgrade"}),"\n",(0,t.jsx)(n.p,{children:"LmcRbacMvc introduces breaking changes from zfcrbac v2:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsxs)(n.li,{children:["[BC] The namespace has been changed from ",(0,t.jsx)(n.code,{children:"ZfcRbac"})," to ",(0,t.jsx)(n.code,{children:"LmcRbacMvc"}),"."]}),"\n",(0,t.jsxs)(n.li,{children:["[BC] The key ",(0,t.jsx)(n.code,{children:"zfc_rbac"})," in autoload and module config files has been replaced\nby the ",(0,t.jsx)(n.code,{children:"lmc_rbac"})," key."]}),"\n",(0,t.jsx)(n.li,{children:"Requires PHP 7.4 or later"}),"\n",(0,t.jsx)(n.li,{children:"Requires Laminas MVC components 3.x or later"}),"\n",(0,t.jsx)(n.li,{children:"Uses PSR-4 autoload"}),"\n"]})]})}function h(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(d,{...e})}):d(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>l,x:()=>r});var t=i(6540);const o={},s=t.createContext(o);function l(e){const n=t.useContext(s);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function r(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:l(e.components),t.createElement(s.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/4a900e2d.a37ef9b5.js b/assets/js/4a900e2d.a37ef9b5.js new file mode 100644 index 00000000..d2cb99bf --- /dev/null +++ b/assets/js/4a900e2d.a37ef9b5.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[82],{2130:a=>{a.exports=JSON.parse('{"label":"laminas","permalink":"/lmc-rbac-mvc/blog/tags/laminas","allTagsPath":"/lmc-rbac-mvc/blog/tags","count":2,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/4b8919da.4dc42bc8.js b/assets/js/4b8919da.4dc42bc8.js new file mode 100644 index 00000000..f265508f --- /dev/null +++ b/assets/js/4b8919da.4dc42bc8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[223],{6624:e=>{e.exports=JSON.parse('{"permalink":"/lmc-rbac-mvc/blog/tags/php","page":1,"postsPerPage":10,"totalPages":1,"totalCount":2,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/4e6224b1.3600afef.js b/assets/js/4e6224b1.3600afef.js new file mode 100644 index 00000000..9a72da95 --- /dev/null +++ b/assets/js/4e6224b1.3600afef.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[107],{2618:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>c,contentTitle:()=>o,default:()=>d,frontMatter:()=>r,metadata:()=>a,toc:()=>l});var s=i(4848),t=i(8453);const r={sidebar_position:7},o="Using the Authorization Service",a={id:"using-the-authorization-service",title:"Using the Authorization Service",description:"This section will teach you how to use the AuthorizationService to its full extent.",source:"@site/docs/using-the-authorization-service.md",sourceDirName:".",slug:"/using-the-authorization-service",permalink:"/lmc-rbac-mvc/docs/using-the-authorization-service",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/using-the-authorization-service.md",tags:[],version:"current",sidebarPosition:7,frontMatter:{sidebar_position:7},sidebar:"tutorialSidebar",previous:{title:"Strategies",permalink:"/lmc-rbac-mvc/docs/strategies"},next:{title:"Cookbook",permalink:"/lmc-rbac-mvc/docs/cookbook"}},c={},l=[{value:"Injecting the Authorization Service",id:"injecting-the-authorization-service",level:2},{value:"Using initializers",id:"using-initializers",level:3},{value:"Using delegator factory",id:"using-delegator-factory",level:3},{value:"Using Factories",id:"using-factories",level:3},{value:"Permissions and Assertions",id:"permissions-and-assertions",level:2},{value:"Defining assertions",id:"defining-assertions",level:3},{value:"Defining the assertion map",id:"defining-the-assertion-map",level:3},{value:"Checking permissions in a service",id:"checking-permissions-in-a-service",level:3},{value:"Checking permissions in controllers and views",id:"checking-permissions-in-controllers-and-views",level:3},{value:"In a controller :",id:"in-a-controller-",level:4},{value:"In a view :",id:"in-a-view-",level:4},{value:"Defining additional permissions",id:"defining-additional-permissions",level:3}];function h(e){const n={a:"a",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",p:"p",pre:"pre",strong:"strong",...(0,t.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"using-the-authorization-service",children:"Using the Authorization Service"}),"\n",(0,s.jsx)(n.p,{children:"This section will teach you how to use the AuthorizationService to its full extent."}),"\n",(0,s.jsx)(n.h2,{id:"injecting-the-authorization-service",children:"Injecting the Authorization Service"}),"\n",(0,s.jsx)(n.h3,{id:"using-initializers",children:"Using initializers"}),"\n",(0,s.jsxs)(n.p,{children:["To automatically inject the authorization service into your classes, you can implement the\n",(0,s.jsx)(n.code,{children:"AuthorizationServiceAwareInterface"})," and use the trait, as shown below:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"namespace YourModule;\n\nuse LmcRbacMvc\\Service\\AuthorizationServiceAwareInterface;\nuse LmcRbacMvc\\Service\\AuthorizationServiceAwareTrait;\n\nclass MyClass implements AuthorizationServiceAwareInterface\n{\n use AuthorizationServiceAwareTrait;\n\n public function doSomethingThatRequiresAuth()\n {\n if (! $this->getAuthorizationService()->isGranted('deletePost')) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n return true;\n }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"Then, register the initializer in your config (it is not registered by default):"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // ...\n\n public function getServiceConfig()\n {\n return [\n 'initializers' => [\n 'LmcRbacMvc\\Initializer\\AuthorizationServiceInitializer'\n ]\n ];\n }\n}\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"While initializers allow rapid prototyping, their use can lead to more fragile code. We'd suggest using factories."}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"using-delegator-factory",children:"Using delegator factory"}),"\n",(0,s.jsxs)(n.p,{children:["LmcRbacMvc is shipped with a ",(0,s.jsx)(n.code,{children:"LmcRbacMvc\\Factory\\AuthorizationServiceDelegatorFactory"})," ",(0,s.jsx)(n.a,{href:"https://docs.laminas.dev/laminas-servicemanager/delegators/",children:"delegator factory"}),"\nto automatically inject the authorization service into your classes."]}),"\n",(0,s.jsxs)(n.p,{children:["As for the initializer, the class must implement the ",(0,s.jsx)(n.code,{children:"AuthorizationServiceAwareInterface"}),"."]}),"\n",(0,s.jsx)(n.p,{children:"You just have to add your classes to the right delegator :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // ...\n\n public function getServiceConfig()\n {\n return [\n 'invokables' => [\n 'Application\\Service\\MyClass' => 'Application\\Service\\MyClassService',\n ],\n 'delegators' => [\n 'Application\\Service\\MyClass' => [\n 'LmcRbacMvc\\Factory\\AuthorizationServiceDelegatorFactory',\n // eventually add more delegators here\n ],\n ],\n ];\n }\n}\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"While they need a little more configuration, delegator factories have better performances than initializers."}),"\n"]}),"\n",(0,s.jsx)(n.h3,{id:"using-factories",children:"Using Factories"}),"\n",(0,s.jsxs)(n.p,{children:["You can inject the AuthorizationService into your factories by using Laminas' ServiceManager. The AuthorizationService\nis known to the ServiceManager as ",(0,s.jsx)(n.code,{children:"'LmcRbacMvc\\Service\\AuthorizationService'"}),". Here is a classic example for injecting\nthe AuthorizationService:"]}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:"YourModule/Module.php"})}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // getAutoloaderConfig(), etc...\n\n public function getServiceConfig()\n {\n return [\n 'factories' => [\n 'MyService' => function($sm) {\n $authService = $sm->get('LmcRbacMvc\\Service\\AuthorizationService');\n return new MyService($authService);\n }\n ]\n ];\n }\n}\n"})}),"\n",(0,s.jsx)(n.h2,{id:"permissions-and-assertions",children:"Permissions and Assertions"}),"\n",(0,s.jsx)(n.p,{children:"Since you now know how to inject the AuthorizationService, let's use it!"}),"\n",(0,s.jsxs)(n.p,{children:["One of the great things the AuthorizationService brings are ",(0,s.jsx)(n.strong,{children:"assertions"}),". Assertions get executed ",(0,s.jsx)(n.em,{children:"if the identity\nin fact holds the permission you are requesting"}),". A common example is a blog post, which only the author can edit. In\nthis case, you have a ",(0,s.jsx)(n.code,{children:"post.edit"})," permission and run an assertion checking the author afterwards."]}),"\n",(0,s.jsx)(n.h3,{id:"defining-assertions",children:"Defining assertions"}),"\n",(0,s.jsxs)(n.p,{children:["The AssertionPluginManager is a great way for you to use assertions and IOC. You can add new assertions quite easily\nby adding this to your ",(0,s.jsx)(n.code,{children:"module.config.php"})," file:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'assertion_manager' => [\n 'factories' => [\n 'MyAssertion' => 'MyAssertionFactory'\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.h3,{id:"defining-the-assertion-map",children:"Defining the assertion map"}),"\n",(0,s.jsxs)(n.p,{children:["The assertion map can automatically map permissions to assertions. This means that every time you check for a\npermission with an assertion map, you'll include the assertion in your check. You can define the assertion map by\nadding this to your ",(0,s.jsx)(n.code,{children:"module.config.php"})," file:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'assertion_map' => [\n 'myPermission' => 'myAssertion'\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Now, every time you check for ",(0,s.jsx)(n.code,{children:"myPermission"}),", ",(0,s.jsx)(n.code,{children:"myAssertion"})," will be checked as well."]}),"\n",(0,s.jsx)(n.h3,{id:"checking-permissions-in-a-service",children:"Checking permissions in a service"}),"\n",(0,s.jsx)(n.p,{children:"So let's check for a permission, shall we?"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$authorizationService->isGranted('myPermission');\n"})}),"\n",(0,s.jsx)(n.p,{children:"That was easy, wasn't it?"}),"\n",(0,s.jsxs)(n.p,{children:[(0,s.jsx)(n.code,{children:"isGranted"})," checks if the current identity is granted the permission and additionally runs the assertion that is\nprovided by the assertion map."]}),"\n",(0,s.jsx)(n.h3,{id:"checking-permissions-in-controllers-and-views",children:"Checking permissions in controllers and views"}),"\n",(0,s.jsx)(n.p,{children:"LmcRbacMvc comes with both a controller plugin and a view helper to check permissions."}),"\n",(0,s.jsx)(n.h4,{id:"in-a-controller-",children:"In a controller :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:" public function doSomethingAction()\n {\n if (!$this->isGranted('myPermission')) {\n // redirect if not granted for example\n }\n }\n"})}),"\n",(0,s.jsx)(n.h4,{id:"in-a-view-",children:"In a view :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:" isGranted('myPermission')): ?>\n
\n

Display only if granted

\n
\n \n"})}),"\n",(0,s.jsx)(n.h3,{id:"defining-additional-permissions",children:"Defining additional permissions"}),"\n",(0,s.jsx)(n.p,{children:"But what if you don't want to use the assertion map? That's quite easy as well!"}),"\n",(0,s.jsx)(n.p,{children:"Here are four examples of how to run an assertion without using the assertion map:"}),"\n",(0,s.jsx)(n.p,{children:"Disable the assertion:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$authorizationService->setAssertion('myPermission', null);\n$authorizationService->isGranted('myPermission');\n"})}),"\n",(0,s.jsx)(n.p,{children:"Callback assertion:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$something = true;\n\n$authorizationService->setAssertion(\n 'myPermission',\n function(AuthorizationService $authorization, $context = true) use ($something) {\n return $something === $context\n }\n);\n\n$authorizationService->isGranted('myPermission'); // returns true, when the identity holds the permission `myPermission`\n"})}),"\n",(0,s.jsxs)(n.p,{children:["Object implementing ",(0,s.jsx)(n.code,{children:"AssertionInterface"}),":"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$context = true;\n\n$authorizationService->setAssertion('myPermission', new MyAssertion($foo, $bar));\n$authorizationService->isGranted('myPermission', $context);\n"})}),"\n",(0,s.jsx)(n.p,{children:"Using the AssertionPluginManager:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"$context = true;\n$authorizationService->setAssertion('myPermission', 'MyAssertion');\n$authorizationService->isGranted('myPermission', $context);\n"})}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.em,{children:"Please note: The context parameter is optional!"})})]})}function d(e={}){const{wrapper:n}={...(0,t.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(h,{...e})}):h(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>o,x:()=>a});var s=i(6540);const t={},r=s.createContext(t);function o(e){const n=s.useContext(r);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function a(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:o(e.components),s.createElement(r.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/533.11304ac5.js b/assets/js/533.11304ac5.js new file mode 100644 index 00000000..2ffcf39e --- /dev/null +++ b/assets/js/533.11304ac5.js @@ -0,0 +1 @@ +(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[533],{7293:(e,t,n)=>{"use strict";n.d(t,{A:()=>L});var s=n(6540),o=n(4848);function c(e){const{mdxAdmonitionTitle:t,rest:n}=function(e){const t=s.Children.toArray(e),n=t.find((e=>s.isValidElement(e)&&"mdxAdmonitionTitle"===e.type)),c=t.filter((e=>e!==n)),a=n?.props.children;return{mdxAdmonitionTitle:a,rest:c.length>0?(0,o.jsx)(o.Fragment,{children:c}):null}}(e.children),c=e.title??t;return{...e,...c&&{title:c},children:n}}var a=n(4164),r=n(1312),i=n(7559);const l={admonition:"admonition_xJq3",admonitionHeading:"admonitionHeading_Gvgb",admonitionIcon:"admonitionIcon_Rf37",admonitionContent:"admonitionContent_BuS1"};function d(e){let{type:t,className:n,children:s}=e;return(0,o.jsx)("div",{className:(0,a.A)(i.G.common.admonition,i.G.common.admonitionType(t),l.admonition,n),children:s})}function u(e){let{icon:t,title:n}=e;return(0,o.jsxs)("div",{className:l.admonitionHeading,children:[(0,o.jsx)("span",{className:l.admonitionIcon,children:t}),n]})}function m(e){let{children:t}=e;return t?(0,o.jsx)("div",{className:l.admonitionContent,children:t}):null}function h(e){const{type:t,icon:n,title:s,children:c,className:a}=e;return(0,o.jsxs)(d,{type:t,className:a,children:[(0,o.jsx)(u,{title:s,icon:n}),(0,o.jsx)(m,{children:c})]})}function p(e){return(0,o.jsx)("svg",{viewBox:"0 0 14 16",...e,children:(0,o.jsx)("path",{fillRule:"evenodd",d:"M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"})})}const f={icon:(0,o.jsx)(p,{}),title:(0,o.jsx)(r.A,{id:"theme.admonition.note",description:"The default label used for the Note admonition (:::note)",children:"note"})};function x(e){return(0,o.jsx)(h,{...f,...e,className:(0,a.A)("alert alert--secondary",e.className),children:e.children})}function b(e){return(0,o.jsx)("svg",{viewBox:"0 0 12 16",...e,children:(0,o.jsx)("path",{fillRule:"evenodd",d:"M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"})})}const g={icon:(0,o.jsx)(b,{}),title:(0,o.jsx)(r.A,{id:"theme.admonition.tip",description:"The default label used for the Tip admonition (:::tip)",children:"tip"})};function j(e){return(0,o.jsx)(h,{...g,...e,className:(0,a.A)("alert alert--success",e.className),children:e.children})}function v(e){return(0,o.jsx)("svg",{viewBox:"0 0 14 16",...e,children:(0,o.jsx)("path",{fillRule:"evenodd",d:"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"})})}const N={icon:(0,o.jsx)(v,{}),title:(0,o.jsx)(r.A,{id:"theme.admonition.info",description:"The default label used for the Info admonition (:::info)",children:"info"})};function y(e){return(0,o.jsx)(h,{...N,...e,className:(0,a.A)("alert alert--info",e.className),children:e.children})}function k(e){return(0,o.jsx)("svg",{viewBox:"0 0 16 16",...e,children:(0,o.jsx)("path",{fillRule:"evenodd",d:"M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"})})}const B={icon:(0,o.jsx)(k,{}),title:(0,o.jsx)(r.A,{id:"theme.admonition.warning",description:"The default label used for the Warning admonition (:::warning)",children:"warning"})};function C(e){return(0,o.jsx)("svg",{viewBox:"0 0 12 16",...e,children:(0,o.jsx)("path",{fillRule:"evenodd",d:"M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"})})}const A={icon:(0,o.jsx)(C,{}),title:(0,o.jsx)(r.A,{id:"theme.admonition.danger",description:"The default label used for the Danger admonition (:::danger)",children:"danger"})};const w={icon:(0,o.jsx)(k,{}),title:(0,o.jsx)(r.A,{id:"theme.admonition.caution",description:"The default label used for the Caution admonition (:::caution)",children:"caution"})};const E={...{note:x,tip:j,info:y,warning:function(e){return(0,o.jsx)(h,{...B,...e,className:(0,a.A)("alert alert--warning",e.className),children:e.children})},danger:function(e){return(0,o.jsx)(h,{...A,...e,className:(0,a.A)("alert alert--danger",e.className),children:e.children})}},...{secondary:e=>(0,o.jsx)(x,{title:"secondary",...e}),important:e=>(0,o.jsx)(y,{title:"important",...e}),success:e=>(0,o.jsx)(j,{title:"success",...e}),caution:function(e){return(0,o.jsx)(h,{...w,...e,className:(0,a.A)("alert alert--warning",e.className),children:e.children})}}};function L(e){const t=c(e),n=(s=t.type,E[s]||(console.warn(`No admonition component found for admonition type "${s}". Using Info as fallback.`),E.info));var s;return(0,o.jsx)(n,{...t})}},5533:(e,t,n)=>{"use strict";n.d(t,{A:()=>ie});var s=n(6540),o=n(8453),c=n(5260),a=n(2303),r=n(4164),i=n(5293),l=n(6342);function d(){const{prism:e}=(0,l.p)(),{colorMode:t}=(0,i.G)(),n=e.theme,s=e.darkTheme||n;return"dark"===t?s:n}var u=n(7559),m=n(8426),h=n.n(m);const p=/title=(?["'])(?.*?)\1/,f=/\{(?<range>[\d,-]+)\}/,x={js:{start:"\\/\\/",end:""},jsBlock:{start:"\\/\\*",end:"\\*\\/"},jsx:{start:"\\{\\s*\\/\\*",end:"\\*\\/\\s*\\}"},bash:{start:"#",end:""},html:{start:"\x3c!--",end:"--\x3e"}},b={...x,lua:{start:"--",end:""},wasm:{start:"\\;\\;",end:""},tex:{start:"%",end:""},vb:{start:"['\u2018\u2019]",end:""},vbnet:{start:"(?:_\\s*)?['\u2018\u2019]",end:""},rem:{start:"[Rr][Ee][Mm]\\b",end:""},f90:{start:"!",end:""},ml:{start:"\\(\\*",end:"\\*\\)"},cobol:{start:"\\*>",end:""}},g=Object.keys(x);function j(e,t){const n=e.map((e=>{const{start:n,end:s}=b[e];return`(?:${n}\\s*(${t.flatMap((e=>[e.line,e.block?.start,e.block?.end].filter(Boolean))).join("|")})\\s*${s})`})).join("|");return new RegExp(`^\\s*(?:${n})\\s*$`)}function v(e,t){let n=e.replace(/\n$/,"");const{language:s,magicComments:o,metastring:c}=t;if(c&&f.test(c)){const e=c.match(f).groups.range;if(0===o.length)throw new Error(`A highlight range has been given in code block's metastring (\`\`\` ${c}), but no magic comment config is available. Docusaurus applies the first magic comment entry's className for metastring ranges.`);const t=o[0].className,s=h()(e).filter((e=>e>0)).map((e=>[e-1,[t]]));return{lineClassNames:Object.fromEntries(s),code:n}}if(void 0===s)return{lineClassNames:{},code:n};const a=function(e,t){switch(e){case"js":case"javascript":case"ts":case"typescript":return j(["js","jsBlock"],t);case"jsx":case"tsx":return j(["js","jsBlock","jsx"],t);case"html":return j(["js","jsBlock","html"],t);case"python":case"py":case"bash":return j(["bash"],t);case"markdown":case"md":return j(["html","jsx","bash"],t);case"tex":case"latex":case"matlab":return j(["tex"],t);case"lua":case"haskell":case"sql":return j(["lua"],t);case"wasm":return j(["wasm"],t);case"vb":case"vba":case"visual-basic":return j(["vb","rem"],t);case"vbnet":return j(["vbnet","rem"],t);case"batch":return j(["rem"],t);case"basic":return j(["rem","f90"],t);case"fsharp":return j(["js","ml"],t);case"ocaml":case"sml":return j(["ml"],t);case"fortran":return j(["f90"],t);case"cobol":return j(["cobol"],t);default:return j(g,t)}}(s,o),r=n.split("\n"),i=Object.fromEntries(o.map((e=>[e.className,{start:0,range:""}]))),l=Object.fromEntries(o.filter((e=>e.line)).map((e=>{let{className:t,line:n}=e;return[n,t]}))),d=Object.fromEntries(o.filter((e=>e.block)).map((e=>{let{className:t,block:n}=e;return[n.start,t]}))),u=Object.fromEntries(o.filter((e=>e.block)).map((e=>{let{className:t,block:n}=e;return[n.end,t]})));for(let h=0;h<r.length;){const e=r[h].match(a);if(!e){h+=1;continue}const t=e.slice(1).find((e=>void 0!==e));l[t]?i[l[t]].range+=`${h},`:d[t]?i[d[t]].start=h:u[t]&&(i[u[t]].range+=`${i[u[t]].start}-${h-1},`),r.splice(h,1)}n=r.join("\n");const m={};return Object.entries(i).forEach((e=>{let[t,{range:n}]=e;h()(n).forEach((e=>{m[e]??=[],m[e].push(t)}))})),{lineClassNames:m,code:n}}const N={codeBlockContainer:"codeBlockContainer_Ckt0"};var y=n(4848);function k(e){let{as:t,...n}=e;const s=function(e){const t={color:"--prism-color",backgroundColor:"--prism-background-color"},n={};return Object.entries(e.plain).forEach((e=>{let[s,o]=e;const c=t[s];c&&"string"==typeof o&&(n[c]=o)})),n}(d());return(0,y.jsx)(t,{...n,style:s,className:(0,r.A)(n.className,N.codeBlockContainer,u.G.common.codeBlock)})}const B={codeBlockContent:"codeBlockContent_biex",codeBlockTitle:"codeBlockTitle_Ktv7",codeBlock:"codeBlock_bY9V",codeBlockStandalone:"codeBlockStandalone_MEMb",codeBlockLines:"codeBlockLines_e6Vv",codeBlockLinesWithNumbering:"codeBlockLinesWithNumbering_o6Pm",buttonGroup:"buttonGroup__atx"};function C(e){let{children:t,className:n}=e;return(0,y.jsx)(k,{as:"pre",tabIndex:0,className:(0,r.A)(B.codeBlockStandalone,"thin-scrollbar",n),children:(0,y.jsx)("code",{className:B.codeBlockLines,children:t})})}var A=n(9532);const w={attributes:!0,characterData:!0,childList:!0,subtree:!0};function E(e,t){const[n,o]=(0,s.useState)(),c=(0,s.useCallback)((()=>{o(e.current?.closest("[role=tabpanel][hidden]"))}),[e,o]);(0,s.useEffect)((()=>{c()}),[c]),function(e,t,n){void 0===n&&(n=w);const o=(0,A._q)(t),c=(0,A.Be)(n);(0,s.useEffect)((()=>{const t=new MutationObserver(o);return e&&t.observe(e,c),()=>t.disconnect()}),[e,o,c])}(n,(e=>{e.forEach((e=>{"attributes"===e.type&&"hidden"===e.attributeName&&(t(),c())}))}),{attributes:!0,characterData:!1,childList:!1,subtree:!1})}var L=n(1765);const T={codeLine:"codeLine_lJS_",codeLineNumber:"codeLineNumber_Tfdd",codeLineContent:"codeLineContent_feaV"};function _(e){let{line:t,classNames:n,showLineNumbers:s,getLineProps:o,getTokenProps:c}=e;1===t.length&&"\n"===t[0].content&&(t[0].content="");const a=o({line:t,className:(0,r.A)(n,s&&T.codeLine)}),i=t.map(((e,t)=>(0,y.jsx)("span",{...c({token:e,key:t})},t)));return(0,y.jsxs)("span",{...a,children:[s?(0,y.jsxs)(y.Fragment,{children:[(0,y.jsx)("span",{className:T.codeLineNumber}),(0,y.jsx)("span",{className:T.codeLineContent,children:i})]}):i,(0,y.jsx)("br",{})]})}var S=n(1312);function M(e){return(0,y.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,y.jsx)("path",{fill:"currentColor",d:"M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"})})}function z(e){return(0,y.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,y.jsx)("path",{fill:"currentColor",d:"M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"})})}const H={copyButtonCopied:"copyButtonCopied_obH4",copyButtonIcons:"copyButtonIcons_eSgA",copyButtonIcon:"copyButtonIcon_y97N",copyButtonSuccessIcon:"copyButtonSuccessIcon_LjdS"};function I(e){let{code:t,className:n}=e;const[o,c]=(0,s.useState)(!1),a=(0,s.useRef)(void 0),i=(0,s.useCallback)((()=>{!function(e,t){let{target:n=document.body}=void 0===t?{}:t;if("string"!=typeof e)throw new TypeError(`Expected parameter \`text\` to be a \`string\`, got \`${typeof e}\`.`);const s=document.createElement("textarea"),o=document.activeElement;s.value=e,s.setAttribute("readonly",""),s.style.contain="strict",s.style.position="absolute",s.style.left="-9999px",s.style.fontSize="12pt";const c=document.getSelection(),a=c.rangeCount>0&&c.getRangeAt(0);n.append(s),s.select(),s.selectionStart=0,s.selectionEnd=e.length;let r=!1;try{r=document.execCommand("copy")}catch{}s.remove(),a&&(c.removeAllRanges(),c.addRange(a)),o&&o.focus()}(t),c(!0),a.current=window.setTimeout((()=>{c(!1)}),1e3)}),[t]);return(0,s.useEffect)((()=>()=>window.clearTimeout(a.current)),[]),(0,y.jsx)("button",{type:"button","aria-label":o?(0,S.T)({id:"theme.CodeBlock.copied",message:"Copied",description:"The copied button label on code blocks"}):(0,S.T)({id:"theme.CodeBlock.copyButtonAriaLabel",message:"Copy code to clipboard",description:"The ARIA label for copy code blocks button"}),title:(0,S.T)({id:"theme.CodeBlock.copy",message:"Copy",description:"The copy button label on code blocks"}),className:(0,r.A)("clean-btn",n,H.copyButton,o&&H.copyButtonCopied),onClick:i,children:(0,y.jsxs)("span",{className:H.copyButtonIcons,"aria-hidden":"true",children:[(0,y.jsx)(M,{className:H.copyButtonIcon}),(0,y.jsx)(z,{className:H.copyButtonSuccessIcon})]})})}function R(e){return(0,y.jsx)("svg",{viewBox:"0 0 24 24",...e,children:(0,y.jsx)("path",{fill:"currentColor",d:"M4 19h6v-2H4v2zM20 5H4v2h16V5zm-3 6H4v2h13.25c1.1 0 2 .9 2 2s-.9 2-2 2H15v-2l-3 3l3 3v-2h2c2.21 0 4-1.79 4-4s-1.79-4-4-4z"})})}const V={wordWrapButtonIcon:"wordWrapButtonIcon_Bwma",wordWrapButtonEnabled:"wordWrapButtonEnabled_EoeP"};function $(e){let{className:t,onClick:n,isEnabled:s}=e;const o=(0,S.T)({id:"theme.CodeBlock.wordWrapToggle",message:"Toggle word wrap",description:"The title attribute for toggle word wrapping button of code block lines"});return(0,y.jsx)("button",{type:"button",onClick:n,className:(0,r.A)("clean-btn",t,s&&V.wordWrapButtonEnabled),"aria-label":o,title:o,children:(0,y.jsx)(R,{className:V.wordWrapButtonIcon,"aria-hidden":"true"})})}function W(e){let{children:t,className:n="",metastring:o,title:c,showLineNumbers:a,language:i}=e;const{prism:{defaultLanguage:u,magicComments:m}}=(0,l.p)(),h=function(e){return e?.toLowerCase()}(i??function(e){const t=e.split(" ").find((e=>e.startsWith("language-")));return t?.replace(/language-/,"")}(n)??u),f=d(),x=function(){const[e,t]=(0,s.useState)(!1),[n,o]=(0,s.useState)(!1),c=(0,s.useRef)(null),a=(0,s.useCallback)((()=>{const n=c.current.querySelector("code");e?n.removeAttribute("style"):(n.style.whiteSpace="pre-wrap",n.style.overflowWrap="anywhere"),t((e=>!e))}),[c,e]),r=(0,s.useCallback)((()=>{const{scrollWidth:e,clientWidth:t}=c.current,n=e>t||c.current.querySelector("code").hasAttribute("style");o(n)}),[c]);return E(c,r),(0,s.useEffect)((()=>{r()}),[e,r]),(0,s.useEffect)((()=>(window.addEventListener("resize",r,{passive:!0}),()=>{window.removeEventListener("resize",r)})),[r]),{codeBlockRef:c,isEnabled:e,isCodeScrollable:n,toggle:a}}(),b=function(e){return e?.match(p)?.groups.title??""}(o)||c,{lineClassNames:g,code:j}=v(t,{metastring:o,language:h,magicComments:m}),N=a??function(e){return Boolean(e?.includes("showLineNumbers"))}(o);return(0,y.jsxs)(k,{as:"div",className:(0,r.A)(n,h&&!n.includes(`language-${h}`)&&`language-${h}`),children:[b&&(0,y.jsx)("div",{className:B.codeBlockTitle,children:b}),(0,y.jsxs)("div",{className:B.codeBlockContent,children:[(0,y.jsx)(L.f4,{theme:f,code:j,language:h??"text",children:e=>{let{className:t,style:n,tokens:s,getLineProps:o,getTokenProps:c}=e;return(0,y.jsx)("pre",{tabIndex:0,ref:x.codeBlockRef,className:(0,r.A)(t,B.codeBlock,"thin-scrollbar"),style:n,children:(0,y.jsx)("code",{className:(0,r.A)(B.codeBlockLines,N&&B.codeBlockLinesWithNumbering),children:s.map(((e,t)=>(0,y.jsx)(_,{line:e,getLineProps:o,getTokenProps:c,classNames:g[t],showLineNumbers:N},t)))})})}}),(0,y.jsxs)("div",{className:B.buttonGroup,children:[(x.isEnabled||x.isCodeScrollable)&&(0,y.jsx)($,{className:B.codeButton,onClick:()=>x.toggle(),isEnabled:x.isEnabled}),(0,y.jsx)(I,{className:B.codeButton,code:j})]})]})]})}function P(e){let{children:t,...n}=e;const o=(0,a.A)(),c=function(e){return s.Children.toArray(e).some((e=>(0,s.isValidElement)(e)))?e:Array.isArray(e)?e.join(""):e}(t),r="string"==typeof c?W:C;return(0,y.jsx)(r,{...n,children:c},String(o))}function D(e){return(0,y.jsx)("code",{...e})}var O=n(8774);var q=n(3427),G=n(1422);const F={details:"details_lb9f",isBrowser:"isBrowser_bmU9",collapsibleContent:"collapsibleContent_i85q"};function U(e){return!!e&&("SUMMARY"===e.tagName||U(e.parentElement))}function J(e,t){return!!e&&(e===t||J(e.parentElement,t))}function Y(e){let{summary:t,children:n,...o}=e;(0,q.A)().collectAnchor(o.id);const c=(0,a.A)(),i=(0,s.useRef)(null),{collapsed:l,setCollapsed:d}=(0,G.u)({initialState:!o.open}),[u,m]=(0,s.useState)(o.open),h=s.isValidElement(t)?t:(0,y.jsx)("summary",{children:t??"Details"});return(0,y.jsxs)("details",{...o,ref:i,open:u,"data-collapsed":l,className:(0,r.A)(F.details,c&&F.isBrowser,o.className),onMouseDown:e=>{U(e.target)&&e.detail>1&&e.preventDefault()},onClick:e=>{e.stopPropagation();const t=e.target;U(t)&&J(t,i.current)&&(e.preventDefault(),l?(d(!1),m(!0)):d(!0))},children:[h,(0,y.jsx)(G.N,{lazy:!1,collapsed:l,disableSSRStyle:!0,onCollapseTransitionEnd:e=>{d(e),m(!e)},children:(0,y.jsx)("div",{className:F.collapsibleContent,children:n})})]})}const Z={details:"details_b_Ee"},K="alert alert--info";function Q(e){let{...t}=e;return(0,y.jsx)(Y,{...t,className:(0,r.A)(K,Z.details,t.className)})}function X(e){const t=s.Children.toArray(e.children),n=t.find((e=>s.isValidElement(e)&&"summary"===e.type)),o=(0,y.jsx)(y.Fragment,{children:t.filter((e=>e!==n))});return(0,y.jsx)(Q,{...e,summary:n,children:o})}var ee=n(1107);function te(e){return(0,y.jsx)(ee.A,{...e})}const ne={containsTaskList:"containsTaskList_mC6p"};function se(e){if(void 0!==e)return(0,r.A)(e,e?.includes("contains-task-list")&&ne.containsTaskList)}const oe={img:"img_ev3q"};var ce=n(7293),ae=n(418);const re={Head:c.A,details:X,Details:X,code:function(e){return function(e){return void 0!==e.children&&s.Children.toArray(e.children).every((e=>"string"==typeof e&&!e.includes("\n")))}(e)?(0,y.jsx)(D,{...e}):(0,y.jsx)(P,{...e})},a:function(e){return(0,y.jsx)(O.A,{...e})},pre:function(e){return(0,y.jsx)(y.Fragment,{children:e.children})},ul:function(e){return(0,y.jsx)("ul",{...e,className:se(e.className)})},li:function(e){return(0,q.A)().collectAnchor(e.id),(0,y.jsx)("li",{...e})},img:function(e){return(0,y.jsx)("img",{decoding:"async",loading:"lazy",...e,className:(t=e.className,(0,r.A)(t,oe.img))});var t},h1:e=>(0,y.jsx)(te,{as:"h1",...e}),h2:e=>(0,y.jsx)(te,{as:"h2",...e}),h3:e=>(0,y.jsx)(te,{as:"h3",...e}),h4:e=>(0,y.jsx)(te,{as:"h4",...e}),h5:e=>(0,y.jsx)(te,{as:"h5",...e}),h6:e=>(0,y.jsx)(te,{as:"h6",...e}),admonition:ce.A,mermaid:ae.A};function ie(e){let{children:t}=e;return(0,y.jsx)(o.x,{components:re,children:t})}},8426:(e,t)=>{function n(e){let t,n=[];for(let s of e.split(",").map((e=>e.trim())))if(/^-?\d+$/.test(s))n.push(parseInt(s,10));else if(t=s.match(/^(-?\d+)(-|\.\.\.?|\u2025|\u2026|\u22EF)(-?\d+)$/)){let[e,s,o,c]=t;if(s&&c){s=parseInt(s),c=parseInt(c);const e=s<c?1:-1;"-"!==o&&".."!==o&&"\u2025"!==o||(c+=e);for(let t=s;t!==c;t+=e)n.push(t)}}return n}t.default=n,e.exports=n},8453:(e,t,n)=>{"use strict";n.d(t,{R:()=>a,x:()=>r});var s=n(6540);const o={},c=s.createContext(o);function a(e){const t=s.useContext(c);return s.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function r(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:a(e.components),s.createElement(c.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/5b3ddbaa.579400ac.js b/assets/js/5b3ddbaa.579400ac.js new file mode 100644 index 00000000..74705b55 --- /dev/null +++ b/assets/js/5b3ddbaa.579400ac.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[75],{5126:e=>{e.exports=JSON.parse('{"blogPosts":[{"id":"new-documentation","metadata":{"permalink":"/lmc-rbac-mvc/blog/new-documentation","editUrl":"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2024-02-22-New-documentation.md","source":"@site/blog/2024-02-22-New-documentation.md","title":"New documentation","description":"This the new documentation site dedicated to the LmcRbacMvc module.","date":"2024-02-22T00:00:00.000Z","formattedDate":"February 22, 2024","tags":[{"label":"laminas","permalink":"/lmc-rbac-mvc/blog/tags/laminas"},{"label":"PHP","permalink":"/lmc-rbac-mvc/blog/tags/php"},{"label":"lmcrbacmvc","permalink":"/lmc-rbac-mvc/blog/tags/lmcrbacmvc"}],"readingTime":0.11,"hasTruncateMarker":false,"authors":[{"name":"Eric Richer","title":"LM-Commons Administrator","url":"https://github.com/visto9259","imageURL":"https://github.com/visto9259.png","key":"ericr"}],"frontMatter":{"slug":"new-documentation","title":"New documentation","authors":["ericr"],"tags":["laminas","PHP","lmcrbacmvc"]},"unlisted":false,"nextItem":{"title":"Welcome","permalink":"/lmc-rbac-mvc/blog/welcome"}},"content":"This the new documentation site dedicated to the LmcRbacMvc module.\\n\\nThere are no changes to the code, just improvements in the documentation."},{"id":"welcome","metadata":{"permalink":"/lmc-rbac-mvc/blog/welcome","editUrl":"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2022-08-02-welcome.md","source":"@site/blog/2022-08-02-welcome.md","title":"Welcome","description":"Welcome to the new documentation website for the LM-Commons organization.","date":"2022-08-02T00:00:00.000Z","formattedDate":"August 2, 2022","tags":[{"label":"laminas","permalink":"/lmc-rbac-mvc/blog/tags/laminas"},{"label":"PHP","permalink":"/lmc-rbac-mvc/blog/tags/php"}],"readingTime":0.155,"hasTruncateMarker":false,"authors":[{"name":"Eric Richer","title":"LM-Commons Administrator","url":"https://github.com/visto9259","imageURL":"https://github.com/visto9259.png","key":"ericr"}],"frontMatter":{"slug":"welcome","title":"Welcome","authors":["ericr"],"tags":["laminas","PHP"]},"unlisted":false,"prevItem":{"title":"New documentation","permalink":"/lmc-rbac-mvc/blog/new-documentation"}},"content":"Welcome to the new documentation website for the LM-Commons organization.\\n\\nThis site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages."}]}')}}]); \ No newline at end of file diff --git a/assets/js/5b72c13b.e8b2bfc0.js b/assets/js/5b72c13b.e8b2bfc0.js new file mode 100644 index 00000000..39973bdd --- /dev/null +++ b/assets/js/5b72c13b.e8b2bfc0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[698],{4061:s=>{s.exports=JSON.parse('{"name":"docusaurus-plugin-content-pages","id":"default"}')}}]); \ No newline at end of file diff --git a/assets/js/5e95c892.1dd55255.js b/assets/js/5e95c892.1dd55255.js new file mode 100644 index 00000000..f34b1c88 --- /dev/null +++ b/assets/js/5e95c892.1dd55255.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[647],{7121:(e,s,r)=>{r.r(s),r.d(s,{default:()=>l});r(6540);var c=r(4164),u=r(1003),a=r(7559),d=r(2831),n=r(781),t=r(4848);function l(e){return(0,t.jsx)(u.e3,{className:(0,c.A)(a.G.wrapper.docsPages),children:(0,t.jsx)(n.A,{children:(0,d.v)(e.route.routes)})})}}}]); \ No newline at end of file diff --git a/assets/js/6875c492.f1af0c91.js b/assets/js/6875c492.f1af0c91.js new file mode 100644 index 00000000..0e035497 --- /dev/null +++ b/assets/js/6875c492.f1af0c91.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[813],{7713:(e,t,n)=>{n.d(t,{A:()=>l});n(6540);var s=n(1312),a=n(9022),i=n(4848);function l(e){const{metadata:t}=e,{previousPage:n,nextPage:l}=t;return(0,i.jsxs)("nav",{className:"pagination-nav","aria-label":(0,s.T)({id:"theme.blog.paginator.navAriaLabel",message:"Blog list page navigation",description:"The ARIA label for the blog pagination"}),children:[n&&(0,i.jsx)(a.A,{permalink:n,title:(0,i.jsx)(s.A,{id:"theme.blog.paginator.newerEntries",description:"The label used to navigate to the newer blog posts page (previous page)",children:"Newer Entries"})}),l&&(0,i.jsx)(a.A,{permalink:l,title:(0,i.jsx)(s.A,{id:"theme.blog.paginator.olderEntries",description:"The label used to navigate to the older blog posts page (next page)",children:"Older Entries"}),isNext:!0})]})}},3892:(e,t,n)=>{n.d(t,{A:()=>l});n(6540);var s=n(7131),a=n(8258),i=n(4848);function l(e){let{items:t,component:n=a.A}=e;return(0,i.jsx)(i.Fragment,{children:t.map((e=>{let{content:t}=e;return(0,i.jsx)(s.i,{content:t,children:(0,i.jsx)(n,{children:(0,i.jsx)(t,{})})},t.metadata.permalink)}))})}},3069:(e,t,n)=>{n.r(t),n.d(t,{default:()=>A});n(6540);var s=n(4164),a=n(1312),i=n(5846),l=n(1003),r=n(7559),o=n(8774),c=n(6535),d=n(7713),g=n(1463),u=n(3892),h=n(996),p=n(1107),m=n(4848);function x(e){const t=function(){const{selectMessage:e}=(0,i.W)();return t=>e(t,(0,a.T)({id:"theme.blog.post.plurals",description:'Pluralized label for "{count} posts". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One post|{count} posts"},{count:t}))}();return(0,a.T)({id:"theme.blog.tagTitle",description:"The title of the page for a blog tag",message:'{nPosts} tagged with "{tagName}"'},{nPosts:t(e.count),tagName:e.label})}function j(e){let{tag:t}=e;const n=x(t);return(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)(l.be,{title:n}),(0,m.jsx)(g.A,{tag:"blog_tags_posts"})]})}function b(e){let{tag:t,items:n,sidebar:s,listMetadata:i}=e;const l=x(t);return(0,m.jsxs)(c.A,{sidebar:s,children:[t.unlisted&&(0,m.jsx)(h.A,{}),(0,m.jsxs)("header",{className:"margin-bottom--xl",children:[(0,m.jsx)(p.A,{as:"h1",children:l}),(0,m.jsx)(o.A,{href:t.allTagsPath,children:(0,m.jsx)(a.A,{id:"theme.tags.tagsPageLink",description:"The label of the link targeting the tag list page",children:"View All Tags"})})]}),(0,m.jsx)(u.A,{items:n}),(0,m.jsx)(d.A,{metadata:i})]})}function A(e){return(0,m.jsxs)(l.e3,{className:(0,s.A)(r.G.wrapper.blogPages,r.G.page.blogTagPostListPage),children:[(0,m.jsx)(j,{...e}),(0,m.jsx)(b,{...e})]})}},996:(e,t,n)=>{n.d(t,{A:()=>h});n(6540);var s=n(4164),a=n(1312),i=n(5260),l=n(4848);function r(){return(0,l.jsx)(a.A,{id:"theme.unlistedContent.title",description:"The unlisted content banner title",children:"Unlisted page"})}function o(){return(0,l.jsx)(a.A,{id:"theme.unlistedContent.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function c(){return(0,l.jsx)(i.A,{children:(0,l.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}var d=n(7559),g=n(7293);function u(e){let{className:t}=e;return(0,l.jsx)(g.A,{type:"caution",title:(0,l.jsx)(r,{}),className:(0,s.A)(t,d.G.common.unlistedBanner),children:(0,l.jsx)(o,{})})}function h(e){return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(c,{}),(0,l.jsx)(u,{...e})]})}}}]); \ No newline at end of file diff --git a/assets/js/72e14192.a1fe92a6.js b/assets/js/72e14192.a1fe92a6.js new file mode 100644 index 00000000..06bb7d68 --- /dev/null +++ b/assets/js/72e14192.a1fe92a6.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[814],{3744:(e,n,i)=>{i.r(n),i.d(n,{assets:()=>a,contentTitle:()=>s,default:()=>h,frontMatter:()=>o,metadata:()=>c,toc:()=>d});var t=i(4848),r=i(8453);const o={sidebar_position:3},s="Quick Start",c={id:"quick-start",title:"Quick Start",description:"In this section, you will learn:",source:"@site/docs/quick-start.md",sourceDirName:".",slug:"/quick-start",permalink:"/lmc-rbac-mvc/docs/quick-start",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/quick-start.md",tags:[],version:"current",sidebarPosition:3,frontMatter:{sidebar_position:3},sidebar:"tutorialSidebar",previous:{title:"Requirements and Installation",permalink:"/lmc-rbac-mvc/docs/installation"},next:{title:"Role providers",permalink:"/lmc-rbac-mvc/docs/role-providers"}},a={},d=[{value:"Specifying an identity provider",id:"specifying-an-identity-provider",level:2},{value:"Adding a guard",id:"adding-a-guard",level:2},{value:"Adding a role provider",id:"adding-a-role-provider",level:2},{value:"Registering a strategy",id:"registering-a-strategy",level:2},{value:"Using the authorization service",id:"using-the-authorization-service",level:2}];function l(e){const n={a:"a",admonition:"admonition",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,r.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(n.h1,{id:"quick-start",children:"Quick Start"}),"\n",(0,t.jsx)(n.p,{children:"In this section, you will learn:"}),"\n",(0,t.jsxs)(n.ul,{children:["\n",(0,t.jsx)(n.li,{children:"How to set up the module"}),"\n",(0,t.jsx)(n.li,{children:"How to specify an identity provider"}),"\n",(0,t.jsx)(n.li,{children:"How to add a simple role provider"}),"\n"]}),"\n",(0,t.jsx)(n.p,{children:"Before starting the quick start, make sure you have properly installed the module by following the instructions in\nthe README file."}),"\n",(0,t.jsx)(n.h2,{id:"specifying-an-identity-provider",children:"Specifying an identity provider"}),"\n",(0,t.jsxs)(n.p,{children:["By default, LmcRbacMvc internally uses the ",(0,t.jsx)(n.code,{children:"Laminas\\Authentication\\AuthenticationService"})," service key to retrieve the user (logged or\nnot). Therefore, you must implement and register this service in your application by adding these lines in your ",(0,t.jsx)(n.code,{children:"module.config.php"})," file:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"return [\n 'service_manager' => [\n 'factories' => [\n\t 'Laminas\\Authentication\\AuthenticationService' => function($sm) {\n\t // Create your authentication service!\n\t }\n\t ]\n ]\n];\n"})}),"\n",(0,t.jsx)(n.admonition,{type:"tip",children:(0,t.jsxs)(n.p,{children:["If you are also using the ",(0,t.jsx)(n.a,{href:"https://github.com/lm-commons/lmcuser",children:"LmcUser"})," package, then the ",(0,t.jsx)(n.code,{children:"Laminas\\Authentication\\AuthenticationService"})," will be provided for you and there is no need to implement your own."]})}),"\n",(0,t.jsxs)(n.p,{children:["The identity given by ",(0,t.jsx)(n.code,{children:"Laminas\\Authentication\\AuthenticationService"})," must implement ",(0,t.jsx)(n.code,{children:"LmcRbacMvc\\Identity\\IdentityInterface"}),"."]}),"\n",(0,t.jsx)(n.admonition,{type:"warning",children:(0,t.jsx)(n.p,{children:"Note that the default identity provided with Laminas does not implement this interface, neither does the LmcUser suite."})}),"\n",(0,t.jsxs)(n.p,{children:["LmcRbacMvc is flexible enough to use something other than the built-in ",(0,t.jsx)(n.code,{children:"AuthenticationService"}),", by specifying custom\nidentity providers. For more information, refer ",(0,t.jsx)(n.a,{href:"/lmc-rbac-mvc/docs/role-providers#identity-providers",children:"to this section"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"adding-a-guard",children:"Adding a guard"}),"\n",(0,t.jsxs)(n.p,{children:["A guard allows your application to block access to routes and/or controllers using a simple syntax. For instance, this configuration\ngrants access to any route that begins with ",(0,t.jsx)(n.code,{children:"admin"})," (or is exactly ",(0,t.jsx)(n.code,{children:"admin"}),") to the ",(0,t.jsx)(n.code,{children:"admin"})," role only:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n\t 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'admin*' => ['admin']\n\t ]\n ]\n ]\n];\n"})}),"\n",(0,t.jsxs)(n.p,{children:["LmcRbacMvc has several built-in guards, and you can also register your own guards. For more information, refer\n",(0,t.jsx)(n.a,{href:"/lmc-rbac-mvc/docs/guards#built-in-guards",children:"to this section"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"adding-a-role-provider",children:"Adding a role provider"}),"\n",(0,t.jsx)(n.p,{children:"RBAC model is based on roles. Therefore, for LmcRbacMvc to work properly, it must be aware of all the roles that are\nused inside your application."}),"\n",(0,t.jsxs)(n.p,{children:["This configuration creates an ",(0,t.jsx)(n.em,{children:"admin"})," role that has a child role called ",(0,t.jsx)(n.em,{children:"member"}),". The ",(0,t.jsx)(n.em,{children:"admin"})," role automatically\ninherits the ",(0,t.jsx)(n.em,{children:"member"})," permissions."]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'role_provider' => [\n\t 'LmcRbacMvc\\Role\\InMemoryRoleProvider' => [\n\t 'admin' => [\n\t 'children' => ['member'],\n\t 'permissions' => ['delete']\n\t ],\n\t\t 'member' => [\n\t\t 'permissions' => ['edit']\n\t\t ]\n\t ]\n\t ]\n ]\n];\n"})}),"\n",(0,t.jsxs)(n.p,{children:["In this example, the ",(0,t.jsx)(n.em,{children:"admin"})," role has two permissions: ",(0,t.jsx)(n.code,{children:"delete"})," and ",(0,t.jsx)(n.code,{children:"edit"})," (because it inherits the permissions from\nits child), while the ",(0,t.jsx)(n.em,{children:"member"})," role only has the ",(0,t.jsx)(n.code,{children:"edit"})," permission."]}),"\n",(0,t.jsxs)(n.p,{children:["LmcRbacMvc has several built-in role providers, and you can also register your own role providers. For more information,\nrefer ",(0,t.jsx)(n.a,{href:"/lmc-rbac-mvc/docs/role-providers#built-in-role-providers",children:"to this section"}),"."]}),"\n",(0,t.jsx)(n.h2,{id:"registering-a-strategy",children:"Registering a strategy"}),"\n",(0,t.jsxs)(n.p,{children:["When a guard blocks access to a route/controller, or if you throw the ",(0,t.jsx)(n.code,{children:"LmcRbacMvc\\Exception\\UnauthorizedException"}),"\nexception in your service, LmcRbacMvc automatically performs some logic for you depending on the view strategy used."]}),"\n",(0,t.jsxs)(n.p,{children:['For instance, if you want LmcRbacMvc to automatically redirect all unauthorized requests to the "login" route, add\nthe following code in the ',(0,t.jsx)(n.code,{children:"onBootstrap"})," method of your ",(0,t.jsx)(n.code,{children:"Module.php"})," class:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"public function onBootstrap(MvcEvent $e)\n{\n $app = $e->getApplication();\n $sm = $app->getServiceManager();\n $em = $app->getEventManager();\n \n $listener = $sm->get(\\LmcRbacMvc\\View\\Strategy\\RedirectStrategy::class);\n $listener->attach($em);\n}\n"})}),"\n",(0,t.jsxs)(n.p,{children:["By default, ",(0,t.jsx)(n.code,{children:"RedirectStrategy"}),' redirects all unauthorized requests to a route named "login" when the user is not connected\nand to a route named "home" when the user is connected. This is, of course, entirely configurable.']}),"\n",(0,t.jsxs)(n.blockquote,{children:["\n",(0,t.jsxs)(n.p,{children:["For flexibility purposes, LmcRbacMvc ",(0,t.jsx)(n.strong,{children:"does not"})," register any strategy for you by default!"]}),"\n"]}),"\n",(0,t.jsxs)(n.p,{children:["For more information about built-in strategies, refer ",(0,t.jsx)(n.a,{href:"/lmc-rbac-mvc/docs/strategies#built-in-strategies",children:"to this section"}),".\n",(0,t.jsx)(n.a,{href:"/lmc-rbac-mvc/docs/strategies",children:"to this section"})]}),"\n",(0,t.jsx)(n.h2,{id:"using-the-authorization-service",children:"Using the authorization service"}),"\n",(0,t.jsx)(n.p,{children:"Now that LmcRbacMvc is properly configured, you can inject the authorization service into any class and use it to check\nif the current identity is granted to do something."}),"\n",(0,t.jsxs)(n.p,{children:["The authorization service is registered inside the service manager using the following key: ",(0,t.jsx)(n.code,{children:"LmcRbacMvc\\Service\\AuthorizationService"}),".\nOnce injected, you can use it as follows:"]}),"\n",(0,t.jsx)(n.pre,{children:(0,t.jsx)(n.code,{className:"language-php",children:"use LmcRbacMvc\\Exception\\UnauthorizedException;\n\nclass ActionController extends \\Laminas\\Mvc\\Controller\\AbstractActionController {\npublic function delete()\n{\n if (!$this->authorizationService->isGranted('delete')) {\n throw new UnauthorizedException();\n }\n\n // Delete the post\n}\n}\n"})})]})}function h(e={}){const{wrapper:n}={...(0,r.R)(),...e.components};return n?(0,t.jsx)(n,{...e,children:(0,t.jsx)(l,{...e})}):l(e)}},8453:(e,n,i)=>{i.d(n,{R:()=>s,x:()=>c});var t=i(6540);const r={},o=t.createContext(r);function s(e){const n=t.useContext(o);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:s(e.components),t.createElement(o.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/747.847fa25b.js b/assets/js/747.847fa25b.js new file mode 100644 index 00000000..d9039af1 --- /dev/null +++ b/assets/js/747.847fa25b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[747],{6535:(e,t,s)=>{s.d(t,{A:()=>f});var a=s(6540),n=s(4164),r=s(781),l=s(4581),i=s(8774),o=s(1312),c=s(6347),m=s(9169);function d(e){const{pathname:t}=(0,c.zy)();return(0,a.useMemo)((()=>e.filter((e=>function(e,t){return!(e.unlisted&&!(0,m.ys)(e.permalink,t))}(e,t)))),[e,t])}const u={sidebar:"sidebar_re4s",sidebarItemTitle:"sidebarItemTitle_pO2u",sidebarItemList:"sidebarItemList_Yudw",sidebarItem:"sidebarItem__DBe",sidebarItemLink:"sidebarItemLink_mo7H",sidebarItemLinkActive:"sidebarItemLinkActive_I1ZP"};var h=s(4848);function g(e){let{sidebar:t}=e;const s=d(t.items);return(0,h.jsx)("aside",{className:"col col--3",children:(0,h.jsxs)("nav",{className:(0,n.A)(u.sidebar,"thin-scrollbar"),"aria-label":(0,o.T)({id:"theme.blog.sidebar.navAriaLabel",message:"Blog recent posts navigation",description:"The ARIA label for recent posts in the blog sidebar"}),children:[(0,h.jsx)("div",{className:(0,n.A)(u.sidebarItemTitle,"margin-bottom--md"),children:t.title}),(0,h.jsx)("ul",{className:(0,n.A)(u.sidebarItemList,"clean-list"),children:s.map((e=>(0,h.jsx)("li",{className:u.sidebarItem,children:(0,h.jsx)(i.A,{isNavLink:!0,to:e.permalink,className:u.sidebarItemLink,activeClassName:u.sidebarItemLinkActive,children:e.title})},e.permalink)))})]})})}var p=s(5600);function x(e){let{sidebar:t}=e;const s=d(t.items);return(0,h.jsx)("ul",{className:"menu__list",children:s.map((e=>(0,h.jsx)("li",{className:"menu__list-item",children:(0,h.jsx)(i.A,{isNavLink:!0,to:e.permalink,className:"menu__link",activeClassName:"menu__link--active",children:e.title})},e.permalink)))})}function j(e){return(0,h.jsx)(p.GX,{component:x,props:e})}function b(e){let{sidebar:t}=e;const s=(0,l.l)();return t?.items.length?"mobile"===s?(0,h.jsx)(j,{sidebar:t}):(0,h.jsx)(g,{sidebar:t}):null}function f(e){const{sidebar:t,toc:s,children:a,...l}=e,i=t&&t.items.length>0;return(0,h.jsx)(r.A,{...l,children:(0,h.jsx)("div",{className:"container margin-vert--lg",children:(0,h.jsxs)("div",{className:"row",children:[(0,h.jsx)(b,{sidebar:t}),(0,h.jsx)("main",{className:(0,n.A)("col",{"col--7":i,"col--9 col--offset-1":!i}),itemScope:!0,itemType:"https://schema.org/Blog",children:a}),s&&(0,h.jsx)("div",{className:"col col--2",children:s})]})})})}},8258:(e,t,s)=>{s.d(t,{A:()=>C});s(6540);var a=s(4164),n=s(7131),r=s(6025),l=s(4848);function i(e){let{children:t,className:s}=e;const{frontMatter:a,assets:i,metadata:{description:o}}=(0,n.e)(),{withBaseUrl:c}=(0,r.h)(),m=i.image??a.image,d=a.keywords??[];return(0,l.jsxs)("article",{className:s,itemProp:"blogPost",itemScope:!0,itemType:"https://schema.org/BlogPosting",children:[o&&(0,l.jsx)("meta",{itemProp:"description",content:o}),m&&(0,l.jsx)("link",{itemProp:"image",href:c(m,{absolute:!0})}),d.length>0&&(0,l.jsx)("meta",{itemProp:"keywords",content:d.join(",")}),t]})}var o=s(8774);const c={title:"title_f1Hy"};function m(e){let{className:t}=e;const{metadata:s,isBlogPostPage:r}=(0,n.e)(),{permalink:i,title:m}=s,d=r?"h1":"h2";return(0,l.jsx)(d,{className:(0,a.A)(c.title,t),itemProp:"headline",children:r?m:(0,l.jsx)(o.A,{itemProp:"url",to:i,children:m})})}var d=s(1312),u=s(5846);const h={container:"container_mt6G"};function g(e){let{readingTime:t}=e;const s=function(){const{selectMessage:e}=(0,u.W)();return t=>{const s=Math.ceil(t);return e(s,(0,d.T)({id:"theme.blog.post.readingTime.plurals",description:'Pluralized label for "{readingTime} min read". Use as much plural forms (separated by "|") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)',message:"One min read|{readingTime} min read"},{readingTime:s}))}}();return(0,l.jsx)(l.Fragment,{children:s(t)})}function p(e){let{date:t,formattedDate:s}=e;return(0,l.jsx)("time",{dateTime:t,itemProp:"datePublished",children:s})}function x(){return(0,l.jsx)(l.Fragment,{children:" \xb7 "})}function j(e){let{className:t}=e;const{metadata:s}=(0,n.e)(),{date:r,formattedDate:i,readingTime:o}=s;return(0,l.jsxs)("div",{className:(0,a.A)(h.container,"margin-vert--md",t),children:[(0,l.jsx)(p,{date:r,formattedDate:i}),void 0!==o&&(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(x,{}),(0,l.jsx)(g,{readingTime:o})]})]})}function b(e){return e.href?(0,l.jsx)(o.A,{...e}):(0,l.jsx)(l.Fragment,{children:e.children})}function f(e){let{author:t,className:s}=e;const{name:n,title:r,url:i,imageURL:o,email:c}=t,m=i||c&&`mailto:${c}`||void 0;return(0,l.jsxs)("div",{className:(0,a.A)("avatar margin-bottom--sm",s),children:[o&&(0,l.jsx)(b,{href:m,className:"avatar__photo-link",children:(0,l.jsx)("img",{className:"avatar__photo",src:o,alt:n,itemProp:"image"})}),n&&(0,l.jsxs)("div",{className:"avatar__intro",itemProp:"author",itemScope:!0,itemType:"https://schema.org/Person",children:[(0,l.jsx)("div",{className:"avatar__name",children:(0,l.jsx)(b,{href:m,itemProp:"url",children:(0,l.jsx)("span",{itemProp:"name",children:n})})}),r&&(0,l.jsx)("small",{className:"avatar__subtitle",itemProp:"description",children:r})]})]})}const v={authorCol:"authorCol_Hf19",imageOnlyAuthorRow:"imageOnlyAuthorRow_pa_O",imageOnlyAuthorCol:"imageOnlyAuthorCol_G86a"};function A(e){let{className:t}=e;const{metadata:{authors:s},assets:r}=(0,n.e)();if(0===s.length)return null;const i=s.every((e=>{let{name:t}=e;return!t}));return(0,l.jsx)("div",{className:(0,a.A)("margin-top--md margin-bottom--sm",i?v.imageOnlyAuthorRow:"row",t),children:s.map(((e,t)=>(0,l.jsx)("div",{className:(0,a.A)(!i&&"col col--6",i?v.imageOnlyAuthorCol:v.authorCol),children:(0,l.jsx)(f,{author:{...e,imageURL:r.authorsImageUrls[t]??e.imageURL}})},t)))})}function N(){return(0,l.jsxs)("header",{children:[(0,l.jsx)(m,{}),(0,l.jsx)(j,{}),(0,l.jsx)(A,{})]})}var _=s(440),P=s(5533);function k(e){let{children:t,className:s}=e;const{isBlogPostPage:r}=(0,n.e)();return(0,l.jsx)("div",{id:r?_.blogPostContainerID:void 0,className:(0,a.A)("markdown",s),itemProp:"articleBody",children:(0,l.jsx)(P.A,{children:t})})}var T=s(1943),w=s(2053);function I(){return(0,l.jsx)("b",{children:(0,l.jsx)(d.A,{id:"theme.blog.post.readMore",description:"The label used in blog post item excerpts to link to full blog posts",children:"Read More"})})}function y(e){const{blogPostTitle:t,...s}=e;return(0,l.jsx)(o.A,{"aria-label":(0,d.T)({message:"Read more about {title}",id:"theme.blog.post.readMoreLabel",description:"The ARIA label for the link to full blog posts from excerpts"},{title:t}),...s,children:(0,l.jsx)(I,{})})}const F={blogPostFooterDetailsFull:"blogPostFooterDetailsFull_mRVl"};function L(){const{metadata:e,isBlogPostPage:t}=(0,n.e)(),{tags:s,title:r,editUrl:i,hasTruncateMarker:o}=e,c=!t&&o,m=s.length>0;return m||c||i?(0,l.jsxs)("footer",{className:(0,a.A)("row docusaurus-mt-lg",t&&F.blogPostFooterDetailsFull),children:[m&&(0,l.jsx)("div",{className:(0,a.A)("col",{"col--9":c}),children:(0,l.jsx)(w.A,{tags:s})}),t&&i&&(0,l.jsx)("div",{className:"col margin-top--sm",children:(0,l.jsx)(T.A,{editUrl:i})}),c&&(0,l.jsx)("div",{className:(0,a.A)("col text--right",{"col--3":m}),children:(0,l.jsx)(y,{blogPostTitle:r,to:e.permalink})})]}):null}function C(e){let{children:t,className:s}=e;const r=function(){const{isBlogPostPage:e}=(0,n.e)();return e?void 0:"margin-bottom--xl"}();return(0,l.jsxs)(i,{className:(0,a.A)(r,s),children:[(0,l.jsx)(N,{}),(0,l.jsx)(k,{children:t}),(0,l.jsx)(L,{})]})}},1943:(e,t,s)=>{s.d(t,{A:()=>m});s(6540);var a=s(1312),n=s(7559),r=s(8774),l=s(4164);const i={iconEdit:"iconEdit_Z9Sw"};var o=s(4848);function c(e){let{className:t,...s}=e;return(0,o.jsx)("svg",{fill:"currentColor",height:"20",width:"20",viewBox:"0 0 40 40",className:(0,l.A)(i.iconEdit,t),"aria-hidden":"true",...s,children:(0,o.jsx)("g",{children:(0,o.jsx)("path",{d:"m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"})})})}function m(e){let{editUrl:t}=e;return(0,o.jsxs)(r.A,{to:t,className:n.G.common.editThisPage,children:[(0,o.jsx)(c,{}),(0,o.jsx)(a.A,{id:"theme.common.editThisPage",description:"The link label to edit the current page",children:"Edit this page"})]})}},9022:(e,t,s)=>{s.d(t,{A:()=>l});s(6540);var a=s(4164),n=s(8774),r=s(4848);function l(e){const{permalink:t,title:s,subLabel:l,isNext:i}=e;return(0,r.jsxs)(n.A,{className:(0,a.A)("pagination-nav__link",i?"pagination-nav__link--next":"pagination-nav__link--prev"),to:t,children:[l&&(0,r.jsx)("div",{className:"pagination-nav__sublabel",children:l}),(0,r.jsx)("div",{className:"pagination-nav__label",children:s})]})}},6133:(e,t,s)=>{s.d(t,{A:()=>i});s(6540);var a=s(4164),n=s(8774);const r={tag:"tag_zVej",tagRegular:"tagRegular_sFm0",tagWithCount:"tagWithCount_h2kH"};var l=s(4848);function i(e){let{permalink:t,label:s,count:i}=e;return(0,l.jsxs)(n.A,{href:t,className:(0,a.A)(r.tag,i?r.tagWithCount:r.tagRegular),children:[s,i&&(0,l.jsx)("span",{children:i})]})}},2053:(e,t,s)=>{s.d(t,{A:()=>o});s(6540);var a=s(4164),n=s(1312),r=s(6133);const l={tags:"tags_jXut",tag:"tag_QGVx"};var i=s(4848);function o(e){let{tags:t}=e;return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)("b",{children:(0,i.jsx)(n.A,{id:"theme.tags.tagsListLabel",description:"The label alongside a tag list",children:"Tags:"})}),(0,i.jsx)("ul",{className:(0,a.A)(l.tags,"padding--none","margin-left--sm"),children:t.map((e=>{let{label:t,permalink:s}=e;return(0,i.jsx)("li",{className:l.tag,children:(0,i.jsx)(r.A,{label:t,permalink:s})},s)}))})]})}},7131:(e,t,s)=>{s.d(t,{e:()=>o,i:()=>i});var a=s(6540),n=s(9532),r=s(4848);const l=a.createContext(null);function i(e){let{children:t,content:s,isBlogPostPage:n=!1}=e;const i=function(e){let{content:t,isBlogPostPage:s}=e;return(0,a.useMemo)((()=>({metadata:t.metadata,frontMatter:t.frontMatter,assets:t.assets,toc:t.toc,isBlogPostPage:s})),[t,s])}({content:s,isBlogPostPage:n});return(0,r.jsx)(l.Provider,{value:i,children:t})}function o(){const e=(0,a.useContext)(l);if(null===e)throw new n.dV("BlogPostProvider");return e}},5846:(e,t,s)=>{s.d(t,{W:()=>c});var a=s(6540),n=s(4586);const r=["zero","one","two","few","many","other"];function l(e){return r.filter((t=>e.includes(t)))}const i={locale:"en",pluralForms:l(["one","other"]),select:e=>1===e?"one":"other"};function o(){const{i18n:{currentLocale:e}}=(0,n.A)();return(0,a.useMemo)((()=>{try{return function(e){const t=new Intl.PluralRules(e);return{locale:e,pluralForms:l(t.resolvedOptions().pluralCategories),select:e=>t.select(e)}}(e)}catch(t){return console.error(`Failed to use Intl.PluralRules for locale "${e}".\nDocusaurus will fallback to the default (English) implementation.\nError: ${t.message}\n`),i}}),[e])}function c(){const e=o();return{selectMessage:(t,s)=>function(e,t,s){const a=e.split("|");if(1===a.length)return a[0];a.length>s.pluralForms.length&&console.error(`For locale=${s.locale}, a maximum of ${s.pluralForms.length} plural forms are expected (${s.pluralForms.join(",")}), but the message contains ${a.length}: ${e}`);const n=s.select(t),r=s.pluralForms.indexOf(n);return a[Math.min(r,a.length-1)]}(s,t,e)}}}}]); \ No newline at end of file diff --git a/assets/js/809dac3e.133b40d5.js b/assets/js/809dac3e.133b40d5.js new file mode 100644 index 00000000..3504dd59 --- /dev/null +++ b/assets/js/809dac3e.133b40d5.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[804],{4e3:(t,e,n)=>{n.r(e),n.d(e,{assets:()=>i,contentTitle:()=>r,default:()=>u,frontMatter:()=>a,metadata:()=>m,toc:()=>s});var c=n(4848),o=n(8453);const a={slug:"new-documentation",title:"New documentation",authors:["ericr"],tags:["laminas","PHP","lmcrbacmvc"]},r=void 0,m={permalink:"/lmc-rbac-mvc/blog/new-documentation",editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2024-02-22-New-documentation.md",source:"@site/blog/2024-02-22-New-documentation.md",title:"New documentation",description:"This the new documentation site dedicated to the LmcRbacMvc module.",date:"2024-02-22T00:00:00.000Z",formattedDate:"February 22, 2024",tags:[{label:"laminas",permalink:"/lmc-rbac-mvc/blog/tags/laminas"},{label:"PHP",permalink:"/lmc-rbac-mvc/blog/tags/php"},{label:"lmcrbacmvc",permalink:"/lmc-rbac-mvc/blog/tags/lmcrbacmvc"}],readingTime:.11,hasTruncateMarker:!1,authors:[{name:"Eric Richer",title:"LM-Commons Administrator",url:"https://github.com/visto9259",imageURL:"https://github.com/visto9259.png",key:"ericr"}],frontMatter:{slug:"new-documentation",title:"New documentation",authors:["ericr"],tags:["laminas","PHP","lmcrbacmvc"]},unlisted:!1,nextItem:{title:"Welcome",permalink:"/lmc-rbac-mvc/blog/welcome"}},i={authorsImageUrls:[void 0]},s=[];function l(t){const e={p:"p",...(0,o.R)(),...t.components};return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(e.p,{children:"This the new documentation site dedicated to the LmcRbacMvc module."}),"\n",(0,c.jsx)(e.p,{children:"There are no changes to the code, just improvements in the documentation."})]})}function u(t={}){const{wrapper:e}={...(0,o.R)(),...t.components};return e?(0,c.jsx)(e,{...t,children:(0,c.jsx)(l,{...t})}):l(t)}},8453:(t,e,n)=>{n.d(e,{R:()=>r,x:()=>m});var c=n(6540);const o={},a=c.createContext(o);function r(t){const e=c.useContext(a);return c.useMemo((function(){return"function"==typeof t?t(e):{...e,...t}}),[e,t])}function m(t){let e;return e=t.disableParentContext?"function"==typeof t.components?t.components(o):t.components||o:r(t.components),c.createElement(a.Provider,{value:e},t.children)}}}]); \ No newline at end of file diff --git a/assets/js/814f3328.d5988fa5.js b/assets/js/814f3328.d5988fa5.js new file mode 100644 index 00000000..6eed08f6 --- /dev/null +++ b/assets/js/814f3328.d5988fa5.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[472],{5513:e=>{e.exports=JSON.parse('{"title":"Recent posts","items":[{"title":"New documentation","permalink":"/lmc-rbac-mvc/blog/new-documentation","unlisted":false},{"title":"Welcome","permalink":"/lmc-rbac-mvc/blog/welcome","unlisted":false}]}')}}]); \ No newline at end of file diff --git a/assets/js/8b5d4ccc.1ace6756.js b/assets/js/8b5d4ccc.1ace6756.js new file mode 100644 index 00000000..19d23aef --- /dev/null +++ b/assets/js/8b5d4ccc.1ace6756.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[604],{82:(e,n,r)=>{r.r(n),r.d(n,{assets:()=>i,contentTitle:()=>a,default:()=>d,frontMatter:()=>t,metadata:()=>l,toc:()=>A});var s=r(4848),o=r(8453);const t={sidebar_position:5},a="Guards",l={id:"guards",title:"Guards",description:"In this section, you will learn:",source:"@site/docs/guards.md",sourceDirName:".",slug:"/guards",permalink:"/lmc-rbac-mvc/docs/guards",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/guards.md",tags:[],version:"current",sidebarPosition:5,frontMatter:{sidebar_position:5},sidebar:"tutorialSidebar",previous:{title:"Role providers",permalink:"/lmc-rbac-mvc/docs/role-providers"},next:{title:"Strategies",permalink:"/lmc-rbac-mvc/docs/strategies"}},i={},A=[{value:"What are guards and when should you use them?",id:"what-are-guards-and-when-should-you-use-them",level:2},{value:"Protection policy",id:"protection-policy",level:3},{value:"Built-in guards",id:"built-in-guards",level:2},{value:"RouteGuard",id:"routeguard",level:3},{value:"RoutePermissionsGuard",id:"routepermissionsguard",level:3},{value:"ControllerGuard",id:"controllerguard",level:3},{value:"ControllerPermissionsGuard",id:"controllerpermissionsguard",level:3},{value:"Security notice",id:"security-notice",level:3},{value:"Creating custom guards",id:"creating-custom-guards",level:2}];function c(e){const n={blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",img:"img",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.R)(),...e.components};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(n.h1,{id:"guards",children:"Guards"}),"\n",(0,s.jsx)(n.p,{children:"In this section, you will learn:"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"What guards are"}),"\n",(0,s.jsx)(n.li,{children:"How to use and configure built-in guards"}),"\n",(0,s.jsx)(n.li,{children:"How to create custom guards"}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"what-are-guards-and-when-should-you-use-them",children:"What are guards and when should you use them?"}),"\n",(0,s.jsx)(n.p,{children:"Guards are listeners that are registered on a specific event of\nthe MVC workflow. They allow your application to quickly mark a request as unauthorized."}),"\n",(0,s.jsx)(n.p,{children:"Here is a simple workflow without guards:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Laminas Framework workflow without guards",src:r(4676).A+"",width:"920",height:"200"})}),"\n",(0,s.jsx)(n.p,{children:"And here is a simple workflow with a route guard:"}),"\n",(0,s.jsx)(n.p,{children:(0,s.jsx)(n.img,{alt:"Laminas Framework workflow with guards",src:r(7376).A+"",width:"1120",height:"1600"})}),"\n",(0,s.jsx)(n.p,{children:'RouteGuard and ControllerGuard are not aware of permissions but rather only think about "roles". For\ninstance, you may want to refuse access to each routes that begin by "admin/*" to all users that do not have the\n"admin" role.'}),"\n",(0,s.jsx)(n.p,{children:'If you want to protect a route for a set of permissions, you must use RoutePermissionsGuard. For instance,\nyou may want to grant access to a route "post/delete" only to roles having the "delete" permission.\nNote that in a RBAC system, a permission is linked to a role, not to a user.'}),"\n",(0,s.jsx)(n.p,{children:"Albeit simple to use, guards should not be the only protection in your application, and you should always\nprotect your services as well. The reason is that your business logic should be handled by your service. Protecting a given\nroute or controller does not mean that the service cannot be access from elsewhere (another action for instance)."}),"\n",(0,s.jsx)(n.h3,{id:"protection-policy",children:"Protection policy"}),"\n",(0,s.jsx)(n.p,{children:'By default, when a guard is added, it will perform a check only on the specified guard rules. Any route or controller\nthat is not specified in the rules will be "granted" by default. Therefore, the default is a "blacklist"\nmechanism.'}),"\n",(0,s.jsx)(n.p,{children:'However, you may want a more restrictive approach (also called "whitelist"). In this mode, once a guard is added,\nanything that is not explicitly added will be refused by default.'}),"\n",(0,s.jsx)(n.p,{children:'For instance, let\'s say you have two routes: "index" and "login". If you specify a route guard rule to allow "index"\nroute to "member" role, your "login" route will become defacto unauthorized to anyone, unless you add a new rule for\nallowing the route "login" to "member" role.'}),"\n",(0,s.jsx)(n.p,{children:"You can change it in LmcRbacMvc config, as follows:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"use LmcRbacMvc\\Guard\\GuardInterface;\n\nreturn [\n 'lmc_rbac' => [\n 'protection_policy' => GuardInterface::POLICY_DENY\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"NOTE: this policy will block ANY route/controller (so it will also block any console routes or controllers). The\ndeny policy is much more secure, but it needs much more configuration to work with."}),"\n"]}),"\n",(0,s.jsx)(n.h2,{id:"built-in-guards",children:"Built-in guards"}),"\n",(0,s.jsx)(n.p,{children:"LmcRbacMvc comes with four guards, in order of priority :"}),"\n",(0,s.jsxs)(n.ul,{children:["\n",(0,s.jsx)(n.li,{children:"RouteGuard : protect a set of routes based on the identity roles"}),"\n",(0,s.jsx)(n.li,{children:"RoutePermissionsGuard : protect a set of routes based on roles permissions"}),"\n",(0,s.jsx)(n.li,{children:"ControllerGuard : protect a controllers and/or actions based on the identity roles"}),"\n",(0,s.jsx)(n.li,{children:"ControllerPermissionsGuard : protect a controllers and/or actions based on roles permissions"}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["All guards must be added in the ",(0,s.jsx)(n.code,{children:"guards"})," subkey:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n // Guards config here!\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:"Because of the way Laminas Framework handles config, you can without problem define some rules in one module, and\nmore rules in another module. All the rules will be automatically merged."}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:'For your mental health, I recommend you to use either the route guard OR the controller guard, but not both. If\nyou decide to use both conjointly, I recommend you to set the protection policy to "allow" (otherwise, you will\nneed to define rules for every routes AND every controller, which can become quite frustrating!).'}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["Please note that if your application uses both route and controller guards, route guards are always executed\n",(0,s.jsx)(n.strong,{children:"before"})," controller guards (they have a higher priority)."]}),"\n",(0,s.jsx)(n.h3,{id:"routeguard",children:"RouteGuard"}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["The RouteGuard listens to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event with a priority of -5."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'The RouteGuard allows your application to protect a route or a hierarchy of routes. You must provide an array of "key" => "value",\nwhere the key is a route pattern and the value is an array of role names:'}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'admin*' => ['admin'],\n 'login' => ['guest']\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"Only one role in a rule needs to be matched (it is an OR condition)."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'Those rules grant access to all admin routes to users that have the "admin" role, and grant access to the "login"\nroute to users that have the "guest" role (eg.: most likely unauthenticated users).'}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"The route pattern is not a regex. It only supports the wildcard (*) character, that replaces any segment."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"You can also use the wildcard character * for roles:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'home' => ['*']\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:'This rule grants access to the "home" route to anyone.'}),"\n",(0,s.jsx)(n.p,{children:"Finally, you can also omit the roles array to completely block a route, for maintenance purpose for example :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'route_under_construction'\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:"This rule will be inaccessible."}),"\n",(0,s.jsx)(n.p,{children:"Note : this last example could be (and should be) written in a more explicit way :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'route_under_construction' => []\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.h3,{id:"routepermissionsguard",children:"RoutePermissionsGuard"}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["The RoutePermissionsGuard listens to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event with a priority of -8."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'The RoutePermissionsGuard allows your application to protect a route or a hierarchy of routes. You must provide an array of "key" => "value",\nwhere the key is a route pattern and the value is an array of permission names:'}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RoutePermissionsGuard' => [\n 'admin*' => ['admin'],\n 'post/manage' => ['post.update', 'post.delete']\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"By default, all permissions in a rule must be matched (an AND condition)."}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["In the previous example, one must have ",(0,s.jsx)(n.code,{children:"post.update"})," ",(0,s.jsx)(n.strong,{children:"AND"})," ",(0,s.jsx)(n.code,{children:"post.delete"})," permissions\nto access the ",(0,s.jsx)(n.code,{children:"post/manage"})," route. You can also specify an OR condition like so:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"use LmcRbacMvc\\Guard\\GuardInterface;\n\nreturn [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RoutePermissionsGuard' => [\n 'post/manage' => [\n 'permissions' => ['post.update', 'post.delete'],\n 'condition' => GuardInterface::CONDITION_OR\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"Permissions are linked to roles, not to users"}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'Those rules grant access to all admin routes to roles that have the "admin" permission, and grant access to the\n"post/delete" route to roles that have the "post.delete" or "admin" permissions.'}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"The route pattern is not a regex. It only supports the wildcard (*) character, that replaces any segment."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"You can also use the wildcard character * for permissions:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RoutePermissionsGuard' => [\n 'home' => ['*']\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:'This rule grants access to the "home" route to anyone.'}),"\n",(0,s.jsx)(n.p,{children:"Finally, you can also use an empty array to completly block a route, for maintenance purpose for example :"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RoutePermissionsGuard' => [\n 'route_under_construction' => []\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:"This route will be inaccessible."}),"\n",(0,s.jsx)(n.h3,{id:"controllerguard",children:"ControllerGuard"}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["The ControllerGuard listens to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event with a priority of -10."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The ControllerGuard allows your application to protect a controller. You must provide an array of arrays:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\ControllerGuard' => [\n [\n 'controller' => 'MyController',\n 'roles' => ['guest', 'member']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"Only one role in a rule need to be matched (it is an OR condition)."}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:'Those rules grant access to each actions of the MyController controller to users that have either the "guest" or\n"member" roles.'}),"\n",(0,s.jsx)(n.p,{children:"As for RouteGuard, you can use a wildcard (*) character for roles."}),"\n",(0,s.jsx)(n.p,{children:"You can also specify optional actions, so that the rule only apply to one or several actions:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\ControllerGuard' => [\n [\n 'controller' => 'MyController',\n 'actions' => ['read', 'edit'],\n 'roles' => ['guest', 'member']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:"You can combine a generic rule and a specific action rule for the same controller, as follows:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\ControllerGuard' => [\n [\n 'controller' => 'PostController',\n 'roles' => ['member']\n ],\n [\n 'controller' => 'PostController',\n 'actions' => ['delete'],\n 'roles' => ['admin']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsx)(n.p,{children:'These rules grant access to each controller action to users that have the "member" role, but restrict the\n"delete" action to "admin" only.'}),"\n",(0,s.jsx)(n.h3,{id:"controllerpermissionsguard",children:"ControllerPermissionsGuard"}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["The ControllerPermissionsGuard listens to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event with a priority of -13."]}),"\n"]}),"\n",(0,s.jsx)(n.p,{children:"The ControllerPermissionsGuard allows your application to protect a controller using permissions. You must provide an array of arrays:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\ControllerPermissionsGuard' => [\n [\n 'controller' => 'MyController',\n 'permissions' => ['post.update', 'post.delete']\n ]\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsx)(n.p,{children:"All permissions in a rule must be matched (it is an AND condition)."}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["In the previous example, the user must have ",(0,s.jsx)(n.code,{children:"post.update"})," ",(0,s.jsx)(n.strong,{children:"AND"})," ",(0,s.jsx)(n.code,{children:"post.delete"})," permissions\nto access each action of the MyController controller."]}),"\n",(0,s.jsx)(n.p,{children:"As for all other guards, you can use a wildcard (*) character for permissions."}),"\n",(0,s.jsx)(n.p,{children:"The configuration rules are the same as for ControllerGuard."}),"\n",(0,s.jsx)(n.h3,{id:"security-notice",children:"Security notice"}),"\n",(0,s.jsxs)(n.p,{children:["RouteGuard and ControllerGuard listen to the ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," event. Therefore, if you use the\n",(0,s.jsx)(n.code,{children:"forward"})," method in your controller, those guards will not intercept and check requests (because internally\nLaminas MVC does not trigger again a new MVC loop)."]}),"\n",(0,s.jsx)(n.p,{children:"Most of the time, this is not an issue, but you must be aware of it, and this is an additional reason why you\nshould always protect your services too."}),"\n",(0,s.jsx)(n.h2,{id:"creating-custom-guards",children:"Creating custom guards"}),"\n",(0,s.jsx)(n.p,{children:"LmcRbacMvc is flexible enough to allow you to create custom guards. Let's say we want to create a guard that will\nrefuse access based on an IP addresses blacklist."}),"\n",(0,s.jsx)(n.p,{children:"First create the guard:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"namespace Application\\Guard;\n\nuse Laminas\\Http\\Request as HttpRequest;\nuse Laminas\\Mvc\\MvcEvent;\nuse LmcRbacMvc\\Guard\\AbstractGuard;\n\nclass IpGuard extends AbstractGuard\n{\n const EVENT_PRIORITY = 100;\n\n /**\n * List of IPs to blacklist\n */\n protected $ipAddresses = [];\n\n /**\n * @param array $ipAddresses\n */\n public function __construct(array $ipAddresses)\n {\n $this->ipAddresses = $ipAddresses;\n }\n\n /**\n * @param MvcEvent $event\n * @return bool\n */\n public function isGranted(MvcEvent $event)\n {\n $request = $event->getRequest();\n\n if (!$request instanceof HttpRequest) {\n return true;\n }\n\n $clientIp = $_SERVER['REMOTE_ADDR'];\n\n return !in_array($clientIp, $this->ipAddresses);\n }\n}\n"})}),"\n",(0,s.jsxs)(n.blockquote,{children:["\n",(0,s.jsxs)(n.p,{children:["Guards must implement ",(0,s.jsx)(n.code,{children:"LmcRbacMvc\\Guard\\GuardInterface"}),"."]}),"\n"]}),"\n",(0,s.jsxs)(n.p,{children:["By default, guards are listening to the event ",(0,s.jsx)(n.code,{children:"MvcEvent::EVENT_ROUTE"})," with a priority of -5 (you can change the default\nevent to listen by overriding the ",(0,s.jsx)(n.code,{children:"EVENT_NAME"})," constant in your guard subclass). However, in this case, we don't\neven need to wait for the route to be matched, so we overload the ",(0,s.jsx)(n.code,{children:"EVENT_PRIORITY"})," constant to be executed earlier."]}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"isGranted"})," method simply retrieves the client IP address, and checks it against the blacklist."]}),"\n",(0,s.jsx)(n.p,{children:"However, for this to work, we must register the newly created guard with the guard plugin manager. To do so, add the\nfollowing code in your config:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'zfc_rbac' => [\n 'guard_manager' => [\n 'factories' => [\n 'Application\\Guard\\IpGuard' => 'Application\\Factory\\IpGuardFactory'\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The ",(0,s.jsx)(n.code,{children:"guard_manager"})," config follows a conventional service manager configuration format."]}),"\n",(0,s.jsx)(n.p,{children:"Now, let's create the factory:"}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"namespace Application\\Factory;\n\nuse Application\\Guard\\IpGuard;\nuse Laminas\\ServiceManager\\Factory\\FactoryInterface;\nuse Laminas\\ServiceManager\\ServiceLocatorInterface;\n\nclass IpGuardFactory implements FactoryInterface\n{\n /**\n * {@inheritDoc}\n */\n public function __invoke(ContainerInterface $container, $requestedName, array $options = null)\n {\n if (null === $options) {\n $options = [];\n }\n return new IpGuard($options);\n }\n}\n"})}),"\n",(0,s.jsx)(n.p,{children:"In a real use case, you would likely fetched the blacklist from a database."}),"\n",(0,s.jsxs)(n.p,{children:["Now we just need to add the guard to the ",(0,s.jsx)(n.code,{children:"guards"})," option, so that LmcRbacMvc can execute the logic behind this guard. In\nyour config, add the following code:"]}),"\n",(0,s.jsx)(n.pre,{children:(0,s.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'Application\\Guard\\IpGuard' => [\n '87.45.66.46',\n '65.87.35.43'\n ]\n ]\n ]\n];\n"})}),"\n",(0,s.jsxs)(n.p,{children:["The array of IP addresses will be passed to ",(0,s.jsx)(n.code,{children:"IpGuardFactory::__invoke"})," in the ",(0,s.jsx)(n.code,{children:"$options"})," parameter."]})]})}function d(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,s.jsx)(n,{...e,children:(0,s.jsx)(c,{...e})}):c(e)}},7376:(e,n,r)=>{r.d(n,{A:()=>s});const s=r.p+"assets/images/workflow-with-guards-9421d2ccbded4218039d3c55d9e07d64.png"},4676:(e,n,r)=>{r.d(n,{A:()=>s});const s="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA5gAAADICAYAAACNrk7XAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAABIAAAASABGyWs+AAAiB0lEQVR42u3df2hb98Hv8U/3qHCpeiFrp/SMJosaPCgpPt3DNgJxSm+pusuSQDo6MuSEQskKLWvd2+2PsY4nRb2so3+UkfjpNuhKYVQ2KylNIMrWJ+6TOyqHhSbcWaZueXAjjwysWnRLQdo/0u65f8hHleUjW5aO9D1Her/ggGUfSV/Z0sff7/n+kgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIBBE1k9ACDoyCsAQ+sm0wUAMHTikv67pNHV23slxVa/vkeS3eHj5iR9sPr1R5IWV7++JKksqWj6hQMInbjIKwDYEhqYAHohImmHpH2SRiTdLSlpulCSCpIuqlaJuyZpQdKS6UIBMIq8AgAf0cAE4IeYpG9K+p+S/ofauKqfSCQUi8UUi8W0d+9eSdK2bdu0Z8+ejgpw6dKl+teXL19WsVjUBx98oFwu187dZySdlfR/Jf1ZtR4EAIOJvAKAHqKBCaATbgXtmDa40m/btu655x4dOnRIu3bt0p133qkdO3YoEunv1KRyuaxisaiFhQVdu3ZNly5d0sWLF1UoFFrdJSfpLUnvSrosqdrXAgPwE3kFAH1EAxNAu0YlfUfSI/K44m9Zlo4cOaK9e/dq3759RipmW1Uul3Xt2jXNz8/r3Llzmp6ebnXqjKRfSPqj6C0AwoC8Iq8AAEAAjUo6IWlZktN4WJblpFIpJ5vNOqVSyRkU+XzeSafTTjKZdJpf8+pxQdIBSVHTfxwAa5BX5BUAAAigqKSn5FFJSyQSTjqddlZWVkzXq/qiUqk42WzWSaVSjmVZXpW3KUljYjsCwBTyirwCAAABNaZaBWRdJS2TyQzUVf9O5XK5VpW3ZdV6TuglAPqDvCKvAABAAEUkjUuaU9NwssnJyaG58t+JbDbrTExMtOoliJv+wwIDiLzqEHkFAAB6LapaRW3NsLJkMulks1nTdaFQKZVKTjqd9uoluKBaLwuA7pBXPiGvAACA39wegDWVi1QqxdV/H2SzWce27eaK25youAGdIK96iLwCAADdcCtq9R4Ad1gZc5X8l8/nvVZ1ZCga0B7yqo/IKwAAsFVjapqzlE6nnUqlYrpeM/BaVNxOisU1gFbIK0PIKwAAsJmYmlZZTKVS9AAYkMvlvIaijYvtAgAXeRUQ5BUAAPCyZt5SMplkzlIAZDKZ5sU15sQwNIC8CiDyCgAASLVegAtqmLfEKovBUqlUnFQq1dw78JToHcDwIa8CjrwCAGC4HVBDJWBiYoJ5SwGWz+ebh6HNqVbhBoYBeRUi5BUAAMMlooa5S5ZlOblcznR9BG2oVCrO5ORkc+/AAdNvKKCHyKuQIq8AABgOMTWsuEgvQDjl8/nmuU4nxRA0DB7yagCQVwAADK4xNVxJzmQypusd6EKlUmneIuCCGIKGwUFeDRDyCgCAwVNfddGyLCefz5uub8An6XS6sdK2LCptCD/yakCRVwAADIYTWv2HnkgkGGI2gLLZbPMQtDHTbzqgQ+TVgCOvAAAIt3plLZlMUlkbYCsrK1TaEHbk1ZAgrwAACJ81Ky+mUinT9Qn0QalUchKJBJU2hA15NYTIKwAAwoXK2pCqVCpU2hA25NWQIq8AAAiH+jAzKmvDiUobQoS8GnLkFQAAwUZlDY7jUGlDKJBXcByHvAIAIKgOiMoaGnhU2tgSAEFBXmEN8goYXjeZLgAAT6OScpKUSCR04cIF0+VBQJTLZY2MjKhQKEhSQdKIpLLpcmGokVfwRF4Bw+kLpgsAYJ2opP+QapW13//+96bLgwCJRqPK5XKyLEuSLElnVFu1EzCBvEJL5BUwnGhgAsFzRpJlWZampqYUifC/GGvFYjGdPn3avZmQ9JzpMmFonRF5hQ2QV8Dw+RfTBQCwxlOrh/7whz/o7rvvNl0eBNRXvvIVfelLX3J7jB6QNCPpuulyYaiQV2gLeQUAgBlxsUgGtqhhEY1l1YYrAv0QF3mFLSKvgOHAIj9AMEQkXZVk27atq1evMtQMbWlaRGNa0rjpMmHgkVfoCHkFDAfmYALBcESSLUlnz56lsoa2RaNRvfbaa+7NpNhvDr1HXqEj5BUwHGhgAuZFJaUlaXJyUvF43HR5EDIHDhxQMpl0b54WqzSid8grdIW8AgYfQ2QB86YkJS3L0vXr1+kNQEeKxaK2b9/u3nxa0r+bLhMGEnmFrpFXwGCjBxMwK67aMCGdPn2ayho6FovFlE6n3ZuTYgEN+C8u8go+IK+AwUYPJmDWlKSkbduam5szXRaEXLVa1c6dO90FNJ6X9ILpMmGgkFfwDXkFDK5h68GMiLH+CI64VnsDfvnLX5ouCwZAJBLRyy+/7N5MiV4B+Ccu8go+Iq8wRIau/TGoPZgRSXslPSjpbtU29bWazilIuijpI0nvSrosqWq64Bgq9AbAd/QKoEfIK/iOvMKAof0xoMYkndTq5s8dHCfFktnoj6hW33fZbNb03tcYMOl0ujHXhuqqKXqCvELPkFcYALQ/mgxKD2Zc0quSEj493oykxyUtmX5hGFgnJKUsy9Ly8rLpsmDANPUKHFWt9wnoFHmFniGvEGJxebQ/EomEHnvsMW3btk179uxZc4eFhQXduHFDr7/+umZmZpofj/ZHQERUC6JOrxhsdkyJq2nojWVJzuTkpOmLxxhQqVTKzTHGM6Jb5BV6irxCyKxrfySTSSebzTqVSqXt932lUnGy2ayTTCYHrv0R5h7MmKSc1o9t9ltBki2paPoFY2CMqvbeValUUjTKugbwX9M+c3eJK6LoDHmFniOvECJr2h+2beuNN97Q6OhoVw86Pz+vY8eOKZfLud8KdfsjrKvIjkmaV+8bl1p9jnkN2NhoGPV9qTaEgsoaeiUWi8m2bffmo6bLg9Air9Bz5BVCYkwNjctMJqO5ubmuG5eSNDo6qrm5OWUyGfdb1upz0f7okzH1bkjsZgd/ZPhhWZKTyWRMj0rCgGtYPIOJc+gUeYW+IK8QcPX2h2VZTj6f79lnIZ/PO5Zlhbr9EbYhsjHVehPvMPT8n6g2XCiU3dUIBIaboW+ahp1tF9mFrSGv0DfkFQKsPiw2kUjozJkzW8rDarW2C0kk0v60ynK5rIcffthdCCh0w2XDNEQ2otof11TjUqvPnVPIJ97CqPul2ph9KmvotaZhZw+ZLg9Ch7xC35BXCCi3/WFZlrVp47JcLmt+fl5TU1MaHx/Xvffeq5tvvlk333yz7r33Xj3zzDOamprS0tLShk8ajUZ15swZWZYlfT5clvZHD/RytdhOVpcFOjElVmNEHzWszkhuYavIK/QVeYUAqrc/NhsWm8lkmoe2bngkEglnZWVlw8fM5/O0P3ooLvONyuYjbvqXglBis3L0VTabbcwtYCvIK/QVeYWAiWv1/bjRPPRSqeS11UjbRzqd3vBzkclkQtf+CMsczAtq2sQ0AGbEEA5sTVxSXgr3fKalpSVdunTJ82e7du3S3r17N5xn4N5/cXFRkjQyMqJ9+/YpHo+vO3d2dlZ/+ctfdOTIEc/HLJfLOnv2rHbt2qWxsbH6+ZvZyvnuuWFVLpd16623ujdZ/h/tiqtHeVUul/XnP/9Z7777rm677Tbdf//92r1794bP0UlujI6Oeq7u2MvcaPWcrmq1qg8//FDz8/O6fPmyvvrVr2r37t365je/qVgs1vK1jI+Pb1i2rZ7Xzuvc7PzNXmsnyCsEzAVJCdu2NTfnvUXr7OysHnnkEX3yySddPdFmczvvvfdedwsT2h8+MblqLKvKwk9jWl19LMwaVvrzPCzLclKp1Lr7raysOIlEouX9ksmkUyqV1tzHvSLYijt0JJlMrjl/s2Mr57vnhpnILGxdT/KqYQhkW5+1bnJDkufws17nhm3bnr0d+XzesW275f0mJibWbdK+WQZ2el47r7Ob19oNkVcIhnr7I5fLeb5XS6WSc8cdd/jWrtiovpHL5fhs+OykzDckWx0nTf9yECrjGoAGi9vAbJ6LUCqV1sw/aKzYlUql+vcty3ImJyedUqnkrKysOKlUas3PGitYW21grqysOPl8vn64ldlUKrXm+27Z3MfPZDJrfu51bpg1VBLHO3zvYvj4nlfu59G9CFUqlZxSqeRks9l6I7Lx4lS3udGq/H7lhlcGTk5O1p+7sUyN86hs23bS6bRTKpXWPJ9Um5Pl9dlt9zPe7nmt8q4581o97srKSv21Nv/+u0VeISBOapMM7GZYbKtjo+GyDc9H+6NLEZlvRG52sKLTcBlT53/zgVgwo1UD0+XOFWisKE5MTNSv0HupVCr1Cmbj72erDcxWZW0V2K0qioOGhTOGVmDyyv0sJpNJz8aI25i0bbveI9ltbrj3b+5h63VuuJ+3xuf1akA3vxa3d7Pxfr1qYLZrs/Pd1+rnPF3yCj7pJv/q7Y9W7+2meZG+Hq2ypWmOcqDbH0HfpmSv6QIMSBnhn6ykimpXjzoKr9tuu830a+ipnTt3SpI+/fTT+vdOnTolSTpy5IjnfSKRiJ599llJ0s9+9jPTL2HgjIyMmC4CzAhMXp07d06SdOzYMc/51NFoVNevX9fc3Fx9DlK3ufHiiy/KsiwdP368vg9dP3zjG9+QJF27dk1SbX/H1b3s9OSTT7Z8LY888ogk6Y033uhbWbvlvj8+++wz3x6TvIJPusm/et1+79711fxyuazjx4/3rOCHDx/2LtTasgS6/RH0BuaDpgvQhmdNFwBGTGjr4fW922+/faD/eVarVf385z+X9HmlsFj8fF9gr6B23X///ZKkQqHQ93IvLCxoaWnJ8+hnxbRXdu3apdtvv12SvmO6LDDCeF5NT09L+vxz7qWx4elHbkSjUb322msqFAr60Y9+5Ofvs6Vyuayf/OQntV/g975X/55U20/UayEf13e+U/t4Xrx4seflbJV3W8m8crmsV199VdLGf9etIq/gs07y73/fcssteuKJJzwviF27dq2ndZVcLue5T2YkElEiUV/zNNBtpEB3r0q623QB2vAN0wWAcROrhySdkvSmpMuSmv9Lf+HTTz/V9u3bTZfXF7/97W81MjKiv/3tb/VVZd1KpG3b9UqhW7lKJpMbri4bjUZl23Y9WL1Wh+yVgwcPtvxZPp/va1l64c4773R7lP+b6bLAuL7nldtYTCQSba9G61duHDhwQIlEQqdOndKzzz6reDzu2+fZzcDFxUV99NFHkj7PwEQiUW9Muvno9lC24q7I2o+LbHfddVfLn3ll3tRUbbSq12udmJjwdZVh8go91G7+7f7HP/6h++67z/NB5ufne17QhYUFz6x67LHH3BERgW4jBb2B+YDpArRhp+kCIFDaDa/Qe/755z2/n81mPbf1+OCDDzZ9zJWVFUna8Cp/L6RSqZY9Nf0uC9BHfcmrW265RZLqw0S3wo/cmJqa0vbt23X48GFdvXp1wwbrVnhloGVZOn369JoM3LVrl6S10wa8uI3qfkin0y1/5vV7PHr06Lrv2bats2fPhv4CHIbWRvm3U5K2bdvmeUd3yH8vXblyRQcOHFj3/YYyBbqNFPQGpmW6AG0I+jBjmNMcXgOl+Sr3+Pi4pqen183F2bFjh6TakI9isdiyEri0tFS/ct98NXx+ft5zv7WFhQVJ3TcCH330USpJGHY9y6vGz3Orz3IzP3LDFYvFNDk5qaefflpvvvnmpntFtqsxA6vVqr7+9a8rl8vpzjvvXHOee/vUqVN6+eWXWzZw//jHP0pS4xC4ntnq78BxnPrX1WpVO3fudPfkAwZBc/59QZL27NnjeXI7F7669dZbb+nEiRPrvt9QpkC3kYLcOAp647eR6ZVsOfp3dGqii/uGwsmTtVWzjx8/vuZKfCQSkWXVcvD9999veX/3imBj5erYsWOSpLffftvzPr/4xS8kbTxHC+uY/gxxDGFeTUzUHrLVZ1mq9TSOj4+rWCx2lRtennjiCdm2raNHj66Z3+mXSCRSX5zn8OHDa+Yxuo1lSfrwww9bPsY777wjqXWlNigikYhOnz4tSXr88cd7/XSmP0Mc4T06tWH+VavVvlxcyeVy7cyHDmxbKcgNTAAhEovFlE6nVSgU1lU63MrI8ePHNTs7uy40i8VifRVId9EISfrWt74lqXYlr7lSuLS0VB9yt2/fPtMvH8AG3NVef/WrX+n8+fPrMmB+fl5Hjx7VxYsX60NqO80NL40NwGeeeaYnr3F0dFQTExPK5XL69a9/vea53SGpP/zhDzU7O7vuvvPz8/VVc1988cWelM9PY2NjSiaTmpmZqc/PBABXYFu+CtectZtMFwB908lVsZyktySlTBe+144cOaKXXnpJ09PT+sEPflCfh+RWRqanp7V//35ZlqWf/vSnuu2227S4uFify5RKpdYMVY1EIkqlUnr++ee1fft2JZNJHTp0SC+99FL9CuLExETXw1vdxTpaOXz4sK+LWBhGXg2PwORVPB6vf5YPHjwoy7L05JNPamRkRK+//nr9YlEul6t/1jrNjVbcBqDbkOuFF198UW+++aaefvppHTp0qF4uNxtnZmY0MzMj27b14x//WJJ0+fLlepkymYxn1rRqxDVnU7fntTrfy6uvvqqLFy/q6NGjeuihh3o1X528Qqd6kn+RSKS+sFgvJRKJduaLh6mtFCjLMt/FvtnxT9O/JPRVO++JZX2+FHa0+b6tNtANC3cT8lavw93E3LKsdRuqZzIZx7Ksdb8zy7KcXC7n+XilUsnJZDL1jcobj3Q67blpe3NZN9swfbMj7H8z92+i7oYNIXwCl1fZbHZdBliW5aRSqZYZsNXccD/XXkqlUv2xksmk5znt5kar3427Abtt22vyqVKpOJOTk55/B9u2PR9vs4xy7+PXea3Ob8X9XSUSCd/eI+QVfNJN/v1TkpPJZDzfoxMTEz1vX6RSqQ3zZbXsgRX0K0NTkpKmC7GJv0iKmy4E+qbVP7xpSW9Iel9ScaP7hn3bi3K5rGKxqB07drS8ulYsFlUul1ueU61W9de//lWSNnycVs8tqa3foXt+LBbzvBrvlnMzWyljEC0tLTVuSxD03Id/AptXbgZEo9G2e77azQ33c92q3O7PWz13u7mxURncPexandNulm2WUe7j+3Veq/M3KqP7Wv16n5BX8Ek3+bckaVc6nfZcEGtqaspzZWU/tVqRv+G5pyX5s2LZEDoh8z2Umx3/afqXhL5y/+4XJD2lrV1c+Kck509/+pNvV3qBdmQyGeeLX/wiIy6GD3mF0CGv4JNu8u8/JTnf//73Pd+jTb3sPTlKpZLnczeM5jrR5msxIuiL/LxrugBt+DfTBUBf2ZJulvSQpH9X7SpXu34nSR9//LHp14Ahc+PGDf3973+XVt+DGBrkFUKHvIJPusm/f5Ok3/zmN54rucbj8Z5uJ5RKpTxHT1Sr1cb9hAPdRgp6A/Oy6QIMSBnhn3l1Oal6cXHR9GvAkGl4z/m/PwOCjLxC6JBX8Ek3+Vev21++7F3N79XqybZt67nnnvMu1NqyBLr9EfQGZlXB3qD+lFjBCe07J0kfffSR6XJgyLz33nvul4H+h4RAIa9gBHmFAKi3P1555RXPE9yt2fx29uzZlnO7G8oS+PZHGCZPj0nKmi5EC/slzXb9KBgW9fey47A4HvrnppvqUW+rdlUX2Ax5BSPIKwREPQNzuZxGR0c9T3rooYcah612pdWiQlJtr1zbtt2bgW9/BL0HU6r9Av35y/lrRgH/4yJw/sv9wl09EOi1pvdawXR5EBrkFfqOvEKA1Nsfx44da3nSmTNnlEx2v+HFRo3LpjKEov0RhgamJD1uugAhKROCrajVf5jvv/++6bJgSDS81wpiThPaR16h78grBMzjUq0H8/z5854nRKNRTU1NdTxc1rZt5fP5DRuX58+fVy6XW1OmoAtLA3NJtf1egmJaW1uNCnBdlKQrV66YLgeGxDvvvON++abpsiB0yCv0FXmFgFnSavvj4MGD9T1fvYyPjyufzzcOY91UKpXS1atXN91n9uDBg+7N0LQ/wjAH0xWRdF2SZbgcBUk7FfDJtQisA5IylmVpeXnZdFkwBL785S+rUChI0kFJ57t8OAwX8gp9RV4hgOrtD8uytLi46LmFSKOlpSUtLCzoypUreuutt+q9j4lEQvfdd58efPBBfe1rX9v0ccrlskZGRtzPRKjaH2FqYEpSTLUJ33cYev5PJI2KYRvoXEzSiiStrKwoFouZLg8GWNOiALdKKpsuE0KFvELfkFcIsJiknCQrkUjozJkzmzYOG7l7abZaHdZLuVzWww8/7C4gVFBt0avQtD/CMkTWVZT0iMHnf0Qh+uMikIqqhZR+9zv2kEZvvf322+6XM6Kyhq0jr9A35BUCrCjpu5I0MzOjkZGRDYfLNotEIltqXC4tLWlkZKRxddrvivZHX4yp1pp3+nQUVp8T8MNTkhzbth2gVyqVimNZlpth412+ZzG8yCv0HHmFkBiTtKzV9kEmk/H9s5DJZBrbH8ui/dF3MTX8kXt4LK8+F+CXmFbfX7lczvT/dQyobDbbmGPtj+UB1iKv0HPkFUJkTfvDtm1fsjGXyzm2bQ9M+yNsQ2QbFVWb7NrL1WWnV5+Dbmn4qajVvZUahgQBvnrllVfcL6fFcDN0jrxCz5FXCJE17Y9cLifbtjU+Pq7Z2dn6fMt2VKtVzc7Oanx8XLZtN25FEvr2R9gW+WklLulVSQmfHm9GtX1mlky/MAysA5IyklQqlbY0WRzYzNLSku666y735n6FYFNmBBp5hZ4hrxBicXm0PxKJhB577DFt27ZNe/bsWXOHhYUF3bhxQ6+//nrjHEsX7Y+AGpN0Up0Phz0pxjqjPyJaHWIxOTlpenQSBkwqlXIzbc70Gx0DgbxCz5BXGAC0P5oMSg9ms4ikvZIelHS3pAe0fv/MgmqbSH8k6V1JlxWSvWUwMJ6SNGlZlq5fv76lFcaAVsrlsm699Vb3JnvJwS/kFXxHXmHA0P4YUpHVAwiCqFavXqXTadMXkTEgJiYmGhcIIO/gF/IKviOvMCSGrv0xqD2YQFiMS0pLzG1C95rmMtEbAL+RV/ANeQUMrjCvIgsMgjdVGy6h5557znRZEHKPP/64+2VOVNbgP/IKviGvgMFFDyZgXr1XIJ/PKx6Pmy4PQuj8+fM6ePCge9OWNG+6TBhI5BW6Rl4Bg40GJhAMc5LsRCKhCxcumC4LQqZarWrnzp0qFApSbf+scdNlwkAjr9Ax8goYfAyRBYLhsCTNzMxoamrKdFkQMo8++qhbWSuotocW0EvkFTpGXgGDjx5MIDhOSEpJ0srKimKxmOnyIASahpqxUAb6hbzClpFXwHCggQkER0TSVUm2bdu6evUqe81hQ8ViUbZtM9QMJpBX2BLyChge/2K6AADq/p+kP0j6X5988ok+++wzffvb3zZdJgRUtVrV/v379fHHH0u1oWYPSaqYLheGBnmFtpFXAACYNa7VDc0zmYzpfbARUA0blDuSRk2/aTG0yCtsirwCAMC8Ka3+M85ms6brBgiYVCrVWFljmBlMI6/QEnkFDB/mYALBFJH0e0kJy7KUy+VYRAOSpNnZWe3fv9+9+bykF0yXCUOPvIIn8goYTjQwgeCKScpJsqi0QVpXWZuR9G1JVdPlAkReoQl5BQwvGphAsK2ptC0uLioajZouEwygsoYQIK8gibwCht0XTBcAwIaKkr4rSYVCQQ8//LDK5bLpMqHPmiprBVFZQzCRVyCvANCDCYTEmKSsJDH8bLh4VNZs1SryQFCRV0OKvAIg0YMJhMWspP1SrWfAtm0Vi/zPHnRNlbWcqKwhHMirIUReAXDRwATCY12lbX5+3nSZ0CMvvPBC8xymr4vKGsKDvBoi5BUAAOEWk7Qs9p0bSJVKpXlT8guqbQMBhBF5NcDIKwAABkdM0pxW/6mnUimnUqmYrmugSysrK04ikWisrJ0QlTWEH3k1gMgrAAAGT0S1q8WOJCeRSDilUsl0nQMdymazjmVZjZW1cdNvMMBH5NUAIa8AABhsJ7T6T96yLIaghUylUnFSqVRjRW1Z0qjpNxXQI+RViJFXAAAMjzE1zHOamJhgCFoI5PN5x7bt5vlL7EyPQUdehRB5BQDA8ImqYQiaZVlOLpczXSeBh0ql4kxOTjZW1BhihmFDXoUEeQUAAMbVUBGYmJhgrlOA5HK55l6AOUlx028awBDyKsDIKwAA4IqpqXcgnU6brqsMtVKp5CSTyeZegKfEqosAeRUw5BUAAGhlXA1znWzbZlGNPiuVSs2LYjiSplSrVAP4HHllGHkFAADaEVHDyo1arbjl83nTdZmBVqlUnHQ63byU/7JqC5wA8EZeGUBeAQCATsRVuxJdr0AkEgl6CHy2QUVtXAwvA9oVF3nVc+QVAADwQ1xNFTd3KBpbBXSuxdAyR7XeGCpqQGfiIq98R14BAIBeiKup4mZZljM5OckqjluQy+W8FsNYVq2ixh5xgD/iIq+6Rl4BAIB+iEs6qaYr2clkkuFoLZRKJWdycrJ5+X6GlgG9Fxd5tSXkFQAAMCWq2jL09VUctdpLkEqlhn4T9FKp5GQyGSeRSHgNK7sgFsMA+om8Iq8AAECIjKlpOJoaKm/DMv9pZWVlo0rasmoVXJbvB8wir8grAAAQElFJB9SwCbqahqWl0+mB2UKgVCo52WzWSaVSXsPJGucqjZr+wwBYh7wirwAEwE2mCwAgNKKS7pd0TFLS64RkMqlDhw5pdHRUu3fvVjQa7DUjisWi3n//fV25ckXvvfeeZmZmvE4rSPqVpHclXZZUNV1uAJsir8grAIbQwATQiYikvZIelPSIJNvrJMuy9MADD2jfvn3avXu39uzZo1gs1veKXLFYVLlc1qVLl7S4uLhR5cw1Lemcaj0hxb4WFoDfyCsA6CMamAD8EJX0NUn/KumwpMRmd7BtW/fcc49isZj27t1b//6+ffs6KsClS5fqX587d06SdPHiRRUKhc3uWpB0UbUK2vzqAWBwkVcA0EM0MAH0SlzSnapV4vZJekCSZbhMOUn/R7WhY/OqVda44g8gLvIKAHxBAxNAv8VU60FwL/2PSLp79et71GL4Whtm9Hnl65Kkv0m6IWlh9ftl0y8cQOiQVwAAAAMqIjYEBxAO5BUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwqf8PNaKebFevR6UAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTMtMTEtMjRUMTQ6NDM6MzUrMDA6MDArZv70AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDEzLTExLTI0VDE0OjQzOjM1KzAwOjAwWjtGSAAAAABJRU5ErkJggg=="},8453:(e,n,r)=>{r.d(n,{R:()=>a,x:()=>l});var s=r(6540);const o={},t=s.createContext(o);function a(e){const n=s.useContext(t);return s.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:a(e.components),s.createElement(t.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/935f2afb.6562d556.js b/assets/js/935f2afb.6562d556.js new file mode 100644 index 00000000..3f33b336 --- /dev/null +++ b/assets/js/935f2afb.6562d556.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[581],{5610:e=>{e.exports=JSON.parse('{"pluginId":"default","version":"current","label":"Next","banner":null,"badge":false,"noIndex":false,"className":"docs-version-current","isLast":true,"docsSidebars":{"tutorialSidebar":[{"type":"link","label":"Introduction","href":"/lmc-rbac-mvc/docs/intro","docId":"intro","unlisted":false},{"type":"link","label":"Requirements and Installation","href":"/lmc-rbac-mvc/docs/installation","docId":"installation","unlisted":false},{"type":"link","label":"Quick Start","href":"/lmc-rbac-mvc/docs/quick-start","docId":"quick-start","unlisted":false},{"type":"link","label":"Role providers","href":"/lmc-rbac-mvc/docs/role-providers","docId":"role-providers","unlisted":false},{"type":"link","label":"Guards","href":"/lmc-rbac-mvc/docs/guards","docId":"guards","unlisted":false},{"type":"link","label":"Strategies","href":"/lmc-rbac-mvc/docs/strategies","docId":"strategies","unlisted":false},{"type":"link","label":"Using the Authorization Service","href":"/lmc-rbac-mvc/docs/using-the-authorization-service","docId":"using-the-authorization-service","unlisted":false},{"type":"link","label":"Cookbook","href":"/lmc-rbac-mvc/docs/cookbook","docId":"cookbook","unlisted":false},{"type":"link","label":"Support","href":"/lmc-rbac-mvc/docs/support","docId":"support","unlisted":false}]},"docs":{"cookbook":{"id":"cookbook","title":"Cookbook","description":"This section will help you further understand how LmcRbacMvc works by providing more concrete examples. If you have","sidebar":"tutorialSidebar"},"guards":{"id":"guards","title":"Guards","description":"In this section, you will learn:","sidebar":"tutorialSidebar"},"installation":{"id":"installation","title":"Requirements and Installation","description":"Requirements","sidebar":"tutorialSidebar"},"intro":{"id":"intro","title":"Introduction","description":"LmcRbacMvc is a role-based access control Laminas MVC module to provide additional features on top of Laminas\\\\Permissions\\\\Rbac","sidebar":"tutorialSidebar"},"quick-start":{"id":"quick-start","title":"Quick Start","description":"In this section, you will learn:","sidebar":"tutorialSidebar"},"role-providers":{"id":"role-providers","title":"Role providers","description":"In this section, you will learn:","sidebar":"tutorialSidebar"},"strategies":{"id":"strategies","title":"Strategies","description":"In this section, you will learn:","sidebar":"tutorialSidebar"},"support":{"id":"support","title":"Support","description":"- File issues at https://github.com/LM-Commons/LmcRbacMvc/issues.","sidebar":"tutorialSidebar"},"using-the-authorization-service":{"id":"using-the-authorization-service","title":"Using the Authorization Service","description":"This section will teach you how to use the AuthorizationService to its full extent.","sidebar":"tutorialSidebar"}}}')}}]); \ No newline at end of file diff --git a/assets/js/93a10038.26f7b70f.js b/assets/js/93a10038.26f7b70f.js new file mode 100644 index 00000000..456d2aeb --- /dev/null +++ b/assets/js/93a10038.26f7b70f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[828],{4738:c=>{c.exports=JSON.parse('{"label":"lmcrbacmvc","permalink":"/lmc-rbac-mvc/blog/tags/lmcrbacmvc","allTagsPath":"/lmc-rbac-mvc/blog/tags","count":1,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/983b303c.20d2d832.js b/assets/js/983b303c.20d2d832.js new file mode 100644 index 00000000..b8e62e06 --- /dev/null +++ b/assets/js/983b303c.20d2d832.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[660],{4009:e=>{e.exports=JSON.parse('{"permalink":"/lmc-rbac-mvc/blog/tags/lmcrbacmvc","page":1,"postsPerPage":10,"totalPages":1,"totalCount":1,"blogDescription":"Blog","blogTitle":"Blog"}')}}]); \ No newline at end of file diff --git a/assets/js/9e4087bc.bddafc5d.js b/assets/js/9e4087bc.bddafc5d.js new file mode 100644 index 00000000..10485eb0 --- /dev/null +++ b/assets/js/9e4087bc.bddafc5d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[711],{9331:(e,s,r)=>{r.r(s),r.d(s,{default:()=>o});r(6540);var t=r(8774),a=r(1312),i=r(1003),c=r(781),n=r(1107),l=r(4848);function d(e){let{year:s,posts:r}=e;return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(n.A,{as:"h3",id:s,children:s}),(0,l.jsx)("ul",{children:r.map((e=>(0,l.jsx)("li",{children:(0,l.jsxs)(t.A,{to:e.metadata.permalink,children:[e.metadata.formattedDate," - ",e.metadata.title]})},e.metadata.date)))})]})}function h(e){let{years:s}=e;return(0,l.jsx)("section",{className:"margin-vert--lg",children:(0,l.jsx)("div",{className:"container",children:(0,l.jsx)("div",{className:"row",children:s.map(((e,s)=>(0,l.jsx)("div",{className:"col col--4 margin-vert--lg",children:(0,l.jsx)(d,{...e})},s)))})})})}function o(e){let{archive:s}=e;const r=(0,a.T)({id:"theme.blog.archive.title",message:"Archive",description:"The page & hero title of the blog archive page"}),t=(0,a.T)({id:"theme.blog.archive.description",message:"Archive",description:"The page & hero description of the blog archive page"}),d=function(e){const s=e.reduce(((e,s)=>{const r=s.metadata.date.split("-")[0],t=e.get(r)??[];return e.set(r,[s,...t])}),new Map);return Array.from(s,(e=>{let[s,r]=e;return{year:s,posts:r}}))}(s.blogPosts);return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(i.be,{title:r,description:t}),(0,l.jsxs)(c.A,{children:[(0,l.jsx)("header",{className:"hero hero--primary",children:(0,l.jsxs)("div",{className:"container",children:[(0,l.jsx)(n.A,{as:"h1",className:"hero__title",children:r}),(0,l.jsx)("p",{className:"hero__subtitle",children:t})]})}),(0,l.jsx)("main",{children:d.length>0&&(0,l.jsx)(h,{years:d})})]})]})}}}]); \ No newline at end of file diff --git a/assets/js/a6185ec0.7643b318.js b/assets/js/a6185ec0.7643b318.js new file mode 100644 index 00000000..243142bf --- /dev/null +++ b/assets/js/a6185ec0.7643b318.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[270],{3808:c=>{c.exports=JSON.parse('[{"label":"laminas","permalink":"/lmc-rbac-mvc/blog/tags/laminas","count":2},{"label":"PHP","permalink":"/lmc-rbac-mvc/blog/tags/php","count":2},{"label":"lmcrbacmvc","permalink":"/lmc-rbac-mvc/blog/tags/lmcrbacmvc","count":1}]')}}]); \ No newline at end of file diff --git a/assets/js/a6aa9e1f.aac55eda.js b/assets/js/a6aa9e1f.aac55eda.js new file mode 100644 index 00000000..e535dc3b --- /dev/null +++ b/assets/js/a6aa9e1f.aac55eda.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[643],{7785:(e,t,a)=>{a.r(t),a.d(t,{default:()=>b});a(6540);var n=a(4164),i=a(4586),s=a(1003),r=a(7559),l=a(6535),o=a(7713),g=a(1463),d=a(3892),c=a(4848);function p(e){const{metadata:t}=e,{siteConfig:{title:a}}=(0,i.A)(),{blogDescription:n,blogTitle:r,permalink:l}=t,o="/"===l?a:r;return(0,c.jsxs)(c.Fragment,{children:[(0,c.jsx)(s.be,{title:o,description:n}),(0,c.jsx)(g.A,{tag:"blog_posts_list"})]})}function m(e){const{metadata:t,items:a,sidebar:n}=e;return(0,c.jsxs)(l.A,{sidebar:n,children:[(0,c.jsx)(d.A,{items:a}),(0,c.jsx)(o.A,{metadata:t})]})}function b(e){return(0,c.jsxs)(s.e3,{className:(0,n.A)(r.G.wrapper.blogPages,r.G.page.blogListPage),children:[(0,c.jsx)(p,{...e}),(0,c.jsx)(m,{...e})]})}},7713:(e,t,a)=>{a.d(t,{A:()=>r});a(6540);var n=a(1312),i=a(9022),s=a(4848);function r(e){const{metadata:t}=e,{previousPage:a,nextPage:r}=t;return(0,s.jsxs)("nav",{className:"pagination-nav","aria-label":(0,n.T)({id:"theme.blog.paginator.navAriaLabel",message:"Blog list page navigation",description:"The ARIA label for the blog pagination"}),children:[a&&(0,s.jsx)(i.A,{permalink:a,title:(0,s.jsx)(n.A,{id:"theme.blog.paginator.newerEntries",description:"The label used to navigate to the newer blog posts page (previous page)",children:"Newer Entries"})}),r&&(0,s.jsx)(i.A,{permalink:r,title:(0,s.jsx)(n.A,{id:"theme.blog.paginator.olderEntries",description:"The label used to navigate to the older blog posts page (next page)",children:"Older Entries"}),isNext:!0})]})}},3892:(e,t,a)=>{a.d(t,{A:()=>r});a(6540);var n=a(7131),i=a(8258),s=a(4848);function r(e){let{items:t,component:a=i.A}=e;return(0,s.jsx)(s.Fragment,{children:t.map((e=>{let{content:t}=e;return(0,s.jsx)(n.i,{content:t,children:(0,s.jsx)(a,{children:(0,s.jsx)(t,{})})},t.metadata.permalink)}))})}}}]); \ No newline at end of file diff --git a/assets/js/a7bd4aaa.fd305e17.js b/assets/js/a7bd4aaa.fd305e17.js new file mode 100644 index 00000000..286d4cea --- /dev/null +++ b/assets/js/a7bd4aaa.fd305e17.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[98],{4532:(n,e,s)=>{s.r(e),s.d(e,{default:()=>d});s(6540);var r=s(1003),o=s(2967),t=s(2252),c=s(2831),i=s(1463),u=s(4848);function a(n){const{version:e}=n;return(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(i.A,{version:e.version,tag:(0,o.tU)(e.pluginId,e.version)}),(0,u.jsx)(r.be,{children:e.noIndex&&(0,u.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})]})}function l(n){const{version:e,route:s}=n;return(0,u.jsx)(r.e3,{className:e.className,children:(0,u.jsx)(t.n,{version:e,children:(0,c.v)(s.routes)})})}function d(n){return(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(a,{...n}),(0,u.jsx)(l,{...n})]})}}}]); \ No newline at end of file diff --git a/assets/js/a94703ab.8b0ccadb.js b/assets/js/a94703ab.8b0ccadb.js new file mode 100644 index 00000000..a940669b --- /dev/null +++ b/assets/js/a94703ab.8b0ccadb.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[48],{2559:(e,t,n)=>{n.r(t),n.d(t,{default:()=>be});var a=n(6540),o=n(4164),i=n(1003),s=n(7559),l=n(1754),r=n(6588),c=n(1312),d=n(3104),u=n(5062);const m={backToTopButton:"backToTopButton_sjWU",backToTopButtonShow:"backToTopButtonShow_xfvO"};var b=n(4848);function h(){const{shown:e,scrollToTop:t}=function(e){let{threshold:t}=e;const[n,o]=(0,a.useState)(!1),i=(0,a.useRef)(!1),{startScroll:s,cancelScroll:l}=(0,d.gk)();return(0,d.Mq)(((e,n)=>{let{scrollY:a}=e;const s=n?.scrollY;s&&(i.current?i.current=!1:a>=s?(l(),o(!1)):a<t?o(!1):a+window.innerHeight<document.documentElement.scrollHeight&&o(!0))})),(0,u.$)((e=>{e.location.hash&&(i.current=!0,o(!1))})),{shown:n,scrollToTop:()=>s(0)}}({threshold:300});return(0,b.jsx)("button",{"aria-label":(0,c.T)({id:"theme.BackToTopButton.buttonAriaLabel",message:"Scroll back to top",description:"The ARIA label for the back to top button"}),className:(0,o.A)("clean-btn",s.G.common.backToTopButton,m.backToTopButton,e&&m.backToTopButtonShow),type:"button",onClick:t})}var p=n(3109),x=n(6347),j=n(4581),f=n(6342),v=n(3465);function _(e){return(0,b.jsx)("svg",{width:"20",height:"20","aria-hidden":"true",...e,children:(0,b.jsxs)("g",{fill:"#7a7a7a",children:[(0,b.jsx)("path",{d:"M9.992 10.023c0 .2-.062.399-.172.547l-4.996 7.492a.982.982 0 01-.828.454H1c-.55 0-1-.453-1-1 0-.2.059-.403.168-.551l4.629-6.942L.168 3.078A.939.939 0 010 2.528c0-.548.45-.997 1-.997h2.996c.352 0 .649.18.828.45L9.82 9.472c.11.148.172.347.172.55zm0 0"}),(0,b.jsx)("path",{d:"M19.98 10.023c0 .2-.058.399-.168.547l-4.996 7.492a.987.987 0 01-.828.454h-3c-.547 0-.996-.453-.996-1 0-.2.059-.403.168-.551l4.625-6.942-4.625-6.945a.939.939 0 01-.168-.55 1 1 0 01.996-.997h3c.348 0 .649.18.828.45l4.996 7.492c.11.148.168.347.168.55zm0 0"})]})})}const A={collapseSidebarButton:"collapseSidebarButton_PEFL",collapseSidebarButtonIcon:"collapseSidebarButtonIcon_kv0_"};function g(e){let{onClick:t}=e;return(0,b.jsx)("button",{type:"button",title:(0,c.T)({id:"theme.docs.sidebar.collapseButtonTitle",message:"Collapse sidebar",description:"The title attribute for collapse button of doc sidebar"}),"aria-label":(0,c.T)({id:"theme.docs.sidebar.collapseButtonAriaLabel",message:"Collapse sidebar",description:"The title attribute for collapse button of doc sidebar"}),className:(0,o.A)("button button--secondary button--outline",A.collapseSidebarButton),onClick:t,children:(0,b.jsx)(_,{className:A.collapseSidebarButtonIcon})})}var k=n(5041),C=n(9532);const S=Symbol("EmptyContext"),T=a.createContext(S);function N(e){let{children:t}=e;const[n,o]=(0,a.useState)(null),i=(0,a.useMemo)((()=>({expandedItem:n,setExpandedItem:o})),[n]);return(0,b.jsx)(T.Provider,{value:i,children:t})}var I=n(1422),B=n(9169),y=n(8774),w=n(2303);function L(e){let{collapsed:t,categoryLabel:n,onClick:a}=e;return(0,b.jsx)("button",{"aria-label":t?(0,c.T)({id:"theme.DocSidebarItem.expandCategoryAriaLabel",message:"Expand sidebar category '{label}'",description:"The ARIA label to expand the sidebar category"},{label:n}):(0,c.T)({id:"theme.DocSidebarItem.collapseCategoryAriaLabel",message:"Collapse sidebar category '{label}'",description:"The ARIA label to collapse the sidebar category"},{label:n}),type:"button",className:"clean-btn menu__caret",onClick:a})}function E(e){let{item:t,onItemClick:n,activePath:i,level:r,index:c,...d}=e;const{items:u,label:m,collapsible:h,className:p,href:x}=t,{docs:{sidebar:{autoCollapseCategories:j}}}=(0,f.p)(),v=function(e){const t=(0,w.A)();return(0,a.useMemo)((()=>e.href&&!e.linkUnlisted?e.href:!t&&e.collapsible?(0,l.Nr)(e):void 0),[e,t])}(t),_=(0,l.w8)(t,i),A=(0,B.ys)(x,i),{collapsed:g,setCollapsed:k}=(0,I.u)({initialState:()=>!!h&&(!_&&t.collapsed)}),{expandedItem:N,setExpandedItem:E}=function(){const e=(0,a.useContext)(T);if(e===S)throw new C.dV("DocSidebarItemsExpandedStateProvider");return e}(),M=function(e){void 0===e&&(e=!g),E(e?null:c),k(e)};return function(e){let{isActive:t,collapsed:n,updateCollapsed:o}=e;const i=(0,C.ZC)(t);(0,a.useEffect)((()=>{t&&!i&&n&&o(!1)}),[t,i,n,o])}({isActive:_,collapsed:g,updateCollapsed:M}),(0,a.useEffect)((()=>{h&&null!=N&&N!==c&&j&&k(!0)}),[h,N,c,k,j]),(0,b.jsxs)("li",{className:(0,o.A)(s.G.docs.docSidebarItemCategory,s.G.docs.docSidebarItemCategoryLevel(r),"menu__list-item",{"menu__list-item--collapsed":g},p),children:[(0,b.jsxs)("div",{className:(0,o.A)("menu__list-item-collapsible",{"menu__list-item-collapsible--active":A}),children:[(0,b.jsx)(y.A,{className:(0,o.A)("menu__link",{"menu__link--sublist":h,"menu__link--sublist-caret":!x&&h,"menu__link--active":_}),onClick:h?e=>{n?.(t),x?M(!1):(e.preventDefault(),M())}:()=>{n?.(t)},"aria-current":A?"page":void 0,"aria-expanded":h?!g:void 0,href:h?v??"#":v,...d,children:m}),x&&h&&(0,b.jsx)(L,{collapsed:g,categoryLabel:m,onClick:e=>{e.preventDefault(),M()}})]}),(0,b.jsx)(I.N,{lazy:!0,as:"ul",className:"menu__list",collapsed:g,children:(0,b.jsx)(U,{items:u,tabIndex:g?-1:0,onItemClick:n,activePath:i,level:r+1})})]})}var M=n(6654),H=n(3186);const G={menuExternalLink:"menuExternalLink_NmtK"};function W(e){let{item:t,onItemClick:n,activePath:a,level:i,index:r,...c}=e;const{href:d,label:u,className:m,autoAddBaseUrl:h}=t,p=(0,l.w8)(t,a),x=(0,M.A)(d);return(0,b.jsx)("li",{className:(0,o.A)(s.G.docs.docSidebarItemLink,s.G.docs.docSidebarItemLinkLevel(i),"menu__list-item",m),children:(0,b.jsxs)(y.A,{className:(0,o.A)("menu__link",!x&&G.menuExternalLink,{"menu__link--active":p}),autoAddBaseUrl:h,"aria-current":p?"page":void 0,to:d,...x&&{onClick:n?()=>n(t):void 0},...c,children:[u,!x&&(0,b.jsx)(H.A,{})]})},u)}const P={menuHtmlItem:"menuHtmlItem_M9Kj"};function R(e){let{item:t,level:n,index:a}=e;const{value:i,defaultStyle:l,className:r}=t;return(0,b.jsx)("li",{className:(0,o.A)(s.G.docs.docSidebarItemLink,s.G.docs.docSidebarItemLinkLevel(n),l&&[P.menuHtmlItem,"menu__list-item"],r),dangerouslySetInnerHTML:{__html:i}},a)}function D(e){let{item:t,...n}=e;switch(t.type){case"category":return(0,b.jsx)(E,{item:t,...n});case"html":return(0,b.jsx)(R,{item:t,...n});default:return(0,b.jsx)(W,{item:t,...n})}}function F(e){let{items:t,...n}=e;const a=(0,l.Y)(t,n.activePath);return(0,b.jsx)(N,{children:a.map(((e,t)=>(0,b.jsx)(D,{item:e,index:t,...n},t)))})}const U=(0,a.memo)(F),V={menu:"menu_SIkG",menuWithAnnouncementBar:"menuWithAnnouncementBar_GW3s"};function Y(e){let{path:t,sidebar:n,className:i}=e;const l=function(){const{isActive:e}=(0,k.Mj)(),[t,n]=(0,a.useState)(e);return(0,d.Mq)((t=>{let{scrollY:a}=t;e&&n(0===a)}),[e]),e&&t}();return(0,b.jsx)("nav",{"aria-label":(0,c.T)({id:"theme.docs.sidebar.navAriaLabel",message:"Docs sidebar",description:"The ARIA label for the sidebar navigation"}),className:(0,o.A)("menu thin-scrollbar",V.menu,l&&V.menuWithAnnouncementBar,i),children:(0,b.jsx)("ul",{className:(0,o.A)(s.G.docs.docSidebarMenu,"menu__list"),children:(0,b.jsx)(U,{items:n,activePath:t,level:1})})})}const K="sidebar_njMd",z="sidebarWithHideableNavbar_wUlq",q="sidebarHidden_VK0M",O="sidebarLogo_isFc";function J(e){let{path:t,sidebar:n,onCollapse:a,isHidden:i}=e;const{navbar:{hideOnScroll:s},docs:{sidebar:{hideable:l}}}=(0,f.p)();return(0,b.jsxs)("div",{className:(0,o.A)(K,s&&z,i&&q),children:[s&&(0,b.jsx)(v.A,{tabIndex:-1,className:O}),(0,b.jsx)(Y,{path:t,sidebar:n}),l&&(0,b.jsx)(g,{onClick:a})]})}const Q=a.memo(J);var X=n(5600),Z=n(9876);const $=e=>{let{sidebar:t,path:n}=e;const a=(0,Z.M)();return(0,b.jsx)("ul",{className:(0,o.A)(s.G.docs.docSidebarMenu,"menu__list"),children:(0,b.jsx)(U,{items:t,activePath:n,onItemClick:e=>{"category"===e.type&&e.href&&a.toggle(),"link"===e.type&&a.toggle()},level:1})})};function ee(e){return(0,b.jsx)(X.GX,{component:$,props:e})}const te=a.memo(ee);function ne(e){const t=(0,j.l)(),n="desktop"===t||"ssr"===t,a="mobile"===t;return(0,b.jsxs)(b.Fragment,{children:[n&&(0,b.jsx)(Q,{...e}),a&&(0,b.jsx)(te,{...e})]})}const ae={expandButton:"expandButton_TmdG",expandButtonIcon:"expandButtonIcon_i1dp"};function oe(e){let{toggleSidebar:t}=e;return(0,b.jsx)("div",{className:ae.expandButton,title:(0,c.T)({id:"theme.docs.sidebar.expandButtonTitle",message:"Expand sidebar",description:"The ARIA label and title attribute for expand button of doc sidebar"}),"aria-label":(0,c.T)({id:"theme.docs.sidebar.expandButtonAriaLabel",message:"Expand sidebar",description:"The ARIA label and title attribute for expand button of doc sidebar"}),tabIndex:0,role:"button",onKeyDown:t,onClick:t,children:(0,b.jsx)(_,{className:ae.expandButtonIcon})})}const ie={docSidebarContainer:"docSidebarContainer_YfHR",docSidebarContainerHidden:"docSidebarContainerHidden_DPk8",sidebarViewport:"sidebarViewport_aRkj"};function se(e){let{children:t}=e;const n=(0,r.t)();return(0,b.jsx)(a.Fragment,{children:t},n?.name??"noSidebar")}function le(e){let{sidebar:t,hiddenSidebarContainer:n,setHiddenSidebarContainer:i}=e;const{pathname:l}=(0,x.zy)(),[r,c]=(0,a.useState)(!1),d=(0,a.useCallback)((()=>{r&&c(!1),!r&&(0,p.O)()&&c(!0),i((e=>!e))}),[i,r]);return(0,b.jsx)("aside",{className:(0,o.A)(s.G.docs.docSidebarContainer,ie.docSidebarContainer,n&&ie.docSidebarContainerHidden),onTransitionEnd:e=>{e.currentTarget.classList.contains(ie.docSidebarContainer)&&n&&c(!0)},children:(0,b.jsx)(se,{children:(0,b.jsxs)("div",{className:(0,o.A)(ie.sidebarViewport,r&&ie.sidebarViewportHidden),children:[(0,b.jsx)(ne,{sidebar:t,path:l,onCollapse:d,isHidden:r}),r&&(0,b.jsx)(oe,{toggleSidebar:d})]})})})}const re={docMainContainer:"docMainContainer_TBSr",docMainContainerEnhanced:"docMainContainerEnhanced_lQrH",docItemWrapperEnhanced:"docItemWrapperEnhanced_JWYK"};function ce(e){let{hiddenSidebarContainer:t,children:n}=e;const a=(0,r.t)();return(0,b.jsx)("main",{className:(0,o.A)(re.docMainContainer,(t||!a)&&re.docMainContainerEnhanced),children:(0,b.jsx)("div",{className:(0,o.A)("container padding-top--md padding-bottom--lg",re.docItemWrapper,t&&re.docItemWrapperEnhanced),children:n})})}const de={docRoot:"docRoot_UBD9",docsWrapper:"docsWrapper_hBAB"};function ue(e){let{children:t}=e;const n=(0,r.t)(),[o,i]=(0,a.useState)(!1);return(0,b.jsxs)("div",{className:de.docsWrapper,children:[(0,b.jsx)(h,{}),(0,b.jsxs)("div",{className:de.docRoot,children:[n&&(0,b.jsx)(le,{sidebar:n.items,hiddenSidebarContainer:o,setHiddenSidebarContainer:i}),(0,b.jsx)(ce,{hiddenSidebarContainer:o,children:t})]})]})}var me=n(3363);function be(e){const t=(0,l.B5)(e);if(!t)return(0,b.jsx)(me.A,{});const{docElement:n,sidebarName:a,sidebarItems:c}=t;return(0,b.jsx)(i.e3,{className:(0,o.A)(s.G.page.docsDocPage),children:(0,b.jsx)(r.V,{name:a,items:c,children:(0,b.jsx)(ue,{children:n})})})}},3363:(e,t,n)=>{n.d(t,{A:()=>l});n(6540);var a=n(4164),o=n(1312),i=n(1107),s=n(4848);function l(e){let{className:t}=e;return(0,s.jsx)("main",{className:(0,a.A)("container margin-vert--xl",t),children:(0,s.jsx)("div",{className:"row",children:(0,s.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,s.jsx)(i.A,{as:"h1",className:"hero__title",children:(0,s.jsx)(o.A,{id:"theme.NotFound.title",description:"The title of the 404 page",children:"Page Not Found"})}),(0,s.jsx)("p",{children:(0,s.jsx)(o.A,{id:"theme.NotFound.p1",description:"The first paragraph of the 404 page",children:"We could not find what you were looking for."})}),(0,s.jsx)("p",{children:(0,s.jsx)(o.A,{id:"theme.NotFound.p2",description:"The 2nd paragraph of the 404 page",children:"Please contact the owner of the site that linked you to the original URL and let them know their link is broken."})})]})})})}}}]); \ No newline at end of file diff --git a/assets/js/b5cb822e.5f84e9da.js b/assets/js/b5cb822e.5f84e9da.js new file mode 100644 index 00000000..9653256c --- /dev/null +++ b/assets/js/b5cb822e.5f84e9da.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[390],{2957:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>i,contentTitle:()=>s,default:()=>u,frontMatter:()=>r,metadata:()=>a,toc:()=>m});var n=o(4848),c=o(8453);const r={slug:"welcome",title:"Welcome",authors:["ericr"],tags:["laminas","PHP"]},s=void 0,a={permalink:"/lmc-rbac-mvc/blog/welcome",editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2022-08-02-welcome.md",source:"@site/blog/2022-08-02-welcome.md",title:"Welcome",description:"Welcome to the new documentation website for the LM-Commons organization.",date:"2022-08-02T00:00:00.000Z",formattedDate:"August 2, 2022",tags:[{label:"laminas",permalink:"/lmc-rbac-mvc/blog/tags/laminas"},{label:"PHP",permalink:"/lmc-rbac-mvc/blog/tags/php"}],readingTime:.155,hasTruncateMarker:!1,authors:[{name:"Eric Richer",title:"LM-Commons Administrator",url:"https://github.com/visto9259",imageURL:"https://github.com/visto9259.png",key:"ericr"}],frontMatter:{slug:"welcome",title:"Welcome",authors:["ericr"],tags:["laminas","PHP"]},unlisted:!1,prevItem:{title:"New documentation",permalink:"/lmc-rbac-mvc/blog/new-documentation"}},i={authorsImageUrls:[void 0]},m=[];function l(e){const t={p:"p",...(0,c.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.p,{children:"Welcome to the new documentation website for the LM-Commons organization."}),"\n",(0,n.jsx)(t.p,{children:"This site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages."})]})}function u(e={}){const{wrapper:t}={...(0,c.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(l,{...e})}):l(e)}},8453:(e,t,o)=>{o.d(t,{R:()=>s,x:()=>a});var n=o(6540);const c={},r=n.createContext(c);function s(e){const t=n.useContext(r);return n.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(c):e.components||c:s(e.components),n.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/b9207088.c1f4a27e.js b/assets/js/b9207088.c1f4a27e.js new file mode 100644 index 00000000..4dd6f216 --- /dev/null +++ b/assets/js/b9207088.c1f4a27e.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[571],{8849:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>a,contentTitle:()=>r,default:()=>d,frontMatter:()=>s,metadata:()=>c,toc:()=>l});var i=t(4848),o=t(8453);const s={sidebar_position:8},r="Cookbook",c={id:"cookbook",title:"Cookbook",description:"This section will help you further understand how LmcRbacMvc works by providing more concrete examples. If you have",source:"@site/docs/cookbook.md",sourceDirName:".",slug:"/cookbook",permalink:"/lmc-rbac-mvc/docs/cookbook",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/cookbook.md",tags:[],version:"current",sidebarPosition:8,frontMatter:{sidebar_position:8},sidebar:"tutorialSidebar",previous:{title:"Using the Authorization Service",permalink:"/lmc-rbac-mvc/docs/using-the-authorization-service"},next:{title:"Support",permalink:"/lmc-rbac-mvc/docs/support"}},a={},l=[{value:"A Real World Application",id:"a-real-world-application",level:2},{value:"Best practices",id:"best-practices",level:2},{value:"When using guards then?",id:"when-using-guards-then",level:3},{value:"A Real World Application Part 2 - Only delete your own Posts",id:"a-real-world-application-part-2---only-delete-your-own-posts",level:2},{value:"A Real World Application Part 3 - Admins can delete everything",id:"a-real-world-application-part-3---admins-can-delete-everything",level:2},{value:"A Real World Application Part 4 - Checking permissions in the view",id:"a-real-world-application-part-4---checking-permissions-in-the-view",level:2},{value:"Using LmcRbacMvc with Doctrine ORM",id:"using-lmcrbacmvc-with-doctrine-orm",level:2},{value:"How to deal with roles with lot of permissions?",id:"how-to-deal-with-roles-with-lot-of-permissions",level:2},{value:"Using LmcRbacMvc and ZF2 Assetic",id:"using-lmcrbacmvc-and-zf2-assetic",level:2},{value:"Using LmcRbacMvc and LmcUser",id:"using-lmcrbacmvc-and-lmcuser",level:2}];function h(e){const n={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",p:"p",pre:"pre",strong:"strong",...(0,o.R)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.h1,{id:"cookbook",children:"Cookbook"}),"\n",(0,i.jsx)(n.p,{children:"This section will help you further understand how LmcRbacMvc works by providing more concrete examples. If you have\nany other recipe you'd like to add, please open an issue!"}),"\n",(0,i.jsx)(n.h2,{id:"a-real-world-application",children:"A Real World Application"}),"\n",(0,i.jsxs)(n.p,{children:["In this example we are going to create a very little real world application. We will create a controller\n",(0,i.jsx)(n.code,{children:"PostController"})," that interacts with a service called ",(0,i.jsx)(n.code,{children:"PostService"}),". For the sake of simplicity we will only\ncover the ",(0,i.jsx)(n.code,{children:"delete"}),"-methods of both parts."]}),"\n",(0,i.jsxs)(n.p,{children:["Let's start by creating a controller that has the ",(0,i.jsx)(n.code,{children:"PostService"})," as dependency:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class PostController\n{\n protected $postService;\n\n public function __construct(PostService $postService)\n {\n $this->postService = $postService;\n }\n\n // addAction(), editAction(), etc...\n\n public function deleteAction()\n {\n $id = $this->params()->fromQuery('id');\n\n $this->postService->deletePost($id);\n\n return $this->redirect()->toRoute('posts');\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Since we have a dependency, let's inject it using the ",(0,i.jsx)(n.code,{children:"ControllerManager"}),", we will do this inside our ",(0,i.jsx)(n.code,{children:"Module"})," class"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class Module\n{\n public function getConfig()\n {\n return [\n 'controllers' => [\n 'factories' => [\n 'PostController' => function ($cpm) {\n // We assume a Service key 'PostService' here that returns the PostService Class\n return new PostController(\n $cpm->getServiceLocator()->get('PostService')\n );\n },\n ],\n ],\n ];\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Now that we have this in place let us quickly define our ",(0,i.jsx)(n.code,{children:"PostService"}),". We will be using a Service that makes use\nof Doctrine, so we require a ",(0,i.jsx)(n.code,{children:"Doctrine\\Persistence\\ObjectManager"})," as dependency."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Persistence\\ObjectManager;\n\nclass PostService\n{\n protected $objectManager;\n\n public function __construct(ObjectManager $objectManager)\n {\n $this->objectManager = $objectManager;\n }\n\n public function deletePost($id)\n {\n $post = $this->objectManager->find('Post', $id);\n $this->objectManager->remove($post);\n $this->objectManager->flush();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["And for this one, too, let's quickly create the factory, again within our ",(0,i.jsx)(n.code,{children:"Module"})," class."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // getAutoloaderConfig(), getConfig(), etc...\n\n public function getServiceConfig()\n {\n return [\n 'factories' => [\n 'PostService' => function($sm) {\n return new PostService(\n $sm->get('doctrine.entitymanager.orm_default')\n );\n }\n ]\n ];\n }\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"With this set up we can now cover some best practices."}),"\n",(0,i.jsx)(n.h2,{id:"best-practices",children:"Best practices"}),"\n",(0,i.jsx)(n.p,{children:"Ideally, you should not protect your applications using only guards (Route or Controller guards).\nThis leaves your application open for some undesired side-effects.\nAs a best practice you should protect all your services or controllers by injecting the authorization service.\nBut let's go step by step:"}),"\n",(0,i.jsx)(n.p,{children:"Assuming the application example above we can easily use LmcRbacMvc to protect our route using the following guard:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'post/delete' => ['admin']\n ]\n ]\n ]\n];\n"})}),"\n",(0,i.jsx)(n.p,{children:'Now, any users that do not have the "admin" role will receive a 403 error (unauthorized) when trying to access\nthe "post/delete" route. However, this does not prevent the service (which should contain the actual logic in a properly\ndesign application) to be injected and used elsewhere in your code. For instance:'}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class PostController\n{\n protected $postService;\n\n public function createAction()\n {\n // this action may have been reached through the \"forward\" method, hence bypassing guards\n $this->postService->deletePost('2');\n }\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"You see the issue!"}),"\n",(0,i.jsxs)(n.p,{children:["The solution is to inject the ",(0,i.jsx)(n.code,{children:"AuthorizationService"})," into your services and check for the\npermissions before doing anything wrong. So let's modify our previously created ",(0,i.jsx)(n.code,{children:"PostService"})," class"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Persistence\\ObjectManager;\nuse LmcRbacMvc\\Service\\AuthorizationService;\n\nclass PostService\n{\n protected $objectManager;\n\n protected $authorizationService;\n\n public function __construct(\n ObjectManager $objectManager,\n AuthorizationService $autorizationService\n ) {\n $this->objectManager = $objectManager;\n $this->authorizationService = $autorizationService;\n }\n\n public function deletePost($id)\n {\n // First check permission\n if (!$this->authorizationService->isGranted('deletePost')) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n $post = $this->objectManager->find('Post', $id);\n $this->objectManager->remove($post);\n $this->objectManager->flush();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Since we now have an additional dependency we should inject it through our factory, again within our ",(0,i.jsx)(n.code,{children:"Module"})," class."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class Module\n{\n // getAutoloaderConfig(), getConfig(), etc...\n\n public function getServiceConfig()\n {\n return [\n 'factories' => [\n 'PostService' => function($sm) {\n return new PostService(\n $sm->get('doctrine.entitymanager.orm_default'),\n $sm->get('LmcRbacMvc\\Service\\AuthorizationService') // This is new!\n );\n }\n ]\n ];\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Alternatively, you can also protect your controllers using the ",(0,i.jsx)(n.code,{children:"isGranted"})," helper (you do not need to inject\nthe AuthorizationService then):"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"class PostController\n{\n protected $postService;\n\n public function createAction()\n {\n if (!$this->isGranted('deletePost')) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n $this->postService->deletePost('2');\n }\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"While protecting services is the more defensive way (because services are usually the last part of the logic flow),\nit is often complicated to deal with.\nIf your application is architectured correctly, it is often simpler to protect your controllers."}),"\n",(0,i.jsx)(n.h3,{id:"when-using-guards-then",children:"When using guards then?"}),"\n",(0,i.jsx)(n.p,{children:"In fact, you should see guards as a very efficient way to quickly reject access to a hierarchy of routes or a\nwhole controller. For instance, assuming you have the following route config:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'router' => [\n 'routes' => [\n 'admin' => [\n 'type' => 'Literal',\n 'options' => [\n 'route' => '/admin'\n ],\n 'may_terminate' => true,\n 'child_routes' => [\n 'users' => [\n 'type' => 'Literal',\n 'options' => [\n 'route' => '/users'\n ]\n ],\n 'invoices' => [\n 'type' => 'Literal',\n 'options' => [\n 'route' => '/invoices'\n ]\n ]\n ]\n ]\n ]\n ]\n};\n"})}),"\n",(0,i.jsx)(n.p,{children:"You can quickly reject access to all admin routes using the following guard:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbacMvc\\Guard\\RouteGuard' => [\n 'admin*' => ['admin']\n ]\n ]\n ]\n];\n"})}),"\n",(0,i.jsx)(n.h2,{id:"a-real-world-application-part-2---only-delete-your-own-posts",children:"A Real World Application Part 2 - Only delete your own Posts"}),"\n",(0,i.jsxs)(n.p,{children:["If you jumped straight to this section please notice that we assume you have the knowledge that we presented in the\nprevious example. In here we will cover a very common use case. Users of our Application should only have delete\npermissions to their own content. So let's quickly refresh our ",(0,i.jsx)(n.code,{children:"PostService"})," class:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Persistence\\ObjectManager;\n\nclass PostService\n{\n protected $objectManager;\n\n protected $authorizationService;\n\n public function __construct(\n ObjectManager $objectManager,\n AuthorizationService $autorizationService\n ) {\n $this->objectManager = $objectManager;\n $this->authorizationService = $autorizationService;\n }\n\n public function deletePost($id)\n {\n // First check permission\n if (!$this->authorizationService->isGranted('deletePost')) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n $post = $this->objectManager->find('Post', $id);\n $this->objectManager->remove($post);\n $this->objectManager->flush();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["As we can see, we check within our Service if the User of our Application is allowed to delete the post with a check\nagainst the ",(0,i.jsx)(n.code,{children:"deletePost"})," permission. Now how can we achieve that only a user who is the owner of the Post to be able to\ndelete his own post, but other users can't? We do not want to change our Service with more complex logic because this\nis not the task of such service. The Permission-System should handle this. And we can, for this we have the\n",(0,i.jsx)(n.code,{children:"AssertionPluginManager"})," and here is how to do it:"]}),"\n",(0,i.jsx)(n.p,{children:"First of all we need to write an Assertion. The Assertion will return a boolean statement about the current\nidentity being the owner of the post."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"namespace Your\\Namespace;\n\nuse LmcRbacMvc\\Assertion\\AssertionInterface;\nuse LmcRbacMvc\\Service\\AuthorizationService;\n\nclass MustBeAuthorAssertion implements AssertionInterface\n{\n /**\n * Check if this assertion is true\n *\n * @param AuthorizationService $authorization\n * @param mixed $post\n *\n * @return bool\n */\n public function assert(AuthorizationService $authorization, $post = null)\n {\n return $authorization->getIdentity() === $post->getAuthor();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["This simple ",(0,i.jsx)(n.code,{children:"MustBeAuthorAssertion"})," will check against the current ",(0,i.jsx)(n.code,{children:"$authorization"}),' if it equals the identity of the\ncurrent context Author. The second parameter is called the "context". A context can be anything (an object, a scalar,\nan array...) and only makes sense in the context of the assertion.']}),"\n",(0,i.jsxs)(n.p,{children:["Imagine a user calls ",(0,i.jsx)(n.code,{children:"http://my.dom/post/delete/42"}),", so obviously he wants to delete the Post-Entity with ID#42. In\nthis case Entity#42 is our Context! If you're wondering how the context gets there, bare with me. We will get to\nthis later."]}),"\n",(0,i.jsxs)(n.p,{children:["Now that we have written the Assertion, we want to make sure that this assertion will always be called, whenever we\ncheck for the ",(0,i.jsx)(n.code,{children:"deletePost"})," permission. We don't want others to delete our previous content! For this we have the so\ncalled ",(0,i.jsx)(n.code,{children:"assertion_map"}),". In this map we glue ",(0,i.jsx)(n.code,{children:"assertions"})," and ",(0,i.jsx)(n.code,{children:"permissions"})," together."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"// module.config.php or wherever you configure your RBAC stuff\nreturn [\n 'lmc_rbac' => [\n 'assertion_map' => [\n 'deletePost' => 'Your\\Namespace\\MustBeAuthorAssertion'\n ]\n ]\n];\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Now, whenever some test the ",(0,i.jsx)(n.code,{children:"deletePost"})," permission, it will automatically call the ",(0,i.jsx)(n.code,{children:"MustBeAuthorAssertion"})," from\nthe ",(0,i.jsx)(n.code,{children:"AssertionPluginManager"}),". This plugin manager is configured to automatically add unknown classes to an invokable.\nHowever, some assertions may need dependencies. You can manually configure the assertion plugin manager as\nshown below:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"// module.config.php or wherever you configure your RBAC stuff\nreturn [\n 'lmc_rbac' => [\n // ... other rbac stuff\n 'assertion_manager' => [\n 'factories' => [\n 'AssertionWithDependency' => 'Your\\Namespace\\AssertionWithDependencyFactory'\n ]\n ]\n ]\n];\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Now we need to remember about the ",(0,i.jsx)(n.strong,{children:"context"}),". Somehow we need to let the ",(0,i.jsx)(n.code,{children:"AssertionPluginManager"})," know about our\ncontext. This is done by simply passing it to the ",(0,i.jsx)(n.code,{children:"isGranted()"})," method. For this we need to modify our Service\none last time."]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Persistence\\ObjectManager;\n\nclass PostService\n{\n protected $objectManager;\n\n protected $authorizationService;\n\n public function __construct(\n ObjectManager $objectManager,\n AuthorizationService $autorizationService\n ) {\n $this->objectManager = $objectManager;\n $this->authorizationService = $autorizationService;\n }\n\n public function deletePost($id)\n {\n // Note, we now need to query for the post of interest first!\n $post = $this->objectManager->find('Post', $id);\n\n // Check the permission now with a given context\n if (!$this->authorizationService->isGranted('deletePost', $post)) {\n throw new UnauthorizedException('You are not allowed !');\n }\n\n $this->objectManager->remove($post);\n $this->objectManager->flush();\n }\n}\n"})}),"\n",(0,i.jsxs)(n.p,{children:["And there you have it. The context is injected into the ",(0,i.jsx)(n.code,{children:"isGranted()"})," method and now the ",(0,i.jsx)(n.code,{children:"AssertionPluginManager"})," knows\nabout it and can do its thing. Note that in reality, after you have queried for the ",(0,i.jsx)(n.code,{children:"$post"})," you would check if ",(0,i.jsx)(n.code,{children:"$post"}),"\nis actually a real post. Because if it is an empty return value then you should throw an exception earlier without\nneeding to check against the permission."]}),"\n",(0,i.jsx)(n.h2,{id:"a-real-world-application-part-3---admins-can-delete-everything",children:"A Real World Application Part 3 - Admins can delete everything"}),"\n",(0,i.jsx)(n.p,{children:"Often, you want users with a specific role to be able to have full access to everything. For instance, admins could\ndelete all the posts, even if they don't own it."}),"\n",(0,i.jsxs)(n.p,{children:["However, with the previous assertion, even if the admin has the permission ",(0,i.jsx)(n.code,{children:"deletePost"}),", it won't work because\nthe assertion will evaluate to false."]}),"\n",(0,i.jsxs)(n.p,{children:["Actually, the answer is quite simple: deleting my own posts and deleting others' posts should be treated like\ntwo different permissions (it makes sense if you think about it). Therefore, admins will have the permission\n",(0,i.jsx)(n.code,{children:"deleteOthersPost"})," (as well as the permission ",(0,i.jsx)(n.code,{children:"deletePost"}),", because admin could write posts, too)."]}),"\n",(0,i.jsx)(n.p,{children:"The assertion must therefore be modified like this:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"namespace Your\\Namespace;\n\nuse LmcRbacMvc\\Assertion\\AssertionInterface;\nuse LmcRbacMvc\\Service\\AuthorizationService;\n\nclass MustBeAuthorAssertion implements AssertionInterface\n{\n /**\n * Check if this assertion is true\n *\n * @param AuthorizationService $authorization\n * @param mixed $context\n *\n * @return bool\n */\n public function assert(AuthorizationService $authorization, $context = null)\n {\n if ($authorization->getIdentity() === $context->getAuthor()) {\n return true;\n }\n\n return $authorization->isGranted('deleteOthersPost');\n }\n}\n"})}),"\n",(0,i.jsx)(n.h2,{id:"a-real-world-application-part-4---checking-permissions-in-the-view",children:"A Real World Application Part 4 - Checking permissions in the view"}),"\n",(0,i.jsxs)(n.p,{children:["If some part of the view needs to be protected, you can use the shipped ",(0,i.jsx)(n.code,{children:"isGranted"})," view helper."]}),"\n",(0,i.jsxs)(n.p,{children:["For example, lets's say that only users with the permissions ",(0,i.jsx)(n.code,{children:"post.manage"})," will have a menu item to acces\nthe adminsitration panel :"]}),"\n",(0,i.jsx)(n.p,{children:"In your template post-index.phtml"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'<ul class="nav">\n <li><a href="/">Home</a></li>\n <li><a href="/posts/list">View posts</a></li>\n <?php if ($this->isGranted(\'post.manage\'): ?>\n <li><a href="/posts/admin">Manage posts</a></li>\n <?php endif ?>\n</ul>\n'})}),"\n",(0,i.jsxs)(n.p,{children:["You can even protect your menu item regarding a role, by using the ",(0,i.jsx)(n.code,{children:"hasRole"})," view helper :"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'<ul class="nav">\n <li><a href="/">Home</a></li>\n <li><a href="/posts/list">View posts</a></li>\n <?php if ($this->hasRole(\'admin\'): ?>\n <li><a href="/posts/admin">Manage posts</a></li>\n <?php endif ?>\n</ul>\n'})}),"\n",(0,i.jsxs)(n.p,{children:["In this last example, the menu item will be hidden for users who don't have the ",(0,i.jsx)(n.code,{children:"admin"})," role."]}),"\n",(0,i.jsx)(n.h2,{id:"using-lmcrbacmvc-with-doctrine-orm",children:"Using LmcRbacMvc with Doctrine ORM"}),"\n",(0,i.jsxs)(n.p,{children:["First your User entity class must implement ",(0,i.jsx)(n.code,{children:"LmcRbacMvc\\Identity\\IdentityInterface"})," :"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'use LmccUser\\Entity\\User as LmcUserEntity;\nuse LmcRbacMvc\\Identity\\IdentityInterface;\nuse Doctrine\\ORM\\Mapping as ORM;\nuse Doctrine\\Common\\Collections\\ArrayCollection;\nuse Doctrine\\Common\\Collections\\Collection;\n\n/**\n * @ORM\\Entity\n * @ORM\\Table(name="user")\n */\nclass User extends LmcUserEntity implements IdentityInterface\n{\n /**\n * @var Collection\n * @ORM\\ManyToMany(targetEntity="HierarchicalRole")\n */\n private $roles;\n\n public function __construct()\n {\n $this->roles = new ArrayCollection();\n }\n\n /**\n * {@inheritDoc}\n */\n public function getRoles()\n {\n return $this->roles->toArray();\n }\n\n /**\n * Set the list of roles\n * @param Collection $roles\n */\n public function setRoles(Collection $roles)\n {\n $this->roles->clear();\n foreach ($roles as $role) {\n $this->roles[] = $role;\n }\n }\n\n /**\n * Add one role to roles list\n * @param \\Rbac\\Role\\RoleInterface $role\n */\n public function addRole(RoleInterface $role)\n {\n $this->roles[] = $role;\n }\n}\n'})}),"\n",(0,i.jsxs)(n.p,{children:["For this example we will use a more complex situation by using ",(0,i.jsx)(n.code,{children:"Rbac\\Role\\HierarchicalRoleInterface"})," so the second step is to create HierarchicalRole entity class"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'class HierarchicalRole implements HierarchicalRoleInterface\n{\n /**\n * @var HierarchicalRoleInterface[]|\\Doctrine\\Common\\Collections\\Collection\n *\n * @ORM\\ManyToMany(targetEntity="HierarchicalRole")\n */\n protected $children;\n\n /**\n * @var PermissionInterface[]|\\Doctrine\\Common\\Collections\\Collection\n *\n * @ORM\\ManyToMany(targetEntity="Permission", indexBy="name", fetch="EAGER", cascade={"persist"})\n */\n protected $permissions;\n\n /**\n * Init the Doctrine collection\n */\n public function __construct()\n {\n $this->children = new ArrayCollection();\n $this->permissions = new ArrayCollection();\n }\n\n /**\n * {@inheritDoc}\n */\n public function addChild(HierarchicalRoleInterface $child)\n {\n $this->children[] = $child;\n }\n\n /*\n * Set the list of permission\n * @param Collection $permissions\n */\n public function setPermissions(Collection $permissions)\n {\n $this->permissions->clear();\n foreach ($permissions as $permission) {\n $this->permissions[] = $permission;\n }\n }\n\n /**\n * {@inheritDoc}\n */\n public function addPermission($permission)\n {\n if (is_string($permission)) {\n $permission = new Permission($permission);\n }\n\n $this->permissions[(string) $permission] = $permission;\n }\n\n /**\n * {@inheritDoc}\n */\n public function hasPermission($permission)\n {\n // This can be a performance problem if your role has a lot of permissions. Please refer\n // to the cookbook to an elegant way to solve this issue\n\n return isset($this->permissions[(string) $permission]);\n }\n\n /**\n * {@inheritDoc}\n */\n public function getChildren()\n {\n return $this->children->toArray();\n }\n\n /**\n * {@inheritDoc}\n */\n public function hasChildren()\n {\n return !$this->children->isEmpty();\n }\n}\n'})}),"\n",(0,i.jsx)(n.p,{children:"And the last step is to create a Permission entity class which is a very simple entity class. You don't have to do specific things!"}),"\n",(0,i.jsxs)(n.p,{children:["You can find all entity examples in this folder : ",(0,i.jsx)(n.a,{href:"https://github.com/LM-Commons/LmcRbacMvc/tree/master/data",children:"Example"})]}),"\n",(0,i.jsxs)(n.p,{children:["You need one more configuration step. Indeed, how can the RoleProvider retrieve your role and permissions? For this you need to configure ",(0,i.jsx)(n.code,{children:"LmcRbacMvc\\Role\\ObjectRepositoryRoleProvider"})," in your ",(0,i.jsx)(n.code,{children:"lmc_rbac.global.php"})," file :"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:" /**\n * Configuration for role provider\n */\n 'role_provider' => [\n 'LmcRbacMvc\\Role\\ObjectRepositoryRoleProvider' => [\n 'object_manager' => 'doctrine.entitymanager.orm_default', // alias for doctrine ObjectManager\n 'class_name' => 'User\\Entity\\HierarchicalRole', // FQCN for your role entity class\n 'role_name_property' => 'name', // Name to show\n ],\n ],\n"})}),"\n",(0,i.jsx)(n.p,{children:"Using DoctrineORM with LmcRbacMvc is very simple. You need to be aware of performance where there is a lot of permissions for roles."}),"\n",(0,i.jsx)(n.h2,{id:"how-to-deal-with-roles-with-lot-of-permissions",children:"How to deal with roles with lot of permissions?"}),"\n",(0,i.jsxs)(n.p,{children:["In very complex applications, your roles may have dozens of permissions. In the [/data/FlatRole.php.dist] entity\nwe provide, we configure the permissions association so that whenever a role is loaded, all of its permissions are also\nloaded in one query (notice the ",(0,i.jsx)(n.code,{children:'fetch="EAGER"'}),"):"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'/**\n * @ORM\\ManyToMany(targetEntity="Permission", indexBy="name", fetch="EAGER")\n */\nprotected $permissions;\n'})}),"\n",(0,i.jsxs)(n.p,{children:["The ",(0,i.jsx)(n.code,{children:"hasPermission"})," method is therefore really simple:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"public function hasPermission($permission)\n{\n return isset($this->permissions[(string) $permission]);\n}\n"})}),"\n",(0,i.jsx)(n.p,{children:"However, with a lot of permissions, this method will quickly kill your database. What you can do is modfiy the Doctrine\nmapping so that the collection is not actually loaded:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:'/**\n * @ORM\\ManyToMany(targetEntity="Permission", indexBy="name", fetch="LAZY")\n */\nprotected $permissions;\n'})}),"\n",(0,i.jsxs)(n.p,{children:["Then, modify the ",(0,i.jsx)(n.code,{children:"hasPermission"})," method to use the Criteria API. The Criteria API is a Doctrine 2.2+ API that allows\nyour application to efficiently filter a collection without loading the whole collection:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"use Doctrine\\Common\\Collections\\Criteria;\n\npublic function hasPermission($permission)\n{\n $criteria = Criteria::create()->where(Criteria::expr()->eq('name', (string) $permission));\n $result = $this->permissions->matching($criteria);\n\n return count($result) > 0;\n}\n"})}),"\n",(0,i.jsxs)(n.blockquote,{children:["\n",(0,i.jsx)(n.p,{children:"NOTE: This is only supported starting from Doctrine ORM 2.5!"}),"\n"]}),"\n",(0,i.jsx)(n.h2,{id:"using-lmcrbacmvc-and-zf2-assetic",children:"Using LmcRbacMvc and ZF2 Assetic"}),"\n",(0,i.jsxs)(n.p,{children:["To use ",(0,i.jsx)(n.a,{href:"https://github.com/widmogrod/zf2-assetic-module",children:"Assetic"})," with LmcRbacMvc guards, you should modify your\n",(0,i.jsx)(n.code,{children:"module.config.php"})," using the following configuration:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'assetic_configuration' => [\n 'acceptableErrors' => [\n \\LmcRbacMvc\\Guard\\GuardInterface::GUARD_UNAUTHORIZED\n ]\n ]\n];\n"})}),"\n",(0,i.jsx)(n.h2,{id:"using-lmcrbacmvc-and-lmcuser",children:"Using LmcRbacMvc and LmcUser"}),"\n",(0,i.jsx)(n.p,{children:"To use the authentication service from LmcUser, just add the following alias in your application.config.php:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'service_manager' => [\n 'aliases' => [\n 'Laminas\\Authentication\\AuthenticationService' => 'lmcuser_auth_service'\n ]\n ]\n];\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Finally add the LmcUser routes to your ",(0,i.jsx)(n.code,{children:"guards"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'guards' => [\n 'LmcRbac\\Guard\\RouteGuard' => [\n 'lmcuser/login' => ['guest'],\n 'lmcuser/register' => ['guest'], // required if registration is enabled\n 'lmcuser*' => ['user'] // includes logout, changepassword and changeemail\n ]\n ]\n ]\n];\n"})})]})}function d(e={}){const{wrapper:n}={...(0,o.R)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(h,{...e})}):h(e)}},8453:(e,n,t)=>{t.d(n,{R:()=>r,x:()=>c});var i=t(6540);const o={},s=i.createContext(o);function r(e){const n=i.useContext(s);return i.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function c(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:r(e.components),i.createElement(s.Provider,{value:n},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/c4f5d8e4.00ce20a8.js b/assets/js/c4f5d8e4.00ce20a8.js new file mode 100644 index 00000000..9fe6ec23 --- /dev/null +++ b/assets/js/c4f5d8e4.00ce20a8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[634],{6467:(e,s,n)=>{n.r(s),n.d(s,{default:()=>u});var t=n(4164),r=(n(8774),n(4586)),a=n(781),i=n(1107);const o={features:"features_t9lD",featureSvg:"featureSvg_GfXr"};var c=n(4848);c.Fragment;function l(){return(0,c.jsx)("section",{className:o.features,children:(0,c.jsx)("div",{className:"container"})})}const h={heroBanner:"heroBanner_qdFl",buttons:"buttons_AeoN"};function d(){const{siteConfig:e}=(0,r.A)();return(0,c.jsx)("header",{className:(0,t.A)("hero hero--primary",h.heroBanner),children:(0,c.jsxs)("div",{className:"container",children:[(0,c.jsx)(i.A,{as:"h1",className:"hero__title",children:e.title}),(0,c.jsx)("p",{className:"hero__subtitle",children:e.tagline}),(0,c.jsx)("p",{children:"Part of the LM-Commons series of community developed packages for Laminas"})]})})}function u(){const{siteConfig:e}=(0,r.A)();return(0,c.jsxs)(a.A,{title:`Hello from ${e.title}`,description:"Description will go into a meta tag in <head />",children:[(0,c.jsx)(d,{}),(0,c.jsx)("main",{children:(0,c.jsx)(l,{})})]})}}}]); \ No newline at end of file diff --git a/assets/js/ccc49370.c41daa2c.js b/assets/js/ccc49370.c41daa2c.js new file mode 100644 index 00000000..c01339c3 --- /dev/null +++ b/assets/js/ccc49370.c41daa2c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[249],{4029:(e,n,t)=>{t.r(n),t.d(n,{default:()=>p});t(6540);var i=t(4164),a=t(1003),s=t(7559),o=t(7131),l=t(6535),r=t(8258),c=t(1312),d=t(9022),u=t(4848);function m(e){const{nextItem:n,prevItem:t}=e;return(0,u.jsxs)("nav",{className:"pagination-nav docusaurus-mt-lg","aria-label":(0,c.T)({id:"theme.blog.post.paginator.navAriaLabel",message:"Blog post page navigation",description:"The ARIA label for the blog posts pagination"}),children:[t&&(0,u.jsx)(d.A,{...t,subLabel:(0,u.jsx)(c.A,{id:"theme.blog.post.paginator.newerPost",description:"The blog post button label to navigate to the newer/previous post",children:"Newer Post"})}),n&&(0,u.jsx)(d.A,{...n,subLabel:(0,u.jsx)(c.A,{id:"theme.blog.post.paginator.olderPost",description:"The blog post button label to navigate to the older/next post",children:"Older Post"}),isNext:!0})]})}function g(){const{assets:e,metadata:n}=(0,o.e)(),{title:t,description:i,date:s,tags:l,authors:r,frontMatter:c}=n,{keywords:d}=c,m=e.image??c.image;return(0,u.jsxs)(a.be,{title:t,description:i,keywords:d,image:m,children:[(0,u.jsx)("meta",{property:"og:type",content:"article"}),(0,u.jsx)("meta",{property:"article:published_time",content:s}),r.some((e=>e.url))&&(0,u.jsx)("meta",{property:"article:author",content:r.map((e=>e.url)).filter(Boolean).join(",")}),l.length>0&&(0,u.jsx)("meta",{property:"article:tag",content:l.map((e=>e.label)).join(",")})]})}var h=t(7763),f=t(996);function v(e){let{sidebar:n,children:t}=e;const{metadata:i,toc:a}=(0,o.e)(),{nextItem:s,prevItem:c,frontMatter:d,unlisted:g}=i,{hide_table_of_contents:v,toc_min_heading_level:p,toc_max_heading_level:x}=d;return(0,u.jsxs)(l.A,{sidebar:n,toc:!v&&a.length>0?(0,u.jsx)(h.A,{toc:a,minHeadingLevel:p,maxHeadingLevel:x}):void 0,children:[g&&(0,u.jsx)(f.A,{}),(0,u.jsx)(r.A,{children:t}),(s||c)&&(0,u.jsx)(m,{nextItem:s,prevItem:c})]})}function p(e){const n=e.content;return(0,u.jsx)(o.i,{content:e.content,isBlogPostPage:!0,children:(0,u.jsxs)(a.e3,{className:(0,i.A)(s.G.wrapper.blogPages,s.G.page.blogPostPage),children:[(0,u.jsx)(g,{}),(0,u.jsx)(v,{sidebar:e.sidebar,children:(0,u.jsx)(n,{})})]})})}},7763:(e,n,t)=>{t.d(n,{A:()=>c});t(6540);var i=t(4164),a=t(5195);const s={tableOfContents:"tableOfContents_bqdL",docItemContainer:"docItemContainer_F8PC"};var o=t(4848);const l="table-of-contents__link toc-highlight",r="table-of-contents__link--active";function c(e){let{className:n,...t}=e;return(0,o.jsx)("div",{className:(0,i.A)(s.tableOfContents,"thin-scrollbar",n),children:(0,o.jsx)(a.A,{...t,linkClassName:l,linkActiveClassName:r})})}},5195:(e,n,t)=>{t.d(n,{A:()=>f});var i=t(6540),a=t(6342);function s(e){const n=e.map((e=>({...e,parentIndex:-1,children:[]}))),t=Array(7).fill(-1);n.forEach(((e,n)=>{const i=t.slice(2,e.level);e.parentIndex=Math.max(...i),t[e.level]=n}));const i=[];return n.forEach((e=>{const{parentIndex:t,...a}=e;t>=0?n[t].children.push(a):i.push(a)})),i}function o(e){let{toc:n,minHeadingLevel:t,maxHeadingLevel:i}=e;return n.flatMap((e=>{const n=o({toc:e.children,minHeadingLevel:t,maxHeadingLevel:i});return function(e){return e.level>=t&&e.level<=i}(e)?[{...e,children:n}]:n}))}function l(e){const n=e.getBoundingClientRect();return n.top===n.bottom?l(e.parentNode):n}function r(e,n){let{anchorTopOffset:t}=n;const i=e.find((e=>l(e).top>=t));if(i){return function(e){return e.top>0&&e.bottom<window.innerHeight/2}(l(i))?i:e[e.indexOf(i)-1]??null}return e[e.length-1]??null}function c(){const e=(0,i.useRef)(0),{navbar:{hideOnScroll:n}}=(0,a.p)();return(0,i.useEffect)((()=>{e.current=n?0:document.querySelector(".navbar").clientHeight}),[n]),e}function d(e){const n=(0,i.useRef)(void 0),t=c();(0,i.useEffect)((()=>{if(!e)return()=>{};const{linkClassName:i,linkActiveClassName:a,minHeadingLevel:s,maxHeadingLevel:o}=e;function l(){const e=function(e){return Array.from(document.getElementsByClassName(e))}(i),l=function(e){let{minHeadingLevel:n,maxHeadingLevel:t}=e;const i=[];for(let a=n;a<=t;a+=1)i.push(`h${a}.anchor`);return Array.from(document.querySelectorAll(i.join()))}({minHeadingLevel:s,maxHeadingLevel:o}),c=r(l,{anchorTopOffset:t.current}),d=e.find((e=>c&&c.id===function(e){return decodeURIComponent(e.href.substring(e.href.indexOf("#")+1))}(e)));e.forEach((e=>{!function(e,t){t?(n.current&&n.current!==e&&n.current.classList.remove(a),e.classList.add(a),n.current=e):e.classList.remove(a)}(e,e===d)}))}return document.addEventListener("scroll",l),document.addEventListener("resize",l),l(),()=>{document.removeEventListener("scroll",l),document.removeEventListener("resize",l)}}),[e,t])}var u=t(8774),m=t(4848);function g(e){let{toc:n,className:t,linkClassName:i,isChild:a}=e;return n.length?(0,m.jsx)("ul",{className:a?void 0:t,children:n.map((e=>(0,m.jsxs)("li",{children:[(0,m.jsx)(u.A,{to:`#${e.id}`,className:i??void 0,dangerouslySetInnerHTML:{__html:e.value}}),(0,m.jsx)(g,{isChild:!0,toc:e.children,className:t,linkClassName:i})]},e.id)))}):null}const h=i.memo(g);function f(e){let{toc:n,className:t="table-of-contents table-of-contents__left-border",linkClassName:l="table-of-contents__link",linkActiveClassName:r,minHeadingLevel:c,maxHeadingLevel:u,...g}=e;const f=(0,a.p)(),v=c??f.tableOfContents.minHeadingLevel,p=u??f.tableOfContents.maxHeadingLevel,x=function(e){let{toc:n,minHeadingLevel:t,maxHeadingLevel:a}=e;return(0,i.useMemo)((()=>o({toc:s(n),minHeadingLevel:t,maxHeadingLevel:a})),[n,t,a])}({toc:n,minHeadingLevel:v,maxHeadingLevel:p});return d((0,i.useMemo)((()=>{if(l&&r)return{linkClassName:l,linkActiveClassName:r,minHeadingLevel:v,maxHeadingLevel:p}}),[l,r,v,p])),(0,m.jsx)(h,{toc:x,className:t,linkClassName:l,...g})}},996:(e,n,t)=>{t.d(n,{A:()=>g});t(6540);var i=t(4164),a=t(1312),s=t(5260),o=t(4848);function l(){return(0,o.jsx)(a.A,{id:"theme.unlistedContent.title",description:"The unlisted content banner title",children:"Unlisted page"})}function r(){return(0,o.jsx)(a.A,{id:"theme.unlistedContent.message",description:"The unlisted content banner message",children:"This page is unlisted. Search engines will not index it, and only users having a direct link can access it."})}function c(){return(0,o.jsx)(s.A,{children:(0,o.jsx)("meta",{name:"robots",content:"noindex, nofollow"})})}var d=t(7559),u=t(7293);function m(e){let{className:n}=e;return(0,o.jsx)(u.A,{type:"caution",title:(0,o.jsx)(l,{}),className:(0,i.A)(n,d.G.common.unlistedBanner),children:(0,o.jsx)(r,{})})}function g(e){return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(c,{}),(0,o.jsx)(m,{...e})]})}}}]); \ No newline at end of file diff --git a/assets/js/d9e16301.5f01f94d.js b/assets/js/d9e16301.5f01f94d.js new file mode 100644 index 00000000..7eba4d63 --- /dev/null +++ b/assets/js/d9e16301.5f01f94d.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[416],{5989:(s,t,e)=>{e.r(t),e.d(t,{assets:()=>a,contentTitle:()=>c,default:()=>l,frontMatter:()=>i,metadata:()=>r,toc:()=>m});var o=e(4848),n=e(8453);const i={sidebar_position:20,title:"Support"},c=void 0,r={id:"support",title:"Support",description:"- File issues at https://github.com/LM-Commons/LmcRbacMvc/issues.",source:"@site/docs/support.md",sourceDirName:".",slug:"/support",permalink:"/lmc-rbac-mvc/docs/support",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/support.md",tags:[],version:"current",sidebarPosition:20,frontMatter:{sidebar_position:20,title:"Support"},sidebar:"tutorialSidebar",previous:{title:"Cookbook",permalink:"/lmc-rbac-mvc/docs/cookbook"}},a={},m=[{value:"Notices and Disclaimers",id:"notices-and-disclaimers",level:5}];function d(s){const t={a:"a",h5:"h5",li:"li",p:"p",ul:"ul",...(0,n.R)(),...s.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsxs)(t.ul,{children:["\n",(0,o.jsxs)(t.li,{children:["File issues at ",(0,o.jsx)(t.a,{href:"https://github.com/LM-Commons/LmcRbacMvc/issues",children:"https://github.com/LM-Commons/LmcRbacMvc/issues"}),"."]}),"\n",(0,o.jsxs)(t.li,{children:["Ask questions in the ",(0,o.jsx)(t.a,{href:"https://gitter.im/LM-Commons/community",children:"LM-Commons Gitter"})," chat."]}),"\n"]}),"\n",(0,o.jsx)(t.h5,{id:"notices-and-disclaimers",children:"Notices and Disclaimers"}),"\n",(0,o.jsx)(t.p,{children:"This is not an official Laminas Project organization."}),"\n",(0,o.jsx)(t.p,{children:"Issues and questions related to the Laminas MVC and components\nshould be addressed to the Laminas Project organisation."}),"\n",(0,o.jsx)(t.p,{children:"Laminas is a trademark of the Laminas Project, a Series of LF Projects, LLC."})]})}function l(s={}){const{wrapper:t}={...(0,n.R)(),...s.components};return t?(0,o.jsx)(t,{...s,children:(0,o.jsx)(d,{...s})}):d(s)}},8453:(s,t,e)=>{e.d(t,{R:()=>c,x:()=>r});var o=e(6540);const n={},i=o.createContext(n);function c(s){const t=o.useContext(i);return o.useMemo((function(){return"function"==typeof s?s(t):{...t,...s}}),[t,s])}function r(s){let t;return t=s.disableParentContext?"function"==typeof s.components?s.components(n):s.components||n:c(s.components),o.createElement(i.Provider,{value:t},s.children)}}}]); \ No newline at end of file diff --git a/assets/js/e252ba15.a3bf887f.js b/assets/js/e252ba15.a3bf887f.js new file mode 100644 index 00000000..a90ec0a4 --- /dev/null +++ b/assets/js/e252ba15.a3bf887f.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[182],{2945:s=>{s.exports=JSON.parse('{"name":"docusaurus-plugin-content-blog","id":"default"}')}}]); \ No newline at end of file diff --git a/assets/js/ee696d42.cd7325d3.js b/assets/js/ee696d42.cd7325d3.js new file mode 100644 index 00000000..df893918 --- /dev/null +++ b/assets/js/ee696d42.cd7325d3.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[18],{3531:(e,t,o)=>{o.r(t),o.d(t,{assets:()=>i,contentTitle:()=>s,default:()=>u,frontMatter:()=>r,metadata:()=>a,toc:()=>m});var n=o(4848),c=o(8453);const r={slug:"welcome",title:"Welcome",authors:["ericr"],tags:["laminas","PHP"]},s=void 0,a={permalink:"/lmc-rbac-mvc/blog/welcome",editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/blog/2022-08-02-welcome.md",source:"@site/blog/2022-08-02-welcome.md",title:"Welcome",description:"Welcome to the new documentation website for the LM-Commons organization.",date:"2022-08-02T00:00:00.000Z",formattedDate:"August 2, 2022",tags:[{label:"laminas",permalink:"/lmc-rbac-mvc/blog/tags/laminas"},{label:"PHP",permalink:"/lmc-rbac-mvc/blog/tags/php"}],readingTime:.155,hasTruncateMarker:!1,authors:[{name:"Eric Richer",title:"LM-Commons Administrator",url:"https://github.com/visto9259",imageURL:"https://github.com/visto9259.png",key:"ericr"}],frontMatter:{slug:"welcome",title:"Welcome",authors:["ericr"],tags:["laminas","PHP"]},unlisted:!1,prevItem:{title:"New documentation",permalink:"/lmc-rbac-mvc/blog/new-documentation"}},i={authorsImageUrls:[void 0]},m=[];function l(e){const t={p:"p",...(0,c.R)(),...e.components};return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(t.p,{children:"Welcome to the new documentation website for the LM-Commons organization."}),"\n",(0,n.jsx)(t.p,{children:"This site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages."})]})}function u(e={}){const{wrapper:t}={...(0,c.R)(),...e.components};return t?(0,n.jsx)(t,{...e,children:(0,n.jsx)(l,{...e})}):l(e)}},8453:(e,t,o)=>{o.d(t,{R:()=>s,x:()=>a});var n=o(6540);const c={},r=n.createContext(c);function s(e){const t=n.useContext(r);return n.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function a(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(c):e.components||c:s(e.components),n.createElement(r.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/f01051f8.63b05f50.js b/assets/js/f01051f8.63b05f50.js new file mode 100644 index 00000000..905ea9a3 --- /dev/null +++ b/assets/js/f01051f8.63b05f50.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[822],{5409:s=>{s.exports=JSON.parse('{"label":"PHP","permalink":"/lmc-rbac-mvc/blog/tags/php","allTagsPath":"/lmc-rbac-mvc/blog/tags","count":2,"unlisted":false}')}}]); \ No newline at end of file diff --git a/assets/js/f6027d55.272e2251.js b/assets/js/f6027d55.272e2251.js new file mode 100644 index 00000000..729fa924 --- /dev/null +++ b/assets/js/f6027d55.272e2251.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[246],{9477:(e,t,n)=>{n.r(t),n.d(t,{assets:()=>o,contentTitle:()=>a,default:()=>p,frontMatter:()=>i,metadata:()=>c,toc:()=>l});var r=n(4848),s=n(8453);const i={sidebar_position:6},a="Strategies",c={id:"strategies",title:"Strategies",description:"In this section, you will learn:",source:"@site/docs/strategies.md",sourceDirName:".",slug:"/strategies",permalink:"/lmc-rbac-mvc/docs/strategies",draft:!1,unlisted:!1,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/docs/strategies.md",tags:[],version:"current",sidebarPosition:6,frontMatter:{sidebar_position:6},sidebar:"tutorialSidebar",previous:{title:"Guards",permalink:"/lmc-rbac-mvc/docs/guards"},next:{title:"Using the Authorization Service",permalink:"/lmc-rbac-mvc/docs/using-the-authorization-service"}},o={},l=[{value:"What are strategies?",id:"what-are-strategies",level:2},{value:"Built-in strategies",id:"built-in-strategies",level:2},{value:"RedirectStrategy",id:"redirectstrategy",level:3},{value:"UnauthorizedStrategy",id:"unauthorizedstrategy",level:3},{value:"Creating custom strategies",id:"creating-custom-strategies",level:2}];function d(e){const t={a:"a",blockquote:"blockquote",code:"code",h1:"h1",h2:"h2",h3:"h3",li:"li",p:"p",pre:"pre",ul:"ul",...(0,s.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(t.h1,{id:"strategies",children:"Strategies"}),"\n",(0,r.jsx)(t.p,{children:"In this section, you will learn:"}),"\n",(0,r.jsxs)(t.ul,{children:["\n",(0,r.jsx)(t.li,{children:"What strategies are"}),"\n",(0,r.jsx)(t.li,{children:"How to use built-in strategies"}),"\n",(0,r.jsx)(t.li,{children:"How to create custom strategies"}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"what-are-strategies",children:"What are strategies?"}),"\n",(0,r.jsxs)(t.p,{children:["A strategy is an object that listens to the ",(0,r.jsx)(t.code,{children:"MvcEvent::EVENT_DISPATCH_ERROR"})," event. It is used to describe what\nhappens when access to a resource is unauthorized by LmcRbacMvc."]}),"\n",(0,r.jsxs)(t.p,{children:["LmcRbacMvc strategies all check if an ",(0,r.jsx)(t.code,{children:"LmcRbacMvc\\Exception\\UnauthorizedExceptionInterface"})," has been thrown."]}),"\n",(0,r.jsxs)(t.p,{children:["By default, LmcRbacMvc does not register any strategy for you. The best place to register it is in your ",(0,r.jsx)(t.code,{children:"onBootstrap"}),"\nmethod of the ",(0,r.jsx)(t.code,{children:"Module.php"})," class:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"public function onBootstrap(MvcEvent $e)\n{\n $app = $e->getApplication();\n $sm = $app->getServiceManager();\n $em = $app->getEventManager();\n \n $listener = $sm->get(\\LmcRbacMvc\\View\\Strategy\\UnauthorizedStrategy::class);\n $listener->attach($em);\n}\n"})}),"\n",(0,r.jsx)(t.h2,{id:"built-in-strategies",children:"Built-in strategies"}),"\n",(0,r.jsxs)(t.p,{children:["LmcRbacMvc comes with two built-in strategies: ",(0,r.jsx)(t.code,{children:"RedirectStrategy"})," and ",(0,r.jsx)(t.code,{children:"UnauthorizedStrategy"}),"."]}),"\n",(0,r.jsx)(t.h3,{id:"redirectstrategy",children:"RedirectStrategy"}),"\n",(0,r.jsx)(t.p,{children:"This strategy allows your application to redirect any unauthorized request to another route by optionally appending the previous\nURL as a query parameter."}),"\n",(0,r.jsx)(t.p,{children:"To register it, copy-paste this code into your Module.php class:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"public function onBootstrap(MvcEvent $e)\n{\n $app = $e->getApplication();\n $sm = $app->getServiceManager();\n $em = $app->getEventManager();\n \n $listener = $sm->get(\\LmcRbacMvc\\View\\Strategy\\RedirectStrategy::class);\n $listener->attach($em);\n}\n"})}),"\n",(0,r.jsxs)(t.p,{children:["You can configure the strategy using the ",(0,r.jsx)(t.code,{children:"redirect_strategy"})," subkey:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'redirect_strategy' => [\n 'redirect_when_connected' => true,\n 'redirect_to_route_connected' => 'home',\n 'redirect_to_route_disconnected' => 'login',\n 'append_previous_uri' => true,\n 'previous_uri_query_key' => 'redirectTo'\n ],\n ]\n];\n"})}),"\n",(0,r.jsxs)(t.p,{children:["If users try to access an unauthorized resource (eg.: ",(0,r.jsx)(t.a,{href:"http://www.example.com/delete",children:"http://www.example.com/delete"}),'), they will be\nredirected to the "login" route if is not connected and to the "home" route otherwise (it must exist in your route configuration\nof course) with the previous URL appended : ',(0,r.jsx)(t.a,{href:"http://www.example.com/login?redirectTo=http://www.example.com/delete",children:"http://www.example.com/login?redirectTo=http://www.example.com/delete"})]}),"\n",(0,r.jsxs)(t.p,{children:["You can prevent redirection when a user is connected (i.e. so that the user gets a 403 page) by setting ",(0,r.jsx)(t.code,{children:"redirect_when_connected"})," to ",(0,r.jsx)(t.code,{children:"false"}),"."]}),"\n",(0,r.jsx)(t.h3,{id:"unauthorizedstrategy",children:"UnauthorizedStrategy"}),"\n",(0,r.jsx)(t.p,{children:"This strategy allows your application to render a template on any unauthorized request."}),"\n",(0,r.jsx)(t.p,{children:"To register it, copy-paste this code into your Module.php class:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"public function onBootstrap(MvcEvent $e)\n{\n $app = $e->getApplication();\n $sm = $app->getServiceManager();\n $em = $app->getEventManager();\n \n $listener = $sm->get(\\LmcRbacMvc\\View\\Strategy\\UnauthorizedStrategy::class);\n $listener->attach($em);\n}\n"})}),"\n",(0,r.jsxs)(t.p,{children:["You can configure the strategy using the ",(0,r.jsx)(t.code,{children:"unauthorized_strategy"})," subkey:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"return [\n 'lmc_rbac' => [\n 'unauthorized_strategy' => [\n 'template' => 'error/custom-403'\n ],\n ]\n];\n"})}),"\n",(0,r.jsxs)(t.blockquote,{children:["\n",(0,r.jsxs)(t.p,{children:["By default, LmcRbacMvc uses a template called ",(0,r.jsx)(t.code,{children:"error/403"}),"."]}),"\n"]}),"\n",(0,r.jsx)(t.h2,{id:"creating-custom-strategies",children:"Creating custom strategies"}),"\n",(0,r.jsxs)(t.p,{children:["Creating a custom strategy is rather easy. Let's say we want to create a strategy that integrates with\nthe ",(0,r.jsx)(t.a,{href:"https://github.com/laminas-api-tools/api-tools-api-problem",children:"ApiProblem"})," Laminas Api Tools module:"]}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"namespace Application\\View\\Strategy;\n\nuse Laminas\\Http\\Response as HttpResponse;\nuse Laminas\\Mvc\\MvcEvent;\nuse Laminas\\ApiTools\\ApiProblem\\ApiProblem;\nuse Laminas\\ApiTools\\ApiProblem\\ApiProblemResponse;\nuse LmcRbacMvc\\View\\Strategy\\AbstractStrategy;\nuse LmcRbacMvc\\Exception\\UnauthorizedExceptionInterface;\n\nclass ApiProblemStrategy extends AbstractStrategy\n{\n public function onError(MvcEvent $event)\n {\n // Do nothing if no error or if response is not HTTP response\n if (!($exception = $event->getParam('exception') instanceof UnauthorizedExceptionInterface)\n || ($result = $event->getResult() instanceof HttpResponse)\n || !($response = $event->getResponse() instanceof HttpResponse)\n ) {\n return;\n }\n\n return new ApiProblemResponse(new ApiProblem($exception->getMessage()));\n }\n}\n"})}),"\n",(0,r.jsx)(t.p,{children:"Register your strategy:"}),"\n",(0,r.jsx)(t.pre,{children:(0,r.jsx)(t.code,{className:"language-php",children:"public function onBootstrap(EventInterface $e)\n{\n $e->getTarget()\n ->getEventManager()\n ->attach(new ApiProblemStrategy());\n}\n"})})]})}function p(e={}){const{wrapper:t}={...(0,s.R)(),...e.components};return t?(0,r.jsx)(t,{...e,children:(0,r.jsx)(d,{...e})}):d(e)}},8453:(e,t,n)=>{n.d(t,{R:()=>a,x:()=>c});var r=n(6540);const s={},i=r.createContext(s);function a(e){const t=r.useContext(i);return r.useMemo((function(){return"function"==typeof e?e(t):{...t,...e}}),[t,e])}function c(e){let t;return t=e.disableParentContext?"function"==typeof e.components?e.components(s):e.components||s:a(e.components),r.createElement(i.Provider,{value:t},e.children)}}}]); \ No newline at end of file diff --git a/assets/js/main.452d3fbd.js b/assets/js/main.452d3fbd.js new file mode 100644 index 00000000..f9169164 --- /dev/null +++ b/assets/js/main.452d3fbd.js @@ -0,0 +1,2 @@ +/*! For license information please see main.452d3fbd.js.LICENSE.txt */ +(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[792],{8328:(e,t,n)=>{"use strict";n.d(t,{A:()=>p});n(6540);var r=n(3259),a=n.n(r),o=n(4054);const i={"01a85c17":[()=>Promise.all([n.e(869),n.e(209)]).then(n.bind(n,9158)),"@theme/BlogTagsListPage",9158],"02def4a7":[()=>n.e(770).then(n.t.bind(n,1966,19)),"/home/runner/work/LmcRbacMvc/LmcRbacMvc/docs/.docusaurus/docusaurus-plugin-content-docs/default/plugin-route-context-module-100.json",1966],"0e384e19":[()=>n.e(976).then(n.bind(n,1512)),"@site/docs/intro.md",1512],17896441:[()=>Promise.all([n.e(869),n.e(533),n.e(401)]).then(n.bind(n,5022)),"@theme/DocItem",5022],"1c81fc78":[()=>n.e(982).then(n.t.bind(n,953,19)),"~blog/default/lmc-rbac-mvc-blog-tags-laminas-d34-list.json",953],"1f391b9e":[()=>Promise.all([n.e(869),n.e(533),n.e(61)]).then(n.bind(n,7973)),"@theme/MDXPage",7973],"22465cd7":[()=>n.e(166).then(n.bind(n,5823)),"@site/docs/role-providers.md",5823],"266b150c":[()=>n.e(346).then(n.t.bind(n,5614,19)),"~blog/default/lmc-rbac-mvc-blog-6bc.json",5614],"378f56c0":[()=>n.e(639).then(n.bind(n,6060)),"@site/blog/2024-02-22-New-documentation.md?truncated=true",6060],"393be207":[()=>n.e(134).then(n.bind(n,6602)),"@site/src/pages/markdown-page.md",6602],"3b8c55ea":[()=>n.e(803).then(n.bind(n,3668)),"@site/docs/installation.md",3668],"4a900e2d":[()=>n.e(82).then(n.t.bind(n,2130,19)),"~blog/default/lmc-rbac-mvc-blog-tags-laminas-d34.json",2130],"4b8919da":[()=>n.e(223).then(n.t.bind(n,6624,19)),"~blog/default/lmc-rbac-mvc-blog-tags-php-2de-list.json",6624],"4e6224b1":[()=>n.e(107).then(n.bind(n,2618)),"@site/docs/using-the-authorization-service.md",2618],"5b3ddbaa":[()=>n.e(75).then(n.t.bind(n,5126,19)),"~blog/default/lmc-rbac-mvc-blog-archive-958.json",5126],"5b72c13b":[()=>n.e(698).then(n.t.bind(n,4061,19)),"/home/runner/work/LmcRbacMvc/LmcRbacMvc/docs/.docusaurus/docusaurus-plugin-content-pages/default/plugin-route-context-module-100.json",4061],"5e95c892":[()=>n.e(647).then(n.bind(n,7121)),"@theme/DocsRoot",7121],"5e9f5e1a":[()=>Promise.resolve().then(n.bind(n,4784)),"@generated/docusaurus.config",4784],"6875c492":[()=>Promise.all([n.e(869),n.e(533),n.e(747),n.e(813)]).then(n.bind(n,3069)),"@theme/BlogTagsPostsPage",3069],"72e14192":[()=>n.e(814).then(n.bind(n,3744)),"@site/docs/quick-start.md",3744],"809dac3e":[()=>n.e(804).then(n.bind(n,4e3)),"@site/blog/2024-02-22-New-documentation.md",4e3],"814f3328":[()=>n.e(472).then(n.t.bind(n,5513,19)),"~blog/default/blog-post-list-prop-default.json",5513],"8b5d4ccc":[()=>n.e(604).then(n.bind(n,82)),"@site/docs/guards.md",82],"935f2afb":[()=>n.e(581).then(n.t.bind(n,5610,19)),"~docs/default/version-current-metadata-prop-751.json",5610],"93a10038":[()=>n.e(828).then(n.t.bind(n,4738,19)),"~blog/default/lmc-rbac-mvc-blog-tags-lmcrbacmvc-c90.json",4738],"983b303c":[()=>n.e(660).then(n.t.bind(n,4009,19)),"~blog/default/lmc-rbac-mvc-blog-tags-lmcrbacmvc-c90-list.json",4009],"9e4087bc":[()=>n.e(711).then(n.bind(n,9331)),"@theme/BlogArchivePage",9331],a6185ec0:[()=>n.e(270).then(n.t.bind(n,3808,19)),"~blog/default/lmc-rbac-mvc-blog-tags-tags-f25.json",3808],a6aa9e1f:[()=>Promise.all([n.e(869),n.e(533),n.e(747),n.e(643)]).then(n.bind(n,7785)),"@theme/BlogListPage",7785],a7bd4aaa:[()=>n.e(98).then(n.bind(n,4532)),"@theme/DocVersionRoot",4532],a94703ab:[()=>Promise.all([n.e(869),n.e(48)]).then(n.bind(n,2559)),"@theme/DocRoot",2559],b5cb822e:[()=>n.e(390).then(n.bind(n,2957)),"@site/blog/2022-08-02-welcome.md?truncated=true",2957],b9207088:[()=>n.e(571).then(n.bind(n,8849)),"@site/docs/cookbook.md",8849],c4f5d8e4:[()=>Promise.all([n.e(869),n.e(634)]).then(n.bind(n,6467)),"@site/src/pages/index.js",6467],ccc49370:[()=>Promise.all([n.e(869),n.e(533),n.e(747),n.e(249)]).then(n.bind(n,4029)),"@theme/BlogPostPage",4029],d9e16301:[()=>n.e(416).then(n.bind(n,5989)),"@site/docs/support.md",5989],e252ba15:[()=>n.e(182).then(n.t.bind(n,2945,19)),"/home/runner/work/LmcRbacMvc/LmcRbacMvc/docs/.docusaurus/docusaurus-plugin-content-blog/default/plugin-route-context-module-100.json",2945],ee696d42:[()=>n.e(18).then(n.bind(n,3531)),"@site/blog/2022-08-02-welcome.md",3531],f01051f8:[()=>n.e(822).then(n.t.bind(n,5409,19)),"~blog/default/lmc-rbac-mvc-blog-tags-php-2de.json",5409],f6027d55:[()=>n.e(246).then(n.bind(n,9477)),"@site/docs/strategies.md",9477]};var l=n(4848);function s(e){let{error:t,retry:n,pastDelay:r}=e;return t?(0,l.jsxs)("div",{style:{textAlign:"center",color:"#fff",backgroundColor:"#fa383e",borderColor:"#fa383e",borderStyle:"solid",borderRadius:"0.25rem",borderWidth:"1px",boxSizing:"border-box",display:"block",padding:"1rem",flex:"0 0 50%",marginLeft:"25%",marginRight:"25%",marginTop:"5rem",maxWidth:"50%",width:"100%"},children:[(0,l.jsx)("p",{children:String(t)}),(0,l.jsx)("div",{children:(0,l.jsx)("button",{type:"button",onClick:n,children:"Retry"})})]}):r?(0,l.jsx)("div",{style:{display:"flex",justifyContent:"center",alignItems:"center",height:"100vh"},children:(0,l.jsx)("svg",{id:"loader",style:{width:128,height:110,position:"absolute",top:"calc(100vh - 64%)"},viewBox:"0 0 45 45",xmlns:"http://www.w3.org/2000/svg",stroke:"#61dafb",children:(0,l.jsxs)("g",{fill:"none",fillRule:"evenodd",transform:"translate(1 1)",strokeWidth:"2",children:[(0,l.jsxs)("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0",children:[(0,l.jsx)("animate",{attributeName:"r",begin:"1.5s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-opacity",begin:"1.5s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-width",begin:"1.5s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})]}),(0,l.jsxs)("circle",{cx:"22",cy:"22",r:"6",strokeOpacity:"0",children:[(0,l.jsx)("animate",{attributeName:"r",begin:"3s",dur:"3s",values:"6;22",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-opacity",begin:"3s",dur:"3s",values:"1;0",calcMode:"linear",repeatCount:"indefinite"}),(0,l.jsx)("animate",{attributeName:"stroke-width",begin:"3s",dur:"3s",values:"2;0",calcMode:"linear",repeatCount:"indefinite"})]}),(0,l.jsx)("circle",{cx:"22",cy:"22",r:"8",children:(0,l.jsx)("animate",{attributeName:"r",begin:"0s",dur:"1.5s",values:"6;1;2;3;4;5;6",calcMode:"linear",repeatCount:"indefinite"})})]})})}):null}var c=n(6921),u=n(3102);function d(e,t){if("*"===e)return a()({loading:s,loader:()=>n.e(237).then(n.bind(n,2237)),modules:["@theme/NotFound"],webpack:()=>[2237],render(e,t){const n=e.default;return(0,l.jsx)(u.W,{value:{plugin:{name:"native",id:"default"}},children:(0,l.jsx)(n,{...t})})}});const r=o[`${e}-${t}`],d={},p=[],f=[],m=(0,c.A)(r);return Object.entries(m).forEach((e=>{let[t,n]=e;const r=i[n];r&&(d[t]=r[0],p.push(r[1]),f.push(r[2]))})),a().Map({loading:s,loader:d,modules:p,webpack:()=>f,render(t,n){const a=JSON.parse(JSON.stringify(r));Object.entries(t).forEach((t=>{let[n,r]=t;const o=r.default;if(!o)throw new Error(`The page component at ${e} doesn't have a default export. This makes it impossible to render anything. Consider default-exporting a React component.`);"object"!=typeof o&&"function"!=typeof o||Object.keys(r).filter((e=>"default"!==e)).forEach((e=>{o[e]=r[e]}));let i=a;const l=n.split(".");l.slice(0,-1).forEach((e=>{i=i[e]})),i[l[l.length-1]]=o}));const o=a.__comp;delete a.__comp;const i=a.__context;return delete a.__context,(0,l.jsx)(u.W,{value:i,children:(0,l.jsx)(o,{...a,...n})})}})}const p=[{path:"/lmc-rbac-mvc/blog",component:d("/lmc-rbac-mvc/blog","a51"),exact:!0},{path:"/lmc-rbac-mvc/blog/archive",component:d("/lmc-rbac-mvc/blog/archive","da6"),exact:!0},{path:"/lmc-rbac-mvc/blog/new-documentation",component:d("/lmc-rbac-mvc/blog/new-documentation","af1"),exact:!0},{path:"/lmc-rbac-mvc/blog/tags",component:d("/lmc-rbac-mvc/blog/tags","fdd"),exact:!0},{path:"/lmc-rbac-mvc/blog/tags/laminas",component:d("/lmc-rbac-mvc/blog/tags/laminas","94d"),exact:!0},{path:"/lmc-rbac-mvc/blog/tags/lmcrbacmvc",component:d("/lmc-rbac-mvc/blog/tags/lmcrbacmvc","780"),exact:!0},{path:"/lmc-rbac-mvc/blog/tags/php",component:d("/lmc-rbac-mvc/blog/tags/php","3f3"),exact:!0},{path:"/lmc-rbac-mvc/blog/welcome",component:d("/lmc-rbac-mvc/blog/welcome","c6b"),exact:!0},{path:"/lmc-rbac-mvc/markdown-page",component:d("/lmc-rbac-mvc/markdown-page","7e3"),exact:!0},{path:"/lmc-rbac-mvc/docs",component:d("/lmc-rbac-mvc/docs","1d1"),routes:[{path:"/lmc-rbac-mvc/docs",component:d("/lmc-rbac-mvc/docs","c16"),routes:[{path:"/lmc-rbac-mvc/docs",component:d("/lmc-rbac-mvc/docs","ad5"),routes:[{path:"/lmc-rbac-mvc/docs/cookbook",component:d("/lmc-rbac-mvc/docs/cookbook","d11"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmc-rbac-mvc/docs/guards",component:d("/lmc-rbac-mvc/docs/guards","dce"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmc-rbac-mvc/docs/installation",component:d("/lmc-rbac-mvc/docs/installation","655"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmc-rbac-mvc/docs/intro",component:d("/lmc-rbac-mvc/docs/intro","82a"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmc-rbac-mvc/docs/quick-start",component:d("/lmc-rbac-mvc/docs/quick-start","359"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmc-rbac-mvc/docs/role-providers",component:d("/lmc-rbac-mvc/docs/role-providers","082"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmc-rbac-mvc/docs/strategies",component:d("/lmc-rbac-mvc/docs/strategies","a09"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmc-rbac-mvc/docs/support",component:d("/lmc-rbac-mvc/docs/support","dfd"),exact:!0,sidebar:"tutorialSidebar"},{path:"/lmc-rbac-mvc/docs/using-the-authorization-service",component:d("/lmc-rbac-mvc/docs/using-the-authorization-service","4f1"),exact:!0,sidebar:"tutorialSidebar"}]}]}]},{path:"/lmc-rbac-mvc/",component:d("/lmc-rbac-mvc/","849"),exact:!0},{path:"*",component:d("*")}]},6125:(e,t,n)=>{"use strict";n.d(t,{o:()=>o,x:()=>i});var r=n(6540),a=n(4848);const o=r.createContext(!1);function i(e){let{children:t}=e;const[n,i]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{i(!0)}),[]),(0,a.jsx)(o.Provider,{value:n,children:t})}},8536:(e,t,n)=>{"use strict";var r=n(6540),a=n(5338),o=n(4625),i=n(545),l=n(8193);const s=[n(119),n(6134),n(6294),n(1043)];var c=n(8328),u=n(6347),d=n(2831),p=n(4848);function f(e){let{children:t}=e;return(0,p.jsx)(p.Fragment,{children:t})}var m=n(5260),g=n(4586),h=n(6025),y=n(6342),b=n(1003),v=n(2131),w=n(4090),k=n(2967),x=n(440),S=n(1463);function E(){const{i18n:{currentLocale:e,defaultLocale:t,localeConfigs:n}}=(0,g.A)(),r=(0,v.o)(),a=n[e].htmlLang,o=e=>e.replace("-","_");return(0,p.jsxs)(m.A,{children:[Object.entries(n).map((e=>{let[t,{htmlLang:n}]=e;return(0,p.jsx)("link",{rel:"alternate",href:r.createUrl({locale:t,fullyQualified:!0}),hrefLang:n},t)})),(0,p.jsx)("link",{rel:"alternate",href:r.createUrl({locale:t,fullyQualified:!0}),hrefLang:"x-default"}),(0,p.jsx)("meta",{property:"og:locale",content:o(a)}),Object.values(n).filter((e=>a!==e.htmlLang)).map((e=>(0,p.jsx)("meta",{property:"og:locale:alternate",content:o(e.htmlLang)},`meta-og-${e.htmlLang}`)))]})}function C(e){let{permalink:t}=e;const{siteConfig:{url:n}}=(0,g.A)(),r=function(){const{siteConfig:{url:e,baseUrl:t,trailingSlash:n}}=(0,g.A)(),{pathname:r}=(0,u.zy)();return e+(0,x.applyTrailingSlash)((0,h.A)(r),{trailingSlash:n,baseUrl:t})}(),a=t?`${n}${t}`:r;return(0,p.jsxs)(m.A,{children:[(0,p.jsx)("meta",{property:"og:url",content:a}),(0,p.jsx)("link",{rel:"canonical",href:a})]})}function _(){const{i18n:{currentLocale:e}}=(0,g.A)(),{metadata:t,image:n}=(0,y.p)();return(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)(m.A,{children:[(0,p.jsx)("meta",{name:"twitter:card",content:"summary_large_image"}),(0,p.jsx)("body",{className:w.w})]}),n&&(0,p.jsx)(b.be,{image:n}),(0,p.jsx)(C,{}),(0,p.jsx)(E,{}),(0,p.jsx)(S.A,{tag:k.Cy,locale:e}),(0,p.jsx)(m.A,{children:t.map(((e,t)=>(0,p.jsx)("meta",{...e},t)))})]})}const A=new Map;function T(e){if(A.has(e.pathname))return{...e,pathname:A.get(e.pathname)};if((0,d.u)(c.A,e.pathname).some((e=>{let{route:t}=e;return!0===t.exact})))return A.set(e.pathname,e.pathname),e;const t=e.pathname.trim().replace(/(?:\/index)?\.html$/,"")||"/";return A.set(e.pathname,t),{...e,pathname:t}}var j=n(6125),L=n(6988),N=n(205);function R(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];const a=s.map((t=>{const r=t.default?.[e]??t[e];return r?.(...n)}));return()=>a.forEach((e=>e?.()))}const P=function(e){let{children:t,location:n,previousLocation:r}=e;return(0,N.A)((()=>{r!==n&&(!function(e){let{location:t,previousLocation:n}=e;if(!n)return;const r=t.pathname===n.pathname,a=t.hash===n.hash,o=t.search===n.search;if(r&&a&&!o)return;const{hash:i}=t;if(i){const e=decodeURIComponent(i.substring(1)),t=document.getElementById(e);t?.scrollIntoView()}else window.scrollTo(0,0)}({location:n,previousLocation:r}),R("onRouteDidUpdate",{previousLocation:r,location:n}))}),[r,n]),t};function O(e){const t=Array.from(new Set([e,decodeURI(e)])).map((e=>(0,d.u)(c.A,e))).flat();return Promise.all(t.map((e=>e.route.component.preload?.())))}class D extends r.Component{previousLocation;routeUpdateCleanupCb;constructor(e){super(e),this.previousLocation=null,this.routeUpdateCleanupCb=l.A.canUseDOM?R("onRouteUpdate",{previousLocation:null,location:this.props.location}):()=>{},this.state={nextRouteHasLoaded:!0}}shouldComponentUpdate(e,t){if(e.location===this.props.location)return t.nextRouteHasLoaded;const n=e.location;return this.previousLocation=this.props.location,this.setState({nextRouteHasLoaded:!1}),this.routeUpdateCleanupCb=R("onRouteUpdate",{previousLocation:this.previousLocation,location:n}),O(n.pathname).then((()=>{this.routeUpdateCleanupCb(),this.setState({nextRouteHasLoaded:!0})})).catch((e=>{console.warn(e),window.location.reload()})),!1}render(){const{children:e,location:t}=this.props;return(0,p.jsx)(P,{previousLocation:this.previousLocation,location:t,children:(0,p.jsx)(u.qh,{location:t,render:()=>e})})}}const M=D,I="__docusaurus-base-url-issue-banner-container",F="__docusaurus-base-url-issue-banner",z="__docusaurus-base-url-issue-banner-suggestion-container";function B(e){return`\ndocument.addEventListener('DOMContentLoaded', function maybeInsertBanner() {\n var shouldInsert = typeof window['docusaurus'] === 'undefined';\n shouldInsert && insertBanner();\n});\n\nfunction insertBanner() {\n var bannerContainer = document.createElement('div');\n bannerContainer.id = '${I}';\n var bannerHtml = ${JSON.stringify(function(e){return`\n<div id="${F}" style="border: thick solid red; background-color: rgb(255, 230, 179); margin: 20px; padding: 20px; font-size: 20px;">\n <p style="font-weight: bold; font-size: 30px;">Your Docusaurus site did not load properly.</p>\n <p>A very common reason is a wrong site <a href="https://docusaurus.io/docs/docusaurus.config.js/#baseUrl" style="font-weight: bold;">baseUrl configuration</a>.</p>\n <p>Current configured baseUrl = <span style="font-weight: bold; color: red;">${e}</span> ${"/"===e?" (default value)":""}</p>\n <p>We suggest trying baseUrl = <span id="${z}" style="font-weight: bold; color: green;"></span></p>\n</div>\n`}(e)).replace(/</g,"\\<")};\n bannerContainer.innerHTML = bannerHtml;\n document.body.prepend(bannerContainer);\n var suggestionContainer = document.getElementById('${z}');\n var actualHomePagePath = window.location.pathname;\n var suggestedBaseUrl = actualHomePagePath.substr(-1) === '/'\n ? actualHomePagePath\n : actualHomePagePath + '/';\n suggestionContainer.innerHTML = suggestedBaseUrl;\n}\n`}function $(){const{siteConfig:{baseUrl:e}}=(0,g.A)();return(0,p.jsx)(p.Fragment,{children:!l.A.canUseDOM&&(0,p.jsx)(m.A,{children:(0,p.jsx)("script",{children:B(e)})})})}function U(){const{siteConfig:{baseUrl:e,baseUrlIssueBanner:t}}=(0,g.A)(),{pathname:n}=(0,u.zy)();return t&&n===e?(0,p.jsx)($,{}):null}function q(){const{siteConfig:{favicon:e,title:t,noIndex:n},i18n:{currentLocale:r,localeConfigs:a}}=(0,g.A)(),o=(0,h.A)(e),{htmlLang:i,direction:l}=a[r];return(0,p.jsxs)(m.A,{children:[(0,p.jsx)("html",{lang:i,dir:l}),(0,p.jsx)("title",{children:t}),(0,p.jsx)("meta",{property:"og:title",content:t}),(0,p.jsx)("meta",{name:"viewport",content:"width=device-width, initial-scale=1.0"}),n&&(0,p.jsx)("meta",{name:"robots",content:"noindex, nofollow"}),e&&(0,p.jsx)("link",{rel:"icon",href:o})]})}var H=n(7489),G=n(2303);function V(){const e=(0,G.A)();return(0,p.jsx)(m.A,{children:(0,p.jsx)("html",{"data-has-hydrated":e})})}function W(){const e=(0,d.v)(c.A),t=(0,u.zy)();return(0,p.jsx)(H.A,{children:(0,p.jsx)(L.l,{children:(0,p.jsxs)(j.x,{children:[(0,p.jsxs)(f,{children:[(0,p.jsx)(q,{}),(0,p.jsx)(_,{}),(0,p.jsx)(U,{}),(0,p.jsx)(M,{location:T(t),children:e})]}),(0,p.jsx)(V,{})]})})})}var Q=n(4054);const K=function(e){try{return document.createElement("link").relList.supports(e)}catch{return!1}}("prefetch")?function(e){return new Promise(((t,n)=>{if("undefined"==typeof document)return void n();const r=document.createElement("link");r.setAttribute("rel","prefetch"),r.setAttribute("href",e),r.onload=()=>t(),r.onerror=()=>n();const a=document.getElementsByTagName("head")[0]??document.getElementsByName("script")[0]?.parentNode;a?.appendChild(r)}))}:function(e){return new Promise(((t,n)=>{const r=new XMLHttpRequest;r.open("GET",e,!0),r.withCredentials=!0,r.onload=()=>{200===r.status?t():n()},r.send(null)}))};var Y=n(6921);const Z=new Set,X=new Set,J=()=>navigator.connection?.effectiveType.includes("2g")||navigator.connection?.saveData,ee={prefetch(e){if(!(e=>!J()&&!X.has(e)&&!Z.has(e))(e))return!1;Z.add(e);const t=(0,d.u)(c.A,e).flatMap((e=>{return t=e.route.path,Object.entries(Q).filter((e=>{let[n]=e;return n.replace(/-[^-]+$/,"")===t})).flatMap((e=>{let[,t]=e;return Object.values((0,Y.A)(t))}));var t}));return Promise.all(t.map((e=>{const t=n.gca(e);return t&&!t.includes("undefined")?K(t).catch((()=>{})):Promise.resolve()})))},preload:e=>!!(e=>!J()&&!X.has(e))(e)&&(X.add(e),O(e))},te=Object.freeze(ee),ne=Boolean(!0);if(l.A.canUseDOM){window.docusaurus=te;const e=document.getElementById("__docusaurus"),t=(0,p.jsx)(i.vd,{children:(0,p.jsx)(o.Kd,{children:(0,p.jsx)(W,{})})}),n=(e,t)=>{console.error("Docusaurus React Root onRecoverableError:",e,t)},l=()=>{if(ne)r.startTransition((()=>{a.hydrateRoot(e,t,{onRecoverableError:n})}));else{const o=a.createRoot(e,{onRecoverableError:n});r.startTransition((()=>{o.render(t)}))}};O(window.location.pathname).then(l)}},6988:(e,t,n)=>{"use strict";n.d(t,{o:()=>d,l:()=>p});var r=n(6540),a=n(4784);const o=JSON.parse('{"docusaurus-plugin-content-docs":{"default":{"path":"/lmc-rbac-mvc/docs","versions":[{"name":"current","label":"Next","isLast":true,"path":"/lmc-rbac-mvc/docs","mainDocId":"intro","docs":[{"id":"cookbook","path":"/lmc-rbac-mvc/docs/cookbook","sidebar":"tutorialSidebar"},{"id":"guards","path":"/lmc-rbac-mvc/docs/guards","sidebar":"tutorialSidebar"},{"id":"installation","path":"/lmc-rbac-mvc/docs/installation","sidebar":"tutorialSidebar"},{"id":"intro","path":"/lmc-rbac-mvc/docs/intro","sidebar":"tutorialSidebar"},{"id":"quick-start","path":"/lmc-rbac-mvc/docs/quick-start","sidebar":"tutorialSidebar"},{"id":"role-providers","path":"/lmc-rbac-mvc/docs/role-providers","sidebar":"tutorialSidebar"},{"id":"strategies","path":"/lmc-rbac-mvc/docs/strategies","sidebar":"tutorialSidebar"},{"id":"support","path":"/lmc-rbac-mvc/docs/support","sidebar":"tutorialSidebar"},{"id":"using-the-authorization-service","path":"/lmc-rbac-mvc/docs/using-the-authorization-service","sidebar":"tutorialSidebar"}],"draftIds":[],"sidebars":{"tutorialSidebar":{"link":{"path":"/lmc-rbac-mvc/docs/intro","label":"intro"}}}}],"breadcrumbs":true}}}'),i=JSON.parse('{"defaultLocale":"en","locales":["en"],"path":"i18n","currentLocale":"en","localeConfigs":{"en":{"label":"English","direction":"ltr","htmlLang":"en","calendar":"gregory","path":"en"}}}');var l=n(2654);const s=JSON.parse('{"docusaurusVersion":"3.1.1","siteVersion":"0.0.0","pluginVersions":{"docusaurus-plugin-content-docs":{"type":"package","name":"@docusaurus/plugin-content-docs","version":"3.1.1"},"docusaurus-plugin-content-blog":{"type":"package","name":"@docusaurus/plugin-content-blog","version":"3.1.1"},"docusaurus-plugin-content-pages":{"type":"package","name":"@docusaurus/plugin-content-pages","version":"3.1.1"},"docusaurus-plugin-sitemap":{"type":"package","name":"@docusaurus/plugin-sitemap","version":"3.1.1"},"docusaurus-theme-classic":{"type":"package","name":"@docusaurus/theme-classic","version":"3.1.1"}}}');var c=n(4848);const u={siteConfig:a.default,siteMetadata:s,globalData:o,i18n:i,codeTranslations:l},d=r.createContext(u);function p(e){let{children:t}=e;return(0,c.jsx)(d.Provider,{value:u,children:t})}},7489:(e,t,n)=>{"use strict";n.d(t,{A:()=>f});var r=n(6540),a=n(8193),o=n(5260),i=n(440),l=n(781),s=n(4848);function c(e){let{error:t,tryAgain:n}=e;return(0,s.jsxs)("div",{style:{display:"flex",flexDirection:"column",justifyContent:"center",alignItems:"flex-start",minHeight:"100vh",width:"100%",maxWidth:"80ch",fontSize:"20px",margin:"0 auto",padding:"1rem"},children:[(0,s.jsx)("h1",{style:{fontSize:"3rem"},children:"This page crashed"}),(0,s.jsx)("button",{type:"button",onClick:n,style:{margin:"1rem 0",fontSize:"2rem",cursor:"pointer",borderRadius:20,padding:"1rem"},children:"Try again"}),(0,s.jsx)(u,{error:t})]})}function u(e){let{error:t}=e;const n=(0,i.getErrorCausalChain)(t).map((e=>e.message)).join("\n\nCause:\n");return(0,s.jsx)("p",{style:{whiteSpace:"pre-wrap"},children:n})}function d(e){let{error:t,tryAgain:n}=e;return(0,s.jsxs)(f,{fallback:()=>(0,s.jsx)(c,{error:t,tryAgain:n}),children:[(0,s.jsx)(o.A,{children:(0,s.jsx)("title",{children:"Page Error"})}),(0,s.jsx)(l.A,{children:(0,s.jsx)(c,{error:t,tryAgain:n})})]})}const p=e=>(0,s.jsx)(d,{...e});class f extends r.Component{constructor(e){super(e),this.state={error:null}}componentDidCatch(e){a.A.canUseDOM&&this.setState({error:e})}render(){const{children:e}=this.props,{error:t}=this.state;if(t){const e={error:t,tryAgain:()=>this.setState({error:null})};return(this.props.fallback??p)(e)}return e??null}}},8193:(e,t,n)=>{"use strict";n.d(t,{A:()=>a});const r="undefined"!=typeof window&&"document"in window&&"createElement"in window.document,a={canUseDOM:r,canUseEventListeners:r&&("addEventListener"in window||"attachEvent"in window),canUseIntersectionObserver:r&&"IntersectionObserver"in window,canUseViewport:r&&"screen"in window}},5260:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});n(6540);var r=n(545),a=n(4848);function o(e){return(0,a.jsx)(r.mg,{...e})}},8774:(e,t,n)=>{"use strict";n.d(t,{A:()=>f});var r=n(6540),a=n(4625),o=n(440),i=n(4586),l=n(6654),s=n(8193),c=n(3427),u=n(6025),d=n(4848);function p(e,t){let{isNavLink:n,to:p,href:f,activeClassName:m,isActive:g,"data-noBrokenLinkCheck":h,autoAddBaseUrl:y=!0,...b}=e;const{siteConfig:{trailingSlash:v,baseUrl:w}}=(0,i.A)(),{withBaseUrl:k}=(0,u.h)(),x=(0,c.A)(),S=(0,r.useRef)(null);(0,r.useImperativeHandle)(t,(()=>S.current));const E=p||f;const C=(0,l.A)(E),_=E?.replace("pathname://","");let A=void 0!==_?(T=_,y&&(e=>e.startsWith("/"))(T)?k(T):T):void 0;var T;A&&C&&(A=(0,o.applyTrailingSlash)(A,{trailingSlash:v,baseUrl:w}));const j=(0,r.useRef)(!1),L=n?a.k2:a.N_,N=s.A.canUseIntersectionObserver,R=(0,r.useRef)(),P=()=>{j.current||null==A||(window.docusaurus.preload(A),j.current=!0)};(0,r.useEffect)((()=>(!N&&C&&null!=A&&window.docusaurus.prefetch(A),()=>{N&&R.current&&R.current.disconnect()})),[R,A,N,C]);const O=A?.startsWith("#")??!1,D=!b.target||"_self"===b.target,M=!A||!C||!D||O;return h||!O&&M||x.collectLink(A),b.id&&x.collectAnchor(b.id),M?(0,d.jsx)("a",{ref:S,href:A,...E&&!C&&{target:"_blank",rel:"noopener noreferrer"},...b}):(0,d.jsx)(L,{...b,onMouseEnter:P,onTouchStart:P,innerRef:e=>{S.current=e,N&&e&&C&&(R.current=new window.IntersectionObserver((t=>{t.forEach((t=>{e===t.target&&(t.isIntersecting||t.intersectionRatio>0)&&(R.current.unobserve(e),R.current.disconnect(),null!=A&&window.docusaurus.prefetch(A))}))})),R.current.observe(e))},to:A,...n&&{isActive:g,activeClassName:m}})}const f=r.forwardRef(p)},418:(e,t,n)=>{"use strict";n.d(t,{A:()=>r});const r=()=>null},1312:(e,t,n)=>{"use strict";n.d(t,{A:()=>c,T:()=>s});var r=n(6540),a=n(4848);function o(e,t){const n=e.split(/(\{\w+\})/).map(((e,n)=>{if(n%2==1){const n=t?.[e.slice(1,-1)];if(void 0!==n)return n}return e}));return n.some((e=>(0,r.isValidElement)(e)))?n.map(((e,t)=>(0,r.isValidElement)(e)?r.cloneElement(e,{key:t}):e)).filter((e=>""!==e)):n.join("")}var i=n(2654);function l(e){let{id:t,message:n}=e;if(void 0===t&&void 0===n)throw new Error("Docusaurus translation declarations must have at least a translation id or a default translation message");return i[t??n]??n??t}function s(e,t){let{message:n,id:r}=e;return o(l({message:n,id:r}),t)}function c(e){let{children:t,id:n,values:r}=e;if(t&&"string"!=typeof t)throw console.warn("Illegal <Translate> children",t),new Error("The Docusaurus <Translate> component only accept simple string values");const i=l({message:t,id:n});return(0,a.jsx)(a.Fragment,{children:o(i,r)})}},7065:(e,t,n)=>{"use strict";n.d(t,{W:()=>r});const r="default"},6654:(e,t,n)=>{"use strict";function r(e){return/^(?:\w*:|\/\/)/.test(e)}function a(e){return void 0!==e&&!r(e)}n.d(t,{A:()=>a,z:()=>r})},6025:(e,t,n)=>{"use strict";n.d(t,{A:()=>l,h:()=>i});var r=n(6540),a=n(4586),o=n(6654);function i(){const{siteConfig:{baseUrl:e,url:t}}=(0,a.A)(),n=(0,r.useCallback)(((n,r)=>function(e,t,n,r){let{forcePrependBaseUrl:a=!1,absolute:i=!1}=void 0===r?{}:r;if(!n||n.startsWith("#")||(0,o.z)(n))return n;if(a)return t+n.replace(/^\//,"");if(n===t.replace(/\/$/,""))return t;const l=n.startsWith(t)?n:t+n.replace(/^\//,"");return i?e+l:l}(t,e,n,r)),[t,e]);return{withBaseUrl:n}}function l(e,t){void 0===t&&(t={});const{withBaseUrl:n}=i();return n(e,t)}},3427:(e,t,n)=>{"use strict";n.d(t,{A:()=>i});var r=n(6540);n(4848);const a=r.createContext({collectAnchor:()=>{},collectLink:()=>{}}),o=()=>(0,r.useContext)(a);function i(){return o()}},4586:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(6988);function o(){return(0,r.useContext)(a.o)}},2303:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=n(6540),a=n(6125);function o(){return(0,r.useContext)(a.o)}},205:(e,t,n)=>{"use strict";n.d(t,{A:()=>a});var r=n(6540);const a=n(8193).A.canUseDOM?r.useLayoutEffect:r.useEffect},6921:(e,t,n)=>{"use strict";n.d(t,{A:()=>a});const r=e=>"object"==typeof e&&!!e&&Object.keys(e).length>0;function a(e){const t={};return function e(n,a){Object.entries(n).forEach((n=>{let[o,i]=n;const l=a?`${a}.${o}`:o;r(i)?e(i,l):t[l]=i}))}(e),t}},3102:(e,t,n)=>{"use strict";n.d(t,{W:()=>i,o:()=>o});var r=n(6540),a=n(4848);const o=r.createContext(null);function i(e){let{children:t,value:n}=e;const i=r.useContext(o),l=(0,r.useMemo)((()=>function(e){let{parent:t,value:n}=e;if(!t){if(!n)throw new Error("Unexpected: no Docusaurus route context found");if(!("plugin"in n))throw new Error("Unexpected: Docusaurus topmost route context has no `plugin` attribute");return n}const r={...t.data,...n?.data};return{plugin:t.plugin,data:r}}({parent:i,value:n})),[i,n]);return(0,a.jsx)(o.Provider,{value:l,children:t})}},4070:(e,t,n)=>{"use strict";n.d(t,{zK:()=>g,vT:()=>p,Gy:()=>u,HW:()=>h,ht:()=>d,r7:()=>m,jh:()=>f});var r=n(6347),a=n(4586),o=n(7065);function i(e,t){void 0===t&&(t={});const n=function(){const{globalData:e}=(0,a.A)();return e}()[e];if(!n&&t.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin.`);return n}const l=e=>e.versions.find((e=>e.isLast));function s(e,t){const n=function(e,t){const n=l(e);return[...e.versions.filter((e=>e!==n)),n].find((e=>!!(0,r.B6)(t,{path:e.path,exact:!1,strict:!1})))}(e,t),a=n?.docs.find((e=>!!(0,r.B6)(t,{path:e.path,exact:!0,strict:!1})));return{activeVersion:n,activeDoc:a,alternateDocVersions:a?function(t){const n={};return e.versions.forEach((e=>{e.docs.forEach((r=>{r.id===t&&(n[e.name]=r)}))})),n}(a.id):{}}}const c={},u=()=>i("docusaurus-plugin-content-docs")??c,d=e=>function(e,t,n){void 0===t&&(t=o.W),void 0===n&&(n={});const r=i(e),a=r?.[t];if(!a&&n.failfast)throw new Error(`Docusaurus plugin global data not found for "${e}" plugin with id "${t}".`);return a}("docusaurus-plugin-content-docs",e,{failfast:!0});function p(e){void 0===e&&(e={});const t=u(),{pathname:n}=(0,r.zy)();return function(e,t,n){void 0===n&&(n={});const a=Object.entries(e).sort(((e,t)=>t[1].path.localeCompare(e[1].path))).find((e=>{let[,n]=e;return!!(0,r.B6)(t,{path:n.path,exact:!1,strict:!1})})),o=a?{pluginId:a[0],pluginData:a[1]}:void 0;if(!o&&n.failfast)throw new Error(`Can't find active docs plugin for "${t}" pathname, while it was expected to be found. Maybe you tried to use a docs feature that can only be used on a docs-related page? Existing docs plugin paths are: ${Object.values(e).map((e=>e.path)).join(", ")}`);return o}(t,n,e)}function f(e){return d(e).versions}function m(e){const t=d(e);return l(t)}function g(e){const t=d(e),{pathname:n}=(0,r.zy)();return s(t,n)}function h(e){const t=d(e),{pathname:n}=(0,r.zy)();return function(e,t){const n=l(e);return{latestDocSuggestion:s(e,t).alternateDocVersions[n.name],latestVersionSuggestion:n}}(t,n)}},6294:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>o});var r=n(5947),a=n.n(r);a().configure({showSpinner:!1});const o={onRouteUpdate(e){let{location:t,previousLocation:n}=e;if(n&&t.pathname!==n.pathname){const e=window.setTimeout((()=>{a().start()}),200);return()=>window.clearTimeout(e)}},onRouteDidUpdate(){a().done()}}},6134:(e,t,n)=>{"use strict";n.r(t);var r=n(1765),a=n(4784);!function(e){const{themeConfig:{prism:t}}=a.default,{additionalLanguages:r}=t;globalThis.Prism=e,r.forEach((e=>{"php"===e&&n(9700),n(8692)(`./prism-${e}`)})),delete globalThis.Prism}(r.My)},1107:(e,t,n)=>{"use strict";n.d(t,{A:()=>u});n(6540);var r=n(4164),a=n(1312),o=n(6342),i=n(8774),l=n(3427);const s={anchorWithStickyNavbar:"anchorWithStickyNavbar_LWe7",anchorWithHideOnScrollNavbar:"anchorWithHideOnScrollNavbar_WYt5"};var c=n(4848);function u(e){let{as:t,id:n,...u}=e;const d=(0,l.A)(),{navbar:{hideOnScroll:p}}=(0,o.p)();if("h1"===t||!n)return(0,c.jsx)(t,{...u,id:void 0});d.collectAnchor(n);const f=(0,a.T)({id:"theme.common.headingLinkTitle",message:"Direct link to {heading}",description:"Title for link to heading"},{heading:"string"==typeof u.children?u.children:n});return(0,c.jsxs)(t,{...u,className:(0,r.A)("anchor",p?s.anchorWithHideOnScrollNavbar:s.anchorWithStickyNavbar,u.className),id:n,children:[u.children,(0,c.jsx)(i.A,{className:"hash-link",to:`#${n}`,"aria-label":f,title:f,children:"\u200b"})]})}},3186:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});n(6540);const r={iconExternalLink:"iconExternalLink_nPIU"};var a=n(4848);function o(e){let{width:t=13.5,height:n=13.5}=e;return(0,a.jsx)("svg",{width:t,height:n,"aria-hidden":"true",viewBox:"0 0 24 24",className:r.iconExternalLink,children:(0,a.jsx)("path",{fill:"currentColor",d:"M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"})})}},781:(e,t,n)=>{"use strict";n.d(t,{A:()=>ft});var r=n(6540),a=n(4164),o=n(7489),i=n(1003),l=n(6347),s=n(1312),c=n(5062),u=n(4848);const d="__docusaurus_skipToContent_fallback";function p(e){e.setAttribute("tabindex","-1"),e.focus(),e.removeAttribute("tabindex")}function f(){const e=(0,r.useRef)(null),{action:t}=(0,l.W6)(),n=(0,r.useCallback)((e=>{e.preventDefault();const t=document.querySelector("main:first-of-type")??document.getElementById(d);t&&p(t)}),[]);return(0,c.$)((n=>{let{location:r}=n;e.current&&!r.hash&&"PUSH"===t&&p(e.current)})),{containerRef:e,onClick:n}}const m=(0,s.T)({id:"theme.common.skipToMainContent",description:"The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation",message:"Skip to main content"});function g(e){const t=e.children??m,{containerRef:n,onClick:r}=f();return(0,u.jsx)("div",{ref:n,role:"region","aria-label":m,children:(0,u.jsx)("a",{...e,href:`#${d}`,onClick:r,children:t})})}var h=n(7559),y=n(4090);const b={skipToContent:"skipToContent_fXgn"};function v(){return(0,u.jsx)(g,{className:b.skipToContent})}var w=n(6342),k=n(5041);function x(e){let{width:t=21,height:n=21,color:r="currentColor",strokeWidth:a=1.2,className:o,...i}=e;return(0,u.jsx)("svg",{viewBox:"0 0 15 15",width:t,height:n,...i,children:(0,u.jsx)("g",{stroke:r,strokeWidth:a,children:(0,u.jsx)("path",{d:"M.75.75l13.5 13.5M14.25.75L.75 14.25"})})})}const S={closeButton:"closeButton_CVFx"};function E(e){return(0,u.jsx)("button",{type:"button","aria-label":(0,s.T)({id:"theme.AnnouncementBar.closeButtonAriaLabel",message:"Close",description:"The ARIA label for close button of announcement bar"}),...e,className:(0,a.A)("clean-btn close",S.closeButton,e.className),children:(0,u.jsx)(x,{width:14,height:14,strokeWidth:3.1})})}const C={content:"content_knG7"};function _(e){const{announcementBar:t}=(0,w.p)(),{content:n}=t;return(0,u.jsx)("div",{...e,className:(0,a.A)(C.content,e.className),dangerouslySetInnerHTML:{__html:n}})}const A={announcementBar:"announcementBar_mb4j",announcementBarPlaceholder:"announcementBarPlaceholder_vyr4",announcementBarClose:"announcementBarClose_gvF7",announcementBarContent:"announcementBarContent_xLdY"};function T(){const{announcementBar:e}=(0,w.p)(),{isActive:t,close:n}=(0,k.Mj)();if(!t)return null;const{backgroundColor:r,textColor:a,isCloseable:o}=e;return(0,u.jsxs)("div",{className:A.announcementBar,style:{backgroundColor:r,color:a},role:"banner",children:[o&&(0,u.jsx)("div",{className:A.announcementBarPlaceholder}),(0,u.jsx)(_,{className:A.announcementBarContent}),o&&(0,u.jsx)(E,{onClick:n,className:A.announcementBarClose})]})}var j=n(9876),L=n(3104);var N=n(9532),R=n(5600);const P=r.createContext(null);function O(e){let{children:t}=e;const n=function(){const e=(0,j.M)(),t=(0,R.YL)(),[n,a]=(0,r.useState)(!1),o=null!==t.component,i=(0,N.ZC)(o);return(0,r.useEffect)((()=>{o&&!i&&a(!0)}),[o,i]),(0,r.useEffect)((()=>{o?e.shown||a(!0):a(!1)}),[e.shown,o]),(0,r.useMemo)((()=>[n,a]),[n])}();return(0,u.jsx)(P.Provider,{value:n,children:t})}function D(e){if(e.component){const t=e.component;return(0,u.jsx)(t,{...e.props})}}function M(){const e=(0,r.useContext)(P);if(!e)throw new N.dV("NavbarSecondaryMenuDisplayProvider");const[t,n]=e,a=(0,r.useCallback)((()=>n(!1)),[n]),o=(0,R.YL)();return(0,r.useMemo)((()=>({shown:t,hide:a,content:D(o)})),[a,o,t])}function I(e){let{header:t,primaryMenu:n,secondaryMenu:r}=e;const{shown:o}=M();return(0,u.jsxs)("div",{className:"navbar-sidebar",children:[t,(0,u.jsxs)("div",{className:(0,a.A)("navbar-sidebar__items",{"navbar-sidebar__items--show-secondary":o}),children:[(0,u.jsx)("div",{className:"navbar-sidebar__item menu",children:n}),(0,u.jsx)("div",{className:"navbar-sidebar__item menu",children:r})]})]})}var F=n(5293),z=n(2303);function B(e){return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,u.jsx)("path",{fill:"currentColor",d:"M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"})})}function $(e){return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:24,height:24,...e,children:(0,u.jsx)("path",{fill:"currentColor",d:"M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"})})}const U={toggle:"toggle_vylO",toggleButton:"toggleButton_gllP",darkToggleIcon:"darkToggleIcon_wfgR",lightToggleIcon:"lightToggleIcon_pyhR",toggleButtonDisabled:"toggleButtonDisabled_aARS"};function q(e){let{className:t,buttonClassName:n,value:r,onChange:o}=e;const i=(0,z.A)(),l=(0,s.T)({message:"Switch between dark and light mode (currently {mode})",id:"theme.colorToggle.ariaLabel",description:"The ARIA label for the navbar color mode toggle"},{mode:"dark"===r?(0,s.T)({message:"dark mode",id:"theme.colorToggle.ariaLabel.mode.dark",description:"The name for the dark color mode"}):(0,s.T)({message:"light mode",id:"theme.colorToggle.ariaLabel.mode.light",description:"The name for the light color mode"})});return(0,u.jsx)("div",{className:(0,a.A)(U.toggle,t),children:(0,u.jsxs)("button",{className:(0,a.A)("clean-btn",U.toggleButton,!i&&U.toggleButtonDisabled,n),type:"button",onClick:()=>o("dark"===r?"light":"dark"),disabled:!i,title:l,"aria-label":l,"aria-live":"polite",children:[(0,u.jsx)(B,{className:(0,a.A)(U.toggleIcon,U.lightToggleIcon)}),(0,u.jsx)($,{className:(0,a.A)(U.toggleIcon,U.darkToggleIcon)})]})})}const H=r.memo(q),G={darkNavbarColorModeToggle:"darkNavbarColorModeToggle_X3D1"};function V(e){let{className:t}=e;const n=(0,w.p)().navbar.style,r=(0,w.p)().colorMode.disableSwitch,{colorMode:a,setColorMode:o}=(0,F.G)();return r?null:(0,u.jsx)(H,{className:t,buttonClassName:"dark"===n?G.darkNavbarColorModeToggle:void 0,value:a,onChange:o})}var W=n(3465);function Q(){return(0,u.jsx)(W.A,{className:"navbar__brand",imageClassName:"navbar__logo",titleClassName:"navbar__title text--truncate"})}function K(){const e=(0,j.M)();return(0,u.jsx)("button",{type:"button","aria-label":(0,s.T)({id:"theme.docs.sidebar.closeSidebarButtonAriaLabel",message:"Close navigation bar",description:"The ARIA label for close button of mobile sidebar"}),className:"clean-btn navbar-sidebar__close",onClick:()=>e.toggle(),children:(0,u.jsx)(x,{color:"var(--ifm-color-emphasis-600)"})})}function Y(){return(0,u.jsxs)("div",{className:"navbar-sidebar__brand",children:[(0,u.jsx)(Q,{}),(0,u.jsx)(V,{className:"margin-right--md"}),(0,u.jsx)(K,{})]})}var Z=n(8774),X=n(6025),J=n(6654);function ee(e,t){return void 0!==e&&void 0!==t&&new RegExp(e,"gi").test(t)}var te=n(3186);function ne(e){let{activeBasePath:t,activeBaseRegex:n,to:r,href:a,label:o,html:i,isDropdownLink:l,prependBaseUrlToHref:s,...c}=e;const d=(0,X.A)(r),p=(0,X.A)(t),f=(0,X.A)(a,{forcePrependBaseUrl:!0}),m=o&&a&&!(0,J.A)(a),g=i?{dangerouslySetInnerHTML:{__html:i}}:{children:(0,u.jsxs)(u.Fragment,{children:[o,m&&(0,u.jsx)(te.A,{...l&&{width:12,height:12}})]})};return a?(0,u.jsx)(Z.A,{href:s?f:a,...c,...g}):(0,u.jsx)(Z.A,{to:d,isNavLink:!0,...(t||n)&&{isActive:(e,t)=>n?ee(n,t.pathname):t.pathname.startsWith(p)},...c,...g})}function re(e){let{className:t,isDropdownItem:n=!1,...r}=e;const o=(0,u.jsx)(ne,{className:(0,a.A)(n?"dropdown__link":"navbar__item navbar__link",t),isDropdownLink:n,...r});return n?(0,u.jsx)("li",{children:o}):o}function ae(e){let{className:t,isDropdownItem:n,...r}=e;return(0,u.jsx)("li",{className:"menu__list-item",children:(0,u.jsx)(ne,{className:(0,a.A)("menu__link",t),...r})})}function oe(e){let{mobile:t=!1,position:n,...r}=e;const a=t?ae:re;return(0,u.jsx)(a,{...r,activeClassName:r.activeClassName??(t?"menu__link--active":"navbar__link--active")})}var ie=n(1422),le=n(9169),se=n(4586);const ce={dropdownNavbarItemMobile:"dropdownNavbarItemMobile_S0Fm"};function ue(e,t){return e.some((e=>function(e,t){return!!(0,le.ys)(e.to,t)||!!ee(e.activeBaseRegex,t)||!(!e.activeBasePath||!t.startsWith(e.activeBasePath))}(e,t)))}function de(e){let{items:t,position:n,className:o,onClick:i,...l}=e;const s=(0,r.useRef)(null),[c,d]=(0,r.useState)(!1);return(0,r.useEffect)((()=>{const e=e=>{s.current&&!s.current.contains(e.target)&&d(!1)};return document.addEventListener("mousedown",e),document.addEventListener("touchstart",e),document.addEventListener("focusin",e),()=>{document.removeEventListener("mousedown",e),document.removeEventListener("touchstart",e),document.removeEventListener("focusin",e)}}),[s]),(0,u.jsxs)("div",{ref:s,className:(0,a.A)("navbar__item","dropdown","dropdown--hoverable",{"dropdown--right":"right"===n,"dropdown--show":c}),children:[(0,u.jsx)(ne,{"aria-haspopup":"true","aria-expanded":c,role:"button",href:l.to?void 0:"#",className:(0,a.A)("navbar__link",o),...l,onClick:l.to?void 0:e=>e.preventDefault(),onKeyDown:e=>{"Enter"===e.key&&(e.preventDefault(),d(!c))},children:l.children??l.label}),(0,u.jsx)("ul",{className:"dropdown__menu",children:t.map(((e,t)=>(0,r.createElement)(Ce,{isDropdownItem:!0,activeClassName:"dropdown__link--active",...e,key:t})))})]})}function pe(e){let{items:t,className:n,position:o,onClick:i,...s}=e;const c=function(){const{siteConfig:{baseUrl:e}}=(0,se.A)(),{pathname:t}=(0,l.zy)();return t.replace(e,"/")}(),d=ue(t,c),{collapsed:p,toggleCollapsed:f,setCollapsed:m}=(0,ie.u)({initialState:()=>!d});return(0,r.useEffect)((()=>{d&&m(!d)}),[c,d,m]),(0,u.jsxs)("li",{className:(0,a.A)("menu__list-item",{"menu__list-item--collapsed":p}),children:[(0,u.jsx)(ne,{role:"button",className:(0,a.A)(ce.dropdownNavbarItemMobile,"menu__link menu__link--sublist menu__link--sublist-caret",n),...s,onClick:e=>{e.preventDefault(),f()},children:s.children??s.label}),(0,u.jsx)(ie.N,{lazy:!0,as:"ul",className:"menu__list",collapsed:p,children:t.map(((e,t)=>(0,r.createElement)(Ce,{mobile:!0,isDropdownItem:!0,onClick:i,activeClassName:"menu__link--active",...e,key:t})))})]})}function fe(e){let{mobile:t=!1,...n}=e;const r=t?pe:de;return(0,u.jsx)(r,{...n})}var me=n(2131);function ge(e){let{width:t=20,height:n=20,...r}=e;return(0,u.jsx)("svg",{viewBox:"0 0 24 24",width:t,height:n,"aria-hidden":!0,...r,children:(0,u.jsx)("path",{fill:"currentColor",d:"M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z"})})}const he="iconLanguage_nlXk";var ye=n(418);const be={navbarSearchContainer:"navbarSearchContainer_Bca1"};function ve(e){let{children:t,className:n}=e;return(0,u.jsx)("div",{className:(0,a.A)(n,be.navbarSearchContainer),children:t})}var we=n(4070),ke=n(1754);var xe=n(5597);const Se=e=>e.docs.find((t=>t.id===e.mainDocId));const Ee={default:oe,localeDropdown:function(e){let{mobile:t,dropdownItemsBefore:n,dropdownItemsAfter:r,queryString:a="",...o}=e;const{i18n:{currentLocale:i,locales:c,localeConfigs:d}}=(0,se.A)(),p=(0,me.o)(),{search:f,hash:m}=(0,l.zy)(),g=[...n,...c.map((e=>{const n=`${`pathname://${p.createUrl({locale:e,fullyQualified:!1})}`}${f}${m}${a}`;return{label:d[e].label,lang:d[e].htmlLang,to:n,target:"_self",autoAddBaseUrl:!1,className:e===i?t?"menu__link--active":"dropdown__link--active":""}})),...r],h=t?(0,s.T)({message:"Languages",id:"theme.navbar.mobileLanguageDropdown.label",description:"The label for the mobile language switcher dropdown"}):d[i].label;return(0,u.jsx)(fe,{...o,mobile:t,label:(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(ge,{className:he}),h]}),items:g})},search:function(e){let{mobile:t,className:n}=e;return t?null:(0,u.jsx)(ve,{className:n,children:(0,u.jsx)(ye.A,{})})},dropdown:fe,html:function(e){let{value:t,className:n,mobile:r=!1,isDropdownItem:o=!1}=e;const i=o?"li":"div";return(0,u.jsx)(i,{className:(0,a.A)({navbar__item:!r&&!o,"menu__list-item":r},n),dangerouslySetInnerHTML:{__html:t}})},doc:function(e){let{docId:t,label:n,docsPluginId:r,...a}=e;const{activeDoc:o}=(0,we.zK)(r),i=(0,ke.QB)(t,r),l=o?.path===i?.path;return null===i||i.unlisted&&!l?null:(0,u.jsx)(oe,{exact:!0,...a,isActive:()=>l||!!o?.sidebar&&o.sidebar===i.sidebar,label:n??i.id,to:i.path})},docSidebar:function(e){let{sidebarId:t,label:n,docsPluginId:r,...a}=e;const{activeDoc:o}=(0,we.zK)(r),i=(0,ke.fW)(t,r).link;if(!i)throw new Error(`DocSidebarNavbarItem: Sidebar with ID "${t}" doesn't have anything to be linked to.`);return(0,u.jsx)(oe,{exact:!0,...a,isActive:()=>o?.sidebar===t,label:n??i.label,to:i.path})},docsVersion:function(e){let{label:t,to:n,docsPluginId:r,...a}=e;const o=(0,ke.Vd)(r)[0],i=t??o.label,l=n??(e=>e.docs.find((t=>t.id===e.mainDocId)))(o).path;return(0,u.jsx)(oe,{...a,label:i,to:l})},docsVersionDropdown:function(e){let{mobile:t,docsPluginId:n,dropdownActiveClassDisabled:r,dropdownItemsBefore:a,dropdownItemsAfter:o,...i}=e;const{search:c,hash:d}=(0,l.zy)(),p=(0,we.zK)(n),f=(0,we.jh)(n),{savePreferredVersionName:m}=(0,xe.g1)(n),g=[...a,...f.map((e=>{const t=p.alternateDocVersions[e.name]??Se(e);return{label:e.label,to:`${t.path}${c}${d}`,isActive:()=>e===p.activeVersion,onClick:()=>m(e.name)}})),...o],h=(0,ke.Vd)(n)[0],y=t&&g.length>1?(0,s.T)({id:"theme.navbar.mobileVersionsDropdown.label",message:"Versions",description:"The label for the navbar versions dropdown on mobile view"}):h.label,b=t&&g.length>1?void 0:Se(h).path;return g.length<=1?(0,u.jsx)(oe,{...i,mobile:t,label:y,to:b,isActive:r?()=>!1:void 0}):(0,u.jsx)(fe,{...i,mobile:t,label:y,to:b,items:g,isActive:r?()=>!1:void 0})}};function Ce(e){let{type:t,...n}=e;const r=function(e,t){return e&&"default"!==e?e:"items"in t?"dropdown":"default"}(t,n),a=Ee[r];if(!a)throw new Error(`No NavbarItem component found for type "${t}".`);return(0,u.jsx)(a,{...n})}function _e(){const e=(0,j.M)(),t=(0,w.p)().navbar.items;return(0,u.jsx)("ul",{className:"menu__list",children:t.map(((t,n)=>(0,r.createElement)(Ce,{mobile:!0,...t,onClick:()=>e.toggle(),key:n})))})}function Ae(e){return(0,u.jsx)("button",{...e,type:"button",className:"clean-btn navbar-sidebar__back",children:(0,u.jsx)(s.A,{id:"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel",description:"The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)",children:"\u2190 Back to main menu"})})}function Te(){const e=0===(0,w.p)().navbar.items.length,t=M();return(0,u.jsxs)(u.Fragment,{children:[!e&&(0,u.jsx)(Ae,{onClick:()=>t.hide()}),t.content]})}function je(){const e=(0,j.M)();var t;return void 0===(t=e.shown)&&(t=!0),(0,r.useEffect)((()=>(document.body.style.overflow=t?"hidden":"visible",()=>{document.body.style.overflow="visible"})),[t]),e.shouldRender?(0,u.jsx)(I,{header:(0,u.jsx)(Y,{}),primaryMenu:(0,u.jsx)(_e,{}),secondaryMenu:(0,u.jsx)(Te,{})}):null}const Le={navbarHideable:"navbarHideable_m1mJ",navbarHidden:"navbarHidden_jGov"};function Ne(e){return(0,u.jsx)("div",{role:"presentation",...e,className:(0,a.A)("navbar-sidebar__backdrop",e.className)})}function Re(e){let{children:t}=e;const{navbar:{hideOnScroll:n,style:o}}=(0,w.p)(),i=(0,j.M)(),{navbarRef:l,isNavbarVisible:d}=function(e){const[t,n]=(0,r.useState)(e),a=(0,r.useRef)(!1),o=(0,r.useRef)(0),i=(0,r.useCallback)((e=>{null!==e&&(o.current=e.getBoundingClientRect().height)}),[]);return(0,L.Mq)(((t,r)=>{let{scrollY:i}=t;if(!e)return;if(i<o.current)return void n(!0);if(a.current)return void(a.current=!1);const l=r?.scrollY,s=document.documentElement.scrollHeight-o.current,c=window.innerHeight;l&&i>=l?n(!1):i+c<s&&n(!0)})),(0,c.$)((t=>{if(!e)return;const r=t.location.hash;if(r?document.getElementById(r.substring(1)):void 0)return a.current=!0,void n(!1);n(!0)})),{navbarRef:i,isNavbarVisible:t}}(n);return(0,u.jsxs)("nav",{ref:l,"aria-label":(0,s.T)({id:"theme.NavBar.navAriaLabel",message:"Main",description:"The ARIA label for the main navigation"}),className:(0,a.A)("navbar","navbar--fixed-top",n&&[Le.navbarHideable,!d&&Le.navbarHidden],{"navbar--dark":"dark"===o,"navbar--primary":"primary"===o,"navbar-sidebar--show":i.shown}),children:[t,(0,u.jsx)(Ne,{onClick:i.toggle}),(0,u.jsx)(je,{})]})}var Pe=n(440);const Oe={errorBoundaryError:"errorBoundaryError_a6uf",errorBoundaryFallback:"errorBoundaryFallback_VBag"};function De(e){return(0,u.jsx)("button",{type:"button",...e,children:(0,u.jsx)(s.A,{id:"theme.ErrorPageContent.tryAgain",description:"The label of the button to try again rendering when the React error boundary captures an error",children:"Try again"})})}function Me(e){let{error:t}=e;const n=(0,Pe.getErrorCausalChain)(t).map((e=>e.message)).join("\n\nCause:\n");return(0,u.jsx)("p",{className:Oe.errorBoundaryError,children:n})}class Ie extends r.Component{componentDidCatch(e,t){throw this.props.onError(e,t)}render(){return this.props.children}}const Fe="right";function ze(e){let{width:t=30,height:n=30,className:r,...a}=e;return(0,u.jsx)("svg",{className:r,width:t,height:n,viewBox:"0 0 30 30","aria-hidden":"true",...a,children:(0,u.jsx)("path",{stroke:"currentColor",strokeLinecap:"round",strokeMiterlimit:"10",strokeWidth:"2",d:"M4 7h22M4 15h22M4 23h22"})})}function Be(){const{toggle:e,shown:t}=(0,j.M)();return(0,u.jsx)("button",{onClick:e,"aria-label":(0,s.T)({id:"theme.docs.sidebar.toggleSidebarButtonAriaLabel",message:"Toggle navigation bar",description:"The ARIA label for hamburger menu button of mobile navigation"}),"aria-expanded":t,className:"navbar__toggle clean-btn",type:"button",children:(0,u.jsx)(ze,{})})}const $e={colorModeToggle:"colorModeToggle_DEke"};function Ue(e){let{items:t}=e;return(0,u.jsx)(u.Fragment,{children:t.map(((e,t)=>(0,u.jsx)(Ie,{onError:t=>new Error(`A theme navbar item failed to render.\nPlease double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config:\n${JSON.stringify(e,null,2)}`,{cause:t}),children:(0,u.jsx)(Ce,{...e})},t)))})}function qe(e){let{left:t,right:n}=e;return(0,u.jsxs)("div",{className:"navbar__inner",children:[(0,u.jsx)("div",{className:"navbar__items",children:t}),(0,u.jsx)("div",{className:"navbar__items navbar__items--right",children:n})]})}function He(){const e=(0,j.M)(),t=(0,w.p)().navbar.items,[n,r]=function(e){function t(e){return"left"===(e.position??Fe)}return[e.filter(t),e.filter((e=>!t(e)))]}(t),a=t.find((e=>"search"===e.type));return(0,u.jsx)(qe,{left:(0,u.jsxs)(u.Fragment,{children:[!e.disabled&&(0,u.jsx)(Be,{}),(0,u.jsx)(Q,{}),(0,u.jsx)(Ue,{items:n})]}),right:(0,u.jsxs)(u.Fragment,{children:[(0,u.jsx)(Ue,{items:r}),(0,u.jsx)(V,{className:$e.colorModeToggle}),!a&&(0,u.jsx)(ve,{children:(0,u.jsx)(ye.A,{})})]})})}function Ge(){return(0,u.jsx)(Re,{children:(0,u.jsx)(He,{})})}function Ve(e){let{item:t}=e;const{to:n,href:r,label:a,prependBaseUrlToHref:o,...i}=t,l=(0,X.A)(n),s=(0,X.A)(r,{forcePrependBaseUrl:!0});return(0,u.jsxs)(Z.A,{className:"footer__link-item",...r?{href:o?s:r}:{to:l},...i,children:[a,r&&!(0,J.A)(r)&&(0,u.jsx)(te.A,{})]})}function We(e){let{item:t}=e;return t.html?(0,u.jsx)("li",{className:"footer__item",dangerouslySetInnerHTML:{__html:t.html}}):(0,u.jsx)("li",{className:"footer__item",children:(0,u.jsx)(Ve,{item:t})},t.href??t.to)}function Qe(e){let{column:t}=e;return(0,u.jsxs)("div",{className:"col footer__col",children:[(0,u.jsx)("div",{className:"footer__title",children:t.title}),(0,u.jsx)("ul",{className:"footer__items clean-list",children:t.items.map(((e,t)=>(0,u.jsx)(We,{item:e},t)))})]})}function Ke(e){let{columns:t}=e;return(0,u.jsx)("div",{className:"row footer__links",children:t.map(((e,t)=>(0,u.jsx)(Qe,{column:e},t)))})}function Ye(){return(0,u.jsx)("span",{className:"footer__link-separator",children:"\xb7"})}function Ze(e){let{item:t}=e;return t.html?(0,u.jsx)("span",{className:"footer__link-item",dangerouslySetInnerHTML:{__html:t.html}}):(0,u.jsx)(Ve,{item:t})}function Xe(e){let{links:t}=e;return(0,u.jsx)("div",{className:"footer__links text--center",children:(0,u.jsx)("div",{className:"footer__links",children:t.map(((e,n)=>(0,u.jsxs)(r.Fragment,{children:[(0,u.jsx)(Ze,{item:e}),t.length!==n+1&&(0,u.jsx)(Ye,{})]},n)))})})}function Je(e){let{links:t}=e;return function(e){return"title"in e[0]}(t)?(0,u.jsx)(Ke,{columns:t}):(0,u.jsx)(Xe,{links:t})}var et=n(1122);const tt={footerLogoLink:"footerLogoLink_BH7S"};function nt(e){let{logo:t}=e;const{withBaseUrl:n}=(0,X.h)(),r={light:n(t.src),dark:n(t.srcDark??t.src)};return(0,u.jsx)(et.A,{className:(0,a.A)("footer__logo",t.className),alt:t.alt,sources:r,width:t.width,height:t.height,style:t.style})}function rt(e){let{logo:t}=e;return t.href?(0,u.jsx)(Z.A,{href:t.href,className:tt.footerLogoLink,target:t.target,children:(0,u.jsx)(nt,{logo:t})}):(0,u.jsx)(nt,{logo:t})}function at(e){let{copyright:t}=e;return(0,u.jsx)("div",{className:"footer__copyright",dangerouslySetInnerHTML:{__html:t}})}function ot(e){let{style:t,links:n,logo:r,copyright:o}=e;return(0,u.jsx)("footer",{className:(0,a.A)("footer",{"footer--dark":"dark"===t}),children:(0,u.jsxs)("div",{className:"container container-fluid",children:[n,(r||o)&&(0,u.jsxs)("div",{className:"footer__bottom text--center",children:[r&&(0,u.jsx)("div",{className:"margin-bottom--sm",children:r}),o]})]})})}function it(){const{footer:e}=(0,w.p)();if(!e)return null;const{copyright:t,links:n,logo:r,style:a}=e;return(0,u.jsx)(ot,{style:a,links:n&&n.length>0&&(0,u.jsx)(Je,{links:n}),logo:r&&(0,u.jsx)(rt,{logo:r}),copyright:t&&(0,u.jsx)(at,{copyright:t})})}const lt=r.memo(it),st=(0,N.fM)([F.a,k.oq,L.Tv,xe.VQ,i.Jx,function(e){let{children:t}=e;return(0,u.jsx)(R.y_,{children:(0,u.jsx)(j.e,{children:(0,u.jsx)(O,{children:t})})})}]);function ct(e){let{children:t}=e;return(0,u.jsx)(st,{children:t})}var ut=n(1107);function dt(e){let{error:t,tryAgain:n}=e;return(0,u.jsx)("main",{className:"container margin-vert--xl",children:(0,u.jsx)("div",{className:"row",children:(0,u.jsxs)("div",{className:"col col--6 col--offset-3",children:[(0,u.jsx)(ut.A,{as:"h1",className:"hero__title",children:(0,u.jsx)(s.A,{id:"theme.ErrorPageContent.title",description:"The title of the fallback page when the page crashed",children:"This page crashed."})}),(0,u.jsx)("div",{className:"margin-vert--lg",children:(0,u.jsx)(De,{onClick:n,className:"button button--primary shadow--lw"})}),(0,u.jsx)("hr",{}),(0,u.jsx)("div",{className:"margin-vert--md",children:(0,u.jsx)(Me,{error:t})})]})})})}const pt={mainWrapper:"mainWrapper_z2l0"};function ft(e){const{children:t,noFooter:n,wrapperClassName:r,title:l,description:s}=e;return(0,y.J)(),(0,u.jsxs)(ct,{children:[(0,u.jsx)(i.be,{title:l,description:s}),(0,u.jsx)(v,{}),(0,u.jsx)(T,{}),(0,u.jsx)(Ge,{}),(0,u.jsx)("div",{id:d,className:(0,a.A)(h.G.wrapper.main,pt.mainWrapper,r),children:(0,u.jsx)(o.A,{fallback:e=>(0,u.jsx)(dt,{...e}),children:t})}),!n&&(0,u.jsx)(lt,{})]})}},3465:(e,t,n)=>{"use strict";n.d(t,{A:()=>u});n(6540);var r=n(8774),a=n(6025),o=n(4586),i=n(6342),l=n(1122),s=n(4848);function c(e){let{logo:t,alt:n,imageClassName:r}=e;const o={light:(0,a.A)(t.src),dark:(0,a.A)(t.srcDark||t.src)},i=(0,s.jsx)(l.A,{className:t.className,sources:o,height:t.height,width:t.width,alt:n,style:t.style});return r?(0,s.jsx)("div",{className:r,children:i}):i}function u(e){const{siteConfig:{title:t}}=(0,o.A)(),{navbar:{title:n,logo:l}}=(0,i.p)(),{imageClassName:u,titleClassName:d,...p}=e,f=(0,a.A)(l?.href||"/"),m=n?"":t,g=l?.alt??m;return(0,s.jsxs)(r.A,{to:f,...p,...l?.target&&{target:l.target},children:[l&&(0,s.jsx)(c,{logo:l,alt:g,imageClassName:u}),null!=n&&(0,s.jsx)("b",{className:d,children:n})]})}},1463:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});n(6540);var r=n(5260),a=n(4848);function o(e){let{locale:t,version:n,tag:o}=e;const i=t;return(0,a.jsxs)(r.A,{children:[t&&(0,a.jsx)("meta",{name:"docusaurus_locale",content:t}),n&&(0,a.jsx)("meta",{name:"docusaurus_version",content:n}),o&&(0,a.jsx)("meta",{name:"docusaurus_tag",content:o}),i&&(0,a.jsx)("meta",{name:"docsearch:language",content:i}),n&&(0,a.jsx)("meta",{name:"docsearch:version",content:n}),o&&(0,a.jsx)("meta",{name:"docsearch:docusaurus_tag",content:o})]})}},1122:(e,t,n)=>{"use strict";n.d(t,{A:()=>u});var r=n(6540),a=n(4164),o=n(2303),i=n(5293);const l={themedComponent:"themedComponent_mlkZ","themedComponent--light":"themedComponent--light_NVdE","themedComponent--dark":"themedComponent--dark_xIcU"};var s=n(4848);function c(e){let{className:t,children:n}=e;const c=(0,o.A)(),{colorMode:u}=(0,i.G)();return(0,s.jsx)(s.Fragment,{children:(c?"dark"===u?["dark"]:["light"]:["light","dark"]).map((e=>{const o=n({theme:e,className:(0,a.A)(t,l.themedComponent,l[`themedComponent--${e}`])});return(0,s.jsx)(r.Fragment,{children:o},e)}))})}function u(e){const{sources:t,className:n,alt:r,...a}=e;return(0,s.jsx)(c,{className:n,children:e=>{let{theme:n,className:o}=e;return(0,s.jsx)("img",{src:t[n],alt:r,className:o,...a})}})}},1422:(e,t,n)=>{"use strict";n.d(t,{N:()=>y,u:()=>c});var r=n(6540),a=n(8193),o=n(205),i=n(3109),l=n(4848);const s="ease-in-out";function c(e){let{initialState:t}=e;const[n,a]=(0,r.useState)(t??!1),o=(0,r.useCallback)((()=>{a((e=>!e))}),[]);return{collapsed:n,setCollapsed:a,toggleCollapsed:o}}const u={display:"none",overflow:"hidden",height:"0px"},d={display:"block",overflow:"visible",height:"auto"};function p(e,t){const n=t?u:d;e.style.display=n.display,e.style.overflow=n.overflow,e.style.height=n.height}function f(e){let{collapsibleRef:t,collapsed:n,animation:a}=e;const o=(0,r.useRef)(!1);(0,r.useEffect)((()=>{const e=t.current;function r(){const t=e.scrollHeight,n=a?.duration??function(e){if((0,i.O)())return 1;const t=e/36;return Math.round(10*(4+15*t**.25+t/5))}(t);return{transition:`height ${n}ms ${a?.easing??s}`,height:`${t}px`}}function l(){const t=r();e.style.transition=t.transition,e.style.height=t.height}if(!o.current)return p(e,n),void(o.current=!0);return e.style.willChange="height",function(){const t=requestAnimationFrame((()=>{n?(l(),requestAnimationFrame((()=>{e.style.height=u.height,e.style.overflow=u.overflow}))):(e.style.display="block",requestAnimationFrame((()=>{l()})))}));return()=>cancelAnimationFrame(t)}()}),[t,n,a])}function m(e){if(!a.A.canUseDOM)return e?u:d}function g(e){let{as:t="div",collapsed:n,children:a,animation:o,onCollapseTransitionEnd:i,className:s,disableSSRStyle:c}=e;const u=(0,r.useRef)(null);return f({collapsibleRef:u,collapsed:n,animation:o}),(0,l.jsx)(t,{ref:u,style:c?void 0:m(n),onTransitionEnd:e=>{"height"===e.propertyName&&(p(u.current,n),i?.(n))},className:s,children:a})}function h(e){let{collapsed:t,...n}=e;const[a,i]=(0,r.useState)(!t),[s,c]=(0,r.useState)(t);return(0,o.A)((()=>{t||i(!0)}),[t]),(0,o.A)((()=>{a&&c(t)}),[a,t]),a?(0,l.jsx)(g,{...n,collapsed:s}):null}function y(e){let{lazy:t,...n}=e;const r=t?h:g;return(0,l.jsx)(r,{...n})}},5041:(e,t,n)=>{"use strict";n.d(t,{Mj:()=>g,oq:()=>m});var r=n(6540),a=n(2303),o=n(9466),i=n(9532),l=n(6342),s=n(4848);const c=(0,o.Wf)("docusaurus.announcement.dismiss"),u=(0,o.Wf)("docusaurus.announcement.id"),d=()=>"true"===c.get(),p=e=>c.set(String(e)),f=r.createContext(null);function m(e){let{children:t}=e;const n=function(){const{announcementBar:e}=(0,l.p)(),t=(0,a.A)(),[n,o]=(0,r.useState)((()=>!!t&&d()));(0,r.useEffect)((()=>{o(d())}),[]);const i=(0,r.useCallback)((()=>{p(!0),o(!0)}),[]);return(0,r.useEffect)((()=>{if(!e)return;const{id:t}=e;let n=u.get();"annoucement-bar"===n&&(n="announcement-bar");const r=t!==n;u.set(t),r&&p(!1),!r&&d()||o(!1)}),[e]),(0,r.useMemo)((()=>({isActive:!!e&&!n,close:i})),[e,n,i])}();return(0,s.jsx)(f.Provider,{value:n,children:t})}function g(){const e=(0,r.useContext)(f);if(!e)throw new i.dV("AnnouncementBarProvider");return e}},5293:(e,t,n)=>{"use strict";n.d(t,{G:()=>y,a:()=>h});var r=n(6540),a=n(8193),o=n(9532),i=n(9466),l=n(6342),s=n(4848);const c=r.createContext(void 0),u="theme",d=(0,i.Wf)(u),p={light:"light",dark:"dark"},f=e=>e===p.dark?p.dark:p.light,m=e=>a.A.canUseDOM?f(document.documentElement.getAttribute("data-theme")):f(e),g=e=>{d.set(f(e))};function h(e){let{children:t}=e;const n=function(){const{colorMode:{defaultMode:e,disableSwitch:t,respectPrefersColorScheme:n}}=(0,l.p)(),[a,o]=(0,r.useState)(m(e));(0,r.useEffect)((()=>{t&&d.del()}),[t]);const i=(0,r.useCallback)((function(t,r){void 0===r&&(r={});const{persist:a=!0}=r;t?(o(t),a&&g(t)):(o(n?window.matchMedia("(prefers-color-scheme: dark)").matches?p.dark:p.light:e),d.del())}),[n,e]);(0,r.useEffect)((()=>{document.documentElement.setAttribute("data-theme",f(a))}),[a]),(0,r.useEffect)((()=>{if(t)return;const e=e=>{if(e.key!==u)return;const t=d.get();null!==t&&i(f(t))};return window.addEventListener("storage",e),()=>window.removeEventListener("storage",e)}),[t,i]);const s=(0,r.useRef)(!1);return(0,r.useEffect)((()=>{if(t&&!n)return;const e=window.matchMedia("(prefers-color-scheme: dark)"),r=()=>{window.matchMedia("print").matches||s.current?s.current=window.matchMedia("print").matches:i(null)};return e.addListener(r),()=>e.removeListener(r)}),[i,t,n]),(0,r.useMemo)((()=>({colorMode:a,setColorMode:i,get isDarkTheme(){return a===p.dark},setLightTheme(){i(p.light)},setDarkTheme(){i(p.dark)}})),[a,i])}();return(0,s.jsx)(c.Provider,{value:n,children:t})}function y(){const e=(0,r.useContext)(c);if(null==e)throw new o.dV("ColorModeProvider","Please see https://docusaurus.io/docs/api/themes/configuration#use-color-mode.");return e}},5597:(e,t,n)=>{"use strict";n.d(t,{VQ:()=>y,g1:()=>v});var r=n(6540),a=n(4070),o=n(7065),i=n(6342),l=n(1754),s=n(9532),c=n(9466),u=n(4848);const d=e=>`docs-preferred-version-${e}`,p={save:(e,t,n)=>{(0,c.Wf)(d(e),{persistence:t}).set(n)},read:(e,t)=>(0,c.Wf)(d(e),{persistence:t}).get(),clear:(e,t)=>{(0,c.Wf)(d(e),{persistence:t}).del()}},f=e=>Object.fromEntries(e.map((e=>[e,{preferredVersionName:null}])));const m=r.createContext(null);function g(){const e=(0,a.Gy)(),t=(0,i.p)().docs.versionPersistence,n=(0,r.useMemo)((()=>Object.keys(e)),[e]),[o,l]=(0,r.useState)((()=>f(n)));(0,r.useEffect)((()=>{l(function(e){let{pluginIds:t,versionPersistence:n,allDocsData:r}=e;function a(e){const t=p.read(e,n);return r[e].versions.some((e=>e.name===t))?{preferredVersionName:t}:(p.clear(e,n),{preferredVersionName:null})}return Object.fromEntries(t.map((e=>[e,a(e)])))}({allDocsData:e,versionPersistence:t,pluginIds:n}))}),[e,t,n]);return[o,(0,r.useMemo)((()=>({savePreferredVersion:function(e,n){p.save(e,t,n),l((t=>({...t,[e]:{preferredVersionName:n}})))}})),[t])]}function h(e){let{children:t}=e;const n=g();return(0,u.jsx)(m.Provider,{value:n,children:t})}function y(e){let{children:t}=e;return l.C5?(0,u.jsx)(h,{children:t}):(0,u.jsx)(u.Fragment,{children:t})}function b(){const e=(0,r.useContext)(m);if(!e)throw new s.dV("DocsPreferredVersionContextProvider");return e}function v(e){void 0===e&&(e=o.W);const t=(0,a.ht)(e),[n,i]=b(),{preferredVersionName:l}=n[e];return{preferredVersion:t.versions.find((e=>e.name===l))??null,savePreferredVersionName:(0,r.useCallback)((t=>{i.savePreferredVersion(e,t)}),[i,e])}}},6588:(e,t,n)=>{"use strict";n.d(t,{V:()=>s,t:()=>c});var r=n(6540),a=n(9532),o=n(4848);const i=Symbol("EmptyContext"),l=r.createContext(i);function s(e){let{children:t,name:n,items:a}=e;const i=(0,r.useMemo)((()=>n&&a?{name:n,items:a}:null),[n,a]);return(0,o.jsx)(l.Provider,{value:i,children:t})}function c(){const e=(0,r.useContext)(l);if(e===i)throw new a.dV("DocsSidebarProvider");return e}},2252:(e,t,n)=>{"use strict";n.d(t,{n:()=>l,r:()=>s});var r=n(6540),a=n(9532),o=n(4848);const i=r.createContext(null);function l(e){let{children:t,version:n}=e;return(0,o.jsx)(i.Provider,{value:n,children:t})}function s(){const e=(0,r.useContext)(i);if(null===e)throw new a.dV("DocsVersionProvider");return e}},9876:(e,t,n)=>{"use strict";n.d(t,{e:()=>f,M:()=>m});var r=n(6540),a=n(5600),o=n(4581),i=n(6347),l=n(9532);function s(e){!function(e){const t=(0,i.W6)(),n=(0,l._q)(e);(0,r.useEffect)((()=>t.block(((e,t)=>n(e,t)))),[t,n])}(((t,n)=>{if("POP"===n)return e(t,n)}))}var c=n(6342),u=n(4848);const d=r.createContext(void 0);function p(){const e=function(){const e=(0,a.YL)(),{items:t}=(0,c.p)().navbar;return 0===t.length&&!e.component}(),t=(0,o.l)(),n=!e&&"mobile"===t,[i,l]=(0,r.useState)(!1);s((()=>{if(i)return l(!1),!1}));const u=(0,r.useCallback)((()=>{l((e=>!e))}),[]);return(0,r.useEffect)((()=>{"desktop"===t&&l(!1)}),[t]),(0,r.useMemo)((()=>({disabled:e,shouldRender:n,toggle:u,shown:i})),[e,n,u,i])}function f(e){let{children:t}=e;const n=p();return(0,u.jsx)(d.Provider,{value:n,children:t})}function m(){const e=r.useContext(d);if(void 0===e)throw new l.dV("NavbarMobileSidebarProvider");return e}},5600:(e,t,n)=>{"use strict";n.d(t,{GX:()=>c,YL:()=>s,y_:()=>l});var r=n(6540),a=n(9532),o=n(4848);const i=r.createContext(null);function l(e){let{children:t}=e;const n=(0,r.useState)({component:null,props:null});return(0,o.jsx)(i.Provider,{value:n,children:t})}function s(){const e=(0,r.useContext)(i);if(!e)throw new a.dV("NavbarSecondaryMenuContentProvider");return e[0]}function c(e){let{component:t,props:n}=e;const o=(0,r.useContext)(i);if(!o)throw new a.dV("NavbarSecondaryMenuContentProvider");const[,l]=o,s=(0,a.Be)(n);return(0,r.useEffect)((()=>{l({component:t,props:s})}),[l,t,s]),(0,r.useEffect)((()=>()=>l({component:null,props:null})),[l]),null}},4090:(e,t,n)=>{"use strict";n.d(t,{w:()=>a,J:()=>o});var r=n(6540);const a="navigation-with-keyboard";function o(){(0,r.useEffect)((()=>{function e(e){"keydown"===e.type&&"Tab"===e.key&&document.body.classList.add(a),"mousedown"===e.type&&document.body.classList.remove(a)}return document.addEventListener("keydown",e),document.addEventListener("mousedown",e),()=>{document.body.classList.remove(a),document.removeEventListener("keydown",e),document.removeEventListener("mousedown",e)}}),[])}},4581:(e,t,n)=>{"use strict";n.d(t,{l:()=>l});var r=n(6540),a=n(8193);const o={desktop:"desktop",mobile:"mobile",ssr:"ssr"},i=996;function l(e){let{desktopBreakpoint:t=i}=void 0===e?{}:e;const[n,l]=(0,r.useState)((()=>"ssr"));return(0,r.useEffect)((()=>{function e(){l(function(e){if(!a.A.canUseDOM)throw new Error("getWindowSize() should only be called after React hydration");return window.innerWidth>e?o.desktop:o.mobile}(t))}return e(),window.addEventListener("resize",e),()=>{window.removeEventListener("resize",e)}}),[t]),n}},7559:(e,t,n)=>{"use strict";n.d(t,{G:()=>r});const r={page:{blogListPage:"blog-list-page",blogPostPage:"blog-post-page",blogTagsListPage:"blog-tags-list-page",blogTagPostListPage:"blog-tags-post-list-page",docsDocPage:"docs-doc-page",docsTagsListPage:"docs-tags-list-page",docsTagDocListPage:"docs-tags-doc-list-page",mdxPage:"mdx-page"},wrapper:{main:"main-wrapper",blogPages:"blog-wrapper",docsPages:"docs-wrapper",mdxPages:"mdx-wrapper"},common:{editThisPage:"theme-edit-this-page",lastUpdated:"theme-last-updated",backToTopButton:"theme-back-to-top-button",codeBlock:"theme-code-block",admonition:"theme-admonition",unlistedBanner:"theme-unlisted-banner",admonitionType:e=>`theme-admonition-${e}`},layout:{},docs:{docVersionBanner:"theme-doc-version-banner",docVersionBadge:"theme-doc-version-badge",docBreadcrumbs:"theme-doc-breadcrumbs",docMarkdown:"theme-doc-markdown",docTocMobile:"theme-doc-toc-mobile",docTocDesktop:"theme-doc-toc-desktop",docFooter:"theme-doc-footer",docFooterTagsRow:"theme-doc-footer-tags-row",docFooterEditMetaRow:"theme-doc-footer-edit-meta-row",docSidebarContainer:"theme-doc-sidebar-container",docSidebarMenu:"theme-doc-sidebar-menu",docSidebarItemCategory:"theme-doc-sidebar-item-category",docSidebarItemLink:"theme-doc-sidebar-item-link",docSidebarItemCategoryLevel:e=>`theme-doc-sidebar-item-category-level-${e}`,docSidebarItemLinkLevel:e=>`theme-doc-sidebar-item-link-level-${e}`},blog:{}}},3109:(e,t,n)=>{"use strict";function r(){return window.matchMedia("(prefers-reduced-motion: reduce)").matches}n.d(t,{O:()=>r})},1754:(e,t,n)=>{"use strict";n.d(t,{Nr:()=>f,w8:()=>h,C5:()=>p,B5:()=>E,Vd:()=>k,QB:()=>S,fW:()=>x,OF:()=>w,Y:()=>b});var r=n(6540),a=n(6347),o=n(2831),i=n(4070),l=n(5597),s=n(2252),c=n(6588);function u(e){return Array.from(new Set(e))}var d=n(9169);const p=!!i.Gy;function f(e){return"link"!==e.type||e.unlisted?"category"===e.type?function(e){if(e.href&&!e.linkUnlisted)return e.href;for(const t of e.items){const e=f(t);if(e)return e}}(e):void 0:e.href}const m=(e,t)=>void 0!==e&&(0,d.ys)(e,t),g=(e,t)=>e.some((e=>h(e,t)));function h(e,t){return"link"===e.type?m(e.href,t):"category"===e.type&&(m(e.href,t)||g(e.items,t))}function y(e,t){switch(e.type){case"category":return h(e,t)||e.items.some((e=>y(e,t)));case"link":return!e.unlisted||h(e,t);default:return!0}}function b(e,t){return(0,r.useMemo)((()=>e.filter((e=>y(e,t)))),[e,t])}function v(e){let{sidebarItems:t,pathname:n,onlyCategories:r=!1}=e;const a=[];return function e(t){for(const o of t)if("category"===o.type&&((0,d.ys)(o.href,n)||e(o.items))||"link"===o.type&&(0,d.ys)(o.href,n)){return r&&"category"!==o.type||a.unshift(o),!0}return!1}(t),a}function w(){const e=(0,c.t)(),{pathname:t}=(0,a.zy)(),n=(0,i.vT)()?.pluginData.breadcrumbs;return!1!==n&&e?v({sidebarItems:e.items,pathname:t}):null}function k(e){const{activeVersion:t}=(0,i.zK)(e),{preferredVersion:n}=(0,l.g1)(e),a=(0,i.r7)(e);return(0,r.useMemo)((()=>u([t,n,a].filter(Boolean))),[t,n,a])}function x(e,t){const n=k(t);return(0,r.useMemo)((()=>{const t=n.flatMap((e=>e.sidebars?Object.entries(e.sidebars):[])),r=t.find((t=>t[0]===e));if(!r)throw new Error(`Can't find any sidebar with id "${e}" in version${n.length>1?"s":""} ${n.map((e=>e.name)).join(", ")}".\nAvailable sidebar ids are:\n- ${t.map((e=>e[0])).join("\n- ")}`);return r[1]}),[e,n])}function S(e,t){const n=k(t);return(0,r.useMemo)((()=>{const t=n.flatMap((e=>e.docs)),r=t.find((t=>t.id===e));if(!r){if(n.flatMap((e=>e.draftIds)).includes(e))return null;throw new Error(`Couldn't find any doc with id "${e}" in version${n.length>1?"s":""} "${n.map((e=>e.name)).join(", ")}".\nAvailable doc ids are:\n- ${u(t.map((e=>e.id))).join("\n- ")}`)}return r}),[e,n])}function E(e){let{route:t}=e;const n=(0,a.zy)(),r=(0,s.r)(),i=t.routes,l=i.find((e=>(0,a.B6)(n.pathname,e)));if(!l)return null;const c=l.sidebar,u=c?r.docsSidebars[c]:void 0;return{docElement:(0,o.v)(i),sidebarName:c,sidebarItems:u}}},1003:(e,t,n)=>{"use strict";n.d(t,{e3:()=>f,be:()=>d,Jx:()=>m});var r=n(6540),a=n(4164),o=n(5260),i=n(3102);function l(){const e=r.useContext(i.o);if(!e)throw new Error("Unexpected: no Docusaurus route context found");return e}var s=n(6025),c=n(4586);var u=n(4848);function d(e){let{title:t,description:n,keywords:r,image:a,children:i}=e;const l=function(e){const{siteConfig:t}=(0,c.A)(),{title:n,titleDelimiter:r}=t;return e?.trim().length?`${e.trim()} ${r} ${n}`:n}(t),{withBaseUrl:d}=(0,s.h)(),p=a?d(a,{absolute:!0}):void 0;return(0,u.jsxs)(o.A,{children:[t&&(0,u.jsx)("title",{children:l}),t&&(0,u.jsx)("meta",{property:"og:title",content:l}),n&&(0,u.jsx)("meta",{name:"description",content:n}),n&&(0,u.jsx)("meta",{property:"og:description",content:n}),r&&(0,u.jsx)("meta",{name:"keywords",content:Array.isArray(r)?r.join(","):r}),p&&(0,u.jsx)("meta",{property:"og:image",content:p}),p&&(0,u.jsx)("meta",{name:"twitter:image",content:p}),i]})}const p=r.createContext(void 0);function f(e){let{className:t,children:n}=e;const i=r.useContext(p),l=(0,a.A)(i,t);return(0,u.jsxs)(p.Provider,{value:l,children:[(0,u.jsx)(o.A,{children:(0,u.jsx)("html",{className:l})}),n]})}function m(e){let{children:t}=e;const n=l(),r=`plugin-${n.plugin.name.replace(/docusaurus-(?:plugin|theme)-(?:content-)?/gi,"")}`;const o=`plugin-id-${n.plugin.id}`;return(0,u.jsx)(f,{className:(0,a.A)(r,o),children:t})}},9532:(e,t,n)=>{"use strict";n.d(t,{Be:()=>c,ZC:()=>l,_q:()=>i,dV:()=>s,fM:()=>u});var r=n(6540),a=n(205),o=n(4848);function i(e){const t=(0,r.useRef)(e);return(0,a.A)((()=>{t.current=e}),[e]),(0,r.useCallback)((function(){return t.current(...arguments)}),[])}function l(e){const t=(0,r.useRef)();return(0,a.A)((()=>{t.current=e})),t.current}class s extends Error{constructor(e,t){super(),this.name="ReactContextError",this.message=`Hook ${this.stack?.split("\n")[1]?.match(/at (?:\w+\.)?(?<name>\w+)/)?.groups.name??""} is called outside the <${e}>. ${t??""}`}}function c(e){const t=Object.entries(e);return t.sort(((e,t)=>e[0].localeCompare(t[0]))),(0,r.useMemo)((()=>e),t.flat())}function u(e){return t=>{let{children:n}=t;return(0,o.jsx)(o.Fragment,{children:e.reduceRight(((e,t)=>(0,o.jsx)(t,{children:e})),n)})}}},9169:(e,t,n)=>{"use strict";n.d(t,{Dt:()=>l,ys:()=>i});var r=n(6540),a=n(8328),o=n(4586);function i(e,t){const n=e=>(!e||e.endsWith("/")?e:`${e}/`)?.toLowerCase();return n(e)===n(t)}function l(){const{baseUrl:e}=(0,o.A)().siteConfig;return(0,r.useMemo)((()=>function(e){let{baseUrl:t,routes:n}=e;function r(e){return e.path===t&&!0===e.exact}function a(e){return e.path===t&&!e.exact}return function e(t){if(0===t.length)return;return t.find(r)||e(t.filter(a).flatMap((e=>e.routes??[])))}(n)}({routes:a.A,baseUrl:e})),[e])}},3104:(e,t,n)=>{"use strict";n.d(t,{Mq:()=>p,Tv:()=>c,gk:()=>f});var r=n(6540),a=n(8193),o=n(2303),i=(n(205),n(9532)),l=n(4848);const s=r.createContext(void 0);function c(e){let{children:t}=e;const n=function(){const e=(0,r.useRef)(!0);return(0,r.useMemo)((()=>({scrollEventsEnabledRef:e,enableScrollEvents:()=>{e.current=!0},disableScrollEvents:()=>{e.current=!1}})),[])}();return(0,l.jsx)(s.Provider,{value:n,children:t})}function u(){const e=(0,r.useContext)(s);if(null==e)throw new i.dV("ScrollControllerProvider");return e}const d=()=>a.A.canUseDOM?{scrollX:window.pageXOffset,scrollY:window.pageYOffset}:null;function p(e,t){void 0===t&&(t=[]);const{scrollEventsEnabledRef:n}=u(),a=(0,r.useRef)(d()),o=(0,i._q)(e);(0,r.useEffect)((()=>{const e=()=>{if(!n.current)return;const e=d();o(e,a.current),a.current=e},t={passive:!0};return e(),window.addEventListener("scroll",e,t),()=>window.removeEventListener("scroll",e,t)}),[o,n,...t])}function f(){const e=(0,r.useRef)(null),t=(0,o.A)()&&"smooth"===getComputedStyle(document.documentElement).scrollBehavior;return{startScroll:n=>{e.current=t?function(e){return window.scrollTo({top:e,behavior:"smooth"}),()=>{}}(n):function(e){let t=null;const n=document.documentElement.scrollTop>e;return function r(){const a=document.documentElement.scrollTop;(n&&a>e||!n&&a<e)&&(t=requestAnimationFrame(r),window.scrollTo(0,Math.floor(.85*(a-e))+e))}(),()=>t&&cancelAnimationFrame(t)}(n)},cancelScroll:()=>e.current?.()}}},2967:(e,t,n)=>{"use strict";n.d(t,{Cy:()=>r,tU:()=>a});n(4586);const r="default";function a(e,t){return`docs-${e}-${t}`}},9466:(e,t,n)=>{"use strict";n.d(t,{Wf:()=>s});n(6540);const r="localStorage";function a(e){let{key:t,oldValue:n,newValue:r,storage:a}=e;if(n===r)return;const o=document.createEvent("StorageEvent");o.initStorageEvent("storage",!1,!1,t,n,r,window.location.href,a),window.dispatchEvent(o)}function o(e){if(void 0===e&&(e=r),"undefined"==typeof window)throw new Error("Browser storage is not available on Node.js/Docusaurus SSR process.");if("none"===e)return null;try{return window[e]}catch(n){return t=n,i||(console.warn("Docusaurus browser storage is not available.\nPossible reasons: running Docusaurus in an iframe, in an incognito browser session, or using too strict browser privacy settings.",t),i=!0),null}var t}let i=!1;const l={get:()=>null,set:()=>{},del:()=>{},listen:()=>()=>{}};function s(e,t){if("undefined"==typeof window)return function(e){function t(){throw new Error(`Illegal storage API usage for storage key "${e}".\nDocusaurus storage APIs are not supposed to be called on the server-rendering process.\nPlease only call storage APIs in effects and event handlers.`)}return{get:t,set:t,del:t,listen:t}}(e);const n=o(t?.persistence);return null===n?l:{get:()=>{try{return n.getItem(e)}catch(t){return console.error(`Docusaurus storage error, can't get key=${e}`,t),null}},set:t=>{try{const r=n.getItem(e);n.setItem(e,t),a({key:e,oldValue:r,newValue:t,storage:n})}catch(r){console.error(`Docusaurus storage error, can't set ${e}=${t}`,r)}},del:()=>{try{const t=n.getItem(e);n.removeItem(e),a({key:e,oldValue:t,newValue:null,storage:n})}catch(t){console.error(`Docusaurus storage error, can't delete key=${e}`,t)}},listen:t=>{try{const r=r=>{r.storageArea===n&&r.key===e&&t(r)};return window.addEventListener("storage",r),()=>window.removeEventListener("storage",r)}catch(r){return console.error(`Docusaurus storage error, can't listen for changes of key=${e}`,r),()=>{}}}}}},2131:(e,t,n)=>{"use strict";n.d(t,{o:()=>i});var r=n(4586),a=n(6347),o=n(440);function i(){const{siteConfig:{baseUrl:e,url:t,trailingSlash:n},i18n:{defaultLocale:i,currentLocale:l}}=(0,r.A)(),{pathname:s}=(0,a.zy)(),c=(0,o.applyTrailingSlash)(s,{trailingSlash:n,baseUrl:e}),u=l===i?e:e.replace(`/${l}/`,"/"),d=c.replace(e,"");return{createUrl:function(e){let{locale:n,fullyQualified:r}=e;return`${r?t:""}${function(e){return e===i?`${u}`:`${u}${e}/`}(n)}${d}`}}}},5062:(e,t,n)=>{"use strict";n.d(t,{$:()=>i});var r=n(6540),a=n(6347),o=n(9532);function i(e){const t=(0,a.zy)(),n=(0,o.ZC)(t),i=(0,o._q)(e);(0,r.useEffect)((()=>{n&&t!==n&&i({location:t,previousLocation:n})}),[i,t,n])}},6342:(e,t,n)=>{"use strict";n.d(t,{p:()=>a});var r=n(4586);function a(){return(0,r.A)().siteConfig.themeConfig}},2983:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=function(e,t){const{trailingSlash:n,baseUrl:r}=t;if(e.startsWith("#"))return e;if(void 0===n)return e;const[a]=e.split(/[#?]/),o="/"===a||a===r?a:(i=a,n?function(e){return e.endsWith("/")?e:`${e}/`}(i):function(e){return e.endsWith("/")?e.slice(0,-1):e}(i));var i;return e.replace(a,o)}},253:(e,t)=>{"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=void 0,t.getErrorCausalChain=function e(t){return t.cause?[t,...e(t.cause)]:[t]}},440:function(e,t,n){"use strict";var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.getErrorCausalChain=t.applyTrailingSlash=t.blogPostContainerID=void 0,t.blogPostContainerID="__blog-post-container";var a=n(2983);Object.defineProperty(t,"applyTrailingSlash",{enumerable:!0,get:function(){return r(a).default}});var o=n(253);Object.defineProperty(t,"getErrorCausalChain",{enumerable:!0,get:function(){return o.getErrorCausalChain}})},1513:(e,t,n)=>{"use strict";n.d(t,{zR:()=>w,TM:()=>_,yJ:()=>f,sC:()=>T,AO:()=>p});var r=n(8168);function a(e){return"/"===e.charAt(0)}function o(e,t){for(var n=t,r=n+1,a=e.length;r<a;n+=1,r+=1)e[n]=e[r];e.pop()}const i=function(e,t){void 0===t&&(t="");var n,r=e&&e.split("/")||[],i=t&&t.split("/")||[],l=e&&a(e),s=t&&a(t),c=l||s;if(e&&a(e)?i=r:r.length&&(i.pop(),i=i.concat(r)),!i.length)return"/";if(i.length){var u=i[i.length-1];n="."===u||".."===u||""===u}else n=!1;for(var d=0,p=i.length;p>=0;p--){var f=i[p];"."===f?o(i,p):".."===f?(o(i,p),d++):d&&(o(i,p),d--)}if(!c)for(;d--;d)i.unshift("..");!c||""===i[0]||i[0]&&a(i[0])||i.unshift("");var m=i.join("/");return n&&"/"!==m.substr(-1)&&(m+="/"),m};var l=n(1561);function s(e){return"/"===e.charAt(0)?e:"/"+e}function c(e){return"/"===e.charAt(0)?e.substr(1):e}function u(e,t){return function(e,t){return 0===e.toLowerCase().indexOf(t.toLowerCase())&&-1!=="/?#".indexOf(e.charAt(t.length))}(e,t)?e.substr(t.length):e}function d(e){return"/"===e.charAt(e.length-1)?e.slice(0,-1):e}function p(e){var t=e.pathname,n=e.search,r=e.hash,a=t||"/";return n&&"?"!==n&&(a+="?"===n.charAt(0)?n:"?"+n),r&&"#"!==r&&(a+="#"===r.charAt(0)?r:"#"+r),a}function f(e,t,n,a){var o;"string"==typeof e?(o=function(e){var t=e||"/",n="",r="",a=t.indexOf("#");-1!==a&&(r=t.substr(a),t=t.substr(0,a));var o=t.indexOf("?");return-1!==o&&(n=t.substr(o),t=t.substr(0,o)),{pathname:t,search:"?"===n?"":n,hash:"#"===r?"":r}}(e),o.state=t):(void 0===(o=(0,r.A)({},e)).pathname&&(o.pathname=""),o.search?"?"!==o.search.charAt(0)&&(o.search="?"+o.search):o.search="",o.hash?"#"!==o.hash.charAt(0)&&(o.hash="#"+o.hash):o.hash="",void 0!==t&&void 0===o.state&&(o.state=t));try{o.pathname=decodeURI(o.pathname)}catch(l){throw l instanceof URIError?new URIError('Pathname "'+o.pathname+'" could not be decoded. This is likely caused by an invalid percent-encoding.'):l}return n&&(o.key=n),a?o.pathname?"/"!==o.pathname.charAt(0)&&(o.pathname=i(o.pathname,a.pathname)):o.pathname=a.pathname:o.pathname||(o.pathname="/"),o}function m(){var e=null;var t=[];return{setPrompt:function(t){return e=t,function(){e===t&&(e=null)}},confirmTransitionTo:function(t,n,r,a){if(null!=e){var o="function"==typeof e?e(t,n):e;"string"==typeof o?"function"==typeof r?r(o,a):a(!0):a(!1!==o)}else a(!0)},appendListener:function(e){var n=!0;function r(){n&&e.apply(void 0,arguments)}return t.push(r),function(){n=!1,t=t.filter((function(e){return e!==r}))}},notifyListeners:function(){for(var e=arguments.length,n=new Array(e),r=0;r<e;r++)n[r]=arguments[r];t.forEach((function(e){return e.apply(void 0,n)}))}}}var g=!("undefined"==typeof window||!window.document||!window.document.createElement);function h(e,t){t(window.confirm(e))}var y="popstate",b="hashchange";function v(){try{return window.history.state||{}}catch(e){return{}}}function w(e){void 0===e&&(e={}),g||(0,l.A)(!1);var t,n=window.history,a=(-1===(t=window.navigator.userAgent).indexOf("Android 2.")&&-1===t.indexOf("Android 4.0")||-1===t.indexOf("Mobile Safari")||-1!==t.indexOf("Chrome")||-1!==t.indexOf("Windows Phone"))&&window.history&&"pushState"in window.history,o=!(-1===window.navigator.userAgent.indexOf("Trident")),i=e,c=i.forceRefresh,w=void 0!==c&&c,k=i.getUserConfirmation,x=void 0===k?h:k,S=i.keyLength,E=void 0===S?6:S,C=e.basename?d(s(e.basename)):"";function _(e){var t=e||{},n=t.key,r=t.state,a=window.location,o=a.pathname+a.search+a.hash;return C&&(o=u(o,C)),f(o,r,n)}function A(){return Math.random().toString(36).substr(2,E)}var T=m();function j(e){(0,r.A)($,e),$.length=n.length,T.notifyListeners($.location,$.action)}function L(e){(function(e){return void 0===e.state&&-1===navigator.userAgent.indexOf("CriOS")})(e)||P(_(e.state))}function N(){P(_(v()))}var R=!1;function P(e){if(R)R=!1,j();else{T.confirmTransitionTo(e,"POP",x,(function(t){t?j({action:"POP",location:e}):function(e){var t=$.location,n=D.indexOf(t.key);-1===n&&(n=0);var r=D.indexOf(e.key);-1===r&&(r=0);var a=n-r;a&&(R=!0,I(a))}(e)}))}}var O=_(v()),D=[O.key];function M(e){return C+p(e)}function I(e){n.go(e)}var F=0;function z(e){1===(F+=e)&&1===e?(window.addEventListener(y,L),o&&window.addEventListener(b,N)):0===F&&(window.removeEventListener(y,L),o&&window.removeEventListener(b,N))}var B=!1;var $={length:n.length,action:"POP",location:O,createHref:M,push:function(e,t){var r="PUSH",o=f(e,t,A(),$.location);T.confirmTransitionTo(o,r,x,(function(e){if(e){var t=M(o),i=o.key,l=o.state;if(a)if(n.pushState({key:i,state:l},null,t),w)window.location.href=t;else{var s=D.indexOf($.location.key),c=D.slice(0,s+1);c.push(o.key),D=c,j({action:r,location:o})}else window.location.href=t}}))},replace:function(e,t){var r="REPLACE",o=f(e,t,A(),$.location);T.confirmTransitionTo(o,r,x,(function(e){if(e){var t=M(o),i=o.key,l=o.state;if(a)if(n.replaceState({key:i,state:l},null,t),w)window.location.replace(t);else{var s=D.indexOf($.location.key);-1!==s&&(D[s]=o.key),j({action:r,location:o})}else window.location.replace(t)}}))},go:I,goBack:function(){I(-1)},goForward:function(){I(1)},block:function(e){void 0===e&&(e=!1);var t=T.setPrompt(e);return B||(z(1),B=!0),function(){return B&&(B=!1,z(-1)),t()}},listen:function(e){var t=T.appendListener(e);return z(1),function(){z(-1),t()}}};return $}var k="hashchange",x={hashbang:{encodePath:function(e){return"!"===e.charAt(0)?e:"!/"+c(e)},decodePath:function(e){return"!"===e.charAt(0)?e.substr(1):e}},noslash:{encodePath:c,decodePath:s},slash:{encodePath:s,decodePath:s}};function S(e){var t=e.indexOf("#");return-1===t?e:e.slice(0,t)}function E(){var e=window.location.href,t=e.indexOf("#");return-1===t?"":e.substring(t+1)}function C(e){window.location.replace(S(window.location.href)+"#"+e)}function _(e){void 0===e&&(e={}),g||(0,l.A)(!1);var t=window.history,n=(window.navigator.userAgent.indexOf("Firefox"),e),a=n.getUserConfirmation,o=void 0===a?h:a,i=n.hashType,c=void 0===i?"slash":i,y=e.basename?d(s(e.basename)):"",b=x[c],v=b.encodePath,w=b.decodePath;function _(){var e=w(E());return y&&(e=u(e,y)),f(e)}var A=m();function T(e){(0,r.A)(B,e),B.length=t.length,A.notifyListeners(B.location,B.action)}var j=!1,L=null;function N(){var e,t,n=E(),r=v(n);if(n!==r)C(r);else{var a=_(),i=B.location;if(!j&&(t=a,(e=i).pathname===t.pathname&&e.search===t.search&&e.hash===t.hash))return;if(L===p(a))return;L=null,function(e){if(j)j=!1,T();else{var t="POP";A.confirmTransitionTo(e,t,o,(function(n){n?T({action:t,location:e}):function(e){var t=B.location,n=D.lastIndexOf(p(t));-1===n&&(n=0);var r=D.lastIndexOf(p(e));-1===r&&(r=0);var a=n-r;a&&(j=!0,M(a))}(e)}))}}(a)}}var R=E(),P=v(R);R!==P&&C(P);var O=_(),D=[p(O)];function M(e){t.go(e)}var I=0;function F(e){1===(I+=e)&&1===e?window.addEventListener(k,N):0===I&&window.removeEventListener(k,N)}var z=!1;var B={length:t.length,action:"POP",location:O,createHref:function(e){var t=document.querySelector("base"),n="";return t&&t.getAttribute("href")&&(n=S(window.location.href)),n+"#"+v(y+p(e))},push:function(e,t){var n="PUSH",r=f(e,void 0,void 0,B.location);A.confirmTransitionTo(r,n,o,(function(e){if(e){var t=p(r),a=v(y+t);if(E()!==a){L=t,function(e){window.location.hash=e}(a);var o=D.lastIndexOf(p(B.location)),i=D.slice(0,o+1);i.push(t),D=i,T({action:n,location:r})}else T()}}))},replace:function(e,t){var n="REPLACE",r=f(e,void 0,void 0,B.location);A.confirmTransitionTo(r,n,o,(function(e){if(e){var t=p(r),a=v(y+t);E()!==a&&(L=t,C(a));var o=D.indexOf(p(B.location));-1!==o&&(D[o]=t),T({action:n,location:r})}}))},go:M,goBack:function(){M(-1)},goForward:function(){M(1)},block:function(e){void 0===e&&(e=!1);var t=A.setPrompt(e);return z||(F(1),z=!0),function(){return z&&(z=!1,F(-1)),t()}},listen:function(e){var t=A.appendListener(e);return F(1),function(){F(-1),t()}}};return B}function A(e,t,n){return Math.min(Math.max(e,t),n)}function T(e){void 0===e&&(e={});var t=e,n=t.getUserConfirmation,a=t.initialEntries,o=void 0===a?["/"]:a,i=t.initialIndex,l=void 0===i?0:i,s=t.keyLength,c=void 0===s?6:s,u=m();function d(e){(0,r.A)(w,e),w.length=w.entries.length,u.notifyListeners(w.location,w.action)}function g(){return Math.random().toString(36).substr(2,c)}var h=A(l,0,o.length-1),y=o.map((function(e){return f(e,void 0,"string"==typeof e?g():e.key||g())})),b=p;function v(e){var t=A(w.index+e,0,w.entries.length-1),r=w.entries[t];u.confirmTransitionTo(r,"POP",n,(function(e){e?d({action:"POP",location:r,index:t}):d()}))}var w={length:y.length,action:"POP",location:y[h],index:h,entries:y,createHref:b,push:function(e,t){var r="PUSH",a=f(e,t,g(),w.location);u.confirmTransitionTo(a,r,n,(function(e){if(e){var t=w.index+1,n=w.entries.slice(0);n.length>t?n.splice(t,n.length-t,a):n.push(a),d({action:r,location:a,index:t,entries:n})}}))},replace:function(e,t){var r="REPLACE",a=f(e,t,g(),w.location);u.confirmTransitionTo(a,r,n,(function(e){e&&(w.entries[w.index]=a,d({action:r,location:a}))}))},go:v,goBack:function(){v(-1)},goForward:function(){v(1)},canGo:function(e){var t=w.index+e;return t>=0&&t<w.entries.length},block:function(e){return void 0===e&&(e=!1),u.setPrompt(e)},listen:function(e){return u.appendListener(e)}};return w}},4146:(e,t,n)=>{"use strict";var r=n(4363),a={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},o={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},i={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},l={};function s(e){return r.isMemo(e)?i:l[e.$$typeof]||a}l[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},l[r.Memo]=i;var c=Object.defineProperty,u=Object.getOwnPropertyNames,d=Object.getOwnPropertySymbols,p=Object.getOwnPropertyDescriptor,f=Object.getPrototypeOf,m=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){if(m){var a=f(n);a&&a!==m&&e(t,a,r)}var i=u(n);d&&(i=i.concat(d(n)));for(var l=s(t),g=s(n),h=0;h<i.length;++h){var y=i[h];if(!(o[y]||r&&r[y]||g&&g[y]||l&&l[y])){var b=p(n,y);try{c(t,y,b)}catch(v){}}}}return t}},311:e=>{"use strict";e.exports=function(e,t,n,r,a,o,i,l){if(!e){var s;if(void 0===t)s=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var c=[n,r,a,o,i,l],u=0;(s=new Error(t.replace(/%s/g,(function(){return c[u++]})))).name="Invariant Violation"}throw s.framesToPop=1,s}}},4634:e=>{e.exports=Array.isArray||function(e){return"[object Array]"==Object.prototype.toString.call(e)}},119:(e,t,n)=>{"use strict";n.r(t)},1043:(e,t,n)=>{"use strict";n.r(t)},5947:function(e,t,n){var r,a;r=function(){var e,t,n={version:"0.2.0"},r=n.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:'<div class="bar" role="bar"><div class="peg"></div></div><div class="spinner" role="spinner"><div class="spinner-icon"></div></div>'};function a(e,t,n){return e<t?t:e>n?n:e}function o(e){return 100*(-1+e)}function i(e,t,n){var a;return(a="translate3d"===r.positionUsing?{transform:"translate3d("+o(e)+"%,0,0)"}:"translate"===r.positionUsing?{transform:"translate("+o(e)+"%,0)"}:{"margin-left":o(e)+"%"}).transition="all "+t+"ms "+n,a}n.configure=function(e){var t,n;for(t in e)void 0!==(n=e[t])&&e.hasOwnProperty(t)&&(r[t]=n);return this},n.status=null,n.set=function(e){var t=n.isStarted();e=a(e,r.minimum,1),n.status=1===e?null:e;var o=n.render(!t),c=o.querySelector(r.barSelector),u=r.speed,d=r.easing;return o.offsetWidth,l((function(t){""===r.positionUsing&&(r.positionUsing=n.getPositioningCSS()),s(c,i(e,u,d)),1===e?(s(o,{transition:"none",opacity:1}),o.offsetWidth,setTimeout((function(){s(o,{transition:"all "+u+"ms linear",opacity:0}),setTimeout((function(){n.remove(),t()}),u)}),u)):setTimeout(t,u)})),this},n.isStarted=function(){return"number"==typeof n.status},n.start=function(){n.status||n.set(0);var e=function(){setTimeout((function(){n.status&&(n.trickle(),e())}),r.trickleSpeed)};return r.trickle&&e(),this},n.done=function(e){return e||n.status?n.inc(.3+.5*Math.random()).set(1):this},n.inc=function(e){var t=n.status;return t?("number"!=typeof e&&(e=(1-t)*a(Math.random()*t,.1,.95)),t=a(t+e,0,.994),n.set(t)):n.start()},n.trickle=function(){return n.inc(Math.random()*r.trickleRate)},e=0,t=0,n.promise=function(r){return r&&"resolved"!==r.state()?(0===t&&n.start(),e++,t++,r.always((function(){0==--t?(e=0,n.done()):n.set((e-t)/e)})),this):this},n.render=function(e){if(n.isRendered())return document.getElementById("nprogress");u(document.documentElement,"nprogress-busy");var t=document.createElement("div");t.id="nprogress",t.innerHTML=r.template;var a,i=t.querySelector(r.barSelector),l=e?"-100":o(n.status||0),c=document.querySelector(r.parent);return s(i,{transition:"all 0 linear",transform:"translate3d("+l+"%,0,0)"}),r.showSpinner||(a=t.querySelector(r.spinnerSelector))&&f(a),c!=document.body&&u(c,"nprogress-custom-parent"),c.appendChild(t),t},n.remove=function(){d(document.documentElement,"nprogress-busy"),d(document.querySelector(r.parent),"nprogress-custom-parent");var e=document.getElementById("nprogress");e&&f(e)},n.isRendered=function(){return!!document.getElementById("nprogress")},n.getPositioningCSS=function(){var e=document.body.style,t="WebkitTransform"in e?"Webkit":"MozTransform"in e?"Moz":"msTransform"in e?"ms":"OTransform"in e?"O":"";return t+"Perspective"in e?"translate3d":t+"Transform"in e?"translate":"margin"};var l=function(){var e=[];function t(){var n=e.shift();n&&n(t)}return function(n){e.push(n),1==e.length&&t()}}(),s=function(){var e=["Webkit","O","Moz","ms"],t={};function n(e){return e.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,(function(e,t){return t.toUpperCase()}))}function r(t){var n=document.body.style;if(t in n)return t;for(var r,a=e.length,o=t.charAt(0).toUpperCase()+t.slice(1);a--;)if((r=e[a]+o)in n)return r;return t}function a(e){return e=n(e),t[e]||(t[e]=r(e))}function o(e,t,n){t=a(t),e.style[t]=n}return function(e,t){var n,r,a=arguments;if(2==a.length)for(n in t)void 0!==(r=t[n])&&t.hasOwnProperty(n)&&o(e,n,r);else o(e,a[1],a[2])}}();function c(e,t){return("string"==typeof e?e:p(e)).indexOf(" "+t+" ")>=0}function u(e,t){var n=p(e),r=n+t;c(n,t)||(e.className=r.substring(1))}function d(e,t){var n,r=p(e);c(e,t)&&(n=r.replace(" "+t+" "," "),e.className=n.substring(1,n.length-1))}function p(e){return(" "+(e.className||"")+" ").replace(/\s+/gi," ")}function f(e){e&&e.parentNode&&e.parentNode.removeChild(e)}return n},void 0===(a="function"==typeof r?r.call(t,n,t,e):r)||(e.exports=a)},6969:e=>{e.exports&&(e.exports={core:{meta:{path:"components/prism-core.js",option:"mandatory"},core:"Core"},themes:{meta:{path:"themes/{id}.css",link:"index.html?theme={id}",exclusive:!0},prism:{title:"Default",option:"default"},"prism-dark":"Dark","prism-funky":"Funky","prism-okaidia":{title:"Okaidia",owner:"ocodia"},"prism-twilight":{title:"Twilight",owner:"remybach"},"prism-coy":{title:"Coy",owner:"tshedor"},"prism-solarizedlight":{title:"Solarized Light",owner:"hectormatos2011 "},"prism-tomorrow":{title:"Tomorrow Night",owner:"Rosey"}},languages:{meta:{path:"components/prism-{id}",noCSS:!0,examplesPath:"examples/prism-{id}",addCheckAll:!0},markup:{title:"Markup",alias:["html","xml","svg","mathml","ssml","atom","rss"],aliasTitles:{html:"HTML",xml:"XML",svg:"SVG",mathml:"MathML",ssml:"SSML",atom:"Atom",rss:"RSS"},option:"default"},css:{title:"CSS",option:"default",modify:"markup"},clike:{title:"C-like",option:"default"},javascript:{title:"JavaScript",require:"clike",modify:"markup",optional:"regex",alias:"js",option:"default"},abap:{title:"ABAP",owner:"dellagustin"},abnf:{title:"ABNF",owner:"RunDevelopment"},actionscript:{title:"ActionScript",require:"javascript",modify:"markup",owner:"Golmote"},ada:{title:"Ada",owner:"Lucretia"},agda:{title:"Agda",owner:"xy-ren"},al:{title:"AL",owner:"RunDevelopment"},antlr4:{title:"ANTLR4",alias:"g4",owner:"RunDevelopment"},apacheconf:{title:"Apache Configuration",owner:"GuiTeK"},apex:{title:"Apex",require:["clike","sql"],owner:"RunDevelopment"},apl:{title:"APL",owner:"ngn"},applescript:{title:"AppleScript",owner:"Golmote"},aql:{title:"AQL",owner:"RunDevelopment"},arduino:{title:"Arduino",require:"cpp",alias:"ino",owner:"dkern"},arff:{title:"ARFF",owner:"Golmote"},armasm:{title:"ARM Assembly",alias:"arm-asm",owner:"RunDevelopment"},arturo:{title:"Arturo",alias:"art",optional:["bash","css","javascript","markup","markdown","sql"],owner:"drkameleon"},asciidoc:{alias:"adoc",title:"AsciiDoc",owner:"Golmote"},aspnet:{title:"ASP.NET (C#)",require:["markup","csharp"],owner:"nauzilus"},asm6502:{title:"6502 Assembly",owner:"kzurawel"},asmatmel:{title:"Atmel AVR Assembly",owner:"cerkit"},autohotkey:{title:"AutoHotkey",owner:"aviaryan"},autoit:{title:"AutoIt",owner:"Golmote"},avisynth:{title:"AviSynth",alias:"avs",owner:"Zinfidel"},"avro-idl":{title:"Avro IDL",alias:"avdl",owner:"RunDevelopment"},awk:{title:"AWK",alias:"gawk",aliasTitles:{gawk:"GAWK"},owner:"RunDevelopment"},bash:{title:"Bash",alias:["sh","shell"],aliasTitles:{sh:"Shell",shell:"Shell"},owner:"zeitgeist87"},basic:{title:"BASIC",owner:"Golmote"},batch:{title:"Batch",owner:"Golmote"},bbcode:{title:"BBcode",alias:"shortcode",aliasTitles:{shortcode:"Shortcode"},owner:"RunDevelopment"},bbj:{title:"BBj",owner:"hyyan"},bicep:{title:"Bicep",owner:"johnnyreilly"},birb:{title:"Birb",require:"clike",owner:"Calamity210"},bison:{title:"Bison",require:"c",owner:"Golmote"},bnf:{title:"BNF",alias:"rbnf",aliasTitles:{rbnf:"RBNF"},owner:"RunDevelopment"},bqn:{title:"BQN",owner:"yewscion"},brainfuck:{title:"Brainfuck",owner:"Golmote"},brightscript:{title:"BrightScript",owner:"RunDevelopment"},bro:{title:"Bro",owner:"wayward710"},bsl:{title:"BSL (1C:Enterprise)",alias:"oscript",aliasTitles:{oscript:"OneScript"},owner:"Diversus23"},c:{title:"C",require:"clike",owner:"zeitgeist87"},csharp:{title:"C#",require:"clike",alias:["cs","dotnet"],owner:"mvalipour"},cpp:{title:"C++",require:"c",owner:"zeitgeist87"},cfscript:{title:"CFScript",require:"clike",alias:"cfc",owner:"mjclemente"},chaiscript:{title:"ChaiScript",require:["clike","cpp"],owner:"RunDevelopment"},cil:{title:"CIL",owner:"sbrl"},cilkc:{title:"Cilk/C",require:"c",alias:"cilk-c",owner:"OpenCilk"},cilkcpp:{title:"Cilk/C++",require:"cpp",alias:["cilk-cpp","cilk"],owner:"OpenCilk"},clojure:{title:"Clojure",owner:"troglotit"},cmake:{title:"CMake",owner:"mjrogozinski"},cobol:{title:"COBOL",owner:"RunDevelopment"},coffeescript:{title:"CoffeeScript",require:"javascript",alias:"coffee",owner:"R-osey"},concurnas:{title:"Concurnas",alias:"conc",owner:"jasontatton"},csp:{title:"Content-Security-Policy",owner:"ScottHelme"},cooklang:{title:"Cooklang",owner:"ahue"},coq:{title:"Coq",owner:"RunDevelopment"},crystal:{title:"Crystal",require:"ruby",owner:"MakeNowJust"},"css-extras":{title:"CSS Extras",require:"css",modify:"css",owner:"milesj"},csv:{title:"CSV",owner:"RunDevelopment"},cue:{title:"CUE",owner:"RunDevelopment"},cypher:{title:"Cypher",owner:"RunDevelopment"},d:{title:"D",require:"clike",owner:"Golmote"},dart:{title:"Dart",require:"clike",owner:"Golmote"},dataweave:{title:"DataWeave",owner:"machaval"},dax:{title:"DAX",owner:"peterbud"},dhall:{title:"Dhall",owner:"RunDevelopment"},diff:{title:"Diff",owner:"uranusjr"},django:{title:"Django/Jinja2",require:"markup-templating",alias:"jinja2",owner:"romanvm"},"dns-zone-file":{title:"DNS zone file",owner:"RunDevelopment",alias:"dns-zone"},docker:{title:"Docker",alias:"dockerfile",owner:"JustinBeckwith"},dot:{title:"DOT (Graphviz)",alias:"gv",optional:"markup",owner:"RunDevelopment"},ebnf:{title:"EBNF",owner:"RunDevelopment"},editorconfig:{title:"EditorConfig",owner:"osipxd"},eiffel:{title:"Eiffel",owner:"Conaclos"},ejs:{title:"EJS",require:["javascript","markup-templating"],owner:"RunDevelopment",alias:"eta",aliasTitles:{eta:"Eta"}},elixir:{title:"Elixir",owner:"Golmote"},elm:{title:"Elm",owner:"zwilias"},etlua:{title:"Embedded Lua templating",require:["lua","markup-templating"],owner:"RunDevelopment"},erb:{title:"ERB",require:["ruby","markup-templating"],owner:"Golmote"},erlang:{title:"Erlang",owner:"Golmote"},"excel-formula":{title:"Excel Formula",alias:["xlsx","xls"],owner:"RunDevelopment"},fsharp:{title:"F#",require:"clike",owner:"simonreynolds7"},factor:{title:"Factor",owner:"catb0t"},false:{title:"False",owner:"edukisto"},"firestore-security-rules":{title:"Firestore security rules",require:"clike",owner:"RunDevelopment"},flow:{title:"Flow",require:"javascript",owner:"Golmote"},fortran:{title:"Fortran",owner:"Golmote"},ftl:{title:"FreeMarker Template Language",require:"markup-templating",owner:"RunDevelopment"},gml:{title:"GameMaker Language",alias:"gamemakerlanguage",require:"clike",owner:"LiarOnce"},gap:{title:"GAP (CAS)",owner:"RunDevelopment"},gcode:{title:"G-code",owner:"RunDevelopment"},gdscript:{title:"GDScript",owner:"RunDevelopment"},gedcom:{title:"GEDCOM",owner:"Golmote"},gettext:{title:"gettext",alias:"po",owner:"RunDevelopment"},gherkin:{title:"Gherkin",owner:"hason"},git:{title:"Git",owner:"lgiraudel"},glsl:{title:"GLSL",require:"c",owner:"Golmote"},gn:{title:"GN",alias:"gni",owner:"RunDevelopment"},"linker-script":{title:"GNU Linker Script",alias:"ld",owner:"RunDevelopment"},go:{title:"Go",require:"clike",owner:"arnehormann"},"go-module":{title:"Go module",alias:"go-mod",owner:"RunDevelopment"},gradle:{title:"Gradle",require:"clike",owner:"zeabdelkhalek-badido18"},graphql:{title:"GraphQL",optional:"markdown",owner:"Golmote"},groovy:{title:"Groovy",require:"clike",owner:"robfletcher"},haml:{title:"Haml",require:"ruby",optional:["css","css-extras","coffeescript","erb","javascript","less","markdown","scss","textile"],owner:"Golmote"},handlebars:{title:"Handlebars",require:"markup-templating",alias:["hbs","mustache"],aliasTitles:{mustache:"Mustache"},owner:"Golmote"},haskell:{title:"Haskell",alias:"hs",owner:"bholst"},haxe:{title:"Haxe",require:"clike",optional:"regex",owner:"Golmote"},hcl:{title:"HCL",owner:"outsideris"},hlsl:{title:"HLSL",require:"c",owner:"RunDevelopment"},hoon:{title:"Hoon",owner:"matildepark"},http:{title:"HTTP",optional:["csp","css","hpkp","hsts","javascript","json","markup","uri"],owner:"danielgtaylor"},hpkp:{title:"HTTP Public-Key-Pins",owner:"ScottHelme"},hsts:{title:"HTTP Strict-Transport-Security",owner:"ScottHelme"},ichigojam:{title:"IchigoJam",owner:"BlueCocoa"},icon:{title:"Icon",owner:"Golmote"},"icu-message-format":{title:"ICU Message Format",owner:"RunDevelopment"},idris:{title:"Idris",alias:"idr",owner:"KeenS",require:"haskell"},ignore:{title:".ignore",owner:"osipxd",alias:["gitignore","hgignore","npmignore"],aliasTitles:{gitignore:".gitignore",hgignore:".hgignore",npmignore:".npmignore"}},inform7:{title:"Inform 7",owner:"Golmote"},ini:{title:"Ini",owner:"aviaryan"},io:{title:"Io",owner:"AlesTsurko"},j:{title:"J",owner:"Golmote"},java:{title:"Java",require:"clike",owner:"sherblot"},javadoc:{title:"JavaDoc",require:["markup","java","javadoclike"],modify:"java",optional:"scala",owner:"RunDevelopment"},javadoclike:{title:"JavaDoc-like",modify:["java","javascript","php"],owner:"RunDevelopment"},javastacktrace:{title:"Java stack trace",owner:"RunDevelopment"},jexl:{title:"Jexl",owner:"czosel"},jolie:{title:"Jolie",require:"clike",owner:"thesave"},jq:{title:"JQ",owner:"RunDevelopment"},jsdoc:{title:"JSDoc",require:["javascript","javadoclike","typescript"],modify:"javascript",optional:["actionscript","coffeescript"],owner:"RunDevelopment"},"js-extras":{title:"JS Extras",require:"javascript",modify:"javascript",optional:["actionscript","coffeescript","flow","n4js","typescript"],owner:"RunDevelopment"},json:{title:"JSON",alias:"webmanifest",aliasTitles:{webmanifest:"Web App Manifest"},owner:"CupOfTea696"},json5:{title:"JSON5",require:"json",owner:"RunDevelopment"},jsonp:{title:"JSONP",require:"json",owner:"RunDevelopment"},jsstacktrace:{title:"JS stack trace",owner:"sbrl"},"js-templates":{title:"JS Templates",require:"javascript",modify:"javascript",optional:["css","css-extras","graphql","markdown","markup","sql"],owner:"RunDevelopment"},julia:{title:"Julia",owner:"cdagnino"},keepalived:{title:"Keepalived Configure",owner:"dev-itsheng"},keyman:{title:"Keyman",owner:"mcdurdin"},kotlin:{title:"Kotlin",alias:["kt","kts"],aliasTitles:{kts:"Kotlin Script"},require:"clike",owner:"Golmote"},kumir:{title:"KuMir (\u041a\u0443\u041c\u0438\u0440)",alias:"kum",owner:"edukisto"},kusto:{title:"Kusto",owner:"RunDevelopment"},latex:{title:"LaTeX",alias:["tex","context"],aliasTitles:{tex:"TeX",context:"ConTeXt"},owner:"japborst"},latte:{title:"Latte",require:["clike","markup-templating","php"],owner:"nette"},less:{title:"Less",require:"css",optional:"css-extras",owner:"Golmote"},lilypond:{title:"LilyPond",require:"scheme",alias:"ly",owner:"RunDevelopment"},liquid:{title:"Liquid",require:"markup-templating",owner:"cinhtau"},lisp:{title:"Lisp",alias:["emacs","elisp","emacs-lisp"],owner:"JuanCaicedo"},livescript:{title:"LiveScript",owner:"Golmote"},llvm:{title:"LLVM IR",owner:"porglezomp"},log:{title:"Log file",optional:"javastacktrace",owner:"RunDevelopment"},lolcode:{title:"LOLCODE",owner:"Golmote"},lua:{title:"Lua",owner:"Golmote"},magma:{title:"Magma (CAS)",owner:"RunDevelopment"},makefile:{title:"Makefile",owner:"Golmote"},markdown:{title:"Markdown",require:"markup",optional:"yaml",alias:"md",owner:"Golmote"},"markup-templating":{title:"Markup templating",require:"markup",owner:"Golmote"},mata:{title:"Mata",owner:"RunDevelopment"},matlab:{title:"MATLAB",owner:"Golmote"},maxscript:{title:"MAXScript",owner:"RunDevelopment"},mel:{title:"MEL",owner:"Golmote"},mermaid:{title:"Mermaid",owner:"RunDevelopment"},metafont:{title:"METAFONT",owner:"LaeriExNihilo"},mizar:{title:"Mizar",owner:"Golmote"},mongodb:{title:"MongoDB",owner:"airs0urce",require:"javascript"},monkey:{title:"Monkey",owner:"Golmote"},moonscript:{title:"MoonScript",alias:"moon",owner:"RunDevelopment"},n1ql:{title:"N1QL",owner:"TMWilds"},n4js:{title:"N4JS",require:"javascript",optional:"jsdoc",alias:"n4jsd",owner:"bsmith-n4"},"nand2tetris-hdl":{title:"Nand To Tetris HDL",owner:"stephanmax"},naniscript:{title:"Naninovel Script",owner:"Elringus",alias:"nani"},nasm:{title:"NASM",owner:"rbmj"},neon:{title:"NEON",owner:"nette"},nevod:{title:"Nevod",owner:"nezaboodka"},nginx:{title:"nginx",owner:"volado"},nim:{title:"Nim",owner:"Golmote"},nix:{title:"Nix",owner:"Golmote"},nsis:{title:"NSIS",owner:"idleberg"},objectivec:{title:"Objective-C",require:"c",alias:"objc",owner:"uranusjr"},ocaml:{title:"OCaml",owner:"Golmote"},odin:{title:"Odin",owner:"edukisto"},opencl:{title:"OpenCL",require:"c",modify:["c","cpp"],owner:"Milania1"},openqasm:{title:"OpenQasm",alias:"qasm",owner:"RunDevelopment"},oz:{title:"Oz",owner:"Golmote"},parigp:{title:"PARI/GP",owner:"Golmote"},parser:{title:"Parser",require:"markup",owner:"Golmote"},pascal:{title:"Pascal",alias:"objectpascal",aliasTitles:{objectpascal:"Object Pascal"},owner:"Golmote"},pascaligo:{title:"Pascaligo",owner:"DefinitelyNotAGoat"},psl:{title:"PATROL Scripting Language",owner:"bertysentry"},pcaxis:{title:"PC-Axis",alias:"px",owner:"RunDevelopment"},peoplecode:{title:"PeopleCode",alias:"pcode",owner:"RunDevelopment"},perl:{title:"Perl",owner:"Golmote"},php:{title:"PHP",require:"markup-templating",owner:"milesj"},phpdoc:{title:"PHPDoc",require:["php","javadoclike"],modify:"php",owner:"RunDevelopment"},"php-extras":{title:"PHP Extras",require:"php",modify:"php",owner:"milesj"},"plant-uml":{title:"PlantUML",alias:"plantuml",owner:"RunDevelopment"},plsql:{title:"PL/SQL",require:"sql",owner:"Golmote"},powerquery:{title:"PowerQuery",alias:["pq","mscript"],owner:"peterbud"},powershell:{title:"PowerShell",owner:"nauzilus"},processing:{title:"Processing",require:"clike",owner:"Golmote"},prolog:{title:"Prolog",owner:"Golmote"},promql:{title:"PromQL",owner:"arendjr"},properties:{title:".properties",owner:"Golmote"},protobuf:{title:"Protocol Buffers",require:"clike",owner:"just-boris"},pug:{title:"Pug",require:["markup","javascript"],optional:["coffeescript","ejs","handlebars","less","livescript","markdown","scss","stylus","twig"],owner:"Golmote"},puppet:{title:"Puppet",owner:"Golmote"},pure:{title:"Pure",optional:["c","cpp","fortran"],owner:"Golmote"},purebasic:{title:"PureBasic",require:"clike",alias:"pbfasm",owner:"HeX0R101"},purescript:{title:"PureScript",require:"haskell",alias:"purs",owner:"sriharshachilakapati"},python:{title:"Python",alias:"py",owner:"multipetros"},qsharp:{title:"Q#",require:"clike",alias:"qs",owner:"fedonman"},q:{title:"Q (kdb+ database)",owner:"Golmote"},qml:{title:"QML",require:"javascript",owner:"RunDevelopment"},qore:{title:"Qore",require:"clike",owner:"temnroegg"},r:{title:"R",owner:"Golmote"},racket:{title:"Racket",require:"scheme",alias:"rkt",owner:"RunDevelopment"},cshtml:{title:"Razor C#",alias:"razor",require:["markup","csharp"],optional:["css","css-extras","javascript","js-extras"],owner:"RunDevelopment"},jsx:{title:"React JSX",require:["markup","javascript"],optional:["jsdoc","js-extras","js-templates"],owner:"vkbansal"},tsx:{title:"React TSX",require:["jsx","typescript"]},reason:{title:"Reason",require:"clike",owner:"Golmote"},regex:{title:"Regex",owner:"RunDevelopment"},rego:{title:"Rego",owner:"JordanSh"},renpy:{title:"Ren'py",alias:"rpy",owner:"HyuchiaDiego"},rescript:{title:"ReScript",alias:"res",owner:"vmarcosp"},rest:{title:"reST (reStructuredText)",owner:"Golmote"},rip:{title:"Rip",owner:"ravinggenius"},roboconf:{title:"Roboconf",owner:"Golmote"},robotframework:{title:"Robot Framework",alias:"robot",owner:"RunDevelopment"},ruby:{title:"Ruby",require:"clike",alias:"rb",owner:"samflores"},rust:{title:"Rust",owner:"Golmote"},sas:{title:"SAS",optional:["groovy","lua","sql"],owner:"Golmote"},sass:{title:"Sass (Sass)",require:"css",optional:"css-extras",owner:"Golmote"},scss:{title:"Sass (SCSS)",require:"css",optional:"css-extras",owner:"MoOx"},scala:{title:"Scala",require:"java",owner:"jozic"},scheme:{title:"Scheme",owner:"bacchus123"},"shell-session":{title:"Shell session",require:"bash",alias:["sh-session","shellsession"],owner:"RunDevelopment"},smali:{title:"Smali",owner:"RunDevelopment"},smalltalk:{title:"Smalltalk",owner:"Golmote"},smarty:{title:"Smarty",require:"markup-templating",optional:"php",owner:"Golmote"},sml:{title:"SML",alias:"smlnj",aliasTitles:{smlnj:"SML/NJ"},owner:"RunDevelopment"},solidity:{title:"Solidity (Ethereum)",alias:"sol",require:"clike",owner:"glachaud"},"solution-file":{title:"Solution file",alias:"sln",owner:"RunDevelopment"},soy:{title:"Soy (Closure Template)",require:"markup-templating",owner:"Golmote"},sparql:{title:"SPARQL",require:"turtle",owner:"Triply-Dev",alias:"rq"},"splunk-spl":{title:"Splunk SPL",owner:"RunDevelopment"},sqf:{title:"SQF: Status Quo Function (Arma 3)",require:"clike",owner:"RunDevelopment"},sql:{title:"SQL",owner:"multipetros"},squirrel:{title:"Squirrel",require:"clike",owner:"RunDevelopment"},stan:{title:"Stan",owner:"RunDevelopment"},stata:{title:"Stata Ado",require:["mata","java","python"],owner:"RunDevelopment"},iecst:{title:"Structured Text (IEC 61131-3)",owner:"serhioromano"},stylus:{title:"Stylus",owner:"vkbansal"},supercollider:{title:"SuperCollider",alias:"sclang",owner:"RunDevelopment"},swift:{title:"Swift",owner:"chrischares"},systemd:{title:"Systemd configuration file",owner:"RunDevelopment"},"t4-templating":{title:"T4 templating",owner:"RunDevelopment"},"t4-cs":{title:"T4 Text Templates (C#)",require:["t4-templating","csharp"],alias:"t4",owner:"RunDevelopment"},"t4-vb":{title:"T4 Text Templates (VB)",require:["t4-templating","vbnet"],owner:"RunDevelopment"},tap:{title:"TAP",owner:"isaacs",require:"yaml"},tcl:{title:"Tcl",owner:"PeterChaplin"},tt2:{title:"Template Toolkit 2",require:["clike","markup-templating"],owner:"gflohr"},textile:{title:"Textile",require:"markup",optional:"css",owner:"Golmote"},toml:{title:"TOML",owner:"RunDevelopment"},tremor:{title:"Tremor",alias:["trickle","troy"],owner:"darach",aliasTitles:{trickle:"trickle",troy:"troy"}},turtle:{title:"Turtle",alias:"trig",aliasTitles:{trig:"TriG"},owner:"jakubklimek"},twig:{title:"Twig",require:"markup-templating",owner:"brandonkelly"},typescript:{title:"TypeScript",require:"javascript",optional:"js-templates",alias:"ts",owner:"vkbansal"},typoscript:{title:"TypoScript",alias:"tsconfig",aliasTitles:{tsconfig:"TSConfig"},owner:"dkern"},unrealscript:{title:"UnrealScript",alias:["uscript","uc"],owner:"RunDevelopment"},uorazor:{title:"UO Razor Script",owner:"jaseowns"},uri:{title:"URI",alias:"url",aliasTitles:{url:"URL"},owner:"RunDevelopment"},v:{title:"V",require:"clike",owner:"taggon"},vala:{title:"Vala",require:"clike",optional:"regex",owner:"TemplarVolk"},vbnet:{title:"VB.Net",require:"basic",owner:"Bigsby"},velocity:{title:"Velocity",require:"markup",owner:"Golmote"},verilog:{title:"Verilog",owner:"a-rey"},vhdl:{title:"VHDL",owner:"a-rey"},vim:{title:"vim",owner:"westonganger"},"visual-basic":{title:"Visual Basic",alias:["vb","vba"],aliasTitles:{vba:"VBA"},owner:"Golmote"},warpscript:{title:"WarpScript",owner:"RunDevelopment"},wasm:{title:"WebAssembly",owner:"Golmote"},"web-idl":{title:"Web IDL",alias:"webidl",owner:"RunDevelopment"},wgsl:{title:"WGSL",owner:"Dr4gonthree"},wiki:{title:"Wiki markup",require:"markup",owner:"Golmote"},wolfram:{title:"Wolfram language",alias:["mathematica","nb","wl"],aliasTitles:{mathematica:"Mathematica",nb:"Mathematica Notebook"},owner:"msollami"},wren:{title:"Wren",owner:"clsource"},xeora:{title:"Xeora",require:"markup",alias:"xeoracube",aliasTitles:{xeoracube:"XeoraCube"},owner:"freakmaxi"},"xml-doc":{title:"XML doc (.net)",require:"markup",modify:["csharp","fsharp","vbnet"],owner:"RunDevelopment"},xojo:{title:"Xojo (REALbasic)",owner:"Golmote"},xquery:{title:"XQuery",require:"markup",owner:"Golmote"},yaml:{title:"YAML",alias:"yml",owner:"hason"},yang:{title:"YANG",owner:"RunDevelopment"},zig:{title:"Zig",owner:"RunDevelopment"}},plugins:{meta:{path:"plugins/{id}/prism-{id}",link:"plugins/{id}/"},"line-highlight":{title:"Line Highlight",description:"Highlights specific lines and/or line ranges."},"line-numbers":{title:"Line Numbers",description:"Line number at the beginning of code lines.",owner:"kuba-kubula"},"show-invisibles":{title:"Show Invisibles",description:"Show hidden characters such as tabs and line breaks.",optional:["autolinker","data-uri-highlight"]},autolinker:{title:"Autolinker",description:"Converts URLs and emails in code to clickable links. Parses Markdown links in comments."},wpd:{title:"WebPlatform Docs",description:'Makes tokens link to <a href="https://webplatform.github.io/docs/">WebPlatform.org documentation</a>. The links open in a new tab.'},"custom-class":{title:"Custom Class",description:"This plugin allows you to prefix Prism's default classes (<code>.comment</code> can become <code>.namespace--comment</code>) or replace them with your defined ones (like <code>.editor__comment</code>). You can even add new classes.",owner:"dvkndn",noCSS:!0},"file-highlight":{title:"File Highlight",description:"Fetch external files and highlight them with Prism. Used on the Prism website itself.",noCSS:!0},"show-language":{title:"Show Language",description:"Display the highlighted language in code blocks (inline code does not show the label).",owner:"nauzilus",noCSS:!0,require:"toolbar"},"jsonp-highlight":{title:"JSONP Highlight",description:"Fetch content with JSONP and highlight some interesting content (e.g. GitHub/Gists or Bitbucket API).",noCSS:!0,owner:"nauzilus"},"highlight-keywords":{title:"Highlight Keywords",description:"Adds special CSS classes for each keyword for fine-grained highlighting.",owner:"vkbansal",noCSS:!0},"remove-initial-line-feed":{title:"Remove initial line feed",description:"Removes the initial line feed in code blocks.",owner:"Golmote",noCSS:!0},"inline-color":{title:"Inline color",description:"Adds a small inline preview for colors in style sheets.",require:"css-extras",owner:"RunDevelopment"},previewers:{title:"Previewers",description:"Previewers for angles, colors, gradients, easing and time.",require:"css-extras",owner:"Golmote"},autoloader:{title:"Autoloader",description:"Automatically loads the needed languages to highlight the code blocks.",owner:"Golmote",noCSS:!0},"keep-markup":{title:"Keep Markup",description:"Prevents custom markup from being dropped out during highlighting.",owner:"Golmote",optional:"normalize-whitespace",noCSS:!0},"command-line":{title:"Command Line",description:"Display a command line with a prompt and, optionally, the output/response from the commands.",owner:"chriswells0"},"unescaped-markup":{title:"Unescaped Markup",description:"Write markup without having to escape anything."},"normalize-whitespace":{title:"Normalize Whitespace",description:"Supports multiple operations to normalize whitespace in code blocks.",owner:"zeitgeist87",optional:"unescaped-markup",noCSS:!0},"data-uri-highlight":{title:"Data-URI Highlight",description:"Highlights data-URI contents.",owner:"Golmote",noCSS:!0},toolbar:{title:"Toolbar",description:"Attach a toolbar for plugins to easily register buttons on the top of a code block.",owner:"mAAdhaTTah"},"copy-to-clipboard":{title:"Copy to Clipboard Button",description:"Add a button that copies the code block to the clipboard when clicked.",owner:"mAAdhaTTah",require:"toolbar",noCSS:!0},"download-button":{title:"Download Button",description:"A button in the toolbar of a code block adding a convenient way to download a code file.",owner:"Golmote",require:"toolbar",noCSS:!0},"match-braces":{title:"Match braces",description:"Highlights matching braces.",owner:"RunDevelopment"},"diff-highlight":{title:"Diff Highlight",description:"Highlights the code inside diff blocks.",owner:"RunDevelopment",require:"diff"},"filter-highlight-all":{title:"Filter highlightAll",description:"Filters the elements the <code>highlightAll</code> and <code>highlightAllUnder</code> methods actually highlight.",owner:"RunDevelopment",noCSS:!0},treeview:{title:"Treeview",description:"A language with special styles to highlight file system tree structures.",owner:"Golmote"}}})},8722:(e,t,n)=>{const r=n(6969),a=n(8380),o=new Set;function i(e){void 0===e?e=Object.keys(r.languages).filter((e=>"meta"!=e)):Array.isArray(e)||(e=[e]);const t=[...o,...Object.keys(Prism.languages)];a(r,e,t).load((e=>{if(!(e in r.languages))return void(i.silent||console.warn("Language does not exist: "+e));const t="./prism-"+e;delete n.c[n(3157).resolve(t)],delete Prism.languages[e],n(3157)(t),o.add(e)}))}i.silent=!1,e.exports=i},9700:()=>{!function(e){function t(e,t){return"___"+e.toUpperCase()+t+"___"}Object.defineProperties(e.languages["markup-templating"]={},{buildPlaceholders:{value:function(n,r,a,o){if(n.language===r){var i=n.tokenStack=[];n.code=n.code.replace(a,(function(e){if("function"==typeof o&&!o(e))return e;for(var a,l=i.length;-1!==n.code.indexOf(a=t(r,l));)++l;return i[l]=e,a})),n.grammar=e.languages.markup}}},tokenizePlaceholders:{value:function(n,r){if(n.language===r&&n.tokenStack){n.grammar=e.languages[r];var a=0,o=Object.keys(n.tokenStack);!function i(l){for(var s=0;s<l.length&&!(a>=o.length);s++){var c=l[s];if("string"==typeof c||c.content&&"string"==typeof c.content){var u=o[a],d=n.tokenStack[u],p="string"==typeof c?c:c.content,f=t(r,u),m=p.indexOf(f);if(m>-1){++a;var g=p.substring(0,m),h=new e.Token(r,e.tokenize(d,n.grammar),"language-"+r,d),y=p.substring(m+f.length),b=[];g&&b.push.apply(b,i([g])),b.push(h),y&&b.push.apply(b,i([y])),"string"==typeof c?l.splice.apply(l,[s,1].concat(b)):c.content=b}}else c.content&&i(c.content)}return l}(n.tokens)}}}})}(Prism)},8692:(e,t,n)=>{var r={"./":8722};function a(e){var t=o(e);return n(t)}function o(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}a.keys=function(){return Object.keys(r)},a.resolve=o,e.exports=a,a.id=8692},3157:(e,t,n)=>{var r={"./":8722};function a(e){var t=o(e);return n(t)}function o(e){if(!n.o(r,e)){var t=new Error("Cannot find module '"+e+"'");throw t.code="MODULE_NOT_FOUND",t}return r[e]}a.keys=function(){return Object.keys(r)},a.resolve=o,e.exports=a,a.id=3157},8380:e=>{"use strict";var t=function(){var e=function(){};function t(e,t){Array.isArray(e)?e.forEach(t):null!=e&&t(e,0)}function n(e){for(var t={},n=0,r=e.length;n<r;n++)t[e[n]]=!0;return t}function r(e){var n={},r=[];function a(r,o){if(!(r in n)){o.push(r);var i=o.indexOf(r);if(i<o.length-1)throw new Error("Circular dependency: "+o.slice(i).join(" -> "));var l={},s=e[r];if(s){function c(t){if(!(t in e))throw new Error(r+" depends on an unknown component "+t);if(!(t in l))for(var i in a(t,o),l[t]=!0,n[t])l[i]=!0}t(s.require,c),t(s.optional,c),t(s.modify,c)}n[r]=l,o.pop()}}return function(e){var t=n[e];return t||(a(e,r),t=n[e]),t}}function a(e){for(var t in e)return!0;return!1}return function(o,i,l){var s=function(e){var t={};for(var n in e){var r=e[n];for(var a in r)if("meta"!=a){var o=r[a];t[a]="string"==typeof o?{title:o}:o}}return t}(o),c=function(e){var n;return function(r){if(r in e)return r;if(!n)for(var a in n={},e){var o=e[a];t(o&&o.alias,(function(t){if(t in n)throw new Error(t+" cannot be alias for both "+a+" and "+n[t]);if(t in e)throw new Error(t+" cannot be alias of "+a+" because it is a component.");n[t]=a}))}return n[r]||r}}(s);i=i.map(c),l=(l||[]).map(c);var u=n(i),d=n(l);i.forEach((function e(n){var r=s[n];t(r&&r.require,(function(t){t in d||(u[t]=!0,e(t))}))}));for(var p,f=r(s),m=u;a(m);){for(var g in p={},m){var h=s[g];t(h&&h.modify,(function(e){e in d&&(p[e]=!0)}))}for(var y in d)if(!(y in u))for(var b in f(y))if(b in u){p[y]=!0;break}for(var v in m=p)u[v]=!0}var w={getIds:function(){var e=[];return w.load((function(t){e.push(t)})),e},load:function(t,n){return function(t,n,r,a){var o=a?a.series:void 0,i=a?a.parallel:e,l={},s={};function c(e){if(e in l)return l[e];s[e]=!0;var a,u=[];for(var d in t(e))d in n&&u.push(d);if(0===u.length)a=r(e);else{var p=i(u.map((function(e){var t=c(e);return delete s[e],t})));o?a=o(p,(function(){return r(e)})):r(e)}return l[e]=a}for(var u in n)c(u);var d=[];for(var p in s)d.push(l[p]);return i(d)}(f,u,t,n)}};return w}}();e.exports=t},2694:(e,t,n)=>{"use strict";var r=n(6925);function a(){}function o(){}o.resetWarningCache=a,e.exports=function(){function e(e,t,n,a,o,i){if(i!==r){var l=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw l.name="Invariant Violation",l}}function t(){return e}e.isRequired=e;var n={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:o,resetWarningCache:a};return n.PropTypes=n,n}},5556:(e,t,n)=>{e.exports=n(2694)()},6925:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},2551:(e,t,n)=>{"use strict";var r=n(6540),a=n(9982);function o(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n<arguments.length;n++)t+="&args[]="+encodeURIComponent(arguments[n]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var i=new Set,l={};function s(e,t){c(e,t),c(e+"Capture",t)}function c(e,t){for(l[e]=t,e=0;e<t.length;e++)i.add(t[e])}var u=!("undefined"==typeof window||void 0===window.document||void 0===window.document.createElement),d=Object.prototype.hasOwnProperty,p=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,f={},m={};function g(e,t,n,r,a,o,i){this.acceptsBooleans=2===t||3===t||4===t,this.attributeName=r,this.attributeNamespace=a,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=o,this.removeEmptyString=i}var h={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach((function(e){h[e]=new g(e,0,!1,e,null,!1,!1)})),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach((function(e){var t=e[0];h[t]=new g(t,1,!1,e[1],null,!1,!1)})),["contentEditable","draggable","spellCheck","value"].forEach((function(e){h[e]=new g(e,2,!1,e.toLowerCase(),null,!1,!1)})),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach((function(e){h[e]=new g(e,2,!1,e,null,!1,!1)})),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach((function(e){h[e]=new g(e,3,!1,e.toLowerCase(),null,!1,!1)})),["checked","multiple","muted","selected"].forEach((function(e){h[e]=new g(e,3,!0,e,null,!1,!1)})),["capture","download"].forEach((function(e){h[e]=new g(e,4,!1,e,null,!1,!1)})),["cols","rows","size","span"].forEach((function(e){h[e]=new g(e,6,!1,e,null,!1,!1)})),["rowSpan","start"].forEach((function(e){h[e]=new g(e,5,!1,e.toLowerCase(),null,!1,!1)}));var y=/[\-:]([a-z])/g;function b(e){return e[1].toUpperCase()}function v(e,t,n,r){var a=h.hasOwnProperty(t)?h[t]:null;(null!==a?0!==a.type:r||!(2<t.length)||"o"!==t[0]&&"O"!==t[0]||"n"!==t[1]&&"N"!==t[1])&&(function(e,t,n,r){if(null==t||function(e,t,n,r){if(null!==n&&0===n.type)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return!r&&(null!==n?!n.acceptsBooleans:"data-"!==(e=e.toLowerCase().slice(0,5))&&"aria-"!==e);default:return!1}}(e,t,n,r))return!0;if(r)return!1;if(null!==n)switch(n.type){case 3:return!t;case 4:return!1===t;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}(t,n,a,r)&&(n=null),r||null===a?function(e){return!!d.call(m,e)||!d.call(f,e)&&(p.test(e)?m[e]=!0:(f[e]=!0,!1))}(t)&&(null===n?e.removeAttribute(t):e.setAttribute(t,""+n)):a.mustUseProperty?e[a.propertyName]=null===n?3!==a.type&&"":n:(t=a.attributeName,r=a.attributeNamespace,null===n?e.removeAttribute(t):(n=3===(a=a.type)||4===a&&!0===n?"":""+n,r?e.setAttributeNS(r,t,n):e.setAttribute(t,n))))}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach((function(e){var t=e.replace(y,b);h[t]=new g(t,1,!1,e,null,!1,!1)})),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach((function(e){var t=e.replace(y,b);h[t]=new g(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)})),["xml:base","xml:lang","xml:space"].forEach((function(e){var t=e.replace(y,b);h[t]=new g(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)})),["tabIndex","crossOrigin"].forEach((function(e){h[e]=new g(e,1,!1,e.toLowerCase(),null,!1,!1)})),h.xlinkHref=new g("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach((function(e){h[e]=new g(e,1,!1,e.toLowerCase(),null,!0,!0)}));var w=r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,k=Symbol.for("react.element"),x=Symbol.for("react.portal"),S=Symbol.for("react.fragment"),E=Symbol.for("react.strict_mode"),C=Symbol.for("react.profiler"),_=Symbol.for("react.provider"),A=Symbol.for("react.context"),T=Symbol.for("react.forward_ref"),j=Symbol.for("react.suspense"),L=Symbol.for("react.suspense_list"),N=Symbol.for("react.memo"),R=Symbol.for("react.lazy");Symbol.for("react.scope"),Symbol.for("react.debug_trace_mode");var P=Symbol.for("react.offscreen");Symbol.for("react.legacy_hidden"),Symbol.for("react.cache"),Symbol.for("react.tracing_marker");var O=Symbol.iterator;function D(e){return null===e||"object"!=typeof e?null:"function"==typeof(e=O&&e[O]||e["@@iterator"])?e:null}var M,I=Object.assign;function F(e){if(void 0===M)try{throw Error()}catch(n){var t=n.stack.trim().match(/\n( *(at )?)/);M=t&&t[1]||""}return"\n"+M+e}var z=!1;function B(e,t){if(!e||z)return"";z=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(t)if(t=function(){throw Error()},Object.defineProperty(t.prototype,"props",{set:function(){throw Error()}}),"object"==typeof Reflect&&Reflect.construct){try{Reflect.construct(t,[])}catch(c){var r=c}Reflect.construct(e,[],t)}else{try{t.call()}catch(c){r=c}e.call(t.prototype)}else{try{throw Error()}catch(c){r=c}e()}}catch(c){if(c&&r&&"string"==typeof c.stack){for(var a=c.stack.split("\n"),o=r.stack.split("\n"),i=a.length-1,l=o.length-1;1<=i&&0<=l&&a[i]!==o[l];)l--;for(;1<=i&&0<=l;i--,l--)if(a[i]!==o[l]){if(1!==i||1!==l)do{if(i--,0>--l||a[i]!==o[l]){var s="\n"+a[i].replace(" at new "," at ");return e.displayName&&s.includes("<anonymous>")&&(s=s.replace("<anonymous>",e.displayName)),s}}while(1<=i&&0<=l);break}}}finally{z=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?F(e):""}function $(e){switch(e.tag){case 5:return F(e.type);case 16:return F("Lazy");case 13:return F("Suspense");case 19:return F("SuspenseList");case 0:case 2:case 15:return e=B(e.type,!1);case 11:return e=B(e.type.render,!1);case 1:return e=B(e.type,!0);default:return""}}function U(e){if(null==e)return null;if("function"==typeof e)return e.displayName||e.name||null;if("string"==typeof e)return e;switch(e){case S:return"Fragment";case x:return"Portal";case C:return"Profiler";case E:return"StrictMode";case j:return"Suspense";case L:return"SuspenseList"}if("object"==typeof e)switch(e.$$typeof){case A:return(e.displayName||"Context")+".Consumer";case _:return(e._context.displayName||"Context")+".Provider";case T:var t=e.render;return(e=e.displayName)||(e=""!==(e=t.displayName||t.name||"")?"ForwardRef("+e+")":"ForwardRef"),e;case N:return null!==(t=e.displayName||null)?t:U(e.type)||"Memo";case R:t=e._payload,e=e._init;try{return U(e(t))}catch(n){}}return null}function q(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=(e=t.render).displayName||e.name||"",t.displayName||(""!==e?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return U(t);case 8:return t===E?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if("function"==typeof t)return t.displayName||t.name||null;if("string"==typeof t)return t}return null}function H(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":case"object":return e;default:return""}}function G(e){var t=e.type;return(e=e.nodeName)&&"input"===e.toLowerCase()&&("checkbox"===t||"radio"===t)}function V(e){e._valueTracker||(e._valueTracker=function(e){var t=G(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&void 0!==n&&"function"==typeof n.get&&"function"==typeof n.set){var a=n.get,o=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return a.call(this)},set:function(e){r=""+e,o.call(this,e)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(e){r=""+e},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}(e))}function W(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=G(e)?e.checked?"true":"false":e.value),(e=r)!==n&&(t.setValue(e),!0)}function Q(e){if(void 0===(e=e||("undefined"!=typeof document?document:void 0)))return null;try{return e.activeElement||e.body}catch(t){return e.body}}function K(e,t){var n=t.checked;return I({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:null!=n?n:e._wrapperState.initialChecked})}function Y(e,t){var n=null==t.defaultValue?"":t.defaultValue,r=null!=t.checked?t.checked:t.defaultChecked;n=H(null!=t.value?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:"checkbox"===t.type||"radio"===t.type?null!=t.checked:null!=t.value}}function Z(e,t){null!=(t=t.checked)&&v(e,"checked",t,!1)}function X(e,t){Z(e,t);var n=H(t.value),r=t.type;if(null!=n)"number"===r?(0===n&&""===e.value||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if("submit"===r||"reset"===r)return void e.removeAttribute("value");t.hasOwnProperty("value")?ee(e,t.type,n):t.hasOwnProperty("defaultValue")&&ee(e,t.type,H(t.defaultValue)),null==t.checked&&null!=t.defaultChecked&&(e.defaultChecked=!!t.defaultChecked)}function J(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!("submit"!==r&&"reset"!==r||void 0!==t.value&&null!==t.value))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}""!==(n=e.name)&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,""!==n&&(e.name=n)}function ee(e,t,n){"number"===t&&Q(e.ownerDocument)===e||(null==n?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var te=Array.isArray;function ne(e,t,n,r){if(e=e.options,t){t={};for(var a=0;a<n.length;a++)t["$"+n[a]]=!0;for(n=0;n<e.length;n++)a=t.hasOwnProperty("$"+e[n].value),e[n].selected!==a&&(e[n].selected=a),a&&r&&(e[n].defaultSelected=!0)}else{for(n=""+H(n),t=null,a=0;a<e.length;a++){if(e[a].value===n)return e[a].selected=!0,void(r&&(e[a].defaultSelected=!0));null!==t||e[a].disabled||(t=e[a])}null!==t&&(t.selected=!0)}}function re(e,t){if(null!=t.dangerouslySetInnerHTML)throw Error(o(91));return I({},t,{value:void 0,defaultValue:void 0,children:""+e._wrapperState.initialValue})}function ae(e,t){var n=t.value;if(null==n){if(n=t.children,t=t.defaultValue,null!=n){if(null!=t)throw Error(o(92));if(te(n)){if(1<n.length)throw Error(o(93));n=n[0]}t=n}null==t&&(t=""),n=t}e._wrapperState={initialValue:H(n)}}function oe(e,t){var n=H(t.value),r=H(t.defaultValue);null!=n&&((n=""+n)!==e.value&&(e.value=n),null==t.defaultValue&&e.defaultValue!==n&&(e.defaultValue=n)),null!=r&&(e.defaultValue=""+r)}function ie(e){var t=e.textContent;t===e._wrapperState.initialValue&&""!==t&&null!==t&&(e.value=t)}function le(e){switch(e){case"svg":return"http://www.w3.org/2000/svg";case"math":return"http://www.w3.org/1998/Math/MathML";default:return"http://www.w3.org/1999/xhtml"}}function se(e,t){return null==e||"http://www.w3.org/1999/xhtml"===e?le(t):"http://www.w3.org/2000/svg"===e&&"foreignObject"===t?"http://www.w3.org/1999/xhtml":e}var ce,ue,de=(ue=function(e,t){if("http://www.w3.org/2000/svg"!==e.namespaceURI||"innerHTML"in e)e.innerHTML=t;else{for((ce=ce||document.createElement("div")).innerHTML="<svg>"+t.valueOf().toString()+"</svg>",t=ce.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}},"undefined"!=typeof MSApp&&MSApp.execUnsafeLocalFunction?function(e,t,n,r){MSApp.execUnsafeLocalFunction((function(){return ue(e,t)}))}:ue);function pe(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType)return void(n.nodeValue=t)}e.textContent=t}var fe={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},me=["Webkit","ms","Moz","O"];function ge(e,t,n){return null==t||"boolean"==typeof t||""===t?"":n||"number"!=typeof t||0===t||fe.hasOwnProperty(e)&&fe[e]?(""+t).trim():t+"px"}function he(e,t){for(var n in e=e.style,t)if(t.hasOwnProperty(n)){var r=0===n.indexOf("--"),a=ge(n,t[n],r);"float"===n&&(n="cssFloat"),r?e.setProperty(n,a):e[n]=a}}Object.keys(fe).forEach((function(e){me.forEach((function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),fe[t]=fe[e]}))}));var ye=I({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function be(e,t){if(t){if(ye[e]&&(null!=t.children||null!=t.dangerouslySetInnerHTML))throw Error(o(137,e));if(null!=t.dangerouslySetInnerHTML){if(null!=t.children)throw Error(o(60));if("object"!=typeof t.dangerouslySetInnerHTML||!("__html"in t.dangerouslySetInnerHTML))throw Error(o(61))}if(null!=t.style&&"object"!=typeof t.style)throw Error(o(62))}}function ve(e,t){if(-1===e.indexOf("-"))return"string"==typeof t.is;switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var we=null;function ke(e){return(e=e.target||e.srcElement||window).correspondingUseElement&&(e=e.correspondingUseElement),3===e.nodeType?e.parentNode:e}var xe=null,Se=null,Ee=null;function Ce(e){if(e=va(e)){if("function"!=typeof xe)throw Error(o(280));var t=e.stateNode;t&&(t=ka(t),xe(e.stateNode,e.type,t))}}function _e(e){Se?Ee?Ee.push(e):Ee=[e]:Se=e}function Ae(){if(Se){var e=Se,t=Ee;if(Ee=Se=null,Ce(e),t)for(e=0;e<t.length;e++)Ce(t[e])}}function Te(e,t){return e(t)}function je(){}var Le=!1;function Ne(e,t,n){if(Le)return e(t,n);Le=!0;try{return Te(e,t,n)}finally{Le=!1,(null!==Se||null!==Ee)&&(je(),Ae())}}function Re(e,t){var n=e.stateNode;if(null===n)return null;var r=ka(n);if(null===r)return null;n=r[t];e:switch(t){case"onClick":case"onClickCapture":case"onDoubleClick":case"onDoubleClickCapture":case"onMouseDown":case"onMouseDownCapture":case"onMouseMove":case"onMouseMoveCapture":case"onMouseUp":case"onMouseUpCapture":case"onMouseEnter":(r=!r.disabled)||(r=!("button"===(e=e.type)||"input"===e||"select"===e||"textarea"===e)),e=!r;break e;default:e=!1}if(e)return null;if(n&&"function"!=typeof n)throw Error(o(231,t,typeof n));return n}var Pe=!1;if(u)try{var Oe={};Object.defineProperty(Oe,"passive",{get:function(){Pe=!0}}),window.addEventListener("test",Oe,Oe),window.removeEventListener("test",Oe,Oe)}catch(ue){Pe=!1}function De(e,t,n,r,a,o,i,l,s){var c=Array.prototype.slice.call(arguments,3);try{t.apply(n,c)}catch(u){this.onError(u)}}var Me=!1,Ie=null,Fe=!1,ze=null,Be={onError:function(e){Me=!0,Ie=e}};function $e(e,t,n,r,a,o,i,l,s){Me=!1,Ie=null,De.apply(Be,arguments)}function Ue(e){var t=e,n=e;if(e.alternate)for(;t.return;)t=t.return;else{e=t;do{0!=(4098&(t=e).flags)&&(n=t.return),e=t.return}while(e)}return 3===t.tag?n:null}function qe(e){if(13===e.tag){var t=e.memoizedState;if(null===t&&(null!==(e=e.alternate)&&(t=e.memoizedState)),null!==t)return t.dehydrated}return null}function He(e){if(Ue(e)!==e)throw Error(o(188))}function Ge(e){return null!==(e=function(e){var t=e.alternate;if(!t){if(null===(t=Ue(e)))throw Error(o(188));return t!==e?null:e}for(var n=e,r=t;;){var a=n.return;if(null===a)break;var i=a.alternate;if(null===i){if(null!==(r=a.return)){n=r;continue}break}if(a.child===i.child){for(i=a.child;i;){if(i===n)return He(a),e;if(i===r)return He(a),t;i=i.sibling}throw Error(o(188))}if(n.return!==r.return)n=a,r=i;else{for(var l=!1,s=a.child;s;){if(s===n){l=!0,n=a,r=i;break}if(s===r){l=!0,r=a,n=i;break}s=s.sibling}if(!l){for(s=i.child;s;){if(s===n){l=!0,n=i,r=a;break}if(s===r){l=!0,r=i,n=a;break}s=s.sibling}if(!l)throw Error(o(189))}}if(n.alternate!==r)throw Error(o(190))}if(3!==n.tag)throw Error(o(188));return n.stateNode.current===n?e:t}(e))?Ve(e):null}function Ve(e){if(5===e.tag||6===e.tag)return e;for(e=e.child;null!==e;){var t=Ve(e);if(null!==t)return t;e=e.sibling}return null}var We=a.unstable_scheduleCallback,Qe=a.unstable_cancelCallback,Ke=a.unstable_shouldYield,Ye=a.unstable_requestPaint,Ze=a.unstable_now,Xe=a.unstable_getCurrentPriorityLevel,Je=a.unstable_ImmediatePriority,et=a.unstable_UserBlockingPriority,tt=a.unstable_NormalPriority,nt=a.unstable_LowPriority,rt=a.unstable_IdlePriority,at=null,ot=null;var it=Math.clz32?Math.clz32:function(e){return e>>>=0,0===e?32:31-(lt(e)/st|0)|0},lt=Math.log,st=Math.LN2;var ct=64,ut=4194304;function dt(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return 4194240&e;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return 130023424&e;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function pt(e,t){var n=e.pendingLanes;if(0===n)return 0;var r=0,a=e.suspendedLanes,o=e.pingedLanes,i=268435455&n;if(0!==i){var l=i&~a;0!==l?r=dt(l):0!==(o&=i)&&(r=dt(o))}else 0!==(i=n&~a)?r=dt(i):0!==o&&(r=dt(o));if(0===r)return 0;if(0!==t&&t!==r&&0==(t&a)&&((a=r&-r)>=(o=t&-t)||16===a&&0!=(4194240&o)))return t;if(0!=(4&r)&&(r|=16&n),0!==(t=e.entangledLanes))for(e=e.entanglements,t&=r;0<t;)a=1<<(n=31-it(t)),r|=e[n],t&=~a;return r}function ft(e,t){switch(e){case 1:case 2:case 4:return t+250;case 8:case 16:case 32:case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;default:return-1}}function mt(e){return 0!==(e=-1073741825&e.pendingLanes)?e:1073741824&e?1073741824:0}function gt(){var e=ct;return 0==(4194240&(ct<<=1))&&(ct=64),e}function ht(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function yt(e,t,n){e.pendingLanes|=t,536870912!==t&&(e.suspendedLanes=0,e.pingedLanes=0),(e=e.eventTimes)[t=31-it(t)]=n}function bt(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-it(n),a=1<<r;a&t|e[r]&t&&(e[r]|=t),n&=~a}}var vt=0;function wt(e){return 1<(e&=-e)?4<e?0!=(268435455&e)?16:536870912:4:1}var kt,xt,St,Et,Ct,_t=!1,At=[],Tt=null,jt=null,Lt=null,Nt=new Map,Rt=new Map,Pt=[],Ot="mousedown mouseup touchcancel touchend touchstart auxclick dblclick pointercancel pointerdown pointerup dragend dragstart drop compositionend compositionstart keydown keypress keyup input textInput copy cut paste click change contextmenu reset submit".split(" ");function Dt(e,t){switch(e){case"focusin":case"focusout":Tt=null;break;case"dragenter":case"dragleave":jt=null;break;case"mouseover":case"mouseout":Lt=null;break;case"pointerover":case"pointerout":Nt.delete(t.pointerId);break;case"gotpointercapture":case"lostpointercapture":Rt.delete(t.pointerId)}}function Mt(e,t,n,r,a,o){return null===e||e.nativeEvent!==o?(e={blockedOn:t,domEventName:n,eventSystemFlags:r,nativeEvent:o,targetContainers:[a]},null!==t&&(null!==(t=va(t))&&xt(t)),e):(e.eventSystemFlags|=r,t=e.targetContainers,null!==a&&-1===t.indexOf(a)&&t.push(a),e)}function It(e){var t=ba(e.target);if(null!==t){var n=Ue(t);if(null!==n)if(13===(t=n.tag)){if(null!==(t=qe(n)))return e.blockedOn=t,void Ct(e.priority,(function(){St(n)}))}else if(3===t&&n.stateNode.current.memoizedState.isDehydrated)return void(e.blockedOn=3===n.tag?n.stateNode.containerInfo:null)}e.blockedOn=null}function Ft(e){if(null!==e.blockedOn)return!1;for(var t=e.targetContainers;0<t.length;){var n=Kt(e.domEventName,e.eventSystemFlags,t[0],e.nativeEvent);if(null!==n)return null!==(t=va(n))&&xt(t),e.blockedOn=n,!1;var r=new(n=e.nativeEvent).constructor(n.type,n);we=r,n.target.dispatchEvent(r),we=null,t.shift()}return!0}function zt(e,t,n){Ft(e)&&n.delete(t)}function Bt(){_t=!1,null!==Tt&&Ft(Tt)&&(Tt=null),null!==jt&&Ft(jt)&&(jt=null),null!==Lt&&Ft(Lt)&&(Lt=null),Nt.forEach(zt),Rt.forEach(zt)}function $t(e,t){e.blockedOn===t&&(e.blockedOn=null,_t||(_t=!0,a.unstable_scheduleCallback(a.unstable_NormalPriority,Bt)))}function Ut(e){function t(t){return $t(t,e)}if(0<At.length){$t(At[0],e);for(var n=1;n<At.length;n++){var r=At[n];r.blockedOn===e&&(r.blockedOn=null)}}for(null!==Tt&&$t(Tt,e),null!==jt&&$t(jt,e),null!==Lt&&$t(Lt,e),Nt.forEach(t),Rt.forEach(t),n=0;n<Pt.length;n++)(r=Pt[n]).blockedOn===e&&(r.blockedOn=null);for(;0<Pt.length&&null===(n=Pt[0]).blockedOn;)It(n),null===n.blockedOn&&Pt.shift()}var qt=w.ReactCurrentBatchConfig,Ht=!0;function Gt(e,t,n,r){var a=vt,o=qt.transition;qt.transition=null;try{vt=1,Wt(e,t,n,r)}finally{vt=a,qt.transition=o}}function Vt(e,t,n,r){var a=vt,o=qt.transition;qt.transition=null;try{vt=4,Wt(e,t,n,r)}finally{vt=a,qt.transition=o}}function Wt(e,t,n,r){if(Ht){var a=Kt(e,t,n,r);if(null===a)Hr(e,t,r,Qt,n),Dt(e,r);else if(function(e,t,n,r,a){switch(t){case"focusin":return Tt=Mt(Tt,e,t,n,r,a),!0;case"dragenter":return jt=Mt(jt,e,t,n,r,a),!0;case"mouseover":return Lt=Mt(Lt,e,t,n,r,a),!0;case"pointerover":var o=a.pointerId;return Nt.set(o,Mt(Nt.get(o)||null,e,t,n,r,a)),!0;case"gotpointercapture":return o=a.pointerId,Rt.set(o,Mt(Rt.get(o)||null,e,t,n,r,a)),!0}return!1}(a,e,t,n,r))r.stopPropagation();else if(Dt(e,r),4&t&&-1<Ot.indexOf(e)){for(;null!==a;){var o=va(a);if(null!==o&&kt(o),null===(o=Kt(e,t,n,r))&&Hr(e,t,r,Qt,n),o===a)break;a=o}null!==a&&r.stopPropagation()}else Hr(e,t,r,null,n)}}var Qt=null;function Kt(e,t,n,r){if(Qt=null,null!==(e=ba(e=ke(r))))if(null===(t=Ue(e)))e=null;else if(13===(n=t.tag)){if(null!==(e=qe(t)))return e;e=null}else if(3===n){if(t.stateNode.current.memoizedState.isDehydrated)return 3===t.tag?t.stateNode.containerInfo:null;e=null}else t!==e&&(e=null);return Qt=e,null}function Yt(e){switch(e){case"cancel":case"click":case"close":case"contextmenu":case"copy":case"cut":case"auxclick":case"dblclick":case"dragend":case"dragstart":case"drop":case"focusin":case"focusout":case"input":case"invalid":case"keydown":case"keypress":case"keyup":case"mousedown":case"mouseup":case"paste":case"pause":case"play":case"pointercancel":case"pointerdown":case"pointerup":case"ratechange":case"reset":case"resize":case"seeked":case"submit":case"touchcancel":case"touchend":case"touchstart":case"volumechange":case"change":case"selectionchange":case"textInput":case"compositionstart":case"compositionend":case"compositionupdate":case"beforeblur":case"afterblur":case"beforeinput":case"blur":case"fullscreenchange":case"focus":case"hashchange":case"popstate":case"select":case"selectstart":return 1;case"drag":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"mousemove":case"mouseout":case"mouseover":case"pointermove":case"pointerout":case"pointerover":case"scroll":case"toggle":case"touchmove":case"wheel":case"mouseenter":case"mouseleave":case"pointerenter":case"pointerleave":return 4;case"message":switch(Xe()){case Je:return 1;case et:return 4;case tt:case nt:return 16;case rt:return 536870912;default:return 16}default:return 16}}var Zt=null,Xt=null,Jt=null;function en(){if(Jt)return Jt;var e,t,n=Xt,r=n.length,a="value"in Zt?Zt.value:Zt.textContent,o=a.length;for(e=0;e<r&&n[e]===a[e];e++);var i=r-e;for(t=1;t<=i&&n[r-t]===a[o-t];t++);return Jt=a.slice(e,1<t?1-t:void 0)}function tn(e){var t=e.keyCode;return"charCode"in e?0===(e=e.charCode)&&13===t&&(e=13):e=t,10===e&&(e=13),32<=e||13===e?e:0}function nn(){return!0}function rn(){return!1}function an(e){function t(t,n,r,a,o){for(var i in this._reactName=t,this._targetInst=r,this.type=n,this.nativeEvent=a,this.target=o,this.currentTarget=null,e)e.hasOwnProperty(i)&&(t=e[i],this[i]=t?t(a):a[i]);return this.isDefaultPrevented=(null!=a.defaultPrevented?a.defaultPrevented:!1===a.returnValue)?nn:rn,this.isPropagationStopped=rn,this}return I(t.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():"unknown"!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=nn)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():"unknown"!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=nn)},persist:function(){},isPersistent:nn}),t}var on,ln,sn,cn={eventPhase:0,bubbles:0,cancelable:0,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:0,isTrusted:0},un=an(cn),dn=I({},cn,{view:0,detail:0}),pn=an(dn),fn=I({},dn,{screenX:0,screenY:0,clientX:0,clientY:0,pageX:0,pageY:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,getModifierState:Cn,button:0,buttons:0,relatedTarget:function(e){return void 0===e.relatedTarget?e.fromElement===e.srcElement?e.toElement:e.fromElement:e.relatedTarget},movementX:function(e){return"movementX"in e?e.movementX:(e!==sn&&(sn&&"mousemove"===e.type?(on=e.screenX-sn.screenX,ln=e.screenY-sn.screenY):ln=on=0,sn=e),on)},movementY:function(e){return"movementY"in e?e.movementY:ln}}),mn=an(fn),gn=an(I({},fn,{dataTransfer:0})),hn=an(I({},dn,{relatedTarget:0})),yn=an(I({},cn,{animationName:0,elapsedTime:0,pseudoElement:0})),bn=I({},cn,{clipboardData:function(e){return"clipboardData"in e?e.clipboardData:window.clipboardData}}),vn=an(bn),wn=an(I({},cn,{data:0})),kn={Esc:"Escape",Spacebar:" ",Left:"ArrowLeft",Up:"ArrowUp",Right:"ArrowRight",Down:"ArrowDown",Del:"Delete",Win:"OS",Menu:"ContextMenu",Apps:"ContextMenu",Scroll:"ScrollLock",MozPrintableKey:"Unidentified"},xn={8:"Backspace",9:"Tab",12:"Clear",13:"Enter",16:"Shift",17:"Control",18:"Alt",19:"Pause",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",45:"Insert",46:"Delete",112:"F1",113:"F2",114:"F3",115:"F4",116:"F5",117:"F6",118:"F7",119:"F8",120:"F9",121:"F10",122:"F11",123:"F12",144:"NumLock",145:"ScrollLock",224:"Meta"},Sn={Alt:"altKey",Control:"ctrlKey",Meta:"metaKey",Shift:"shiftKey"};function En(e){var t=this.nativeEvent;return t.getModifierState?t.getModifierState(e):!!(e=Sn[e])&&!!t[e]}function Cn(){return En}var _n=I({},dn,{key:function(e){if(e.key){var t=kn[e.key]||e.key;if("Unidentified"!==t)return t}return"keypress"===e.type?13===(e=tn(e))?"Enter":String.fromCharCode(e):"keydown"===e.type||"keyup"===e.type?xn[e.keyCode]||"Unidentified":""},code:0,location:0,ctrlKey:0,shiftKey:0,altKey:0,metaKey:0,repeat:0,locale:0,getModifierState:Cn,charCode:function(e){return"keypress"===e.type?tn(e):0},keyCode:function(e){return"keydown"===e.type||"keyup"===e.type?e.keyCode:0},which:function(e){return"keypress"===e.type?tn(e):"keydown"===e.type||"keyup"===e.type?e.keyCode:0}}),An=an(_n),Tn=an(I({},fn,{pointerId:0,width:0,height:0,pressure:0,tangentialPressure:0,tiltX:0,tiltY:0,twist:0,pointerType:0,isPrimary:0})),jn=an(I({},dn,{touches:0,targetTouches:0,changedTouches:0,altKey:0,metaKey:0,ctrlKey:0,shiftKey:0,getModifierState:Cn})),Ln=an(I({},cn,{propertyName:0,elapsedTime:0,pseudoElement:0})),Nn=I({},fn,{deltaX:function(e){return"deltaX"in e?e.deltaX:"wheelDeltaX"in e?-e.wheelDeltaX:0},deltaY:function(e){return"deltaY"in e?e.deltaY:"wheelDeltaY"in e?-e.wheelDeltaY:"wheelDelta"in e?-e.wheelDelta:0},deltaZ:0,deltaMode:0}),Rn=an(Nn),Pn=[9,13,27,32],On=u&&"CompositionEvent"in window,Dn=null;u&&"documentMode"in document&&(Dn=document.documentMode);var Mn=u&&"TextEvent"in window&&!Dn,In=u&&(!On||Dn&&8<Dn&&11>=Dn),Fn=String.fromCharCode(32),zn=!1;function Bn(e,t){switch(e){case"keyup":return-1!==Pn.indexOf(t.keyCode);case"keydown":return 229!==t.keyCode;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function $n(e){return"object"==typeof(e=e.detail)&&"data"in e?e.data:null}var Un=!1;var qn={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};function Hn(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return"input"===t?!!qn[e.type]:"textarea"===t}function Gn(e,t,n,r){_e(r),0<(t=Vr(t,"onChange")).length&&(n=new un("onChange","change",null,n,r),e.push({event:n,listeners:t}))}var Vn=null,Wn=null;function Qn(e){Fr(e,0)}function Kn(e){if(W(wa(e)))return e}function Yn(e,t){if("change"===e)return t}var Zn=!1;if(u){var Xn;if(u){var Jn="oninput"in document;if(!Jn){var er=document.createElement("div");er.setAttribute("oninput","return;"),Jn="function"==typeof er.oninput}Xn=Jn}else Xn=!1;Zn=Xn&&(!document.documentMode||9<document.documentMode)}function tr(){Vn&&(Vn.detachEvent("onpropertychange",nr),Wn=Vn=null)}function nr(e){if("value"===e.propertyName&&Kn(Wn)){var t=[];Gn(t,Wn,e,ke(e)),Ne(Qn,t)}}function rr(e,t,n){"focusin"===e?(tr(),Wn=n,(Vn=t).attachEvent("onpropertychange",nr)):"focusout"===e&&tr()}function ar(e){if("selectionchange"===e||"keyup"===e||"keydown"===e)return Kn(Wn)}function or(e,t){if("click"===e)return Kn(t)}function ir(e,t){if("input"===e||"change"===e)return Kn(t)}var lr="function"==typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e==1/t)||e!=e&&t!=t};function sr(e,t){if(lr(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var n=Object.keys(e),r=Object.keys(t);if(n.length!==r.length)return!1;for(r=0;r<n.length;r++){var a=n[r];if(!d.call(t,a)||!lr(e[a],t[a]))return!1}return!0}function cr(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function ur(e,t){var n,r=cr(e);for(e=0;r;){if(3===r.nodeType){if(n=e+r.textContent.length,e<=t&&n>=t)return{node:r,offset:t-e};e=n}e:{for(;r;){if(r.nextSibling){r=r.nextSibling;break e}r=r.parentNode}r=void 0}r=cr(r)}}function dr(e,t){return!(!e||!t)&&(e===t||(!e||3!==e.nodeType)&&(t&&3===t.nodeType?dr(e,t.parentNode):"contains"in e?e.contains(t):!!e.compareDocumentPosition&&!!(16&e.compareDocumentPosition(t))))}function pr(){for(var e=window,t=Q();t instanceof e.HTMLIFrameElement;){try{var n="string"==typeof t.contentWindow.location.href}catch(r){n=!1}if(!n)break;t=Q((e=t.contentWindow).document)}return t}function fr(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&("input"===t&&("text"===e.type||"search"===e.type||"tel"===e.type||"url"===e.type||"password"===e.type)||"textarea"===t||"true"===e.contentEditable)}function mr(e){var t=pr(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&dr(n.ownerDocument.documentElement,n)){if(null!==r&&fr(n))if(t=r.start,void 0===(e=r.end)&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if((e=(t=n.ownerDocument||document)&&t.defaultView||window).getSelection){e=e.getSelection();var a=n.textContent.length,o=Math.min(r.start,a);r=void 0===r.end?o:Math.min(r.end,a),!e.extend&&o>r&&(a=r,r=o,o=a),a=ur(n,o);var i=ur(n,r);a&&i&&(1!==e.rangeCount||e.anchorNode!==a.node||e.anchorOffset!==a.offset||e.focusNode!==i.node||e.focusOffset!==i.offset)&&((t=t.createRange()).setStart(a.node,a.offset),e.removeAllRanges(),o>r?(e.addRange(t),e.extend(i.node,i.offset)):(t.setEnd(i.node,i.offset),e.addRange(t)))}for(t=[],e=n;e=e.parentNode;)1===e.nodeType&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for("function"==typeof n.focus&&n.focus(),n=0;n<t.length;n++)(e=t[n]).element.scrollLeft=e.left,e.element.scrollTop=e.top}}var gr=u&&"documentMode"in document&&11>=document.documentMode,hr=null,yr=null,br=null,vr=!1;function wr(e,t,n){var r=n.window===n?n.document:9===n.nodeType?n:n.ownerDocument;vr||null==hr||hr!==Q(r)||("selectionStart"in(r=hr)&&fr(r)?r={start:r.selectionStart,end:r.selectionEnd}:r={anchorNode:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection()).anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset},br&&sr(br,r)||(br=r,0<(r=Vr(yr,"onSelect")).length&&(t=new un("onSelect","select",null,t,n),e.push({event:t,listeners:r}),t.target=hr)))}function kr(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n["Webkit"+e]="webkit"+t,n["Moz"+e]="moz"+t,n}var xr={animationend:kr("Animation","AnimationEnd"),animationiteration:kr("Animation","AnimationIteration"),animationstart:kr("Animation","AnimationStart"),transitionend:kr("Transition","TransitionEnd")},Sr={},Er={};function Cr(e){if(Sr[e])return Sr[e];if(!xr[e])return e;var t,n=xr[e];for(t in n)if(n.hasOwnProperty(t)&&t in Er)return Sr[e]=n[t];return e}u&&(Er=document.createElement("div").style,"AnimationEvent"in window||(delete xr.animationend.animation,delete xr.animationiteration.animation,delete xr.animationstart.animation),"TransitionEvent"in window||delete xr.transitionend.transition);var _r=Cr("animationend"),Ar=Cr("animationiteration"),Tr=Cr("animationstart"),jr=Cr("transitionend"),Lr=new Map,Nr="abort auxClick cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(" ");function Rr(e,t){Lr.set(e,t),s(t,[e])}for(var Pr=0;Pr<Nr.length;Pr++){var Or=Nr[Pr];Rr(Or.toLowerCase(),"on"+(Or[0].toUpperCase()+Or.slice(1)))}Rr(_r,"onAnimationEnd"),Rr(Ar,"onAnimationIteration"),Rr(Tr,"onAnimationStart"),Rr("dblclick","onDoubleClick"),Rr("focusin","onFocus"),Rr("focusout","onBlur"),Rr(jr,"onTransitionEnd"),c("onMouseEnter",["mouseout","mouseover"]),c("onMouseLeave",["mouseout","mouseover"]),c("onPointerEnter",["pointerout","pointerover"]),c("onPointerLeave",["pointerout","pointerover"]),s("onChange","change click focusin focusout input keydown keyup selectionchange".split(" ")),s("onSelect","focusout contextmenu dragend focusin keydown keyup mousedown mouseup selectionchange".split(" ")),s("onBeforeInput",["compositionend","keypress","textInput","paste"]),s("onCompositionEnd","compositionend focusout keydown keypress keyup mousedown".split(" ")),s("onCompositionStart","compositionstart focusout keydown keypress keyup mousedown".split(" ")),s("onCompositionUpdate","compositionupdate focusout keydown keypress keyup mousedown".split(" "));var Dr="abort canplay canplaythrough durationchange emptied encrypted ended error loadeddata loadedmetadata loadstart pause play playing progress ratechange resize seeked seeking stalled suspend timeupdate volumechange waiting".split(" "),Mr=new Set("cancel close invalid load scroll toggle".split(" ").concat(Dr));function Ir(e,t,n){var r=e.type||"unknown-event";e.currentTarget=n,function(e,t,n,r,a,i,l,s,c){if($e.apply(this,arguments),Me){if(!Me)throw Error(o(198));var u=Ie;Me=!1,Ie=null,Fe||(Fe=!0,ze=u)}}(r,t,void 0,e),e.currentTarget=null}function Fr(e,t){t=0!=(4&t);for(var n=0;n<e.length;n++){var r=e[n],a=r.event;r=r.listeners;e:{var o=void 0;if(t)for(var i=r.length-1;0<=i;i--){var l=r[i],s=l.instance,c=l.currentTarget;if(l=l.listener,s!==o&&a.isPropagationStopped())break e;Ir(a,l,c),o=s}else for(i=0;i<r.length;i++){if(s=(l=r[i]).instance,c=l.currentTarget,l=l.listener,s!==o&&a.isPropagationStopped())break e;Ir(a,l,c),o=s}}}if(Fe)throw e=ze,Fe=!1,ze=null,e}function zr(e,t){var n=t[ga];void 0===n&&(n=t[ga]=new Set);var r=e+"__bubble";n.has(r)||(qr(t,e,2,!1),n.add(r))}function Br(e,t,n){var r=0;t&&(r|=4),qr(n,e,r,t)}var $r="_reactListening"+Math.random().toString(36).slice(2);function Ur(e){if(!e[$r]){e[$r]=!0,i.forEach((function(t){"selectionchange"!==t&&(Mr.has(t)||Br(t,!1,e),Br(t,!0,e))}));var t=9===e.nodeType?e:e.ownerDocument;null===t||t[$r]||(t[$r]=!0,Br("selectionchange",!1,t))}}function qr(e,t,n,r){switch(Yt(t)){case 1:var a=Gt;break;case 4:a=Vt;break;default:a=Wt}n=a.bind(null,t,n,e),a=void 0,!Pe||"touchstart"!==t&&"touchmove"!==t&&"wheel"!==t||(a=!0),r?void 0!==a?e.addEventListener(t,n,{capture:!0,passive:a}):e.addEventListener(t,n,!0):void 0!==a?e.addEventListener(t,n,{passive:a}):e.addEventListener(t,n,!1)}function Hr(e,t,n,r,a){var o=r;if(0==(1&t)&&0==(2&t)&&null!==r)e:for(;;){if(null===r)return;var i=r.tag;if(3===i||4===i){var l=r.stateNode.containerInfo;if(l===a||8===l.nodeType&&l.parentNode===a)break;if(4===i)for(i=r.return;null!==i;){var s=i.tag;if((3===s||4===s)&&((s=i.stateNode.containerInfo)===a||8===s.nodeType&&s.parentNode===a))return;i=i.return}for(;null!==l;){if(null===(i=ba(l)))return;if(5===(s=i.tag)||6===s){r=o=i;continue e}l=l.parentNode}}r=r.return}Ne((function(){var r=o,a=ke(n),i=[];e:{var l=Lr.get(e);if(void 0!==l){var s=un,c=e;switch(e){case"keypress":if(0===tn(n))break e;case"keydown":case"keyup":s=An;break;case"focusin":c="focus",s=hn;break;case"focusout":c="blur",s=hn;break;case"beforeblur":case"afterblur":s=hn;break;case"click":if(2===n.button)break e;case"auxclick":case"dblclick":case"mousedown":case"mousemove":case"mouseup":case"mouseout":case"mouseover":case"contextmenu":s=mn;break;case"drag":case"dragend":case"dragenter":case"dragexit":case"dragleave":case"dragover":case"dragstart":case"drop":s=gn;break;case"touchcancel":case"touchend":case"touchmove":case"touchstart":s=jn;break;case _r:case Ar:case Tr:s=yn;break;case jr:s=Ln;break;case"scroll":s=pn;break;case"wheel":s=Rn;break;case"copy":case"cut":case"paste":s=vn;break;case"gotpointercapture":case"lostpointercapture":case"pointercancel":case"pointerdown":case"pointermove":case"pointerout":case"pointerover":case"pointerup":s=Tn}var u=0!=(4&t),d=!u&&"scroll"===e,p=u?null!==l?l+"Capture":null:l;u=[];for(var f,m=r;null!==m;){var g=(f=m).stateNode;if(5===f.tag&&null!==g&&(f=g,null!==p&&(null!=(g=Re(m,p))&&u.push(Gr(m,g,f)))),d)break;m=m.return}0<u.length&&(l=new s(l,c,null,n,a),i.push({event:l,listeners:u}))}}if(0==(7&t)){if(s="mouseout"===e||"pointerout"===e,(!(l="mouseover"===e||"pointerover"===e)||n===we||!(c=n.relatedTarget||n.fromElement)||!ba(c)&&!c[ma])&&(s||l)&&(l=a.window===a?a:(l=a.ownerDocument)?l.defaultView||l.parentWindow:window,s?(s=r,null!==(c=(c=n.relatedTarget||n.toElement)?ba(c):null)&&(c!==(d=Ue(c))||5!==c.tag&&6!==c.tag)&&(c=null)):(s=null,c=r),s!==c)){if(u=mn,g="onMouseLeave",p="onMouseEnter",m="mouse","pointerout"!==e&&"pointerover"!==e||(u=Tn,g="onPointerLeave",p="onPointerEnter",m="pointer"),d=null==s?l:wa(s),f=null==c?l:wa(c),(l=new u(g,m+"leave",s,n,a)).target=d,l.relatedTarget=f,g=null,ba(a)===r&&((u=new u(p,m+"enter",c,n,a)).target=f,u.relatedTarget=d,g=u),d=g,s&&c)e:{for(p=c,m=0,f=u=s;f;f=Wr(f))m++;for(f=0,g=p;g;g=Wr(g))f++;for(;0<m-f;)u=Wr(u),m--;for(;0<f-m;)p=Wr(p),f--;for(;m--;){if(u===p||null!==p&&u===p.alternate)break e;u=Wr(u),p=Wr(p)}u=null}else u=null;null!==s&&Qr(i,l,s,u,!1),null!==c&&null!==d&&Qr(i,d,c,u,!0)}if("select"===(s=(l=r?wa(r):window).nodeName&&l.nodeName.toLowerCase())||"input"===s&&"file"===l.type)var h=Yn;else if(Hn(l))if(Zn)h=ir;else{h=ar;var y=rr}else(s=l.nodeName)&&"input"===s.toLowerCase()&&("checkbox"===l.type||"radio"===l.type)&&(h=or);switch(h&&(h=h(e,r))?Gn(i,h,n,a):(y&&y(e,l,r),"focusout"===e&&(y=l._wrapperState)&&y.controlled&&"number"===l.type&&ee(l,"number",l.value)),y=r?wa(r):window,e){case"focusin":(Hn(y)||"true"===y.contentEditable)&&(hr=y,yr=r,br=null);break;case"focusout":br=yr=hr=null;break;case"mousedown":vr=!0;break;case"contextmenu":case"mouseup":case"dragend":vr=!1,wr(i,n,a);break;case"selectionchange":if(gr)break;case"keydown":case"keyup":wr(i,n,a)}var b;if(On)e:{switch(e){case"compositionstart":var v="onCompositionStart";break e;case"compositionend":v="onCompositionEnd";break e;case"compositionupdate":v="onCompositionUpdate";break e}v=void 0}else Un?Bn(e,n)&&(v="onCompositionEnd"):"keydown"===e&&229===n.keyCode&&(v="onCompositionStart");v&&(In&&"ko"!==n.locale&&(Un||"onCompositionStart"!==v?"onCompositionEnd"===v&&Un&&(b=en()):(Xt="value"in(Zt=a)?Zt.value:Zt.textContent,Un=!0)),0<(y=Vr(r,v)).length&&(v=new wn(v,e,null,n,a),i.push({event:v,listeners:y}),b?v.data=b:null!==(b=$n(n))&&(v.data=b))),(b=Mn?function(e,t){switch(e){case"compositionend":return $n(t);case"keypress":return 32!==t.which?null:(zn=!0,Fn);case"textInput":return(e=t.data)===Fn&&zn?null:e;default:return null}}(e,n):function(e,t){if(Un)return"compositionend"===e||!On&&Bn(e,t)?(e=en(),Jt=Xt=Zt=null,Un=!1,e):null;switch(e){case"paste":default:return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1<t.char.length)return t.char;if(t.which)return String.fromCharCode(t.which)}return null;case"compositionend":return In&&"ko"!==t.locale?null:t.data}}(e,n))&&(0<(r=Vr(r,"onBeforeInput")).length&&(a=new wn("onBeforeInput","beforeinput",null,n,a),i.push({event:a,listeners:r}),a.data=b))}Fr(i,t)}))}function Gr(e,t,n){return{instance:e,listener:t,currentTarget:n}}function Vr(e,t){for(var n=t+"Capture",r=[];null!==e;){var a=e,o=a.stateNode;5===a.tag&&null!==o&&(a=o,null!=(o=Re(e,n))&&r.unshift(Gr(e,o,a)),null!=(o=Re(e,t))&&r.push(Gr(e,o,a))),e=e.return}return r}function Wr(e){if(null===e)return null;do{e=e.return}while(e&&5!==e.tag);return e||null}function Qr(e,t,n,r,a){for(var o=t._reactName,i=[];null!==n&&n!==r;){var l=n,s=l.alternate,c=l.stateNode;if(null!==s&&s===r)break;5===l.tag&&null!==c&&(l=c,a?null!=(s=Re(n,o))&&i.unshift(Gr(n,s,l)):a||null!=(s=Re(n,o))&&i.push(Gr(n,s,l))),n=n.return}0!==i.length&&e.push({event:t,listeners:i})}var Kr=/\r\n?/g,Yr=/\u0000|\uFFFD/g;function Zr(e){return("string"==typeof e?e:""+e).replace(Kr,"\n").replace(Yr,"")}function Xr(e,t,n){if(t=Zr(t),Zr(e)!==t&&n)throw Error(o(425))}function Jr(){}var ea=null,ta=null;function na(e,t){return"textarea"===e||"noscript"===e||"string"==typeof t.children||"number"==typeof t.children||"object"==typeof t.dangerouslySetInnerHTML&&null!==t.dangerouslySetInnerHTML&&null!=t.dangerouslySetInnerHTML.__html}var ra="function"==typeof setTimeout?setTimeout:void 0,aa="function"==typeof clearTimeout?clearTimeout:void 0,oa="function"==typeof Promise?Promise:void 0,ia="function"==typeof queueMicrotask?queueMicrotask:void 0!==oa?function(e){return oa.resolve(null).then(e).catch(la)}:ra;function la(e){setTimeout((function(){throw e}))}function sa(e,t){var n=t,r=0;do{var a=n.nextSibling;if(e.removeChild(n),a&&8===a.nodeType)if("/$"===(n=a.data)){if(0===r)return e.removeChild(a),void Ut(t);r--}else"$"!==n&&"$?"!==n&&"$!"!==n||r++;n=a}while(n);Ut(t)}function ca(e){for(;null!=e;e=e.nextSibling){var t=e.nodeType;if(1===t||3===t)break;if(8===t){if("$"===(t=e.data)||"$!"===t||"$?"===t)break;if("/$"===t)return null}}return e}function ua(e){e=e.previousSibling;for(var t=0;e;){if(8===e.nodeType){var n=e.data;if("$"===n||"$!"===n||"$?"===n){if(0===t)return e;t--}else"/$"===n&&t++}e=e.previousSibling}return null}var da=Math.random().toString(36).slice(2),pa="__reactFiber$"+da,fa="__reactProps$"+da,ma="__reactContainer$"+da,ga="__reactEvents$"+da,ha="__reactListeners$"+da,ya="__reactHandles$"+da;function ba(e){var t=e[pa];if(t)return t;for(var n=e.parentNode;n;){if(t=n[ma]||n[pa]){if(n=t.alternate,null!==t.child||null!==n&&null!==n.child)for(e=ua(e);null!==e;){if(n=e[pa])return n;e=ua(e)}return t}n=(e=n).parentNode}return null}function va(e){return!(e=e[pa]||e[ma])||5!==e.tag&&6!==e.tag&&13!==e.tag&&3!==e.tag?null:e}function wa(e){if(5===e.tag||6===e.tag)return e.stateNode;throw Error(o(33))}function ka(e){return e[fa]||null}var xa=[],Sa=-1;function Ea(e){return{current:e}}function Ca(e){0>Sa||(e.current=xa[Sa],xa[Sa]=null,Sa--)}function _a(e,t){Sa++,xa[Sa]=e.current,e.current=t}var Aa={},Ta=Ea(Aa),ja=Ea(!1),La=Aa;function Na(e,t){var n=e.type.contextTypes;if(!n)return Aa;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var a,o={};for(a in n)o[a]=t[a];return r&&((e=e.stateNode).__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=o),o}function Ra(e){return null!=(e=e.childContextTypes)}function Pa(){Ca(ja),Ca(Ta)}function Oa(e,t,n){if(Ta.current!==Aa)throw Error(o(168));_a(Ta,t),_a(ja,n)}function Da(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,"function"!=typeof r.getChildContext)return n;for(var a in r=r.getChildContext())if(!(a in t))throw Error(o(108,q(e)||"Unknown",a));return I({},n,r)}function Ma(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Aa,La=Ta.current,_a(Ta,e),_a(ja,ja.current),!0}function Ia(e,t,n){var r=e.stateNode;if(!r)throw Error(o(169));n?(e=Da(e,t,La),r.__reactInternalMemoizedMergedChildContext=e,Ca(ja),Ca(Ta),_a(Ta,e)):Ca(ja),_a(ja,n)}var Fa=null,za=!1,Ba=!1;function $a(e){null===Fa?Fa=[e]:Fa.push(e)}function Ua(){if(!Ba&&null!==Fa){Ba=!0;var e=0,t=vt;try{var n=Fa;for(vt=1;e<n.length;e++){var r=n[e];do{r=r(!0)}while(null!==r)}Fa=null,za=!1}catch(a){throw null!==Fa&&(Fa=Fa.slice(e+1)),We(Je,Ua),a}finally{vt=t,Ba=!1}}return null}var qa=[],Ha=0,Ga=null,Va=0,Wa=[],Qa=0,Ka=null,Ya=1,Za="";function Xa(e,t){qa[Ha++]=Va,qa[Ha++]=Ga,Ga=e,Va=t}function Ja(e,t,n){Wa[Qa++]=Ya,Wa[Qa++]=Za,Wa[Qa++]=Ka,Ka=e;var r=Ya;e=Za;var a=32-it(r)-1;r&=~(1<<a),n+=1;var o=32-it(t)+a;if(30<o){var i=a-a%5;o=(r&(1<<i)-1).toString(32),r>>=i,a-=i,Ya=1<<32-it(t)+a|n<<a|r,Za=o+e}else Ya=1<<o|n<<a|r,Za=e}function eo(e){null!==e.return&&(Xa(e,1),Ja(e,1,0))}function to(e){for(;e===Ga;)Ga=qa[--Ha],qa[Ha]=null,Va=qa[--Ha],qa[Ha]=null;for(;e===Ka;)Ka=Wa[--Qa],Wa[Qa]=null,Za=Wa[--Qa],Wa[Qa]=null,Ya=Wa[--Qa],Wa[Qa]=null}var no=null,ro=null,ao=!1,oo=null;function io(e,t){var n=Rc(5,null,null,0);n.elementType="DELETED",n.stateNode=t,n.return=e,null===(t=e.deletions)?(e.deletions=[n],e.flags|=16):t.push(n)}function lo(e,t){switch(e.tag){case 5:var n=e.type;return null!==(t=1!==t.nodeType||n.toLowerCase()!==t.nodeName.toLowerCase()?null:t)&&(e.stateNode=t,no=e,ro=ca(t.firstChild),!0);case 6:return null!==(t=""===e.pendingProps||3!==t.nodeType?null:t)&&(e.stateNode=t,no=e,ro=null,!0);case 13:return null!==(t=8!==t.nodeType?null:t)&&(n=null!==Ka?{id:Ya,overflow:Za}:null,e.memoizedState={dehydrated:t,treeContext:n,retryLane:1073741824},(n=Rc(18,null,null,0)).stateNode=t,n.return=e,e.child=n,no=e,ro=null,!0);default:return!1}}function so(e){return 0!=(1&e.mode)&&0==(128&e.flags)}function co(e){if(ao){var t=ro;if(t){var n=t;if(!lo(e,t)){if(so(e))throw Error(o(418));t=ca(n.nextSibling);var r=no;t&&lo(e,t)?io(r,n):(e.flags=-4097&e.flags|2,ao=!1,no=e)}}else{if(so(e))throw Error(o(418));e.flags=-4097&e.flags|2,ao=!1,no=e}}}function uo(e){for(e=e.return;null!==e&&5!==e.tag&&3!==e.tag&&13!==e.tag;)e=e.return;no=e}function po(e){if(e!==no)return!1;if(!ao)return uo(e),ao=!0,!1;var t;if((t=3!==e.tag)&&!(t=5!==e.tag)&&(t="head"!==(t=e.type)&&"body"!==t&&!na(e.type,e.memoizedProps)),t&&(t=ro)){if(so(e))throw fo(),Error(o(418));for(;t;)io(e,t),t=ca(t.nextSibling)}if(uo(e),13===e.tag){if(!(e=null!==(e=e.memoizedState)?e.dehydrated:null))throw Error(o(317));e:{for(e=e.nextSibling,t=0;e;){if(8===e.nodeType){var n=e.data;if("/$"===n){if(0===t){ro=ca(e.nextSibling);break e}t--}else"$"!==n&&"$!"!==n&&"$?"!==n||t++}e=e.nextSibling}ro=null}}else ro=no?ca(e.stateNode.nextSibling):null;return!0}function fo(){for(var e=ro;e;)e=ca(e.nextSibling)}function mo(){ro=no=null,ao=!1}function go(e){null===oo?oo=[e]:oo.push(e)}var ho=w.ReactCurrentBatchConfig;function yo(e,t){if(e&&e.defaultProps){for(var n in t=I({},t),e=e.defaultProps)void 0===t[n]&&(t[n]=e[n]);return t}return t}var bo=Ea(null),vo=null,wo=null,ko=null;function xo(){ko=wo=vo=null}function So(e){var t=bo.current;Ca(bo),e._currentValue=t}function Eo(e,t,n){for(;null!==e;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,null!==r&&(r.childLanes|=t)):null!==r&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Co(e,t){vo=e,ko=wo=null,null!==(e=e.dependencies)&&null!==e.firstContext&&(0!=(e.lanes&t)&&(wl=!0),e.firstContext=null)}function _o(e){var t=e._currentValue;if(ko!==e)if(e={context:e,memoizedValue:t,next:null},null===wo){if(null===vo)throw Error(o(308));wo=e,vo.dependencies={lanes:0,firstContext:e}}else wo=wo.next=e;return t}var Ao=null;function To(e){null===Ao?Ao=[e]:Ao.push(e)}function jo(e,t,n,r){var a=t.interleaved;return null===a?(n.next=n,To(t)):(n.next=a.next,a.next=n),t.interleaved=n,Lo(e,r)}function Lo(e,t){e.lanes|=t;var n=e.alternate;for(null!==n&&(n.lanes|=t),n=e,e=e.return;null!==e;)e.childLanes|=t,null!==(n=e.alternate)&&(n.childLanes|=t),n=e,e=e.return;return 3===n.tag?n.stateNode:null}var No=!1;function Ro(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Po(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Oo(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function Do(e,t,n){var r=e.updateQueue;if(null===r)return null;if(r=r.shared,0!=(2&js)){var a=r.pending;return null===a?t.next=t:(t.next=a.next,a.next=t),r.pending=t,Lo(e,n)}return null===(a=r.interleaved)?(t.next=t,To(r)):(t.next=a.next,a.next=t),r.interleaved=t,Lo(e,n)}function Mo(e,t,n){if(null!==(t=t.updateQueue)&&(t=t.shared,0!=(4194240&n))){var r=t.lanes;n|=r&=e.pendingLanes,t.lanes=n,bt(e,n)}}function Io(e,t){var n=e.updateQueue,r=e.alternate;if(null!==r&&n===(r=r.updateQueue)){var a=null,o=null;if(null!==(n=n.firstBaseUpdate)){do{var i={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};null===o?a=o=i:o=o.next=i,n=n.next}while(null!==n);null===o?a=o=t:o=o.next=t}else a=o=t;return n={baseState:r.baseState,firstBaseUpdate:a,lastBaseUpdate:o,shared:r.shared,effects:r.effects},void(e.updateQueue=n)}null===(e=n.lastBaseUpdate)?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function Fo(e,t,n,r){var a=e.updateQueue;No=!1;var o=a.firstBaseUpdate,i=a.lastBaseUpdate,l=a.shared.pending;if(null!==l){a.shared.pending=null;var s=l,c=s.next;s.next=null,null===i?o=c:i.next=c,i=s;var u=e.alternate;null!==u&&((l=(u=u.updateQueue).lastBaseUpdate)!==i&&(null===l?u.firstBaseUpdate=c:l.next=c,u.lastBaseUpdate=s))}if(null!==o){var d=a.baseState;for(i=0,u=c=s=null,l=o;;){var p=l.lane,f=l.eventTime;if((r&p)===p){null!==u&&(u=u.next={eventTime:f,lane:0,tag:l.tag,payload:l.payload,callback:l.callback,next:null});e:{var m=e,g=l;switch(p=t,f=n,g.tag){case 1:if("function"==typeof(m=g.payload)){d=m.call(f,d,p);break e}d=m;break e;case 3:m.flags=-65537&m.flags|128;case 0:if(null==(p="function"==typeof(m=g.payload)?m.call(f,d,p):m))break e;d=I({},d,p);break e;case 2:No=!0}}null!==l.callback&&0!==l.lane&&(e.flags|=64,null===(p=a.effects)?a.effects=[l]:p.push(l))}else f={eventTime:f,lane:p,tag:l.tag,payload:l.payload,callback:l.callback,next:null},null===u?(c=u=f,s=d):u=u.next=f,i|=p;if(null===(l=l.next)){if(null===(l=a.shared.pending))break;l=(p=l).next,p.next=null,a.lastBaseUpdate=p,a.shared.pending=null}}if(null===u&&(s=d),a.baseState=s,a.firstBaseUpdate=c,a.lastBaseUpdate=u,null!==(t=a.shared.interleaved)){a=t;do{i|=a.lane,a=a.next}while(a!==t)}else null===o&&(a.shared.lanes=0);Is|=i,e.lanes=i,e.memoizedState=d}}function zo(e,t,n){if(e=t.effects,t.effects=null,null!==e)for(t=0;t<e.length;t++){var r=e[t],a=r.callback;if(null!==a){if(r.callback=null,r=n,"function"!=typeof a)throw Error(o(191,a));a.call(r)}}}var Bo=(new r.Component).refs;function $o(e,t,n,r){n=null==(n=n(r,t=e.memoizedState))?t:I({},t,n),e.memoizedState=n,0===e.lanes&&(e.updateQueue.baseState=n)}var Uo={isMounted:function(e){return!!(e=e._reactInternals)&&Ue(e)===e},enqueueSetState:function(e,t,n){e=e._reactInternals;var r=tc(),a=nc(e),o=Oo(r,a);o.payload=t,null!=n&&(o.callback=n),null!==(t=Do(e,o,a))&&(rc(t,e,a,r),Mo(t,e,a))},enqueueReplaceState:function(e,t,n){e=e._reactInternals;var r=tc(),a=nc(e),o=Oo(r,a);o.tag=1,o.payload=t,null!=n&&(o.callback=n),null!==(t=Do(e,o,a))&&(rc(t,e,a,r),Mo(t,e,a))},enqueueForceUpdate:function(e,t){e=e._reactInternals;var n=tc(),r=nc(e),a=Oo(n,r);a.tag=2,null!=t&&(a.callback=t),null!==(t=Do(e,a,r))&&(rc(t,e,r,n),Mo(t,e,r))}};function qo(e,t,n,r,a,o,i){return"function"==typeof(e=e.stateNode).shouldComponentUpdate?e.shouldComponentUpdate(r,o,i):!t.prototype||!t.prototype.isPureReactComponent||(!sr(n,r)||!sr(a,o))}function Ho(e,t,n){var r=!1,a=Aa,o=t.contextType;return"object"==typeof o&&null!==o?o=_o(o):(a=Ra(t)?La:Ta.current,o=(r=null!=(r=t.contextTypes))?Na(e,a):Aa),t=new t(n,o),e.memoizedState=null!==t.state&&void 0!==t.state?t.state:null,t.updater=Uo,e.stateNode=t,t._reactInternals=e,r&&((e=e.stateNode).__reactInternalMemoizedUnmaskedChildContext=a,e.__reactInternalMemoizedMaskedChildContext=o),t}function Go(e,t,n,r){e=t.state,"function"==typeof t.componentWillReceiveProps&&t.componentWillReceiveProps(n,r),"function"==typeof t.UNSAFE_componentWillReceiveProps&&t.UNSAFE_componentWillReceiveProps(n,r),t.state!==e&&Uo.enqueueReplaceState(t,t.state,null)}function Vo(e,t,n,r){var a=e.stateNode;a.props=n,a.state=e.memoizedState,a.refs=Bo,Ro(e);var o=t.contextType;"object"==typeof o&&null!==o?a.context=_o(o):(o=Ra(t)?La:Ta.current,a.context=Na(e,o)),a.state=e.memoizedState,"function"==typeof(o=t.getDerivedStateFromProps)&&($o(e,t,o,n),a.state=e.memoizedState),"function"==typeof t.getDerivedStateFromProps||"function"==typeof a.getSnapshotBeforeUpdate||"function"!=typeof a.UNSAFE_componentWillMount&&"function"!=typeof a.componentWillMount||(t=a.state,"function"==typeof a.componentWillMount&&a.componentWillMount(),"function"==typeof a.UNSAFE_componentWillMount&&a.UNSAFE_componentWillMount(),t!==a.state&&Uo.enqueueReplaceState(a,a.state,null),Fo(e,n,a,r),a.state=e.memoizedState),"function"==typeof a.componentDidMount&&(e.flags|=4194308)}function Wo(e,t,n){if(null!==(e=n.ref)&&"function"!=typeof e&&"object"!=typeof e){if(n._owner){if(n=n._owner){if(1!==n.tag)throw Error(o(309));var r=n.stateNode}if(!r)throw Error(o(147,e));var a=r,i=""+e;return null!==t&&null!==t.ref&&"function"==typeof t.ref&&t.ref._stringRef===i?t.ref:(t=function(e){var t=a.refs;t===Bo&&(t=a.refs={}),null===e?delete t[i]:t[i]=e},t._stringRef=i,t)}if("string"!=typeof e)throw Error(o(284));if(!n._owner)throw Error(o(290,e))}return e}function Qo(e,t){throw e=Object.prototype.toString.call(t),Error(o(31,"[object Object]"===e?"object with keys {"+Object.keys(t).join(", ")+"}":e))}function Ko(e){return(0,e._init)(e._payload)}function Yo(e){function t(t,n){if(e){var r=t.deletions;null===r?(t.deletions=[n],t.flags|=16):r.push(n)}}function n(n,r){if(!e)return null;for(;null!==r;)t(n,r),r=r.sibling;return null}function r(e,t){for(e=new Map;null!==t;)null!==t.key?e.set(t.key,t):e.set(t.index,t),t=t.sibling;return e}function a(e,t){return(e=Oc(e,t)).index=0,e.sibling=null,e}function i(t,n,r){return t.index=r,e?null!==(r=t.alternate)?(r=r.index)<n?(t.flags|=2,n):r:(t.flags|=2,n):(t.flags|=1048576,n)}function l(t){return e&&null===t.alternate&&(t.flags|=2),t}function s(e,t,n,r){return null===t||6!==t.tag?((t=Fc(n,e.mode,r)).return=e,t):((t=a(t,n)).return=e,t)}function c(e,t,n,r){var o=n.type;return o===S?d(e,t,n.props.children,r,n.key):null!==t&&(t.elementType===o||"object"==typeof o&&null!==o&&o.$$typeof===R&&Ko(o)===t.type)?((r=a(t,n.props)).ref=Wo(e,t,n),r.return=e,r):((r=Dc(n.type,n.key,n.props,null,e.mode,r)).ref=Wo(e,t,n),r.return=e,r)}function u(e,t,n,r){return null===t||4!==t.tag||t.stateNode.containerInfo!==n.containerInfo||t.stateNode.implementation!==n.implementation?((t=zc(n,e.mode,r)).return=e,t):((t=a(t,n.children||[])).return=e,t)}function d(e,t,n,r,o){return null===t||7!==t.tag?((t=Mc(n,e.mode,r,o)).return=e,t):((t=a(t,n)).return=e,t)}function p(e,t,n){if("string"==typeof t&&""!==t||"number"==typeof t)return(t=Fc(""+t,e.mode,n)).return=e,t;if("object"==typeof t&&null!==t){switch(t.$$typeof){case k:return(n=Dc(t.type,t.key,t.props,null,e.mode,n)).ref=Wo(e,null,t),n.return=e,n;case x:return(t=zc(t,e.mode,n)).return=e,t;case R:return p(e,(0,t._init)(t._payload),n)}if(te(t)||D(t))return(t=Mc(t,e.mode,n,null)).return=e,t;Qo(e,t)}return null}function f(e,t,n,r){var a=null!==t?t.key:null;if("string"==typeof n&&""!==n||"number"==typeof n)return null!==a?null:s(e,t,""+n,r);if("object"==typeof n&&null!==n){switch(n.$$typeof){case k:return n.key===a?c(e,t,n,r):null;case x:return n.key===a?u(e,t,n,r):null;case R:return f(e,t,(a=n._init)(n._payload),r)}if(te(n)||D(n))return null!==a?null:d(e,t,n,r,null);Qo(e,n)}return null}function m(e,t,n,r,a){if("string"==typeof r&&""!==r||"number"==typeof r)return s(t,e=e.get(n)||null,""+r,a);if("object"==typeof r&&null!==r){switch(r.$$typeof){case k:return c(t,e=e.get(null===r.key?n:r.key)||null,r,a);case x:return u(t,e=e.get(null===r.key?n:r.key)||null,r,a);case R:return m(e,t,n,(0,r._init)(r._payload),a)}if(te(r)||D(r))return d(t,e=e.get(n)||null,r,a,null);Qo(t,r)}return null}function g(a,o,l,s){for(var c=null,u=null,d=o,g=o=0,h=null;null!==d&&g<l.length;g++){d.index>g?(h=d,d=null):h=d.sibling;var y=f(a,d,l[g],s);if(null===y){null===d&&(d=h);break}e&&d&&null===y.alternate&&t(a,d),o=i(y,o,g),null===u?c=y:u.sibling=y,u=y,d=h}if(g===l.length)return n(a,d),ao&&Xa(a,g),c;if(null===d){for(;g<l.length;g++)null!==(d=p(a,l[g],s))&&(o=i(d,o,g),null===u?c=d:u.sibling=d,u=d);return ao&&Xa(a,g),c}for(d=r(a,d);g<l.length;g++)null!==(h=m(d,a,g,l[g],s))&&(e&&null!==h.alternate&&d.delete(null===h.key?g:h.key),o=i(h,o,g),null===u?c=h:u.sibling=h,u=h);return e&&d.forEach((function(e){return t(a,e)})),ao&&Xa(a,g),c}function h(a,l,s,c){var u=D(s);if("function"!=typeof u)throw Error(o(150));if(null==(s=u.call(s)))throw Error(o(151));for(var d=u=null,g=l,h=l=0,y=null,b=s.next();null!==g&&!b.done;h++,b=s.next()){g.index>h?(y=g,g=null):y=g.sibling;var v=f(a,g,b.value,c);if(null===v){null===g&&(g=y);break}e&&g&&null===v.alternate&&t(a,g),l=i(v,l,h),null===d?u=v:d.sibling=v,d=v,g=y}if(b.done)return n(a,g),ao&&Xa(a,h),u;if(null===g){for(;!b.done;h++,b=s.next())null!==(b=p(a,b.value,c))&&(l=i(b,l,h),null===d?u=b:d.sibling=b,d=b);return ao&&Xa(a,h),u}for(g=r(a,g);!b.done;h++,b=s.next())null!==(b=m(g,a,h,b.value,c))&&(e&&null!==b.alternate&&g.delete(null===b.key?h:b.key),l=i(b,l,h),null===d?u=b:d.sibling=b,d=b);return e&&g.forEach((function(e){return t(a,e)})),ao&&Xa(a,h),u}return function e(r,o,i,s){if("object"==typeof i&&null!==i&&i.type===S&&null===i.key&&(i=i.props.children),"object"==typeof i&&null!==i){switch(i.$$typeof){case k:e:{for(var c=i.key,u=o;null!==u;){if(u.key===c){if((c=i.type)===S){if(7===u.tag){n(r,u.sibling),(o=a(u,i.props.children)).return=r,r=o;break e}}else if(u.elementType===c||"object"==typeof c&&null!==c&&c.$$typeof===R&&Ko(c)===u.type){n(r,u.sibling),(o=a(u,i.props)).ref=Wo(r,u,i),o.return=r,r=o;break e}n(r,u);break}t(r,u),u=u.sibling}i.type===S?((o=Mc(i.props.children,r.mode,s,i.key)).return=r,r=o):((s=Dc(i.type,i.key,i.props,null,r.mode,s)).ref=Wo(r,o,i),s.return=r,r=s)}return l(r);case x:e:{for(u=i.key;null!==o;){if(o.key===u){if(4===o.tag&&o.stateNode.containerInfo===i.containerInfo&&o.stateNode.implementation===i.implementation){n(r,o.sibling),(o=a(o,i.children||[])).return=r,r=o;break e}n(r,o);break}t(r,o),o=o.sibling}(o=zc(i,r.mode,s)).return=r,r=o}return l(r);case R:return e(r,o,(u=i._init)(i._payload),s)}if(te(i))return g(r,o,i,s);if(D(i))return h(r,o,i,s);Qo(r,i)}return"string"==typeof i&&""!==i||"number"==typeof i?(i=""+i,null!==o&&6===o.tag?(n(r,o.sibling),(o=a(o,i)).return=r,r=o):(n(r,o),(o=Fc(i,r.mode,s)).return=r,r=o),l(r)):n(r,o)}}var Zo=Yo(!0),Xo=Yo(!1),Jo={},ei=Ea(Jo),ti=Ea(Jo),ni=Ea(Jo);function ri(e){if(e===Jo)throw Error(o(174));return e}function ai(e,t){switch(_a(ni,t),_a(ti,e),_a(ei,Jo),e=t.nodeType){case 9:case 11:t=(t=t.documentElement)?t.namespaceURI:se(null,"");break;default:t=se(t=(e=8===e?t.parentNode:t).namespaceURI||null,e=e.tagName)}Ca(ei),_a(ei,t)}function oi(){Ca(ei),Ca(ti),Ca(ni)}function ii(e){ri(ni.current);var t=ri(ei.current),n=se(t,e.type);t!==n&&(_a(ti,e),_a(ei,n))}function li(e){ti.current===e&&(Ca(ei),Ca(ti))}var si=Ea(0);function ci(e){for(var t=e;null!==t;){if(13===t.tag){var n=t.memoizedState;if(null!==n&&(null===(n=n.dehydrated)||"$?"===n.data||"$!"===n.data))return t}else if(19===t.tag&&void 0!==t.memoizedProps.revealOrder){if(0!=(128&t.flags))return t}else if(null!==t.child){t.child.return=t,t=t.child;continue}if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return null;t=t.return}t.sibling.return=t.return,t=t.sibling}return null}var ui=[];function di(){for(var e=0;e<ui.length;e++)ui[e]._workInProgressVersionPrimary=null;ui.length=0}var pi=w.ReactCurrentDispatcher,fi=w.ReactCurrentBatchConfig,mi=0,gi=null,hi=null,yi=null,bi=!1,vi=!1,wi=0,ki=0;function xi(){throw Error(o(321))}function Si(e,t){if(null===t)return!1;for(var n=0;n<t.length&&n<e.length;n++)if(!lr(e[n],t[n]))return!1;return!0}function Ei(e,t,n,r,a,i){if(mi=i,gi=t,t.memoizedState=null,t.updateQueue=null,t.lanes=0,pi.current=null===e||null===e.memoizedState?ll:sl,e=n(r,a),vi){i=0;do{if(vi=!1,wi=0,25<=i)throw Error(o(301));i+=1,yi=hi=null,t.updateQueue=null,pi.current=cl,e=n(r,a)}while(vi)}if(pi.current=il,t=null!==hi&&null!==hi.next,mi=0,yi=hi=gi=null,bi=!1,t)throw Error(o(300));return e}function Ci(){var e=0!==wi;return wi=0,e}function _i(){var e={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null};return null===yi?gi.memoizedState=yi=e:yi=yi.next=e,yi}function Ai(){if(null===hi){var e=gi.alternate;e=null!==e?e.memoizedState:null}else e=hi.next;var t=null===yi?gi.memoizedState:yi.next;if(null!==t)yi=t,hi=e;else{if(null===e)throw Error(o(310));e={memoizedState:(hi=e).memoizedState,baseState:hi.baseState,baseQueue:hi.baseQueue,queue:hi.queue,next:null},null===yi?gi.memoizedState=yi=e:yi=yi.next=e}return yi}function Ti(e,t){return"function"==typeof t?t(e):t}function ji(e){var t=Ai(),n=t.queue;if(null===n)throw Error(o(311));n.lastRenderedReducer=e;var r=hi,a=r.baseQueue,i=n.pending;if(null!==i){if(null!==a){var l=a.next;a.next=i.next,i.next=l}r.baseQueue=a=i,n.pending=null}if(null!==a){i=a.next,r=r.baseState;var s=l=null,c=null,u=i;do{var d=u.lane;if((mi&d)===d)null!==c&&(c=c.next={lane:0,action:u.action,hasEagerState:u.hasEagerState,eagerState:u.eagerState,next:null}),r=u.hasEagerState?u.eagerState:e(r,u.action);else{var p={lane:d,action:u.action,hasEagerState:u.hasEagerState,eagerState:u.eagerState,next:null};null===c?(s=c=p,l=r):c=c.next=p,gi.lanes|=d,Is|=d}u=u.next}while(null!==u&&u!==i);null===c?l=r:c.next=s,lr(r,t.memoizedState)||(wl=!0),t.memoizedState=r,t.baseState=l,t.baseQueue=c,n.lastRenderedState=r}if(null!==(e=n.interleaved)){a=e;do{i=a.lane,gi.lanes|=i,Is|=i,a=a.next}while(a!==e)}else null===a&&(n.lanes=0);return[t.memoizedState,n.dispatch]}function Li(e){var t=Ai(),n=t.queue;if(null===n)throw Error(o(311));n.lastRenderedReducer=e;var r=n.dispatch,a=n.pending,i=t.memoizedState;if(null!==a){n.pending=null;var l=a=a.next;do{i=e(i,l.action),l=l.next}while(l!==a);lr(i,t.memoizedState)||(wl=!0),t.memoizedState=i,null===t.baseQueue&&(t.baseState=i),n.lastRenderedState=i}return[i,r]}function Ni(){}function Ri(e,t){var n=gi,r=Ai(),a=t(),i=!lr(r.memoizedState,a);if(i&&(r.memoizedState=a,wl=!0),r=r.queue,Hi(Di.bind(null,n,r,e),[e]),r.getSnapshot!==t||i||null!==yi&&1&yi.memoizedState.tag){if(n.flags|=2048,zi(9,Oi.bind(null,n,r,a,t),void 0,null),null===Ls)throw Error(o(349));0!=(30&mi)||Pi(n,t,a)}return a}function Pi(e,t,n){e.flags|=16384,e={getSnapshot:t,value:n},null===(t=gi.updateQueue)?(t={lastEffect:null,stores:null},gi.updateQueue=t,t.stores=[e]):null===(n=t.stores)?t.stores=[e]:n.push(e)}function Oi(e,t,n,r){t.value=n,t.getSnapshot=r,Mi(t)&&Ii(e)}function Di(e,t,n){return n((function(){Mi(t)&&Ii(e)}))}function Mi(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!lr(e,n)}catch(r){return!0}}function Ii(e){var t=Lo(e,1);null!==t&&rc(t,e,1,-1)}function Fi(e){var t=_i();return"function"==typeof e&&(e=e()),t.memoizedState=t.baseState=e,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:Ti,lastRenderedState:e},t.queue=e,e=e.dispatch=nl.bind(null,gi,e),[t.memoizedState,e]}function zi(e,t,n,r){return e={tag:e,create:t,destroy:n,deps:r,next:null},null===(t=gi.updateQueue)?(t={lastEffect:null,stores:null},gi.updateQueue=t,t.lastEffect=e.next=e):null===(n=t.lastEffect)?t.lastEffect=e.next=e:(r=n.next,n.next=e,e.next=r,t.lastEffect=e),e}function Bi(){return Ai().memoizedState}function $i(e,t,n,r){var a=_i();gi.flags|=e,a.memoizedState=zi(1|t,n,void 0,void 0===r?null:r)}function Ui(e,t,n,r){var a=Ai();r=void 0===r?null:r;var o=void 0;if(null!==hi){var i=hi.memoizedState;if(o=i.destroy,null!==r&&Si(r,i.deps))return void(a.memoizedState=zi(t,n,o,r))}gi.flags|=e,a.memoizedState=zi(1|t,n,o,r)}function qi(e,t){return $i(8390656,8,e,t)}function Hi(e,t){return Ui(2048,8,e,t)}function Gi(e,t){return Ui(4,2,e,t)}function Vi(e,t){return Ui(4,4,e,t)}function Wi(e,t){return"function"==typeof t?(e=e(),t(e),function(){t(null)}):null!=t?(e=e(),t.current=e,function(){t.current=null}):void 0}function Qi(e,t,n){return n=null!=n?n.concat([e]):null,Ui(4,4,Wi.bind(null,t,e),n)}function Ki(){}function Yi(e,t){var n=Ai();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&Si(t,r[1])?r[0]:(n.memoizedState=[e,t],e)}function Zi(e,t){var n=Ai();t=void 0===t?null:t;var r=n.memoizedState;return null!==r&&null!==t&&Si(t,r[1])?r[0]:(e=e(),n.memoizedState=[e,t],e)}function Xi(e,t,n){return 0==(21&mi)?(e.baseState&&(e.baseState=!1,wl=!0),e.memoizedState=n):(lr(n,t)||(n=gt(),gi.lanes|=n,Is|=n,e.baseState=!0),t)}function Ji(e,t){var n=vt;vt=0!==n&&4>n?n:4,e(!0);var r=fi.transition;fi.transition={};try{e(!1),t()}finally{vt=n,fi.transition=r}}function el(){return Ai().memoizedState}function tl(e,t,n){var r=nc(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},rl(e))al(t,n);else if(null!==(n=jo(e,t,n,r))){rc(n,e,r,tc()),ol(n,t,r)}}function nl(e,t,n){var r=nc(e),a={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(rl(e))al(t,a);else{var o=e.alternate;if(0===e.lanes&&(null===o||0===o.lanes)&&null!==(o=t.lastRenderedReducer))try{var i=t.lastRenderedState,l=o(i,n);if(a.hasEagerState=!0,a.eagerState=l,lr(l,i)){var s=t.interleaved;return null===s?(a.next=a,To(t)):(a.next=s.next,s.next=a),void(t.interleaved=a)}}catch(c){}null!==(n=jo(e,t,a,r))&&(rc(n,e,r,a=tc()),ol(n,t,r))}}function rl(e){var t=e.alternate;return e===gi||null!==t&&t===gi}function al(e,t){vi=bi=!0;var n=e.pending;null===n?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function ol(e,t,n){if(0!=(4194240&n)){var r=t.lanes;n|=r&=e.pendingLanes,t.lanes=n,bt(e,n)}}var il={readContext:_o,useCallback:xi,useContext:xi,useEffect:xi,useImperativeHandle:xi,useInsertionEffect:xi,useLayoutEffect:xi,useMemo:xi,useReducer:xi,useRef:xi,useState:xi,useDebugValue:xi,useDeferredValue:xi,useTransition:xi,useMutableSource:xi,useSyncExternalStore:xi,useId:xi,unstable_isNewReconciler:!1},ll={readContext:_o,useCallback:function(e,t){return _i().memoizedState=[e,void 0===t?null:t],e},useContext:_o,useEffect:qi,useImperativeHandle:function(e,t,n){return n=null!=n?n.concat([e]):null,$i(4194308,4,Wi.bind(null,t,e),n)},useLayoutEffect:function(e,t){return $i(4194308,4,e,t)},useInsertionEffect:function(e,t){return $i(4,2,e,t)},useMemo:function(e,t){var n=_i();return t=void 0===t?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=_i();return t=void 0!==n?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=tl.bind(null,gi,e),[r.memoizedState,e]},useRef:function(e){return e={current:e},_i().memoizedState=e},useState:Fi,useDebugValue:Ki,useDeferredValue:function(e){return _i().memoizedState=e},useTransition:function(){var e=Fi(!1),t=e[0];return e=Ji.bind(null,e[1]),_i().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=gi,a=_i();if(ao){if(void 0===n)throw Error(o(407));n=n()}else{if(n=t(),null===Ls)throw Error(o(349));0!=(30&mi)||Pi(r,t,n)}a.memoizedState=n;var i={value:n,getSnapshot:t};return a.queue=i,qi(Di.bind(null,r,i,e),[e]),r.flags|=2048,zi(9,Oi.bind(null,r,i,n,t),void 0,null),n},useId:function(){var e=_i(),t=Ls.identifierPrefix;if(ao){var n=Za;t=":"+t+"R"+(n=(Ya&~(1<<32-it(Ya)-1)).toString(32)+n),0<(n=wi++)&&(t+="H"+n.toString(32)),t+=":"}else t=":"+t+"r"+(n=ki++).toString(32)+":";return e.memoizedState=t},unstable_isNewReconciler:!1},sl={readContext:_o,useCallback:Yi,useContext:_o,useEffect:Hi,useImperativeHandle:Qi,useInsertionEffect:Gi,useLayoutEffect:Vi,useMemo:Zi,useReducer:ji,useRef:Bi,useState:function(){return ji(Ti)},useDebugValue:Ki,useDeferredValue:function(e){return Xi(Ai(),hi.memoizedState,e)},useTransition:function(){return[ji(Ti)[0],Ai().memoizedState]},useMutableSource:Ni,useSyncExternalStore:Ri,useId:el,unstable_isNewReconciler:!1},cl={readContext:_o,useCallback:Yi,useContext:_o,useEffect:Hi,useImperativeHandle:Qi,useInsertionEffect:Gi,useLayoutEffect:Vi,useMemo:Zi,useReducer:Li,useRef:Bi,useState:function(){return Li(Ti)},useDebugValue:Ki,useDeferredValue:function(e){var t=Ai();return null===hi?t.memoizedState=e:Xi(t,hi.memoizedState,e)},useTransition:function(){return[Li(Ti)[0],Ai().memoizedState]},useMutableSource:Ni,useSyncExternalStore:Ri,useId:el,unstable_isNewReconciler:!1};function ul(e,t){try{var n="",r=t;do{n+=$(r),r=r.return}while(r);var a=n}catch(o){a="\nError generating stack: "+o.message+"\n"+o.stack}return{value:e,source:t,stack:a,digest:null}}function dl(e,t,n){return{value:e,source:null,stack:null!=n?n:null,digest:null!=t?t:null}}function pl(e,t){try{console.error(t.value)}catch(n){setTimeout((function(){throw n}))}}var fl="function"==typeof WeakMap?WeakMap:Map;function ml(e,t,n){(n=Oo(-1,n)).tag=3,n.payload={element:null};var r=t.value;return n.callback=function(){Gs||(Gs=!0,Vs=r),pl(0,t)},n}function gl(e,t,n){(n=Oo(-1,n)).tag=3;var r=e.type.getDerivedStateFromError;if("function"==typeof r){var a=t.value;n.payload=function(){return r(a)},n.callback=function(){pl(0,t)}}var o=e.stateNode;return null!==o&&"function"==typeof o.componentDidCatch&&(n.callback=function(){pl(0,t),"function"!=typeof r&&(null===Ws?Ws=new Set([this]):Ws.add(this));var e=t.stack;this.componentDidCatch(t.value,{componentStack:null!==e?e:""})}),n}function hl(e,t,n){var r=e.pingCache;if(null===r){r=e.pingCache=new fl;var a=new Set;r.set(t,a)}else void 0===(a=r.get(t))&&(a=new Set,r.set(t,a));a.has(n)||(a.add(n),e=_c.bind(null,e,t,n),t.then(e,e))}function yl(e){do{var t;if((t=13===e.tag)&&(t=null===(t=e.memoizedState)||null!==t.dehydrated),t)return e;e=e.return}while(null!==e);return null}function bl(e,t,n,r,a){return 0==(1&e.mode)?(e===t?e.flags|=65536:(e.flags|=128,n.flags|=131072,n.flags&=-52805,1===n.tag&&(null===n.alternate?n.tag=17:((t=Oo(-1,1)).tag=2,Do(n,t,1))),n.lanes|=1),e):(e.flags|=65536,e.lanes=a,e)}var vl=w.ReactCurrentOwner,wl=!1;function kl(e,t,n,r){t.child=null===e?Xo(t,null,n,r):Zo(t,e.child,n,r)}function xl(e,t,n,r,a){n=n.render;var o=t.ref;return Co(t,a),r=Ei(e,t,n,r,o,a),n=Ci(),null===e||wl?(ao&&n&&eo(t),t.flags|=1,kl(e,t,r,a),t.child):(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~a,Gl(e,t,a))}function Sl(e,t,n,r,a){if(null===e){var o=n.type;return"function"!=typeof o||Pc(o)||void 0!==o.defaultProps||null!==n.compare||void 0!==n.defaultProps?((e=Dc(n.type,null,r,t,t.mode,a)).ref=t.ref,e.return=t,t.child=e):(t.tag=15,t.type=o,El(e,t,o,r,a))}if(o=e.child,0==(e.lanes&a)){var i=o.memoizedProps;if((n=null!==(n=n.compare)?n:sr)(i,r)&&e.ref===t.ref)return Gl(e,t,a)}return t.flags|=1,(e=Oc(o,r)).ref=t.ref,e.return=t,t.child=e}function El(e,t,n,r,a){if(null!==e){var o=e.memoizedProps;if(sr(o,r)&&e.ref===t.ref){if(wl=!1,t.pendingProps=r=o,0==(e.lanes&a))return t.lanes=e.lanes,Gl(e,t,a);0!=(131072&e.flags)&&(wl=!0)}}return Al(e,t,n,r,a)}function Cl(e,t,n){var r=t.pendingProps,a=r.children,o=null!==e?e.memoizedState:null;if("hidden"===r.mode)if(0==(1&t.mode))t.memoizedState={baseLanes:0,cachePool:null,transitions:null},_a(Os,Ps),Ps|=n;else{if(0==(1073741824&n))return e=null!==o?o.baseLanes|n:n,t.lanes=t.childLanes=1073741824,t.memoizedState={baseLanes:e,cachePool:null,transitions:null},t.updateQueue=null,_a(Os,Ps),Ps|=e,null;t.memoizedState={baseLanes:0,cachePool:null,transitions:null},r=null!==o?o.baseLanes:n,_a(Os,Ps),Ps|=r}else null!==o?(r=o.baseLanes|n,t.memoizedState=null):r=n,_a(Os,Ps),Ps|=r;return kl(e,t,a,n),t.child}function _l(e,t){var n=t.ref;(null===e&&null!==n||null!==e&&e.ref!==n)&&(t.flags|=512,t.flags|=2097152)}function Al(e,t,n,r,a){var o=Ra(n)?La:Ta.current;return o=Na(t,o),Co(t,a),n=Ei(e,t,n,r,o,a),r=Ci(),null===e||wl?(ao&&r&&eo(t),t.flags|=1,kl(e,t,n,a),t.child):(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~a,Gl(e,t,a))}function Tl(e,t,n,r,a){if(Ra(n)){var o=!0;Ma(t)}else o=!1;if(Co(t,a),null===t.stateNode)Hl(e,t),Ho(t,n,r),Vo(t,n,r,a),r=!0;else if(null===e){var i=t.stateNode,l=t.memoizedProps;i.props=l;var s=i.context,c=n.contextType;"object"==typeof c&&null!==c?c=_o(c):c=Na(t,c=Ra(n)?La:Ta.current);var u=n.getDerivedStateFromProps,d="function"==typeof u||"function"==typeof i.getSnapshotBeforeUpdate;d||"function"!=typeof i.UNSAFE_componentWillReceiveProps&&"function"!=typeof i.componentWillReceiveProps||(l!==r||s!==c)&&Go(t,i,r,c),No=!1;var p=t.memoizedState;i.state=p,Fo(t,r,i,a),s=t.memoizedState,l!==r||p!==s||ja.current||No?("function"==typeof u&&($o(t,n,u,r),s=t.memoizedState),(l=No||qo(t,n,l,r,p,s,c))?(d||"function"!=typeof i.UNSAFE_componentWillMount&&"function"!=typeof i.componentWillMount||("function"==typeof i.componentWillMount&&i.componentWillMount(),"function"==typeof i.UNSAFE_componentWillMount&&i.UNSAFE_componentWillMount()),"function"==typeof i.componentDidMount&&(t.flags|=4194308)):("function"==typeof i.componentDidMount&&(t.flags|=4194308),t.memoizedProps=r,t.memoizedState=s),i.props=r,i.state=s,i.context=c,r=l):("function"==typeof i.componentDidMount&&(t.flags|=4194308),r=!1)}else{i=t.stateNode,Po(e,t),l=t.memoizedProps,c=t.type===t.elementType?l:yo(t.type,l),i.props=c,d=t.pendingProps,p=i.context,"object"==typeof(s=n.contextType)&&null!==s?s=_o(s):s=Na(t,s=Ra(n)?La:Ta.current);var f=n.getDerivedStateFromProps;(u="function"==typeof f||"function"==typeof i.getSnapshotBeforeUpdate)||"function"!=typeof i.UNSAFE_componentWillReceiveProps&&"function"!=typeof i.componentWillReceiveProps||(l!==d||p!==s)&&Go(t,i,r,s),No=!1,p=t.memoizedState,i.state=p,Fo(t,r,i,a);var m=t.memoizedState;l!==d||p!==m||ja.current||No?("function"==typeof f&&($o(t,n,f,r),m=t.memoizedState),(c=No||qo(t,n,c,r,p,m,s)||!1)?(u||"function"!=typeof i.UNSAFE_componentWillUpdate&&"function"!=typeof i.componentWillUpdate||("function"==typeof i.componentWillUpdate&&i.componentWillUpdate(r,m,s),"function"==typeof i.UNSAFE_componentWillUpdate&&i.UNSAFE_componentWillUpdate(r,m,s)),"function"==typeof i.componentDidUpdate&&(t.flags|=4),"function"==typeof i.getSnapshotBeforeUpdate&&(t.flags|=1024)):("function"!=typeof i.componentDidUpdate||l===e.memoizedProps&&p===e.memoizedState||(t.flags|=4),"function"!=typeof i.getSnapshotBeforeUpdate||l===e.memoizedProps&&p===e.memoizedState||(t.flags|=1024),t.memoizedProps=r,t.memoizedState=m),i.props=r,i.state=m,i.context=s,r=c):("function"!=typeof i.componentDidUpdate||l===e.memoizedProps&&p===e.memoizedState||(t.flags|=4),"function"!=typeof i.getSnapshotBeforeUpdate||l===e.memoizedProps&&p===e.memoizedState||(t.flags|=1024),r=!1)}return jl(e,t,n,r,o,a)}function jl(e,t,n,r,a,o){_l(e,t);var i=0!=(128&t.flags);if(!r&&!i)return a&&Ia(t,n,!1),Gl(e,t,o);r=t.stateNode,vl.current=t;var l=i&&"function"!=typeof n.getDerivedStateFromError?null:r.render();return t.flags|=1,null!==e&&i?(t.child=Zo(t,e.child,null,o),t.child=Zo(t,null,l,o)):kl(e,t,l,o),t.memoizedState=r.state,a&&Ia(t,n,!0),t.child}function Ll(e){var t=e.stateNode;t.pendingContext?Oa(0,t.pendingContext,t.pendingContext!==t.context):t.context&&Oa(0,t.context,!1),ai(e,t.containerInfo)}function Nl(e,t,n,r,a){return mo(),go(a),t.flags|=256,kl(e,t,n,r),t.child}var Rl,Pl,Ol,Dl,Ml={dehydrated:null,treeContext:null,retryLane:0};function Il(e){return{baseLanes:e,cachePool:null,transitions:null}}function Fl(e,t,n){var r,a=t.pendingProps,i=si.current,l=!1,s=0!=(128&t.flags);if((r=s)||(r=(null===e||null!==e.memoizedState)&&0!=(2&i)),r?(l=!0,t.flags&=-129):null!==e&&null===e.memoizedState||(i|=1),_a(si,1&i),null===e)return co(t),null!==(e=t.memoizedState)&&null!==(e=e.dehydrated)?(0==(1&t.mode)?t.lanes=1:"$!"===e.data?t.lanes=8:t.lanes=1073741824,null):(s=a.children,e=a.fallback,l?(a=t.mode,l=t.child,s={mode:"hidden",children:s},0==(1&a)&&null!==l?(l.childLanes=0,l.pendingProps=s):l=Ic(s,a,0,null),e=Mc(e,a,n,null),l.return=t,e.return=t,l.sibling=e,t.child=l,t.child.memoizedState=Il(n),t.memoizedState=Ml,e):zl(t,s));if(null!==(i=e.memoizedState)&&null!==(r=i.dehydrated))return function(e,t,n,r,a,i,l){if(n)return 256&t.flags?(t.flags&=-257,Bl(e,t,l,r=dl(Error(o(422))))):null!==t.memoizedState?(t.child=e.child,t.flags|=128,null):(i=r.fallback,a=t.mode,r=Ic({mode:"visible",children:r.children},a,0,null),(i=Mc(i,a,l,null)).flags|=2,r.return=t,i.return=t,r.sibling=i,t.child=r,0!=(1&t.mode)&&Zo(t,e.child,null,l),t.child.memoizedState=Il(l),t.memoizedState=Ml,i);if(0==(1&t.mode))return Bl(e,t,l,null);if("$!"===a.data){if(r=a.nextSibling&&a.nextSibling.dataset)var s=r.dgst;return r=s,Bl(e,t,l,r=dl(i=Error(o(419)),r,void 0))}if(s=0!=(l&e.childLanes),wl||s){if(null!==(r=Ls)){switch(l&-l){case 4:a=2;break;case 16:a=8;break;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:a=32;break;case 536870912:a=268435456;break;default:a=0}0!==(a=0!=(a&(r.suspendedLanes|l))?0:a)&&a!==i.retryLane&&(i.retryLane=a,Lo(e,a),rc(r,e,a,-1))}return hc(),Bl(e,t,l,r=dl(Error(o(421))))}return"$?"===a.data?(t.flags|=128,t.child=e.child,t=Tc.bind(null,e),a._reactRetry=t,null):(e=i.treeContext,ro=ca(a.nextSibling),no=t,ao=!0,oo=null,null!==e&&(Wa[Qa++]=Ya,Wa[Qa++]=Za,Wa[Qa++]=Ka,Ya=e.id,Za=e.overflow,Ka=t),t=zl(t,r.children),t.flags|=4096,t)}(e,t,s,a,r,i,n);if(l){l=a.fallback,s=t.mode,r=(i=e.child).sibling;var c={mode:"hidden",children:a.children};return 0==(1&s)&&t.child!==i?((a=t.child).childLanes=0,a.pendingProps=c,t.deletions=null):(a=Oc(i,c)).subtreeFlags=14680064&i.subtreeFlags,null!==r?l=Oc(r,l):(l=Mc(l,s,n,null)).flags|=2,l.return=t,a.return=t,a.sibling=l,t.child=a,a=l,l=t.child,s=null===(s=e.child.memoizedState)?Il(n):{baseLanes:s.baseLanes|n,cachePool:null,transitions:s.transitions},l.memoizedState=s,l.childLanes=e.childLanes&~n,t.memoizedState=Ml,a}return e=(l=e.child).sibling,a=Oc(l,{mode:"visible",children:a.children}),0==(1&t.mode)&&(a.lanes=n),a.return=t,a.sibling=null,null!==e&&(null===(n=t.deletions)?(t.deletions=[e],t.flags|=16):n.push(e)),t.child=a,t.memoizedState=null,a}function zl(e,t){return(t=Ic({mode:"visible",children:t},e.mode,0,null)).return=e,e.child=t}function Bl(e,t,n,r){return null!==r&&go(r),Zo(t,e.child,null,n),(e=zl(t,t.pendingProps.children)).flags|=2,t.memoizedState=null,e}function $l(e,t,n){e.lanes|=t;var r=e.alternate;null!==r&&(r.lanes|=t),Eo(e.return,t,n)}function Ul(e,t,n,r,a){var o=e.memoizedState;null===o?e.memoizedState={isBackwards:t,rendering:null,renderingStartTime:0,last:r,tail:n,tailMode:a}:(o.isBackwards=t,o.rendering=null,o.renderingStartTime=0,o.last=r,o.tail=n,o.tailMode=a)}function ql(e,t,n){var r=t.pendingProps,a=r.revealOrder,o=r.tail;if(kl(e,t,r.children,n),0!=(2&(r=si.current)))r=1&r|2,t.flags|=128;else{if(null!==e&&0!=(128&e.flags))e:for(e=t.child;null!==e;){if(13===e.tag)null!==e.memoizedState&&$l(e,n,t);else if(19===e.tag)$l(e,n,t);else if(null!==e.child){e.child.return=e,e=e.child;continue}if(e===t)break e;for(;null===e.sibling;){if(null===e.return||e.return===t)break e;e=e.return}e.sibling.return=e.return,e=e.sibling}r&=1}if(_a(si,r),0==(1&t.mode))t.memoizedState=null;else switch(a){case"forwards":for(n=t.child,a=null;null!==n;)null!==(e=n.alternate)&&null===ci(e)&&(a=n),n=n.sibling;null===(n=a)?(a=t.child,t.child=null):(a=n.sibling,n.sibling=null),Ul(t,!1,a,n,o);break;case"backwards":for(n=null,a=t.child,t.child=null;null!==a;){if(null!==(e=a.alternate)&&null===ci(e)){t.child=a;break}e=a.sibling,a.sibling=n,n=a,a=e}Ul(t,!0,n,null,o);break;case"together":Ul(t,!1,null,null,void 0);break;default:t.memoizedState=null}return t.child}function Hl(e,t){0==(1&t.mode)&&null!==e&&(e.alternate=null,t.alternate=null,t.flags|=2)}function Gl(e,t,n){if(null!==e&&(t.dependencies=e.dependencies),Is|=t.lanes,0==(n&t.childLanes))return null;if(null!==e&&t.child!==e.child)throw Error(o(153));if(null!==t.child){for(n=Oc(e=t.child,e.pendingProps),t.child=n,n.return=t;null!==e.sibling;)e=e.sibling,(n=n.sibling=Oc(e,e.pendingProps)).return=t;n.sibling=null}return t.child}function Vl(e,t){if(!ao)switch(e.tailMode){case"hidden":t=e.tail;for(var n=null;null!==t;)null!==t.alternate&&(n=t),t=t.sibling;null===n?e.tail=null:n.sibling=null;break;case"collapsed":n=e.tail;for(var r=null;null!==n;)null!==n.alternate&&(r=n),n=n.sibling;null===r?t||null===e.tail?e.tail=null:e.tail.sibling=null:r.sibling=null}}function Wl(e){var t=null!==e.alternate&&e.alternate.child===e.child,n=0,r=0;if(t)for(var a=e.child;null!==a;)n|=a.lanes|a.childLanes,r|=14680064&a.subtreeFlags,r|=14680064&a.flags,a.return=e,a=a.sibling;else for(a=e.child;null!==a;)n|=a.lanes|a.childLanes,r|=a.subtreeFlags,r|=a.flags,a.return=e,a=a.sibling;return e.subtreeFlags|=r,e.childLanes=n,t}function Ql(e,t,n){var r=t.pendingProps;switch(to(t),t.tag){case 2:case 16:case 15:case 0:case 11:case 7:case 8:case 12:case 9:case 14:return Wl(t),null;case 1:case 17:return Ra(t.type)&&Pa(),Wl(t),null;case 3:return r=t.stateNode,oi(),Ca(ja),Ca(Ta),di(),r.pendingContext&&(r.context=r.pendingContext,r.pendingContext=null),null!==e&&null!==e.child||(po(t)?t.flags|=4:null===e||e.memoizedState.isDehydrated&&0==(256&t.flags)||(t.flags|=1024,null!==oo&&(lc(oo),oo=null))),Pl(e,t),Wl(t),null;case 5:li(t);var a=ri(ni.current);if(n=t.type,null!==e&&null!=t.stateNode)Ol(e,t,n,r,a),e.ref!==t.ref&&(t.flags|=512,t.flags|=2097152);else{if(!r){if(null===t.stateNode)throw Error(o(166));return Wl(t),null}if(e=ri(ei.current),po(t)){r=t.stateNode,n=t.type;var i=t.memoizedProps;switch(r[pa]=t,r[fa]=i,e=0!=(1&t.mode),n){case"dialog":zr("cancel",r),zr("close",r);break;case"iframe":case"object":case"embed":zr("load",r);break;case"video":case"audio":for(a=0;a<Dr.length;a++)zr(Dr[a],r);break;case"source":zr("error",r);break;case"img":case"image":case"link":zr("error",r),zr("load",r);break;case"details":zr("toggle",r);break;case"input":Y(r,i),zr("invalid",r);break;case"select":r._wrapperState={wasMultiple:!!i.multiple},zr("invalid",r);break;case"textarea":ae(r,i),zr("invalid",r)}for(var s in be(n,i),a=null,i)if(i.hasOwnProperty(s)){var c=i[s];"children"===s?"string"==typeof c?r.textContent!==c&&(!0!==i.suppressHydrationWarning&&Xr(r.textContent,c,e),a=["children",c]):"number"==typeof c&&r.textContent!==""+c&&(!0!==i.suppressHydrationWarning&&Xr(r.textContent,c,e),a=["children",""+c]):l.hasOwnProperty(s)&&null!=c&&"onScroll"===s&&zr("scroll",r)}switch(n){case"input":V(r),J(r,i,!0);break;case"textarea":V(r),ie(r);break;case"select":case"option":break;default:"function"==typeof i.onClick&&(r.onclick=Jr)}r=a,t.updateQueue=r,null!==r&&(t.flags|=4)}else{s=9===a.nodeType?a:a.ownerDocument,"http://www.w3.org/1999/xhtml"===e&&(e=le(n)),"http://www.w3.org/1999/xhtml"===e?"script"===n?((e=s.createElement("div")).innerHTML="<script><\/script>",e=e.removeChild(e.firstChild)):"string"==typeof r.is?e=s.createElement(n,{is:r.is}):(e=s.createElement(n),"select"===n&&(s=e,r.multiple?s.multiple=!0:r.size&&(s.size=r.size))):e=s.createElementNS(e,n),e[pa]=t,e[fa]=r,Rl(e,t,!1,!1),t.stateNode=e;e:{switch(s=ve(n,r),n){case"dialog":zr("cancel",e),zr("close",e),a=r;break;case"iframe":case"object":case"embed":zr("load",e),a=r;break;case"video":case"audio":for(a=0;a<Dr.length;a++)zr(Dr[a],e);a=r;break;case"source":zr("error",e),a=r;break;case"img":case"image":case"link":zr("error",e),zr("load",e),a=r;break;case"details":zr("toggle",e),a=r;break;case"input":Y(e,r),a=K(e,r),zr("invalid",e);break;case"option":default:a=r;break;case"select":e._wrapperState={wasMultiple:!!r.multiple},a=I({},r,{value:void 0}),zr("invalid",e);break;case"textarea":ae(e,r),a=re(e,r),zr("invalid",e)}for(i in be(n,a),c=a)if(c.hasOwnProperty(i)){var u=c[i];"style"===i?he(e,u):"dangerouslySetInnerHTML"===i?null!=(u=u?u.__html:void 0)&&de(e,u):"children"===i?"string"==typeof u?("textarea"!==n||""!==u)&&pe(e,u):"number"==typeof u&&pe(e,""+u):"suppressContentEditableWarning"!==i&&"suppressHydrationWarning"!==i&&"autoFocus"!==i&&(l.hasOwnProperty(i)?null!=u&&"onScroll"===i&&zr("scroll",e):null!=u&&v(e,i,u,s))}switch(n){case"input":V(e),J(e,r,!1);break;case"textarea":V(e),ie(e);break;case"option":null!=r.value&&e.setAttribute("value",""+H(r.value));break;case"select":e.multiple=!!r.multiple,null!=(i=r.value)?ne(e,!!r.multiple,i,!1):null!=r.defaultValue&&ne(e,!!r.multiple,r.defaultValue,!0);break;default:"function"==typeof a.onClick&&(e.onclick=Jr)}switch(n){case"button":case"input":case"select":case"textarea":r=!!r.autoFocus;break e;case"img":r=!0;break e;default:r=!1}}r&&(t.flags|=4)}null!==t.ref&&(t.flags|=512,t.flags|=2097152)}return Wl(t),null;case 6:if(e&&null!=t.stateNode)Dl(e,t,e.memoizedProps,r);else{if("string"!=typeof r&&null===t.stateNode)throw Error(o(166));if(n=ri(ni.current),ri(ei.current),po(t)){if(r=t.stateNode,n=t.memoizedProps,r[pa]=t,(i=r.nodeValue!==n)&&null!==(e=no))switch(e.tag){case 3:Xr(r.nodeValue,n,0!=(1&e.mode));break;case 5:!0!==e.memoizedProps.suppressHydrationWarning&&Xr(r.nodeValue,n,0!=(1&e.mode))}i&&(t.flags|=4)}else(r=(9===n.nodeType?n:n.ownerDocument).createTextNode(r))[pa]=t,t.stateNode=r}return Wl(t),null;case 13:if(Ca(si),r=t.memoizedState,null===e||null!==e.memoizedState&&null!==e.memoizedState.dehydrated){if(ao&&null!==ro&&0!=(1&t.mode)&&0==(128&t.flags))fo(),mo(),t.flags|=98560,i=!1;else if(i=po(t),null!==r&&null!==r.dehydrated){if(null===e){if(!i)throw Error(o(318));if(!(i=null!==(i=t.memoizedState)?i.dehydrated:null))throw Error(o(317));i[pa]=t}else mo(),0==(128&t.flags)&&(t.memoizedState=null),t.flags|=4;Wl(t),i=!1}else null!==oo&&(lc(oo),oo=null),i=!0;if(!i)return 65536&t.flags?t:null}return 0!=(128&t.flags)?(t.lanes=n,t):((r=null!==r)!==(null!==e&&null!==e.memoizedState)&&r&&(t.child.flags|=8192,0!=(1&t.mode)&&(null===e||0!=(1&si.current)?0===Ds&&(Ds=3):hc())),null!==t.updateQueue&&(t.flags|=4),Wl(t),null);case 4:return oi(),Pl(e,t),null===e&&Ur(t.stateNode.containerInfo),Wl(t),null;case 10:return So(t.type._context),Wl(t),null;case 19:if(Ca(si),null===(i=t.memoizedState))return Wl(t),null;if(r=0!=(128&t.flags),null===(s=i.rendering))if(r)Vl(i,!1);else{if(0!==Ds||null!==e&&0!=(128&e.flags))for(e=t.child;null!==e;){if(null!==(s=ci(e))){for(t.flags|=128,Vl(i,!1),null!==(r=s.updateQueue)&&(t.updateQueue=r,t.flags|=4),t.subtreeFlags=0,r=n,n=t.child;null!==n;)e=r,(i=n).flags&=14680066,null===(s=i.alternate)?(i.childLanes=0,i.lanes=e,i.child=null,i.subtreeFlags=0,i.memoizedProps=null,i.memoizedState=null,i.updateQueue=null,i.dependencies=null,i.stateNode=null):(i.childLanes=s.childLanes,i.lanes=s.lanes,i.child=s.child,i.subtreeFlags=0,i.deletions=null,i.memoizedProps=s.memoizedProps,i.memoizedState=s.memoizedState,i.updateQueue=s.updateQueue,i.type=s.type,e=s.dependencies,i.dependencies=null===e?null:{lanes:e.lanes,firstContext:e.firstContext}),n=n.sibling;return _a(si,1&si.current|2),t.child}e=e.sibling}null!==i.tail&&Ze()>qs&&(t.flags|=128,r=!0,Vl(i,!1),t.lanes=4194304)}else{if(!r)if(null!==(e=ci(s))){if(t.flags|=128,r=!0,null!==(n=e.updateQueue)&&(t.updateQueue=n,t.flags|=4),Vl(i,!0),null===i.tail&&"hidden"===i.tailMode&&!s.alternate&&!ao)return Wl(t),null}else 2*Ze()-i.renderingStartTime>qs&&1073741824!==n&&(t.flags|=128,r=!0,Vl(i,!1),t.lanes=4194304);i.isBackwards?(s.sibling=t.child,t.child=s):(null!==(n=i.last)?n.sibling=s:t.child=s,i.last=s)}return null!==i.tail?(t=i.tail,i.rendering=t,i.tail=t.sibling,i.renderingStartTime=Ze(),t.sibling=null,n=si.current,_a(si,r?1&n|2:1&n),t):(Wl(t),null);case 22:case 23:return pc(),r=null!==t.memoizedState,null!==e&&null!==e.memoizedState!==r&&(t.flags|=8192),r&&0!=(1&t.mode)?0!=(1073741824&Ps)&&(Wl(t),6&t.subtreeFlags&&(t.flags|=8192)):Wl(t),null;case 24:case 25:return null}throw Error(o(156,t.tag))}function Kl(e,t){switch(to(t),t.tag){case 1:return Ra(t.type)&&Pa(),65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 3:return oi(),Ca(ja),Ca(Ta),di(),0!=(65536&(e=t.flags))&&0==(128&e)?(t.flags=-65537&e|128,t):null;case 5:return li(t),null;case 13:if(Ca(si),null!==(e=t.memoizedState)&&null!==e.dehydrated){if(null===t.alternate)throw Error(o(340));mo()}return 65536&(e=t.flags)?(t.flags=-65537&e|128,t):null;case 19:return Ca(si),null;case 4:return oi(),null;case 10:return So(t.type._context),null;case 22:case 23:return pc(),null;default:return null}}Rl=function(e,t){for(var n=t.child;null!==n;){if(5===n.tag||6===n.tag)e.appendChild(n.stateNode);else if(4!==n.tag&&null!==n.child){n.child.return=n,n=n.child;continue}if(n===t)break;for(;null===n.sibling;){if(null===n.return||n.return===t)return;n=n.return}n.sibling.return=n.return,n=n.sibling}},Pl=function(){},Ol=function(e,t,n,r){var a=e.memoizedProps;if(a!==r){e=t.stateNode,ri(ei.current);var o,i=null;switch(n){case"input":a=K(e,a),r=K(e,r),i=[];break;case"select":a=I({},a,{value:void 0}),r=I({},r,{value:void 0}),i=[];break;case"textarea":a=re(e,a),r=re(e,r),i=[];break;default:"function"!=typeof a.onClick&&"function"==typeof r.onClick&&(e.onclick=Jr)}for(u in be(n,r),n=null,a)if(!r.hasOwnProperty(u)&&a.hasOwnProperty(u)&&null!=a[u])if("style"===u){var s=a[u];for(o in s)s.hasOwnProperty(o)&&(n||(n={}),n[o]="")}else"dangerouslySetInnerHTML"!==u&&"children"!==u&&"suppressContentEditableWarning"!==u&&"suppressHydrationWarning"!==u&&"autoFocus"!==u&&(l.hasOwnProperty(u)?i||(i=[]):(i=i||[]).push(u,null));for(u in r){var c=r[u];if(s=null!=a?a[u]:void 0,r.hasOwnProperty(u)&&c!==s&&(null!=c||null!=s))if("style"===u)if(s){for(o in s)!s.hasOwnProperty(o)||c&&c.hasOwnProperty(o)||(n||(n={}),n[o]="");for(o in c)c.hasOwnProperty(o)&&s[o]!==c[o]&&(n||(n={}),n[o]=c[o])}else n||(i||(i=[]),i.push(u,n)),n=c;else"dangerouslySetInnerHTML"===u?(c=c?c.__html:void 0,s=s?s.__html:void 0,null!=c&&s!==c&&(i=i||[]).push(u,c)):"children"===u?"string"!=typeof c&&"number"!=typeof c||(i=i||[]).push(u,""+c):"suppressContentEditableWarning"!==u&&"suppressHydrationWarning"!==u&&(l.hasOwnProperty(u)?(null!=c&&"onScroll"===u&&zr("scroll",e),i||s===c||(i=[])):(i=i||[]).push(u,c))}n&&(i=i||[]).push("style",n);var u=i;(t.updateQueue=u)&&(t.flags|=4)}},Dl=function(e,t,n,r){n!==r&&(t.flags|=4)};var Yl=!1,Zl=!1,Xl="function"==typeof WeakSet?WeakSet:Set,Jl=null;function es(e,t){var n=e.ref;if(null!==n)if("function"==typeof n)try{n(null)}catch(r){Cc(e,t,r)}else n.current=null}function ts(e,t,n){try{n()}catch(r){Cc(e,t,r)}}var ns=!1;function rs(e,t,n){var r=t.updateQueue;if(null!==(r=null!==r?r.lastEffect:null)){var a=r=r.next;do{if((a.tag&e)===e){var o=a.destroy;a.destroy=void 0,void 0!==o&&ts(t,n,o)}a=a.next}while(a!==r)}}function as(e,t){if(null!==(t=null!==(t=t.updateQueue)?t.lastEffect:null)){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function os(e){var t=e.ref;if(null!==t){var n=e.stateNode;e.tag,e=n,"function"==typeof t?t(e):t.current=e}}function is(e){var t=e.alternate;null!==t&&(e.alternate=null,is(t)),e.child=null,e.deletions=null,e.sibling=null,5===e.tag&&(null!==(t=e.stateNode)&&(delete t[pa],delete t[fa],delete t[ga],delete t[ha],delete t[ya])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function ls(e){return 5===e.tag||3===e.tag||4===e.tag}function ss(e){e:for(;;){for(;null===e.sibling;){if(null===e.return||ls(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;5!==e.tag&&6!==e.tag&&18!==e.tag;){if(2&e.flags)continue e;if(null===e.child||4===e.tag)continue e;e.child.return=e,e=e.child}if(!(2&e.flags))return e.stateNode}}function cs(e,t,n){var r=e.tag;if(5===r||6===r)e=e.stateNode,t?8===n.nodeType?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(8===n.nodeType?(t=n.parentNode).insertBefore(e,n):(t=n).appendChild(e),null!=(n=n._reactRootContainer)||null!==t.onclick||(t.onclick=Jr));else if(4!==r&&null!==(e=e.child))for(cs(e,t,n),e=e.sibling;null!==e;)cs(e,t,n),e=e.sibling}function us(e,t,n){var r=e.tag;if(5===r||6===r)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(4!==r&&null!==(e=e.child))for(us(e,t,n),e=e.sibling;null!==e;)us(e,t,n),e=e.sibling}var ds=null,ps=!1;function fs(e,t,n){for(n=n.child;null!==n;)ms(e,t,n),n=n.sibling}function ms(e,t,n){if(ot&&"function"==typeof ot.onCommitFiberUnmount)try{ot.onCommitFiberUnmount(at,n)}catch(l){}switch(n.tag){case 5:Zl||es(n,t);case 6:var r=ds,a=ps;ds=null,fs(e,t,n),ps=a,null!==(ds=r)&&(ps?(e=ds,n=n.stateNode,8===e.nodeType?e.parentNode.removeChild(n):e.removeChild(n)):ds.removeChild(n.stateNode));break;case 18:null!==ds&&(ps?(e=ds,n=n.stateNode,8===e.nodeType?sa(e.parentNode,n):1===e.nodeType&&sa(e,n),Ut(e)):sa(ds,n.stateNode));break;case 4:r=ds,a=ps,ds=n.stateNode.containerInfo,ps=!0,fs(e,t,n),ds=r,ps=a;break;case 0:case 11:case 14:case 15:if(!Zl&&(null!==(r=n.updateQueue)&&null!==(r=r.lastEffect))){a=r=r.next;do{var o=a,i=o.destroy;o=o.tag,void 0!==i&&(0!=(2&o)||0!=(4&o))&&ts(n,t,i),a=a.next}while(a!==r)}fs(e,t,n);break;case 1:if(!Zl&&(es(n,t),"function"==typeof(r=n.stateNode).componentWillUnmount))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(l){Cc(n,t,l)}fs(e,t,n);break;case 21:fs(e,t,n);break;case 22:1&n.mode?(Zl=(r=Zl)||null!==n.memoizedState,fs(e,t,n),Zl=r):fs(e,t,n);break;default:fs(e,t,n)}}function gs(e){var t=e.updateQueue;if(null!==t){e.updateQueue=null;var n=e.stateNode;null===n&&(n=e.stateNode=new Xl),t.forEach((function(t){var r=jc.bind(null,e,t);n.has(t)||(n.add(t),t.then(r,r))}))}}function hs(e,t){var n=t.deletions;if(null!==n)for(var r=0;r<n.length;r++){var a=n[r];try{var i=e,l=t,s=l;e:for(;null!==s;){switch(s.tag){case 5:ds=s.stateNode,ps=!1;break e;case 3:case 4:ds=s.stateNode.containerInfo,ps=!0;break e}s=s.return}if(null===ds)throw Error(o(160));ms(i,l,a),ds=null,ps=!1;var c=a.alternate;null!==c&&(c.return=null),a.return=null}catch(u){Cc(a,t,u)}}if(12854&t.subtreeFlags)for(t=t.child;null!==t;)ys(t,e),t=t.sibling}function ys(e,t){var n=e.alternate,r=e.flags;switch(e.tag){case 0:case 11:case 14:case 15:if(hs(t,e),bs(e),4&r){try{rs(3,e,e.return),as(3,e)}catch(h){Cc(e,e.return,h)}try{rs(5,e,e.return)}catch(h){Cc(e,e.return,h)}}break;case 1:hs(t,e),bs(e),512&r&&null!==n&&es(n,n.return);break;case 5:if(hs(t,e),bs(e),512&r&&null!==n&&es(n,n.return),32&e.flags){var a=e.stateNode;try{pe(a,"")}catch(h){Cc(e,e.return,h)}}if(4&r&&null!=(a=e.stateNode)){var i=e.memoizedProps,l=null!==n?n.memoizedProps:i,s=e.type,c=e.updateQueue;if(e.updateQueue=null,null!==c)try{"input"===s&&"radio"===i.type&&null!=i.name&&Z(a,i),ve(s,l);var u=ve(s,i);for(l=0;l<c.length;l+=2){var d=c[l],p=c[l+1];"style"===d?he(a,p):"dangerouslySetInnerHTML"===d?de(a,p):"children"===d?pe(a,p):v(a,d,p,u)}switch(s){case"input":X(a,i);break;case"textarea":oe(a,i);break;case"select":var f=a._wrapperState.wasMultiple;a._wrapperState.wasMultiple=!!i.multiple;var m=i.value;null!=m?ne(a,!!i.multiple,m,!1):f!==!!i.multiple&&(null!=i.defaultValue?ne(a,!!i.multiple,i.defaultValue,!0):ne(a,!!i.multiple,i.multiple?[]:"",!1))}a[fa]=i}catch(h){Cc(e,e.return,h)}}break;case 6:if(hs(t,e),bs(e),4&r){if(null===e.stateNode)throw Error(o(162));a=e.stateNode,i=e.memoizedProps;try{a.nodeValue=i}catch(h){Cc(e,e.return,h)}}break;case 3:if(hs(t,e),bs(e),4&r&&null!==n&&n.memoizedState.isDehydrated)try{Ut(t.containerInfo)}catch(h){Cc(e,e.return,h)}break;case 4:default:hs(t,e),bs(e);break;case 13:hs(t,e),bs(e),8192&(a=e.child).flags&&(i=null!==a.memoizedState,a.stateNode.isHidden=i,!i||null!==a.alternate&&null!==a.alternate.memoizedState||(Us=Ze())),4&r&&gs(e);break;case 22:if(d=null!==n&&null!==n.memoizedState,1&e.mode?(Zl=(u=Zl)||d,hs(t,e),Zl=u):hs(t,e),bs(e),8192&r){if(u=null!==e.memoizedState,(e.stateNode.isHidden=u)&&!d&&0!=(1&e.mode))for(Jl=e,d=e.child;null!==d;){for(p=Jl=d;null!==Jl;){switch(m=(f=Jl).child,f.tag){case 0:case 11:case 14:case 15:rs(4,f,f.return);break;case 1:es(f,f.return);var g=f.stateNode;if("function"==typeof g.componentWillUnmount){r=f,n=f.return;try{t=r,g.props=t.memoizedProps,g.state=t.memoizedState,g.componentWillUnmount()}catch(h){Cc(r,n,h)}}break;case 5:es(f,f.return);break;case 22:if(null!==f.memoizedState){xs(p);continue}}null!==m?(m.return=f,Jl=m):xs(p)}d=d.sibling}e:for(d=null,p=e;;){if(5===p.tag){if(null===d){d=p;try{a=p.stateNode,u?"function"==typeof(i=a.style).setProperty?i.setProperty("display","none","important"):i.display="none":(s=p.stateNode,l=null!=(c=p.memoizedProps.style)&&c.hasOwnProperty("display")?c.display:null,s.style.display=ge("display",l))}catch(h){Cc(e,e.return,h)}}}else if(6===p.tag){if(null===d)try{p.stateNode.nodeValue=u?"":p.memoizedProps}catch(h){Cc(e,e.return,h)}}else if((22!==p.tag&&23!==p.tag||null===p.memoizedState||p===e)&&null!==p.child){p.child.return=p,p=p.child;continue}if(p===e)break e;for(;null===p.sibling;){if(null===p.return||p.return===e)break e;d===p&&(d=null),p=p.return}d===p&&(d=null),p.sibling.return=p.return,p=p.sibling}}break;case 19:hs(t,e),bs(e),4&r&&gs(e);case 21:}}function bs(e){var t=e.flags;if(2&t){try{e:{for(var n=e.return;null!==n;){if(ls(n)){var r=n;break e}n=n.return}throw Error(o(160))}switch(r.tag){case 5:var a=r.stateNode;32&r.flags&&(pe(a,""),r.flags&=-33),us(e,ss(e),a);break;case 3:case 4:var i=r.stateNode.containerInfo;cs(e,ss(e),i);break;default:throw Error(o(161))}}catch(l){Cc(e,e.return,l)}e.flags&=-3}4096&t&&(e.flags&=-4097)}function vs(e,t,n){Jl=e,ws(e,t,n)}function ws(e,t,n){for(var r=0!=(1&e.mode);null!==Jl;){var a=Jl,o=a.child;if(22===a.tag&&r){var i=null!==a.memoizedState||Yl;if(!i){var l=a.alternate,s=null!==l&&null!==l.memoizedState||Zl;l=Yl;var c=Zl;if(Yl=i,(Zl=s)&&!c)for(Jl=a;null!==Jl;)s=(i=Jl).child,22===i.tag&&null!==i.memoizedState?Ss(a):null!==s?(s.return=i,Jl=s):Ss(a);for(;null!==o;)Jl=o,ws(o,t,n),o=o.sibling;Jl=a,Yl=l,Zl=c}ks(e)}else 0!=(8772&a.subtreeFlags)&&null!==o?(o.return=a,Jl=o):ks(e)}}function ks(e){for(;null!==Jl;){var t=Jl;if(0!=(8772&t.flags)){var n=t.alternate;try{if(0!=(8772&t.flags))switch(t.tag){case 0:case 11:case 15:Zl||as(5,t);break;case 1:var r=t.stateNode;if(4&t.flags&&!Zl)if(null===n)r.componentDidMount();else{var a=t.elementType===t.type?n.memoizedProps:yo(t.type,n.memoizedProps);r.componentDidUpdate(a,n.memoizedState,r.__reactInternalSnapshotBeforeUpdate)}var i=t.updateQueue;null!==i&&zo(t,i,r);break;case 3:var l=t.updateQueue;if(null!==l){if(n=null,null!==t.child)switch(t.child.tag){case 5:case 1:n=t.child.stateNode}zo(t,l,n)}break;case 5:var s=t.stateNode;if(null===n&&4&t.flags){n=s;var c=t.memoizedProps;switch(t.type){case"button":case"input":case"select":case"textarea":c.autoFocus&&n.focus();break;case"img":c.src&&(n.src=c.src)}}break;case 6:case 4:case 12:case 19:case 17:case 21:case 22:case 23:case 25:break;case 13:if(null===t.memoizedState){var u=t.alternate;if(null!==u){var d=u.memoizedState;if(null!==d){var p=d.dehydrated;null!==p&&Ut(p)}}}break;default:throw Error(o(163))}Zl||512&t.flags&&os(t)}catch(f){Cc(t,t.return,f)}}if(t===e){Jl=null;break}if(null!==(n=t.sibling)){n.return=t.return,Jl=n;break}Jl=t.return}}function xs(e){for(;null!==Jl;){var t=Jl;if(t===e){Jl=null;break}var n=t.sibling;if(null!==n){n.return=t.return,Jl=n;break}Jl=t.return}}function Ss(e){for(;null!==Jl;){var t=Jl;try{switch(t.tag){case 0:case 11:case 15:var n=t.return;try{as(4,t)}catch(s){Cc(t,n,s)}break;case 1:var r=t.stateNode;if("function"==typeof r.componentDidMount){var a=t.return;try{r.componentDidMount()}catch(s){Cc(t,a,s)}}var o=t.return;try{os(t)}catch(s){Cc(t,o,s)}break;case 5:var i=t.return;try{os(t)}catch(s){Cc(t,i,s)}}}catch(s){Cc(t,t.return,s)}if(t===e){Jl=null;break}var l=t.sibling;if(null!==l){l.return=t.return,Jl=l;break}Jl=t.return}}var Es,Cs=Math.ceil,_s=w.ReactCurrentDispatcher,As=w.ReactCurrentOwner,Ts=w.ReactCurrentBatchConfig,js=0,Ls=null,Ns=null,Rs=0,Ps=0,Os=Ea(0),Ds=0,Ms=null,Is=0,Fs=0,zs=0,Bs=null,$s=null,Us=0,qs=1/0,Hs=null,Gs=!1,Vs=null,Ws=null,Qs=!1,Ks=null,Ys=0,Zs=0,Xs=null,Js=-1,ec=0;function tc(){return 0!=(6&js)?Ze():-1!==Js?Js:Js=Ze()}function nc(e){return 0==(1&e.mode)?1:0!=(2&js)&&0!==Rs?Rs&-Rs:null!==ho.transition?(0===ec&&(ec=gt()),ec):0!==(e=vt)?e:e=void 0===(e=window.event)?16:Yt(e.type)}function rc(e,t,n,r){if(50<Zs)throw Zs=0,Xs=null,Error(o(185));yt(e,n,r),0!=(2&js)&&e===Ls||(e===Ls&&(0==(2&js)&&(Fs|=n),4===Ds&&sc(e,Rs)),ac(e,r),1===n&&0===js&&0==(1&t.mode)&&(qs=Ze()+500,za&&Ua()))}function ac(e,t){var n=e.callbackNode;!function(e,t){for(var n=e.suspendedLanes,r=e.pingedLanes,a=e.expirationTimes,o=e.pendingLanes;0<o;){var i=31-it(o),l=1<<i,s=a[i];-1===s?0!=(l&n)&&0==(l&r)||(a[i]=ft(l,t)):s<=t&&(e.expiredLanes|=l),o&=~l}}(e,t);var r=pt(e,e===Ls?Rs:0);if(0===r)null!==n&&Qe(n),e.callbackNode=null,e.callbackPriority=0;else if(t=r&-r,e.callbackPriority!==t){if(null!=n&&Qe(n),1===t)0===e.tag?function(e){za=!0,$a(e)}(cc.bind(null,e)):$a(cc.bind(null,e)),ia((function(){0==(6&js)&&Ua()})),n=null;else{switch(wt(r)){case 1:n=Je;break;case 4:n=et;break;case 16:default:n=tt;break;case 536870912:n=rt}n=Lc(n,oc.bind(null,e))}e.callbackPriority=t,e.callbackNode=n}}function oc(e,t){if(Js=-1,ec=0,0!=(6&js))throw Error(o(327));var n=e.callbackNode;if(Sc()&&e.callbackNode!==n)return null;var r=pt(e,e===Ls?Rs:0);if(0===r)return null;if(0!=(30&r)||0!=(r&e.expiredLanes)||t)t=yc(e,r);else{t=r;var a=js;js|=2;var i=gc();for(Ls===e&&Rs===t||(Hs=null,qs=Ze()+500,fc(e,t));;)try{vc();break}catch(s){mc(e,s)}xo(),_s.current=i,js=a,null!==Ns?t=0:(Ls=null,Rs=0,t=Ds)}if(0!==t){if(2===t&&(0!==(a=mt(e))&&(r=a,t=ic(e,a))),1===t)throw n=Ms,fc(e,0),sc(e,r),ac(e,Ze()),n;if(6===t)sc(e,r);else{if(a=e.current.alternate,0==(30&r)&&!function(e){for(var t=e;;){if(16384&t.flags){var n=t.updateQueue;if(null!==n&&null!==(n=n.stores))for(var r=0;r<n.length;r++){var a=n[r],o=a.getSnapshot;a=a.value;try{if(!lr(o(),a))return!1}catch(l){return!1}}}if(n=t.child,16384&t.subtreeFlags&&null!==n)n.return=t,t=n;else{if(t===e)break;for(;null===t.sibling;){if(null===t.return||t.return===e)return!0;t=t.return}t.sibling.return=t.return,t=t.sibling}}return!0}(a)&&(2===(t=yc(e,r))&&(0!==(i=mt(e))&&(r=i,t=ic(e,i))),1===t))throw n=Ms,fc(e,0),sc(e,r),ac(e,Ze()),n;switch(e.finishedWork=a,e.finishedLanes=r,t){case 0:case 1:throw Error(o(345));case 2:case 5:xc(e,$s,Hs);break;case 3:if(sc(e,r),(130023424&r)===r&&10<(t=Us+500-Ze())){if(0!==pt(e,0))break;if(((a=e.suspendedLanes)&r)!==r){tc(),e.pingedLanes|=e.suspendedLanes&a;break}e.timeoutHandle=ra(xc.bind(null,e,$s,Hs),t);break}xc(e,$s,Hs);break;case 4:if(sc(e,r),(4194240&r)===r)break;for(t=e.eventTimes,a=-1;0<r;){var l=31-it(r);i=1<<l,(l=t[l])>a&&(a=l),r&=~i}if(r=a,10<(r=(120>(r=Ze()-r)?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Cs(r/1960))-r)){e.timeoutHandle=ra(xc.bind(null,e,$s,Hs),r);break}xc(e,$s,Hs);break;default:throw Error(o(329))}}}return ac(e,Ze()),e.callbackNode===n?oc.bind(null,e):null}function ic(e,t){var n=Bs;return e.current.memoizedState.isDehydrated&&(fc(e,t).flags|=256),2!==(e=yc(e,t))&&(t=$s,$s=n,null!==t&&lc(t)),e}function lc(e){null===$s?$s=e:$s.push.apply($s,e)}function sc(e,t){for(t&=~zs,t&=~Fs,e.suspendedLanes|=t,e.pingedLanes&=~t,e=e.expirationTimes;0<t;){var n=31-it(t),r=1<<n;e[n]=-1,t&=~r}}function cc(e){if(0!=(6&js))throw Error(o(327));Sc();var t=pt(e,0);if(0==(1&t))return ac(e,Ze()),null;var n=yc(e,t);if(0!==e.tag&&2===n){var r=mt(e);0!==r&&(t=r,n=ic(e,r))}if(1===n)throw n=Ms,fc(e,0),sc(e,t),ac(e,Ze()),n;if(6===n)throw Error(o(345));return e.finishedWork=e.current.alternate,e.finishedLanes=t,xc(e,$s,Hs),ac(e,Ze()),null}function uc(e,t){var n=js;js|=1;try{return e(t)}finally{0===(js=n)&&(qs=Ze()+500,za&&Ua())}}function dc(e){null!==Ks&&0===Ks.tag&&0==(6&js)&&Sc();var t=js;js|=1;var n=Ts.transition,r=vt;try{if(Ts.transition=null,vt=1,e)return e()}finally{vt=r,Ts.transition=n,0==(6&(js=t))&&Ua()}}function pc(){Ps=Os.current,Ca(Os)}function fc(e,t){e.finishedWork=null,e.finishedLanes=0;var n=e.timeoutHandle;if(-1!==n&&(e.timeoutHandle=-1,aa(n)),null!==Ns)for(n=Ns.return;null!==n;){var r=n;switch(to(r),r.tag){case 1:null!=(r=r.type.childContextTypes)&&Pa();break;case 3:oi(),Ca(ja),Ca(Ta),di();break;case 5:li(r);break;case 4:oi();break;case 13:case 19:Ca(si);break;case 10:So(r.type._context);break;case 22:case 23:pc()}n=n.return}if(Ls=e,Ns=e=Oc(e.current,null),Rs=Ps=t,Ds=0,Ms=null,zs=Fs=Is=0,$s=Bs=null,null!==Ao){for(t=0;t<Ao.length;t++)if(null!==(r=(n=Ao[t]).interleaved)){n.interleaved=null;var a=r.next,o=n.pending;if(null!==o){var i=o.next;o.next=a,r.next=i}n.pending=r}Ao=null}return e}function mc(e,t){for(;;){var n=Ns;try{if(xo(),pi.current=il,bi){for(var r=gi.memoizedState;null!==r;){var a=r.queue;null!==a&&(a.pending=null),r=r.next}bi=!1}if(mi=0,yi=hi=gi=null,vi=!1,wi=0,As.current=null,null===n||null===n.return){Ds=1,Ms=t,Ns=null;break}e:{var i=e,l=n.return,s=n,c=t;if(t=Rs,s.flags|=32768,null!==c&&"object"==typeof c&&"function"==typeof c.then){var u=c,d=s,p=d.tag;if(0==(1&d.mode)&&(0===p||11===p||15===p)){var f=d.alternate;f?(d.updateQueue=f.updateQueue,d.memoizedState=f.memoizedState,d.lanes=f.lanes):(d.updateQueue=null,d.memoizedState=null)}var m=yl(l);if(null!==m){m.flags&=-257,bl(m,l,s,0,t),1&m.mode&&hl(i,u,t),c=u;var g=(t=m).updateQueue;if(null===g){var h=new Set;h.add(c),t.updateQueue=h}else g.add(c);break e}if(0==(1&t)){hl(i,u,t),hc();break e}c=Error(o(426))}else if(ao&&1&s.mode){var y=yl(l);if(null!==y){0==(65536&y.flags)&&(y.flags|=256),bl(y,l,s,0,t),go(ul(c,s));break e}}i=c=ul(c,s),4!==Ds&&(Ds=2),null===Bs?Bs=[i]:Bs.push(i),i=l;do{switch(i.tag){case 3:i.flags|=65536,t&=-t,i.lanes|=t,Io(i,ml(0,c,t));break e;case 1:s=c;var b=i.type,v=i.stateNode;if(0==(128&i.flags)&&("function"==typeof b.getDerivedStateFromError||null!==v&&"function"==typeof v.componentDidCatch&&(null===Ws||!Ws.has(v)))){i.flags|=65536,t&=-t,i.lanes|=t,Io(i,gl(i,s,t));break e}}i=i.return}while(null!==i)}kc(n)}catch(w){t=w,Ns===n&&null!==n&&(Ns=n=n.return);continue}break}}function gc(){var e=_s.current;return _s.current=il,null===e?il:e}function hc(){0!==Ds&&3!==Ds&&2!==Ds||(Ds=4),null===Ls||0==(268435455&Is)&&0==(268435455&Fs)||sc(Ls,Rs)}function yc(e,t){var n=js;js|=2;var r=gc();for(Ls===e&&Rs===t||(Hs=null,fc(e,t));;)try{bc();break}catch(a){mc(e,a)}if(xo(),js=n,_s.current=r,null!==Ns)throw Error(o(261));return Ls=null,Rs=0,Ds}function bc(){for(;null!==Ns;)wc(Ns)}function vc(){for(;null!==Ns&&!Ke();)wc(Ns)}function wc(e){var t=Es(e.alternate,e,Ps);e.memoizedProps=e.pendingProps,null===t?kc(e):Ns=t,As.current=null}function kc(e){var t=e;do{var n=t.alternate;if(e=t.return,0==(32768&t.flags)){if(null!==(n=Ql(n,t,Ps)))return void(Ns=n)}else{if(null!==(n=Kl(n,t)))return n.flags&=32767,void(Ns=n);if(null===e)return Ds=6,void(Ns=null);e.flags|=32768,e.subtreeFlags=0,e.deletions=null}if(null!==(t=t.sibling))return void(Ns=t);Ns=t=e}while(null!==t);0===Ds&&(Ds=5)}function xc(e,t,n){var r=vt,a=Ts.transition;try{Ts.transition=null,vt=1,function(e,t,n,r){do{Sc()}while(null!==Ks);if(0!=(6&js))throw Error(o(327));n=e.finishedWork;var a=e.finishedLanes;if(null===n)return null;if(e.finishedWork=null,e.finishedLanes=0,n===e.current)throw Error(o(177));e.callbackNode=null,e.callbackPriority=0;var i=n.lanes|n.childLanes;if(function(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0<n;){var a=31-it(n),o=1<<a;t[a]=0,r[a]=-1,e[a]=-1,n&=~o}}(e,i),e===Ls&&(Ns=Ls=null,Rs=0),0==(2064&n.subtreeFlags)&&0==(2064&n.flags)||Qs||(Qs=!0,Lc(tt,(function(){return Sc(),null}))),i=0!=(15990&n.flags),0!=(15990&n.subtreeFlags)||i){i=Ts.transition,Ts.transition=null;var l=vt;vt=1;var s=js;js|=4,As.current=null,function(e,t){if(ea=Ht,fr(e=pr())){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{var r=(n=(n=e.ownerDocument)&&n.defaultView||window).getSelection&&n.getSelection();if(r&&0!==r.rangeCount){n=r.anchorNode;var a=r.anchorOffset,i=r.focusNode;r=r.focusOffset;try{n.nodeType,i.nodeType}catch(k){n=null;break e}var l=0,s=-1,c=-1,u=0,d=0,p=e,f=null;t:for(;;){for(var m;p!==n||0!==a&&3!==p.nodeType||(s=l+a),p!==i||0!==r&&3!==p.nodeType||(c=l+r),3===p.nodeType&&(l+=p.nodeValue.length),null!==(m=p.firstChild);)f=p,p=m;for(;;){if(p===e)break t;if(f===n&&++u===a&&(s=l),f===i&&++d===r&&(c=l),null!==(m=p.nextSibling))break;f=(p=f).parentNode}p=m}n=-1===s||-1===c?null:{start:s,end:c}}else n=null}n=n||{start:0,end:0}}else n=null;for(ta={focusedElem:e,selectionRange:n},Ht=!1,Jl=t;null!==Jl;)if(e=(t=Jl).child,0!=(1028&t.subtreeFlags)&&null!==e)e.return=t,Jl=e;else for(;null!==Jl;){t=Jl;try{var g=t.alternate;if(0!=(1024&t.flags))switch(t.tag){case 0:case 11:case 15:case 5:case 6:case 4:case 17:break;case 1:if(null!==g){var h=g.memoizedProps,y=g.memoizedState,b=t.stateNode,v=b.getSnapshotBeforeUpdate(t.elementType===t.type?h:yo(t.type,h),y);b.__reactInternalSnapshotBeforeUpdate=v}break;case 3:var w=t.stateNode.containerInfo;1===w.nodeType?w.textContent="":9===w.nodeType&&w.documentElement&&w.removeChild(w.documentElement);break;default:throw Error(o(163))}}catch(k){Cc(t,t.return,k)}if(null!==(e=t.sibling)){e.return=t.return,Jl=e;break}Jl=t.return}g=ns,ns=!1}(e,n),ys(n,e),mr(ta),Ht=!!ea,ta=ea=null,e.current=n,vs(n,e,a),Ye(),js=s,vt=l,Ts.transition=i}else e.current=n;if(Qs&&(Qs=!1,Ks=e,Ys=a),i=e.pendingLanes,0===i&&(Ws=null),function(e){if(ot&&"function"==typeof ot.onCommitFiberRoot)try{ot.onCommitFiberRoot(at,e,void 0,128==(128&e.current.flags))}catch(t){}}(n.stateNode),ac(e,Ze()),null!==t)for(r=e.onRecoverableError,n=0;n<t.length;n++)a=t[n],r(a.value,{componentStack:a.stack,digest:a.digest});if(Gs)throw Gs=!1,e=Vs,Vs=null,e;0!=(1&Ys)&&0!==e.tag&&Sc(),i=e.pendingLanes,0!=(1&i)?e===Xs?Zs++:(Zs=0,Xs=e):Zs=0,Ua()}(e,t,n,r)}finally{Ts.transition=a,vt=r}return null}function Sc(){if(null!==Ks){var e=wt(Ys),t=Ts.transition,n=vt;try{if(Ts.transition=null,vt=16>e?16:e,null===Ks)var r=!1;else{if(e=Ks,Ks=null,Ys=0,0!=(6&js))throw Error(o(331));var a=js;for(js|=4,Jl=e.current;null!==Jl;){var i=Jl,l=i.child;if(0!=(16&Jl.flags)){var s=i.deletions;if(null!==s){for(var c=0;c<s.length;c++){var u=s[c];for(Jl=u;null!==Jl;){var d=Jl;switch(d.tag){case 0:case 11:case 15:rs(8,d,i)}var p=d.child;if(null!==p)p.return=d,Jl=p;else for(;null!==Jl;){var f=(d=Jl).sibling,m=d.return;if(is(d),d===u){Jl=null;break}if(null!==f){f.return=m,Jl=f;break}Jl=m}}}var g=i.alternate;if(null!==g){var h=g.child;if(null!==h){g.child=null;do{var y=h.sibling;h.sibling=null,h=y}while(null!==h)}}Jl=i}}if(0!=(2064&i.subtreeFlags)&&null!==l)l.return=i,Jl=l;else e:for(;null!==Jl;){if(0!=(2048&(i=Jl).flags))switch(i.tag){case 0:case 11:case 15:rs(9,i,i.return)}var b=i.sibling;if(null!==b){b.return=i.return,Jl=b;break e}Jl=i.return}}var v=e.current;for(Jl=v;null!==Jl;){var w=(l=Jl).child;if(0!=(2064&l.subtreeFlags)&&null!==w)w.return=l,Jl=w;else e:for(l=v;null!==Jl;){if(0!=(2048&(s=Jl).flags))try{switch(s.tag){case 0:case 11:case 15:as(9,s)}}catch(x){Cc(s,s.return,x)}if(s===l){Jl=null;break e}var k=s.sibling;if(null!==k){k.return=s.return,Jl=k;break e}Jl=s.return}}if(js=a,Ua(),ot&&"function"==typeof ot.onPostCommitFiberRoot)try{ot.onPostCommitFiberRoot(at,e)}catch(x){}r=!0}return r}finally{vt=n,Ts.transition=t}}return!1}function Ec(e,t,n){e=Do(e,t=ml(0,t=ul(n,t),1),1),t=tc(),null!==e&&(yt(e,1,t),ac(e,t))}function Cc(e,t,n){if(3===e.tag)Ec(e,e,n);else for(;null!==t;){if(3===t.tag){Ec(t,e,n);break}if(1===t.tag){var r=t.stateNode;if("function"==typeof t.type.getDerivedStateFromError||"function"==typeof r.componentDidCatch&&(null===Ws||!Ws.has(r))){t=Do(t,e=gl(t,e=ul(n,e),1),1),e=tc(),null!==t&&(yt(t,1,e),ac(t,e));break}}t=t.return}}function _c(e,t,n){var r=e.pingCache;null!==r&&r.delete(t),t=tc(),e.pingedLanes|=e.suspendedLanes&n,Ls===e&&(Rs&n)===n&&(4===Ds||3===Ds&&(130023424&Rs)===Rs&&500>Ze()-Us?fc(e,0):zs|=n),ac(e,t)}function Ac(e,t){0===t&&(0==(1&e.mode)?t=1:(t=ut,0==(130023424&(ut<<=1))&&(ut=4194304)));var n=tc();null!==(e=Lo(e,t))&&(yt(e,t,n),ac(e,n))}function Tc(e){var t=e.memoizedState,n=0;null!==t&&(n=t.retryLane),Ac(e,n)}function jc(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,a=e.memoizedState;null!==a&&(n=a.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(o(314))}null!==r&&r.delete(t),Ac(e,n)}function Lc(e,t){return We(e,t)}function Nc(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Rc(e,t,n,r){return new Nc(e,t,n,r)}function Pc(e){return!(!(e=e.prototype)||!e.isReactComponent)}function Oc(e,t){var n=e.alternate;return null===n?((n=Rc(e.tag,t,e.key,e.mode)).elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=14680064&e.flags,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=null===t?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Dc(e,t,n,r,a,i){var l=2;if(r=e,"function"==typeof e)Pc(e)&&(l=1);else if("string"==typeof e)l=5;else e:switch(e){case S:return Mc(n.children,a,i,t);case E:l=8,a|=8;break;case C:return(e=Rc(12,n,t,2|a)).elementType=C,e.lanes=i,e;case j:return(e=Rc(13,n,t,a)).elementType=j,e.lanes=i,e;case L:return(e=Rc(19,n,t,a)).elementType=L,e.lanes=i,e;case P:return Ic(n,a,i,t);default:if("object"==typeof e&&null!==e)switch(e.$$typeof){case _:l=10;break e;case A:l=9;break e;case T:l=11;break e;case N:l=14;break e;case R:l=16,r=null;break e}throw Error(o(130,null==e?e:typeof e,""))}return(t=Rc(l,n,t,a)).elementType=e,t.type=r,t.lanes=i,t}function Mc(e,t,n,r){return(e=Rc(7,e,r,t)).lanes=n,e}function Ic(e,t,n,r){return(e=Rc(22,e,r,t)).elementType=P,e.lanes=n,e.stateNode={isHidden:!1},e}function Fc(e,t,n){return(e=Rc(6,e,null,t)).lanes=n,e}function zc(e,t,n){return(t=Rc(4,null!==e.children?e.children:[],e.key,t)).lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function Bc(e,t,n,r,a){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=ht(0),this.expirationTimes=ht(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=ht(0),this.identifierPrefix=r,this.onRecoverableError=a,this.mutableSourceEagerHydrationData=null}function $c(e,t,n,r,a,o,i,l,s){return e=new Bc(e,t,n,l,s),1===t?(t=1,!0===o&&(t|=8)):t=0,o=Rc(3,null,null,t),e.current=o,o.stateNode=e,o.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Ro(o),e}function Uc(e){if(!e)return Aa;e:{if(Ue(e=e._reactInternals)!==e||1!==e.tag)throw Error(o(170));var t=e;do{switch(t.tag){case 3:t=t.stateNode.context;break e;case 1:if(Ra(t.type)){t=t.stateNode.__reactInternalMemoizedMergedChildContext;break e}}t=t.return}while(null!==t);throw Error(o(171))}if(1===e.tag){var n=e.type;if(Ra(n))return Da(e,n,t)}return t}function qc(e,t,n,r,a,o,i,l,s){return(e=$c(n,r,!0,e,0,o,0,l,s)).context=Uc(null),n=e.current,(o=Oo(r=tc(),a=nc(n))).callback=null!=t?t:null,Do(n,o,a),e.current.lanes=a,yt(e,a,r),ac(e,r),e}function Hc(e,t,n,r){var a=t.current,o=tc(),i=nc(a);return n=Uc(n),null===t.context?t.context=n:t.pendingContext=n,(t=Oo(o,i)).payload={element:e},null!==(r=void 0===r?null:r)&&(t.callback=r),null!==(e=Do(a,t,i))&&(rc(e,a,i,o),Mo(e,a,i)),i}function Gc(e){return(e=e.current).child?(e.child.tag,e.child.stateNode):null}function Vc(e,t){if(null!==(e=e.memoizedState)&&null!==e.dehydrated){var n=e.retryLane;e.retryLane=0!==n&&n<t?n:t}}function Wc(e,t){Vc(e,t),(e=e.alternate)&&Vc(e,t)}Es=function(e,t,n){if(null!==e)if(e.memoizedProps!==t.pendingProps||ja.current)wl=!0;else{if(0==(e.lanes&n)&&0==(128&t.flags))return wl=!1,function(e,t,n){switch(t.tag){case 3:Ll(t),mo();break;case 5:ii(t);break;case 1:Ra(t.type)&&Ma(t);break;case 4:ai(t,t.stateNode.containerInfo);break;case 10:var r=t.type._context,a=t.memoizedProps.value;_a(bo,r._currentValue),r._currentValue=a;break;case 13:if(null!==(r=t.memoizedState))return null!==r.dehydrated?(_a(si,1&si.current),t.flags|=128,null):0!=(n&t.child.childLanes)?Fl(e,t,n):(_a(si,1&si.current),null!==(e=Gl(e,t,n))?e.sibling:null);_a(si,1&si.current);break;case 19:if(r=0!=(n&t.childLanes),0!=(128&e.flags)){if(r)return ql(e,t,n);t.flags|=128}if(null!==(a=t.memoizedState)&&(a.rendering=null,a.tail=null,a.lastEffect=null),_a(si,si.current),r)break;return null;case 22:case 23:return t.lanes=0,Cl(e,t,n)}return Gl(e,t,n)}(e,t,n);wl=0!=(131072&e.flags)}else wl=!1,ao&&0!=(1048576&t.flags)&&Ja(t,Va,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;Hl(e,t),e=t.pendingProps;var a=Na(t,Ta.current);Co(t,n),a=Ei(null,t,r,e,a,n);var i=Ci();return t.flags|=1,"object"==typeof a&&null!==a&&"function"==typeof a.render&&void 0===a.$$typeof?(t.tag=1,t.memoizedState=null,t.updateQueue=null,Ra(r)?(i=!0,Ma(t)):i=!1,t.memoizedState=null!==a.state&&void 0!==a.state?a.state:null,Ro(t),a.updater=Uo,t.stateNode=a,a._reactInternals=t,Vo(t,r,e,n),t=jl(null,t,r,!0,i,n)):(t.tag=0,ao&&i&&eo(t),kl(null,t,a,n),t=t.child),t;case 16:r=t.elementType;e:{switch(Hl(e,t),e=t.pendingProps,r=(a=r._init)(r._payload),t.type=r,a=t.tag=function(e){if("function"==typeof e)return Pc(e)?1:0;if(null!=e){if((e=e.$$typeof)===T)return 11;if(e===N)return 14}return 2}(r),e=yo(r,e),a){case 0:t=Al(null,t,r,e,n);break e;case 1:t=Tl(null,t,r,e,n);break e;case 11:t=xl(null,t,r,e,n);break e;case 14:t=Sl(null,t,r,yo(r.type,e),n);break e}throw Error(o(306,r,""))}return t;case 0:return r=t.type,a=t.pendingProps,Al(e,t,r,a=t.elementType===r?a:yo(r,a),n);case 1:return r=t.type,a=t.pendingProps,Tl(e,t,r,a=t.elementType===r?a:yo(r,a),n);case 3:e:{if(Ll(t),null===e)throw Error(o(387));r=t.pendingProps,a=(i=t.memoizedState).element,Po(e,t),Fo(t,r,null,n);var l=t.memoizedState;if(r=l.element,i.isDehydrated){if(i={element:r,isDehydrated:!1,cache:l.cache,pendingSuspenseBoundaries:l.pendingSuspenseBoundaries,transitions:l.transitions},t.updateQueue.baseState=i,t.memoizedState=i,256&t.flags){t=Nl(e,t,r,n,a=ul(Error(o(423)),t));break e}if(r!==a){t=Nl(e,t,r,n,a=ul(Error(o(424)),t));break e}for(ro=ca(t.stateNode.containerInfo.firstChild),no=t,ao=!0,oo=null,n=Xo(t,null,r,n),t.child=n;n;)n.flags=-3&n.flags|4096,n=n.sibling}else{if(mo(),r===a){t=Gl(e,t,n);break e}kl(e,t,r,n)}t=t.child}return t;case 5:return ii(t),null===e&&co(t),r=t.type,a=t.pendingProps,i=null!==e?e.memoizedProps:null,l=a.children,na(r,a)?l=null:null!==i&&na(r,i)&&(t.flags|=32),_l(e,t),kl(e,t,l,n),t.child;case 6:return null===e&&co(t),null;case 13:return Fl(e,t,n);case 4:return ai(t,t.stateNode.containerInfo),r=t.pendingProps,null===e?t.child=Zo(t,null,r,n):kl(e,t,r,n),t.child;case 11:return r=t.type,a=t.pendingProps,xl(e,t,r,a=t.elementType===r?a:yo(r,a),n);case 7:return kl(e,t,t.pendingProps,n),t.child;case 8:case 12:return kl(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,a=t.pendingProps,i=t.memoizedProps,l=a.value,_a(bo,r._currentValue),r._currentValue=l,null!==i)if(lr(i.value,l)){if(i.children===a.children&&!ja.current){t=Gl(e,t,n);break e}}else for(null!==(i=t.child)&&(i.return=t);null!==i;){var s=i.dependencies;if(null!==s){l=i.child;for(var c=s.firstContext;null!==c;){if(c.context===r){if(1===i.tag){(c=Oo(-1,n&-n)).tag=2;var u=i.updateQueue;if(null!==u){var d=(u=u.shared).pending;null===d?c.next=c:(c.next=d.next,d.next=c),u.pending=c}}i.lanes|=n,null!==(c=i.alternate)&&(c.lanes|=n),Eo(i.return,n,t),s.lanes|=n;break}c=c.next}}else if(10===i.tag)l=i.type===t.type?null:i.child;else if(18===i.tag){if(null===(l=i.return))throw Error(o(341));l.lanes|=n,null!==(s=l.alternate)&&(s.lanes|=n),Eo(l,n,t),l=i.sibling}else l=i.child;if(null!==l)l.return=i;else for(l=i;null!==l;){if(l===t){l=null;break}if(null!==(i=l.sibling)){i.return=l.return,l=i;break}l=l.return}i=l}kl(e,t,a.children,n),t=t.child}return t;case 9:return a=t.type,r=t.pendingProps.children,Co(t,n),r=r(a=_o(a)),t.flags|=1,kl(e,t,r,n),t.child;case 14:return a=yo(r=t.type,t.pendingProps),Sl(e,t,r,a=yo(r.type,a),n);case 15:return El(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,a=t.pendingProps,a=t.elementType===r?a:yo(r,a),Hl(e,t),t.tag=1,Ra(r)?(e=!0,Ma(t)):e=!1,Co(t,n),Ho(t,r,a),Vo(t,r,a,n),jl(null,t,r,!0,e,n);case 19:return ql(e,t,n);case 22:return Cl(e,t,n)}throw Error(o(156,t.tag))};var Qc="function"==typeof reportError?reportError:function(e){console.error(e)};function Kc(e){this._internalRoot=e}function Yc(e){this._internalRoot=e}function Zc(e){return!(!e||1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeType)}function Xc(e){return!(!e||1!==e.nodeType&&9!==e.nodeType&&11!==e.nodeType&&(8!==e.nodeType||" react-mount-point-unstable "!==e.nodeValue))}function Jc(){}function eu(e,t,n,r,a){var o=n._reactRootContainer;if(o){var i=o;if("function"==typeof a){var l=a;a=function(){var e=Gc(i);l.call(e)}}Hc(t,i,e,a)}else i=function(e,t,n,r,a){if(a){if("function"==typeof r){var o=r;r=function(){var e=Gc(i);o.call(e)}}var i=qc(t,r,e,0,null,!1,0,"",Jc);return e._reactRootContainer=i,e[ma]=i.current,Ur(8===e.nodeType?e.parentNode:e),dc(),i}for(;a=e.lastChild;)e.removeChild(a);if("function"==typeof r){var l=r;r=function(){var e=Gc(s);l.call(e)}}var s=$c(e,0,!1,null,0,!1,0,"",Jc);return e._reactRootContainer=s,e[ma]=s.current,Ur(8===e.nodeType?e.parentNode:e),dc((function(){Hc(t,s,n,r)})),s}(n,t,e,a,r);return Gc(i)}Yc.prototype.render=Kc.prototype.render=function(e){var t=this._internalRoot;if(null===t)throw Error(o(409));Hc(e,t,null,null)},Yc.prototype.unmount=Kc.prototype.unmount=function(){var e=this._internalRoot;if(null!==e){this._internalRoot=null;var t=e.containerInfo;dc((function(){Hc(null,e,null,null)})),t[ma]=null}},Yc.prototype.unstable_scheduleHydration=function(e){if(e){var t=Et();e={blockedOn:null,target:e,priority:t};for(var n=0;n<Pt.length&&0!==t&&t<Pt[n].priority;n++);Pt.splice(n,0,e),0===n&&It(e)}},kt=function(e){switch(e.tag){case 3:var t=e.stateNode;if(t.current.memoizedState.isDehydrated){var n=dt(t.pendingLanes);0!==n&&(bt(t,1|n),ac(t,Ze()),0==(6&js)&&(qs=Ze()+500,Ua()))}break;case 13:dc((function(){var t=Lo(e,1);if(null!==t){var n=tc();rc(t,e,1,n)}})),Wc(e,1)}},xt=function(e){if(13===e.tag){var t=Lo(e,134217728);if(null!==t)rc(t,e,134217728,tc());Wc(e,134217728)}},St=function(e){if(13===e.tag){var t=nc(e),n=Lo(e,t);if(null!==n)rc(n,e,t,tc());Wc(e,t)}},Et=function(){return vt},Ct=function(e,t){var n=vt;try{return vt=e,t()}finally{vt=n}},xe=function(e,t,n){switch(t){case"input":if(X(e,n),t=n.name,"radio"===n.type&&null!=t){for(n=e;n.parentNode;)n=n.parentNode;for(n=n.querySelectorAll("input[name="+JSON.stringify(""+t)+'][type="radio"]'),t=0;t<n.length;t++){var r=n[t];if(r!==e&&r.form===e.form){var a=ka(r);if(!a)throw Error(o(90));W(r),X(r,a)}}}break;case"textarea":oe(e,n);break;case"select":null!=(t=n.value)&&ne(e,!!n.multiple,t,!1)}},Te=uc,je=dc;var tu={usingClientEntryPoint:!1,Events:[va,wa,ka,_e,Ae,uc]},nu={findFiberByHostInstance:ba,bundleType:0,version:"18.2.0",rendererPackageName:"react-dom"},ru={bundleType:nu.bundleType,version:nu.version,rendererPackageName:nu.rendererPackageName,rendererConfig:nu.rendererConfig,overrideHookState:null,overrideHookStateDeletePath:null,overrideHookStateRenamePath:null,overrideProps:null,overridePropsDeletePath:null,overridePropsRenamePath:null,setErrorHandler:null,setSuspenseHandler:null,scheduleUpdate:null,currentDispatcherRef:w.ReactCurrentDispatcher,findHostInstanceByFiber:function(e){return null===(e=Ge(e))?null:e.stateNode},findFiberByHostInstance:nu.findFiberByHostInstance||function(){return null},findHostInstancesForRefresh:null,scheduleRefresh:null,scheduleRoot:null,setRefreshHandler:null,getCurrentFiber:null,reconcilerVersion:"18.2.0-next-9e3b772b8-20220608"};if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__){var au=__REACT_DEVTOOLS_GLOBAL_HOOK__;if(!au.isDisabled&&au.supportsFiber)try{at=au.inject(ru),ot=au}catch(ue){}}t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=tu,t.createPortal=function(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null;if(!Zc(t))throw Error(o(200));return function(e,t,n){var r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;return{$$typeof:x,key:null==r?null:""+r,children:e,containerInfo:t,implementation:n}}(e,t,null,n)},t.createRoot=function(e,t){if(!Zc(e))throw Error(o(299));var n=!1,r="",a=Qc;return null!=t&&(!0===t.unstable_strictMode&&(n=!0),void 0!==t.identifierPrefix&&(r=t.identifierPrefix),void 0!==t.onRecoverableError&&(a=t.onRecoverableError)),t=$c(e,1,!1,null,0,n,0,r,a),e[ma]=t.current,Ur(8===e.nodeType?e.parentNode:e),new Kc(t)},t.findDOMNode=function(e){if(null==e)return null;if(1===e.nodeType)return e;var t=e._reactInternals;if(void 0===t){if("function"==typeof e.render)throw Error(o(188));throw e=Object.keys(e).join(","),Error(o(268,e))}return e=null===(e=Ge(t))?null:e.stateNode},t.flushSync=function(e){return dc(e)},t.hydrate=function(e,t,n){if(!Xc(t))throw Error(o(200));return eu(null,e,t,!0,n)},t.hydrateRoot=function(e,t,n){if(!Zc(e))throw Error(o(405));var r=null!=n&&n.hydratedSources||null,a=!1,i="",l=Qc;if(null!=n&&(!0===n.unstable_strictMode&&(a=!0),void 0!==n.identifierPrefix&&(i=n.identifierPrefix),void 0!==n.onRecoverableError&&(l=n.onRecoverableError)),t=qc(t,null,e,1,null!=n?n:null,a,0,i,l),e[ma]=t.current,Ur(e),r)for(e=0;e<r.length;e++)a=(a=(n=r[e])._getVersion)(n._source),null==t.mutableSourceEagerHydrationData?t.mutableSourceEagerHydrationData=[n,a]:t.mutableSourceEagerHydrationData.push(n,a);return new Yc(t)},t.render=function(e,t,n){if(!Xc(t))throw Error(o(200));return eu(null,e,t,!1,n)},t.unmountComponentAtNode=function(e){if(!Xc(e))throw Error(o(40));return!!e._reactRootContainer&&(dc((function(){eu(null,null,e,!1,(function(){e._reactRootContainer=null,e[ma]=null}))})),!0)},t.unstable_batchedUpdates=uc,t.unstable_renderSubtreeIntoContainer=function(e,t,n,r){if(!Xc(n))throw Error(o(200));if(null==e||void 0===e._reactInternals)throw Error(o(38));return eu(e,t,n,!1,r)},t.version="18.2.0-next-9e3b772b8-20220608"},5338:(e,t,n)=>{"use strict";var r=n(961);t.createRoot=r.createRoot,t.hydrateRoot=r.hydrateRoot},961:(e,t,n)=>{"use strict";!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}(),e.exports=n(2551)},115:e=>{var t="undefined"!=typeof Element,n="function"==typeof Map,r="function"==typeof Set,a="function"==typeof ArrayBuffer&&!!ArrayBuffer.isView;function o(e,i){if(e===i)return!0;if(e&&i&&"object"==typeof e&&"object"==typeof i){if(e.constructor!==i.constructor)return!1;var l,s,c,u;if(Array.isArray(e)){if((l=e.length)!=i.length)return!1;for(s=l;0!=s--;)if(!o(e[s],i[s]))return!1;return!0}if(n&&e instanceof Map&&i instanceof Map){if(e.size!==i.size)return!1;for(u=e.entries();!(s=u.next()).done;)if(!i.has(s.value[0]))return!1;for(u=e.entries();!(s=u.next()).done;)if(!o(s.value[1],i.get(s.value[0])))return!1;return!0}if(r&&e instanceof Set&&i instanceof Set){if(e.size!==i.size)return!1;for(u=e.entries();!(s=u.next()).done;)if(!i.has(s.value[0]))return!1;return!0}if(a&&ArrayBuffer.isView(e)&&ArrayBuffer.isView(i)){if((l=e.length)!=i.length)return!1;for(s=l;0!=s--;)if(e[s]!==i[s])return!1;return!0}if(e.constructor===RegExp)return e.source===i.source&&e.flags===i.flags;if(e.valueOf!==Object.prototype.valueOf&&"function"==typeof e.valueOf&&"function"==typeof i.valueOf)return e.valueOf()===i.valueOf();if(e.toString!==Object.prototype.toString&&"function"==typeof e.toString&&"function"==typeof i.toString)return e.toString()===i.toString();if((l=(c=Object.keys(e)).length)!==Object.keys(i).length)return!1;for(s=l;0!=s--;)if(!Object.prototype.hasOwnProperty.call(i,c[s]))return!1;if(t&&e instanceof Element)return!1;for(s=l;0!=s--;)if(("_owner"!==c[s]&&"__v"!==c[s]&&"__o"!==c[s]||!e.$$typeof)&&!o(e[c[s]],i[c[s]]))return!1;return!0}return e!=e&&i!=i}e.exports=function(e,t){try{return o(e,t)}catch(n){if((n.message||"").match(/stack|recursion/i))return console.warn("react-fast-compare cannot handle circular refs"),!1;throw n}}},545:(e,t,n)=>{"use strict";n.d(t,{mg:()=>J,vd:()=>G});var r=n(6540),a=n(5556),o=n.n(a),i=n(115),l=n.n(i),s=n(311),c=n.n(s),u=n(2833),d=n.n(u);function p(){return p=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},p.apply(this,arguments)}function f(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,m(e,t)}function m(e,t){return m=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},m(e,t)}function g(e,t){if(null==e)return{};var n,r,a={},o=Object.keys(e);for(r=0;r<o.length;r++)t.indexOf(n=o[r])>=0||(a[n]=e[n]);return a}var h={BASE:"base",BODY:"body",HEAD:"head",HTML:"html",LINK:"link",META:"meta",NOSCRIPT:"noscript",SCRIPT:"script",STYLE:"style",TITLE:"title",FRAGMENT:"Symbol(react.fragment)"},y={rel:["amphtml","canonical","alternate"]},b={type:["application/ld+json"]},v={charset:"",name:["robots","description"],property:["og:type","og:title","og:url","og:image","og:image:alt","og:description","twitter:url","twitter:title","twitter:description","twitter:image","twitter:image:alt","twitter:card","twitter:site"]},w=Object.keys(h).map((function(e){return h[e]})),k={accesskey:"accessKey",charset:"charSet",class:"className",contenteditable:"contentEditable",contextmenu:"contextMenu","http-equiv":"httpEquiv",itemprop:"itemProp",tabindex:"tabIndex"},x=Object.keys(k).reduce((function(e,t){return e[k[t]]=t,e}),{}),S=function(e,t){for(var n=e.length-1;n>=0;n-=1){var r=e[n];if(Object.prototype.hasOwnProperty.call(r,t))return r[t]}return null},E=function(e){var t=S(e,h.TITLE),n=S(e,"titleTemplate");if(Array.isArray(t)&&(t=t.join("")),n&&t)return n.replace(/%s/g,(function(){return t}));var r=S(e,"defaultTitle");return t||r||void 0},C=function(e){return S(e,"onChangeClientState")||function(){}},_=function(e,t){return t.filter((function(t){return void 0!==t[e]})).map((function(t){return t[e]})).reduce((function(e,t){return p({},e,t)}),{})},A=function(e,t){return t.filter((function(e){return void 0!==e[h.BASE]})).map((function(e){return e[h.BASE]})).reverse().reduce((function(t,n){if(!t.length)for(var r=Object.keys(n),a=0;a<r.length;a+=1){var o=r[a].toLowerCase();if(-1!==e.indexOf(o)&&n[o])return t.concat(n)}return t}),[])},T=function(e,t,n){var r={};return n.filter((function(t){return!!Array.isArray(t[e])||(void 0!==t[e]&&console&&"function"==typeof console.warn&&console.warn("Helmet: "+e+' should be of type "Array". Instead found type "'+typeof t[e]+'"'),!1)})).map((function(t){return t[e]})).reverse().reduce((function(e,n){var a={};n.filter((function(e){for(var n,o=Object.keys(e),i=0;i<o.length;i+=1){var l=o[i],s=l.toLowerCase();-1===t.indexOf(s)||"rel"===n&&"canonical"===e[n].toLowerCase()||"rel"===s&&"stylesheet"===e[s].toLowerCase()||(n=s),-1===t.indexOf(l)||"innerHTML"!==l&&"cssText"!==l&&"itemprop"!==l||(n=l)}if(!n||!e[n])return!1;var c=e[n].toLowerCase();return r[n]||(r[n]={}),a[n]||(a[n]={}),!r[n][c]&&(a[n][c]=!0,!0)})).reverse().forEach((function(t){return e.push(t)}));for(var o=Object.keys(a),i=0;i<o.length;i+=1){var l=o[i],s=p({},r[l],a[l]);r[l]=s}return e}),[]).reverse()},j=function(e,t){if(Array.isArray(e)&&e.length)for(var n=0;n<e.length;n+=1)if(e[n][t])return!0;return!1},L=function(e){return Array.isArray(e)?e.join(""):e},N=function(e,t){return Array.isArray(e)?e.reduce((function(e,n){return function(e,t){for(var n=Object.keys(e),r=0;r<n.length;r+=1)if(t[n[r]]&&t[n[r]].includes(e[n[r]]))return!0;return!1}(n,t)?e.priority.push(n):e.default.push(n),e}),{priority:[],default:[]}):{default:e}},R=function(e,t){var n;return p({},e,((n={})[t]=void 0,n))},P=[h.NOSCRIPT,h.SCRIPT,h.STYLE],O=function(e,t){return void 0===t&&(t=!0),!1===t?String(e):String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")},D=function(e){return Object.keys(e).reduce((function(t,n){var r=void 0!==e[n]?n+'="'+e[n]+'"':""+n;return t?t+" "+r:r}),"")},M=function(e,t){return void 0===t&&(t={}),Object.keys(e).reduce((function(t,n){return t[k[n]||n]=e[n],t}),t)},I=function(e,t){return t.map((function(t,n){var a,o=((a={key:n})["data-rh"]=!0,a);return Object.keys(t).forEach((function(e){var n=k[e]||e;"innerHTML"===n||"cssText"===n?o.dangerouslySetInnerHTML={__html:t.innerHTML||t.cssText}:o[n]=t[e]})),r.createElement(e,o)}))},F=function(e,t,n){switch(e){case h.TITLE:return{toComponent:function(){return n=t.titleAttributes,(a={key:e=t.title})["data-rh"]=!0,o=M(n,a),[r.createElement(h.TITLE,o,e)];var e,n,a,o},toString:function(){return function(e,t,n,r){var a=D(n),o=L(t);return a?"<"+e+' data-rh="true" '+a+">"+O(o,r)+"</"+e+">":"<"+e+' data-rh="true">'+O(o,r)+"</"+e+">"}(e,t.title,t.titleAttributes,n)}};case"bodyAttributes":case"htmlAttributes":return{toComponent:function(){return M(t)},toString:function(){return D(t)}};default:return{toComponent:function(){return I(e,t)},toString:function(){return function(e,t,n){return t.reduce((function(t,r){var a=Object.keys(r).filter((function(e){return!("innerHTML"===e||"cssText"===e)})).reduce((function(e,t){var a=void 0===r[t]?t:t+'="'+O(r[t],n)+'"';return e?e+" "+a:a}),""),o=r.innerHTML||r.cssText||"",i=-1===P.indexOf(e);return t+"<"+e+' data-rh="true" '+a+(i?"/>":">"+o+"</"+e+">")}),"")}(e,t,n)}}}},z=function(e){var t=e.baseTag,n=e.bodyAttributes,r=e.encode,a=e.htmlAttributes,o=e.noscriptTags,i=e.styleTags,l=e.title,s=void 0===l?"":l,c=e.titleAttributes,u=e.linkTags,d=e.metaTags,p=e.scriptTags,f={toComponent:function(){},toString:function(){return""}};if(e.prioritizeSeoTags){var m=function(e){var t=e.linkTags,n=e.scriptTags,r=e.encode,a=N(e.metaTags,v),o=N(t,y),i=N(n,b);return{priorityMethods:{toComponent:function(){return[].concat(I(h.META,a.priority),I(h.LINK,o.priority),I(h.SCRIPT,i.priority))},toString:function(){return F(h.META,a.priority,r)+" "+F(h.LINK,o.priority,r)+" "+F(h.SCRIPT,i.priority,r)}},metaTags:a.default,linkTags:o.default,scriptTags:i.default}}(e);f=m.priorityMethods,u=m.linkTags,d=m.metaTags,p=m.scriptTags}return{priority:f,base:F(h.BASE,t,r),bodyAttributes:F("bodyAttributes",n,r),htmlAttributes:F("htmlAttributes",a,r),link:F(h.LINK,u,r),meta:F(h.META,d,r),noscript:F(h.NOSCRIPT,o,r),script:F(h.SCRIPT,p,r),style:F(h.STYLE,i,r),title:F(h.TITLE,{title:s,titleAttributes:c},r)}},B=[],$=function(e,t){var n=this;void 0===t&&(t="undefined"!=typeof document),this.instances=[],this.value={setHelmet:function(e){n.context.helmet=e},helmetInstances:{get:function(){return n.canUseDOM?B:n.instances},add:function(e){(n.canUseDOM?B:n.instances).push(e)},remove:function(e){var t=(n.canUseDOM?B:n.instances).indexOf(e);(n.canUseDOM?B:n.instances).splice(t,1)}}},this.context=e,this.canUseDOM=t,t||(e.helmet=z({baseTag:[],bodyAttributes:{},encodeSpecialCharacters:!0,htmlAttributes:{},linkTags:[],metaTags:[],noscriptTags:[],scriptTags:[],styleTags:[],title:"",titleAttributes:{}}))},U=r.createContext({}),q=o().shape({setHelmet:o().func,helmetInstances:o().shape({get:o().func,add:o().func,remove:o().func})}),H="undefined"!=typeof document,G=function(e){function t(n){var r;return(r=e.call(this,n)||this).helmetData=new $(r.props.context,t.canUseDOM),r}return f(t,e),t.prototype.render=function(){return r.createElement(U.Provider,{value:this.helmetData.value},this.props.children)},t}(r.Component);G.canUseDOM=H,G.propTypes={context:o().shape({helmet:o().shape()}),children:o().node.isRequired},G.defaultProps={context:{}},G.displayName="HelmetProvider";var V=function(e,t){var n,r=document.head||document.querySelector(h.HEAD),a=r.querySelectorAll(e+"[data-rh]"),o=[].slice.call(a),i=[];return t&&t.length&&t.forEach((function(t){var r=document.createElement(e);for(var a in t)Object.prototype.hasOwnProperty.call(t,a)&&("innerHTML"===a?r.innerHTML=t.innerHTML:"cssText"===a?r.styleSheet?r.styleSheet.cssText=t.cssText:r.appendChild(document.createTextNode(t.cssText)):r.setAttribute(a,void 0===t[a]?"":t[a]));r.setAttribute("data-rh","true"),o.some((function(e,t){return n=t,r.isEqualNode(e)}))?o.splice(n,1):i.push(r)})),o.forEach((function(e){return e.parentNode.removeChild(e)})),i.forEach((function(e){return r.appendChild(e)})),{oldTags:o,newTags:i}},W=function(e,t){var n=document.getElementsByTagName(e)[0];if(n){for(var r=n.getAttribute("data-rh"),a=r?r.split(","):[],o=[].concat(a),i=Object.keys(t),l=0;l<i.length;l+=1){var s=i[l],c=t[s]||"";n.getAttribute(s)!==c&&n.setAttribute(s,c),-1===a.indexOf(s)&&a.push(s);var u=o.indexOf(s);-1!==u&&o.splice(u,1)}for(var d=o.length-1;d>=0;d-=1)n.removeAttribute(o[d]);a.length===o.length?n.removeAttribute("data-rh"):n.getAttribute("data-rh")!==i.join(",")&&n.setAttribute("data-rh",i.join(","))}},Q=function(e,t){var n=e.baseTag,r=e.htmlAttributes,a=e.linkTags,o=e.metaTags,i=e.noscriptTags,l=e.onChangeClientState,s=e.scriptTags,c=e.styleTags,u=e.title,d=e.titleAttributes;W(h.BODY,e.bodyAttributes),W(h.HTML,r),function(e,t){void 0!==e&&document.title!==e&&(document.title=L(e)),W(h.TITLE,t)}(u,d);var p={baseTag:V(h.BASE,n),linkTags:V(h.LINK,a),metaTags:V(h.META,o),noscriptTags:V(h.NOSCRIPT,i),scriptTags:V(h.SCRIPT,s),styleTags:V(h.STYLE,c)},f={},m={};Object.keys(p).forEach((function(e){var t=p[e],n=t.newTags,r=t.oldTags;n.length&&(f[e]=n),r.length&&(m[e]=p[e].oldTags)})),t&&t(),l(e,f,m)},K=null,Y=function(e){function t(){for(var t,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(t=e.call.apply(e,[this].concat(r))||this).rendered=!1,t}f(t,e);var n=t.prototype;return n.shouldComponentUpdate=function(e){return!d()(e,this.props)},n.componentDidUpdate=function(){this.emitChange()},n.componentWillUnmount=function(){this.props.context.helmetInstances.remove(this),this.emitChange()},n.emitChange=function(){var e,t,n=this.props.context,r=n.setHelmet,a=null,o=(e=n.helmetInstances.get().map((function(e){var t=p({},e.props);return delete t.context,t})),{baseTag:A(["href"],e),bodyAttributes:_("bodyAttributes",e),defer:S(e,"defer"),encode:S(e,"encodeSpecialCharacters"),htmlAttributes:_("htmlAttributes",e),linkTags:T(h.LINK,["rel","href"],e),metaTags:T(h.META,["name","charset","http-equiv","property","itemprop"],e),noscriptTags:T(h.NOSCRIPT,["innerHTML"],e),onChangeClientState:C(e),scriptTags:T(h.SCRIPT,["src","innerHTML"],e),styleTags:T(h.STYLE,["cssText"],e),title:E(e),titleAttributes:_("titleAttributes",e),prioritizeSeoTags:j(e,"prioritizeSeoTags")});G.canUseDOM?(t=o,K&&cancelAnimationFrame(K),t.defer?K=requestAnimationFrame((function(){Q(t,(function(){K=null}))})):(Q(t),K=null)):z&&(a=z(o)),r(a)},n.init=function(){this.rendered||(this.rendered=!0,this.props.context.helmetInstances.add(this),this.emitChange())},n.render=function(){return this.init(),null},t}(r.Component);Y.propTypes={context:q.isRequired},Y.displayName="HelmetDispatcher";var Z=["children"],X=["children"],J=function(e){function t(){return e.apply(this,arguments)||this}f(t,e);var n=t.prototype;return n.shouldComponentUpdate=function(e){return!l()(R(this.props,"helmetData"),R(e,"helmetData"))},n.mapNestedChildrenToProps=function(e,t){if(!t)return null;switch(e.type){case h.SCRIPT:case h.NOSCRIPT:return{innerHTML:t};case h.STYLE:return{cssText:t};default:throw new Error("<"+e.type+" /> elements are self-closing and can not contain children. Refer to our API for more information.")}},n.flattenArrayTypeChildren=function(e){var t,n=e.child,r=e.arrayTypeChildren;return p({},r,((t={})[n.type]=[].concat(r[n.type]||[],[p({},e.newChildProps,this.mapNestedChildrenToProps(n,e.nestedChildren))]),t))},n.mapObjectTypeChildren=function(e){var t,n,r=e.child,a=e.newProps,o=e.newChildProps,i=e.nestedChildren;switch(r.type){case h.TITLE:return p({},a,((t={})[r.type]=i,t.titleAttributes=p({},o),t));case h.BODY:return p({},a,{bodyAttributes:p({},o)});case h.HTML:return p({},a,{htmlAttributes:p({},o)});default:return p({},a,((n={})[r.type]=p({},o),n))}},n.mapArrayTypeChildrenToProps=function(e,t){var n=p({},t);return Object.keys(e).forEach((function(t){var r;n=p({},n,((r={})[t]=e[t],r))})),n},n.warnOnInvalidChildren=function(e,t){return c()(w.some((function(t){return e.type===t})),"function"==typeof e.type?"You may be attempting to nest <Helmet> components within each other, which is not allowed. Refer to our API for more information.":"Only elements types "+w.join(", ")+" are allowed. Helmet does not support rendering <"+e.type+"> elements. Refer to our API for more information."),c()(!t||"string"==typeof t||Array.isArray(t)&&!t.some((function(e){return"string"!=typeof e})),"Helmet expects a string as a child of <"+e.type+">. Did you forget to wrap your children in braces? ( <"+e.type+">{``}</"+e.type+"> ) Refer to our API for more information."),!0},n.mapChildrenToProps=function(e,t){var n=this,a={};return r.Children.forEach(e,(function(e){if(e&&e.props){var r=e.props,o=r.children,i=g(r,Z),l=Object.keys(i).reduce((function(e,t){return e[x[t]||t]=i[t],e}),{}),s=e.type;switch("symbol"==typeof s?s=s.toString():n.warnOnInvalidChildren(e,o),s){case h.FRAGMENT:t=n.mapChildrenToProps(o,t);break;case h.LINK:case h.META:case h.NOSCRIPT:case h.SCRIPT:case h.STYLE:a=n.flattenArrayTypeChildren({child:e,arrayTypeChildren:a,newChildProps:l,nestedChildren:o});break;default:t=n.mapObjectTypeChildren({child:e,newProps:t,newChildProps:l,nestedChildren:o})}}})),this.mapArrayTypeChildrenToProps(a,t)},n.render=function(){var e=this.props,t=e.children,n=g(e,X),a=p({},n),o=n.helmetData;return t&&(a=this.mapChildrenToProps(t,a)),!o||o instanceof $||(o=new $(o.context,o.instances)),o?r.createElement(Y,p({},a,{context:o.value,helmetData:void 0})):r.createElement(U.Consumer,null,(function(e){return r.createElement(Y,p({},a,{context:e}))}))},t}(r.Component);J.propTypes={base:o().object,bodyAttributes:o().object,children:o().oneOfType([o().arrayOf(o().node),o().node]),defaultTitle:o().string,defer:o().bool,encodeSpecialCharacters:o().bool,htmlAttributes:o().object,link:o().arrayOf(o().object),meta:o().arrayOf(o().object),noscript:o().arrayOf(o().object),onChangeClientState:o().func,script:o().arrayOf(o().object),style:o().arrayOf(o().object),title:o().string,titleAttributes:o().object,titleTemplate:o().string,prioritizeSeoTags:o().bool,helmetData:o().object},J.defaultProps={defer:!0,encodeSpecialCharacters:!0,prioritizeSeoTags:!1},J.displayName="Helmet"},2799:(e,t)=>{"use strict";var n="function"==typeof Symbol&&Symbol.for,r=n?Symbol.for("react.element"):60103,a=n?Symbol.for("react.portal"):60106,o=n?Symbol.for("react.fragment"):60107,i=n?Symbol.for("react.strict_mode"):60108,l=n?Symbol.for("react.profiler"):60114,s=n?Symbol.for("react.provider"):60109,c=n?Symbol.for("react.context"):60110,u=n?Symbol.for("react.async_mode"):60111,d=n?Symbol.for("react.concurrent_mode"):60111,p=n?Symbol.for("react.forward_ref"):60112,f=n?Symbol.for("react.suspense"):60113,m=n?Symbol.for("react.suspense_list"):60120,g=n?Symbol.for("react.memo"):60115,h=n?Symbol.for("react.lazy"):60116,y=n?Symbol.for("react.block"):60121,b=n?Symbol.for("react.fundamental"):60117,v=n?Symbol.for("react.responder"):60118,w=n?Symbol.for("react.scope"):60119;function k(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case r:switch(e=e.type){case u:case d:case o:case l:case i:case f:return e;default:switch(e=e&&e.$$typeof){case c:case p:case h:case g:case s:return e;default:return t}}case a:return t}}}function x(e){return k(e)===d}t.AsyncMode=u,t.ConcurrentMode=d,t.ContextConsumer=c,t.ContextProvider=s,t.Element=r,t.ForwardRef=p,t.Fragment=o,t.Lazy=h,t.Memo=g,t.Portal=a,t.Profiler=l,t.StrictMode=i,t.Suspense=f,t.isAsyncMode=function(e){return x(e)||k(e)===u},t.isConcurrentMode=x,t.isContextConsumer=function(e){return k(e)===c},t.isContextProvider=function(e){return k(e)===s},t.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===r},t.isForwardRef=function(e){return k(e)===p},t.isFragment=function(e){return k(e)===o},t.isLazy=function(e){return k(e)===h},t.isMemo=function(e){return k(e)===g},t.isPortal=function(e){return k(e)===a},t.isProfiler=function(e){return k(e)===l},t.isStrictMode=function(e){return k(e)===i},t.isSuspense=function(e){return k(e)===f},t.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===o||e===d||e===l||e===i||e===f||e===m||"object"==typeof e&&null!==e&&(e.$$typeof===h||e.$$typeof===g||e.$$typeof===s||e.$$typeof===c||e.$$typeof===p||e.$$typeof===b||e.$$typeof===v||e.$$typeof===w||e.$$typeof===y)},t.typeOf=k},4363:(e,t,n)=>{"use strict";e.exports=n(2799)},3259:(e,t,n)=>{"use strict";function r(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,e.__proto__=t}function a(e){if(void 0===e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return e}function o(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function i(){return i=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},i.apply(this,arguments)}var l=n(6540),s=n(5556),c=[],u=[];function d(e){var t=e(),n={loading:!0,loaded:null,error:null};return n.promise=t.then((function(e){return n.loading=!1,n.loaded=e,e})).catch((function(e){throw n.loading=!1,n.error=e,e})),n}function p(e){var t={loading:!1,loaded:{},error:null},n=[];try{Object.keys(e).forEach((function(r){var a=d(e[r]);a.loading?t.loading=!0:(t.loaded[r]=a.loaded,t.error=a.error),n.push(a.promise),a.promise.then((function(e){t.loaded[r]=e})).catch((function(e){t.error=e}))}))}catch(r){t.error=r}return t.promise=Promise.all(n).then((function(e){return t.loading=!1,e})).catch((function(e){throw t.loading=!1,e})),t}function f(e,t){return l.createElement((n=e)&&n.__esModule?n.default:n,t);var n}function m(e,t){var d,p;if(!t.loading)throw new Error("react-loadable requires a `loading` component");var m=i({loader:null,loading:null,delay:200,timeout:null,render:f,webpack:null,modules:null},t),g=null;function h(){return g||(g=e(m.loader)),g.promise}return c.push(h),"function"==typeof m.webpack&&u.push((function(){if((0,m.webpack)().every((function(e){return void 0!==e&&void 0!==n.m[e]})))return h()})),p=d=function(t){function n(n){var r;return o(a(a(r=t.call(this,n)||this)),"retry",(function(){r.setState({error:null,loading:!0,timedOut:!1}),g=e(m.loader),r._loadModule()})),h(),r.state={error:g.error,pastDelay:!1,timedOut:!1,loading:g.loading,loaded:g.loaded},r}r(n,t),n.preload=function(){return h()};var i=n.prototype;return i.UNSAFE_componentWillMount=function(){this._loadModule()},i.componentDidMount=function(){this._mounted=!0},i._loadModule=function(){var e=this;if(this.context.loadable&&Array.isArray(m.modules)&&m.modules.forEach((function(t){e.context.loadable.report(t)})),g.loading){var t=function(t){e._mounted&&e.setState(t)};"number"==typeof m.delay&&(0===m.delay?this.setState({pastDelay:!0}):this._delay=setTimeout((function(){t({pastDelay:!0})}),m.delay)),"number"==typeof m.timeout&&(this._timeout=setTimeout((function(){t({timedOut:!0})}),m.timeout));var n=function(){t({error:g.error,loaded:g.loaded,loading:g.loading}),e._clearTimeouts()};g.promise.then((function(){return n(),null})).catch((function(e){return n(),null}))}},i.componentWillUnmount=function(){this._mounted=!1,this._clearTimeouts()},i._clearTimeouts=function(){clearTimeout(this._delay),clearTimeout(this._timeout)},i.render=function(){return this.state.loading||this.state.error?l.createElement(m.loading,{isLoading:this.state.loading,pastDelay:this.state.pastDelay,timedOut:this.state.timedOut,error:this.state.error,retry:this.retry}):this.state.loaded?m.render(this.state.loaded,this.props):null},n}(l.Component),o(d,"contextTypes",{loadable:s.shape({report:s.func.isRequired})}),p}function g(e){return m(d,e)}g.Map=function(e){if("function"!=typeof e.render)throw new Error("LoadableMap requires a `render(loaded, props)` function");return m(p,e)};var h=function(e){function t(){return e.apply(this,arguments)||this}r(t,e);var n=t.prototype;return n.getChildContext=function(){return{loadable:{report:this.props.report}}},n.render=function(){return l.Children.only(this.props.children)},t}(l.Component);function y(e){for(var t=[];e.length;){var n=e.pop();t.push(n())}return Promise.all(t).then((function(){if(e.length)return y(e)}))}o(h,"propTypes",{report:s.func.isRequired}),o(h,"childContextTypes",{loadable:s.shape({report:s.func.isRequired}).isRequired}),g.Capture=h,g.preloadAll=function(){return new Promise((function(e,t){y(c).then(e,t)}))},g.preloadReady=function(){return new Promise((function(e,t){y(u).then(e,e)}))},e.exports=g},2831:(e,t,n)=>{"use strict";n.d(t,{u:()=>i,v:()=>l});var r=n(6347),a=n(8168),o=n(6540);function i(e,t,n){return void 0===n&&(n=[]),e.some((function(e){var a=e.path?(0,r.B6)(t,e):n.length?n[n.length-1].match:r.Ix.computeRootMatch(t);return a&&(n.push({route:e,match:a}),e.routes&&i(e.routes,t,n)),a})),n}function l(e,t,n){return void 0===t&&(t={}),void 0===n&&(n={}),e?o.createElement(r.dO,n,e.map((function(e,n){return o.createElement(r.qh,{key:e.key||n,path:e.path,exact:e.exact,strict:e.strict,render:function(n){return e.render?e.render((0,a.A)({},n,{},t,{route:e})):o.createElement(e.component,(0,a.A)({},n,t,{route:e}))}})}))):null}},4625:(e,t,n)=>{"use strict";n.d(t,{Kd:()=>u,N_:()=>h,k2:()=>v});var r=n(6347),a=n(2892),o=n(6540),i=n(1513),l=n(8168),s=n(8587),c=n(1561),u=function(e){function t(){for(var t,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(t=e.call.apply(e,[this].concat(r))||this).history=(0,i.zR)(t.props),t}return(0,a.A)(t,e),t.prototype.render=function(){return o.createElement(r.Ix,{history:this.history,children:this.props.children})},t}(o.Component);o.Component;var d=function(e,t){return"function"==typeof e?e(t):e},p=function(e,t){return"string"==typeof e?(0,i.yJ)(e,null,null,t):e},f=function(e){return e},m=o.forwardRef;void 0===m&&(m=f);var g=m((function(e,t){var n=e.innerRef,r=e.navigate,a=e.onClick,i=(0,s.A)(e,["innerRef","navigate","onClick"]),c=i.target,u=(0,l.A)({},i,{onClick:function(e){try{a&&a(e)}catch(t){throw e.preventDefault(),t}e.defaultPrevented||0!==e.button||c&&"_self"!==c||function(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}(e)||(e.preventDefault(),r())}});return u.ref=f!==m&&t||n,o.createElement("a",u)}));var h=m((function(e,t){var n=e.component,a=void 0===n?g:n,u=e.replace,h=e.to,y=e.innerRef,b=(0,s.A)(e,["component","replace","to","innerRef"]);return o.createElement(r.XZ.Consumer,null,(function(e){e||(0,c.A)(!1);var n=e.history,r=p(d(h,e.location),e.location),s=r?n.createHref(r):"",g=(0,l.A)({},b,{href:s,navigate:function(){var t=d(h,e.location),r=(0,i.AO)(e.location)===(0,i.AO)(p(t));(u||r?n.replace:n.push)(t)}});return f!==m?g.ref=t||y:g.innerRef=y,o.createElement(a,g)}))})),y=function(e){return e},b=o.forwardRef;void 0===b&&(b=y);var v=b((function(e,t){var n=e["aria-current"],a=void 0===n?"page":n,i=e.activeClassName,u=void 0===i?"active":i,f=e.activeStyle,m=e.className,g=e.exact,v=e.isActive,w=e.location,k=e.sensitive,x=e.strict,S=e.style,E=e.to,C=e.innerRef,_=(0,s.A)(e,["aria-current","activeClassName","activeStyle","className","exact","isActive","location","sensitive","strict","style","to","innerRef"]);return o.createElement(r.XZ.Consumer,null,(function(e){e||(0,c.A)(!1);var n=w||e.location,i=p(d(E,n),n),s=i.pathname,A=s&&s.replace(/([.+*?=^!:${}()[\]|/\\])/g,"\\$1"),T=A?(0,r.B6)(n.pathname,{path:A,exact:g,sensitive:k,strict:x}):null,j=!!(v?v(T,n):T),L="function"==typeof m?m(j):m,N="function"==typeof S?S(j):S;j&&(L=function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return t.filter((function(e){return e})).join(" ")}(L,u),N=(0,l.A)({},N,f));var R=(0,l.A)({"aria-current":j&&a||null,className:L,style:N,to:i},_);return y!==b?R.ref=t||C:R.innerRef=C,o.createElement(h,R)}))}))},6347:(e,t,n)=>{"use strict";n.d(t,{B6:()=>S,Ix:()=>v,W6:()=>R,XZ:()=>b,dO:()=>L,qh:()=>E,zy:()=>P});var r=n(2892),a=n(6540),o=n(5556),i=n.n(o),l=n(1513),s=n(1561),c=n(8168),u=n(8505),d=n.n(u),p=(n(4363),n(8587)),f=(n(4146),1073741823),m="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:void 0!==n.g?n.g:{};var g=a.createContext||function(e,t){var n,o,l="__create-react-context-"+function(){var e="__global_unique_id__";return m[e]=(m[e]||0)+1}()+"__",s=function(e){function n(){for(var t,n,r,a=arguments.length,o=new Array(a),i=0;i<a;i++)o[i]=arguments[i];return(t=e.call.apply(e,[this].concat(o))||this).emitter=(n=t.props.value,r=[],{on:function(e){r.push(e)},off:function(e){r=r.filter((function(t){return t!==e}))},get:function(){return n},set:function(e,t){n=e,r.forEach((function(e){return e(n,t)}))}}),t}(0,r.A)(n,e);var a=n.prototype;return a.getChildContext=function(){var e;return(e={})[l]=this.emitter,e},a.componentWillReceiveProps=function(e){if(this.props.value!==e.value){var n,r=this.props.value,a=e.value;((o=r)===(i=a)?0!==o||1/o==1/i:o!=o&&i!=i)?n=0:(n="function"==typeof t?t(r,a):f,0!==(n|=0)&&this.emitter.set(e.value,n))}var o,i},a.render=function(){return this.props.children},n}(a.Component);s.childContextTypes=((n={})[l]=i().object.isRequired,n);var c=function(t){function n(){for(var e,n=arguments.length,r=new Array(n),a=0;a<n;a++)r[a]=arguments[a];return(e=t.call.apply(t,[this].concat(r))||this).observedBits=void 0,e.state={value:e.getValue()},e.onUpdate=function(t,n){0!=((0|e.observedBits)&n)&&e.setState({value:e.getValue()})},e}(0,r.A)(n,t);var a=n.prototype;return a.componentWillReceiveProps=function(e){var t=e.observedBits;this.observedBits=null==t?f:t},a.componentDidMount=function(){this.context[l]&&this.context[l].on(this.onUpdate);var e=this.props.observedBits;this.observedBits=null==e?f:e},a.componentWillUnmount=function(){this.context[l]&&this.context[l].off(this.onUpdate)},a.getValue=function(){return this.context[l]?this.context[l].get():e},a.render=function(){return(e=this.props.children,Array.isArray(e)?e[0]:e)(this.state.value);var e},n}(a.Component);return c.contextTypes=((o={})[l]=i().object,o),{Provider:s,Consumer:c}},h=function(e){var t=g();return t.displayName=e,t},y=h("Router-History"),b=h("Router"),v=function(e){function t(t){var n;return(n=e.call(this,t)||this).state={location:t.history.location},n._isMounted=!1,n._pendingLocation=null,t.staticContext||(n.unlisten=t.history.listen((function(e){n._pendingLocation=e}))),n}(0,r.A)(t,e),t.computeRootMatch=function(e){return{path:"/",url:"/",params:{},isExact:"/"===e}};var n=t.prototype;return n.componentDidMount=function(){var e=this;this._isMounted=!0,this.unlisten&&this.unlisten(),this.props.staticContext||(this.unlisten=this.props.history.listen((function(t){e._isMounted&&e.setState({location:t})}))),this._pendingLocation&&this.setState({location:this._pendingLocation})},n.componentWillUnmount=function(){this.unlisten&&(this.unlisten(),this._isMounted=!1,this._pendingLocation=null)},n.render=function(){return a.createElement(b.Provider,{value:{history:this.props.history,location:this.state.location,match:t.computeRootMatch(this.state.location.pathname),staticContext:this.props.staticContext}},a.createElement(y.Provider,{children:this.props.children||null,value:this.props.history}))},t}(a.Component);a.Component;a.Component;var w={},k=1e4,x=0;function S(e,t){void 0===t&&(t={}),("string"==typeof t||Array.isArray(t))&&(t={path:t});var n=t,r=n.path,a=n.exact,o=void 0!==a&&a,i=n.strict,l=void 0!==i&&i,s=n.sensitive,c=void 0!==s&&s;return[].concat(r).reduce((function(t,n){if(!n&&""!==n)return null;if(t)return t;var r=function(e,t){var n=""+t.end+t.strict+t.sensitive,r=w[n]||(w[n]={});if(r[e])return r[e];var a=[],o={regexp:d()(e,a,t),keys:a};return x<k&&(r[e]=o,x++),o}(n,{end:o,strict:l,sensitive:c}),a=r.regexp,i=r.keys,s=a.exec(e);if(!s)return null;var u=s[0],p=s.slice(1),f=e===u;return o&&!f?null:{path:n,url:"/"===n&&""===u?"/":u,isExact:f,params:i.reduce((function(e,t,n){return e[t.name]=p[n],e}),{})}}),null)}var E=function(e){function t(){return e.apply(this,arguments)||this}return(0,r.A)(t,e),t.prototype.render=function(){var e=this;return a.createElement(b.Consumer,null,(function(t){t||(0,s.A)(!1);var n=e.props.location||t.location,r=e.props.computedMatch?e.props.computedMatch:e.props.path?S(n.pathname,e.props):t.match,o=(0,c.A)({},t,{location:n,match:r}),i=e.props,l=i.children,u=i.component,d=i.render;return Array.isArray(l)&&function(e){return 0===a.Children.count(e)}(l)&&(l=null),a.createElement(b.Provider,{value:o},o.match?l?"function"==typeof l?l(o):l:u?a.createElement(u,o):d?d(o):null:"function"==typeof l?l(o):null)}))},t}(a.Component);function C(e){return"/"===e.charAt(0)?e:"/"+e}function _(e,t){if(!e)return t;var n=C(e);return 0!==t.pathname.indexOf(n)?t:(0,c.A)({},t,{pathname:t.pathname.substr(n.length)})}function A(e){return"string"==typeof e?e:(0,l.AO)(e)}function T(e){return function(){(0,s.A)(!1)}}function j(){}a.Component;var L=function(e){function t(){return e.apply(this,arguments)||this}return(0,r.A)(t,e),t.prototype.render=function(){var e=this;return a.createElement(b.Consumer,null,(function(t){t||(0,s.A)(!1);var n,r,o=e.props.location||t.location;return a.Children.forEach(e.props.children,(function(e){if(null==r&&a.isValidElement(e)){n=e;var i=e.props.path||e.props.from;r=i?S(o.pathname,(0,c.A)({},e.props,{path:i})):t.match}})),r?a.cloneElement(n,{location:o,computedMatch:r}):null}))},t}(a.Component);var N=a.useContext;function R(){return N(y)}function P(){return N(b).location}},8505:(e,t,n)=>{var r=n(4634);e.exports=f,e.exports.parse=o,e.exports.compile=function(e,t){return l(o(e,t),t)},e.exports.tokensToFunction=l,e.exports.tokensToRegExp=p;var a=new RegExp(["(\\\\.)","([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))"].join("|"),"g");function o(e,t){for(var n,r=[],o=0,i=0,l="",u=t&&t.delimiter||"/";null!=(n=a.exec(e));){var d=n[0],p=n[1],f=n.index;if(l+=e.slice(i,f),i=f+d.length,p)l+=p[1];else{var m=e[i],g=n[2],h=n[3],y=n[4],b=n[5],v=n[6],w=n[7];l&&(r.push(l),l="");var k=null!=g&&null!=m&&m!==g,x="+"===v||"*"===v,S="?"===v||"*"===v,E=n[2]||u,C=y||b;r.push({name:h||o++,prefix:g||"",delimiter:E,optional:S,repeat:x,partial:k,asterisk:!!w,pattern:C?c(C):w?".*":"[^"+s(E)+"]+?"})}}return i<e.length&&(l+=e.substr(i)),l&&r.push(l),r}function i(e){return encodeURI(e).replace(/[\/?#]/g,(function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()}))}function l(e,t){for(var n=new Array(e.length),a=0;a<e.length;a++)"object"==typeof e[a]&&(n[a]=new RegExp("^(?:"+e[a].pattern+")$",d(t)));return function(t,a){for(var o="",l=t||{},s=(a||{}).pretty?i:encodeURIComponent,c=0;c<e.length;c++){var u=e[c];if("string"!=typeof u){var d,p=l[u.name];if(null==p){if(u.optional){u.partial&&(o+=u.prefix);continue}throw new TypeError('Expected "'+u.name+'" to be defined')}if(r(p)){if(!u.repeat)throw new TypeError('Expected "'+u.name+'" to not repeat, but received `'+JSON.stringify(p)+"`");if(0===p.length){if(u.optional)continue;throw new TypeError('Expected "'+u.name+'" to not be empty')}for(var f=0;f<p.length;f++){if(d=s(p[f]),!n[c].test(d))throw new TypeError('Expected all "'+u.name+'" to match "'+u.pattern+'", but received `'+JSON.stringify(d)+"`");o+=(0===f?u.prefix:u.delimiter)+d}}else{if(d=u.asterisk?encodeURI(p).replace(/[?#]/g,(function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()})):s(p),!n[c].test(d))throw new TypeError('Expected "'+u.name+'" to match "'+u.pattern+'", but received "'+d+'"');o+=u.prefix+d}}else o+=u}return o}}function s(e){return e.replace(/([.+*?=^!:${}()[\]|\/\\])/g,"\\$1")}function c(e){return e.replace(/([=!:$\/()])/g,"\\$1")}function u(e,t){return e.keys=t,e}function d(e){return e&&e.sensitive?"":"i"}function p(e,t,n){r(t)||(n=t||n,t=[]);for(var a=(n=n||{}).strict,o=!1!==n.end,i="",l=0;l<e.length;l++){var c=e[l];if("string"==typeof c)i+=s(c);else{var p=s(c.prefix),f="(?:"+c.pattern+")";t.push(c),c.repeat&&(f+="(?:"+p+f+")*"),i+=f=c.optional?c.partial?p+"("+f+")?":"(?:"+p+"("+f+"))?":p+"("+f+")"}}var m=s(n.delimiter||"/"),g=i.slice(-m.length)===m;return a||(i=(g?i.slice(0,-m.length):i)+"(?:"+m+"(?=$))?"),i+=o?"$":a&&g?"":"(?="+m+"|$)",u(new RegExp("^"+i,d(n)),t)}function f(e,t,n){return r(t)||(n=t||n,t=[]),n=n||{},e instanceof RegExp?function(e,t){var n=e.source.match(/\((?!\?)/g);if(n)for(var r=0;r<n.length;r++)t.push({name:r,prefix:null,delimiter:null,optional:!1,repeat:!1,partial:!1,asterisk:!1,pattern:null});return u(e,t)}(e,t):r(e)?function(e,t,n){for(var r=[],a=0;a<e.length;a++)r.push(f(e[a],t,n).source);return u(new RegExp("(?:"+r.join("|")+")",d(n)),t)}(e,t,n):function(e,t,n){return p(o(e,n),t,n)}(e,t,n)}},1020:(e,t,n)=>{"use strict";var r=n(6540),a=Symbol.for("react.element"),o=Symbol.for("react.fragment"),i=Object.prototype.hasOwnProperty,l=r.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,s={key:!0,ref:!0,__self:!0,__source:!0};function c(e,t,n){var r,o={},c=null,u=null;for(r in void 0!==n&&(c=""+n),void 0!==t.key&&(c=""+t.key),void 0!==t.ref&&(u=t.ref),t)i.call(t,r)&&!s.hasOwnProperty(r)&&(o[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps)void 0===o[r]&&(o[r]=t[r]);return{$$typeof:a,type:e,key:c,ref:u,props:o,_owner:l.current}}t.Fragment=o,t.jsx=c,t.jsxs=c},5287:(e,t)=>{"use strict";var n=Symbol.for("react.element"),r=Symbol.for("react.portal"),a=Symbol.for("react.fragment"),o=Symbol.for("react.strict_mode"),i=Symbol.for("react.profiler"),l=Symbol.for("react.provider"),s=Symbol.for("react.context"),c=Symbol.for("react.forward_ref"),u=Symbol.for("react.suspense"),d=Symbol.for("react.memo"),p=Symbol.for("react.lazy"),f=Symbol.iterator;var m={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,h={};function y(e,t,n){this.props=e,this.context=t,this.refs=h,this.updater=n||m}function b(){}function v(e,t,n){this.props=e,this.context=t,this.refs=h,this.updater=n||m}y.prototype.isReactComponent={},y.prototype.setState=function(e,t){if("object"!=typeof e&&"function"!=typeof e&&null!=e)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")},y.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")},b.prototype=y.prototype;var w=v.prototype=new b;w.constructor=v,g(w,y.prototype),w.isPureReactComponent=!0;var k=Array.isArray,x=Object.prototype.hasOwnProperty,S={current:null},E={key:!0,ref:!0,__self:!0,__source:!0};function C(e,t,r){var a,o={},i=null,l=null;if(null!=t)for(a in void 0!==t.ref&&(l=t.ref),void 0!==t.key&&(i=""+t.key),t)x.call(t,a)&&!E.hasOwnProperty(a)&&(o[a]=t[a]);var s=arguments.length-2;if(1===s)o.children=r;else if(1<s){for(var c=Array(s),u=0;u<s;u++)c[u]=arguments[u+2];o.children=c}if(e&&e.defaultProps)for(a in s=e.defaultProps)void 0===o[a]&&(o[a]=s[a]);return{$$typeof:n,type:e,key:i,ref:l,props:o,_owner:S.current}}function _(e){return"object"==typeof e&&null!==e&&e.$$typeof===n}var A=/\/+/g;function T(e,t){return"object"==typeof e&&null!==e&&null!=e.key?function(e){var t={"=":"=0",":":"=2"};return"$"+e.replace(/[=:]/g,(function(e){return t[e]}))}(""+e.key):t.toString(36)}function j(e,t,a,o,i){var l=typeof e;"undefined"!==l&&"boolean"!==l||(e=null);var s=!1;if(null===e)s=!0;else switch(l){case"string":case"number":s=!0;break;case"object":switch(e.$$typeof){case n:case r:s=!0}}if(s)return i=i(s=e),e=""===o?"."+T(s,0):o,k(i)?(a="",null!=e&&(a=e.replace(A,"$&/")+"/"),j(i,t,a,"",(function(e){return e}))):null!=i&&(_(i)&&(i=function(e,t){return{$$typeof:n,type:e.type,key:t,ref:e.ref,props:e.props,_owner:e._owner}}(i,a+(!i.key||s&&s.key===i.key?"":(""+i.key).replace(A,"$&/")+"/")+e)),t.push(i)),1;if(s=0,o=""===o?".":o+":",k(e))for(var c=0;c<e.length;c++){var u=o+T(l=e[c],c);s+=j(l,t,a,u,i)}else if(u=function(e){return null===e||"object"!=typeof e?null:"function"==typeof(e=f&&e[f]||e["@@iterator"])?e:null}(e),"function"==typeof u)for(e=u.call(e),c=0;!(l=e.next()).done;)s+=j(l=l.value,t,a,u=o+T(l,c++),i);else if("object"===l)throw t=String(e),Error("Objects are not valid as a React child (found: "+("[object Object]"===t?"object with keys {"+Object.keys(e).join(", ")+"}":t)+"). If you meant to render a collection of children, use an array instead.");return s}function L(e,t,n){if(null==e)return e;var r=[],a=0;return j(e,r,"","",(function(e){return t.call(n,e,a++)})),r}function N(e){if(-1===e._status){var t=e._result;(t=t()).then((function(t){0!==e._status&&-1!==e._status||(e._status=1,e._result=t)}),(function(t){0!==e._status&&-1!==e._status||(e._status=2,e._result=t)})),-1===e._status&&(e._status=0,e._result=t)}if(1===e._status)return e._result.default;throw e._result}var R={current:null},P={transition:null},O={ReactCurrentDispatcher:R,ReactCurrentBatchConfig:P,ReactCurrentOwner:S};t.Children={map:L,forEach:function(e,t,n){L(e,(function(){t.apply(this,arguments)}),n)},count:function(e){var t=0;return L(e,(function(){t++})),t},toArray:function(e){return L(e,(function(e){return e}))||[]},only:function(e){if(!_(e))throw Error("React.Children.only expected to receive a single React element child.");return e}},t.Component=y,t.Fragment=a,t.Profiler=i,t.PureComponent=v,t.StrictMode=o,t.Suspense=u,t.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=O,t.cloneElement=function(e,t,r){if(null==e)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+e+".");var a=g({},e.props),o=e.key,i=e.ref,l=e._owner;if(null!=t){if(void 0!==t.ref&&(i=t.ref,l=S.current),void 0!==t.key&&(o=""+t.key),e.type&&e.type.defaultProps)var s=e.type.defaultProps;for(c in t)x.call(t,c)&&!E.hasOwnProperty(c)&&(a[c]=void 0===t[c]&&void 0!==s?s[c]:t[c])}var c=arguments.length-2;if(1===c)a.children=r;else if(1<c){s=Array(c);for(var u=0;u<c;u++)s[u]=arguments[u+2];a.children=s}return{$$typeof:n,type:e.type,key:o,ref:i,props:a,_owner:l}},t.createContext=function(e){return(e={$$typeof:s,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null,_defaultValue:null,_globalName:null}).Provider={$$typeof:l,_context:e},e.Consumer=e},t.createElement=C,t.createFactory=function(e){var t=C.bind(null,e);return t.type=e,t},t.createRef=function(){return{current:null}},t.forwardRef=function(e){return{$$typeof:c,render:e}},t.isValidElement=_,t.lazy=function(e){return{$$typeof:p,_payload:{_status:-1,_result:e},_init:N}},t.memo=function(e,t){return{$$typeof:d,type:e,compare:void 0===t?null:t}},t.startTransition=function(e){var t=P.transition;P.transition={};try{e()}finally{P.transition=t}},t.unstable_act=function(){throw Error("act(...) is not supported in production builds of React.")},t.useCallback=function(e,t){return R.current.useCallback(e,t)},t.useContext=function(e){return R.current.useContext(e)},t.useDebugValue=function(){},t.useDeferredValue=function(e){return R.current.useDeferredValue(e)},t.useEffect=function(e,t){return R.current.useEffect(e,t)},t.useId=function(){return R.current.useId()},t.useImperativeHandle=function(e,t,n){return R.current.useImperativeHandle(e,t,n)},t.useInsertionEffect=function(e,t){return R.current.useInsertionEffect(e,t)},t.useLayoutEffect=function(e,t){return R.current.useLayoutEffect(e,t)},t.useMemo=function(e,t){return R.current.useMemo(e,t)},t.useReducer=function(e,t,n){return R.current.useReducer(e,t,n)},t.useRef=function(e){return R.current.useRef(e)},t.useState=function(e){return R.current.useState(e)},t.useSyncExternalStore=function(e,t,n){return R.current.useSyncExternalStore(e,t,n)},t.useTransition=function(){return R.current.useTransition()},t.version="18.2.0"},6540:(e,t,n)=>{"use strict";e.exports=n(5287)},4848:(e,t,n)=>{"use strict";e.exports=n(1020)},7463:(e,t)=>{"use strict";function n(e,t){var n=e.length;e.push(t);e:for(;0<n;){var r=n-1>>>1,a=e[r];if(!(0<o(a,t)))break e;e[r]=t,e[n]=a,n=r}}function r(e){return 0===e.length?null:e[0]}function a(e){if(0===e.length)return null;var t=e[0],n=e.pop();if(n!==t){e[0]=n;e:for(var r=0,a=e.length,i=a>>>1;r<i;){var l=2*(r+1)-1,s=e[l],c=l+1,u=e[c];if(0>o(s,n))c<a&&0>o(u,s)?(e[r]=u,e[c]=n,r=c):(e[r]=s,e[l]=n,r=l);else{if(!(c<a&&0>o(u,n)))break e;e[r]=u,e[c]=n,r=c}}}return t}function o(e,t){var n=e.sortIndex-t.sortIndex;return 0!==n?n:e.id-t.id}if("object"==typeof performance&&"function"==typeof performance.now){var i=performance;t.unstable_now=function(){return i.now()}}else{var l=Date,s=l.now();t.unstable_now=function(){return l.now()-s}}var c=[],u=[],d=1,p=null,f=3,m=!1,g=!1,h=!1,y="function"==typeof setTimeout?setTimeout:null,b="function"==typeof clearTimeout?clearTimeout:null,v="undefined"!=typeof setImmediate?setImmediate:null;function w(e){for(var t=r(u);null!==t;){if(null===t.callback)a(u);else{if(!(t.startTime<=e))break;a(u),t.sortIndex=t.expirationTime,n(c,t)}t=r(u)}}function k(e){if(h=!1,w(e),!g)if(null!==r(c))g=!0,P(x);else{var t=r(u);null!==t&&O(k,t.startTime-e)}}function x(e,n){g=!1,h&&(h=!1,b(_),_=-1),m=!0;var o=f;try{for(w(n),p=r(c);null!==p&&(!(p.expirationTime>n)||e&&!j());){var i=p.callback;if("function"==typeof i){p.callback=null,f=p.priorityLevel;var l=i(p.expirationTime<=n);n=t.unstable_now(),"function"==typeof l?p.callback=l:p===r(c)&&a(c),w(n)}else a(c);p=r(c)}if(null!==p)var s=!0;else{var d=r(u);null!==d&&O(k,d.startTime-n),s=!1}return s}finally{p=null,f=o,m=!1}}"undefined"!=typeof navigator&&void 0!==navigator.scheduling&&void 0!==navigator.scheduling.isInputPending&&navigator.scheduling.isInputPending.bind(navigator.scheduling);var S,E=!1,C=null,_=-1,A=5,T=-1;function j(){return!(t.unstable_now()-T<A)}function L(){if(null!==C){var e=t.unstable_now();T=e;var n=!0;try{n=C(!0,e)}finally{n?S():(E=!1,C=null)}}else E=!1}if("function"==typeof v)S=function(){v(L)};else if("undefined"!=typeof MessageChannel){var N=new MessageChannel,R=N.port2;N.port1.onmessage=L,S=function(){R.postMessage(null)}}else S=function(){y(L,0)};function P(e){C=e,E||(E=!0,S())}function O(e,n){_=y((function(){e(t.unstable_now())}),n)}t.unstable_IdlePriority=5,t.unstable_ImmediatePriority=1,t.unstable_LowPriority=4,t.unstable_NormalPriority=3,t.unstable_Profiling=null,t.unstable_UserBlockingPriority=2,t.unstable_cancelCallback=function(e){e.callback=null},t.unstable_continueExecution=function(){g||m||(g=!0,P(x))},t.unstable_forceFrameRate=function(e){0>e||125<e?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):A=0<e?Math.floor(1e3/e):5},t.unstable_getCurrentPriorityLevel=function(){return f},t.unstable_getFirstCallbackNode=function(){return r(c)},t.unstable_next=function(e){switch(f){case 1:case 2:case 3:var t=3;break;default:t=f}var n=f;f=t;try{return e()}finally{f=n}},t.unstable_pauseExecution=function(){},t.unstable_requestPaint=function(){},t.unstable_runWithPriority=function(e,t){switch(e){case 1:case 2:case 3:case 4:case 5:break;default:e=3}var n=f;f=e;try{return t()}finally{f=n}},t.unstable_scheduleCallback=function(e,a,o){var i=t.unstable_now();switch("object"==typeof o&&null!==o?o="number"==typeof(o=o.delay)&&0<o?i+o:i:o=i,e){case 1:var l=-1;break;case 2:l=250;break;case 5:l=1073741823;break;case 4:l=1e4;break;default:l=5e3}return e={id:d++,callback:a,priorityLevel:e,startTime:o,expirationTime:l=o+l,sortIndex:-1},o>i?(e.sortIndex=o,n(u,e),null===r(c)&&e===r(u)&&(h?(b(_),_=-1):h=!0,O(k,o-i))):(e.sortIndex=l,n(c,e),g||m||(g=!0,P(x))),e},t.unstable_shouldYield=j,t.unstable_wrapCallback=function(e){var t=f;return function(){var n=f;f=t;try{return e.apply(this,arguments)}finally{f=n}}}},9982:(e,t,n)=>{"use strict";e.exports=n(7463)},2833:e=>{e.exports=function(e,t,n,r){var a=n?n.call(r,e,t):void 0;if(void 0!==a)return!!a;if(e===t)return!0;if("object"!=typeof e||!e||"object"!=typeof t||!t)return!1;var o=Object.keys(e),i=Object.keys(t);if(o.length!==i.length)return!1;for(var l=Object.prototype.hasOwnProperty.bind(t),s=0;s<o.length;s++){var c=o[s];if(!l(c))return!1;var u=e[c],d=t[c];if(!1===(a=n?n.call(r,u,d,c):void 0)||void 0===a&&u!==d)return!1}return!0}},4784:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>r});const r={title:"LmcRbacMvc",tagline:"Role-based access control for Laminas MVC application",favicon:"img/favicon.ico",url:"https://lm-commons.github.io",baseUrl:"/lmc-rbac-mvc/",organizationName:"LM Commons",projectName:"lmcrbacmvc",onBrokenLinks:"throw",onBrokenMarkdownLinks:"warn",i18n:{defaultLocale:"en",locales:["en"],path:"i18n",localeConfigs:{}},presets:[["classic",{docs:{sidebarPath:"./sidebars.js",editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/"},blog:{showReadingTime:!0,editUrl:"https://github.com/lm-commons/lmcrbacmvc/tree/master/docs/"},theme:{customCss:"./src/css/custom.css"}}]],themeConfig:{image:"img/LMC-social-card.png",navbar:{title:"LM-Commons Rbac Mvc",logo:{alt:"LM-Commons Logo",src:"img/LMC-logo.png"},items:[{type:"docSidebar",sidebarId:"tutorialSidebar",position:"left",label:"Documentation"},{to:"/blog",label:"Blog",position:"left"},{href:"https://lm-commons.github.io",label:"LM-Commons",position:"right"},{href:"https://github.com/lm-commons/lmcrbacmvc",label:"GitHub",position:"right"}],hideOnScroll:!1},footer:{style:"dark",links:[{title:"Docs",items:[{label:"Documentation",to:"/docs/intro"}]},{title:"Community",items:[{label:"Gitter",href:"https://gitter.im/LM-Commons/community"}]},{title:"More",items:[{label:"Blog",to:"/blog"},{label:"GitHub",href:"https://github.com/lm-commons/lmcrbacmvc"}]}],copyright:"Copyright \xa9 2024 LM Commons Organization. Built with Docusaurus."},prism:{theme:{plain:{color:"#393A34",backgroundColor:"#f6f8fa"},styles:[{types:["comment","prolog","doctype","cdata"],style:{color:"#999988",fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}},{types:["string","attr-value"],style:{color:"#e3116c"}},{types:["punctuation","operator"],style:{color:"#393A34"}},{types:["entity","url","symbol","number","boolean","variable","constant","property","regex","inserted"],style:{color:"#36acaa"}},{types:["atrule","keyword","attr-name","selector"],style:{color:"#00a4db"}},{types:["function","deleted","tag"],style:{color:"#d73a49"}},{types:["function-variable"],style:{color:"#6f42c1"}},{types:["tag","selector","keyword"],style:{color:"#00009f"}}]},darkTheme:{plain:{color:"#F8F8F2",backgroundColor:"#282A36"},styles:[{types:["prolog","constant","builtin"],style:{color:"rgb(189, 147, 249)"}},{types:["inserted","function"],style:{color:"rgb(80, 250, 123)"}},{types:["deleted"],style:{color:"rgb(255, 85, 85)"}},{types:["changed"],style:{color:"rgb(255, 184, 108)"}},{types:["punctuation","symbol"],style:{color:"rgb(248, 248, 242)"}},{types:["string","char","tag","selector"],style:{color:"rgb(255, 121, 198)"}},{types:["keyword","variable"],style:{color:"rgb(189, 147, 249)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(98, 114, 164)"}},{types:["attr-name"],style:{color:"rgb(241, 250, 140)"}}]},additionalLanguages:[],magicComments:[{className:"theme-code-block-highlighted-line",line:"highlight-next-line",block:{start:"highlight-start",end:"highlight-end"}}]},colorMode:{defaultMode:"light",disableSwitch:!1,respectPrefersColorScheme:!1},docs:{versionPersistence:"localStorage",sidebar:{hideable:!1,autoCollapseCategories:!1}},metadata:[],tableOfContents:{minHeadingLevel:2,maxHeadingLevel:3}},baseUrlIssueBanner:!0,onBrokenAnchors:"warn",onDuplicateRoutes:"warn",staticDirectories:["static"],customFields:{},plugins:[],themes:[],scripts:[],headTags:[],stylesheets:[],clientModules:[],titleDelimiter:"|",noIndex:!1,markdown:{format:"mdx",mermaid:!1,mdx1Compat:{comments:!0,admonitions:!0,headingIds:!0}}}},8168:(e,t,n)=>{"use strict";function r(){return r=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},r.apply(this,arguments)}n.d(t,{A:()=>r})},2892:(e,t,n)=>{"use strict";function r(e,t){return r=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},r(e,t)}function a(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,r(e,t)}n.d(t,{A:()=>a})},8587:(e,t,n)=>{"use strict";function r(e,t){if(null==e)return{};var n,r,a={},o=Object.keys(e);for(r=0;r<o.length;r++)n=o[r],t.indexOf(n)>=0||(a[n]=e[n]);return a}n.d(t,{A:()=>r})},4164:(e,t,n)=>{"use strict";function r(e){var t,n,a="";if("string"==typeof e||"number"==typeof e)a+=e;else if("object"==typeof e)if(Array.isArray(e)){var o=e.length;for(t=0;t<o;t++)e[t]&&(n=r(e[t]))&&(a&&(a+=" "),a+=n)}else for(n in e)e[n]&&(a&&(a+=" "),a+=n);return a}n.d(t,{A:()=>a});const a=function(){for(var e,t,n=0,a="",o=arguments.length;n<o;n++)(e=arguments[n])&&(t=r(e))&&(a&&(a+=" "),a+=t);return a}},1765:(e,t,n)=>{"use strict";n.d(t,{My:()=>A,f4:()=>ee});var r,a,o,i,l,s,c,u=n(6540),d=n(4164),p=Object.create,f=Object.defineProperty,m=Object.defineProperties,g=Object.getOwnPropertyDescriptor,h=Object.getOwnPropertyDescriptors,y=Object.getOwnPropertyNames,b=Object.getOwnPropertySymbols,v=Object.getPrototypeOf,w=Object.prototype.hasOwnProperty,k=Object.prototype.propertyIsEnumerable,x=(e,t,n)=>t in e?f(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,S=(e,t)=>{for(var n in t||(t={}))w.call(t,n)&&x(e,n,t[n]);if(b)for(var n of b(t))k.call(t,n)&&x(e,n,t[n]);return e},E=(e,t)=>m(e,h(t)),C=(e,t)=>{var n={};for(var r in e)w.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(null!=e&&b)for(var r of b(e))t.indexOf(r)<0&&k.call(e,r)&&(n[r]=e[r]);return n},_=(r={"../../node_modules/.pnpm/prismjs@1.29.0_patch_hash=vrxx3pzkik6jpmgpayxfjunetu/node_modules/prismjs/prism.js"(e,t){var n=function(){var e=/(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i,t=0,n={},r={util:{encode:function e(t){return t instanceof a?new a(t.type,e(t.content),t.alias):Array.isArray(t)?t.map(e):t.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++t}),e.__id},clone:function e(t,n){var a,o;switch(n=n||{},r.util.type(t)){case"Object":if(o=r.util.objId(t),n[o])return n[o];for(var i in a={},n[o]=a,t)t.hasOwnProperty(i)&&(a[i]=e(t[i],n));return a;case"Array":return o=r.util.objId(t),n[o]?n[o]:(a=[],n[o]=a,t.forEach((function(t,r){a[r]=e(t,n)})),a);default:return t}},getLanguage:function(t){for(;t;){var n=e.exec(t.className);if(n)return n[1].toLowerCase();t=t.parentElement}return"none"},setLanguage:function(t,n){t.className=t.className.replace(RegExp(e,"gi"),""),t.classList.add("language-"+n)},isActive:function(e,t,n){for(var r="no-"+t;e;){var a=e.classList;if(a.contains(t))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!n}},languages:{plain:n,plaintext:n,text:n,txt:n,extend:function(e,t){var n=r.util.clone(r.languages[e]);for(var a in t)n[a]=t[a];return n},insertBefore:function(e,t,n,a){var o=(a=a||r.languages)[e],i={};for(var l in o)if(o.hasOwnProperty(l)){if(l==t)for(var s in n)n.hasOwnProperty(s)&&(i[s]=n[s]);n.hasOwnProperty(l)||(i[l]=o[l])}var c=a[e];return a[e]=i,r.languages.DFS(r.languages,(function(t,n){n===c&&t!=e&&(this[t]=i)})),i},DFS:function e(t,n,a,o){o=o||{};var i=r.util.objId;for(var l in t)if(t.hasOwnProperty(l)){n.call(t,l,t[l],a||l);var s=t[l],c=r.util.type(s);"Object"!==c||o[i(s)]?"Array"!==c||o[i(s)]||(o[i(s)]=!0,e(s,n,l,o)):(o[i(s)]=!0,e(s,n,null,o))}}},plugins:{},highlight:function(e,t,n){var o={code:e,grammar:t,language:n};if(r.hooks.run("before-tokenize",o),!o.grammar)throw new Error('The language "'+o.language+'" has no grammar.');return o.tokens=r.tokenize(o.code,o.grammar),r.hooks.run("after-tokenize",o),a.stringify(r.util.encode(o.tokens),o.language)},tokenize:function(e,t){var n=t.rest;if(n){for(var r in n)t[r]=n[r];delete t.rest}var a=new l;return s(a,a.head,e),i(e,a,t,a.head,0),function(e){for(var t=[],n=e.head.next;n!==e.tail;)t.push(n.value),n=n.next;return t}(a)},hooks:{all:{},add:function(e,t){var n=r.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=r.hooks.all[e];if(n&&n.length)for(var a,o=0;a=n[o++];)a(t)}},Token:a};function a(e,t,n,r){this.type=e,this.content=t,this.alias=n,this.length=0|(r||"").length}function o(e,t,n,r){e.lastIndex=t;var a=e.exec(n);if(a&&r&&a[1]){var o=a[1].length;a.index+=o,a[0]=a[0].slice(o)}return a}function i(e,t,n,l,u,d){for(var p in n)if(n.hasOwnProperty(p)&&n[p]){var f=n[p];f=Array.isArray(f)?f:[f];for(var m=0;m<f.length;++m){if(d&&d.cause==p+","+m)return;var g=f[m],h=g.inside,y=!!g.lookbehind,b=!!g.greedy,v=g.alias;if(b&&!g.pattern.global){var w=g.pattern.toString().match(/[imsuy]*$/)[0];g.pattern=RegExp(g.pattern.source,w+"g")}for(var k=g.pattern||g,x=l.next,S=u;x!==t.tail&&!(d&&S>=d.reach);S+=x.value.length,x=x.next){var E=x.value;if(t.length>e.length)return;if(!(E instanceof a)){var C,_=1;if(b){if(!(C=o(k,S,e,y))||C.index>=e.length)break;var A=C.index,T=C.index+C[0].length,j=S;for(j+=x.value.length;A>=j;)j+=(x=x.next).value.length;if(S=j-=x.value.length,x.value instanceof a)continue;for(var L=x;L!==t.tail&&(j<T||"string"==typeof L.value);L=L.next)_++,j+=L.value.length;_--,E=e.slice(S,j),C.index-=S}else if(!(C=o(k,0,E,y)))continue;A=C.index;var N=C[0],R=E.slice(0,A),P=E.slice(A+N.length),O=S+E.length;d&&O>d.reach&&(d.reach=O);var D=x.prev;if(R&&(D=s(t,D,R),S+=R.length),c(t,D,_),x=s(t,D,new a(p,h?r.tokenize(N,h):N,v,N)),P&&s(t,x,P),_>1){var M={cause:p+","+m,reach:O};i(e,t,n,x.prev,S,M),d&&M.reach>d.reach&&(d.reach=M.reach)}}}}}}function l(){var e={value:null,prev:null,next:null},t={value:null,prev:e,next:null};e.next=t,this.head=e,this.tail=t,this.length=0}function s(e,t,n){var r=t.next,a={value:n,prev:t,next:r};return t.next=a,r.prev=a,e.length++,a}function c(e,t,n){for(var r=t.next,a=0;a<n&&r!==e.tail;a++)r=r.next;t.next=r,r.prev=t,e.length-=a}return a.stringify=function e(t,n){if("string"==typeof t)return t;if(Array.isArray(t)){var a="";return t.forEach((function(t){a+=e(t,n)})),a}var o={type:t.type,content:e(t.content,n),tag:"span",classes:["token",t.type],attributes:{},language:n},i=t.alias;i&&(Array.isArray(i)?Array.prototype.push.apply(o.classes,i):o.classes.push(i)),r.hooks.run("wrap",o);var l="";for(var s in o.attributes)l+=" "+s+'="'+(o.attributes[s]||"").replace(/"/g,""")+'"';return"<"+o.tag+' class="'+o.classes.join(" ")+'"'+l+">"+o.content+"</"+o.tag+">"},r}();t.exports=n,n.default=n}},function(){return a||(0,r[y(r)[0]])((a={exports:{}}).exports,a),a.exports}),A=((e,t,n)=>(n=null!=e?p(v(e)):{},((e,t,n,r)=>{if(t&&"object"==typeof t||"function"==typeof t)for(let a of y(t))w.call(e,a)||a===n||f(e,a,{get:()=>t[a],enumerable:!(r=g(t,a))||r.enumerable});return e})(!t&&e&&e.__esModule?n:f(n,"default",{value:e,enumerable:!0}),e)))(_());A.languages.markup={comment:{pattern:/<!--(?:(?!<!--)[\s\S])*?-->/,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^<!|>$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},A.languages.markup.tag.inside["attr-value"].inside.entity=A.languages.markup.entity,A.languages.markup.doctype.inside["internal-subset"].inside=A.languages.markup,A.hooks.add("wrap",(function(e){"entity"===e.type&&(e.attributes.title=e.content.replace(/&/,"&"))})),Object.defineProperty(A.languages.markup.tag,"addInlined",{value:function(e,t){var n;(t=((n=((n={})["language-"+t]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:A.languages[t]},n.cdata=/^<!\[CDATA\[|\]\]>$/i,{"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:n}}))["language-"+t]={pattern:/[\s\S]+/,inside:A.languages[t]},{}))[e]={pattern:RegExp(/(<__[^>]*>)(?:<!\[CDATA\[(?:[^\]]|\](?!\]>))*\]\]>|(?!<!\[CDATA\[)[\s\S])*?(?=<\/__>)/.source.replace(/__/g,(function(){return e})),"i"),lookbehind:!0,greedy:!0,inside:n},A.languages.insertBefore("markup","cdata",t)}}),Object.defineProperty(A.languages.markup.tag,"addAttribute",{value:function(e,t){A.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:A.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),A.languages.html=A.languages.markup,A.languages.mathml=A.languages.markup,A.languages.svg=A.languages.markup,A.languages.xml=A.languages.extend("markup",{}),A.languages.ssml=A.languages.xml,A.languages.atom=A.languages.xml,A.languages.rss=A.languages.xml,o=A,i={pattern:/\\[\\(){}[\]^$+*?|.]/,alias:"escape"},s="(?:[^\\\\-]|"+(l=/\\(?:x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]+\}|0[0-7]{0,2}|[123][0-7]{2}|c[a-zA-Z]|.)/).source+")",s=RegExp(s+"-"+s),c={pattern:/(<|')[^<>']+(?=[>']$)/,lookbehind:!0,alias:"variable"},o.languages.regex={"char-class":{pattern:/((?:^|[^\\])(?:\\\\)*)\[(?:[^\\\]]|\\[\s\S])*\]/,lookbehind:!0,inside:{"char-class-negation":{pattern:/(^\[)\^/,lookbehind:!0,alias:"operator"},"char-class-punctuation":{pattern:/^\[|\]$/,alias:"punctuation"},range:{pattern:s,inside:{escape:l,"range-punctuation":{pattern:/-/,alias:"operator"}}},"special-escape":i,"char-set":{pattern:/\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},escape:l}},"special-escape":i,"char-set":{pattern:/\.|\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},backreference:[{pattern:/\\(?![123][0-7]{2})[1-9]/,alias:"keyword"},{pattern:/\\k<[^<>']+>/,alias:"keyword",inside:{"group-name":c}}],anchor:{pattern:/[$^]|\\[ABbGZz]/,alias:"function"},escape:l,group:[{pattern:/\((?:\?(?:<[^<>']+>|'[^<>']+'|[>:]|<?[=!]|[idmnsuxU]+(?:-[idmnsuxU]+)?:?))?/,alias:"punctuation",inside:{"group-name":c}},{pattern:/\)/,alias:"punctuation"}],quantifier:{pattern:/(?:[+*?]|\{\d+(?:,\d*)?\})[?+]?/,alias:"number"},alternation:{pattern:/\|/,alias:"keyword"}},A.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/,boolean:/\b(?:false|true)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},A.languages.javascript=A.languages.extend("clike",{"class-name":[A.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),A.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,A.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source+/\//.source+"(?:"+/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source+"|"+/(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source+")"+/(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:A.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:A.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:A.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:A.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:A.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),A.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:A.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),A.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),A.languages.markup&&(A.languages.markup.tag.addInlined("script","javascript"),A.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),A.languages.js=A.languages.javascript,A.languages.actionscript=A.languages.extend("javascript",{keyword:/\b(?:as|break|case|catch|class|const|default|delete|do|dynamic|each|else|extends|final|finally|for|function|get|if|implements|import|in|include|instanceof|interface|internal|is|namespace|native|new|null|override|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|use|var|void|while|with)\b/,operator:/\+\+|--|(?:[+\-*\/%^]|&&?|\|\|?|<<?|>>?>?|[!=]=?)=?|[~?@]/}),A.languages.actionscript["class-name"].alias="function",delete A.languages.actionscript.parameter,delete A.languages.actionscript["literal-property"],A.languages.markup&&A.languages.insertBefore("actionscript","string",{xml:{pattern:/(^|[^.])<\/?\w+(?:\s+[^\s>\/=]+=("|')(?:\\[\s\S]|(?!\2)[^\\])*\2)*\s*\/?>/,lookbehind:!0,inside:A.languages.markup}}),function(e){var t=/#(?!\{).+/,n={pattern:/#\{[^}]+\}/,alias:"variable"};e.languages.coffeescript=e.languages.extend("javascript",{comment:t,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:n}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),e.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:t,interpolation:n}}}),e.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},script:{pattern:/[\s\S]+/,alias:"language-javascript",inside:e.languages.javascript}}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:n}}]}),e.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete e.languages.coffeescript["template-string"],e.languages.coffee=e.languages.coffeescript}(A),function(e){var t=e.languages.javadoclike={parameter:{pattern:/(^[\t ]*(?:\/{3}|\*|\/\*\*)\s*@(?:arg|arguments|param)\s+)\w+/m,lookbehind:!0},keyword:{pattern:/(^[\t ]*(?:\/{3}|\*|\/\*\*)\s*|\{)@[a-z][a-zA-Z-]+\b/m,lookbehind:!0},punctuation:/[{}]/};Object.defineProperty(t,"addSupport",{value:function(t,n){(t="string"==typeof t?[t]:t).forEach((function(t){var r=function(e){e.inside||(e.inside={}),e.inside.rest=n},a="doc-comment";if(o=e.languages[t]){var o,i=o[a];if((i=i||(o=e.languages.insertBefore(t,"comment",{"doc-comment":{pattern:/(^|[^\\])\/\*\*[^/][\s\S]*?(?:\*\/|$)/,lookbehind:!0,alias:"comment"}}))[a])instanceof RegExp&&(i=o[a]={pattern:i}),Array.isArray(i))for(var l=0,s=i.length;l<s;l++)i[l]instanceof RegExp&&(i[l]={pattern:i[l]}),r(i[l]);else r(i)}}))}}),t.addSupport(["java","javascript","php"],t)}(A),function(e){var t=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;(t=(e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:RegExp("@[\\w-](?:"+/[^;{\s"']|\s+(?!\s)/.source+"|"+t.source+")*?"+/(?:;|(?=\s*\{))/.source),inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|"+/(?:[^\\\r\n()"']|\\[\s\S])*/.source+")\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+t.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:t,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css,e.languages.markup))&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(A),function(e){var t=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,n=(t=(e.languages.css.selector={pattern:e.languages.css.selector.pattern,lookbehind:!0,inside:t={"pseudo-element":/:(?:after|before|first-letter|first-line|selection)|::[-\w]+/,"pseudo-class":/:[-\w]+/,class:/\.[-\w]+/,id:/#[-\w]+/,attribute:{pattern:RegExp("\\[(?:[^[\\]\"']|"+t.source+")*\\]"),greedy:!0,inside:{punctuation:/^\[|\]$/,"case-sensitivity":{pattern:/(\s)[si]$/i,lookbehind:!0,alias:"keyword"},namespace:{pattern:/^(\s*)(?:(?!\s)[-*\w\xA0-\uFFFF])*\|(?!=)/,lookbehind:!0,inside:{punctuation:/\|$/}},"attr-name":{pattern:/^(\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+/,lookbehind:!0},"attr-value":[t,{pattern:/(=\s*)(?:(?!\s)[-\w\xA0-\uFFFF])+(?=\s*$)/,lookbehind:!0}],operator:/[|~*^$]?=/}},"n-th":[{pattern:/(\(\s*)[+-]?\d*[\dn](?:\s*[+-]\s*\d+)?(?=\s*\))/,lookbehind:!0,inside:{number:/[\dn]+/,operator:/[+-]/}},{pattern:/(\(\s*)(?:even|odd)(?=\s*\))/i,lookbehind:!0}],combinator:/>|\+|~|\|\|/,punctuation:/[(),]/}},e.languages.css.atrule.inside["selector-function-argument"].inside=t,e.languages.insertBefore("css","property",{variable:{pattern:/(^|[^-\w\xA0-\uFFFF])--(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*/i,lookbehind:!0}}),{pattern:/(\b\d+)(?:%|[a-z]+(?![\w-]))/,lookbehind:!0}),{pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0});e.languages.insertBefore("css","function",{operator:{pattern:/(\s)[+\-*\/](?=\s)/,lookbehind:!0},hexcode:{pattern:/\B#[\da-f]{3,8}\b/i,alias:"color"},color:[{pattern:/(^|[^\w-])(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|RebeccaPurple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)(?![\w-])/i,lookbehind:!0},{pattern:/\b(?:hsl|rgb)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:hsl|rgb)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:t,number:n,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:t,number:n})}(A),function(e){var t=/[*&][^\s[\]{},]+/,n=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,r="(?:"+n.source+"(?:[ \t]+"+t.source+")?|"+t.source+"(?:[ \t]+"+n.source+")?)",a=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-]<PLAIN>)(?:[ \t]*(?:(?![#:])<PLAIN>|:<PLAIN>))*/.source.replace(/<PLAIN>/g,(function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source})),o=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function i(e,t){t=(t||"").replace(/m/g,"")+"m";var n=/([:\-,[{]\s*(?:\s<<prop>>[ \t]+)?)(?:<<value>>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<<prop>>/g,(function(){return r})).replace(/<<value>>/g,(function(){return e}));return RegExp(n,t)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<<prop>>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<<prop>>/g,(function(){return r}))),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<<prop>>[ \t]+)?)<<key>>(?=\s*:\s)/.source.replace(/<<prop>>/g,(function(){return r})).replace(/<<key>>/g,(function(){return"(?:"+a+"|"+o+")"}))),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:i(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:i(/false|true/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:i(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:i(o),lookbehind:!0,greedy:!0},number:{pattern:i(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:n,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml}(A),function(e){var t=/(?:\\.|[^\\\n\r]|(?:\n|\r\n?)(?![\r\n]))/.source;function n(e){return e=e.replace(/<inner>/g,(function(){return t})),RegExp(/((?:^|[^\\])(?:\\{2})*)/.source+"(?:"+e+")")}var r=/(?:\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\|\r\n`])+/.source,a=/\|?__(?:\|__)+\|?(?:(?:\n|\r\n?)|(?![\s\S]))/.source.replace(/__/g,(function(){return r})),o=/\|?[ \t]*:?-{3,}:?[ \t]*(?:\|[ \t]*:?-{3,}:?[ \t]*)+\|?(?:\n|\r\n?)/.source,i=(e.languages.markdown=e.languages.extend("markup",{}),e.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"front-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:e.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+a+o+"(?:"+a+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+a+o+")(?:"+a+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(r),inside:e.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+a+")"+o+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+a+"$"),inside:{"table-header":{pattern:RegExp(r),alias:"important",inside:e.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:n(/\b__(?:(?!_)<inner>|_(?:(?!_)<inner>)+_)+__\b|\*\*(?:(?!\*)<inner>|\*(?:(?!\*)<inner>)+\*)+\*\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:n(/\b_(?:(?!_)<inner>|__(?:(?!_)<inner>)+__)+_\b|\*(?:(?!\*)<inner>|\*\*(?:(?!\*)<inner>)+\*\*)+\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:n(/(~~?)(?:(?!~)<inner>)+\2/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:n(/!?\[(?:(?!\])<inner>)+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)|[ \t]?\[(?:(?!\])<inner>)+\])/.source),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach((function(t){["url","bold","italic","strike","code-snippet"].forEach((function(n){t!==n&&(e.languages.markdown[t].inside.content.inside[n]=e.languages.markdown[n])}))})),e.hooks.add("after-tokenize",(function(e){"markdown"!==e.language&&"md"!==e.language||function e(t){if(t&&"string"!=typeof t)for(var n=0,r=t.length;n<r;n++){var a,o=t[n];"code"!==o.type?e(o.content):(a=o.content[1],o=o.content[3],a&&o&&"code-language"===a.type&&"code-block"===o.type&&"string"==typeof a.content&&(a=a.content.replace(/\b#/g,"sharp").replace(/\b\+\+/g,"pp"),a="language-"+(a=(/[a-z][\w-]*/i.exec(a)||[""])[0].toLowerCase()),o.alias?"string"==typeof o.alias?o.alias=[o.alias,a]:o.alias.push(a):o.alias=[a]))}}(e.tokens)})),e.hooks.add("wrap",(function(t){if("code-block"===t.type){for(var n="",r=0,a=t.classes.length;r<a;r++){var o=t.classes[r];if(o=/language-(.+)/.exec(o)){n=o[1];break}}var c,u=e.languages[n];u?t.content=e.highlight(t.content.replace(i,"").replace(/&(\w{1,8}|#x?[\da-f]{1,8});/gi,(function(e,t){var n;return"#"===(t=t.toLowerCase())[0]?(n="x"===t[1]?parseInt(t.slice(2),16):Number(t.slice(1)),s(n)):l[t]||e})),u,n):n&&"none"!==n&&e.plugins.autoloader&&(c="md-"+(new Date).valueOf()+"-"+Math.floor(1e16*Math.random()),t.attributes.id=c,e.plugins.autoloader.loadLanguages(n,(function(){var t=document.getElementById(c);t&&(t.innerHTML=e.highlight(t.textContent,e.languages[n],n))})))}})),RegExp(e.languages.markup.tag.pattern.source,"gi")),l={amp:"&",lt:"<",gt:">",quot:'"'},s=String.fromCodePoint||String.fromCharCode;e.languages.md=e.languages.markdown}(A),A.languages.graphql={comment:/#.*/,description:{pattern:/(?:"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*")(?=\s*[a-z_])/i,greedy:!0,alias:"string",inside:{"language-markdown":{pattern:/(^"(?:"")?)(?!\1)[\s\S]+(?=\1$)/,lookbehind:!0,inside:A.languages.markdown}}},string:{pattern:/"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,boolean:/\b(?:false|true)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":{pattern:/\b[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i,greedy:!0},"atom-input":{pattern:/\b[A-Z]\w*Input\b/,alias:"class-name"},scalar:/\b(?:Boolean|Float|ID|Int|String)\b/,constant:/\b[A-Z][A-Z_\d]*\b/,"class-name":{pattern:/(\b(?:enum|implements|interface|on|scalar|type|union)\s+|&\s*|:\s*|\[)[A-Z_]\w*/,lookbehind:!0},fragment:{pattern:/(\bfragment\s+|\.{3}\s*(?!on\b))[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-mutation":{pattern:/(\bmutation\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-query":{pattern:/(\bquery\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},keyword:/\b(?:directive|enum|extend|fragment|implements|input|interface|mutation|on|query|repeatable|scalar|schema|subscription|type|union)\b/,operator:/[!=|&]|\.{3}/,"property-query":/\w+(?=\s*\()/,object:/\w+(?=\s*\{)/,punctuation:/[!(){}\[\]:=,]/,property:/\w+/},A.hooks.add("after-tokenize",(function(e){if("graphql"===e.language)for(var t=e.tokens.filter((function(e){return"string"!=typeof e&&"comment"!==e.type&&"scalar"!==e.type})),n=0;n<t.length;){var r=t[n++];if("keyword"===r.type&&"mutation"===r.content){var a=[];if(d(["definition-mutation","punctuation"])&&"("===u(1).content){n+=2;var o=p(/^\($/,/^\)$/);if(-1===o)continue;for(;n<o;n++){var i=u(0);"variable"===i.type&&(f(i,"variable-input"),a.push(i.content))}n=o+1}if(d(["punctuation","property-query"])&&"{"===u(0).content&&(n++,f(u(0),"property-mutation"),0<a.length)){var l=p(/^\{$/,/^\}$/);if(-1!==l)for(var s=n;s<l;s++){var c=t[s];"variable"===c.type&&0<=a.indexOf(c.content)&&f(c,"variable-input")}}}}function u(e){return t[n+e]}function d(e,t){t=t||0;for(var n=0;n<e.length;n++){var r=u(n+t);if(!r||r.type!==e[n])return}return 1}function p(e,r){for(var a=1,o=n;o<t.length;o++){var i=t[o],l=i.content;if("punctuation"===i.type&&"string"==typeof l)if(e.test(l))a++;else if(r.test(l)&&0==--a)return o}return-1}function f(e,t){var n=e.alias;n?Array.isArray(n)||(e.alias=n=[n]):e.alias=n=[],n.push(t)}})),A.languages.sql={comment:{pattern:/(^|[^\\])(?:\/\*[\s\S]*?\*\/|(?:--|\/\/|#).*)/,lookbehind:!0},variable:[{pattern:/@(["'`])(?:\\[\s\S]|(?!\1)[^\\])+\1/,greedy:!0},/@[\w.$]+/],string:{pattern:/(^|[^@\\])("|')(?:\\[\s\S]|(?!\2)[^\\]|\2\2)*\2/,greedy:!0,lookbehind:!0},identifier:{pattern:/(^|[^@\\])`(?:\\[\s\S]|[^`\\]|``)*`/,greedy:!0,lookbehind:!0,inside:{punctuation:/^`|`$/}},function:/\b(?:AVG|COUNT|FIRST|FORMAT|LAST|LCASE|LEN|MAX|MID|MIN|MOD|NOW|ROUND|SUM|UCASE)(?=\s*\()/i,keyword:/\b(?:ACTION|ADD|AFTER|ALGORITHM|ALL|ALTER|ANALYZE|ANY|APPLY|AS|ASC|AUTHORIZATION|AUTO_INCREMENT|BACKUP|BDB|BEGIN|BERKELEYDB|BIGINT|BINARY|BIT|BLOB|BOOL|BOOLEAN|BREAK|BROWSE|BTREE|BULK|BY|CALL|CASCADED?|CASE|CHAIN|CHAR(?:ACTER|SET)?|CHECK(?:POINT)?|CLOSE|CLUSTERED|COALESCE|COLLATE|COLUMNS?|COMMENT|COMMIT(?:TED)?|COMPUTE|CONNECT|CONSISTENT|CONSTRAINT|CONTAINS(?:TABLE)?|CONTINUE|CONVERT|CREATE|CROSS|CURRENT(?:_DATE|_TIME|_TIMESTAMP|_USER)?|CURSOR|CYCLE|DATA(?:BASES?)?|DATE(?:TIME)?|DAY|DBCC|DEALLOCATE|DEC|DECIMAL|DECLARE|DEFAULT|DEFINER|DELAYED|DELETE|DELIMITERS?|DENY|DESC|DESCRIBE|DETERMINISTIC|DISABLE|DISCARD|DISK|DISTINCT|DISTINCTROW|DISTRIBUTED|DO|DOUBLE|DROP|DUMMY|DUMP(?:FILE)?|DUPLICATE|ELSE(?:IF)?|ENABLE|ENCLOSED|END|ENGINE|ENUM|ERRLVL|ERRORS|ESCAPED?|EXCEPT|EXEC(?:UTE)?|EXISTS|EXIT|EXPLAIN|EXTENDED|FETCH|FIELDS|FILE|FILLFACTOR|FIRST|FIXED|FLOAT|FOLLOWING|FOR(?: EACH ROW)?|FORCE|FOREIGN|FREETEXT(?:TABLE)?|FROM|FULL|FUNCTION|GEOMETRY(?:COLLECTION)?|GLOBAL|GOTO|GRANT|GROUP|HANDLER|HASH|HAVING|HOLDLOCK|HOUR|IDENTITY(?:COL|_INSERT)?|IF|IGNORE|IMPORT|INDEX|INFILE|INNER|INNODB|INOUT|INSERT|INT|INTEGER|INTERSECT|INTERVAL|INTO|INVOKER|ISOLATION|ITERATE|JOIN|KEYS?|KILL|LANGUAGE|LAST|LEAVE|LEFT|LEVEL|LIMIT|LINENO|LINES|LINESTRING|LOAD|LOCAL|LOCK|LONG(?:BLOB|TEXT)|LOOP|MATCH(?:ED)?|MEDIUM(?:BLOB|INT|TEXT)|MERGE|MIDDLEINT|MINUTE|MODE|MODIFIES|MODIFY|MONTH|MULTI(?:LINESTRING|POINT|POLYGON)|NATIONAL|NATURAL|NCHAR|NEXT|NO|NONCLUSTERED|NULLIF|NUMERIC|OFF?|OFFSETS?|ON|OPEN(?:DATASOURCE|QUERY|ROWSET)?|OPTIMIZE|OPTION(?:ALLY)?|ORDER|OUT(?:ER|FILE)?|OVER|PARTIAL|PARTITION|PERCENT|PIVOT|PLAN|POINT|POLYGON|PRECEDING|PRECISION|PREPARE|PREV|PRIMARY|PRINT|PRIVILEGES|PROC(?:EDURE)?|PUBLIC|PURGE|QUICK|RAISERROR|READS?|REAL|RECONFIGURE|REFERENCES|RELEASE|RENAME|REPEAT(?:ABLE)?|REPLACE|REPLICATION|REQUIRE|RESIGNAL|RESTORE|RESTRICT|RETURN(?:ING|S)?|REVOKE|RIGHT|ROLLBACK|ROUTINE|ROW(?:COUNT|GUIDCOL|S)?|RTREE|RULE|SAVE(?:POINT)?|SCHEMA|SECOND|SELECT|SERIAL(?:IZABLE)?|SESSION(?:_USER)?|SET(?:USER)?|SHARE|SHOW|SHUTDOWN|SIMPLE|SMALLINT|SNAPSHOT|SOME|SONAME|SQL|START(?:ING)?|STATISTICS|STATUS|STRIPED|SYSTEM_USER|TABLES?|TABLESPACE|TEMP(?:ORARY|TABLE)?|TERMINATED|TEXT(?:SIZE)?|THEN|TIME(?:STAMP)?|TINY(?:BLOB|INT|TEXT)|TOP?|TRAN(?:SACTIONS?)?|TRIGGER|TRUNCATE|TSEQUAL|TYPES?|UNBOUNDED|UNCOMMITTED|UNDEFINED|UNION|UNIQUE|UNLOCK|UNPIVOT|UNSIGNED|UPDATE(?:TEXT)?|USAGE|USE|USER|USING|VALUES?|VAR(?:BINARY|CHAR|CHARACTER|YING)|VIEW|WAITFOR|WARNINGS|WHEN|WHERE|WHILE|WITH(?: ROLLUP|IN)?|WORK|WRITE(?:TEXT)?|YEAR)\b/i,boolean:/\b(?:FALSE|NULL|TRUE)\b/i,number:/\b0x[\da-f]+\b|\b\d+(?:\.\d*)?|\B\.\d+\b/i,operator:/[-+*\/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/},function(e){var t=e.languages.javascript["template-string"],n=t.pattern.source,r=t.inside.interpolation,a=r.inside["interpolation-punctuation"],o=r.pattern.source;function i(t,r){if(e.languages[t])return{pattern:RegExp("((?:"+r+")\\s*)"+n),lookbehind:!0,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},"embedded-code":{pattern:/[\s\S]+/,alias:t}}}}function l(t,n,r){return t={code:t,grammar:n,language:r},e.hooks.run("before-tokenize",t),t.tokens=e.tokenize(t.code,t.grammar),e.hooks.run("after-tokenize",t),t.tokens}function s(t,n,i){var s=e.tokenize(t,{interpolation:{pattern:RegExp(o),lookbehind:!0}}),c=0,u={},d=(s=l(s.map((function(e){if("string"==typeof e)return e;var n,r;for(e=e.content;-1!==t.indexOf((r=c++,n="___"+i.toUpperCase()+"_"+r+"___")););return u[n]=e,n})).join(""),n,i),Object.keys(u));return c=0,function t(n){for(var o=0;o<n.length;o++){if(c>=d.length)return;var i,s,p,f,m,g,h,y=n[o];"string"==typeof y||"string"==typeof y.content?(i=d[c],-1!==(h=(g="string"==typeof y?y:y.content).indexOf(i))&&(++c,s=g.substring(0,h),m=u[i],p=void 0,(f={})["interpolation-punctuation"]=a,3===(f=e.tokenize(m,f)).length&&((p=[1,1]).push.apply(p,l(f[1],e.languages.javascript,"javascript")),f.splice.apply(f,p)),p=new e.Token("interpolation",f,r.alias,m),f=g.substring(h+i.length),m=[],s&&m.push(s),m.push(p),f&&(t(g=[f]),m.push.apply(m,g)),"string"==typeof y?(n.splice.apply(n,[o,1].concat(m)),o+=m.length-1):y.content=m)):(h=y.content,Array.isArray(h)?t(h):t([h]))}}(s),new e.Token(i,s,"language-"+i,t)}e.languages.javascript["template-string"]=[i("css",/\b(?:styled(?:\([^)]*\))?(?:\s*\.\s*\w+(?:\([^)]*\))*)*|css(?:\s*\.\s*(?:global|resolve))?|createGlobalStyle|keyframes)/.source),i("html",/\bhtml|\.\s*(?:inner|outer)HTML\s*\+?=/.source),i("svg",/\bsvg/.source),i("markdown",/\b(?:markdown|md)/.source),i("graphql",/\b(?:gql|graphql(?:\s*\.\s*experimental)?)/.source),i("sql",/\bsql/.source),t].filter(Boolean);var c={javascript:!0,js:!0,typescript:!0,ts:!0,jsx:!0,tsx:!0};function u(e){return"string"==typeof e?e:Array.isArray(e)?e.map(u).join(""):u(e.content)}e.hooks.add("after-tokenize",(function(t){t.language in c&&function t(n){for(var r=0,a=n.length;r<a;r++){var o,i,l,c=n[r];"string"!=typeof c&&(o=c.content,Array.isArray(o)?"template-string"===c.type?(c=o[1],3===o.length&&"string"!=typeof c&&"embedded-code"===c.type&&(i=u(c),c=c.alias,c=Array.isArray(c)?c[0]:c,l=e.languages[c])&&(o[1]=s(i,l,c))):t(o):"string"!=typeof o&&t([o]))}}(t.tokens)}))}(A),function(e){e.languages.typescript=e.languages.extend("javascript",{"class-name":{pattern:/(\b(?:class|extends|implements|instanceof|interface|new|type)\s+)(?!keyof\b)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?:\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete e.languages.typescript.parameter,delete e.languages.typescript["literal-property"];var t=e.languages.extend("typescript",{});delete t["class-name"],e.languages.typescript["class-name"].inside=t,e.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:t}}}}),e.languages.ts=e.languages.typescript}(A),function(e){var t=e.languages.javascript,n=/\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})+\}/.source,r="(@(?:arg|argument|param|property)\\s+(?:"+n+"\\s+)?)";e.languages.jsdoc=e.languages.extend("javadoclike",{parameter:{pattern:RegExp(r+/(?:(?!\s)[$\w\xA0-\uFFFF.])+(?=\s|$)/.source),lookbehind:!0,inside:{punctuation:/\./}}}),e.languages.insertBefore("jsdoc","keyword",{"optional-parameter":{pattern:RegExp(r+/\[(?:(?!\s)[$\w\xA0-\uFFFF.])+(?:=[^[\]]+)?\](?=\s|$)/.source),lookbehind:!0,inside:{parameter:{pattern:/(^\[)[$\w\xA0-\uFFFF\.]+/,lookbehind:!0,inside:{punctuation:/\./}},code:{pattern:/(=)[\s\S]*(?=\]$)/,lookbehind:!0,inside:t,alias:"language-javascript"},punctuation:/[=[\]]/}},"class-name":[{pattern:RegExp(/(@(?:augments|class|extends|interface|memberof!?|template|this|typedef)\s+(?:<TYPE>\s+)?)[A-Z]\w*(?:\.[A-Z]\w*)*/.source.replace(/<TYPE>/g,(function(){return n}))),lookbehind:!0,inside:{punctuation:/\./}},{pattern:RegExp("(@[a-z]+\\s+)"+n),lookbehind:!0,inside:{string:t.string,number:t.number,boolean:t.boolean,keyword:e.languages.typescript.keyword,operator:/=>|\.\.\.|[&|?:*]/,punctuation:/[.,;=<>{}()[\]]/}}],example:{pattern:/(@example\s+(?!\s))(?:[^@\s]|\s+(?!\s))+?(?=\s*(?:\*\s*)?(?:@\w|\*\/))/,lookbehind:!0,inside:{code:{pattern:/^([\t ]*(?:\*\s*)?)\S.*$/m,lookbehind:!0,inside:t,alias:"language-javascript"}}}}),e.languages.javadoclike.addSupport("javascript",e.languages.jsdoc)}(A),function(e){e.languages.flow=e.languages.extend("javascript",{}),e.languages.insertBefore("flow","keyword",{type:[{pattern:/\b(?:[Bb]oolean|Function|[Nn]umber|[Ss]tring|[Ss]ymbol|any|mixed|null|void)\b/,alias:"class-name"}]}),e.languages.flow["function-variable"].pattern=/(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=\s*(?:function\b|(?:\([^()]*\)(?:\s*:\s*\w+)?|(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/i,delete e.languages.flow.parameter,e.languages.insertBefore("flow","operator",{"flow-punctuation":{pattern:/\{\||\|\}/,alias:"punctuation"}}),Array.isArray(e.languages.flow.keyword)||(e.languages.flow.keyword=[e.languages.flow.keyword]),e.languages.flow.keyword.unshift({pattern:/(^|[^$]\b)(?:Class|declare|opaque|type)\b(?!\$)/,lookbehind:!0},{pattern:/(^|[^$]\B)\$(?:Diff|Enum|Exact|Keys|ObjMap|PropertyType|Record|Shape|Subtype|Supertype|await)\b(?!\$)/,lookbehind:!0})}(A),A.languages.n4js=A.languages.extend("javascript",{keyword:/\b(?:Array|any|boolean|break|case|catch|class|const|constructor|continue|debugger|declare|default|delete|do|else|enum|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|module|new|null|number|package|private|protected|public|return|set|static|string|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/}),A.languages.insertBefore("n4js","constant",{annotation:{pattern:/@+\w+/,alias:"operator"}}),A.languages.n4jsd=A.languages.n4js,function(e){function t(e,t){return RegExp(e.replace(/<ID>/g,(function(){return/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/.source})),t)}e.languages.insertBefore("javascript","function-variable",{"method-variable":{pattern:RegExp("(\\.\\s*)"+e.languages.javascript["function-variable"].pattern.source),lookbehind:!0,alias:["function-variable","method","function","property-access"]}}),e.languages.insertBefore("javascript","function",{method:{pattern:RegExp("(\\.\\s*)"+e.languages.javascript.function.source),lookbehind:!0,alias:["function","property-access"]}}),e.languages.insertBefore("javascript","constant",{"known-class-name":[{pattern:/\b(?:(?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)?Array|ArrayBuffer|BigInt|Boolean|DataView|Date|Error|Function|Intl|JSON|(?:Weak)?(?:Map|Set)|Math|Number|Object|Promise|Proxy|Reflect|RegExp|String|Symbol|WebAssembly)\b/,alias:"class-name"},{pattern:/\b(?:[A-Z]\w*)Error\b/,alias:"class-name"}]}),e.languages.insertBefore("javascript","keyword",{imports:{pattern:t(/(\bimport\b\s*)(?:<ID>(?:\s*,\s*(?:\*\s*as\s+<ID>|\{[^{}]*\}))?|\*\s*as\s+<ID>|\{[^{}]*\})(?=\s*\bfrom\b)/.source),lookbehind:!0,inside:e.languages.javascript},exports:{pattern:t(/(\bexport\b\s*)(?:\*(?:\s*as\s+<ID>)?(?=\s*\bfrom\b)|\{[^{}]*\})/.source),lookbehind:!0,inside:e.languages.javascript}}),e.languages.javascript.keyword.unshift({pattern:/\b(?:as|default|export|from|import)\b/,alias:"module"},{pattern:/\b(?:await|break|catch|continue|do|else|finally|for|if|return|switch|throw|try|while|yield)\b/,alias:"control-flow"},{pattern:/\bnull\b/,alias:["null","nil"]},{pattern:/\bundefined\b/,alias:"nil"}),e.languages.insertBefore("javascript","operator",{spread:{pattern:/\.{3}/,alias:"operator"},arrow:{pattern:/=>/,alias:"operator"}}),e.languages.insertBefore("javascript","punctuation",{"property-access":{pattern:t(/(\.\s*)#?<ID>/.source),lookbehind:!0},"maybe-class-name":{pattern:/(^|[^$\w\xA0-\uFFFF])[A-Z][$\w\xA0-\uFFFF]+/,lookbehind:!0},dom:{pattern:/\b(?:document|(?:local|session)Storage|location|navigator|performance|window)\b/,alias:"variable"},console:{pattern:/\bconsole(?=\s*\.)/,alias:"class-name"}});for(var n=["function","function-variable","method","method-variable","property-access"],r=0;r<n.length;r++){var a=n[r],o=e.languages.javascript[a];a=(o="RegExp"===e.util.type(o)?e.languages.javascript[a]={pattern:o}:o).inside||{};(o.inside=a)["maybe-class-name"]=/^[A-Z][\s\S]*/}}(A),function(e){var t=e.util.clone(e.languages.javascript),n=/(?:\s|\/\/.*(?!.)|\/\*(?:[^*]|\*(?!\/))\*\/)/.source,r=/(?:\{(?:\{(?:\{[^{}]*\}|[^{}])*\}|[^{}])*\})/.source,a=/(?:\{<S>*\.{3}(?:[^{}]|<BRACES>)*\})/.source;function o(e,t){return e=e.replace(/<S>/g,(function(){return n})).replace(/<BRACES>/g,(function(){return r})).replace(/<SPREAD>/g,(function(){return a})),RegExp(e,t)}function i(t){for(var n=[],r=0;r<t.length;r++){var a=t[r],o=!1;"string"!=typeof a&&("tag"===a.type&&a.content[0]&&"tag"===a.content[0].type?"</"===a.content[0].content[0].content?0<n.length&&n[n.length-1].tagName===l(a.content[0].content[1])&&n.pop():"/>"!==a.content[a.content.length-1].content&&n.push({tagName:l(a.content[0].content[1]),openedBraces:0}):0<n.length&&"punctuation"===a.type&&"{"===a.content?n[n.length-1].openedBraces++:0<n.length&&0<n[n.length-1].openedBraces&&"punctuation"===a.type&&"}"===a.content?n[n.length-1].openedBraces--:o=!0),(o||"string"==typeof a)&&0<n.length&&0===n[n.length-1].openedBraces&&(o=l(a),r<t.length-1&&("string"==typeof t[r+1]||"plain-text"===t[r+1].type)&&(o+=l(t[r+1]),t.splice(r+1,1)),0<r&&("string"==typeof t[r-1]||"plain-text"===t[r-1].type)&&(o=l(t[r-1])+o,t.splice(r-1,1),r--),t[r]=new e.Token("plain-text",o,null,o)),a.content&&"string"!=typeof a.content&&i(a.content)}}a=o(a).source,e.languages.jsx=e.languages.extend("markup",t),e.languages.jsx.tag.pattern=o(/<\/?(?:[\w.:-]+(?:<S>+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|<BRACES>))?|<SPREAD>))*<S>*\/?)?>/.source),e.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,e.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,e.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,e.languages.jsx.tag.inside.comment=t.comment,e.languages.insertBefore("inside","attr-name",{spread:{pattern:o(/<SPREAD>/.source),inside:e.languages.jsx}},e.languages.jsx.tag),e.languages.insertBefore("inside","special-attr",{script:{pattern:o(/=<BRACES>/.source),alias:"language-javascript",inside:{"script-punctuation":{pattern:/^=(?=\{)/,alias:"punctuation"},rest:e.languages.jsx}}},e.languages.jsx.tag);var l=function(e){return e?"string"==typeof e?e:"string"==typeof e.content?e.content:e.content.map(l).join(""):""};e.hooks.add("after-tokenize",(function(e){"jsx"!==e.language&&"tsx"!==e.language||i(e.tokens)}))}(A),function(e){var t=e.util.clone(e.languages.typescript);(t=(e.languages.tsx=e.languages.extend("jsx",t),delete e.languages.tsx.parameter,delete e.languages.tsx["literal-property"],e.languages.tsx.tag)).pattern=RegExp(/(^|[^\w$]|(?=<\/))/.source+"(?:"+t.pattern.source+")",t.pattern.flags),t.lookbehind=!0}(A),A.languages.swift={comment:{pattern:/(^|[^\\:])(?:\/\/.*|\/\*(?:[^/*]|\/(?!\*)|\*(?!\/)|\/\*(?:[^*]|\*(?!\/))*\*\/)*\*\/)/,lookbehind:!0,greedy:!0},"string-literal":[{pattern:RegExp(/(^|[^"#])/.source+"(?:"+/"(?:\\(?:\((?:[^()]|\([^()]*\))*\)|\r\n|[^(])|[^\\\r\n"])*"/.source+"|"+/"""(?:\\(?:\((?:[^()]|\([^()]*\))*\)|[^(])|[^\\"]|"(?!""))*"""/.source+")"+/(?!["#])/.source),lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/(\\\()(?:[^()]|\([^()]*\))*(?=\))/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/^\)|\\\($/,alias:"punctuation"},punctuation:/\\(?=[\r\n])/,string:/[\s\S]+/}},{pattern:RegExp(/(^|[^"#])(#+)/.source+"(?:"+/"(?:\\(?:#+\((?:[^()]|\([^()]*\))*\)|\r\n|[^#])|[^\\\r\n])*?"/.source+"|"+/"""(?:\\(?:#+\((?:[^()]|\([^()]*\))*\)|[^#])|[^\\])*?"""/.source+")\\2"),lookbehind:!0,greedy:!0,inside:{interpolation:{pattern:/(\\#+\()(?:[^()]|\([^()]*\))*(?=\))/,lookbehind:!0,inside:null},"interpolation-punctuation":{pattern:/^\)|\\#+\($/,alias:"punctuation"},string:/[\s\S]+/}}],directive:{pattern:RegExp(/#/.source+"(?:"+/(?:elseif|if)\b/.source+"(?:[ \t]*"+/(?:![ \t]*)?(?:\b\w+\b(?:[ \t]*\((?:[^()]|\([^()]*\))*\))?|\((?:[^()]|\([^()]*\))*\))(?:[ \t]*(?:&&|\|\|))?/.source+")+|"+/(?:else|endif)\b/.source+")"),alias:"property",inside:{"directive-name":/^#\w+/,boolean:/\b(?:false|true)\b/,number:/\b\d+(?:\.\d+)*\b/,operator:/!|&&|\|\||[<>]=?/,punctuation:/[(),]/}},literal:{pattern:/#(?:colorLiteral|column|dsohandle|file(?:ID|Literal|Path)?|function|imageLiteral|line)\b/,alias:"constant"},"other-directive":{pattern:/#\w+\b/,alias:"property"},attribute:{pattern:/@\w+/,alias:"atrule"},"function-definition":{pattern:/(\bfunc\s+)\w+/,lookbehind:!0,alias:"function"},label:{pattern:/\b(break|continue)\s+\w+|\b[a-zA-Z_]\w*(?=\s*:\s*(?:for|repeat|while)\b)/,lookbehind:!0,alias:"important"},keyword:/\b(?:Any|Protocol|Self|Type|actor|as|assignment|associatedtype|associativity|async|await|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic|else|enum|extension|fallthrough|fileprivate|final|for|func|get|guard|higherThan|if|import|in|indirect|infix|init|inout|internal|is|isolated|lazy|left|let|lowerThan|mutating|none|nonisolated|nonmutating|open|operator|optional|override|postfix|precedencegroup|prefix|private|protocol|public|repeat|required|rethrows|return|right|safe|self|set|some|static|struct|subscript|super|switch|throw|throws|try|typealias|unowned|unsafe|var|weak|where|while|willSet)\b/,boolean:/\b(?:false|true)\b/,nil:{pattern:/\bnil\b/,alias:"constant"},"short-argument":/\$\d+\b/,omit:{pattern:/\b_\b/,alias:"keyword"},number:/\b(?:[\d_]+(?:\.[\de_]+)?|0x[a-f0-9_]+(?:\.[a-f0-9p_]+)?|0b[01_]+|0o[0-7_]+)\b/i,"class-name":/\b[A-Z](?:[A-Z_\d]*[a-z]\w*)?\b/,function:/\b[a-z_]\w*(?=\s*\()/i,constant:/\b(?:[A-Z_]{2,}|k[A-Z][A-Za-z_]+)\b/,operator:/[-+*/%=!<>&|^~?]+|\.[.\-+*/%=!<>&|^~?]+/,punctuation:/[{}[\]();,.:\\]/},A.languages.swift["string-literal"].forEach((function(e){e.inside.interpolation.inside=A.languages.swift})),function(e){e.languages.kotlin=e.languages.extend("clike",{keyword:{pattern:/(^|[^.])\b(?:abstract|actual|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|dynamic|else|enum|expect|external|final|finally|for|fun|get|if|import|in|infix|init|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|operator|out|override|package|private|protected|public|reified|return|sealed|set|super|suspend|tailrec|this|throw|to|try|typealias|val|var|vararg|when|where|while)\b/,lookbehind:!0},function:[{pattern:/(?:`[^\r\n`]+`|\b\w+)(?=\s*\()/,greedy:!0},{pattern:/(\.)(?:`[^\r\n`]+`|\w+)(?=\s*\{)/,lookbehind:!0,greedy:!0}],number:/\b(?:0[xX][\da-fA-F]+(?:_[\da-fA-F]+)*|0[bB][01]+(?:_[01]+)*|\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:[eE][+-]?\d+(?:_\d+)*)?[fFL]?)\b/,operator:/\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\/*%<>]=?|[?:]:?|\.\.|&&|\|\||\b(?:and|inv|or|shl|shr|ushr|xor)\b/}),delete e.languages.kotlin["class-name"];var t={"interpolation-punctuation":{pattern:/^\$\{?|\}$/,alias:"punctuation"},expression:{pattern:/[\s\S]+/,inside:e.languages.kotlin}};e.languages.insertBefore("kotlin","string",{"string-literal":[{pattern:/"""(?:[^$]|\$(?:(?!\{)|\{[^{}]*\}))*?"""/,alias:"multiline",inside:{interpolation:{pattern:/\$(?:[a-z_]\w*|\{[^{}]*\})/i,inside:t},string:/[\s\S]+/}},{pattern:/"(?:[^"\\\r\n$]|\\.|\$(?:(?!\{)|\{[^{}]*\}))*"/,alias:"singleline",inside:{interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$(?:[a-z_]\w*|\{[^{}]*\})/i,lookbehind:!0,inside:t},string:/[\s\S]+/}}],char:{pattern:/'(?:[^'\\\r\n]|\\(?:.|u[a-fA-F0-9]{0,4}))'/,greedy:!0}}),delete e.languages.kotlin.string,e.languages.insertBefore("kotlin","keyword",{annotation:{pattern:/\B@(?:\w+:)?(?:[A-Z]\w*|\[[^\]]+\])/,alias:"builtin"}}),e.languages.insertBefore("kotlin","function",{label:{pattern:/\b\w+@|@\w+\b/,alias:"symbol"}}),e.languages.kt=e.languages.kotlin,e.languages.kts=e.languages.kotlin}(A),A.languages.c=A.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),A.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),A.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},A.languages.c.string],char:A.languages.c.char,comment:A.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:A.languages.c}}}}),A.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete A.languages.c.boolean,A.languages.objectivec=A.languages.extend("c",{string:{pattern:/@?"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},keyword:/\b(?:asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|in|inline|int|long|register|return|self|short|signed|sizeof|static|struct|super|switch|typedef|typeof|union|unsigned|void|volatile|while)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,operator:/-[->]?|\+\+?|!=?|<<?=?|>>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete A.languages.objectivec["class-name"],A.languages.objc=A.languages.objectivec,A.languages.reason=A.languages.extend("clike",{string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^\\\r\n"])*"/,greedy:!0},"class-name":/\b[A-Z]\w*/,keyword:/\b(?:and|as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|method|module|mutable|new|nonrec|object|of|open|or|private|rec|sig|struct|switch|then|to|try|type|val|virtual|when|while|with)\b/,operator:/\.{3}|:[:=]|\|>|->|=(?:==?|>)?|<=?|>=?|[|^?'#!~`]|[+\-*\/]\.?|\b(?:asr|land|lor|lsl|lsr|lxor|mod)\b/}),A.languages.insertBefore("reason","class-name",{char:{pattern:/'(?:\\x[\da-f]{2}|\\o[0-3][0-7][0-7]|\\\d{3}|\\.|[^'\\\r\n])'/,greedy:!0},constructor:/\b[A-Z]\w*\b(?!\s*\.)/,label:{pattern:/\b[a-z]\w*(?=::)/,alias:"symbol"}}),delete A.languages.reason.function,function(e){for(var t=/\/\*(?:[^*/]|\*(?!\/)|\/(?!\*)|<self>)*\*\//.source,n=0;n<2;n++)t=t.replace(/<self>/g,(function(){return t}));t=t.replace(/<self>/g,(function(){return/[^\s\S]/.source})),e.languages.rust={comment:[{pattern:RegExp(/(^|[^\\])/.source+t),lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/b?"(?:\\[\s\S]|[^\\"])*"|b?r(#*)"(?:[^"]|"(?!\1))*"\1/,greedy:!0},char:{pattern:/b?'(?:\\(?:x[0-7][\da-fA-F]|u\{(?:[\da-fA-F]_*){1,6}\}|.)|[^\\\r\n\t'])'/,greedy:!0},attribute:{pattern:/#!?\[(?:[^\[\]"]|"(?:\\[\s\S]|[^\\"])*")*\]/,greedy:!0,alias:"attr-name",inside:{string:null}},"closure-params":{pattern:/([=(,:]\s*|\bmove\s*)\|[^|]*\||\|[^|]*\|(?=\s*(?:\{|->))/,lookbehind:!0,greedy:!0,inside:{"closure-punctuation":{pattern:/^\||\|$/,alias:"punctuation"},rest:null}},"lifetime-annotation":{pattern:/'\w+/,alias:"symbol"},"fragment-specifier":{pattern:/(\$\w+:)[a-z]+/,lookbehind:!0,alias:"punctuation"},variable:/\$\w+/,"function-definition":{pattern:/(\bfn\s+)\w+/,lookbehind:!0,alias:"function"},"type-definition":{pattern:/(\b(?:enum|struct|trait|type|union)\s+)\w+/,lookbehind:!0,alias:"class-name"},"module-declaration":[{pattern:/(\b(?:crate|mod)\s+)[a-z][a-z_\d]*/,lookbehind:!0,alias:"namespace"},{pattern:/(\b(?:crate|self|super)\s*)::\s*[a-z][a-z_\d]*\b(?:\s*::(?:\s*[a-z][a-z_\d]*\s*::)*)?/,lookbehind:!0,alias:"namespace",inside:{punctuation:/::/}}],keyword:[/\b(?:Self|abstract|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref|return|self|static|struct|super|trait|try|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/,/\b(?:bool|char|f(?:32|64)|[ui](?:8|16|32|64|128|size)|str)\b/],function:/\b[a-z_]\w*(?=\s*(?:::\s*<|\())/,macro:{pattern:/\b\w+!/,alias:"property"},constant:/\b[A-Z_][A-Z_\d]+\b/,"class-name":/\b[A-Z]\w*\b/,namespace:{pattern:/(?:\b[a-z][a-z_\d]*\s*::\s*)*\b[a-z][a-z_\d]*\s*::(?!\s*<)/,inside:{punctuation:/::/}},number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:f32|f64|[iu](?:8|16|32|64|size)?))?\b/,boolean:/\b(?:false|true)\b/,punctuation:/->|\.\.=|\.{1,3}|::|[{}[\];(),:]/,operator:/[-+*\/%!^]=?|=[=>]?|&[&=]?|\|[|=]?|<<?=?|>>?=?|[@?]/},e.languages.rust["closure-params"].inside.rest=e.languages.rust,e.languages.rust.attribute.inside.string=e.languages.rust.string}(A),A.languages.go=A.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|false|iota|nil|true)\b/,number:[/\b0(?:b[01_]+|o[0-7_]+)i?\b/i,/\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i,/(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i],operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/}),A.languages.insertBefore("go","string",{char:{pattern:/'(?:\\.|[^'\\\r\n]){0,10}'/,greedy:!0}}),delete A.languages.go["class-name"],function(e){var t=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,n=/\b(?!<keyword>)\w+(?:\s*\.\s*\w+)*\b/.source.replace(/<keyword>/g,(function(){return t.source}));e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!<keyword>)\w+/.source.replace(/<keyword>/g,(function(){return t.source}))),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:t,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),e.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:import|module)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/<mod-name>(?:\s*:\s*<mod-name>)?|:\s*<mod-name>/.source.replace(/<mod-name>/g,(function(){return n}))+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),e.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:e.languages.cpp}}}}),e.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),e.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend("cpp",{})}}),e.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},e.languages.cpp["base-clause"])}(A),A.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},A.languages.python["string-interpolation"].inside.interpolation.inside.rest=A.languages.python,A.languages.py=A.languages.python;((e,t)=>{for(var n in t)f(e,n,{get:t[n],enumerable:!0})})({},{dracula:()=>T,duotoneDark:()=>j,duotoneLight:()=>L,github:()=>N,jettwaveDark:()=>H,jettwaveLight:()=>G,nightOwl:()=>R,nightOwlLight:()=>P,oceanicNext:()=>M,okaidia:()=>I,oneDark:()=>V,oneLight:()=>W,palenight:()=>F,shadesOfPurple:()=>z,synthwave84:()=>B,ultramin:()=>$,vsDark:()=>U,vsLight:()=>q});var T={plain:{color:"#F8F8F2",backgroundColor:"#282A36"},styles:[{types:["prolog","constant","builtin"],style:{color:"rgb(189, 147, 249)"}},{types:["inserted","function"],style:{color:"rgb(80, 250, 123)"}},{types:["deleted"],style:{color:"rgb(255, 85, 85)"}},{types:["changed"],style:{color:"rgb(255, 184, 108)"}},{types:["punctuation","symbol"],style:{color:"rgb(248, 248, 242)"}},{types:["string","char","tag","selector"],style:{color:"rgb(255, 121, 198)"}},{types:["keyword","variable"],style:{color:"rgb(189, 147, 249)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(98, 114, 164)"}},{types:["attr-name"],style:{color:"rgb(241, 250, 140)"}}]},j={plain:{backgroundColor:"#2a2734",color:"#9a86fd"},styles:[{types:["comment","prolog","doctype","cdata","punctuation"],style:{color:"#6c6783"}},{types:["namespace"],style:{opacity:.7}},{types:["tag","operator","number"],style:{color:"#e09142"}},{types:["property","function"],style:{color:"#9a86fd"}},{types:["tag-id","selector","atrule-id"],style:{color:"#eeebff"}},{types:["attr-name"],style:{color:"#c4b9fe"}},{types:["boolean","string","entity","url","attr-value","keyword","control","directive","unit","statement","regex","atrule","placeholder","variable"],style:{color:"#ffcc99"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"#c4b9fe"}}]},L={plain:{backgroundColor:"#faf8f5",color:"#728fcb"},styles:[{types:["comment","prolog","doctype","cdata","punctuation"],style:{color:"#b6ad9a"}},{types:["namespace"],style:{opacity:.7}},{types:["tag","operator","number"],style:{color:"#063289"}},{types:["property","function"],style:{color:"#b29762"}},{types:["tag-id","selector","atrule-id"],style:{color:"#2d2006"}},{types:["attr-name"],style:{color:"#896724"}},{types:["boolean","string","entity","url","attr-value","keyword","control","directive","unit","statement","regex","atrule"],style:{color:"#728fcb"}},{types:["placeholder","variable"],style:{color:"#93abdc"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"#896724"}}]},N={plain:{color:"#393A34",backgroundColor:"#f6f8fa"},styles:[{types:["comment","prolog","doctype","cdata"],style:{color:"#999988",fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}},{types:["string","attr-value"],style:{color:"#e3116c"}},{types:["punctuation","operator"],style:{color:"#393A34"}},{types:["entity","url","symbol","number","boolean","variable","constant","property","regex","inserted"],style:{color:"#36acaa"}},{types:["atrule","keyword","attr-name","selector"],style:{color:"#00a4db"}},{types:["function","deleted","tag"],style:{color:"#d73a49"}},{types:["function-variable"],style:{color:"#6f42c1"}},{types:["tag","selector","keyword"],style:{color:"#00009f"}}]},R={plain:{color:"#d6deeb",backgroundColor:"#011627"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)",fontStyle:"italic"}},{types:["inserted","attr-name"],style:{color:"rgb(173, 219, 103)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(99, 119, 119)",fontStyle:"italic"}},{types:["string","url"],style:{color:"rgb(173, 219, 103)"}},{types:["variable"],style:{color:"rgb(214, 222, 235)"}},{types:["number"],style:{color:"rgb(247, 140, 108)"}},{types:["builtin","char","constant","function"],style:{color:"rgb(130, 170, 255)"}},{types:["punctuation"],style:{color:"rgb(199, 146, 234)"}},{types:["selector","doctype"],style:{color:"rgb(199, 146, 234)",fontStyle:"italic"}},{types:["class-name"],style:{color:"rgb(255, 203, 139)"}},{types:["tag","operator","keyword"],style:{color:"rgb(127, 219, 202)"}},{types:["boolean"],style:{color:"rgb(255, 88, 116)"}},{types:["property"],style:{color:"rgb(128, 203, 196)"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)"}}]},P={plain:{color:"#403f53",backgroundColor:"#FBFBFB"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)",fontStyle:"italic"}},{types:["inserted","attr-name"],style:{color:"rgb(72, 118, 214)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(152, 159, 177)",fontStyle:"italic"}},{types:["string","builtin","char","constant","url"],style:{color:"rgb(72, 118, 214)"}},{types:["variable"],style:{color:"rgb(201, 103, 101)"}},{types:["number"],style:{color:"rgb(170, 9, 130)"}},{types:["punctuation"],style:{color:"rgb(153, 76, 195)"}},{types:["function","selector","doctype"],style:{color:"rgb(153, 76, 195)",fontStyle:"italic"}},{types:["class-name"],style:{color:"rgb(17, 17, 17)"}},{types:["tag"],style:{color:"rgb(153, 76, 195)"}},{types:["operator","property","keyword","namespace"],style:{color:"rgb(12, 150, 155)"}},{types:["boolean"],style:{color:"rgb(188, 84, 84)"}}]},O="#c5a5c5",D="#8dc891",M={plain:{backgroundColor:"#282c34",color:"#ffffff"},styles:[{types:["attr-name"],style:{color:O}},{types:["attr-value"],style:{color:D}},{types:["comment","block-comment","prolog","doctype","cdata","shebang"],style:{color:"#999999"}},{types:["property","number","function-name","constant","symbol","deleted"],style:{color:"#5a9bcf"}},{types:["boolean"],style:{color:"#ff8b50"}},{types:["tag"],style:{color:"#fc929e"}},{types:["string"],style:{color:D}},{types:["punctuation"],style:{color:D}},{types:["selector","char","builtin","inserted"],style:{color:"#D8DEE9"}},{types:["function"],style:{color:"#79b6f2"}},{types:["operator","entity","url","variable"],style:{color:"#d7deea"}},{types:["keyword"],style:{color:O}},{types:["atrule","class-name"],style:{color:"#FAC863"}},{types:["important"],style:{fontWeight:"400"}},{types:["bold"],style:{fontWeight:"bold"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}}]},I={plain:{color:"#f8f8f2",backgroundColor:"#272822"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"#f92672",fontStyle:"italic"}},{types:["inserted"],style:{color:"rgb(173, 219, 103)",fontStyle:"italic"}},{types:["comment"],style:{color:"#8292a2",fontStyle:"italic"}},{types:["string","url"],style:{color:"#a6e22e"}},{types:["variable"],style:{color:"#f8f8f2"}},{types:["number"],style:{color:"#ae81ff"}},{types:["builtin","char","constant","function","class-name"],style:{color:"#e6db74"}},{types:["punctuation"],style:{color:"#f8f8f2"}},{types:["selector","doctype"],style:{color:"#a6e22e",fontStyle:"italic"}},{types:["tag","operator","keyword"],style:{color:"#66d9ef"}},{types:["boolean"],style:{color:"#ae81ff"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)",opacity:.7}},{types:["tag","property"],style:{color:"#f92672"}},{types:["attr-name"],style:{color:"#a6e22e !important"}},{types:["doctype"],style:{color:"#8292a2"}},{types:["rule"],style:{color:"#e6db74"}}]},F={plain:{color:"#bfc7d5",backgroundColor:"#292d3e"},styles:[{types:["comment"],style:{color:"rgb(105, 112, 152)",fontStyle:"italic"}},{types:["string","inserted"],style:{color:"rgb(195, 232, 141)"}},{types:["number"],style:{color:"rgb(247, 140, 108)"}},{types:["builtin","char","constant","function"],style:{color:"rgb(130, 170, 255)"}},{types:["punctuation","selector"],style:{color:"rgb(199, 146, 234)"}},{types:["variable"],style:{color:"rgb(191, 199, 213)"}},{types:["class-name","attr-name"],style:{color:"rgb(255, 203, 107)"}},{types:["tag","deleted"],style:{color:"rgb(255, 85, 114)"}},{types:["operator"],style:{color:"rgb(137, 221, 255)"}},{types:["boolean"],style:{color:"rgb(255, 88, 116)"}},{types:["keyword"],style:{fontStyle:"italic"}},{types:["doctype"],style:{color:"rgb(199, 146, 234)",fontStyle:"italic"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)"}},{types:["url"],style:{color:"rgb(221, 221, 221)"}}]},z={plain:{color:"#9EFEFF",backgroundColor:"#2D2A55"},styles:[{types:["changed"],style:{color:"rgb(255, 238, 128)"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)"}},{types:["inserted"],style:{color:"rgb(173, 219, 103)"}},{types:["comment"],style:{color:"rgb(179, 98, 255)",fontStyle:"italic"}},{types:["punctuation"],style:{color:"rgb(255, 255, 255)"}},{types:["constant"],style:{color:"rgb(255, 98, 140)"}},{types:["string","url"],style:{color:"rgb(165, 255, 144)"}},{types:["variable"],style:{color:"rgb(255, 238, 128)"}},{types:["number","boolean"],style:{color:"rgb(255, 98, 140)"}},{types:["attr-name"],style:{color:"rgb(255, 180, 84)"}},{types:["keyword","operator","property","namespace","tag","selector","doctype"],style:{color:"rgb(255, 157, 0)"}},{types:["builtin","char","constant","function","class-name"],style:{color:"rgb(250, 208, 0)"}}]},B={plain:{backgroundColor:"linear-gradient(to bottom, #2a2139 75%, #34294f)",backgroundImage:"#34294f",color:"#f92aad",textShadow:"0 0 2px #100c0f, 0 0 5px #dc078e33, 0 0 10px #fff3"},styles:[{types:["comment","block-comment","prolog","doctype","cdata"],style:{color:"#495495",fontStyle:"italic"}},{types:["punctuation"],style:{color:"#ccc"}},{types:["tag","attr-name","namespace","number","unit","hexcode","deleted"],style:{color:"#e2777a"}},{types:["property","selector"],style:{color:"#72f1b8",textShadow:"0 0 2px #100c0f, 0 0 10px #257c5575, 0 0 35px #21272475"}},{types:["function-name"],style:{color:"#6196cc"}},{types:["boolean","selector-id","function"],style:{color:"#fdfdfd",textShadow:"0 0 2px #001716, 0 0 3px #03edf975, 0 0 5px #03edf975, 0 0 8px #03edf975"}},{types:["class-name","maybe-class-name","builtin"],style:{color:"#fff5f6",textShadow:"0 0 2px #000, 0 0 10px #fc1f2c75, 0 0 5px #fc1f2c75, 0 0 25px #fc1f2c75"}},{types:["constant","symbol"],style:{color:"#f92aad",textShadow:"0 0 2px #100c0f, 0 0 5px #dc078e33, 0 0 10px #fff3"}},{types:["important","atrule","keyword","selector-class"],style:{color:"#f4eee4",textShadow:"0 0 2px #393a33, 0 0 8px #f39f0575, 0 0 2px #f39f0575"}},{types:["string","char","attr-value","regex","variable"],style:{color:"#f87c32"}},{types:["parameter"],style:{fontStyle:"italic"}},{types:["entity","url"],style:{color:"#67cdcc"}},{types:["operator"],style:{color:"ffffffee"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["entity"],style:{cursor:"help"}},{types:["inserted"],style:{color:"green"}}]},$={plain:{color:"#282a2e",backgroundColor:"#ffffff"},styles:[{types:["comment"],style:{color:"rgb(197, 200, 198)"}},{types:["string","number","builtin","variable"],style:{color:"rgb(150, 152, 150)"}},{types:["class-name","function","tag","attr-name"],style:{color:"rgb(40, 42, 46)"}}]},U={plain:{color:"#9CDCFE",backgroundColor:"#1E1E1E"},styles:[{types:["prolog"],style:{color:"rgb(0, 0, 128)"}},{types:["comment"],style:{color:"rgb(106, 153, 85)"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"rgb(86, 156, 214)"}},{types:["number","inserted"],style:{color:"rgb(181, 206, 168)"}},{types:["constant"],style:{color:"rgb(100, 102, 149)"}},{types:["attr-name","variable"],style:{color:"rgb(156, 220, 254)"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"rgb(206, 145, 120)"}},{types:["selector"],style:{color:"rgb(215, 186, 125)"}},{types:["tag"],style:{color:"rgb(78, 201, 176)"}},{types:["tag"],languages:["markup"],style:{color:"rgb(86, 156, 214)"}},{types:["punctuation","operator"],style:{color:"rgb(212, 212, 212)"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"rgb(220, 220, 170)"}},{types:["class-name"],style:{color:"rgb(78, 201, 176)"}},{types:["char"],style:{color:"rgb(209, 105, 105)"}}]},q={plain:{color:"#000000",backgroundColor:"#ffffff"},styles:[{types:["comment"],style:{color:"rgb(0, 128, 0)"}},{types:["builtin"],style:{color:"rgb(0, 112, 193)"}},{types:["number","variable","inserted"],style:{color:"rgb(9, 134, 88)"}},{types:["operator"],style:{color:"rgb(0, 0, 0)"}},{types:["constant","char"],style:{color:"rgb(129, 31, 63)"}},{types:["tag"],style:{color:"rgb(128, 0, 0)"}},{types:["attr-name"],style:{color:"rgb(255, 0, 0)"}},{types:["deleted","string"],style:{color:"rgb(163, 21, 21)"}},{types:["changed","punctuation"],style:{color:"rgb(4, 81, 165)"}},{types:["function","keyword"],style:{color:"rgb(0, 0, 255)"}},{types:["class-name"],style:{color:"rgb(38, 127, 153)"}}]},H={plain:{color:"#f8fafc",backgroundColor:"#011627"},styles:[{types:["prolog"],style:{color:"#000080"}},{types:["comment"],style:{color:"#6A9955"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"#569CD6"}},{types:["number","inserted"],style:{color:"#B5CEA8"}},{types:["constant"],style:{color:"#f8fafc"}},{types:["attr-name","variable"],style:{color:"#9CDCFE"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"#cbd5e1"}},{types:["selector"],style:{color:"#D7BA7D"}},{types:["tag"],style:{color:"#0ea5e9"}},{types:["tag"],languages:["markup"],style:{color:"#0ea5e9"}},{types:["punctuation","operator"],style:{color:"#D4D4D4"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"#7dd3fc"}},{types:["class-name"],style:{color:"#0ea5e9"}},{types:["char"],style:{color:"#D16969"}}]},G={plain:{color:"#0f172a",backgroundColor:"#f1f5f9"},styles:[{types:["prolog"],style:{color:"#000080"}},{types:["comment"],style:{color:"#6A9955"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"#0c4a6e"}},{types:["number","inserted"],style:{color:"#B5CEA8"}},{types:["constant"],style:{color:"#0f172a"}},{types:["attr-name","variable"],style:{color:"#0c4a6e"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"#64748b"}},{types:["selector"],style:{color:"#D7BA7D"}},{types:["tag"],style:{color:"#0ea5e9"}},{types:["tag"],languages:["markup"],style:{color:"#0ea5e9"}},{types:["punctuation","operator"],style:{color:"#475569"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"#0e7490"}},{types:["class-name"],style:{color:"#0ea5e9"}},{types:["char"],style:{color:"#D16969"}}]},V={plain:{backgroundColor:"hsl(220, 13%, 18%)",color:"hsl(220, 14%, 71%)",textShadow:"0 1px rgba(0, 0, 0, 0.3)"},styles:[{types:["comment","prolog","cdata"],style:{color:"hsl(220, 10%, 40%)"}},{types:["doctype","punctuation","entity"],style:{color:"hsl(220, 14%, 71%)"}},{types:["attr-name","class-name","maybe-class-name","boolean","constant","number","atrule"],style:{color:"hsl(29, 54%, 61%)"}},{types:["keyword"],style:{color:"hsl(286, 60%, 67%)"}},{types:["property","tag","symbol","deleted","important"],style:{color:"hsl(355, 65%, 65%)"}},{types:["selector","string","char","builtin","inserted","regex","attr-value"],style:{color:"hsl(95, 38%, 62%)"}},{types:["variable","operator","function"],style:{color:"hsl(207, 82%, 66%)"}},{types:["url"],style:{color:"hsl(187, 47%, 55%)"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"hsl(220, 14%, 71%)"}}]},W={plain:{backgroundColor:"hsl(230, 1%, 98%)",color:"hsl(230, 8%, 24%)"},styles:[{types:["comment","prolog","cdata"],style:{color:"hsl(230, 4%, 64%)"}},{types:["doctype","punctuation","entity"],style:{color:"hsl(230, 8%, 24%)"}},{types:["attr-name","class-name","boolean","constant","number","atrule"],style:{color:"hsl(35, 99%, 36%)"}},{types:["keyword"],style:{color:"hsl(301, 63%, 40%)"}},{types:["property","tag","symbol","deleted","important"],style:{color:"hsl(5, 74%, 59%)"}},{types:["selector","string","char","builtin","inserted","regex","attr-value","punctuation"],style:{color:"hsl(119, 34%, 47%)"}},{types:["variable","operator","function"],style:{color:"hsl(221, 87%, 60%)"}},{types:["url"],style:{color:"hsl(198, 99%, 37%)"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"hsl(230, 8%, 24%)"}}]},Q=(e,t)=>{const{plain:n}=e,r=e.styles.reduce(((e,n)=>{const{languages:r,style:a}=n;return r&&!r.includes(t)||n.types.forEach((t=>{const n=S(S({},e[t]),a);e[t]=n})),e}),{});return r.root=n,r.plain=E(S({},n),{backgroundColor:void 0}),r},K=/\r\n|\r|\n/,Y=e=>{0===e.length?e.push({types:["plain"],content:"\n",empty:!0}):1===e.length&&""===e[0].content&&(e[0].content="\n",e[0].empty=!0)},Z=(e,t)=>{const n=e.length;return n>0&&e[n-1]===t?e:e.concat(t)},X=e=>{const t=[[]],n=[e],r=[0],a=[e.length];let o=0,i=0,l=[];const s=[l];for(;i>-1;){for(;(o=r[i]++)<a[i];){let e,c=t[i];const u=n[i][o];if("string"==typeof u?(c=i>0?c:["plain"],e=u):(c=Z(c,u.type),u.alias&&(c=Z(c,u.alias)),e=u.content),"string"!=typeof e){i++,t.push(c),n.push(e),r.push(0),a.push(e.length);continue}const d=e.split(K),p=d.length;l.push({types:c,content:d[0]});for(let t=1;t<p;t++)Y(l),s.push(l=[]),l.push({types:c,content:d[t]})}i--,t.pop(),n.pop(),r.pop(),a.pop()}return Y(l),s},J=({children:e,language:t,code:n,theme:r,prism:a})=>{const o=t.toLowerCase(),i=((e,t)=>{const[n,r]=(0,u.useState)(Q(t,e)),a=(0,u.useRef)(),o=(0,u.useRef)();return(0,u.useEffect)((()=>{t===a.current&&e===o.current||(a.current=t,o.current=e,r(Q(t,e)))}),[e,t]),n})(o,r),l=(e=>(0,u.useCallback)((t=>{var n=t,{className:r,style:a,line:o}=n,i=C(n,["className","style","line"]);const l=E(S({},i),{className:(0,d.A)("token-line",r)});return"object"==typeof e&&"plain"in e&&(l.style=e.plain),"object"==typeof a&&(l.style=S(S({},l.style||{}),a)),l}),[e]))(i),s=(e=>{const t=(0,u.useCallback)((({types:t,empty:n})=>{if(null!=e)return 1===t.length&&"plain"===t[0]?null!=n?{display:"inline-block"}:void 0:1===t.length&&null!=n?e[t[0]]:Object.assign(null!=n?{display:"inline-block"}:{},...t.map((t=>e[t])))}),[e]);return(0,u.useCallback)((e=>{var n=e,{token:r,className:a,style:o}=n,i=C(n,["token","className","style"]);const l=E(S({},i),{className:(0,d.A)("token",...r.types,a),children:r.content,style:t(r)});return null!=o&&(l.style=S(S({},l.style||{}),o)),l}),[t])})(i),c=(({prism:e,code:t,grammar:n,language:r})=>{const a=(0,u.useRef)(e);return(0,u.useMemo)((()=>{if(null==n)return X([t]);const e={code:t,grammar:n,language:r,tokens:[]};return a.current.hooks.run("before-tokenize",e),e.tokens=a.current.tokenize(t,n),a.current.hooks.run("after-tokenize",e),X(e.tokens)}),[t,n,r])})({prism:a,language:o,code:n,grammar:a.languages[o]});return e({tokens:c,className:`prism-code language-${o}`,style:null!=i?i.root:{},getLineProps:l,getTokenProps:s})},ee=e=>(0,u.createElement)(J,E(S({},e),{prism:e.prism||A,theme:e.theme||U,code:e.code,language:e.language}))},1561:(e,t,n)=>{"use strict";n.d(t,{A:()=>o});var r=!0,a="Invariant failed";function o(e,t){if(!e){if(r)throw new Error(a);var n="function"==typeof t?t():t,o=n?"".concat(a,": ").concat(n):a;throw new Error(o)}}},2654:e=>{"use strict";e.exports={}},4054:e=>{"use strict";e.exports=JSON.parse('{"/lmc-rbac-mvc/blog-a51":{"__comp":"a6aa9e1f","__context":{"plugin":"e252ba15"},"sidebar":"814f3328","items":[{"content":"378f56c0"},{"content":"b5cb822e"}],"metadata":"266b150c"},"/lmc-rbac-mvc/blog/archive-da6":{"__comp":"9e4087bc","__context":{"plugin":"e252ba15"},"archive":"5b3ddbaa"},"/lmc-rbac-mvc/blog/new-documentation-af1":{"__comp":"ccc49370","__context":{"plugin":"e252ba15"},"sidebar":"814f3328","content":"809dac3e"},"/lmc-rbac-mvc/blog/tags-fdd":{"__comp":"01a85c17","__context":{"plugin":"e252ba15"},"sidebar":"814f3328","tags":"a6185ec0"},"/lmc-rbac-mvc/blog/tags/laminas-94d":{"__comp":"6875c492","__context":{"plugin":"e252ba15"},"sidebar":"814f3328","items":[{"content":"378f56c0"},{"content":"b5cb822e"}],"tag":"4a900e2d","listMetadata":"1c81fc78"},"/lmc-rbac-mvc/blog/tags/lmcrbacmvc-780":{"__comp":"6875c492","__context":{"plugin":"e252ba15"},"sidebar":"814f3328","items":[{"content":"378f56c0"}],"tag":"93a10038","listMetadata":"983b303c"},"/lmc-rbac-mvc/blog/tags/php-3f3":{"__comp":"6875c492","__context":{"plugin":"e252ba15"},"sidebar":"814f3328","items":[{"content":"378f56c0"},{"content":"b5cb822e"}],"tag":"f01051f8","listMetadata":"4b8919da"},"/lmc-rbac-mvc/blog/welcome-c6b":{"__comp":"ccc49370","__context":{"plugin":"e252ba15"},"sidebar":"814f3328","content":"ee696d42"},"/lmc-rbac-mvc/markdown-page-7e3":{"__comp":"1f391b9e","__context":{"plugin":"5b72c13b"},"content":"393be207"},"/lmc-rbac-mvc/docs-1d1":{"__comp":"5e95c892","__context":{"plugin":"02def4a7"}},"/lmc-rbac-mvc/docs-c16":{"__comp":"a7bd4aaa","version":"935f2afb"},"/lmc-rbac-mvc/docs-ad5":{"__comp":"a94703ab"},"/lmc-rbac-mvc/docs/cookbook-d11":{"__comp":"17896441","content":"b9207088"},"/lmc-rbac-mvc/docs/guards-dce":{"__comp":"17896441","content":"8b5d4ccc"},"/lmc-rbac-mvc/docs/installation-655":{"__comp":"17896441","content":"3b8c55ea"},"/lmc-rbac-mvc/docs/intro-82a":{"__comp":"17896441","content":"0e384e19"},"/lmc-rbac-mvc/docs/quick-start-359":{"__comp":"17896441","content":"72e14192"},"/lmc-rbac-mvc/docs/role-providers-082":{"__comp":"17896441","content":"22465cd7"},"/lmc-rbac-mvc/docs/strategies-a09":{"__comp":"17896441","content":"f6027d55"},"/lmc-rbac-mvc/docs/support-dfd":{"__comp":"17896441","content":"d9e16301"},"/lmc-rbac-mvc/docs/using-the-authorization-service-4f1":{"__comp":"17896441","content":"4e6224b1"},"/lmc-rbac-mvc/-849":{"__comp":"c4f5d8e4","__context":{"plugin":"5b72c13b"},"config":"5e9f5e1a"}}')}},e=>{e.O(0,[869],(()=>{return t=8536,e(e.s=t);var t}));e.O()}]); \ No newline at end of file diff --git a/assets/js/main.452d3fbd.js.LICENSE.txt b/assets/js/main.452d3fbd.js.LICENSE.txt new file mode 100644 index 00000000..91dc8949 --- /dev/null +++ b/assets/js/main.452d3fbd.js.LICENSE.txt @@ -0,0 +1,64 @@ +/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress + * @license MIT */ + +/*! Bundled license information: + +prismjs/prism.js: + (** + * Prism: Lightweight, robust, elegant syntax highlighting + * + * @license MIT <https://opensource.org/licenses/MIT> + * @author Lea Verou <https://lea.verou.me> + * @namespace + * @public + *) +*/ + +/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * @license React + * react-jsx-runtime.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * @license React + * scheduler.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** @license React v16.13.1 + * react-is.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ diff --git a/assets/js/runtime~main.705cea34.js b/assets/js/runtime~main.705cea34.js new file mode 100644 index 00000000..d6f0f329 --- /dev/null +++ b/assets/js/runtime~main.705cea34.js @@ -0,0 +1 @@ +(()=>{"use strict";var e,a,t,r,c,f={},d={};function o(e){var a=d[e];if(void 0!==a)return a.exports;var t=d[e]={id:e,loaded:!1,exports:{}};return f[e].call(t.exports,t,t.exports,o),t.loaded=!0,t.exports}o.m=f,o.c=d,e=[],o.O=(a,t,r,c)=>{if(!t){var f=1/0;for(i=0;i<e.length;i++){t=e[i][0],r=e[i][1],c=e[i][2];for(var d=!0,b=0;b<t.length;b++)(!1&c||f>=c)&&Object.keys(o.O).every((e=>o.O[e](t[b])))?t.splice(b--,1):(d=!1,c<f&&(f=c));if(d){e.splice(i--,1);var n=r();void 0!==n&&(a=n)}}return a}c=c||0;for(var i=e.length;i>0&&e[i-1][2]>c;i--)e[i]=e[i-1];e[i]=[t,r,c]},o.n=e=>{var a=e&&e.__esModule?()=>e.default:()=>e;return o.d(a,{a:a}),a},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(e,r){if(1&r&&(e=this(e)),8&r)return e;if("object"==typeof e&&e){if(4&r&&e.__esModule)return e;if(16&r&&"function"==typeof e.then)return e}var c=Object.create(null);o.r(c);var f={};a=a||[null,t({}),t([]),t(t)];for(var d=2&r&&e;"object"==typeof d&&!~a.indexOf(d);d=t(d))Object.getOwnPropertyNames(d).forEach((a=>f[a]=()=>e[a]));return f.default=()=>e,o.d(c,f),c},o.d=(e,a)=>{for(var t in a)o.o(a,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},o.f={},o.e=e=>Promise.all(Object.keys(o.f).reduce(((a,t)=>(o.f[t](e,a),a)),[])),o.u=e=>"assets/js/"+({18:"ee696d42",48:"a94703ab",61:"1f391b9e",75:"5b3ddbaa",82:"4a900e2d",98:"a7bd4aaa",107:"4e6224b1",134:"393be207",166:"22465cd7",182:"e252ba15",209:"01a85c17",223:"4b8919da",246:"f6027d55",249:"ccc49370",270:"a6185ec0",346:"266b150c",390:"b5cb822e",401:"17896441",416:"d9e16301",472:"814f3328",571:"b9207088",581:"935f2afb",604:"8b5d4ccc",634:"c4f5d8e4",639:"378f56c0",643:"a6aa9e1f",647:"5e95c892",660:"983b303c",698:"5b72c13b",711:"9e4087bc",770:"02def4a7",803:"3b8c55ea",804:"809dac3e",813:"6875c492",814:"72e14192",822:"f01051f8",828:"93a10038",976:"0e384e19",982:"1c81fc78"}[e]||e)+"."+{18:"cd7325d3",48:"8b0ccadb",61:"a27a432b",75:"579400ac",82:"a37ef9b5",98:"fd305e17",107:"3600afef",134:"8c825263",166:"9e45649f",182:"a3bf887f",209:"8dd5da0a",223:"4dc42bc8",237:"7663bed8",246:"272e2251",249:"c41daa2c",270:"7643b318",346:"c169f2f7",390:"5f84e9da",401:"ab15fc7f",416:"5f01f94d",472:"d5988fa5",533:"11304ac5",571:"c1f4a27e",581:"6562d556",604:"1ace6756",634:"00ce20a8",639:"f10e3e52",643:"aac55eda",647:"1dd55255",660:"20d2d832",698:"e8b2bfc0",711:"bddafc5d",747:"847fa25b",770:"53c9a7f1",803:"95bbb53a",804:"133b40d5",813:"f1af0c91",814:"a1fe92a6",822:"63b05f50",828:"26f7b70f",976:"3d824450",982:"f1f88d0c"}[e]+".js",o.miniCssF=e=>{},o.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),o.o=(e,a)=>Object.prototype.hasOwnProperty.call(e,a),r={},c="docs:",o.l=(e,a,t,f)=>{if(r[e])r[e].push(a);else{var d,b;if(void 0!==t)for(var n=document.getElementsByTagName("script"),i=0;i<n.length;i++){var u=n[i];if(u.getAttribute("src")==e||u.getAttribute("data-webpack")==c+t){d=u;break}}d||(b=!0,(d=document.createElement("script")).charset="utf-8",d.timeout=120,o.nc&&d.setAttribute("nonce",o.nc),d.setAttribute("data-webpack",c+t),d.src=e),r[e]=[a];var l=(a,t)=>{d.onerror=d.onload=null,clearTimeout(s);var c=r[e];if(delete r[e],d.parentNode&&d.parentNode.removeChild(d),c&&c.forEach((e=>e(t))),a)return a(t)},s=setTimeout(l.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=l.bind(null,d.onerror),d.onload=l.bind(null,d.onload),b&&document.head.appendChild(d)}},o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},o.p="/lmc-rbac-mvc/",o.gca=function(e){return e={17896441:"401",ee696d42:"18",a94703ab:"48","1f391b9e":"61","5b3ddbaa":"75","4a900e2d":"82",a7bd4aaa:"98","4e6224b1":"107","393be207":"134","22465cd7":"166",e252ba15:"182","01a85c17":"209","4b8919da":"223",f6027d55:"246",ccc49370:"249",a6185ec0:"270","266b150c":"346",b5cb822e:"390",d9e16301:"416","814f3328":"472",b9207088:"571","935f2afb":"581","8b5d4ccc":"604",c4f5d8e4:"634","378f56c0":"639",a6aa9e1f:"643","5e95c892":"647","983b303c":"660","5b72c13b":"698","9e4087bc":"711","02def4a7":"770","3b8c55ea":"803","809dac3e":"804","6875c492":"813","72e14192":"814",f01051f8:"822","93a10038":"828","0e384e19":"976","1c81fc78":"982"}[e]||e,o.p+o.u(e)},(()=>{var e={354:0,869:0};o.f.j=(a,t)=>{var r=o.o(e,a)?e[a]:void 0;if(0!==r)if(r)t.push(r[2]);else if(/^(354|869)$/.test(a))e[a]=0;else{var c=new Promise(((t,c)=>r=e[a]=[t,c]));t.push(r[2]=c);var f=o.p+o.u(a),d=new Error;o.l(f,(t=>{if(o.o(e,a)&&(0!==(r=e[a])&&(e[a]=void 0),r)){var c=t&&("load"===t.type?"missing":t.type),f=t&&t.target&&t.target.src;d.message="Loading chunk "+a+" failed.\n("+c+": "+f+")",d.name="ChunkLoadError",d.type=c,d.request=f,r[1](d)}}),"chunk-"+a,a)}},o.O.j=a=>0===e[a];var a=(a,t)=>{var r,c,f=t[0],d=t[1],b=t[2],n=0;if(f.some((a=>0!==e[a]))){for(r in d)o.o(d,r)&&(o.m[r]=d[r]);if(b)var i=b(o)}for(a&&a(t);n<f.length;n++)c=f[n],o.o(e,c)&&e[c]&&e[c][0](),e[c]=0;return o.O(i)},t=self.webpackChunkdocs=self.webpackChunkdocs||[];t.forEach(a.bind(null,0)),t.push=a.bind(null,t.push.bind(t))})()})(); \ No newline at end of file diff --git a/blog/archive/index.html b/blog/archive/index.html new file mode 100644 index 00000000..8552b9e7 --- /dev/null +++ b/blog/archive/index.html @@ -0,0 +1,14 @@ +<!doctype html> +<html lang="en" dir="ltr" class="plugin-blog plugin-id-default" data-has-hydrated="false"> +<head> +<meta charset="UTF-8"> +<meta name="generator" content="Docusaurus v3.1.1"> +<title data-rh="true">Archive | LmcRbacMvc + + + + + + + + \ No newline at end of file diff --git a/blog/atom.xml b/blog/atom.xml new file mode 100644 index 00000000..ebfbb773 --- /dev/null +++ b/blog/atom.xml @@ -0,0 +1,41 @@ + + + https://lm-commons.github.io/lmc-rbac-mvc/blog + LmcRbacMvc Blog + 2024-02-22T00:00:00.000Z + https://github.com/jpmonette/feed + + LmcRbacMvc Blog + https://lm-commons.github.io/lmc-rbac-mvc/img/favicon.ico + + <![CDATA[New documentation]]> + https://lm-commons.github.io/lmc-rbac-mvc/blog/new-documentation + + 2024-02-22T00:00:00.000Z + + This the new documentation site dedicated to the LmcRbacMvc module.

+

There are no changes to the code, just improvements in the documentation.

]]>
+ + Eric Richer + https://github.com/visto9259 + + + + +
+ + <![CDATA[Welcome]]> + https://lm-commons.github.io/lmc-rbac-mvc/blog/welcome + + 2022-08-02T00:00:00.000Z + + Welcome to the new documentation website for the LM-Commons organization.

+

This site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages.

]]>
+ + Eric Richer + https://github.com/visto9259 + + + +
+
\ No newline at end of file diff --git a/blog/index.html b/blog/index.html new file mode 100644 index 00000000..562517a0 --- /dev/null +++ b/blog/index.html @@ -0,0 +1,16 @@ + + + + + +Blog | LmcRbacMvc + + + + + +
Skip to main content

· One min read
Eric Richer

This the new documentation site dedicated to the LmcRbacMvc module.

+

There are no changes to the code, just improvements in the documentation.

· One min read
Eric Richer

Welcome to the new documentation website for the LM-Commons organization.

+

This site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages.

+ + \ No newline at end of file diff --git a/blog/new-documentation/index.html b/blog/new-documentation/index.html new file mode 100644 index 00000000..b45c138e --- /dev/null +++ b/blog/new-documentation/index.html @@ -0,0 +1,15 @@ + + + + + +New documentation | LmcRbacMvc + + + + + +
Skip to main content

New documentation

· One min read
Eric Richer

This the new documentation site dedicated to the LmcRbacMvc module.

+

There are no changes to the code, just improvements in the documentation.

+ + \ No newline at end of file diff --git a/blog/rss.xml b/blog/rss.xml new file mode 100644 index 00000000..8a4bac63 --- /dev/null +++ b/blog/rss.xml @@ -0,0 +1,35 @@ + + + + LmcRbacMvc Blog + https://lm-commons.github.io/lmc-rbac-mvc/blog + LmcRbacMvc Blog + Thu, 22 Feb 2024 00:00:00 GMT + https://validator.w3.org/feed/docs/rss2.html + https://github.com/jpmonette/feed + en + + <![CDATA[New documentation]]> + https://lm-commons.github.io/lmc-rbac-mvc/blog/new-documentation + https://lm-commons.github.io/lmc-rbac-mvc/blog/new-documentation + Thu, 22 Feb 2024 00:00:00 GMT + + This the new documentation site dedicated to the LmcRbacMvc module.

+

There are no changes to the code, just improvements in the documentation.

]]>
+ laminas + PHP + lmcrbacmvc +
+ + <![CDATA[Welcome]]> + https://lm-commons.github.io/lmc-rbac-mvc/blog/welcome + https://lm-commons.github.io/lmc-rbac-mvc/blog/welcome + Tue, 02 Aug 2022 00:00:00 GMT + + Welcome to the new documentation website for the LM-Commons organization.

+

This site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages.

]]>
+ laminas + PHP +
+
+
\ No newline at end of file diff --git a/blog/tags/index.html b/blog/tags/index.html new file mode 100644 index 00000000..c02cab78 --- /dev/null +++ b/blog/tags/index.html @@ -0,0 +1,14 @@ + + + + + +Tags | LmcRbacMvc + + + + + + + + \ No newline at end of file diff --git a/blog/tags/laminas/index.html b/blog/tags/laminas/index.html new file mode 100644 index 00000000..cbd6e117 --- /dev/null +++ b/blog/tags/laminas/index.html @@ -0,0 +1,16 @@ + + + + + +2 posts tagged with "laminas" | LmcRbacMvc + + + + + +
Skip to main content

2 posts tagged with "laminas"

View All Tags

· One min read
Eric Richer

This the new documentation site dedicated to the LmcRbacMvc module.

+

There are no changes to the code, just improvements in the documentation.

· One min read
Eric Richer

Welcome to the new documentation website for the LM-Commons organization.

+

This site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages.

+ + \ No newline at end of file diff --git a/blog/tags/lmcrbacmvc/index.html b/blog/tags/lmcrbacmvc/index.html new file mode 100644 index 00000000..d7fbe475 --- /dev/null +++ b/blog/tags/lmcrbacmvc/index.html @@ -0,0 +1,15 @@ + + + + + +One post tagged with "lmcrbacmvc" | LmcRbacMvc + + + + + +
Skip to main content

One post tagged with "lmcrbacmvc"

View All Tags

· One min read
Eric Richer

This the new documentation site dedicated to the LmcRbacMvc module.

+

There are no changes to the code, just improvements in the documentation.

+ + \ No newline at end of file diff --git a/blog/tags/php/index.html b/blog/tags/php/index.html new file mode 100644 index 00000000..0cf0c3f4 --- /dev/null +++ b/blog/tags/php/index.html @@ -0,0 +1,16 @@ + + + + + +2 posts tagged with "PHP" | LmcRbacMvc + + + + + +
Skip to main content

2 posts tagged with "PHP"

View All Tags

· One min read
Eric Richer

This the new documentation site dedicated to the LmcRbacMvc module.

+

There are no changes to the code, just improvements in the documentation.

· One min read
Eric Richer

Welcome to the new documentation website for the LM-Commons organization.

+

This site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages.

+ + \ No newline at end of file diff --git a/blog/welcome/index.html b/blog/welcome/index.html new file mode 100644 index 00000000..26b0d703 --- /dev/null +++ b/blog/welcome/index.html @@ -0,0 +1,15 @@ + + + + + +Welcome | LmcRbacMvc + + + + + +
Skip to main content

Welcome

· One min read
Eric Richer

Welcome to the new documentation website for the LM-Commons organization.

+

This site is work in progress and the intent is obviously to keep it current with updates to the LM-Commons packages.

+ + \ No newline at end of file diff --git a/composer.json b/composer.json deleted file mode 100644 index 5ea0b202..00000000 --- a/composer.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "name": "lm-commons/lmc-rbac-mvc", - "description": "Laminas Framework MVC Module that provides a layer of features of Laminas\\Permissions\\Rbac", - "type": "library", - "license": "MIT", - "keywords": [ - "module", - "laminas", - "rbac", - "permissions" - ], - "homepage": "http://www.github.com/Laminas-Commons/LmcRbacMvc", - "authors": [ - { - "name": "Kyle Spraggs", - "email": "theman@spiffyjr.me", - "homepage": "http://www.spiffyjr.me/" - }, - { - "name": "Micha\u00ebl Gallego", - "email": "mic.gallego@gmail.com", - "homepage": "http://www.michaelgallego.fr" - }, - { - "name": "Jean-Marie Leroux", - "email": "jmleroux.pro@gmail.com" - } - ], - "require": { - "php": "^7.4 || ^8.0", - "laminas/laminas-config": "^3.1", - "laminas/laminas-eventmanager": "^3.0", - "laminas/laminas-mvc": "^3.0", - "laminas/laminas-servicemanager": "^3.0", - "zfr/rbac": "~1.2", - "doctrine/persistence": "^2.1" - }, - "require-dev": { - "laminas/laminas-authentication": "^2.2", - "laminas/laminas-developer-tools": "^2.1", - "laminas/laminas-log": "^2.2", - "laminas/laminas-http": "^2.2", - "laminas/laminas-i18n": "^2.7", - "laminas/laminas-serializer": "^2.2", - "laminas/laminas-view": "^2.12", - "phpunit/phpunit": "9.5.21", - "squizlabs/php_codesniffer": "^3.5.5", - "php-coveralls/php-coveralls": "^2.2", - "phpspec/prophecy-phpunit": "^2.0", - "doctrine/doctrine-orm-module": "^4.1" - }, - "suggest": { - "laminas/laminas-developer-tools": "if you want to show information about the roles", - "doctrine/doctrine-module": "if you want to use Doctrine role provider" - }, - "replace": { - "laminas-commons/lmc-rbac-mvc": "3.0.1" - }, - "autoload": { - "psr-4": { - "LmcRbacMvc\\": "src" - } - }, - "autoload-dev": { - "psr-4": { - "LmcRbacMvcTest\\": "tests" - } - }, - "scripts": { - "test": "phpunit", - "test-coverage": "phpunit --coverage-clover ./build/logs/clover.xml --exclude-group Functional", - "upload-coverage": "php-coveralls -v", - "cs-check": "phpcs -n --standard=PSR2 ./src/", - "cs-fix": "phpcbf ./src/" - } -} diff --git a/config/lmc_rbac.global.php.dist b/config/lmc_rbac.global.php.dist deleted file mode 100644 index ca4b8953..00000000 --- a/config/lmc_rbac.global.php.dist +++ /dev/null @@ -1,132 +0,0 @@ - [ - /** - * Key that is used to fetch the identity provider - * - * Please note that when an identity is found, it MUST implements the LmcRbacMvc\Identity\IdentityProviderInterface - * interface, otherwise it will throw an exception. - */ - // 'identity_provider' => 'LmcRbacMvc\Identity\AuthenticationIdentityProvider', - - /** - * Set the guest role - * - * This role is used by the authorization service when the authentication service returns no identity - */ - // 'guest_role' => 'guest', - - /** - * Set the guards - * - * You must comply with the various options of guards. The format must be of the following format: - * - * 'guards' => [ - * 'LmcRbacMvc\Guard\RouteGuard' => [ - * // options - * ] - * ] - */ - // 'guards' => [], - - /** - * As soon as one rule for either route or controller is specified, a guard will be automatically - * created and will start to hook into the MVC loop. - * - * If the protection policy is set to DENY, then any route/controller will be denied by - * default UNLESS it is explicitly added as a rule. On the other hand, if it is set to ALLOW, then - * not specified route/controller will be implicitly approved. - * - * DENY is the most secure way, but it is more work for the developer - */ - // 'protection_policy' => \LmcRbacMvc\Guard\GuardInterface::POLICY_ALLOW, - - /** - * Configuration for role provider - * - * It must be an array that contains configuration for the role provider. The provider config - * must follow the following format: - * - * 'LmcRbacMvc\Role\InMemoryRoleProvider' => [ - * 'role1' => [ - * 'children' => ['children1', 'children2'], // OPTIONAL - * 'permissions' => ['edit', 'read'] // OPTIONAL - * ] - * ] - * - * Supported options depend of the role provider, so please refer to the official documentation - */ - 'role_provider' => [], - - /** - * Configure the unauthorized strategy. It is used to render a template whenever a user is unauthorized - */ - 'unauthorized_strategy' => [ - /** - * Set the template name to render - */ - // 'template' => 'error/403' - ], - - /** - * Configure the redirect strategy. It is used to redirect the user to another route when a user is - * unauthorized - */ - 'redirect_strategy' => [ - /** - * Enable redirection when the user is connected - */ - // 'redirect_when_connected' => true, - - /** - * Set the route to redirect when user is connected (of course, it must exist!) - */ - // 'redirect_to_route_connected' => 'home', - - /** - * Set the route to redirect when user is disconnected (of course, it must exist!) - */ - // 'redirect_to_route_disconnected' => 'login', - - /** - * If a user is unauthorized and redirected to another route (login, for instance), should we - * append the previous URI (the one that was unauthorized) in the query params? - */ - // 'append_previous_uri' => true, - - /** - * If append_previous_uri option is set to true, this option set the query key to use when - * the previous uri is appended - */ - // 'previous_uri_query_key' => 'redirectTo' - ], - - /** - * Various plugin managers for guards and role providers. Each of them must follow a common - * plugin manager config format, and can be used to create your custom objects - */ - // 'guard_manager' => [], - // 'role_provider_manager' => [] - ] -]; diff --git a/config/module.config.php b/config/module.config.php deleted file mode 100644 index 51a1cc8e..00000000 --- a/config/module.config.php +++ /dev/null @@ -1,90 +0,0 @@ - [ - 'factories' => [ - /* Factories that do not map to a class */ - 'LmcRbacMvc\Guards' => \LmcRbacMvc\Factory\GuardsFactory::class, - - /* Factories that map to a class */ - \Rbac\Rbac::class => \LmcRbacMvc\Factory\RbacFactory::class, - \LmcRbacMvc\Assertion\AssertionPluginManager::class => \LmcRbacMvc\Factory\AssertionPluginManagerFactory::class, - \LmcRbacMvc\Collector\RbacCollector::class => \Laminas\ServiceManager\Factory\InvokableFactory::class, - \LmcRbacMvc\Guard\GuardPluginManager::class => \LmcRbacMvc\Factory\GuardPluginManagerFactory::class, - \LmcRbacMvc\Identity\AuthenticationIdentityProvider::class => \LmcRbacMvc\Factory\AuthenticationIdentityProviderFactory::class, - \LmcRbacMvc\Options\ModuleOptions::class => \LmcRbacMvc\Factory\ModuleOptionsFactory::class, - \LmcRbacMvc\Role\RoleProviderPluginManager::class => \LmcRbacMvc\Factory\RoleProviderPluginManagerFactory::class, - \LmcRbacMvc\Service\AuthorizationService::class => \LmcRbacMvc\Factory\AuthorizationServiceFactory::class, - \LmcRbacMvc\Service\RoleService::class => \LmcRbacMvc\Factory\RoleServiceFactory::class, - \LmcRbacMvc\View\Strategy\RedirectStrategy::class => \LmcRbacMvc\Factory\RedirectStrategyFactory::class, - \LmcRbacMvc\View\Strategy\UnauthorizedStrategy::class => \LmcRbacMvc\Factory\UnauthorizedStrategyFactory::class, - ], - ], - - 'view_helpers' => [ - 'factories' => [ - \LmcRbacMvc\View\Helper\IsGranted::class => \LmcRbacMvc\Factory\IsGrantedViewHelperFactory::class, - \LmcRbacMvc\View\Helper\HasRole::class => \LmcRbacMvc\Factory\HasRoleViewHelperFactory::class, - ], - 'aliases' => [ - 'isGranted' => \LmcRbacMvc\View\Helper\IsGranted::class, - 'hasRole' => \LmcRbacMvc\View\Helper\HasRole::class, - ] - ], - - 'controller_plugins' => [ - 'factories' => [ - \LmcRbacMvc\Mvc\Controller\Plugin\IsGranted::class => \LmcRbacMvc\Factory\IsGrantedPluginFactory::class, - ], - 'aliases' => [ - 'isGranted' => \LmcRbacMvc\Mvc\Controller\Plugin\IsGranted::class, - ] - ], - - 'view_manager' => [ - 'template_map' => [ - 'error/403' => __DIR__ . '/../view/error/403.phtml', - 'laminas-developer-tools/toolbar/lmc-rbac' => __DIR__ . '/../view/laminas-developer-tools/toolbar/lmc-rbac.phtml' - ] - ], - - 'laminas-developer-tools' => [ - 'profiler' => [ - 'collectors' => [ - 'lmc_rbac' => \LmcRbacMvc\Collector\RbacCollector::class, - ], - ], - 'toolbar' => [ - 'entries' => [ - 'lmc_rbac' => 'laminas-developer-tools/toolbar/lmc-rbac', - ], - ], - ], - - 'lmc_rbac' => [ - // Guard plugin manager - 'guard_manager' => [], - - // Role provider plugin manager - 'role_provider_manager' => [], - - // Assertion plugin manager - 'assertion_manager' => [] - ] -]; diff --git a/data/FlatRole.php.dist b/data/FlatRole.php.dist deleted file mode 100644 index 109d6bba..00000000 --- a/data/FlatRole.php.dist +++ /dev/null @@ -1,114 +0,0 @@ -permissions = new ArrayCollection(); - } - - /** - * Get the role identifier - * - * @return int - */ - public function getId() - { - return $this->id; - } - - /** - * Set the role name - * - * @param string $name - * @return void - */ - public function setName($name) - { - $this->name = (string) $name; - } - - /** - * Get the role name - * - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * {@inheritDoc} - */ - public function addPermission($permission) - { - if (is_string($permission)) { - $permission = new Permission($permission); - } - - $this->permissions[(string) $permission] = $permission; - } - - /** - * {@inheritDoc} - */ - public function hasPermission($permission) - { - // This can be a performance problem if your role has a lot of permissions. Please refer - // to the cookbook to an elegant way to solve this issue - - return isset($this->permissions[(string) $permission]); - } -} diff --git a/data/HierarchicalRole.php.dist b/data/HierarchicalRole.php.dist deleted file mode 100644 index 9066025b..00000000 --- a/data/HierarchicalRole.php.dist +++ /dev/null @@ -1,146 +0,0 @@ -children = new ArrayCollection(); - $this->permissions = new ArrayCollection(); - } - - /** - * Get the role identifier - * - * @return int - */ - public function getId() - { - return $this->id; - } - - /** - * Set the role name - * - * @param string $name - * @return void - */ - public function setName($name) - { - $this->name = (string) $name; - } - - /** - * Get the role name - * - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * {@inheritDoc} - */ - public function addChild(HierarchicalRoleInterface $child) - { - $this->children[] = $child; - } - - /** - * {@inheritDoc} - */ - public function addPermission($permission) - { - if (is_string($permission)) { - $permission = new Permission($permission); - } - - $this->permissions[(string) $permission] = $permission; - } - - /** - * {@inheritDoc} - */ - public function hasPermission($permission) - { - // This can be a performance problem if your role has a lot of permissions. Please refer - // to the cookbook to an elegant way to solve this issue - - return isset($this->permissions[(string) $permission]); - } - - /** - * {@inheritDoc} - */ - public function getChildren() - { - return $this->children; - } - - /** - * {@inheritDoc} - */ - public function hasChildren() - { - return !$this->children->isEmpty(); - } -} diff --git a/data/Permission.php.dist b/data/Permission.php.dist deleted file mode 100644 index 945a65f2..00000000 --- a/data/Permission.php.dist +++ /dev/null @@ -1,69 +0,0 @@ -name = (string) $name; - } - - /** - * Get the permission identifier - * - * @return int - */ - public function getId() - { - return $this->id; - } - - /** - * {@inheritDoc} - */ - public function __toString() - { - return $this->name; - } -} diff --git a/data/README.md b/data/README.md deleted file mode 100644 index 67e21dd3..00000000 --- a/data/README.md +++ /dev/null @@ -1,14 +0,0 @@ -These files are only provided as-in, and are not part of LmcRbac. They provide you some basic Doctrine ORM -entities that you can use as a starting point. - -## Flat role or hierarchical role? - -As you can see, there are a FlatRole and HierarchicalRole entity classes. You must only use one of them, not both. - -The flat role is easier to follow, because each role contains all its permissions, and the database schema is easier -as you do not need a join table for the role hierarchy. - -On the other hand, the hierarchical role is much more flexible, and prevent you from duplicating the same permissions -into all roles. - -It really depends on your application. diff --git a/docs/01. Introduction.md b/docs/01. Introduction.md deleted file mode 100644 index f6449830..00000000 --- a/docs/01. Introduction.md +++ /dev/null @@ -1,57 +0,0 @@ -# Introduction - -Welcome to the official documentation of LmcRbacMvc! - -In this part, the following questions will be answered: - -* Why should I use an authorization module? -* What is the Rbac model? -* How can I integrate LmcRbacMvc into my application? - -## Why should I use an authorization module? - -The authorization part of an application is an essential aspect of securing your application. While the authentication -part tells you who is using your website, the authorization answers if the given identity has the permission to -perform specific actions. - -## What is the Rbac model? - -Rbac stands for **role-based access control**. We use a very simple (albeit powerful) implementation of this model -through the use of the [zf-fr/rbac](https://github.com/zf-fr/rbac) library. - - -The basic idea of Rbac is to use roles and permissions: - -* **Users** can have one or many **Roles** -* **Roles** request access to **Permissions** -* **Permissions** are granted to **Roles** - -By default, LmcRbacMvc can be used for two kinds of Rbac model: - -* Flat RBAC model: in this model, roles cannot have children. This is ideal for smaller applications, as it is easier -to understand, and the database design is simpler (no need for a join table). -* Hierarchical RBAC model: in this model, roles can have child roles. When evaluating if a given role has a -permission, this model also checks recursively if any of its child roles also have the permission. - - -## How can I integrate LmcRbacMvc into my application? - -LmcRbacMvc offers multiple ways to protect your application: - -* Using **Guards**: these classes act as "firewalls" that block access to routes and/or controllers. Guards are usually - configured using PHP arrays, and are executed early in the MVC dispatch process. Typically this happens right after - the route has been matched. -* Using **AuthorizationService**: a complementary method is to use the `AuthorizationService` class and inject it into your - service classes to protect them from unwanted access. - -While it is advised to use both methods to make your application even more secure, this is completely optional and you -can choose either of them independently. - -To find out about how you can easily make your existing application more secure, please refer to the following section: - -* [Cookbook: A real world example](07.%20Cookbook.md#a-real-world-application) - -### Navigation - -* Continue to [the **Quick Start**](02.%20Quick%20Start.md) -* Back to [the Index](README.md) diff --git a/docs/02. Quick Start.md b/docs/02. Quick Start.md deleted file mode 100644 index 77a555b2..00000000 --- a/docs/02. Quick Start.md +++ /dev/null @@ -1,139 +0,0 @@ -# Quick Start - -In this section, you will learn: - -* How to set up the module -* How to specify an identity provider -* How to add a simple role provider - -Before starting the quick start, make sure you have properly installed the module by following the instructions in -the README file. - -## Specifying an identity provider - -By default, LmcRbacMvc internally uses the `Laminas\Authentication\AuthenticationService` service key to retrieve the user (logged or -not). Therefore, you must implement and register this service in your application by adding these lines in your `module.config.php` file: - -```php -return [ - 'service_manager' => [ - 'factories' => [ - 'Laminas\Authentication\AuthenticationService' => function($sm) { - // Create your authentication service! - } - ] - ] -]; -``` -The identity given by `Laminas\Authentication\AuthenticationService` must implement `LmcRbacMvc\Identity\IdentityInterface`. Note that the default identity provided with Laminas does not implement this interface, neither does the LmcUser suite. - -LmcRbacMvc is flexible enough to use something other than the built-in `AuthenticationService`, by specifying custom -identity providers. For more information, refer [to this section](03.%20Role%20providers.md#identity-providers). - -## Adding a guard - -A guard allows your application to block access to routes and/or controllers using a simple syntax. For instance, this configuration -grants access to any route that begins with `admin` (or is exactly `admin`) to the `admin` role only: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\RouteGuard' => [ - 'admin*' => ['admin'] - ] - ] - ] -]; -``` - -LmcRbacMvc has several built-in guards, and you can also register your own guards. For more information, refer -[to this section](04.%20Guards.md#built-in-guards). - -## Adding a role provider - -RBAC model is based on roles. Therefore, for LmcRbacMvc to work properly, it must be aware of all the roles that are -used inside your application. - -This configuration creates an *admin* role that has a child role called *member*. The *admin* role automatically -inherits the *member* permissions. - -```php -return [ - 'lmc_rbac' => [ - 'role_provider' => [ - 'LmcRbacMvc\Role\InMemoryRoleProvider' => [ - 'admin' => [ - 'children' => ['member'], - 'permissions' => ['delete'] - ], - 'member' => [ - 'permissions' => ['edit'] - ] - ] - ] - ] -]; -``` - -In this example, the *admin* role has two permissions: `delete` and `edit` (because it inherits the permissions from -its child), while the *member* role only has the `edit` permission. - -LmcRbacMvc has several built-in role providers, and you can also register your own role providers. For more information, -refer [to this section](03.%20Role%20providers.md#built-in-role-providers). - -## Registering a strategy - -When a guard blocks access to a route/controller, or if you throw the `LmcRbacMvc\Exception\UnauthorizedException` -exception in your service, LmcRbacMvc automatically performs some logic for you depending on the view strategy used. - -For instance, if you want LmcRbacMvc to automatically redirect all unauthorized requests to the "login" route, add -the following code in the `onBootstrap` method of your `Module.php` class: - -```php -public function onBootstrap(MvcEvent $e) -{ - $app = $e->getApplication(); - $sm = $app->getServiceManager(); - $em = $app->getEventManager(); - - $listener = $sm->get(\LmcRbacMvc\View\Strategy\RedirectStrategy::class); - $listener->attach($em); -} -``` - -By default, `RedirectStrategy` redirects all unauthorized requests to a route named "login" when the user is not connected -and to a route named "home" when the user is connected. This is, of course, entirely configurable. - -> For flexibility purposes, LmcRbacMvc **does not** register any strategy for you by default! - -For more information about built-in strategies, refer [to this section](05.%20Strategies.md#built-in-strategies). - -## Using the authorization service - -Now that LmcRbacMvc is properly configured, you can inject the authorization service into any class and use it to check -if the current identity is granted to do something. - -The authorization service is registered inside the service manager using the following key: `LmcRbacMvc\Service\AuthorizationService`. -Once injected, you can use it as follow: - -```php -use LmcRbacMvc\Exception\UnauthorizedException; - -class ActionController extends \Laminas\Mvc\Controller\AbstractActionController { -public function delete() -{ - if (!$this->authorizationService->isGranted('delete')) { - throw new UnauthorizedException(); - } - - // Delete the post -} -} -``` - -### Navigation - -* Continue to [the **Role providers**](03.%20Role%20providers.md) -* Back to [the Introduction](01.%20Introduction.md) -* Back to [the Index](README.md) diff --git a/docs/03. Role providers.md b/docs/03. Role providers.md deleted file mode 100644 index f3adf3de..00000000 --- a/docs/03. Role providers.md +++ /dev/null @@ -1,200 +0,0 @@ -# Role providers - -In this section, you will learn: - -* What are role providers -* What are identity providers -* How to use and configure built-in providers -* How to create custom role providers - -## What are role providers? - -A role provider is an object that returns a list of roles. Each role provider must implement the -`LmcRbacMvc\Role\RoleProviderInterface` interface. The only required method is `getRoles`, and must return an array -of `Rbac\Role\RoleInterface` objects. - -Roles can come from one of many sources: in memory, from a file, from a database... However, please note that -you can specify only one role provider per application. The reason is that having multiple role providers makes -the workflow harder and can lead to security problems that are very hard to spot. - -## Identity providers? - -Identity providers return the current identity. Most of the time, this means the logged in user. LmcRbacMvc comes with a -default identity provider (`LmcRbacMvc\Identity\AuthenticationIdentityProvider`) that uses the -`Laminas\Authentication\AuthenticationService` service. - -### Create your own identity provider - -If you want to implement your own identity provider, create a new class that implements -`LmcRbacMvc\Identity\IdentityProviderInterface` class. Then, change the `identity_provider` option in LmcRbacMvc config, -as shown below: - -```php -return [ - 'lmc_rbac' => [ - 'identity_provider' => 'MyCustomIdentityProvider' - ] -]; -``` - -The identity provider is automatically pulled from the service manager. - -## Built-in role providers - -LmcRbacMvc comes with two built-in role providers: `InMemoryRoleProvider` and `ObjectRepositoryRoleProvider`. A role -provider must be added to the `role_provider` subkey: - -```php -return [ - 'lmc_rbac' => [ - 'role_provider' => [ - // Role provider config here! - ] - ] -]; -``` - -### InMemoryRoleProvider - -This provider is ideal for small/medium sites with few roles/permissions. All the data is specified in a simple -PHP file, so you never hit a database. - -Here is an example of the format you need to use: - -```php -return [ - 'lmc_rbac' => [ - 'role_provider' => [ - 'LmcRbacMvc\Role\InMemoryRoleProvider' => [ - 'admin' => [ - 'children' => ['member'], - 'permissions' => ['article.delete'] - ], - 'member' => [ - 'children' => ['guest'], - 'permissions' => ['article.edit', 'article.archive'] - ], - 'guest' => [ - 'permissions' => ['article.read'] - ] - ] - ] - ] -]; -``` - -The `children` and `permissions` subkeys are entirely optional. Internally, the `InMemoryRoleProvider` creates -either a `Rbac\Role\Role` object if the role does not have any children, or a `Rbac\Role\HierarchicalRole` if -the role has at least one child. - -If you are more confident with flat RBAC, the previous config can be re-written to remove any inheritence between roles: - -```php -return [ - 'lmc_rbac' => [ - 'role_provider' => [ - 'LmcRbacMvc\Role\InMemoryRoleProvider' => [ - 'admin' => [ - 'permissions' => [ - 'article.delete', - 'article.edit', - 'article.archive', - 'article.read' - ] - ], - 'member' => [ - 'permissions' => [ - 'article.edit', - 'article.archive', - 'article.read' - ] - ], - 'guest' => [ - 'permissions' => ['article.read'] - ] - ] - ] - ] -]; -``` - -### ObjectRepositoryRoleProvider - -This provider fetches roles from the database using `Doctrine\Common\Persistence\ObjectRepository` interface. - -You can configure this provider by giving an object repository service name that is fetched from the service manager -using the `object_repository` key: - -```php -return [ - 'lmc_rbac' => [ - 'role_provider' => [ - 'LmcRbacMvc\Role\ObjectRepositoryRoleProvider' => [ - 'object_repository' => 'App\Repository\RoleRepository', - 'role_name_property' => 'name' - ] - ] - ] -]; -``` - -Or you can specify the `object_manager` and `class_name` options: - -```php -return [ - 'lmc_rbac' => [ - 'role_provider' => [ - 'LmcRbacMvc\Role\ObjectRepositoryRoleProvider' => [ - 'object_manager' => 'doctrine.entitymanager.orm_default', - 'class_name' => 'App\Entity\Role', - 'role_name_property' => 'name' - ] - ] - ] -]; -``` - -In both cases, you need to specify the `role_name_property` value, which is the name of the entity's property -that holds the actual role name. This is used internally to only load the identity roles, instead of loading -the whole table every time. - -Please note that your entity fetched from the table MUST implement the `Rbac\Role\RoleInterface` interface. - -## Creating custom role providers - -To create a custom role provider, you first need to create a class that implements the `LmcRbacMvc\Role\RoleProviderInterface` -interface. - -Then, you need to add it to the role provider manager: - -```php -return [ - 'lmc_rbac' => [ - 'role_provider_manager' => [ - 'factories' => [ - 'Application\Role\CustomRoleProvider' => 'Application\Factory\CustomRoleProviderFactory' - ] - ] - ] -]; -``` - -You can now use it like any other role provider: - -```php -return [ - 'lmc_rbac' => [ - 'role_provider' => [ - 'Application\Role\CustomRoleProvider' => [ - // Options - ] - ] - ] -]; -``` - -### Navigation - -* Continue to [the **Guards**](04.%20Guards.md) -* Back to [the Quick Start](02.%20Quick%20Start.md) -* Back to [the Index](README.md) diff --git a/docs/04. Guards.md b/docs/04. Guards.md deleted file mode 100644 index 0d3f7a71..00000000 --- a/docs/04. Guards.md +++ /dev/null @@ -1,481 +0,0 @@ -# Guards - -In this section, you will learn: - -* What guards are -* How to use and configure built-in guards -* How to create custom guards - -## What are guards and when should you use them? - -Guards are listeners that are registered on a specific event of -the MVC workflow. They allow your application to quickly mark a request as unauthorized. - -Here is a simple workflow without guards: - -![Zend Framework workflow without guards](images/workflow-without-guards.png?raw=true) - -And here is a simple workflow with a route guard: - -![Zend Framework workflow with guards](images/workflow-with-guards.png?raw=true) - -RouteGuard and ControllerGuard are not aware of permissions but rather only think about "roles". For -instance, you may want to refuse access to each routes that begin by "admin/*" to all users that do not have the -"admin" role. - -If you want to protect a route for a set of permissions, you must use RoutePermissionsGuard. For instance, -you may want to grant access to a route "post/delete" only to roles having the "delete" permission. -Note that in a RBAC system, a permission is linked to a role, not to a user. - -Albeit simple to use, guards should not be the only protection in your application, and you should always -protect your services as well. The reason is that your business logic should be handled by your service. Protecting a given -route or controller does not mean that the service cannot be access from elsewhere (another action for instance). - -### Protection policy - -By default, when a guard is added, it will perform a check only on the specified guard rules. Any route or controller -that is not specified in the rules will be "granted" by default. Therefore, the default is a "blacklist" -mechanism. - -However, you may want a more restrictive approach (also called "whitelist"). In this mode, once a guard is added, -anything that is not explicitely added will be refused by default. - -For instance, let's say you have two routes: "index" and "login". If you specify a route guard rule to allow "index" -route to "member" role, your "login" route will become defacto unauthorized to anyone, unless you add a new rule for -allowing the route "login" to "member" role. - -You can change it in LmcRbacMvc config, as follows: - -```php -use LmcRbacMvc\Guard\GuardInterface; - -return [ - 'lmc_rbac' => [ - 'protection_policy' => GuardInterface::POLICY_DENY - ] -]; -``` - -> NOTE: this policy will block ANY route/controller (so it will also block any console routes or controllers). The -deny policy is much more secure, but it needs much more configuration to work with. - -## Built-in guards - -LmcRbacMvc comes with four guards, in order of priority : - -* RouteGuard : protect a set of routes based on the identity roles -* RoutePermissionsGuard : protect a set of routes based on roles permissions -* ControllerGuard : protect a controllers and/or actions based on the identity roles -* ControllerPermissionsGuard : protect a controllers and/or actions based on roles permissions - -All guards must be added in the `guards` subkey: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - // Guards config here! - ] - ] -]; -``` - -Because of the way Zend Framework 2 handles config, you can without problem define some rules in one module, and -more rules in another module. All the rules will be automatically merged. - -> For your mental health, I recommend you to use either the route guard OR the controller guard, but not both. If -you decide to use both conjointly, I recommend you to set the protection policy to "allow" (otherwise, you will -need to define rules for every routes AND every controller, which can become quite frustrating!). - -Please note that if your application uses both route and controller guards, route guards are always executed -**before** controller guards (they have a higher priority). - -### RouteGuard - -> The RouteGuard listens to the `MvcEvent::EVENT_ROUTE` event with a priority of -5. - -The RouteGuard allows your application to protect a route or a hierarchy of routes. You must provide an array of "key" => "value", -where the key is a route pattern and the value is an array of role names: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\RouteGuard' => [ - 'admin*' => ['admin'], - 'login' => ['guest'] - ] - ] - ] -]; -``` - -> Only one role in a rule needs to be matched (it is an OR condition). - -Those rules grant access to all admin routes to users that have the "admin" role, and grant access to the "login" -route to users that have the "guest" role (eg.: most likely unauthenticated users). - -> The route pattern is not a regex. It only supports the wildcard (*) character, that replaces any segment. - -You can also use the wildcard character * for roles: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\RouteGuard' => [ - 'home' => ['*'] - ] - ] - ] -]; -``` - -This rule grants access to the "home" route to anyone. - -Finally, you can also omit the roles array to completely block a route, for maintenance purpose for example : - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\RouteGuard' => [ - 'route_under_construction' - ] - ] - ] -]; -``` - -This rule will be inaccessible. - -Note : this last example could be (and should be) written in a more explicit way : - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\RouteGuard' => [ - 'route_under_construction' => [] - ] - ] - ] -]; -``` - - -### RoutePermissionsGuard - -> The RoutePermissionsGuard listens to the `MvcEvent::EVENT_ROUTE` event with a priority of -8. - -The RoutePermissionsGuard allows your application to protect a route or a hierarchy of routes. You must provide an array of "key" => "value", -where the key is a route pattern and the value is an array of permission names: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\RoutePermissionsGuard' => [ - 'admin*' => ['admin'], - 'post/manage' => ['post.update', 'post.delete'] - ] - ] - ] -]; -``` - -> By default, all permissions in a rule must be matched (an AND condition). - -In the previous example, one must have ```post.update``` **AND** ```post.delete``` permissions -to access the ```post/manage``` route. You can also specify an OR condition like so: - -```php -use LmcRbacMvc\Guard\GuardInterface; - -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\RoutePermissionsGuard' => [ - 'post/manage' => [ - 'permissions' => ['post.update', 'post.delete'], - 'condition' => GuardInterface::CONDITION_OR - ] - ] - ] - ] -]; -``` - -> Permissions are linked to roles, not to users - -Those rules grant access to all admin routes to roles that have the "admin" permission, and grant access to the -"post/delete" route to roles that have the "post.delete" or "admin" permissions. - -> The route pattern is not a regex. It only supports the wildcard (*) character, that replaces any segment. - -You can also use the wildcard character * for permissions: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\RoutePermissionsGuard' => [ - 'home' => ['*'] - ] - ] - ] -]; -``` - -This rule grants access to the "home" route to anyone. - -Finally, you can also use an empty array to completly block a route, for maintenance purpose for example : - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\RoutePermissionsGuard' => [ - 'route_under_construction' => [] - ] - ] - ] -]; -``` - -This route will be inaccessible. - - -### ControllerGuard - -> The ControllerGuard listens to the `MvcEvent::EVENT_ROUTE` event with a priority of -10. - -The ControllerGuard allows your application to protect a controller. You must provide an array of arrays: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\ControllerGuard' => [ - [ - 'controller' => 'MyController', - 'roles' => ['guest', 'member'] - ] - ] - ] - ] -]; -``` - -> Only one role in a rule need to be matched (it is an OR condition). - -Those rules grant access to each actions of the MyController controller to users that have either the "guest" or -"member" roles. - -As for RouteGuard, you can use a wildcard (*) character for roles. - -You can also specify optional actions, so that the rule only apply to one or several actions: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\ControllerGuard' => [ - [ - 'controller' => 'MyController', - 'actions' => ['read', 'edit'], - 'roles' => ['guest', 'member'] - ] - ] - ] - ] -]; -``` - -You can combine a generic rule and a specific action rule for the same controller, as follows: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\ControllerGuard' => [ - [ - 'controller' => 'PostController', - 'roles' => ['member'] - ], - [ - 'controller' => 'PostController', - 'actions' => ['delete'], - 'roles' => ['admin'] - ] - ] - ] - ] -]; -``` - -These rules grant access to each controller action to users that have the "member" role, but restrict the -"delete" action to "admin" only. - -### ControllerPermissionsGuard - -> The ControllerPermissionsGuard listens to the `MvcEvent::EVENT_ROUTE` event with a priority of -13. - -The ControllerPermissionsGuard allows your application to protect a controller using permissions. You must provide an array of arrays: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\ControllerPermissionsGuard' => [ - [ - 'controller' => 'MyController', - 'permissions' => ['post.update', 'post.delete'] - ] - ] - ] - ] -]; -``` - -> All permissions in a rule must be matched (it is an AND condition). - -In the previous example, the user must have ```post.update``` **AND** ```post.delete``` permissions -to access each action of the MyController controller. - -As for all other guards, you can use a wildcard (*) character for permissions. - -The configuration rules are the same as for ControllerGuard. - -### Security notice - -RouteGuard and ControllerGuard listen to the `MvcEvent::EVENT_ROUTE` event. Therefore, if you use the -`forward` method in your controller, those guards will not intercept and check requests (because internally -Laminas MVC does not trigger again a new MVC loop). - -Most of the time, this is not an issue, but you must be aware of it, and this is an additional reason why you -should always protect your services too. - -## Creating custom guards - -LmcRbacMvc is flexible enough to allow you to create custom guards. Let's say we want to create a guard that will -refuse access based on an IP addresses blacklist. - -First create the guard: - -```php -namespace Application\Guard; - -use Laminas\Http\Request as HttpRequest; -use Laminas\Mvc\MvcEvent; -use LmcRbacMvc\Guard\AbstractGuard; - -class IpGuard extends AbstractGuard -{ - const EVENT_PRIORITY = 100; - - /** - * List of IPs to blacklist - */ - protected $ipAddresses = []; - - /** - * @param array $ipAddresses - */ - public function __construct(array $ipAddresses) - { - $this->ipAddresses = $ipAddresses; - } - - /** - * @param MvcEvent $event - * @return bool - */ - public function isGranted(MvcEvent $event) - { - $request = $event->getRequest(); - - if (!$request instanceof HttpRequest) { - return true; - } - - $clientIp = $_SERVER['REMOTE_ADDR']; - - return !in_array($clientIp, $this->ipAddresses); - } -} -``` - -> Guards must implement `LmcRbacMvc\Guard\GuardInterface`. - -By default, guards are listening to the event `MvcEvent::EVENT_ROUTE` with a priority of -5 (you can change the default -event to listen by overriding the `EVENT_NAME` constant in your guard subclass). However, in this case, we don't -even need to wait for the route to be matched, so we overload the `EVENT_PRIORITY` constant to be executed earlier. - -The `isGranted` method simply retrieves the client IP address, and checks it against the blacklist. - -However, for this to work, we must register the newly created guard with the guard plugin manager. To do so, add the -following code in your config: - -```php -return [ - 'zfc_rbac' => [ - 'guard_manager' => [ - 'factories' => [ - 'Application\Guard\IpGuard' => 'Application\Factory\IpGuardFactory' - ] - ] - ] -]; -``` - -The `guard_manager` config follows a conventional service manager configuration format. - -Now, let's create the factory: - -```php -namespace Application\Factory; - -use Application\Guard\IpGuard; -use Laminas\ServiceManager\Factory\FactoryInterface; -use Laminas\ServiceManager\ServiceLocatorInterface; - -class IpGuardFactory implements FactoryInterface -{ - /** - * {@inheritDoc} - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - if (null === $options) { - $options = []; - } - return new IpGuard($options); - } -} -``` - -In a real use case, you would likely fetched the blacklist from a database. - -Now we just need to add the guard to the `guards` option, so that LmcRbacMvc can execute the logic behind this guard. In -your config, add the following code: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'Application\Guard\IpGuard' => [ - '87.45.66.46', - '65.87.35.43' - ] - ] - ] -]; -``` -The array of IP addresses will be passed to `IpGuardFactory::__invoke` in the `$options` parameter. - -### Navigation - -* Continue to [the **Strategies**](05.%20Strategies.md) -* Back to [the Role providers](03.%20Role%20providers.md) -* Back to [the Index](README.md) diff --git a/docs/05. Strategies.md b/docs/05. Strategies.md deleted file mode 100644 index fa6bdf26..00000000 --- a/docs/05. Strategies.md +++ /dev/null @@ -1,155 +0,0 @@ -# Strategies - -In this section, you will learn: - -* What strategies are -* How to use built-in strategies -* How to create custom strategies - -## What are strategies? - -A strategy is an object that listens to the `MvcEvent::EVENT_DISPATCH_ERROR` event. It is used to describe what -happens when access to a resource is unauthorized by LmcRbacMvc. - -LmcRbacMvc strategies all check if an `LmcRbacMvc\Exception\UnauthorizedExceptionInterface` has been thrown. - -By default, LmcRbacMvc does not register any strategy for you. The best place to register it is in your `onBootstrap` -method of the `Module.php` class: - -```php -public function onBootstrap(MvcEvent $e) -{ - $app = $e->getApplication(); - $sm = $app->getServiceManager(); - $em = $app->getEventManager(); - - $listener = $sm->get(\LmcRbacMvc\View\Strategy\UnauthorizedStrategy::class); - $listener->attach($em); -} -``` - -## Built-in strategies - -LmcRbacMvc comes with two built-in strategies: `RedirectStrategy` and `UnauthorizedStrategy`. - -### RedirectStrategy - -This strategy allows your application to redirect any unauthorized request to another route by optionally appending the previous -URL as a query parameter. - -To register it, copy-paste this code into your Module.php class: - -```php -public function onBootstrap(MvcEvent $e) -{ - $app = $e->getApplication(); - $sm = $app->getServiceManager(); - $em = $app->getEventManager(); - - $listener = $sm->get(\LmcRbacMvc\View\Strategy\RedirectStrategy::class); - $listener->attach($em); -} -``` - -You can configure the strategy using the `redirect_strategy` subkey: - -```php -return [ - 'lmc_rbac' => [ - 'redirect_strategy' => [ - 'redirect_when_connected' => true, - 'redirect_to_route_connected' => 'home', - 'redirect_to_route_disconnected' => 'login', - 'append_previous_uri' => true, - 'previous_uri_query_key' => 'redirectTo' - ], - ] -]; -``` - -If users try to access an unauthorized resource (eg.: http://www.example.com/delete), they will be -redirected to the "login" route if is not connected and to the "home" route otherwise (it must exist in your route configuration -of course) with the previous URL appended : http://www.example.com/login?redirectTo=http://www.example.com/delete - -You can prevent redirection when a user is connected (i.e. so that the user gets a 403 page) by setting `redirect_when_connected` to `false`. - -### UnauthorizedStrategy - -This strategy allows your application to render a template on any unauthorized request. - -To register it, copy-paste this code into your Module.php class: - -```php -public function onBootstrap(MvcEvent $e) -{ - $app = $e->getApplication(); - $sm = $app->getServiceManager(); - $em = $app->getEventManager(); - - $listener = $sm->get(\LmcRbacMvc\View\Strategy\UnauthorizedStrategy::class); - $listener->attach($em); -} -``` - -You can configure the strategy using the `unauthorized_strategy` subkey: - -```php -return [ - 'lmc_rbac' => [ - 'unauthorized_strategy' => [ - 'template' => 'error/custom-403' - ], - ] -]; -``` - -> By default, LmcRbacMvc uses a template called `error/403`. - -## Creating custom strategies - -Creating a custom strategy is rather easy. Let's say we want to create a strategy that integrates with -the [ApiProblem](https://github.com/laminas-api-tools/api-tools-api-problem) Laminas Api Tools module: - -```php -namespace Application\View\Strategy; - -use Laminas\Http\Response as HttpResponse; -use Laminas\Mvc\MvcEvent; -use Laminas\ApiTools\ApiProblem\ApiProblem; -use Laminas\ApiTools\ApiProblem\ApiProblemResponse; -use LmcRbacMvc\View\Strategy\AbstractStrategy; -use LmcRbacMvc\Exception\UnauthorizedExceptionInterface; - -class ApiProblemStrategy extends AbstractStrategy -{ - public function onError(MvcEvent $event) - { - // Do nothing if no error or if response is not HTTP response - if (!($exception = $event->getParam('exception') instanceof UnauthorizedExceptionInterface) - || ($result = $event->getResult() instanceof HttpResponse) - || !($response = $event->getResponse() instanceof HttpResponse) - ) { - return; - } - - return new ApiProblemResponse(new ApiProblem($exception->getMessage())); - } -} -``` - -Register your strategy: - -```php -public function onBootstrap(EventInterface $e) -{ - $e->getTarget() - ->getEventManager() - ->attach(new ApiProblemStrategy()); -} -``` - -### Navigation - -* Continue to [the **Authorization Service**](06.%20Using%20the%20Authorization%20Service.md) -* Back to [the Guards](04.%20Guards.md) -* Back to [the Index](README.md) diff --git a/docs/06. Using the Authorization Service.md b/docs/06. Using the Authorization Service.md deleted file mode 100644 index 7c8a6b2c..00000000 --- a/docs/06. Using the Authorization Service.md +++ /dev/null @@ -1,275 +0,0 @@ -# Using the Authorization Service - -This section will teach you how to use the AuthorizationService to its full extent. - -## Injecting the Authorization Service - -### Using initializers - -To automatically inject the authorization service into your classes, you can implement the -`AuthorizationServiceAwareInterface` and use the trait, as shown below: - -```php -namespace YourModule; - -use LmcRbacMvc\Service\AuthorizationServiceAwareInterface; -use LmcRbacMvc\Service\AuthorizationServiceAwareTrait; - -class MyClass implements AuthorizationServiceAwareInterface -{ - use AuthorizationServiceAwareTrait; - - public function doSomethingThatRequiresAuth() - { - if (! $this->getAuthorizationService()->isGranted('deletePost')) { - throw new UnauthorizedException('You are not allowed !'); - } - - return true; - } -} -``` - -Then, register the initializer in your config (it is not registered by default): - -```php -class Module -{ - // ... - - public function getServiceConfig() - { - return [ - 'initializers' => [ - 'LmcRbacMvc\Initializer\AuthorizationServiceInitializer' - ] - ]; - } -} -``` - -> While initializers allow rapid prototyping, their use can lead to more fragile code. We'd suggest using factories. - -### Using delegator factory - -LmcRbacMvc is shipped with a `LmcRbacMvc\Factory\AuthorizationServiceDelegatorFactory` [delegator factory](https://docs.laminas.dev/laminas-servicemanager/delegators/) -to automatically inject the authorization service into your classes. - -As for the initializer, the class must implement the `AuthorizationServiceAwareInterface`. - -You just have to add your classes to the right delegator : - -```php -class Module -{ - // ... - - public function getServiceConfig() - { - return [ - 'invokables' => [ - 'Application\Service\MyClass' => 'Application\Service\MyClassService', - ], - 'delegators' => [ - 'Application\Service\MyClass' => [ - 'LmcRbacMvc\Factory\AuthorizationServiceDelegatorFactory', - // eventually add more delegators here - ], - ], - ]; - } -} -``` - -> While they need a little more configuration, delegator factories have better performances than initializers. - -### Using Factories - -You can inject the AuthorizationService into your factories by using Laminas' ServiceManager. The AuthorizationService -is known to the ServiceManager as `'LmcRbacMvc\Service\AuthorizationService'`. Here is a classic example for injecting -the AuthorizationService: - -*YourModule/Module.php* - -```php -class Module -{ - // getAutoloaderConfig(), etc... - - public function getServiceConfig() - { - return [ - 'factories' => [ - 'MyService' => function($sm) { - $authService = $sm->get('LmcRbacMvc\Service\AuthorizationService'); - return new MyService($authService); - } - ] - ]; - } -} -``` - -### Using Laminas\DI - -DI is a great way for prototyping, getting results *fast* and maintaining a flexible structure. However it adds overhead and can get very slow. Unless you are using a compiler it is **not** recommended for production. -Here's how you enable Laminas\DI to inject the AuthorizationService in MyClass: - -*YourModule/Module.php* - -```php -namespace YourModule; - -class Module -{ - // getAutoloaderConfig(), etc... - - public function getConfig() - { - return [ - 'di' => [ - 'definition' => [ - 'class' => [ - __NAMESPACE__ . '\MyClass' => [ - 'setAuthorizationService' => [ - 'required' => true - ] - ] - ] - ] - ] - ]; - } -} -``` - -## Permissions and Assertions - -Since you now know how to inject the AuthorizationService, let's use it! - -One of the great things the AuthorizationService brings are **assertions**. Assertions get executed *if the identity -in fact holds the permission you are requesting*. A common example is a blog post, which only the author can edit. In -this case, you have a `post.edit` permission and run an assertion checking the author afterwards. - -### Defining assertions - -The AssertionPluginManager is a great way for you to use assertions and IOC. You can add new assertions quite easily -by adding this to your `module.config.php` file: - -```php -return [ - 'lmc_rbac' => [ - 'assertion_manager' => [ - 'factories' => [ - 'MyAssertion' => 'MyAssertionFactory' - ] - ] - ] -]; -``` - -### Defining the assertion map - -The assertion map can automatically map permissions to assertions. This means that every time you check for a -permission with an assertion map, you'll include the assertion in your check. You can define the assertion map by -adding this to your `module.config.php` file: - -```php -return [ - 'lmc_rbac' => [ - 'assertion_map' => [ - 'myPermission' => 'myAssertion' - ] - ] -]; -``` - -Now, every time you check for `myPermission`, `myAssertion` will be checked as well. - -### Checking permissions in a service - -So let's check for a permission, shall we? - -```php -$authorizationService->isGranted('myPermission'); -``` - -That was easy, wasn't it? - -`isGranted` checks if the current identity is granted the permission and additionally runs the assertion that is -provided by the assertion map. - -### Checking permissions in controllers and views - -LmcRbacMvc comes with both a controller plugin and a view helper to check permissions. - -#### In a controller : - -```php - public function doSomethingAction() - { - if (!$this->isGranted('myPermission')) { - // redirect if not granted for example - } - } -``` - -#### In a view : - -```php - isGranted('myPermission')): ?> -
-

Display only if granted

-
- -``` - -### Defining additional permissions - -But what if you don't want to use the assertion map? That's quite easy as well! - -Here are four examples of how to run an assertion without using the assertion map: - -Disable the assertion: - -```php -$authorizationService->setAssertion('myPermission', null); -$authorizationService->isGranted('myPermission'); -``` - -Callback assertion: -```php -$something = true; - -$authorizationService->setAssertion( - 'myPermission', - function(AuthorizationService $authorization, $context = true) use ($something) { - return $something === $context - } -); - -$authorizationService->isGranted('myPermission'); // returns true, when the identity holds the permission `myPermission` -``` - -Object implementing `AssertionInterface`: -```php -$context = true; - -$authorizationService->setAssertion('myPermission', new MyAssertion($foo, $bar)); -$authorizationService->isGranted('myPermission', $context); -``` - -Using the AssertionPluginManager: -```php -$context = true; -$authorizationService->setAssertion('myPermission', 'MyAssertion'); -$authorizationService->isGranted('myPermission', $context); -``` - -*Please note: The context parameter is optional!* - -### Navigation - -* Continue to [the **Cookbook**](07.%20Cookbook.md) -* Back to [the Strategies](05.%20Strategies.md) -* Back to [the Index](README.md) diff --git a/docs/07. Cookbook.md b/docs/07. Cookbook.md deleted file mode 100644 index 1e1e02e2..00000000 --- a/docs/07. Cookbook.md +++ /dev/null @@ -1,779 +0,0 @@ -# Cookbook - -This section will help you further understand how LmcRbacMvc works by providing more concrete examples. If you have -any other recipe you'd like to add, please open an issue! - -- [A Real World Application](07.%20Cookbook.md#a-real-world-application) - - [When to use Guards](07.%20Cookbook.md#when-using-guards-then) -- [A Real World Application Part 2 - Only delete your own Posts](07.%20Cookbook.md#a-real-world-application-part-2---only-delete-your-own-posts) -- [A Real World Application Part 3 - But Admins can delete everything](07.%20Cookbook.md#a-real-world-application-part-3---admins-can-delete-everything) -- [A Real World Application Part 4 - Only admins have the menu item for managing the posts](07.%20Cookbook.md#user-content-a-real-world-application-part-4---checking-permissions-in-the-view) -- [Using LmcRbacMvc with Doctrine ORM](07.%20Cookbook.md#using-lmcrbac-with-doctrine-orm) - - [How to deal with roles with lot of permissions?](07.%20Cookbook.md#how-to-deal-with-roles-with-lot-of-permissions) -- [Using LmcRbacMvc and ZF2 Assetic](07.%20Cookbook.md#using-lmcrbacmvc-and-zf2-assetic) -- [Using LmcRbacMvc and LmcUser](07.%20Cookbook.md#using-lmcrbacmvc-and-lmcuser) - -## A Real World Application - -In this example we are going to create a very little real world application. We will create a controller -`PostController` that interacts with a service called `PostService`. For the sake of simplicity we will only -cover the `delete`-methods of both parts. - -Let's start by creating a controller that has the `PostService` as dependency: - -```php -class PostController -{ - protected $postService; - - public function __construct(PostService $postService) - { - $this->postService = $postService; - } - - // addAction(), editAction(), etc... - - public function deleteAction() - { - $id = $this->params()->fromQuery('id'); - - $this->postService->deletePost($id); - - return $this->redirect()->toRoute('posts'); - } -} -``` - -Since we have a dependency, let's inject it using the `ControllerManager`, we will do this inside our `Module` class - -```php -class Module -{ - public function getConfig() - { - return [ - 'controllers' => [ - 'factories' => [ - 'PostController' => function ($cpm) { - // We assume a Service key 'PostService' here that returns the PostService Class - return new PostController( - $cpm->getServiceLocator()->get('PostService') - ); - }, - ], - ], - ]; - } -} -``` - -Now that we have this in place let us quickly define our `PostService`. We will be using a Service that makes use -of Doctrine, so we require a `Doctrine\Persistence\ObjectManager` as dependency. - -```php -use Doctrine\Persistence\ObjectManager; - -class PostService -{ - protected $objectManager; - - public function __construct(ObjectManager $objectManager) - { - $this->objectManager = $objectManager; - } - - public function deletePost($id) - { - $post = $this->objectManager->find('Post', $id); - $this->objectManager->remove($post); - $this->objectManager->flush(); - } -} -``` - -And for this one, too, let's quickly create the factory, again within our `Module` class. - -```php -class Module -{ - // getAutoloaderConfig(), getConfig(), etc... - - public function getServiceConfig() - { - return [ - 'factories' => [ - 'PostService' => function($sm) { - return new PostService( - $sm->get('doctrine.entitymanager.orm_default') - ); - } - ] - ]; - } -} -``` - -With this set up we can now cover some best practices. - -## Best practices - -Ideally, you should not protect your applications using only guards (Route or Controller guards). -This leaves your application open for some undesired side-effects. -As a best practice you should protect all your services or controllers by injecting the authorization service. -But let's go step by step: - -Assuming the application example above we can easily use LmcRbacMvc to protect our route using the following guard: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\RouteGuard' => [ - 'post/delete' => ['admin'] - ] - ] - ] -]; -``` - -Now, any users that do not have the "admin" role will receive a 403 error (unauthorized) when trying to access -the "post/delete" route. However, this does not prevent the service (which should contain the actual logic in a properly -design application) to be injected and used elsewhere in your code. For instance: - -```php -class PostController -{ - protected $postService; - - public function createAction() - { - // this action may have been reached through the "forward" method, hence bypassing guards - $this->postService->deletePost('2'); - } -} -``` - -You see the issue! - -The solution is to inject the `AuthorizationService` into your services and check for the -permissions before doing anything wrong. So let's modify our previously created `PostService` class - -```php -use Doctrine\Persistence\ObjectManager; -use LmcRbacMvc\Service\AuthorizationService; - -class PostService -{ - protected $objectManager; - - protected $authorizationService; - - public function __construct( - ObjectManager $objectManager, - AuthorizationService $autorizationService - ) { - $this->objectManager = $objectManager; - $this->authorizationService = $autorizationService; - } - - public function deletePost($id) - { - // First check permission - if (!$this->authorizationService->isGranted('deletePost')) { - throw new UnauthorizedException('You are not allowed !'); - } - - $post = $this->objectManager->find('Post', $id); - $this->objectManager->remove($post); - $this->objectManager->flush(); - } -} -``` - -Since we now have an additional dependency we should inject it through our factory, again within our `Module` class. - -```php -class Module -{ - // getAutoloaderConfig(), getConfig(), etc... - - public function getServiceConfig() - { - return [ - 'factories' => [ - 'PostService' => function($sm) { - return new PostService( - $sm->get('doctrine.entitymanager.orm_default'), - $sm->get('LmcRbacMvc\Service\AuthorizationService') // This is new! - ); - } - ] - ]; - } -} -``` - -Alternatively, you can also protect your controllers using the `isGranted` helper (you do not need to inject -the AuthorizationService then): - -```php -class PostController -{ - protected $postService; - - public function createAction() - { - if (!$this->isGranted('deletePost')) { - throw new UnauthorizedException('You are not allowed !'); - } - - $this->postService->deletePost('2'); - } -} -``` - -While protecting services is the more defensive way (because services are usually the last part of the logic flow), -it is often complicated to deal with. -If your application is architectured correctly, it is often simpler to protect your controllers. - -### When using guards then? - -In fact, you should see guards as a very efficient way to quickly reject access to a hierarchy of routes or a -whole controller. For instance, assuming you have the following route config: - -```php -return [ - 'router' => [ - 'routes' => [ - 'admin' => [ - 'type' => 'Literal', - 'options' => [ - 'route' => '/admin' - ], - 'may_terminate' => true, - 'child_routes' => [ - 'users' => [ - 'type' => 'Literal', - 'options' => [ - 'route' => '/users' - ] - ], - 'invoices' => [ - 'type' => 'Literal', - 'options' => [ - 'route' => '/invoices' - ] - ] - ] - ] - ] - ] -}; -``` - -You can quickly reject access to all admin routes using the following guard: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbacMvc\Guard\RouteGuard' => [ - 'admin*' => ['admin'] - ] - ] - ] -]; -``` - -## A Real World Application Part 2 - Only delete your own Posts - -If you jumped straight to this section please notice that we assume you have the knowledge that we presented in the -previous example. In here we will cover a very common use case. Users of our Application should only have delete -permissions to their own content. So let's quickly refresh our `PostService` class: - -```php -use Doctrine\Persistence\ObjectManager; - -class PostService -{ - protected $objectManager; - - protected $authorizationService; - - public function __construct( - ObjectManager $objectManager, - AuthorizationService $autorizationService - ) { - $this->objectManager = $objectManager; - $this->authorizationService = $autorizationService; - } - - public function deletePost($id) - { - // First check permission - if (!$this->authorizationService->isGranted('deletePost')) { - throw new UnauthorizedException('You are not allowed !'); - } - - $post = $this->objectManager->find('Post', $id); - $this->objectManager->remove($post); - $this->objectManager->flush(); - } -} -``` - -As we can see, we check within our Service if the User of our Application is allowed to delete the post with a check -against the `deletePost` permission. Now how can we achieve that only a user who is the owner of the Post to be able to -delete his own post, but other users can't? We do not want to change our Service with more complex logic because this -is not the task of such service. The Permission-System should handle this. And we can, for this we have the - [`AssertionPluginManager`](/LmcRbacMvc/Assertion/AssertionPluginManager.php) and here is how to do it: - -First of all we need to write an Assertion. The Assertion will return a boolean statement about the current -identity being the owner of the post. - -```php -namespace Your\Namespace; - -use LmcRbacMvc\Assertion\AssertionInterface; -use LmcRbacMvc\Service\AuthorizationService; - -class MustBeAuthorAssertion implements AssertionInterface -{ - /** - * Check if this assertion is true - * - * @param AuthorizationService $authorization - * @param mixed $post - * - * @return bool - */ - public function assert(AuthorizationService $authorization, $post = null) - { - return $authorization->getIdentity() === $post->getAuthor(); - } -} -``` - -This simple `MustBeAuthorAssertion` will check against the current `$authorization` if it equals the identity of the -current context Author. The second parameter is called the "context". A context can be anything (an object, a scalar, -an array...) and only makes sense in the context of the assertion. - -Imagine a user calls `http://my.dom/post/delete/42`, so obviously he wants to delete the Post-Entity with ID#42. In -this case Entity#42 is our Context! If you're wondering how the context gets there, bare with me. We will get to -this later. - -Now that we have written the Assertion, we want to make sure that this assertion will always be called, whenever we -check for the `deletePost` permission. We don't want others to delete our previous content! For this we have the so -called `assertion_map`. In this map we glue `assertions` and `permissions` together. - -```php -// module.config.php or wherever you configure your RBAC stuff -return [ - 'lmc_rbac' => [ - 'assertion_map' => [ - 'deletePost' => 'Your\Namespace\MustBeAuthorAssertion' - ] - ] -]; -``` - -Now, whenever some test the `deletePost` permission, it will automatically call the `MustBeAuthorAssertion` from -the `AssertionPluginManager`. This plugin manager is configured to automatically add unknown classes to an invokable. -However, some assertions may need dependencies. You can manually configure the assertion plugin manager as -shown below: - -```php -// module.config.php or wherever you configure your RBAC stuff -return [ - 'lmc_rbac' => [ - // ... other rbac stuff - 'assertion_manager' => [ - 'factories' => [ - 'AssertionWithDependency' => 'Your\Namespace\AssertionWithDependencyFactory' - ] - ] - ] -]; -``` - -Now we need to remember about the **context**. Somehow we need to let the `AssertionPluginManager` know about our -context. This is done by simply passing it to the `isGranted()` method. For this we need to modify our Service -one last time. - -```php -use Doctrine\Persistence\ObjectManager; - -class PostService -{ - protected $objectManager; - - protected $authorizationService; - - public function __construct( - ObjectManager $objectManager, - AuthorizationService $autorizationService - ) { - $this->objectManager = $objectManager; - $this->authorizationService = $autorizationService; - } - - public function deletePost($id) - { - // Note, we now need to query for the post of interest first! - $post = $this->objectManager->find('Post', $id); - - // Check the permission now with a given context - if (!$this->authorizationService->isGranted('deletePost', $post)) { - throw new UnauthorizedException('You are not allowed !'); - } - - $this->objectManager->remove($post); - $this->objectManager->flush(); - } -} -``` - -And there you have it. The context is injected into the `isGranted()` method and now the `AssertionPluginManager` knows -about it and can do its thing. Note that in reality, after you have queried for the `$post` you would check if `$post` -is actually a real post. Because if it is an empty return value then you should throw an exception earlier without -needing to check against the permission. - -## A Real World Application Part 3 - Admins can delete everything - -Often, you want users with a specific role to be able to have full access to everything. For instance, admins could -delete all the posts, even if they don't own it. - -However, with the previous assertion, even if the admin has the permission `deletePost`, it won't work because -the assertion will evaluate to false. - -Actually, the answer is quite simple: deleting my own posts and deleting others' posts should be treated like -two different permissions (it makes sense if you think about it). Therefore, admins will have the permission -`deleteOthersPost` (as well as the permission `deletePost`, because admin could write posts, too). - -The assertion must therefore be modified like this: - -```php -namespace Your\Namespace; - -use LmcRbacMvc\Assertion\AssertionInterface; -use LmcRbacMvc\Service\AuthorizationService; - -class MustBeAuthorAssertion implements AssertionInterface -{ - /** - * Check if this assertion is true - * - * @param AuthorizationService $authorization - * @param mixed $context - * - * @return bool - */ - public function assert(AuthorizationService $authorization, $context = null) - { - if ($authorization->getIdentity() === $context->getAuthor()) { - return true; - } - - return $authorization->isGranted('deleteOthersPost'); - } -} -``` - -## A Real World Application Part 4 - Checking permissions in the view - -If some part of the view needs to be protected, you can use the shipped ```isGranted``` view helper. - -For example, lets's say that only users with the permissions ```post.manage``` will have a menu item to acces -the adminsitration panel : - -In your template post-index.phtml - -```php - -``` - -You can even protect your menu item regarding a role, by using the ```hasRole``` view helper : - -```php - -``` - -In this last example, the menu item will be hidden for users who don't have the ```admin``` role. - -## Using LmcRbacMvc with Doctrine ORM - -First your User entity class must implement `LmcRbacMvc\Identity\IdentityInterface` : - -```php -use LmccUser\Entity\User as LmcUserEntity; -use LmcRbacMvc\Identity\IdentityInterface; -use Doctrine\ORM\Mapping as ORM; -use Doctrine\Common\Collections\ArrayCollection; -use Doctrine\Common\Collections\Collection; - -/** - * @ORM\Entity - * @ORM\Table(name="user") - */ -class User extends LmcUserEntity implements IdentityInterface -{ - /** - * @var Collection - * @ORM\ManyToMany(targetEntity="HierarchicalRole") - */ - private $roles; - - public function __construct() - { - $this->roles = new ArrayCollection(); - } - - /** - * {@inheritDoc} - */ - public function getRoles() - { - return $this->roles->toArray(); - } - - /** - * Set the list of roles - * @param Collection $roles - */ - public function setRoles(Collection $roles) - { - $this->roles->clear(); - foreach ($roles as $role) { - $this->roles[] = $role; - } - } - - /** - * Add one role to roles list - * @param \Rbac\Role\RoleInterface $role - */ - public function addRole(RoleInterface $role) - { - $this->roles[] = $role; - } -} -``` -For this example we will use a more complex situation by using `Rbac\Role\HierarchicalRoleInterface` so the second step is to create HierarchicalRole entity class - -```php -class HierarchicalRole implements HierarchicalRoleInterface -{ - /** - * @var HierarchicalRoleInterface[]|\Doctrine\Common\Collections\Collection - * - * @ORM\ManyToMany(targetEntity="HierarchicalRole") - */ - protected $children; - - /** - * @var PermissionInterface[]|\Doctrine\Common\Collections\Collection - * - * @ORM\ManyToMany(targetEntity="Permission", indexBy="name", fetch="EAGER", cascade={"persist"}) - */ - protected $permissions; - - /** - * Init the Doctrine collection - */ - public function __construct() - { - $this->children = new ArrayCollection(); - $this->permissions = new ArrayCollection(); - } - - /** - * {@inheritDoc} - */ - public function addChild(HierarchicalRoleInterface $child) - { - $this->children[] = $child; - } - - /* - * Set the list of permission - * @param Collection $permissions - */ - public function setPermissions(Collection $permissions) - { - $this->permissions->clear(); - foreach ($permissions as $permission) { - $this->permissions[] = $permission; - } - } - - /** - * {@inheritDoc} - */ - public function addPermission($permission) - { - if (is_string($permission)) { - $permission = new Permission($permission); - } - - $this->permissions[(string) $permission] = $permission; - } - - /** - * {@inheritDoc} - */ - public function hasPermission($permission) - { - // This can be a performance problem if your role has a lot of permissions. Please refer - // to the cookbook to an elegant way to solve this issue - - return isset($this->permissions[(string) $permission]); - } - - /** - * {@inheritDoc} - */ - public function getChildren() - { - return $this->children->toArray(); - } - - /** - * {@inheritDoc} - */ - public function hasChildren() - { - return !$this->children->isEmpty(); - } -} -``` - -And the last step is to create a Permission entity class which is a very simple entity class. You don't have to do specific things! - -You can find all entity examples in this folder : [Example](/data) - -You need one more configuration step. Indeed, how can the RoleProvider retrieve your role and permissions? For this you need to configure `LmcRbacMvc\Role\ObjectRepositoryRoleProvider` in your `lmc_rbac.global.php` file : -```php - /** - * Configuration for role provider - */ - 'role_provider' => [ - 'LmcRbacMvc\Role\ObjectRepositoryRoleProvider' => [ - 'object_manager' => 'doctrine.entitymanager.orm_default', // alias for doctrine ObjectManager - 'class_name' => 'User\Entity\HierarchicalRole', // FQCN for your role entity class - 'role_name_property' => 'name', // Name to show - ], - ], -``` - -Using DoctrineORM with LmcRbacMvc is very simple. You need to be aware of performance where there is a lot of permissions for roles. - -## How to deal with roles with lot of permissions? - -In very complex applications, your roles may have dozens of permissions. In the [/data/FlatRole.php.dist] entity -we provide, we configure the permissions association so that whenever a role is loaded, all of its permissions are also -loaded in one query (notice the `fetch="EAGER"`): - -```php -/** - * @ORM\ManyToMany(targetEntity="Permission", indexBy="name", fetch="EAGER") - */ -protected $permissions; -``` - -The `hasPermission` method is therefore really simple: - -```php -public function hasPermission($permission) -{ - return isset($this->permissions[(string) $permission]); -} -``` - -However, with a lot of permissions, this method will quickly kill your database. What you can do is modfiy the Doctrine -mapping so that the collection is not actually loaded: - -```php -/** - * @ORM\ManyToMany(targetEntity="Permission", indexBy="name", fetch="LAZY") - */ -protected $permissions; -``` - -Then, modify the `hasPermission` method to use the Criteria API. The Criteria API is a Doctrine 2.2+ API that allows -your application to efficiently filter a collection without loading the whole collection: - -```php -use Doctrine\Common\Collections\Criteria; - -public function hasPermission($permission) -{ - $criteria = Criteria::create()->where(Criteria::expr()->eq('name', (string) $permission)); - $result = $this->permissions->matching($criteria); - - return count($result) > 0; -} -``` - -> NOTE: This is only supported starting from Doctrine ORM 2.5! - -## Using LmcRbacMvc and ZF2 Assetic - -To use [Assetic](https://github.com/widmogrod/zf2-assetic-module) with LmcRbacMvc guards, you should modify your -`module.config.php` using the following configuration: - -```php -return [ - 'assetic_configuration' => [ - 'acceptableErrors' => [ - \LmcRbacMvc\Guard\GuardInterface::GUARD_UNAUTHORIZED - ] - ] -]; -``` - -## Using LmcRbacMvc and LmcUser - -To use the authentication service from LmcUser, just add the following alias in your application.config.php: - -```php -return [ - 'service_manager' => [ - 'aliases' => [ - 'Laminas\Authentication\AuthenticationService' => 'lmcuser_auth_service' - ] - ] -]; -``` - -Finally add the LmcUser routes to your `guards`: - -```php -return [ - 'lmc_rbac' => [ - 'guards' => [ - 'LmcRbac\Guard\RouteGuard' => [ - 'lmcuser/login' => ['guest'], - 'lmcuser/register' => ['guest'], // required if registration is enabled - 'lmcuser*' => ['user'] // includes logout, changepassword and changeemail - ] - ] - ] -]; -``` - -### Navigation - -* Back to [the Using the Authorization Service](06.%20Using%20the%20Authorization%20Service.md) -* Back to [the Index](README.md) diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index ccbb364d..00000000 --- a/docs/README.md +++ /dev/null @@ -1,48 +0,0 @@ -Welcome to the official LmcRbacMvc documentation. This documentation will help you quickly understand how to use -and extend LmcRbacMvc. - -If you are looking for some information that is not listed in the documentation, please open an issue! - -1. [Introduction](01.%20Introduction.md) - 1. [Why should I use an authorization module?](01.%20Introduction.md#why-should-i-use-an-authorization-module) - 2. [What is the Rbac model?](01.%20Introduction.md#what-is-the-rbac-model) - 3. [How can I integrate LmcRbacMvc into my application?](01.%20Introduction.md#how-can-i-integrate-lmcrbacmvc-into-my-application) - -2. [Quick Start](02.%20Quick%20Start.md) - 1. [Specifying an identity provider](02.%20Quick%20Start.md#specifying-an-identity-provider) - 2. [Adding a guard](02.%20Quick%20Start.md#adding-a-guard) - 3. [Adding a role provider](02.%20Quick%20Start.md#adding-a-role-provider) - 5. [Registering a strategy](02.%20Quick%20Start.md#registering-a-strategy) - 6. [Using the authorization service](02.%20Quick%20Start.md#using-the-authorization-service) - -3. [Role providers](03.%20Role%20providers.md) - 1. [What are role providers?](03.%20Role%20providers.md#what-are-role-providers) - 2. [Identity providers](03.%20Role%20providers.md#identity-providers) - 3. [Built-in role providers](03.%20Role%20providers.md#built-in-role-providers) - 4. [Creating custom role providers](03.%20Role%20providers.md#creating-custom-role-providers) - -4. [Guards](04.%20Guards.md) - 1. [What are guards and when to use them?](04.%20Guards.md#what-are-guards-and-when-should-you-use-them) - 2. [Built-in guards](04.%20Guards.md#built-in-guards) - 3. [Creating custom guards](04.%20Guards.md#creating-custom-guards) - -5. [Strategies](05.%20Strategies.md) - 1. [What are strategies?](05.%20Strategies.md#what-are-strategies) - 2. [Built-in strategies](05.%20Strategies.md#built-in-strategies) - 3. [Creating custom strategies](05.%20Strategies.md#creating-custom-strategies) - -6. [Using the Authorization Service](06.%20Using%20the%20Authorization%20Service.md) - 1. [Injecting the AuthorizationService](06.%20Using%20the%20Authorization%20Service.md#injecting-the-authorization-service) - 2. [Checking permissions](06.%20Using%20the%20Authorization%20Service.md#checking-permissions-in-a-service) - 1. [In a service](06.%20Using%20the%20Authorization%20Service.md#checking-permissions-in-a-service) - 2. [In a controller's action using the isGranted controller plugin](06.%20Using%20the%20Authorization%20Service.md#in-a-controller-) - 3. [In a view using the isGranted view helper](06.%20Using%20the%20Authorization%20Service.md#in-a-view-) - 3. [Permissions and Assertions](06.%20Using%20the%20Authorization%20Service.md#permissions-and-assertions) - -7. [Cookbook](07.%20Cookbook.md) - 1. [A real world example](07.%20Cookbook.md#a-real-world-application) - 2. [Best practices](07.%20Cookbook.md#best-practices) - 3. [Using LmcRbacMvc with Doctrine ORM](07.%20Cookbook.md#using-lmcrbacmvc-with-doctrine-orm) - 4. [How to deal with roles with lot of permissions?](07.%20Cookbook.md#how-to-deal-with-roles-with-lot-of-permissions) - 5. [Using LmcRbacMvc and ZF2 Assetic](07.%20Cookbook.md#using-lmcrbacmvc-and-zf2-assetic) - 6. [Using LmcRbacMvc and LmcUser](07.%20Cookbook.md#using-lmcrbacmvc-and-lmcuser) diff --git a/docs/cookbook/index.html b/docs/cookbook/index.html new file mode 100644 index 00000000..595a242b --- /dev/null +++ b/docs/cookbook/index.html @@ -0,0 +1,149 @@ + + + + + +Cookbook | LmcRbacMvc + + + + + +
Skip to main content

Cookbook

+

This section will help you further understand how LmcRbacMvc works by providing more concrete examples. If you have +any other recipe you'd like to add, please open an issue!

+

A Real World Application

+

In this example we are going to create a very little real world application. We will create a controller +PostController that interacts with a service called PostService. For the sake of simplicity we will only +cover the delete-methods of both parts.

+

Let's start by creating a controller that has the PostService as dependency:

+
class PostController
{
protected $postService;

public function __construct(PostService $postService)
{
$this->postService = $postService;
}

// addAction(), editAction(), etc...

public function deleteAction()
{
$id = $this->params()->fromQuery('id');

$this->postService->deletePost($id);

return $this->redirect()->toRoute('posts');
}
}
+

Since we have a dependency, let's inject it using the ControllerManager, we will do this inside our Module class

+
class Module
{
public function getConfig()
{
return [
'controllers' => [
'factories' => [
'PostController' => function ($cpm) {
// We assume a Service key 'PostService' here that returns the PostService Class
return new PostController(
$cpm->getServiceLocator()->get('PostService')
);
},
],
],
];
}
}
+

Now that we have this in place let us quickly define our PostService. We will be using a Service that makes use +of Doctrine, so we require a Doctrine\Persistence\ObjectManager as dependency.

+
use Doctrine\Persistence\ObjectManager;

class PostService
{
protected $objectManager;

public function __construct(ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}

public function deletePost($id)
{
$post = $this->objectManager->find('Post', $id);
$this->objectManager->remove($post);
$this->objectManager->flush();
}
}
+

And for this one, too, let's quickly create the factory, again within our Module class.

+
class Module
{
// getAutoloaderConfig(), getConfig(), etc...

public function getServiceConfig()
{
return [
'factories' => [
'PostService' => function($sm) {
return new PostService(
$sm->get('doctrine.entitymanager.orm_default')
);
}
]
];
}
}
+

With this set up we can now cover some best practices.

+

Best practices

+

Ideally, you should not protect your applications using only guards (Route or Controller guards). +This leaves your application open for some undesired side-effects. +As a best practice you should protect all your services or controllers by injecting the authorization service. +But let's go step by step:

+

Assuming the application example above we can easily use LmcRbacMvc to protect our route using the following guard:

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\RouteGuard' => [
'post/delete' => ['admin']
]
]
]
];
+

Now, any users that do not have the "admin" role will receive a 403 error (unauthorized) when trying to access +the "post/delete" route. However, this does not prevent the service (which should contain the actual logic in a properly +design application) to be injected and used elsewhere in your code. For instance:

+
class PostController
{
protected $postService;

public function createAction()
{
// this action may have been reached through the "forward" method, hence bypassing guards
$this->postService->deletePost('2');
}
}
+

You see the issue!

+

The solution is to inject the AuthorizationService into your services and check for the +permissions before doing anything wrong. So let's modify our previously created PostService class

+
use Doctrine\Persistence\ObjectManager;
use LmcRbacMvc\Service\AuthorizationService;

class PostService
{
protected $objectManager;

protected $authorizationService;

public function __construct(
ObjectManager $objectManager,
AuthorizationService $autorizationService
) {
$this->objectManager = $objectManager;
$this->authorizationService = $autorizationService;
}

public function deletePost($id)
{
// First check permission
if (!$this->authorizationService->isGranted('deletePost')) {
throw new UnauthorizedException('You are not allowed !');
}

$post = $this->objectManager->find('Post', $id);
$this->objectManager->remove($post);
$this->objectManager->flush();
}
}
+

Since we now have an additional dependency we should inject it through our factory, again within our Module class.

+
class Module
{
// getAutoloaderConfig(), getConfig(), etc...

public function getServiceConfig()
{
return [
'factories' => [
'PostService' => function($sm) {
return new PostService(
$sm->get('doctrine.entitymanager.orm_default'),
$sm->get('LmcRbacMvc\Service\AuthorizationService') // This is new!
);
}
]
];
}
}
+

Alternatively, you can also protect your controllers using the isGranted helper (you do not need to inject +the AuthorizationService then):

+
class PostController
{
protected $postService;

public function createAction()
{
if (!$this->isGranted('deletePost')) {
throw new UnauthorizedException('You are not allowed !');
}

$this->postService->deletePost('2');
}
}
+

While protecting services is the more defensive way (because services are usually the last part of the logic flow), +it is often complicated to deal with. +If your application is architectured correctly, it is often simpler to protect your controllers.

+

When using guards then?

+

In fact, you should see guards as a very efficient way to quickly reject access to a hierarchy of routes or a +whole controller. For instance, assuming you have the following route config:

+
return [
'router' => [
'routes' => [
'admin' => [
'type' => 'Literal',
'options' => [
'route' => '/admin'
],
'may_terminate' => true,
'child_routes' => [
'users' => [
'type' => 'Literal',
'options' => [
'route' => '/users'
]
],
'invoices' => [
'type' => 'Literal',
'options' => [
'route' => '/invoices'
]
]
]
]
]
]
};
+

You can quickly reject access to all admin routes using the following guard:

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\RouteGuard' => [
'admin*' => ['admin']
]
]
]
];
+

A Real World Application Part 2 - Only delete your own Posts

+

If you jumped straight to this section please notice that we assume you have the knowledge that we presented in the +previous example. In here we will cover a very common use case. Users of our Application should only have delete +permissions to their own content. So let's quickly refresh our PostService class:

+
use Doctrine\Persistence\ObjectManager;

class PostService
{
protected $objectManager;

protected $authorizationService;

public function __construct(
ObjectManager $objectManager,
AuthorizationService $autorizationService
) {
$this->objectManager = $objectManager;
$this->authorizationService = $autorizationService;
}

public function deletePost($id)
{
// First check permission
if (!$this->authorizationService->isGranted('deletePost')) {
throw new UnauthorizedException('You are not allowed !');
}

$post = $this->objectManager->find('Post', $id);
$this->objectManager->remove($post);
$this->objectManager->flush();
}
}
+

As we can see, we check within our Service if the User of our Application is allowed to delete the post with a check +against the deletePost permission. Now how can we achieve that only a user who is the owner of the Post to be able to +delete his own post, but other users can't? We do not want to change our Service with more complex logic because this +is not the task of such service. The Permission-System should handle this. And we can, for this we have the +AssertionPluginManager and here is how to do it:

+

First of all we need to write an Assertion. The Assertion will return a boolean statement about the current +identity being the owner of the post.

+
namespace Your\Namespace;

use LmcRbacMvc\Assertion\AssertionInterface;
use LmcRbacMvc\Service\AuthorizationService;

class MustBeAuthorAssertion implements AssertionInterface
{
/**
* Check if this assertion is true
*
* @param AuthorizationService $authorization
* @param mixed $post
*
* @return bool
*/
public function assert(AuthorizationService $authorization, $post = null)
{
return $authorization->getIdentity() === $post->getAuthor();
}
}
+

This simple MustBeAuthorAssertion will check against the current $authorization if it equals the identity of the +current context Author. The second parameter is called the "context". A context can be anything (an object, a scalar, +an array...) and only makes sense in the context of the assertion.

+

Imagine a user calls http://my.dom/post/delete/42, so obviously he wants to delete the Post-Entity with ID#42. In +this case Entity#42 is our Context! If you're wondering how the context gets there, bare with me. We will get to +this later.

+

Now that we have written the Assertion, we want to make sure that this assertion will always be called, whenever we +check for the deletePost permission. We don't want others to delete our previous content! For this we have the so +called assertion_map. In this map we glue assertions and permissions together.

+
// module.config.php or wherever you configure your RBAC stuff
return [
'lmc_rbac' => [
'assertion_map' => [
'deletePost' => 'Your\Namespace\MustBeAuthorAssertion'
]
]
];
+

Now, whenever some test the deletePost permission, it will automatically call the MustBeAuthorAssertion from +the AssertionPluginManager. This plugin manager is configured to automatically add unknown classes to an invokable. +However, some assertions may need dependencies. You can manually configure the assertion plugin manager as +shown below:

+
// module.config.php or wherever you configure your RBAC stuff
return [
'lmc_rbac' => [
// ... other rbac stuff
'assertion_manager' => [
'factories' => [
'AssertionWithDependency' => 'Your\Namespace\AssertionWithDependencyFactory'
]
]
]
];
+

Now we need to remember about the context. Somehow we need to let the AssertionPluginManager know about our +context. This is done by simply passing it to the isGranted() method. For this we need to modify our Service +one last time.

+
use Doctrine\Persistence\ObjectManager;

class PostService
{
protected $objectManager;

protected $authorizationService;

public function __construct(
ObjectManager $objectManager,
AuthorizationService $autorizationService
) {
$this->objectManager = $objectManager;
$this->authorizationService = $autorizationService;
}

public function deletePost($id)
{
// Note, we now need to query for the post of interest first!
$post = $this->objectManager->find('Post', $id);

// Check the permission now with a given context
if (!$this->authorizationService->isGranted('deletePost', $post)) {
throw new UnauthorizedException('You are not allowed !');
}

$this->objectManager->remove($post);
$this->objectManager->flush();
}
}
+

And there you have it. The context is injected into the isGranted() method and now the AssertionPluginManager knows +about it and can do its thing. Note that in reality, after you have queried for the $post you would check if $post +is actually a real post. Because if it is an empty return value then you should throw an exception earlier without +needing to check against the permission.

+

A Real World Application Part 3 - Admins can delete everything

+

Often, you want users with a specific role to be able to have full access to everything. For instance, admins could +delete all the posts, even if they don't own it.

+

However, with the previous assertion, even if the admin has the permission deletePost, it won't work because +the assertion will evaluate to false.

+

Actually, the answer is quite simple: deleting my own posts and deleting others' posts should be treated like +two different permissions (it makes sense if you think about it). Therefore, admins will have the permission +deleteOthersPost (as well as the permission deletePost, because admin could write posts, too).

+

The assertion must therefore be modified like this:

+
namespace Your\Namespace;

use LmcRbacMvc\Assertion\AssertionInterface;
use LmcRbacMvc\Service\AuthorizationService;

class MustBeAuthorAssertion implements AssertionInterface
{
/**
* Check if this assertion is true
*
* @param AuthorizationService $authorization
* @param mixed $context
*
* @return bool
*/
public function assert(AuthorizationService $authorization, $context = null)
{
if ($authorization->getIdentity() === $context->getAuthor()) {
return true;
}

return $authorization->isGranted('deleteOthersPost');
}
}
+

A Real World Application Part 4 - Checking permissions in the view

+

If some part of the view needs to be protected, you can use the shipped isGranted view helper.

+

For example, lets's say that only users with the permissions post.manage will have a menu item to acces +the adminsitration panel :

+

In your template post-index.phtml

+
<ul class="nav">
<li><a href="/">Home</a></li>
<li><a href="/posts/list">View posts</a></li>
<?php if ($this->isGranted('post.manage'): ?>
<li><a href="/posts/admin">Manage posts</a></li>
<?php endif ?>
</ul>
+

You can even protect your menu item regarding a role, by using the hasRole view helper :

+
<ul class="nav">
<li><a href="/">Home</a></li>
<li><a href="/posts/list">View posts</a></li>
<?php if ($this->hasRole('admin'): ?>
<li><a href="/posts/admin">Manage posts</a></li>
<?php endif ?>
</ul>
+

In this last example, the menu item will be hidden for users who don't have the admin role.

+

Using LmcRbacMvc with Doctrine ORM

+

First your User entity class must implement LmcRbacMvc\Identity\IdentityInterface :

+
use LmccUser\Entity\User as LmcUserEntity;
use LmcRbacMvc\Identity\IdentityInterface;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;

/**
* @ORM\Entity
* @ORM\Table(name="user")
*/
class User extends LmcUserEntity implements IdentityInterface
{
/**
* @var Collection
* @ORM\ManyToMany(targetEntity="HierarchicalRole")
*/
private $roles;

public function __construct()
{
$this->roles = new ArrayCollection();
}

/**
* {@inheritDoc}
*/
public function getRoles()
{
return $this->roles->toArray();
}

/**
* Set the list of roles
* @param Collection $roles
*/
public function setRoles(Collection $roles)
{
$this->roles->clear();
foreach ($roles as $role) {
$this->roles[] = $role;
}
}

/**
* Add one role to roles list
* @param \Rbac\Role\RoleInterface $role
*/
public function addRole(RoleInterface $role)
{
$this->roles[] = $role;
}
}
+

For this example we will use a more complex situation by using Rbac\Role\HierarchicalRoleInterface so the second step is to create HierarchicalRole entity class

+
class HierarchicalRole implements HierarchicalRoleInterface
{
/**
* @var HierarchicalRoleInterface[]|\Doctrine\Common\Collections\Collection
*
* @ORM\ManyToMany(targetEntity="HierarchicalRole")
*/
protected $children;

/**
* @var PermissionInterface[]|\Doctrine\Common\Collections\Collection
*
* @ORM\ManyToMany(targetEntity="Permission", indexBy="name", fetch="EAGER", cascade={"persist"})
*/
protected $permissions;

/**
* Init the Doctrine collection
*/
public function __construct()
{
$this->children = new ArrayCollection();
$this->permissions = new ArrayCollection();
}

/**
* {@inheritDoc}
*/
public function addChild(HierarchicalRoleInterface $child)
{
$this->children[] = $child;
}

/*
* Set the list of permission
* @param Collection $permissions
*/
public function setPermissions(Collection $permissions)
{
$this->permissions->clear();
foreach ($permissions as $permission) {
$this->permissions[] = $permission;
}
}

/**
* {@inheritDoc}
*/
public function addPermission($permission)
{
if (is_string($permission)) {
$permission = new Permission($permission);
}

$this->permissions[(string) $permission] = $permission;
}

/**
* {@inheritDoc}
*/
public function hasPermission($permission)
{
// This can be a performance problem if your role has a lot of permissions. Please refer
// to the cookbook to an elegant way to solve this issue

return isset($this->permissions[(string) $permission]);
}

/**
* {@inheritDoc}
*/
public function getChildren()
{
return $this->children->toArray();
}

/**
* {@inheritDoc}
*/
public function hasChildren()
{
return !$this->children->isEmpty();
}
}
+

And the last step is to create a Permission entity class which is a very simple entity class. You don't have to do specific things!

+

You can find all entity examples in this folder : Example

+

You need one more configuration step. Indeed, how can the RoleProvider retrieve your role and permissions? For this you need to configure LmcRbacMvc\Role\ObjectRepositoryRoleProvider in your lmc_rbac.global.php file :

+
        /**
* Configuration for role provider
*/
'role_provider' => [
'LmcRbacMvc\Role\ObjectRepositoryRoleProvider' => [
'object_manager' => 'doctrine.entitymanager.orm_default', // alias for doctrine ObjectManager
'class_name' => 'User\Entity\HierarchicalRole', // FQCN for your role entity class
'role_name_property' => 'name', // Name to show
],
],
+

Using DoctrineORM with LmcRbacMvc is very simple. You need to be aware of performance where there is a lot of permissions for roles.

+

How to deal with roles with lot of permissions?

+

In very complex applications, your roles may have dozens of permissions. In the [/data/FlatRole.php.dist] entity +we provide, we configure the permissions association so that whenever a role is loaded, all of its permissions are also +loaded in one query (notice the fetch="EAGER"):

+
/**
* @ORM\ManyToMany(targetEntity="Permission", indexBy="name", fetch="EAGER")
*/
protected $permissions;
+

The hasPermission method is therefore really simple:

+
public function hasPermission($permission)
{
return isset($this->permissions[(string) $permission]);
}
+

However, with a lot of permissions, this method will quickly kill your database. What you can do is modfiy the Doctrine +mapping so that the collection is not actually loaded:

+
/**
* @ORM\ManyToMany(targetEntity="Permission", indexBy="name", fetch="LAZY")
*/
protected $permissions;
+

Then, modify the hasPermission method to use the Criteria API. The Criteria API is a Doctrine 2.2+ API that allows +your application to efficiently filter a collection without loading the whole collection:

+
use Doctrine\Common\Collections\Criteria;

public function hasPermission($permission)
{
$criteria = Criteria::create()->where(Criteria::expr()->eq('name', (string) $permission));
$result = $this->permissions->matching($criteria);

return count($result) > 0;
}
+
+

NOTE: This is only supported starting from Doctrine ORM 2.5!

+
+

Using LmcRbacMvc and ZF2 Assetic

+

To use Assetic with LmcRbacMvc guards, you should modify your +module.config.php using the following configuration:

+
return [
'assetic_configuration' => [
'acceptableErrors' => [
\LmcRbacMvc\Guard\GuardInterface::GUARD_UNAUTHORIZED
]
]
];
+

Using LmcRbacMvc and LmcUser

+

To use the authentication service from LmcUser, just add the following alias in your application.config.php:

+
return [
'service_manager' => [
'aliases' => [
'Laminas\Authentication\AuthenticationService' => 'lmcuser_auth_service'
]
]
];
+

Finally add the LmcUser routes to your guards:

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbac\Guard\RouteGuard' => [
'lmcuser/login' => ['guest'],
'lmcuser/register' => ['guest'], // required if registration is enabled
'lmcuser*' => ['user'] // includes logout, changepassword and changeemail
]
]
]
];
+ + \ No newline at end of file diff --git a/docs/guards/index.html b/docs/guards/index.html new file mode 100644 index 00000000..ba7760a6 --- /dev/null +++ b/docs/guards/index.html @@ -0,0 +1,180 @@ + + + + + +Guards | LmcRbacMvc + + + + + +
Skip to main content

Guards

+

In this section, you will learn:

+
    +
  • What guards are
  • +
  • How to use and configure built-in guards
  • +
  • How to create custom guards
  • +
+

What are guards and when should you use them?

+

Guards are listeners that are registered on a specific event of +the MVC workflow. They allow your application to quickly mark a request as unauthorized.

+

Here is a simple workflow without guards:

+

Laminas Framework workflow without guards

+

And here is a simple workflow with a route guard:

+

Laminas Framework workflow with guards

+

RouteGuard and ControllerGuard are not aware of permissions but rather only think about "roles". For +instance, you may want to refuse access to each routes that begin by "admin/*" to all users that do not have the +"admin" role.

+

If you want to protect a route for a set of permissions, you must use RoutePermissionsGuard. For instance, +you may want to grant access to a route "post/delete" only to roles having the "delete" permission. +Note that in a RBAC system, a permission is linked to a role, not to a user.

+

Albeit simple to use, guards should not be the only protection in your application, and you should always +protect your services as well. The reason is that your business logic should be handled by your service. Protecting a given +route or controller does not mean that the service cannot be access from elsewhere (another action for instance).

+

Protection policy

+

By default, when a guard is added, it will perform a check only on the specified guard rules. Any route or controller +that is not specified in the rules will be "granted" by default. Therefore, the default is a "blacklist" +mechanism.

+

However, you may want a more restrictive approach (also called "whitelist"). In this mode, once a guard is added, +anything that is not explicitly added will be refused by default.

+

For instance, let's say you have two routes: "index" and "login". If you specify a route guard rule to allow "index" +route to "member" role, your "login" route will become defacto unauthorized to anyone, unless you add a new rule for +allowing the route "login" to "member" role.

+

You can change it in LmcRbacMvc config, as follows:

+
use LmcRbacMvc\Guard\GuardInterface;

return [
'lmc_rbac' => [
'protection_policy' => GuardInterface::POLICY_DENY
]
];
+
+

NOTE: this policy will block ANY route/controller (so it will also block any console routes or controllers). The +deny policy is much more secure, but it needs much more configuration to work with.

+
+

Built-in guards

+

LmcRbacMvc comes with four guards, in order of priority :

+
    +
  • RouteGuard : protect a set of routes based on the identity roles
  • +
  • RoutePermissionsGuard : protect a set of routes based on roles permissions
  • +
  • ControllerGuard : protect a controllers and/or actions based on the identity roles
  • +
  • ControllerPermissionsGuard : protect a controllers and/or actions based on roles permissions
  • +
+

All guards must be added in the guards subkey:

+
return [
'lmc_rbac' => [
'guards' => [
// Guards config here!
]
]
];
+

Because of the way Laminas Framework handles config, you can without problem define some rules in one module, and +more rules in another module. All the rules will be automatically merged.

+
+

For your mental health, I recommend you to use either the route guard OR the controller guard, but not both. If +you decide to use both conjointly, I recommend you to set the protection policy to "allow" (otherwise, you will +need to define rules for every routes AND every controller, which can become quite frustrating!).

+
+

Please note that if your application uses both route and controller guards, route guards are always executed +before controller guards (they have a higher priority).

+

RouteGuard

+
+

The RouteGuard listens to the MvcEvent::EVENT_ROUTE event with a priority of -5.

+
+

The RouteGuard allows your application to protect a route or a hierarchy of routes. You must provide an array of "key" => "value", +where the key is a route pattern and the value is an array of role names:

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\RouteGuard' => [
'admin*' => ['admin'],
'login' => ['guest']
]
]
]
];
+
+

Only one role in a rule needs to be matched (it is an OR condition).

+
+

Those rules grant access to all admin routes to users that have the "admin" role, and grant access to the "login" +route to users that have the "guest" role (eg.: most likely unauthenticated users).

+
+

The route pattern is not a regex. It only supports the wildcard (*) character, that replaces any segment.

+
+

You can also use the wildcard character * for roles:

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\RouteGuard' => [
'home' => ['*']
]
]
]
];
+

This rule grants access to the "home" route to anyone.

+

Finally, you can also omit the roles array to completely block a route, for maintenance purpose for example :

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\RouteGuard' => [
'route_under_construction'
]
]
]
];
+

This rule will be inaccessible.

+

Note : this last example could be (and should be) written in a more explicit way :

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\RouteGuard' => [
'route_under_construction' => []
]
]
]
];
+

RoutePermissionsGuard

+
+

The RoutePermissionsGuard listens to the MvcEvent::EVENT_ROUTE event with a priority of -8.

+
+

The RoutePermissionsGuard allows your application to protect a route or a hierarchy of routes. You must provide an array of "key" => "value", +where the key is a route pattern and the value is an array of permission names:

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\RoutePermissionsGuard' => [
'admin*' => ['admin'],
'post/manage' => ['post.update', 'post.delete']
]
]
]
];
+
+

By default, all permissions in a rule must be matched (an AND condition).

+
+

In the previous example, one must have post.update AND post.delete permissions +to access the post/manage route. You can also specify an OR condition like so:

+
use LmcRbacMvc\Guard\GuardInterface;

return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\RoutePermissionsGuard' => [
'post/manage' => [
'permissions' => ['post.update', 'post.delete'],
'condition' => GuardInterface::CONDITION_OR
]
]
]
]
];
+
+

Permissions are linked to roles, not to users

+
+

Those rules grant access to all admin routes to roles that have the "admin" permission, and grant access to the +"post/delete" route to roles that have the "post.delete" or "admin" permissions.

+
+

The route pattern is not a regex. It only supports the wildcard (*) character, that replaces any segment.

+
+

You can also use the wildcard character * for permissions:

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\RoutePermissionsGuard' => [
'home' => ['*']
]
]
]
];
+

This rule grants access to the "home" route to anyone.

+

Finally, you can also use an empty array to completly block a route, for maintenance purpose for example :

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\RoutePermissionsGuard' => [
'route_under_construction' => []
]
]
]
];
+

This route will be inaccessible.

+

ControllerGuard

+
+

The ControllerGuard listens to the MvcEvent::EVENT_ROUTE event with a priority of -10.

+
+

The ControllerGuard allows your application to protect a controller. You must provide an array of arrays:

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\ControllerGuard' => [
[
'controller' => 'MyController',
'roles' => ['guest', 'member']
]
]
]
]
];
+
+

Only one role in a rule need to be matched (it is an OR condition).

+
+

Those rules grant access to each actions of the MyController controller to users that have either the "guest" or +"member" roles.

+

As for RouteGuard, you can use a wildcard (*) character for roles.

+

You can also specify optional actions, so that the rule only apply to one or several actions:

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\ControllerGuard' => [
[
'controller' => 'MyController',
'actions' => ['read', 'edit'],
'roles' => ['guest', 'member']
]
]
]
]
];
+

You can combine a generic rule and a specific action rule for the same controller, as follows:

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\ControllerGuard' => [
[
'controller' => 'PostController',
'roles' => ['member']
],
[
'controller' => 'PostController',
'actions' => ['delete'],
'roles' => ['admin']
]
]
]
]
];
+

These rules grant access to each controller action to users that have the "member" role, but restrict the +"delete" action to "admin" only.

+

ControllerPermissionsGuard

+
+

The ControllerPermissionsGuard listens to the MvcEvent::EVENT_ROUTE event with a priority of -13.

+
+

The ControllerPermissionsGuard allows your application to protect a controller using permissions. You must provide an array of arrays:

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\ControllerPermissionsGuard' => [
[
'controller' => 'MyController',
'permissions' => ['post.update', 'post.delete']
]
]
]
]
];
+
+

All permissions in a rule must be matched (it is an AND condition).

+
+

In the previous example, the user must have post.update AND post.delete permissions +to access each action of the MyController controller.

+

As for all other guards, you can use a wildcard (*) character for permissions.

+

The configuration rules are the same as for ControllerGuard.

+

Security notice

+

RouteGuard and ControllerGuard listen to the MvcEvent::EVENT_ROUTE event. Therefore, if you use the +forward method in your controller, those guards will not intercept and check requests (because internally +Laminas MVC does not trigger again a new MVC loop).

+

Most of the time, this is not an issue, but you must be aware of it, and this is an additional reason why you +should always protect your services too.

+

Creating custom guards

+

LmcRbacMvc is flexible enough to allow you to create custom guards. Let's say we want to create a guard that will +refuse access based on an IP addresses blacklist.

+

First create the guard:

+
namespace Application\Guard;

use Laminas\Http\Request as HttpRequest;
use Laminas\Mvc\MvcEvent;
use LmcRbacMvc\Guard\AbstractGuard;

class IpGuard extends AbstractGuard
{
const EVENT_PRIORITY = 100;

/**
* List of IPs to blacklist
*/
protected $ipAddresses = [];

/**
* @param array $ipAddresses
*/
public function __construct(array $ipAddresses)
{
$this->ipAddresses = $ipAddresses;
}

/**
* @param MvcEvent $event
* @return bool
*/
public function isGranted(MvcEvent $event)
{
$request = $event->getRequest();

if (!$request instanceof HttpRequest) {
return true;
}

$clientIp = $_SERVER['REMOTE_ADDR'];

return !in_array($clientIp, $this->ipAddresses);
}
}
+
+

Guards must implement LmcRbacMvc\Guard\GuardInterface.

+
+

By default, guards are listening to the event MvcEvent::EVENT_ROUTE with a priority of -5 (you can change the default +event to listen by overriding the EVENT_NAME constant in your guard subclass). However, in this case, we don't +even need to wait for the route to be matched, so we overload the EVENT_PRIORITY constant to be executed earlier.

+

The isGranted method simply retrieves the client IP address, and checks it against the blacklist.

+

However, for this to work, we must register the newly created guard with the guard plugin manager. To do so, add the +following code in your config:

+
return [
'zfc_rbac' => [
'guard_manager' => [
'factories' => [
'Application\Guard\IpGuard' => 'Application\Factory\IpGuardFactory'
]
]
]
];
+

The guard_manager config follows a conventional service manager configuration format.

+

Now, let's create the factory:

+
namespace Application\Factory;

use Application\Guard\IpGuard;
use Laminas\ServiceManager\Factory\FactoryInterface;
use Laminas\ServiceManager\ServiceLocatorInterface;

class IpGuardFactory implements FactoryInterface
{
/**
* {@inheritDoc}
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
if (null === $options) {
$options = [];
}
return new IpGuard($options);
}
}
+

In a real use case, you would likely fetched the blacklist from a database.

+

Now we just need to add the guard to the guards option, so that LmcRbacMvc can execute the logic behind this guard. In +your config, add the following code:

+
return [
'lmc_rbac' => [
'guards' => [
'Application\Guard\IpGuard' => [
'87.45.66.46',
'65.87.35.43'
]
]
]
];
+

The array of IP addresses will be passed to IpGuardFactory::__invoke in the $options parameter.

+ + \ No newline at end of file diff --git a/docs/images/workflow-without-guards.png b/docs/images/workflow-without-guards.png deleted file mode 100644 index fd49abadd8e3f151c4b9c330eaa9099175e0bcea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8905 zcmdsdXH=6<^kyg`QUpN+0jWWh-cgV$0s%y&g&I0Y2`JTsY5_z zgXdqVslfl}QvG3Yp|aD}(twI8g4E12^>!Ljw~4Wp zn6g)bx0;G`LSD!1666+iY>niEuiobA7!q8KR}_i}yF6}|?}2*2A46Ac?xU+Vs?xt& z7xGmrVV2(_@fA@?3*G!P)R7xzJ<|VWYHjZr1I+p#^9WUAHdBJ|EPNG)++4ama0(*f z^BZ&AklQyn79h}O(u~)_O#GUjg9g3d-_pWdfH&izSP|^~{V)pZ^jgs92NQcT1kKCm z5|JB((S%NjUUjLzi=n}Y6`DjgTy|0G#}L%XBwjRcMr#W* zwV~&u+Q4t6{~7|;UyU5AbneM`Me~5rpSM4pByMd|X8Qd!cVL#K*zmUrXJ~o?F+||V ztcr^g@-(uMJd`W@@tG`Uk5enja<%=QW7Vs5^IVq{e6$<QFM=H%CZ||uMxBBLX;3h$HfRb2 zY4!7O)c*D~OMJz7J-aDj$8`!Hq$%WNVJw#9yD}=<-P5x)Ugw>fk>TOH_%hd8dp~DE z6!I~&kq-xfs6wK36nI_CFawWF6K8V`t_r4gE94opUp&7TI0<_4y7%aZhWFY>s4oxP~$j=)?W~S>_7d_pj*&r-BiPY z2j%VU?QiQgrXt82JFV!n^FUH;!)RLw1;Ly)W zwoN<`cHQEF_?uqzPB={JnQt=jb-2Rb%e7G$0~QbDy|KM(YQ<~)cj-ELcJ&D9b#tR} z{TFSrp(bC2Z~na@B{|i^OfS!X2w{uuoNWxxj*eO(hp?hp7)jw;FL#I&)-}22lTg$L1FX>-BcZ`@#r4+13x`1HN+o42Vc#62 z@|P|xsOK5DO!EDF!uBNmgBpUK5!e~}!O+^buUtehvnQ?8vqg~LQ(x9!Eh2=ZR@rb> zjU)9C&TM`Zk)aJ`vy%%gA93Ggtdtz^RLHtHGZ@MbhNrGUJBp_08JKa{2n!1vjWYVp z494=1KFE|67SA`hIc=(bz&GA<#e$G$1)b9kDMV zE#%f3sl%cEtdZS|6oU_%j`Xjl@4DQ)5(|I883Dq^Q*icq2Rr*CqtPEs5H&S~E&T=! z8$v!s3<9~6gYm+=)`t50`gm$!R!#MFr(^WXmj!pPXd9^YKhG^Ec-a=j!PhkqWUU0+ zd|HT2dO_|;>~><@(B>Q)O)0U&Hv}o$`|0OUVf3-^mi0YWkjdVEHY|!bUXM^& zSZ!F7v*M!s4##h>1QB$ty#iix#mev#>PhCr!tc|9g&%tHVVO2nb%E`@e{)~e!P@_)Kot^{sJ^UJNJj@*(NQ-TYlgLC-7qKmO!3>iSUmKe{-4`GZhmm ztTYbZVv}>4SwgsURDu_TBDI5E;X+g!*iaK`@pfeNyW(OSG|D`<&<~mnmu-e#=jI~E zQq$6;tu8#XtTG?`maaH)N+u(T_yjnT6$@Xd2ac55E`50UyzH3zRjo+?Metsyb*dOG zl`52}uT#8vc~UPDLAEPM>Xr{Dp%GqsBw3)8d=3^Mpmc@cF(|iHmHO?fcKsCs|G&KI zTK|(xh)C7qPo&Ax`YOEps_|p!+at-W&(QSmi*r4fI0f42ei0B&3ASZ36@=!AboOs-loQHr zT4c6pIE_no&Mcfqg=O&K80{XSDpi;wpK%2&OCrarZwG8In|VioQ(&U@;Z0ogalMn1 zQ!01Nww9`f5pA|Uj?LXqFS5vGA=(iJHhC@~>I>UltWig|;&Z*JS-n6j@P?=HiVDvD%d4wNopn<*z6bop zMPFaRQsHYHN#;SXQ1m~3+-KVg!pc2+(!q`vxDeG|jog{b0lTJ`57+BG!=i4dM<3m( zcR8o7uCA0L2_vJRP*_)Y-BIe|j>XWTci-_syyTXL_OjFI5Qkk`GfaHDgINnykD;20llZAUno=__ZhjX3jW_9!MghSeXPtL81297Hp(Ne~Y+3*;=Xyq*}O zrVLMe>zNv!j$|DPE8)fU%FnQn7b}a%#c3~erAS+Jw7OPR)54(r$CVx61diYi{$WLZ z#i!M=RYpTDdz5?FXxgBtC*AO?;N-`GgS^q?AR)Jfs(f2#h7>Ud^@v9GsaQDmm6{`4 zv&1Tw>2gakx8Puep`jr=H}`PU&SX)VC*DkcDzIX;X$PUS*(IHtn%WN@;dj`hHGSv0 zE}A6@1Swl4Zfh|Ry{dlbn4icSwz> z_ILzkI^*o<=-B$DYksp*1${+qGrD7r-(x@N$z8h&FQTzr8nR2&M)+Ub&`7tfquRb3 zL<8(h)u8s6mO}dEx_4Z=gZl6>bth*Rt?TF~8Hm#-#-4TKJA;% zB#<)M6d?b``R8QF!-w}UGE!36K?mDcxngjN{(gQYAz?95oTto(e_t)=DVVlLFoxuA`9K`W&`CvU|yFbV#& z%%j}Ol69^kMI9gS*qd7giFC1>>~G!>>GU$dey=|$DK5SisgXTlKLkaO7A<(O9zyox&8Ym)2t`~)+VCiS9Gp5FeZZg8iNeEm^Dy3DF8S{cmHsHe$92x`y6-OM zcF996E(zW~@nRI3W)G!`8f$USh`I~R=u){~`~&Rg{T0mmGfvH#{*X|y6 zVN(mFO(}S}7l;q8O&Bo}s+v3S7#aW4;~?c0yz1+bENjH{MKN&AZg)2WzB=F6L~SgT z@CNkZP%(n$FaPt~sHU#3@Gy8b98RULuRoO8P^K5XS>QhA*H5S z0}n15&WCJ1HkHURrWlz0#&W;-ioTxScU*4()<|fJ#%X%Zxrn~Y#1YBzqeSS%)9^6q za1)!2jw6xF@vxrH&&QH0kqwjlBS??h9nV$UC0~!)%$m>hTdu0|%@=C?EB*vGx!P2_ zRcuZSFV5ev0*Rk+`DAe=#$;_J9~Fn^Dk;x%dF7Qg#;rD$?|>ta(`jG)m06to!WAB) zK!Y3eXy_)oEnw3X{dF+PN5p~s9sP6#0hof<%=7smzPRTZLEC1q{+{4%=>c)upD$m& zOco>WqSixz2KpGERndjn+1m%*lG;!OOV>Oj$$8ynA|1dv=8EX*2a@hZv(%hFR^@r_UJ)VDXICxX`| zw+0VEVw(OEvs3&tf`NNm?E7drB}jHes=256w7ucZw^<>YdiR%pZ8eQ~HV0*$piGL4 zj}GTml8C6h_ojz|);72-KhieCQt*cOa}bJ> zcfJXhV)J!(oS;ni`0iP6%Jd?0RsC@~^Oq;1IL#%v2j{g1s*xJ%@AT9AD|(cR z9^F_Sn;+?JX=}R`MC{YTDEG8z@ZIy&tKVDLzFYmf^y_F$l)L|gFT67o2w7P; z?3SLKyzZM|yFsJ$-Q#%W_>0Q%+k@4Ho=vs6`S}1uFF4wJiqXquU&ha!d+fn~s_F4w zD1p4v_8ozo=k6#y*bv8i^GeN`-A+n1Tf0A>GROsWB_y!GM%RTx#B&X>iAu6ZVzj+l zLG48r9(A`ci>(?pA7jRo!MOtB;=8+UYt@AIZKYZ=dE}VoXr>UfPfVp7uX0Puts0%~il@*n~VecM$QO5RA=Fsk7*Fk9Sdzm$ZskD@#2_Kqyn3A^B7O-Rh>S`iVBG*6EeKTOgn zk`QRvz`(%%2ZrFgnGrK4nqc)*g`5PNCFW%w6W`UZRF75G)|zH^zShYbqBD-x;M2)7 zK!Yt&6?EVy5Dz0sC*n;+gCm;XpFU{}w^~fJDVSdm+b4h|XsNu!VK?ujK8g$yZlN1KdOWsSuyg|S)(-UEv+KO=bG1@v5i5R>O{ zc5;%DmgXO;-kRnMX+H3!_-Y7Z>_+&N`eaRH`CzqzOgY{~g6ol;Y6_>n(%2AZKnW5u;d#~}Q9zsfy) ziCix|tv~aI39fbG(|CEYFGr!X)Wt90Nwaik`5TZK_Z|T`rrkkmE4qUt#z$eL&LzOx zOnyuvsDghvM0&?Fdo`S%H;_CDKH~0YMfF$2!6sgOKREtCWf%eCc2ov1%>4D0InAP} z=mxR|64z`LpR zI_=Vl%YM=`yw}MpD^w}3#L9B_9d!Oq$bP&h9R1kOLF(dsl=UGWzgRc}Dy)|m_cPvO zV@*ZiI!ORfn##G|w5)VpCl534E&p;(p>NJjl5ZjEu+a@9$6fh>Q>xY@7>`6^Y;FjA zMF`#yEC}NcIV>X#hw^$)f2Kb=S_?@snR?2`4#-f%zk@y`K6mPHPCi4p?(_7Y3=cuD z_-|<4F)AATeDsWBLX!qfv045fXhHd2$D$uT>?69h#JW7{*=tszc=EDQec_+)aNL+R ze%Y#fZEz6~ucf;AjLrXz7o3bgOlBRwHvMpBMG@E(M2sVN*?eu>nG9G>dVC!5OqCaB zLkY-6L5&ImHOtb&JYF(#=m#=sqIUyF2h;Ah7Te`JF|dwQF%8-Sfo{K}D{L3GcFL#U zU*p=J&re&t=UL=u%=&{4b&4M#aSbI3@47ddT3SkXNIQf*qQoz6Fk9UKiL)H5o=AJ_ z8A91~+OY75{AjOp&rOr;WSZ~6ap%!TDu`8EG)(~R!SmwJ|cbS|7KAFAY058=Ja{G_B{FeG`c`2-J`wzf>;FpZr)^C%EC z)=0!)+Mwk0CP;f3f&kRqzJ1&GhUHK-5@=!c97*#%nQ*X34fC^Tbt$}IRY!;?cIFlq z(KHXNUgrwKOOwS;hkpXudOCP7pHR-@^B=P^>vuhv{`ukQmXdW7U?akrJ@RMmCyay= z`Ui{P@)OS6;Iq~4-b$5^5~l}P;(V+yjtn9kb<{nwsN18U>@=wyrih26|Ozru^B^$9JI7FKjs$EcNw;d>H=J<%`Xk<)>#g$z@fh7j z_7#zQlF}G5eocJ-VLn8IcYm5M_C;aX>);*5?O09Erlak#)gxVc62U36 z{!B_XnX(3oDDlLXuE1SxEZKe~W|nN|KM}&=_Caek(z;wSsq=a@l0ol)-~FZo2a9pq z@@VIv<2!ifQxBJ2Y{6ht0(&?m86G{G03R2B*Y-f#2Q0@Pz~bU$S7I#11Oe4lRxTYG z8QBl)^fM|l?*8`8H89Y!8Y%B_i)nvb>&JyX7XdMXG3-nxm#1b`kW->wvjiP!>M>xM zRLhug{^;uO$E?;biS5hcOm{9h3AA@v;2&Jo^6>?Pg%rDtV zUcc^w_F<&FfuM?iKW)f_&k=+p(bEOc{FXA>|73L{~!*|1}%hbOYbq8LEIn(^^*b<93pSP zWq>tKk_XUTc2;VhwVz<^;`wnB(EoHdRnwQcI=Wp*^_|o?sXh)!GtgH5YIqVjK3cjb z^2QuuZ|5@tzWJCjdc~+5*B&_P8x8$b0iEyyP4E`b1QAVep3EL$u9!5Sxh#M^tng?( zD_|f|#W43HG53C*Vcjc zcXYh4D@!J9@KC0Pru19YP1$5iqY_q#pjgP+)RLM$A^Gnx~rf8gp6o zlFrMDPtOHhFP^o4PH%p5nB4Ne@v*a3xw`V|K;_S()?@O?iqGDn~ z?9r)W%ye1h;fy>u9#G{9pI`-OuO<8i1}_W?Q&$7J&3mwJ2vTo*Qia@3-7~u>5eEli zfA7;X&psvR7DFWu-D@8i4Y>~rZK&RjgWm+M@brNX+lq6K3`(3^s*x;Fhdo?Aw-c(~ z6cqDvel6k!a{L2Oo%?8Sm4^hZ!Y8GdU%YW$eUmdE7uCW3@o6Lb-)hLO3-OgA+vyzI zKr2BRZqODYDaf9kL&>&~stHtDdi`%qPO$F`uD&zCCYQQp>4IW}K40AZ;^K711lecY zDA~}zH{Q{xDr$QL6^zrq1~8aUI>n_9rwUNmicY9{IN&MKqBS!JA=Q`I+yyK5?@X49 zglC0;3eZDP!?C}GH0v)Q$g}kF&lo@dTN{ayl&(io%X?zMJAo0rtiZlVT!Qy!aunv{>iMC0p| za#z|nkabPOMO6eDBvk*CaRZYT4k+$t(A0s|YPXPSjZg!#7)TAW)GuOjV z6>uAAQ$s?7pX(}M^Myy+9WcM{i@w$oaQ5;lN9_fNCz3U>vZAU4NP|fT6pl&}w~RTf zMs;I#^1_3eNn$5JabWr$(F-^5Txf;tNB*lZK7WaIxt7W<29;sQj}9G%f}R~E~} zwbNaEIMUQJGTnon{{H?k8hTxT?G=kJ0Wy@_) zAmWk9#;hj!=o$1cJ#&!4-i$cw0_anN|C)&m`?4%QD2hymoCJ~RG*BQjfTG{1FRZ$(5e8#=v{}MXjQ$n-z+p9s{PHt0}3sWKs8X3Zf-MLQhOkN6r)`qu(cHM z-EP;M!-^2gEs50a^uLPM{FtlcPM%c$PGU|8pMiu_p|&*%|I| zoK_z2O2FrqsgHxLkE4RUmm|19q$DJzMI|LgrDTjGWfWwj6{KZ_B_tFiB!r!giT_sv dq^E<66YBrnz+6$2^z9P}?3TVpsk+UR{{?v^prrr+ diff --git a/docs/installation/index.html b/docs/installation/index.html new file mode 100644 index 00000000..d4d5fea0 --- /dev/null +++ b/docs/installation/index.html @@ -0,0 +1,43 @@ + + + + + +Requirements and Installation | LmcRbacMvc + + + + + +

Requirements and Installation

+

Requirements

+ +

Optional

+
    +
  • DoctrineModule: if you want to use some built-in role and permission providers.
  • +
  • Laminas\DeveloperTools: if you want to have useful stats added to +the Laminas Developer toolbar.
  • +
+

Installation

+

LmcRbacMvc only officially supports installation through Composer. For Composer documentation, please refer to +getcomposer.org.

+

Install the module:

+
$ composer require lm-commons/lmc-rbac-mvc:^3.0
+

Enable the module by adding LmcRbacMvc key to your application.config.php or modules.config.php file. Customize the module by copy-pasting +the lmc_rbac.global.php.dist file to your config/autoload folder.

+

Upgrade

+

LmcRbacMvc introduces breaking changes from zfcrbac v2:

+
    +
  • [BC] The namespace has been changed from ZfcRbac to LmcRbacMvc.
  • +
  • [BC] The key zfc_rbac in autoload and module config files has been replaced +by the lmc_rbac key.
  • +
  • Requires PHP 7.4 or later
  • +
  • Requires Laminas MVC components 3.x or later
  • +
  • Uses PSR-4 autoload
  • +
+ + \ No newline at end of file diff --git a/docs/intro/index.html b/docs/intro/index.html new file mode 100644 index 00000000..bbf33244 --- /dev/null +++ b/docs/intro/index.html @@ -0,0 +1,54 @@ + + + + + +Introduction | LmcRbacMvc + + + + + +

Introduction

+

LmcRbacMvc is a role-based access control Laminas MVC module to provide additional features on top of Laminas\Permissions\Rbac

+

LmcRbacMvc is part of the LM-Commons series of community developed packages for Laminas.

+

LM-Commons is a GitHub organization dedicated to the collaborative +and community-driven long-term maintenance of packages & libraries based on the Laminas MVC and Components.

+
tip

Important Note:

If you are migrating from ZfcRbac v2, there are breaking changes to take into account. See the Upgrade section for details.

+

Why should I use an authorization module?

+

The authorization part of an application is an essential aspect of securing your application. +While the authentication part tells you who is using your website, the authorization answers if the given identity has the permission to +perform specific actions.

+

What is the Rbac model?

+

Rbac stands for role-based access control. We use a very simple (albeit powerful) implementation of this model +through the use of the zf-fr/rbac library.

+

The basic idea of Rbac is to use roles and permissions:

+
    +
  • Users can have one or many Roles
  • +
  • Roles request access to Permissions
  • +
  • Permissions are granted to Roles
  • +
+

By default, LmcRbacMvc can be used for two kinds of Rbac model:

+
    +
  • Flat RBAC model: in this model, roles cannot have children. This is ideal for smaller applications, as it is easier +to understand, and the database design is simpler (no need for a join table).
  • +
  • Hierarchical RBAC model: in this model, roles can have child roles. When evaluating if a given role has a +permission, this model also checks recursively if any of its child roles also have the permission.
  • +
+

How can I integrate LmcRbacMvc into my application?

+

LmcRbacMvc offers multiple ways to protect your application:

+
    +
  • Using Guards: these classes act as "firewalls" that block access to routes and/or controllers. Guards are usually +configured using PHP arrays, and are executed early in the MVC dispatch process. Typically this happens right after +the route has been matched.
  • +
  • Using AuthorizationService: a complementary method is to use the AuthorizationService class and inject it into your +service classes to protect them from unwanted access.
  • +
+

While it is advised to use both methods to make your application even more secure, this is completely optional and you +can choose either of them independently.

+

To find out about how you can easily make your existing application more secure, please refer to the following section:

+
+ + \ No newline at end of file diff --git a/docs/quick-start/index.html b/docs/quick-start/index.html new file mode 100644 index 00000000..9bd69cfb --- /dev/null +++ b/docs/quick-start/index.html @@ -0,0 +1,66 @@ + + + + + +Quick Start | LmcRbacMvc + + + + + +

Quick Start

+

In this section, you will learn:

+
    +
  • How to set up the module
  • +
  • How to specify an identity provider
  • +
  • How to add a simple role provider
  • +
+

Before starting the quick start, make sure you have properly installed the module by following the instructions in +the README file.

+

Specifying an identity provider

+

By default, LmcRbacMvc internally uses the Laminas\Authentication\AuthenticationService service key to retrieve the user (logged or +not). Therefore, you must implement and register this service in your application by adding these lines in your module.config.php file:

+
return [
'service_manager' => [
'factories' => [
'Laminas\Authentication\AuthenticationService' => function($sm) {
// Create your authentication service!
}
]
]
];
+
tip

If you are also using the LmcUser package, then the Laminas\Authentication\AuthenticationService will be provided for you and there is no need to implement your own.

+

The identity given by Laminas\Authentication\AuthenticationService must implement LmcRbacMvc\Identity\IdentityInterface.

+
warning

Note that the default identity provided with Laminas does not implement this interface, neither does the LmcUser suite.

+

LmcRbacMvc is flexible enough to use something other than the built-in AuthenticationService, by specifying custom +identity providers. For more information, refer to this section.

+

Adding a guard

+

A guard allows your application to block access to routes and/or controllers using a simple syntax. For instance, this configuration +grants access to any route that begins with admin (or is exactly admin) to the admin role only:

+
return [
'lmc_rbac' => [
'guards' => [
'LmcRbacMvc\Guard\RouteGuard' => [
'admin*' => ['admin']
]
]
]
];
+

LmcRbacMvc has several built-in guards, and you can also register your own guards. For more information, refer +to this section.

+

Adding a role provider

+

RBAC model is based on roles. Therefore, for LmcRbacMvc to work properly, it must be aware of all the roles that are +used inside your application.

+

This configuration creates an admin role that has a child role called member. The admin role automatically +inherits the member permissions.

+
return [
'lmc_rbac' => [
'role_provider' => [
'LmcRbacMvc\Role\InMemoryRoleProvider' => [
'admin' => [
'children' => ['member'],
'permissions' => ['delete']
],
'member' => [
'permissions' => ['edit']
]
]
]
]
];
+

In this example, the admin role has two permissions: delete and edit (because it inherits the permissions from +its child), while the member role only has the edit permission.

+

LmcRbacMvc has several built-in role providers, and you can also register your own role providers. For more information, +refer to this section.

+

Registering a strategy

+

When a guard blocks access to a route/controller, or if you throw the LmcRbacMvc\Exception\UnauthorizedException +exception in your service, LmcRbacMvc automatically performs some logic for you depending on the view strategy used.

+

For instance, if you want LmcRbacMvc to automatically redirect all unauthorized requests to the "login" route, add +the following code in the onBootstrap method of your Module.php class:

+
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$sm = $app->getServiceManager();
$em = $app->getEventManager();

$listener = $sm->get(\LmcRbacMvc\View\Strategy\RedirectStrategy::class);
$listener->attach($em);
}
+

By default, RedirectStrategy redirects all unauthorized requests to a route named "login" when the user is not connected +and to a route named "home" when the user is connected. This is, of course, entirely configurable.

+
+

For flexibility purposes, LmcRbacMvc does not register any strategy for you by default!

+
+

For more information about built-in strategies, refer to this section. +to this section

+

Using the authorization service

+

Now that LmcRbacMvc is properly configured, you can inject the authorization service into any class and use it to check +if the current identity is granted to do something.

+

The authorization service is registered inside the service manager using the following key: LmcRbacMvc\Service\AuthorizationService. +Once injected, you can use it as follows:

+
use LmcRbacMvc\Exception\UnauthorizedException;

class ActionController extends \Laminas\Mvc\Controller\AbstractActionController {
public function delete()
{
if (!$this->authorizationService->isGranted('delete')) {
throw new UnauthorizedException();
}

// Delete the post
}
}
+ + \ No newline at end of file diff --git a/docs/role-providers/index.html b/docs/role-providers/index.html new file mode 100644 index 00000000..43451d14 --- /dev/null +++ b/docs/role-providers/index.html @@ -0,0 +1,70 @@ + + + + + +Role providers | LmcRbacMvc + + + + + +

Role providers

+

In this section, you will learn:

+
    +
  • What are role providers
  • +
  • What are identity providers
  • +
  • How to use and configure built-in providers
  • +
  • How to create custom role providers
  • +
+

What are role providers?

+

A role provider is an object that returns a list of roles. Each role provider must implement the +LmcRbacMvc\Role\RoleProviderInterface interface. The only required method is getRoles, and must return an array +of Rbac\Role\RoleInterface objects.

+

Roles can come from one of many sources: in memory, from a file, from a database... However, please note that +you can specify only one role provider per application. The reason is that having multiple role providers makes +the workflow harder and can lead to security problems that are very hard to spot.

+

Identity providers?

+

Identity providers return the current identity. Most of the time, this means the logged in user. LmcRbacMvc comes with a +default identity provider (LmcRbacMvc\Identity\AuthenticationIdentityProvider) that uses the +Laminas\Authentication\AuthenticationService service.

+

Create your own identity provider

+

If you want to implement your own identity provider, create a new class that implements +LmcRbacMvc\Identity\IdentityProviderInterface class. Then, change the identity_provider option in LmcRbacMvc config, +as shown below:

+
return [
'lmc_rbac' => [
'identity_provider' => 'MyCustomIdentityProvider'
]
];
+

The identity provider is automatically pulled from the service manager.

+

Built-in role providers

+

LmcRbacMvc comes with two built-in role providers: InMemoryRoleProvider and ObjectRepositoryRoleProvider. A role +provider must be added to the role_provider subkey:

+
return [
'lmc_rbac' => [
'role_provider' => [
// Role provider config here!
]
]
];
+

InMemoryRoleProvider

+

This provider is ideal for small/medium sites with few roles/permissions. All the data is specified in a simple +PHP file, so you never hit a database.

+

Here is an example of the format you need to use:

+
return [
'lmc_rbac' => [
'role_provider' => [
'LmcRbacMvc\Role\InMemoryRoleProvider' => [
'admin' => [
'children' => ['member'],
'permissions' => ['article.delete']
],
'member' => [
'children' => ['guest'],
'permissions' => ['article.edit', 'article.archive']
],
'guest' => [
'permissions' => ['article.read']
]
]
]
]
];
+

The children and permissions subkeys are entirely optional. Internally, the InMemoryRoleProvider creates +either a Rbac\Role\Role object if the role does not have any children, or a Rbac\Role\HierarchicalRole if +the role has at least one child.

+

If you are more confident with flat RBAC, the previous config can be re-written to remove any inheritence between roles:

+
return [
'lmc_rbac' => [
'role_provider' => [
'LmcRbacMvc\Role\InMemoryRoleProvider' => [
'admin' => [
'permissions' => [
'article.delete',
'article.edit',
'article.archive',
'article.read'
]
],
'member' => [
'permissions' => [
'article.edit',
'article.archive',
'article.read'
]
],
'guest' => [
'permissions' => ['article.read']
]
]
]
]
];
+

ObjectRepositoryRoleProvider

+

This provider fetches roles from the database using Doctrine\Common\Persistence\ObjectRepository interface.

+

You can configure this provider by giving an object repository service name that is fetched from the service manager +using the object_repository key:

+
return [
'lmc_rbac' => [
'role_provider' => [
'LmcRbacMvc\Role\ObjectRepositoryRoleProvider' => [
'object_repository' => 'App\Repository\RoleRepository',
'role_name_property' => 'name'
]
]
]
];
+

Or you can specify the object_manager and class_name options:

+
return [
'lmc_rbac' => [
'role_provider' => [
'LmcRbacMvc\Role\ObjectRepositoryRoleProvider' => [
'object_manager' => 'doctrine.entitymanager.orm_default',
'class_name' => 'App\Entity\Role',
'role_name_property' => 'name'
]
]
]
];
+

In both cases, you need to specify the role_name_property value, which is the name of the entity's property +that holds the actual role name. This is used internally to only load the identity roles, instead of loading +the whole table every time.

+

Please note that your entity fetched from the table MUST implement the Rbac\Role\RoleInterface interface.

+

Creating custom role providers

+

To create a custom role provider, you first need to create a class that implements the LmcRbacMvc\Role\RoleProviderInterface +interface.

+

Then, you need to add it to the role provider manager:

+
return [
'lmc_rbac' => [
'role_provider_manager' => [
'factories' => [
'Application\Role\CustomRoleProvider' => 'Application\Factory\CustomRoleProviderFactory'
]
]
]
];
+

You can now use it like any other role provider:

+
return [
'lmc_rbac' => [
'role_provider' => [
'Application\Role\CustomRoleProvider' => [
// Options
]
]
]
];
+ + \ No newline at end of file diff --git a/docs/strategies/index.html b/docs/strategies/index.html new file mode 100644 index 00000000..ed391bc6 --- /dev/null +++ b/docs/strategies/index.html @@ -0,0 +1,55 @@ + + + + + +Strategies | LmcRbacMvc + + + + + +

Strategies

+

In this section, you will learn:

+
    +
  • What strategies are
  • +
  • How to use built-in strategies
  • +
  • How to create custom strategies
  • +
+

What are strategies?

+

A strategy is an object that listens to the MvcEvent::EVENT_DISPATCH_ERROR event. It is used to describe what +happens when access to a resource is unauthorized by LmcRbacMvc.

+

LmcRbacMvc strategies all check if an LmcRbacMvc\Exception\UnauthorizedExceptionInterface has been thrown.

+

By default, LmcRbacMvc does not register any strategy for you. The best place to register it is in your onBootstrap +method of the Module.php class:

+
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$sm = $app->getServiceManager();
$em = $app->getEventManager();

$listener = $sm->get(\LmcRbacMvc\View\Strategy\UnauthorizedStrategy::class);
$listener->attach($em);
}
+

Built-in strategies

+

LmcRbacMvc comes with two built-in strategies: RedirectStrategy and UnauthorizedStrategy.

+

RedirectStrategy

+

This strategy allows your application to redirect any unauthorized request to another route by optionally appending the previous +URL as a query parameter.

+

To register it, copy-paste this code into your Module.php class:

+
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$sm = $app->getServiceManager();
$em = $app->getEventManager();

$listener = $sm->get(\LmcRbacMvc\View\Strategy\RedirectStrategy::class);
$listener->attach($em);
}
+

You can configure the strategy using the redirect_strategy subkey:

+
return [
'lmc_rbac' => [
'redirect_strategy' => [
'redirect_when_connected' => true,
'redirect_to_route_connected' => 'home',
'redirect_to_route_disconnected' => 'login',
'append_previous_uri' => true,
'previous_uri_query_key' => 'redirectTo'
],
]
];
+

If users try to access an unauthorized resource (eg.: http://www.example.com/delete), they will be +redirected to the "login" route if is not connected and to the "home" route otherwise (it must exist in your route configuration +of course) with the previous URL appended : http://www.example.com/login?redirectTo=http://www.example.com/delete

+

You can prevent redirection when a user is connected (i.e. so that the user gets a 403 page) by setting redirect_when_connected to false.

+

UnauthorizedStrategy

+

This strategy allows your application to render a template on any unauthorized request.

+

To register it, copy-paste this code into your Module.php class:

+
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$sm = $app->getServiceManager();
$em = $app->getEventManager();

$listener = $sm->get(\LmcRbacMvc\View\Strategy\UnauthorizedStrategy::class);
$listener->attach($em);
}
+

You can configure the strategy using the unauthorized_strategy subkey:

+
return [
'lmc_rbac' => [
'unauthorized_strategy' => [
'template' => 'error/custom-403'
],
]
];
+
+

By default, LmcRbacMvc uses a template called error/403.

+
+

Creating custom strategies

+

Creating a custom strategy is rather easy. Let's say we want to create a strategy that integrates with +the ApiProblem Laminas Api Tools module:

+
namespace Application\View\Strategy;

use Laminas\Http\Response as HttpResponse;
use Laminas\Mvc\MvcEvent;
use Laminas\ApiTools\ApiProblem\ApiProblem;
use Laminas\ApiTools\ApiProblem\ApiProblemResponse;
use LmcRbacMvc\View\Strategy\AbstractStrategy;
use LmcRbacMvc\Exception\UnauthorizedExceptionInterface;

class ApiProblemStrategy extends AbstractStrategy
{
public function onError(MvcEvent $event)
{
// Do nothing if no error or if response is not HTTP response
if (!($exception = $event->getParam('exception') instanceof UnauthorizedExceptionInterface)
|| ($result = $event->getResult() instanceof HttpResponse)
|| !($response = $event->getResponse() instanceof HttpResponse)
) {
return;
}

return new ApiProblemResponse(new ApiProblem($exception->getMessage()));
}
}
+

Register your strategy:

+
public function onBootstrap(EventInterface $e)
{
$e->getTarget()
->getEventManager()
->attach(new ApiProblemStrategy());
}
+ + \ No newline at end of file diff --git a/docs/support/index.html b/docs/support/index.html new file mode 100644 index 00000000..b5977094 --- /dev/null +++ b/docs/support/index.html @@ -0,0 +1,22 @@ + + + + + +Support | LmcRbacMvc + + + + + +

Support

+
Notices and Disclaimers
+

This is not an official Laminas Project organization.

+

Issues and questions related to the Laminas MVC and components +should be addressed to the Laminas Project organisation.

+

Laminas is a trademark of the Laminas Project, a Series of LF Projects, LLC.

+ + \ No newline at end of file diff --git a/docs/using-the-authorization-service/index.html b/docs/using-the-authorization-service/index.html new file mode 100644 index 00000000..e2c9f6c9 --- /dev/null +++ b/docs/using-the-authorization-service/index.html @@ -0,0 +1,79 @@ + + + + + +Using the Authorization Service | LmcRbacMvc + + + + + +

Using the Authorization Service

+

This section will teach you how to use the AuthorizationService to its full extent.

+

Injecting the Authorization Service

+

Using initializers

+

To automatically inject the authorization service into your classes, you can implement the +AuthorizationServiceAwareInterface and use the trait, as shown below:

+
namespace YourModule;

use LmcRbacMvc\Service\AuthorizationServiceAwareInterface;
use LmcRbacMvc\Service\AuthorizationServiceAwareTrait;

class MyClass implements AuthorizationServiceAwareInterface
{
use AuthorizationServiceAwareTrait;

public function doSomethingThatRequiresAuth()
{
if (! $this->getAuthorizationService()->isGranted('deletePost')) {
throw new UnauthorizedException('You are not allowed !');
}

return true;
}
}
+

Then, register the initializer in your config (it is not registered by default):

+
class Module
{
// ...

public function getServiceConfig()
{
return [
'initializers' => [
'LmcRbacMvc\Initializer\AuthorizationServiceInitializer'
]
];
}
}
+
+

While initializers allow rapid prototyping, their use can lead to more fragile code. We'd suggest using factories.

+
+

Using delegator factory

+

LmcRbacMvc is shipped with a LmcRbacMvc\Factory\AuthorizationServiceDelegatorFactory delegator factory +to automatically inject the authorization service into your classes.

+

As for the initializer, the class must implement the AuthorizationServiceAwareInterface.

+

You just have to add your classes to the right delegator :

+
class Module
{
// ...

public function getServiceConfig()
{
return [
'invokables' => [
'Application\Service\MyClass' => 'Application\Service\MyClassService',
],
'delegators' => [
'Application\Service\MyClass' => [
'LmcRbacMvc\Factory\AuthorizationServiceDelegatorFactory',
// eventually add more delegators here
],
],
];
}
}
+
+

While they need a little more configuration, delegator factories have better performances than initializers.

+
+

Using Factories

+

You can inject the AuthorizationService into your factories by using Laminas' ServiceManager. The AuthorizationService +is known to the ServiceManager as 'LmcRbacMvc\Service\AuthorizationService'. Here is a classic example for injecting +the AuthorizationService:

+

YourModule/Module.php

+
class Module
{
// getAutoloaderConfig(), etc...

public function getServiceConfig()
{
return [
'factories' => [
'MyService' => function($sm) {
$authService = $sm->get('LmcRbacMvc\Service\AuthorizationService');
return new MyService($authService);
}
]
];
}
}
+

Permissions and Assertions

+

Since you now know how to inject the AuthorizationService, let's use it!

+

One of the great things the AuthorizationService brings are assertions. Assertions get executed if the identity +in fact holds the permission you are requesting. A common example is a blog post, which only the author can edit. In +this case, you have a post.edit permission and run an assertion checking the author afterwards.

+

Defining assertions

+

The AssertionPluginManager is a great way for you to use assertions and IOC. You can add new assertions quite easily +by adding this to your module.config.php file:

+
return [
'lmc_rbac' => [
'assertion_manager' => [
'factories' => [
'MyAssertion' => 'MyAssertionFactory'
]
]
]
];
+

Defining the assertion map

+

The assertion map can automatically map permissions to assertions. This means that every time you check for a +permission with an assertion map, you'll include the assertion in your check. You can define the assertion map by +adding this to your module.config.php file:

+
return [
'lmc_rbac' => [
'assertion_map' => [
'myPermission' => 'myAssertion'
]
]
];
+

Now, every time you check for myPermission, myAssertion will be checked as well.

+

Checking permissions in a service

+

So let's check for a permission, shall we?

+
$authorizationService->isGranted('myPermission');
+

That was easy, wasn't it?

+

isGranted checks if the current identity is granted the permission and additionally runs the assertion that is +provided by the assertion map.

+

Checking permissions in controllers and views

+

LmcRbacMvc comes with both a controller plugin and a view helper to check permissions.

+

In a controller :

+
    public function doSomethingAction()
{
if (!$this->isGranted('myPermission')) {
// redirect if not granted for example
}
}
+

In a view :

+
    <?php if ($this->isGranted('myPermission')): ?>
<div>
<p>Display only if granted</p>
</div>
<?php endif ?>
+

Defining additional permissions

+

But what if you don't want to use the assertion map? That's quite easy as well!

+

Here are four examples of how to run an assertion without using the assertion map:

+

Disable the assertion:

+
$authorizationService->setAssertion('myPermission', null);
$authorizationService->isGranted('myPermission');
+

Callback assertion:

+
$something = true;

$authorizationService->setAssertion(
'myPermission',
function(AuthorizationService $authorization, $context = true) use ($something) {
return $something === $context
}
);

$authorizationService->isGranted('myPermission'); // returns true, when the identity holds the permission `myPermission`
+

Object implementing AssertionInterface:

+
$context = true;

$authorizationService->setAssertion('myPermission', new MyAssertion($foo, $bar));
$authorizationService->isGranted('myPermission', $context);
+

Using the AssertionPluginManager:

+
$context = true;
$authorizationService->setAssertion('myPermission', 'MyAssertion');
$authorizationService->isGranted('myPermission', $context);
+

Please note: The context parameter is optional!

+ + \ No newline at end of file diff --git a/img/LMC-logo.png b/img/LMC-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..df20b7d294f9d273633a928719e1c7df74dfe287 GIT binary patch literal 3694 zcmb_fg?0k4NYwt>$&SCUO$9FgL!+fsk^Y=4}#a(q^u&-hqpd=T|-9hazAP^3}y)2e_lyrNvpW9!C7Ce(I zP0;2wAy5(4I%G$Se@(B!cW{9(>B_)5DDMZlop4>qx!U%WTE1ZGQAkLndvu}pK;%O1 zt(J|b&?v?F`g$jM$7bQ{eOURDXr8|N)0pQH|4TE`f*$ORXj>r_0>6JEReeapJu=Hz zyKP~iTdeHr>RMuuBI^1?GvY8$^uQ?!lBi zKi7Z=!p+SMfk0p|>ou<3cs%}hlm9|{Tlhh7bqYNxCl2Z3@vSdqzxRUno!J>B0f)mW zdJdztj=J9Ur3#>C{Ni^?_Ky~#ZEbD2xw%Jj6g@|abQ^fIvz5Fp{rvpw?7n`^kgWW- zd2F2C~l;2K-+xqn5p<%D|`F!y`ftfV7kO>Ls^-eHv<+s+ait*H@=QZ&XrVF zN=rywn%On>IXd3?#$2fddd@E~u2YftbENR%vt;3@;ZRX{a%yVd!cuo)N#Dn4n7llD zp%{93ySn4=>6CLc@ixbqOm&#WUttu`3e)_Ar9FiN`*7ux8G2(DzyZwwEOE)l=@aB!o_!AE8 z^MJ_6^B-BxX4;uWxVShv$3nsc&)r&T%XYhiwKW31+rw#&wz(B{@*~ST0**^eED;Lt z7YKcksj7|qHC26d+ge7<;JFSMag?EOLQAK4$fj}*jW_jiQpNh9Kfl~v^IVmoqxY3GAH2u@A^TtF<7;v4W;~_xRP6dU zFEPs4#`zM*kj_j`nlQ@2m3}RoDiAooW_UwG0|DoaRv3^J=>gV2NpmVGDYfqO@iF_B z{+N3gw)b-`a8Rx^KQ{UMOWV7;Ii>fnJ2iNvXCw^;={0j`?t{z3w|dcy?s@e)-}*Cd zb`89govLy|!SU(oQ0!?iTwVFCpS zhZnm;;6 z!3^Gqp5T0~!)lUJ;C0_>fI&h+V&&eQUI<&s7dQD$F%+^uerXBamMo0K<1Emo9-f}SgHm>Ma+<5X*Kfr1l$)&L zH>;W_2!X$K?)dvDT}<2r?dAyqM`zTjSO&cZLI}gexQ+sg@v!L9B7Qf+49D;Ifh>9 z2c5LEbiKz=_`!rNx4h$V7n9I`c`#1Hw02hT4SO~(=su~sy_f*f`m=9^{Cr*M{=ikp zKciJiTG-PH&O5Whw{CG`XTE=T|I)Q=K`JXRKiZth!uj-G{5{(i&x;Klr+fnr#V)9k zjk^b6>%q4)ZTt(9?3=oLpV;g)FpZx+89f@Gcey-4m-ltm?)L2`2{4ecfdOL~Trka^ zP2+U6gn-XZB4S`mOG_Rlo-<5A4c-F8=b_|~Zz;Ffg|zZ;t!cr>zg;0+agcF!C+0B< zg(7ZJgFOpD8h?27OC@*}4G~ETC3A6cy`KLJ!=5(mT~C6b*+H;_EX#!HCV!zaaVQkJ z?mGbPZ?U1z$;lz$|8@G%P+9p5fv%t^ii*MoETQm0MHa2g^BZ+B<>X8}9>T|mf*d=K8l2trsJAdbu(C!H?W03}*~k z;5_CIY{Cg4+w=K2uyfQ&+2Qk7g^RYwa-cA)h4JxPuDF@n(XL-MC^Iub&2S}<$UEPe zNeWUZKi;{rUQR(l;dFmZ?<08F^XQ`E@f=1oeE-wHfT##aJ^|CgvL%flrZD>7fVKK5 zFKQ%UD2h;M?IwNaWe3xq{(jQEE%lI%>Ba}#pbMR-^CTR26;qKxFma8ZCd{|rGoJs;7&$g_lZ zx_cD97(;LS#>6vmSgu>fkPS@oR4r17UQs7|t&F=BY>mK8PZu-92gJH|Cs-ieFlyxj z(-?%cHR04cz|=HF8+tYDMFtK`1?N8;w=;JEpm&{I15P9#3Q?ciDJz8FxHRVqI7xB~ zv*Y8ztD_1ijZ+5x@Ijs%q0~CwqMz^Qe~N*o5ToxLnn;RZJ+YsxpfJr9MQW zP!BCGh1XZf`83)9v_oPgJ_-~~PD(P9+TN`NYhv9!JPubW@i_Odb@vH@i2$Fs9!xsi zX#EX0GaFu?AjJ#1x{}tsj`Y;k(lI^)3hqy5(z2UkkhTu1`!oI>la?MR5Lp5%+I5Qe(VIW=rszB~rP>U?e*`n?Sezo#(!G#*kCs>fOMQVQ1A)~G##4~lwUlTr3|b~zatCFG=Q z{8HcZL+353rFw<{hFTI87f=O!c%W`Q%@4mBFE*##`OZSH$|s>4prR=?J- zD2`~9$cwP4_)_=J_eRtsiC3ah;SEqgKCWO;H})}Q*W|# zvTx6|WGTQpdz<(>PJZUHlV($Yd-9VS4jfWlmH7=JQHs9QhRi9~qxA{d8#g$HN;36D z_V)IKqR$^XQ=?y-0epMf|GECI5)~9OHJ4#MDg$nTQ9?b@s2L=Wj#$Ro0 zY@BKGH)N${ix*H2IX_t`+<(Flk!+B=_aq=T*(UkfGs@Ov&c{NB4uiKgXmrslX?gjo zA7#m2GE3u(&`<8O7#cr4|0@@Uk3@8ZZt_#})6>&e8{95Luo>$iTeHtPj<<{0?46y{ zR4P0aVt&#_hHN%Y;X|Ijo%v5k;sb|owN)MyI!3m=L_Y<)oz6yAPup+_@kP$zoyMR2 zF@enhKs~rN$I>&2j?UjSNg?j=`rfIc8N)zriC6U44a7-foCVraVpi z@AOh@(0OZ3O-;sB8xzx!&W$(~yD$AQwDb)Y31C|6m$z@t8$0eWo4EA z-O9bdKj00ClqRTYfA?;C#L*T#6T5Jm=ZN5emh^v0UZX{S|D2T^B)mv!_Y4kJvM-d^ tRPgLO literal 0 HcmV?d00001 diff --git a/img/LMC-social-card.jpg b/img/LMC-social-card.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9b1572cbe80cd3188129d1ba86e45954ee439dca GIT binary patch literal 25971 zcmeFYc{r5++b}$mgi5xAn2Hdg>>=|lMQ8}2h^g$vBs*iKlzo{Z6rn8H$)0T{dkERL zm>EKrVa75p&EmcK-S_i8&vU=;AMbr1$9){{AI~-Ond3UHxjxHze$M?oKl>B=M96U? zeM5Z+8yg!W5d1*)Nf757fo{$a2pkTPgFqmMA&1!bA?)BC8~A~+vq3oibq|49v9teu z@~;LlJN|F?e;=6fK@R@g^JA@M{QvVFdDR z<3D%pe}(WJh5!eg*xAlP4)C$D^Rex>L!jVFIY6xZMa;inYzNp6avTDe#LWY~P;(qy zK0EsXaIqW*L2%hZ!T%u#`8fE`T)KKl!0aLCS#Lq*=dV8=mb_NcB4j>Dky3f|G>nV; zgz!lb(R0!=vghSg)zmdKFKg*tzoBn%)6mG`j-{3L-Fr3;j!w=lu5Ru=zJC4zfkDA9 zUWP|RMn%UYCcQ~cNqze+EjuSS?^Aw3VNqpObxm#Em->d*w)T$Bu5aBvL&GDZW8)K( zQ>3~1Uki&%zn532n_JsEv|V8D&tJIMz%BeA*S`VwAK>Bx;W_~B!9mWyaIqZ-02@2s zL5?$*4)I?#<9z5Xa8~*GVZm#!KUTDGNvfDrgdRN|89U}|*KkfhE;QwzlP(HvkWpUR~4~?^qaSh`gfo;U0 zeMoClJ*9ALVI*zr*Q&m^)49$QraIRoe->SQC?m30Kfiqde{!dM&rbN}KBN*w_~V-P zDIF*MMoTw@QFtejo@V+B?s;)5aptM*k`*5&*b_A{*~2K^hlH^(Ena9fOp6N46%nBe z0knyDHlT>!Ii)aL{6y!fTY#T@wYkt;(p|*|okFMHr(C!RIiQOim17{l&P1CVn0*MR z0ITJP=_wyQwf0NhVr2_{TG0oO2Zw2PILY>ZlIOj4?B~`3eO_r)W>eiy()g%-Q}W*F zgO}Eab!I6s}6Ai^{i)2Nll76uDdr&c~!xjDnim?));6mo2Ls!ge zurE+Sm#JB86M3=~mEp=!Ilqm?^ktuDE%$w0#O3ZoUL?UhS2rekSX|8|uJ*z~wsXRr zws{m-qnI^O5shdfL)_=hq`cht(geaU~fiA3dS6&YE0eZD%PdCFciD`_@VF z7^GBx-pQkA1Y!IXy^t^;+ns<4YF2AGpye>P)TIQR9}jEdA`v3@A-vW^B`oU zqoaA=Lo~b+E~xahO5xkfOIvfP5?60aaiyGYMlV`#wb3om9sU@KDRw%N#pOULn$_&s zja(bT7!CEp;ji|-jY)-?6wu{}AloZz;`Pw{=xZC~m z*O4m{k%+v==E4Lm+B z)K*l}@;2wdkB@sEKk!8I#uU#T;LJYcV8m#3sq~8RWt?PSLR*`L@^!kSdakjb@{3pg z4O3CmWmfu}FAx@(7U(|2SwWa~10*eXhB(DP-T~>jN=jESwQzmbH~Z0#+tf#eZ}(lE ze~T39S^??CQ_|cD-6o!1{nNo~GjXvoG_iID8G0;jlV;zaX1_g>hmN^~Y6z;^RcM4t zGep5*D_N5SHnduxd@H^Nv6khT3j}o8?9{$M&O;-230|Lv^6K9`2w#7AZ=Lor;Ql-5 z=!(RmC2ovaL3vLWAV0tm#Lnkq@x%|AT+G;gh*Rjbf!2M_c9n z2Vdt+Js%vq_*v=grl+&?-9+Uw6L(A1a3QgNc%E0$_;~h$5xf_!MPjhCWb9pw@DI+qTm=vh^YQ|_O>t_aliRMSr@ zgZf7*yN+lGr`{GmT*9vr>G2*iNJTusv6WrPFZ_Qs_dLmmxCKd~iMl6PL>X=cw+AyLFYa%p~1*M9Q4n*V5ucYn z-ig!8oiJAYIL}vtb~ya&TG;(mv6Raix{~OTtlA~U@kknD7|q#5Ww&IOUcg;+3#6!` z6&f;)a|-U?d=Nfmn3s5WN@Sc@cuMEpSyfbrN2-zDP^44cu$D zG^^Mb1u3|GID9S9e4%-kHdtM2;~_Q3V(P3J0=qPTnF`WYd(Pu$Kw&6mPd2Vpg1P1R*lZf5~9)@_K` zwD|X;=$g5B#T%n%>dap$kgJDm@2mckXr-Pa>~t_u=vjB?t-gZ6LbDB%pS`2%B{Yv2 z_f_LkMdt-K1NgZRn3~6@I+H%_D*=|_iqSm@AmUxnP*2t9xMI%Qu%QaI8NH73P3Llcqw`1&`~BgmsN2Sg^G~~^m)en2NwlBfI*+pm zX~?et>$E$*CNH(~sFK7XY)73%x33>h^pWF@hN_juVtQJnIl4iT6ui;*)^tw(q5Q*F zR;1<{YUlfUz2aALLU)H0RE!3n`04iI{!9J|WQ#NQT;7n*1 z0b02aSsN@z5Bs2}GL^?zl7Wgp0CDwa8y6+yj(T zZRZEbX8Vvsv#b%=QR}(X&Sn={BJ(|3+C9<)H=?6oSzDgnoxF9@P&LU2dkVRDcCSyc z_f(O&^i*r%)t=korv8=+Jy}7v$*_f|xYU(V@{{6oLgy`Z-ylPRkY2Pg+S)01&E(1wd#p~T^7l+pVz zOGC~BP4gxo`u$n61d0VL(i?T3iYHslAtvkYjw{6PM(BzM8i_TAz@=weoA_4vTl{#c zOLT$P&c1}Cup^S18nXRvZd~{FA;e3Gb^hx^#&%-WMtAUBd8;>dK%{VBbkhixuk%N|dre=9|l!tRvyS%Hu!Vx@8I;8n>e!j8>U$ z?uDPKFmh1VznihBX7K!aNJg%fk+)Vcgz% zEp;HIQQqShdc+JNgK1I5J7BShicmtFjyBbT_N*D$BfiV2N@`7=8ck{|cdLxZBAfj* z8j!yg4#$nVy-$QLL_x$>MP&rD8a9}on}W8Da7@bwKM)t#j|E~5QEi}A~ z{1%MG4Jy4ujKDft3y89pn6~REZDwAR0R`5PHSxoZhnY-TZ2S^sAaLz_D&Oq%XY2US zw+lB0P9NEj9nUk9&t1UKgWr=dxSLO7GDL>G`ZlLoGlg8X>#?v~@>DE~$88@Xf0ES# z+u7x0y^jyhZ^Hm*kYqC=Au8=yMd3cAMW3bQG3Aw9Fg3+RzGaQqk#ADZd#v;w$@J8|8OyfOwr>G{%+cYi@El(x&so`F)2Iy&U_94RI9qi;l z$3BE@AL7Z&ey@8U!lluI<(qc;?!%oX7#qyveaIx@ zA7&lQ2`P?}rE08iV@|m#FAr#jJ9(Tp?K|2k|HS^h)n(z=Tl$y9J-*_JG5ZjTfgLC7 z@DfF!-Cn4IYRZtwJWX{jXXY2KZsaPy9oBzc^2D~}+P$coU2ofz)oomK*q?odHl#02 z9qrEo7T+L#Zd~!jMGcG4zbTr=xLv~#V;4;qn|l@0a4A3|i)VQsa()WLEGQ2rTA-{9 z1$o+2oEozk94~m^uP!mT@iOUa`BQLpBCJUqlvxWd z>g_&+n{hRFC1v+dSER4D=}{nI#&Je1BFBYWPwrduoiCQjKBMO>lG6SRn+=!l#OsC_ zHwJo@dgZt}M>z7H`qp-&BtI;d|FF(+kInMAa)xL(iExQFDUbYMbl$Af#w$;#cRa9W+IF>Uz^MBU z&8In!Lv~NIdkd9@k*?l?nKv2F^(nQaJ)QLsP07lgGSsmuzOvAIRm}K8>?yN|M~8M_ zCkq8vU1XPfj);RDYr4w$6$9urcE!#giZn$;<9K4G+cbpDIc#e^cof2vXC_O^9tn7;dH(9Y(*BIo zQrjU#P3l1ym(T{3Vu=LKcD3RGIaczASs;7$J_U=_k$n9zu1h|>jbdA;;8^#?>?oTLNz?Q8K`{d)}Qv^x$11tRYEZQsIN=55m-X) zMi(M3>loR|aJo&orsI8pV#2*6Snm-3(zw@+J;&Jg?ge(Oz*?|j5<<)j+;LZXc8Y$i z3klB!ICuLpRZMHlC2OM%3Z<&}ja4pPSo{9nnBT-a-#su|CcvDV%fD)|F~qJ>s>Zf) zTRQiBbB1bl-23$Py&!Yr+R|F`pl#uv0Vp&b_aWU0s0X0Ko9eR<$yI-YX?X%$fWauH zS@a`#R;umz1jxbq1Ax>vhH{e-hEt_H*44p4Z_Et{^v+2&go@D>N=8XBXV?(B$ctEc z?alg`UH$Vg&+@-LYE8-e5V|hIC#fr*Gz#$f?>gw&36l+Mr5)e!{*`r*+wAmuWnTBS zdXH1l=LcEk`CP~>FfFR{456+Z+m>Y>$OI+S&I613+su`Dhzigjn^ z(a0|B{O_iFb3uQdlpKsxO!;XT+r6qxZfQz;Fy`w$mG!qY%% zlNSDf;_RBMM>exy)$x1ca~k%F&NFTZCP4Zs*{-r6Q=1F1HthSesorf9Z+k@*pAwwz6g4K&Z&JwLH@KvL3L?Wx% z3u2Mg%t-P@9eZSQy#|?((U_{)X<$Qy5mWXdpR*X+wzN|eWD6EB!jM!!H}FQeDXoj< zLJOE;4F_Er|ASSxOGlCMt76(doli7f45n_k4|&ZFT{K=GEA9Fy*+(Mkeg(R9S)1hM zj;CJm+^zBCeT5+oB4QeN6JSK_ABpFmW^8IJhy%yn{cV~nHMc${dPLMm&jdfC_I!~k z>x3|Jc|rH{#dnlPK9EGS22?1;CQdl&Qe0Ee(Z%FX*QU#yVzh(Ey&OL^-O4`xbMsds z+ex+MEGV8h(X4=RRq&a!UK^+G&KayVmj@R4c$Z8C2K0RyF5|iUdQ5Y!zV@}G+c8jK zD!?#to-6Sn8Z~c}rZiamRU}{^0m(K%42Fj09*( zefGq&Vr=$OSv*Q$k18ySdzmi3j5!MJ;-xUF%lW1QBd!?wV#xoHk7yj<(}!wFoV+j4 zh~Y3egv+OlVfP`2HzHM;u*GfD?Y(xH;b9e z#mG$|BX5Lt{FTCCZ*>SWR#GDEoC&Id}MRCUIoPWAZOTBS-%s5JD-q~C9<;;;E6%a3_XY!vxh|c8kif2scJ80fX>#*e^mOQ8JcwU&CLVVMv|=c? zk+E47b!4u&qhfOg(Yiv*lGZ~Emq%gRvBiEKW()U^3x8J-UucYQXDb0w1YL@j9kHO9VoA+sPk33!RRK^y+h8zl|Td zc!y_)>3b)c1UH;@KX15}r{ML+w{p_%)1UVhh^F+I>if+jJC&8g8;vu+TVrHP;Qwv* z{C}69@Lw8N{u{<)V|tpnvBpP?LS3bX%pH0`jMx2LIh8lvR2R_U{pG4Bf76!Gw}F)S zxVX7b=4QY&+Gt5s^HLizz*KqR#eRr&($%jaxQ5_e1voBcVbWm_)F+`G~M-&Uw?n zHa=iNX#c#Lb#SNVht5A!DQTXid0!Od7H8q@H|xXqv`x6*KYT9bhbj(1{7Qo}$x3Nl zAr*@uc1r1u#!D`{5=%FHFuc0TfqHXmX+wZ%hrJk8FA!;#baOP-aD>#XWw#c%rQotT z*;JS$>5GgI5joIcq_Ynx)%ylIcM#c+>F!C(eSvjrB6L5%0Q9^lS{NuGZ$gzw&6!@c zZV=Eboo+iim^oyssLykxvaA)oJd>#Pd?;#FMySDh&rTj((c3G+DpRz*$;Qxb{A;qR z*$s%`znZ<+d^sS?`2r0^6C^m98Aw%vfZ;q68i-zPPYYdBr>G|%ZN{fJpL_NF+&$jJ z2_6CV*D0rnHr-dfHjs%=F;C%)Fq<3QYo$Pq&~9z&_gy*J>}Mca|Gkw90YP+sz+etR z##1?o;t3gZH&G8M1pSI}HSKOP$xb~`>A>3*;d{5@=AZl&Rb#JERdD=J7f{!&&ia+k znR`F8hY)KRO3vH5QuM#kmwX4t2{~QY3aeRaABde5qKswC8H?)?@zLq64L{Q|nl82Z z`3hEqIGif$JHcsv2T60a=}ksDvkY?y1S#Bu?fU~B(~HCY^dyYrR6%xxXOU^XYPzA_ zGc$WJkiYjKOneh~!yy4E6#}bY{~)8QOJxklH*_wmYArM!zhBc)cvVuaO|^I-W8Uoo z*OHw4&%){J^xW|rYtzNFhofbE`70S|d#XRoZU{pDtIhfsg24|X;z4;#{Dh#Gn@)w$ z-U7w>_kbb#SF~mpY4_Pp-L}s5)4P`Eu1cZnU|lCvXfZ*d19pGIMx&gjkP|rUxIX(Hv*>&=+h@q3VVQWNu z?yeFC$o-(1H~>bpt2e^Ye2mM~5{zKe!#S+Fl`d~2-P&}}Gben(qqw(P_m;Pl6)bsj zxA&wRo77(|bMPj!94CQk(bPSI9>P@ZL%Q%Hfi5Hj7PNt4M6+TZucItNC6YYm#C3Nb zZF)Vb|KgNzt@ZSTLSo`4m53I&efCS7#Jz5Xq{KG@bs^`COlBNPhg{{1ek|JbP-8=; z6yS`qAtSnvTk)+wIV>(JNK4AVbE*NYn2KE^(60i#dKBI+r9;c-=T6E{$Up1QP^xpv z(BVFEy5YOCY?GX+k}6MUM8t(VSmGd@)_8ZqR4T23`2okxfC3g29NiMIo}1imgq;M^ zJXU#+{-$xg=C_Y>ObKb6e0Tp~-Caw|faCh!NpwZj^&t!~7eVp%Z>HvuVx^lYS;VY1 zEO1SR>iJRW#pdSaK6Z$ zH{x(5qo;0e{>9fWrX~|ds%NfTNK+|2$00jBzWXXBWNO>#vXZu;T3Mfme*%(d$~a*` zD+9KuN}X9%WvO%+pn!2Jk8DV?0b<()+^zW-nu8^27bys%m4QGTyB^6JbGvb+CeOg( z89Xr!PXVKvF|d;u_kkEhRXLf91my%A<~7>Hj6>a!s51(^za`ENE2@(-f8I}drylg0 zy*~QU(o5Oi`xCP8o$UMZc?| zWNBgn_a77UTSQxxJX-L3<4b!Q-h570IxAF8ZI~3hf!c>eVknjyQ+2dFW*Od9DGbla z5@aahM$tk*ZK&cvr>=C`pSHH(D`Ae;Q?o_RWvja`QAG*xIi5CphpQ3 z=ySj@&2I?w96)L)C#z9*%{>r&fnlCTbFfHJwIqEBo@Kjx8$8DK*Zqr(8y0rs%SC;& zD0;kmHw_qqDy*IoSkHPjC=A~3LpHOrrl(oWD3CZlEtXK8EZ+I3fp+IhUtj1pK`2c5 z6RdNlij4-#&E0INU=)wC-tje z$ULI2f2Qq~e}z)4l%^VKADLyaf9*V)pK)oV1{sc4N;j;gMn+3Ff2Pc0AG|Diw0%RW zYG&4`2Qcc!0 zTSGriO2^Nt-wovWsnm#-38TIX9(BFv93M@*Q|7BXyTIzffO{LCRV&#P0@!2y5c**BPxYc=H&laM}__J=)bj zD3$4w^y`M`t+-5k%I|!%nCl;hc}(XOPFfJ4F;^}TnEiVK@M&qV?5`>`5%w`a9)w>A zw+7PPUqODoKej{NV2HKQFR#Ej@x+WNWP*-~R8ki~qQq>E z`{h)W+``*5@pBfD%`DaQ)Z{m1sk`QcCoNiman}-Ar}FYmpDxR68NVYG2Pahhku9fFEKA{@M8rJTG4EnMMyDoS<_#-|m0iAgOsD5+JD_ zzz|EKPG1GigG70U8rhB&QX7jU5sm;k9lEDSb$|+QH>BVvH^F?yHuvG_kvgGr@I_El zgw3G)VaLD-41(tumABoF$CQeBj*@X8l^V2M>J?_3(-2L5f8LGe;d$w1npMhmiF?;4 zqYo|KS0{iHEzFU@4`%cbV5p%76+l5mY%`5`1%P}D>eJ;vkGp@_HGj+MGd8f;b@ac= zXYr%r;BHYTrfQ^viI}~er)b?w*0iDJL1M?S5pajbFgmqOOpXggr#xWGanUGfSw3wAj#EtIeMV z3)Qalu)Vhd5XVLIAc10s;RBOh=UF1?F(?n}d^VgEf)|)v&N}XE=g+lXwymxAgjV-x z_O4SjYGPf;Qp(TCDf#4w1&?2NaD!WT0clvL(ge1}I!my|^k^V-&i}=y8#zFi0oA%A z)S<18>YnoL>=&o%q}$>C+UNa_f9fDqarfLo$D=ryai9`}Vqp|RQ+|{@GmE@NW^to# z4(-G$sLzRPcsXeBH+^m%YFH|@w30r7Zl)7Ko-xq2xAD~?*lC)?NFI^ zo1M*xsX+aHvQ*l*Ra5ip6A*I^1K-WbT)BkYqEmchpnpF#z~WY#-o$g$P53s=5o`g%u>jr;c5HxCh$n2e*OxGLT@+ zbT6{H_90c!xW_xPiWtXtJ4(kJL83I#_%r9>UIXLnW|}>>zkv#2K9(-+i0mO$Bg6O7T$J#{aicQPb@Lv zTMB#8Bsa2aN2XV&#lmHfZrUBVpS9jU&-dJ~w6JH&d3M|ELz-ra{eH!m8@u?YhbRS( zfnsDIQX{-3MolN>f%!&~>GOuvHcZUy**Z#iel$+R&fUA4)ND>NtTS#5*|c%Zb@Ikt z@Vw*v;Mj;)04;qVlAMQk#?#N)5LmP0VkG<|CyPtlldAF6_#+TW+KADyEC3=}m5$zZ z%Mz-|;=8Y#JFGNW<#FPp`rx8ack;mpEn97!d=GQqU;}vDa4J+*mQP`zOb3pwu`#PHQ%e1}W9E2o!*`0Y=%I7D54a$nZ1YNB z-B;x}`5XxfG6O@{fL2G@=rTR-2IGBv6ot^RPlkw^^W zJH*vdr2mH}3IoiO5P%$pA?!hWIE3b2Nprz6ROnha3W2Ilw8D>d{w>2w4YO?9DTN_{ zQCspEKE#S{l-;xsXUFk?b4$;9onNC8qVvYu`S`BWC%ip1_=LKDuLT6=1lkO?5*Tj~q zZE>rA)TMv3<8@ z`t8l3?dc@(l8V@R7P-N2p|Tj?-57Sq2c!WpoK7GZ>2LxIA~?{!Gz`a1vrHGbMOoX; z4((_qs($!ccTvk%jV}#2J~zb2d96`9FP8kH;+>dKEP}y(9^bA1Il@TvQ`_`5QeLM8 zz)yEdCkIeAWi|KeM1zC2)aNI?N`_qq@lY7?BjzMp0X>Pkz{~`^>ES~_~Y5}SZoz_1NbH-2Uwd!(G zHabEO$7JGE6~3yP@~e#LQ*j(1QZo_9F<@B}o;1J^ z&Zk8U!XA6LsfKdSP2CO5Cyht%AcWj?B?gx)qe^EQe@@@OdeX^xLWkDM%!0f)yFqth zagEY1V_I}kY;-3>Kf0#7ga8%V(X7#yD$pH}4G`{7Z~3Qr^a`3U-cu}7xSbl55%)X!;+LI-gln;>_Dw&R8|}xE z3~f3scNu~Mv{WXT{s5gZFi@1J!%J~CJ%kb*#YQsp<~E`={yeK2tC5=XFqSR({Clvn zVdKf~e~u>q{Ma_4%I0RcL7&BNF4W_ zUWomc$>k`USasnlEzD#x$@TQ{#w>~vbSkn9Q43=TSJ6HI&xsR$c;NaDXnUKvX*48c z()8|z2_KLBCd6{CP0n$S|HhH}{#bJ6q5d*av)%f_9yskyLG6x>>ois=)ptX)QP$el z#yiGtsPtgyf%Dd_y!>`xAku(hdTUe%T1Hs@ z6B*OwpquAJB46(~&v$oX`s#%`{-05`s!2M9{8gS)nld{H$Q}5$+FlN5EEHjSv>2d! z=w#6j>z|GBrX??(m`T=9%F^M-$m8uYMu!_J7jjOdlw~6njS?{!Ii_0G{f#xS5Q!t6 zSqvlQ%<|zW`_GgQ+w^e;v*(Q^! z$gc9>wCCDM>qJaT6JhfrdXhkUf;$UpO|=$K>#3l`f~dkYGH3^V2*kkOdDa{aXjlK^ zSJDv^m6z7TQhl7DFY^4`cDCQA!uo3(Ngv6c%-yv#8k-(K`J4Fr3ERUjrcywjIh(t$E_h_J3!LOk>!a{e>*(@RCx=%Z*k zm9`L1-HShr3f9;z+~LrEckeC(lnI9b5Tcv7Bdp#miV0JRhy^3U&_E61Mmw0Ru=!M+ z6ZqmP)hqu{wC+}@&fLxLHre#E_r+h<{_=vz-2!u*o}%+KCbMB5;>9~9->ROPhG~OE zkDXw#Q@kpqB#b;Z+}vnx8;|Zmq^}INx4ea*T64XuEvM?%_vRy+zl6>k8i#btZuYG1 zP1nf{`nXZvbrHtu>Xlq`yt=zTrLFb~F7z>He3KK(iIEH;b{Y_GGeBog-`-X zL`0-oNQRQJuI88$nQ?W#<#D@CiUQ=g&Bj+tx>2&#V`0zI+SI=`GSnQXp(fGBsJa!T zP(+JXZ4K3e9MEv67+dQmpn4QHdrk9E{Od1Iz8<)g_6tUQ20COG&@g;O9Ya8Z_a&&a zbx#K(NT%(G=YKv`ztHiwg%A>k%~`sonZTwGH$J`JLw36~c8)ix3RCA%bZ!z&j}& z4_xgMMMn6R+8RYx~A@w=4Sg{yMOK^d#9k~03d@PPtR>kZOxC9 zuaFS@%d=t?!z>NyvzhHRVMK;S649aM7JvvspC;Eyb{SC^t(GJrE3SvwPZ0cWImD zr}|eE8qP68Os56}vs~+@osbIcXj^^k7o~TRPY#)W2dTXL+0-ui3dk))^oRsIrOD@h zAhz9Jmg=kJ*#xiIP~>}%sL2M#s!GCi1NT-z$3Dy%bsYS4U_4?*F@%Ds#&;;=&sj3A zT8=ibIA4qq%6d#k8*`%YahQCBt)bdsYlq5IGw#QB^ou2VH$sNOs_e<4M^Vcw z+KuvS({NkwxO_jf`{?lAok9Cu>!!@KMz2IS=HxsEP~3-v`(^=>zR;QkEJE;EjFU#O8!N_OQMhkHt(r)cgAUy+-#Ia_dg{^-~hk zcXGrX*BT0N*MHhHk7i6I;Ty&^V`aPq99wr%6A~vnlkXRNrg0H z+cExVA7a-$tor4`x?1t`_i#TcchR6ji+jH=pdAL(hRtrw?191?@e4f%E=BMMTt`s( znHrZEU!iXj{!#mV$85S6sfaJ8x32GQfZ@KqQ?s!YpH^K_;5lZlPrDT-Hq5XrnrzFr zUsa!SyyPyN0x}4AzGL({RusdnB4q#70>wkIZo%__Ca40UO_!fC5b1{$wu@!@@7@|B zR(%;OJLIi4^18t7mstAJtBO6W&N%!A4;)|IhzASO3NVPLl?>JGAB(=_VYs6|aOd4K zFIP}fBOWt6OQ-S@a^?=JevuqiA`g3gbFSw;m#8-N#p=nka2yCnYXJ56jDN;(A`IA=PEhXKzSL+X5M|!QlW$q1gyne^ z>7VaCAI4D;vX~ZMm;*iafB|^>5jU zmT87giW3)$g*GWqBybzqdHUyDrf+r|&4|Gb5k1QYkL2%U1;01y6UEuT z$1+b1l2w_yQ)1d;hs|fFW(B!U@nwf!w>-tc4T1O`C^frrKXAmV8i;kF)RFq~+r^?K?x9S1BT90{90N}JdsIN*--|q$+ATDUQUeT0h!+yjR6`0T z0tF*oQ4A?W=2i_>|9-6S>q~N-@uh@wmde^szw6)KG%O2?F&n%WjA}<SiF1XA$E& z{z0&I%!EAAr4uIJJ(nd6rZY|F$_09Q28vfD_=Y#ie-^MAX5LM^_7CJ(;MeloL)(pZ zyzsiJ%DTF$s+#zRVTUF8y0~RqiweG4LLRVvEfzHMPnDZ6wf6U(J;cmlK^aQajVP2Q zn5zJ^+iBaB z8jgJQ04uyFPRz}&NS|~}ROh&NlNfgRY`K|H&(G!qf42N|>PWn6+7xSAC{^1d^8f&& z)U}8X7wY4G=$wD}X-4_|FOf6Rwwy9?yJb}tfIM*qIhUC}Up&AS=)46UY|EK&W1UVn z4ue~@&g>#1YB!hn*vEEP!Q)zcE1>5jf_K3~Sc9`- zu?9dm5yJ^=8AO=r`0Jh>EZs47{Uv_NKuarPmtD<+1Z%-?qBtmVXedSLB}0YSd`MW? z;L~rL`=WknmsVP3EpH1v@7K15-#GW_>rTy|#~|Nru(;8l zvAi7WRJy`H_B4If zWg*w$sIHmy*5;32xJ%&K6D6_$DQep^N`jk`noxarK_D+O$~Pp`d{heZZ0_>ln~MkI z<~5~10+;BQQCFDnRqt&RA~V@3F3+}C^ZDT(q#6Ec;|ME}Hsyb!ayNq{oBe%nr(4(E z4lY*dXI)}_%L0LQ%?L3W)jf?;0s`hsEez~Zgfq`j7BUOH9UJQ&$z-ZKiq*Hi9#H$? ziswX0lJHSlk+HrT+<;eGhfV2wtFLH>jfY`{y<<<0JpddrA!4tx1N2I11P`85VRGuk__EznT;`r`-xlwb*mVJsx=8 zV9ew6UDr!`3g$$u219%G`WY_t(~NTfmTKPBWq%exP&VwTxVH}K=rbcI z4Hv(nsUVk2djZP&PVoG7^51!}Ed(x|^OV*5f9C}p+NMtYk9q(6zs@^oC{>LZ-DRZy zJg>gXD$Qmbb0Jx`AP+IPD8Hs)&lxhLtV7Eh8s>;ThmXro+{T7}8e)~56ND_4)ekL6 z%CJj~i&3F8Za{>J?RpxR-$CGD2>`|Io0U~7f4W}!(pPuoAm%P9(yv}Y*fTH6kI8*HGcPUfHl2Q;|IkVH_A&MO zlVu*n$hA~TIn6m_5kOJ~NV{!|BhQ2*+w8@whTuQMbq1br>Z?npCvv7mIznkZD^uae`0p2iZ-eoqH ziN{km3-2!#BKJiH-Zg+#f~S5ZugxUy3>!Ck3a&E+lu|~P%$RTMnrjSeewWpRoWtww zRME_sWvo-IpRhxZ(PvQdR5g68&Y6Nfg(hLDt*PfbNzbx)!QY8F=hu38Pj8=XK8w4| z%*GRoU$fewm37Zhrc`T^N&N?gBk&RB+b2dch*XpJ#8{1}shmN`X-8e_kmI;m_5A~R z>-9e_a;1ybP|AI16rsu`j-iOJF<2{~!%psg%~T#aJM>t`i8l@`0uXZZ*Dw!lDG+ni zbAIVl-z#yF+7X?-GK@ILYazzY(TAvG(2r^-aKq*ts{G&H6{r$dX4@19AxadG;BqE~Km z?%h$1cDB>0>{9-CiYXZE0p&k|4Cbr3z^XC43xXfTQCX2NwA!{&;_<`X!eeY}ul?QG z1XT~i9KJ4`h=%gJXIC!wXKe`PiD$3<@%WUszGI`9y0*4rsF#{fmKwMHG=ic4DM7T7sfx4LweyRMZOm8W_eeNu6PdLE!DvN!B=j( zj3cVD$U;QI)BrI*l64jZ8-tNfQ;;#IJu*~o)1_#5w|Ga_?#=w`$xnW_9r-}be5*EX zJ(tDc7hrLxQVmF02EQ>W;xz?9yGPyaf@_d)@qW;loi97ZV7AECs7^yb=LCp;LVJ5pwU(_vcIQo}Mk=^CByzsaB^93EHqEnP}_hj2yflfkic!S zY_}8(cHQN-`rBAM8q}OQ!gFUJm4c#jx_|Ug_dO&(L&1N#>@@#W@#RG$jxa(D|GuMQ>x3n!3!1} zppM`JRy#69DTv4q$!C)8jMr$YPHX=1sX5-KB6rA{c;Kn&fdsSw1d7%#EusZDo_94dnc!O=;J+^z8aj+!t#|=4;v^Pcm>DZaWVBBU;4X0PSZt~uG zDmHaS(R@Z|lb38jN25>xhT3}{)UE^IIgT#q$1 zhQm;YWXOf6-f6W`FvdLy(q7gi7-_tE26+C#L*f9Hvo$LQ!R05rjXxQvXPu##JM5Tj zmz^m8((>L%jN(7#HHH%=>Xj<8A%i})=P=rK^3(ohV}*Wpt@-s{UbS|kQw{J^0gs%K zKiM&iugokAk%WPQN21urZHcLCsq@fidlgDTUVz8kQ7!0+spSUUq=V*_*D=-WAL8JC za^-Io9)?exhhDzSZVG0)7^e*U=nnvsX+waWuE{U}vZzULL*7`1NJzm|(lm@i$NX)Y zcAwQUDt=SBKAO+?OMKgfTY1-8jSMw+49TTw&E)3N#>vN1PDY9fg`p|KYia&9v}T$u zE){0X<0+!UHZuN>)eEatYJ*L?d<|g9fYohejZT|msfE2cGvGOo+LD;D**=HW^0rpb(}~?tt``)uHvzmugVog zJDj^OPri1`i#yI|fk1&nr&n9>0$?>0C`xq|0Vpvm0xjgG%Rb7uJ{*X+_9aX#)MnoF zCH!2Q;n5PgMANSq9zDItr8E>h60aL{Cs_Z~t_{8J|7zzuqng^kZBg;4Afh5&h=n3u zK}4lQML^&nMT*oYNR1)%8WIKRO+Y|F%u#w*2t-;!=LiDQO(=mNaDW67N(iKQ_Zj2f zG2Z+5f8%|;-}aY1MzZ#At>0X0&biXtf`cZCiyJ0%Yp(PR=UjDY+Jcre7A$(!w?+_T z@**%%1Op_5Eep)!V)W#Z6R7&)^lo@G_)A~6U}BzL{*x&^r6$NZNAC~^r+-?O8ph_l zbf~tEr{Yexv^;U?XXIvE*v#gRBQ_tT4dV4J9K&OFW=%lCKbfUQI0LWI2hz)gM}Id7Tu~Cbc&c1U>GXS+ zQ6EHaJsMni{wAvc5$M@f$7sD8MtV+ik?MFej%*^RVpmqGY%*attkuJHb}vGEg^QfYF*GCqUu(r(Q2?TeWjPgyaMktKmL;mGNT=% zpy>Q$(NrDd1xN5%=#9Xzw7z>It(wZL9JAYI59h-JW&z)ZVcDrkqxl<0z)wJc8?caN zjU1I$XI`f3163kHkC*L3b9pD-0*;3_cxhH@b8gLAI88(DRG$B6zbFtpo!eP2?AL6V z|K`2O8ppO3+txxHqB`3{T-{~G%G(NXPLmBRm-R>=Y7;|Kc)t${++7s9e78B#b5XPd z5(lnFCF{wtA2FYjwNo!mn~fp#;hv=~_IJA=b8Dj4jE+XvyPsA5KGdF)c4C0(2GGYM zGYH4l2|e+D@b&TC1H;5hgYnWaIPRS6FJpm(M14)l!S{rmFZPJPae-y78*dE_?Vgtp zS|`2HTsAQ^3KfM}%@zj_Lbuijq1xV8zGB6_`-w<1h#|wSzJaTv5?RT8YY72(ua29* zX{oS65`j;534n(yUAjOo+&dgwA|AYXoV50OZ6x3RvuhK!HPKQoB`ziN+@a-O>#wkh zVYVGm`7z9{9+Y&eJ8hluoO!4g`6Cb8rH!X-imnZkBfhfosRB`5sT$ z$ko_5#~KArKJST=!Vs+a8O16&3UmDT2rWwozSp1{ZY zg~k9aU+P%{8n)X-jJZHl91W#|x&&MgZf;5}!G3)pTxd;|z6IMr)P0#zJdI?G7W4Zc z2hlVe6hHC@m}UZJ`Xpk)F}kP5x+2nA%DqTX*8Lo-pYL)apK9gg4>84*_Mxx)Ljapb z(VL66Gl>euZ5)E!*jUiM%M{yY?Y+~Im~2%MIcsEF$yu8?Y|~a#16Xp_DwLMcOj$fz z7zFTnG%M6kJy0sO$dM_{Z@^HW8GcJ*k@zBKIDs2$c=9-_|ub|zQlN(XVSujAtn6+H~ z40KzqcD`E@z+j)~(yp+E3v-D|)h9 zVNZ~$pvD%?>H_@>nSYXiq9j=r)B;J67NCbQ)5r`I%dLP-{j#pInxbL-X;CKjnT@{pq&Ym?C>8^sOPH>O)0RBYZI zj2Hxh|3Da%;bLqB8n=2;?Ie+@(c_Ss5-RZ_7G9BRrF$tv9E!qJ`B(L5MEAcmJ|-^a zHfCd@s_q}9he{gB5?3%V4&Fa{&eF=_|JAkeKkwNsL)gNl+((fBx&wNd&wYugowPZ; znbLQh3bGG!O7AuT&J3kr?s1Xgj?+s!-_`i)sFZPVYl0MPlYw!1_W9&HIO>j@kiNz($eA=&H3j( zXHYb?{S{?ioUZF7MclI1ICM!kc*f5V+6{h2VqTJ`RI>!x_tJaN0(t^WlmT6{X|S<0 zGBmu(`Eld5=Gje;x9#0$`3*3y9}JzSS{-}%#%~uXHrT%&@_wJV720g|Pj&GmRWvZ^Q2&EzN7CR;;%57rO>d^`G_=H462dJLSfYdgA;zJ*c6%q@Os?^kJfzRuO3 zhgU)Z=3D`?{O_T_L1c~HJGMV34QS;@!Dnz%OlS&PpJ?h7UrkVFA_x!bN z+`%u8m0dU!eW>;%=Ph{tHM$;6ai0fC0~VIrE-3uuKO`aLAvF1MTy2^2w{pj~zta1+ zOAD4#Lk2D!zTqS)^JLpev#dYN$xxDu^g(+L^p80=hB|Cik8c*+-)rC_ZBtunR{Pb= zH9bp))~&;**pf9s56%0Kc??kNYoj@;&496Dz5pWFdY7)v6-eMzv}!Iqu92INnbLVG zoLW!^_r8?+G@DAk%h@@5#byoiG7D0Ns;{N2&XWk*^B-Q)Ru;JFoR{+?{`N_t2b$5e zfg6;3Fe<$58Jb-;)TjJwJMFD-7E6)ahdzaJg~V&&gqUs%B&Hz(T(*XlnC&T@-l#g{R)iCRQirjo7^U)KaR@YmC2-cIIv;tEGJ2xk`&@4tNFp z*%Lj}E3#wI1JFA>?E(Jh$2T11T1n&;wqzemobAsR9St?V5{m-VL2*R$81|V3j(jIh^vB07#()VnBKuA8?ec)84XkY&5tmv?sos9po0XKJ_PuaowYsXH z!LHtCZ(nJ3e-{8s?x=oAgynA@H`T=7JjIESPtxbJ^go`AHvTG=%uQ3K@e0x9#HfpBv#8Zo$FmKsgVol7dxnkN+X=4`d&0mi zk6TrrNBmM(=m92b)ic$b&Xj`O=Ird92&Gn2#`}@<&KZYQcM-H|{wgYb9hr21_*ZuR ze=EsqQ5`KbBWN{}kkOlb9?H5GmT=S(Be?5aNre+0F?5-Mth0#QfHSO1@Dxy&G!{7e zHeV+#UnWfp26=PUGL5Gk?1?6T7B(rR{)@(lBmYu4;(8au;#PW) z7r?$v%`9ZGfBJ0{=>gY;TKFs7=cL~!Y^kbk{;J{i-#pYMN(~1b!rpa&yX#s{MbwpV zA*akCQQ=;@Ku`gir9%w0q==$ff?3i#c#crG%Ojxj0NCjb56~GgB8*lwu4!RW`6Um z22UV>@ieayr`(8g7kD_wT&3R)@^1KtD-|}@y~oJ2HeaeLJ}K=4P{BF=aKFbdLTbo| zbU+hu!W1Kj2B#Fz%^u~vVa{^egLtGCdGqbpxAYS{-EsoL`~Kux zL3yHnLKn;_&zm7wldfY3gHj*coD40ZGQWF4 zhSM>=>KAJyrB}fW!NaHy9OTPz7yRs+L}M0B>yGK5q0-xTz6~n(w)i0!Re}pqyhd8d z{@RPWJ7ue}KL=i>-)91No41JbfeS_s&YfWzFRH~wm?!8Qn^(F?(b0Ab3=L+aN}n@G zQPy&`3@G|^L1lU4NumGsQeTrKzM0XI(U!gNTcjTPjrUo}TLfK~k${z$ET;7Y6|bI7 zU|J!nuh$mNQgEJ}M3hf@8mS)37FLB51n+veR#x~euT-6b_n|rZA@p_&~(MP?( z3dhb~&3?{Q@5-#Ci0jg$orqQtLjmXkUD?3h8XZhn%Ropn{folXou4a`oQr#Uc6Ub= z{!Xp&eU{@Urt^w~k@nBqOKLKqB}HHgW~)O(re^Z&W{I1&_uU2*0xDIMv5kq~wi4@yq8wFSEjqic%|Jgu)O&kYYur_ z2>PbiuxAIP$iLZ@)nI;f8)tZr76jhh#2p8gv8jG53QFdE0C17WD7I zy|Dqa!Xs6pz@ciow)sT5^lu)`=80gy$wi2m^WPd-|AhY=(g30M&|4OPZG)DmXZUD< zb2aKrypI1vP!#&%I$b0!xNv! z7cyCk@OZ9vSc?%;O}FM_mA8u|<_D_ot&gSpPcu0DdWGDb&U^MT)OUw0uA2W7N(Gdk zybb@toukJmg3OG_n_UIq9tUjvAv0~OG{)wi<0@IQt#}O4^jx)=>tjFx)BXC8%ot#6 zfdHVO9Yna?cc7M}C&AVjK}gnkNt~aP!ZeMQd`-U*Xp&_jtAEjW$usrD2b=H&14-I7 z^J5W#DQmuM#RsH3jg(Mgzwz~EC6evC#8&cx09rn~$^8!ov$lVBZm<|81d&B`oCGgj z1ko&pS&E;5O=-=5E+*QqLkiCpU1Tt~Mji#@W8O8U_{u+gp0xp`!e64kkh;)>TC!+2 zSU5}`P~S}rY`IO!NEbn&u1^xteE$N0>dFyGWjm6bZhEJMq_L_O#(;K|E z#z=k82BS{|3prD&@}QOVBU+CTLlM(?7Mbg`Q44QsON*KA^^vtSeeMYWRq+y;Zmjdj zageZ%<6j7OW(FNj4o!M~odps>i-(mA*pDlJO1asfUUb?(#yvyadfaQf`?<+D z!o0=eVXAN61@dKh6(Xq zBG{16+)>+R)p8oK^XdR7zf%3DkGJ$3D$Syzt-8I>=RljwZ$wiv;=}-&Km1RT&aDY| zKuQlLKYR%%N5v<=9)j>d-Mg1%x7PxKA+tp!5~n{V#9n zE3_f3U@Acr>NN{>n!-2^iuUTA!i``w z1ZDziu%VH@Kc;1ytPnG;N&A1kNciqo@qi7`j?vj{xhXvnR0jr9mkazbPrtk#bOPe? zsgLd`arHYg#?zFo%fC_+$m*pfGf&)v4^Gg)TmcRz4JbY4NjJC}wA%Jzfi5!tE!-IF zMsZYBWf$}cY(U0#FKghWZ*_E40^jKy882OSSvFiCZ%{CNe50N)0Eb9SV~muR4R(@R z4WK^vrMbH;{kc)C8BNnoIIEtA;eS-1mE}tCD@zLCE2H_@xgq<-gy$`FKDUNWNjeC- zqC>3|wW2cpogF6*bO09@Su>e&hWWjsq5{V|`2vtzF-}y{(A*PKZe>6d%$8y)a0${^ z>sy$yG|wK~Y51Y_TsuEb#rneaIv=eQ!b@-Y6RMxd#w;$Y=yq_q)Fi@sB3JnW0S(;N&hCU02X<$dBNf!J9aYSmK<_(mDEu_hnX zc#YRVZ7Nqi15@P1ta3Zwj5V%pWF1X)_hA?Z_`3@)D+u@GxHHx}ypyz8djCXtvtZlh__x}`4 V{O|LBE?@rNnEii`*|Fb*{{qY%$07g# literal 0 HcmV?d00001 diff --git a/img/docusaurus.png b/img/docusaurus.png new file mode 100644 index 0000000000000000000000000000000000000000..f458149e3c8f53335f28fbc162ae67f55575c881 GIT binary patch literal 5142 zcma)=cTf{R(}xj7f`AaDml%oxrAm_`5IRVc-jPtHML-0kDIiip57LWD@4bW~(nB|) z34|^sbOZqj<;8ct`Tl-)=Jw`pZtiw=e$UR_Mn2b8rM$y@hlq%XQe90+?|Mf68-Ux_ zzTBiDn~3P%oVt>{f$z+YC7A)8ak`PktoIXDkpXod+*gQW4fxTWh!EyR9`L|fi4YlH z{IyM;2-~t3s~J-KF~r-Z)FWquQCfG*TQy6w*9#k2zUWV-+tCNvjrtl9(o}V>-)N!) ziZgEgV>EG+b(j@ex!dx5@@nGZim*UfFe<+e;(xL|j-Pxg(PCsTL~f^br)4{n5?OU@ z*pjt{4tG{qBcDSa3;yKlopENd6Yth=+h9)*lkjQ0NwgOOP+5Xf?SEh$x6@l@ZoHoYGc5~d2>pO43s3R|*yZw9yX^kEyUV2Zw1%J4o`X!BX>CwJ zI8rh1-NLH^x1LnaPGki_t#4PEz$ad+hO^$MZ2 ziwt&AR}7_yq-9Pfn}k3`k~dKCbOsHjvWjnLsP1{)rzE8ERxayy?~{Qz zHneZ2gWT3P|H)fmp>vA78a{0&2kk3H1j|n59y{z@$?jmk9yptqCO%* zD2!3GHNEgPX=&Ibw?oU1>RSxw3;hhbOV77-BiL%qQb1(4J|k=Y{dani#g>=Mr?Uyd z)1v~ZXO_LT-*RcG%;i|Wy)MvnBrshlQoPxoO*82pKnFSGNKWrb?$S$4x+24tUdpb= zr$c3K25wQNUku5VG@A=`$K7%?N*K+NUJ(%%)m0Vhwis*iokN#atyu(BbK?+J+=H z!kaHkFGk+qz`uVgAc600d#i}WSs|mtlkuwPvFp) z1{Z%nt|NwDEKj1(dhQ}GRvIj4W?ipD76jZI!PGjd&~AXwLK*98QMwN&+dQN1ML(6< z@+{1`=aIc z9Buqm97vy3RML|NsM@A>Nw2=sY_3Ckk|s;tdn>rf-@Ke1m!%F(9(3>V%L?w#O&>yn z(*VIm;%bgezYB;xRq4?rY})aTRm>+RL&*%2-B%m; zLtxLTBS=G!bC$q;FQ|K3{nrj1fUp`43Qs&V!b%rTVfxlDGsIt3}n4p;1%Llj5ePpI^R} zl$Jhx@E}aetLO!;q+JH@hmelqg-f}8U=XnQ+~$9RHGUDOoR*fR{io*)KtYig%OR|08ygwX%UqtW81b@z0*`csGluzh_lBP=ls#1bwW4^BTl)hd|IIfa zhg|*M%$yt@AP{JD8y!7kCtTmu{`YWw7T1}Xlr;YJTU1mOdaAMD172T8Mw#UaJa1>V zQ6CD0wy9NEwUsor-+y)yc|Vv|H^WENyoa^fWWX zwJz@xTHtfdhF5>*T70(VFGX#8DU<^Z4Gez7vn&4E<1=rdNb_pj@0?Qz?}k;I6qz@| zYdWfcA4tmI@bL5JcXuoOWp?ROVe*&o-T!><4Ie9@ypDc!^X&41u(dFc$K$;Tv$c*o zT1#8mGWI8xj|Hq+)#h5JToW#jXJ73cpG-UE^tsRf4gKw>&%Z9A>q8eFGC zG@Iv(?40^HFuC_-%@u`HLx@*ReU5KC9NZ)bkS|ZWVy|_{BOnlK)(Gc+eYiFpMX>!# zG08xle)tntYZ9b!J8|4H&jaV3oO(-iFqB=d}hGKk0 z%j)johTZhTBE|B-xdinS&8MD=XE2ktMUX8z#eaqyU?jL~PXEKv!^) zeJ~h#R{@O93#A4KC`8@k8N$T3H8EV^E2 z+FWxb6opZnX-av5ojt@`l3TvSZtYLQqjps{v;ig5fDo^}{VP=L0|uiRB@4ww$Eh!CC;75L%7|4}xN+E)3K&^qwJizphcnn=#f<&Np$`Ny%S)1*YJ`#@b_n4q zi%3iZw8(I)Dzp0yY}&?<-`CzYM5Rp+@AZg?cn00DGhf=4|dBF8BO~2`M_My>pGtJwNt4OuQm+dkEVP4 z_f*)ZaG6@t4-!}fViGNd%E|2%ylnzr#x@C!CrZSitkHQ}?_;BKAIk|uW4Zv?_npjk z*f)ztC$Cj6O<_{K=dPwO)Z{I=o9z*lp?~wmeTTP^DMP*=<-CS z2FjPA5KC!wh2A)UzD-^v95}^^tT<4DG17#wa^C^Q`@f@=jLL_c3y8@>vXDJd6~KP( zurtqU1^(rnc=f5s($#IxlkpnU=ATr0jW`)TBlF5$sEwHLR_5VPTGiO?rSW9*ND`bYN*OX&?=>!@61{Z4)@E;VI9 zvz%NmR*tl>p-`xSPx$}4YcdRc{_9k)>4Jh&*TSISYu+Y!so!0JaFENVY3l1n*Fe3_ zRyPJ(CaQ-cNP^!3u-X6j&W5|vC1KU!-*8qCcT_rQN^&yqJ{C(T*`(!A=))=n%*-zp_ewRvYQoJBS7b~ zQlpFPqZXKCXUY3RT{%UFB`I-nJcW0M>1^*+v)AxD13~5#kfSkpWys^#*hu)tcd|VW zEbVTi`dbaM&U485c)8QG#2I#E#h)4Dz8zy8CLaq^W#kXdo0LH=ALhK{m_8N@Bj=Um zTmQOO*ID(;Xm}0kk`5nCInvbW9rs0pEw>zlO`ZzIGkB7e1Afs9<0Z(uS2g*BUMhp> z?XdMh^k}k<72>}p`Gxal3y7-QX&L{&Gf6-TKsE35Pv%1 z;bJcxPO+A9rPGsUs=rX(9^vydg2q`rU~otOJ37zb{Z{|)bAS!v3PQ5?l$+LkpGNJq zzXDLcS$vMy|9sIidXq$NE6A-^v@)Gs_x_3wYxF%y*_e{B6FvN-enGst&nq0z8Hl0< z*p6ZXC*su`M{y|Fv(Vih_F|83=)A6ay-v_&ph1Fqqcro{oeu99Y0*FVvRFmbFa@gs zJ*g%Gik{Sb+_zNNf?Qy7PTf@S*dTGt#O%a9WN1KVNj`q$1Qoiwd|y&_v?}bR#>fdP zSlMy2#KzRq4%?ywXh1w;U&=gKH%L~*m-l%D4Cl?*riF2~r*}ic9_{JYMAwcczTE`!Z z^KfriRf|_YcQ4b8NKi?9N7<4;PvvQQ}*4YxemKK3U-7i}ap8{T7=7`e>PN7BG-Ej;Uti2$o=4T#VPb zm1kISgGzj*b?Q^MSiLxj26ypcLY#RmTPp+1>9zDth7O?w9)onA%xqpXoKA-`Jh8cZ zGE(7763S3qHTKNOtXAUA$H;uhGv75UuBkyyD;eZxzIn6;Ye7JpRQ{-6>)ioiXj4Mr zUzfB1KxvI{ZsNj&UA`+|)~n}96q%_xKV~rs?k=#*r*7%Xs^Hm*0~x>VhuOJh<2tcb zKbO9e-w3zbekha5!N@JhQm7;_X+J!|P?WhssrMv5fnQh$v*986uWGGtS}^szWaJ*W z6fLVt?OpPMD+-_(3x8Ra^sX~PT1t5S6bfk@Jb~f-V)jHRul#Hqu;0(+ER7Z(Z4MTR z+iG>bu+BW2SNh|RAGR2-mN5D1sTcb-rLTha*@1@>P~u;|#2N{^AC1hxMQ|(sp3gTa zDO-E8Yn@S7u=a?iZ!&&Qf2KKKk7IT`HjO`U*j1~Df9Uxz$~@otSCK;)lbLSmBuIj% zPl&YEoRwsk$8~Az>>djrdtp`PX z`Pu#IITS7lw07vx>YE<4pQ!&Z^7L?{Uox`CJnGjYLh1XN^tt#zY*0}tA*a=V)rf=&-kLgD|;t1D|ORVY}8 F{0H{b<4^zq literal 0 HcmV?d00001 diff --git a/img/favicon.ico b/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1545f3896178afcc46859ccad051d752e5d626d5 GIT binary patch literal 5430 zcmeHLStxH^6hGhXKJcRZ;8iKPM9T0eLnLG`ub>dbCaH)o@i!fhAuBJ zX=P=FUS3}4`T3cOi;JnEqC)r9=jZ2CQc^+7_;yGy;jy#${H1qC!eKTjuZiRf*A2pj`xVmZv`=Id3l+07~^-q7DfQ3)77U+|kj|WMgB)B{MTq*Zzw7?-x1N+uNJEy1H~_Z*Pw~#l^+Z z#l;2d$YIXT&YZ)92R}bQoqyyNFE1}1Bl5MAlM_u$Oo;A=C-|eHq6~Inkol4OkiT+r zazr!9{K#XjuC6@yqQlV85XHpAh^paf4L^91t0E&KH9s;x*4*FUU$jSbW8aU4-Y9eHkPqvWo0xuImx=rk9FDF+S2*|S;bQR>FH_S zd+;|lHbxi&MCSMP^(Bnc8nKkWyu6(H`}^57Vn$k88tXDY&hhE#sWtQ#uRqp&XJ?0f zBc_Ffgc$gJe0)BPALmLwLx_7gTk`vV)coV@1O^7`<~2JzoA;W`k2v-b`wuZEH8oYo zk9#k^uVntLtSr;>hwqp7#FG8j+S*E`rKLI-3kwT;9%TNSni?uBEY$TwB{(>kkVnnL z68_xWTpAxA*TGLtPAD-kQTP69Yiox1i`?$!=BC9X0494_u3H; g% + + + + +Hello from LmcRbacMvc | LmcRbacMvc + + + + + +

LmcRbacMvc

Role-based access control for Laminas MVC application

Part of the LM-Commons series of community developed packages for Laminas

+ + \ No newline at end of file diff --git a/markdown-page/index.html b/markdown-page/index.html new file mode 100644 index 00000000..dcfadf6a --- /dev/null +++ b/markdown-page/index.html @@ -0,0 +1,15 @@ + + + + + +Markdown page example | LmcRbacMvc + + + + + +

Markdown page example

+

You don't need React to write simple standalone pages.

+ + \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist deleted file mode 100644 index 2bfda57b..00000000 --- a/phpunit.xml.dist +++ /dev/null @@ -1,11 +0,0 @@ - - - - - ./src - - - - ./tests - - diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 00000000..0c47a953 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1 @@ +https://lm-commons.github.io/lmc-rbac-mvc/blogweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/blog/archiveweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/blog/new-documentationweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/blog/tagsweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/blog/tags/laminasweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/blog/tags/lmcrbacmvcweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/blog/tags/phpweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/blog/welcomeweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/markdown-pageweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/docs/cookbookweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/docs/guardsweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/docs/installationweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/docs/introweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/docs/quick-startweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/docs/role-providersweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/docs/strategiesweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/docs/supportweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/docs/using-the-authorization-serviceweekly0.5https://lm-commons.github.io/lmc-rbac-mvc/weekly0.5 \ No newline at end of file diff --git a/src/Assertion/AssertionInterface.php b/src/Assertion/AssertionInterface.php deleted file mode 100644 index 9724a59c..00000000 --- a/src/Assertion/AssertionInterface.php +++ /dev/null @@ -1,43 +0,0 @@ - - * @author Aeneas Rekkas - * @author Daniel Gimenes - * @license MIT - */ -interface AssertionInterface -{ - /** - * Check if this assertion is true - * - * @TODO: for v3, update the interface to typehint to AuthorizationServiceInterface instead - * - * @param AuthorizationService $authorizationService - * @param mixed $context - * @return bool - */ - public function assert(AuthorizationService $authorizationService); -} diff --git a/src/Assertion/AssertionPluginManager.php b/src/Assertion/AssertionPluginManager.php deleted file mode 100644 index 43ab9b54..00000000 --- a/src/Assertion/AssertionPluginManager.php +++ /dev/null @@ -1,64 +0,0 @@ -validate($plugin); - } - - /** - * {@inheritDoc} - */ - protected function canonicalizeName($name) - { - return $name; - } -} diff --git a/src/Collector/RbacCollector.php b/src/Collector/RbacCollector.php deleted file mode 100644 index 3a0c645a..00000000 --- a/src/Collector/RbacCollector.php +++ /dev/null @@ -1,251 +0,0 @@ - - * @license MIT - */ -class RbacCollector implements CollectorInterface, Serializable -{ - /** - * Collector priority - */ - const PRIORITY = -100; - - /** - * @var array - */ - protected $collectedGuards = []; - - /** - * @var array - */ - protected $collectedRoles = []; - - /** - * @var array - */ - protected $collectedPermissions = []; - - /** - * @var array - */ - protected $collectedOptions = []; - - /** - * Collector Name. - * - * @return string - */ - public function getName() - { - return 'lmc_rbac'; - } - - /** - * Collector Priority. - * - * @return integer - */ - public function getPriority() - { - return self::PRIORITY; - } - - /** - * Collects data. - * - * @param MvcEvent $mvcEvent - * @throws ReflectionException - */ - public function collect(MvcEvent $mvcEvent) - { - if (!$application = $mvcEvent->getApplication()) { - return; - } - - $serviceManager = $application->getServiceManager(); - - /* @var RoleService $roleService */ - $roleService = $serviceManager->get('LmcRbacMvc\Service\RoleService'); - - /* @var ModuleOptions $options */ - $options = $serviceManager->get('LmcRbacMvc\Options\ModuleOptions'); - - // Start collect all the data we need! - $this->collectOptions($options); - $this->collectGuards($options->getGuards()); - $this->collectIdentityRolesAndPermissions($roleService); - } - - /** - * Collect options - * - * @param ModuleOptions $moduleOptions - * @return void - */ - private function collectOptions(ModuleOptions $moduleOptions) - { - $this->collectedOptions = [ - 'guest_role' => $moduleOptions->getGuestRole(), - 'protection_policy' => $moduleOptions->getProtectionPolicy() - ]; - } - - /** - * Collect guards - * - * @param array $guards - * @return void - */ - private function collectGuards($guards) - { - $this->collectedGuards = []; - - foreach ($guards as $type => $rules) { - $this->collectedGuards[$type] = $rules; - } - } - - /** - * Collect roles and permissions - * - * @param RoleService $roleService - * @return void - * @throws ReflectionException - */ - private function collectIdentityRolesAndPermissions(RoleService $roleService) - { - $identityRoles = $roleService->getIdentityRoles(); - - foreach ($identityRoles as $role) { - $roleName = $role->getName(); - - if (!$role instanceof HierarchicalRoleInterface) { - $this->collectedRoles[] = $roleName; - } else { - $iteratorIterator = new RecursiveIteratorIterator( - new RecursiveRoleIterator($role->getChildren()), - RecursiveIteratorIterator::SELF_FIRST - ); - - foreach ($iteratorIterator as $childRole) { - $this->collectedRoles[$roleName][] = $childRole->getName(); - $this->collectPermissions($childRole); - } - } - - $this->collectPermissions($role); - } - } - - /** - * Collect permissions for the given role - * - * @param RoleInterface $role - * @return void - * @throws ReflectionException - */ - private function collectPermissions(RoleInterface $role) - { - if (method_exists($role, 'getPermissions')) { - $permissions = $role->getPermissions(); - } else { - // Gather the permissions for the given role. We have to use reflection as - // the RoleInterface does not have "getPermissions" method - $reflectionProperty = new ReflectionProperty($role, 'permissions'); - $reflectionProperty->setAccessible(true); - - $permissions = $reflectionProperty->getValue($role); - } - - if ($permissions instanceof Traversable) { - $permissions = iterator_to_array($permissions); - } - - array_walk($permissions, function (&$permission) { - $permission = (string) $permission; - }); - - $this->collectedPermissions[$role->getName()] = array_values($permissions); - } - - /** - * @return array|string[] - */ - public function getCollection() - { - return [ - 'guards' => $this->collectedGuards, - 'roles' => $this->collectedRoles, - 'permissions' => $this->collectedPermissions, - 'options' => $this->collectedOptions - ]; - } - /** - * {@inheritDoc} - */ - public function serialize() - { - return serialize($this->__serialize()); - } - - /** - * {@inheritDoc} - */ - public function unserialize($data) - { - $collection = unserialize($data); - if (!is_array($collection)) { - throw new InvalidArgumentException(__METHOD__ . ": Unserialized data should be an array."); - } - - $this->__unserialize($collection); - } - - public function __serialize(): array - { - return $this->getCollection(); - } - - public function __unserialize(array $data): void - { - $this->collectedGuards = $data['guards']; - $this->collectedRoles = $data['roles']; - $this->collectedPermissions = $data['permissions']; - $this->collectedOptions = $data['options']; - } -} diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php deleted file mode 100644 index ee1cddfd..00000000 --- a/src/Exception/ExceptionInterface.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @license MIT - */ -interface ExceptionInterface -{ -} diff --git a/src/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php deleted file mode 100644 index 65a03edc..00000000 --- a/src/Exception/InvalidArgumentException.php +++ /dev/null @@ -1,31 +0,0 @@ - - * @license MIT - */ -class InvalidArgumentException extends BaseInvalidArgumentException implements ExceptionInterface -{ -} diff --git a/src/Exception/RoleNotFoundException.php b/src/Exception/RoleNotFoundException.php deleted file mode 100644 index 97301870..00000000 --- a/src/Exception/RoleNotFoundException.php +++ /dev/null @@ -1,35 +0,0 @@ - - * @license MIT - */ -class RoleNotFoundException extends BaseRuntimeException implements ExceptionInterface -{ - /** - * @var string - */ - protected $message = 'No role could be found'; -} diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php deleted file mode 100644 index ec9f873d..00000000 --- a/src/Exception/RuntimeException.php +++ /dev/null @@ -1,31 +0,0 @@ - - * @license MIT - */ -class RuntimeException extends BaseRuntimeException implements ExceptionInterface -{ -} diff --git a/src/Exception/UnauthorizedException.php b/src/Exception/UnauthorizedException.php deleted file mode 100644 index 2f28fa88..00000000 --- a/src/Exception/UnauthorizedException.php +++ /dev/null @@ -1,35 +0,0 @@ - - * @license MIT - */ -class UnauthorizedException extends BaseRuntimeException implements UnauthorizedExceptionInterface -{ - /** - * @var string - */ - protected $message = 'You are not authorized to access this resource'; -} diff --git a/src/Exception/UnauthorizedExceptionInterface.php b/src/Exception/UnauthorizedExceptionInterface.php deleted file mode 100644 index 72d3a544..00000000 --- a/src/Exception/UnauthorizedExceptionInterface.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @license MIT - */ -interface UnauthorizedExceptionInterface extends ExceptionInterface -{ -} diff --git a/src/Factory/AssertionPluginManagerFactory.php b/src/Factory/AssertionPluginManagerFactory.php deleted file mode 100644 index 2512529a..00000000 --- a/src/Factory/AssertionPluginManagerFactory.php +++ /dev/null @@ -1,56 +0,0 @@ -get('Config')['lmc_rbac']['assertion_manager']; - - return new AssertionPluginManager($container, $config); - } - - /** - * {@inheritDoc} - * @return AssertionPluginManager - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator, AssertionPluginManager::class); - } -} diff --git a/src/Factory/AuthenticationIdentityProviderFactory.php b/src/Factory/AuthenticationIdentityProviderFactory.php deleted file mode 100644 index 707f2f55..00000000 --- a/src/Factory/AuthenticationIdentityProviderFactory.php +++ /dev/null @@ -1,57 +0,0 @@ - - * @license MIT - */ -class AuthenticationIdentityProviderFactory implements FactoryInterface -{ - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return AuthenticationIdentityProvider - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - /* @var AuthenticationService $authenticationProvider */ - $authenticationProvider = $container->get(AuthenticationService::class); - - return new AuthenticationIdentityProvider($authenticationProvider); - } - - /** - * {@inheritDoc} - * @return AuthenticationIdentityProvider - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator, AuthenticationIdentityProvider::class); - } -} diff --git a/src/Factory/AuthorizationServiceDelegatorFactory.php b/src/Factory/AuthorizationServiceDelegatorFactory.php deleted file mode 100644 index a09adf52..00000000 --- a/src/Factory/AuthorizationServiceDelegatorFactory.php +++ /dev/null @@ -1,73 +0,0 @@ - - * @license MIT License - */ -class AuthorizationServiceDelegatorFactory implements DelegatorFactoryInterface -{ - /** - * @param ContainerInterface $container - * @param string $name - * @param callable $callback - * @param array|null $options - * @return mixed - */ - public function __invoke(ContainerInterface $container, $name, callable $callback, array $options = null) - { - $instanceToDecorate = call_user_func($callback); - - if (!$instanceToDecorate instanceof AuthorizationServiceAwareInterface) { - throw new RuntimeException("The service $name must implement AuthorizationServiceAwareInterface."); - } -/* - if ($container instanceof AbstractPluginManager) { - $container = $container->getServiceLocator(); - } -*/ - $authorizationService = $container->get(AuthorizationService::class); - $instanceToDecorate->setAuthorizationService($authorizationService); - - return $instanceToDecorate; - } - - /** - * @param ServiceLocatorInterface $serviceLocator - * @param string $name - * @param string $requestedName - * @param callable $callback - * @return mixed - */ - public function createDelegatorWithName(ServiceLocatorInterface $serviceLocator, $name, $requestedName, $callback) - { - return $this($serviceLocator, $requestedName, $callback); - } -} diff --git a/src/Factory/AuthorizationServiceFactory.php b/src/Factory/AuthorizationServiceFactory.php deleted file mode 100644 index a8332e00..00000000 --- a/src/Factory/AuthorizationServiceFactory.php +++ /dev/null @@ -1,72 +0,0 @@ - - * @license MIT - */ -class AuthorizationServiceFactory implements FactoryInterface -{ - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return AuthorizationService - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - /* @var Rbac $rbac */ - $rbac = $container->get(Rbac::class); - - /* @var RoleService $roleService */ - $roleService = $container->get(RoleService::class); - - /* @var AssertionPluginManager $assertionPluginManager */ - $assertionPluginManager = $container->get(AssertionPluginManager::class); - - /* @var ModuleOptions $moduleOptions */ - $moduleOptions = $container->get(ModuleOptions::class); - - $authorizationService = new AuthorizationService($rbac, $roleService, $assertionPluginManager); - $authorizationService->setAssertions($moduleOptions->getAssertionMap()); - - return $authorizationService; - } - - /** - * {@inheritDoc} - * @return AuthorizationService - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator, AuthorizationService::class); - } -} diff --git a/src/Factory/ControllerGuardFactory.php b/src/Factory/ControllerGuardFactory.php deleted file mode 100644 index 43274007..00000000 --- a/src/Factory/ControllerGuardFactory.php +++ /dev/null @@ -1,90 +0,0 @@ - - * @license MIT - */ -class ControllerGuardFactory implements FactoryInterface -{ - /** - * @var array - */ - protected $options = []; - - /** - * {@inheritDoc} - */ - public function __construct(array $options = []) - { - $this->setCreationOptions($options); - } - - /** - * {@inheritDoc} - */ - public function setCreationOptions(array $options) - { - $this->options = $options; - } - - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return ControllerGuard - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - if (null === $options) { - $options = []; - } - - /* @var ModuleOptions $moduleOptions */ - $moduleOptions = $container->get('LmcRbacMvc\Options\ModuleOptions'); - - /* @var RoleService $roleService */ - $roleService = $container->get('LmcRbacMvc\Service\RoleService'); - - $controllerGuard = new ControllerGuard($roleService, $options); - $controllerGuard->setProtectionPolicy($moduleOptions->getProtectionPolicy()); - - return $controllerGuard; - } - - - /** - * {@inheritDoc} - * @return ControllerGuard - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator->getServiceLocator(), ControllerGuard::class, $this->options); - } -} diff --git a/src/Factory/ControllerPermissionsGuardFactory.php b/src/Factory/ControllerPermissionsGuardFactory.php deleted file mode 100644 index 0e023150..00000000 --- a/src/Factory/ControllerPermissionsGuardFactory.php +++ /dev/null @@ -1,90 +0,0 @@ - - * @license MIT - */ -class ControllerPermissionsGuardFactory implements FactoryInterface -{ - /** - * @var array - */ - protected $options = []; - - /** - * {@inheritDoc} - */ - public function __construct(array $options = []) - { - $this->setCreationOptions($options); - } - - /** - * {@inheritDoc} - */ - public function setCreationOptions(array $options) - { - $this->options = $options; - } - - /** - * @param ContainerInterface $container - * @param string $resolvedName - * @param array|null $options - * @return ControllerPermissionsGuard - */ - public function __invoke(ContainerInterface $container, $resolvedName, array $options = null) - { - if (null === $options) { - $options = []; - } - - /* @var ModuleOptions $moduleOptions */ - $moduleOptions = $container->get('LmcRbacMvc\Options\ModuleOptions'); - - /* @var AuthorizationService $authorizationService */ - $authorizationService = $container->get('LmcRbacMvc\Service\AuthorizationService'); - - $guard = new ControllerPermissionsGuard($authorizationService, $options); - $guard->setProtectionPolicy($moduleOptions->getProtectionPolicy()); - - return $guard; - } - - /** - * @param AbstractPluginManager|ServiceLocatorInterface $serviceLocator - * @return ControllerPermissionsGuard - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator->getServiceLocator(), ControllerPermissionsGuard::class, $this->options); - } -} diff --git a/src/Factory/GuardPluginManagerFactory.php b/src/Factory/GuardPluginManagerFactory.php deleted file mode 100644 index 3c4e5b58..00000000 --- a/src/Factory/GuardPluginManagerFactory.php +++ /dev/null @@ -1,58 +0,0 @@ - - * @license MIT - */ -class GuardPluginManagerFactory implements FactoryInterface -{ - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return GuardPluginManager - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - $config = $container->get('Config')['lmc_rbac']['guard_manager']; - - $pluginManager = new GuardPluginManager($container, $config); - - return $pluginManager; - } - - /** - * {@inheritDoc} - * @return GuardPluginManager - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator, GuardPluginManager::class); - } -} diff --git a/src/Factory/GuardsFactory.php b/src/Factory/GuardsFactory.php deleted file mode 100644 index f69b3117..00000000 --- a/src/Factory/GuardsFactory.php +++ /dev/null @@ -1,71 +0,0 @@ - - * @license MIT - */ -class GuardsFactory implements FactoryInterface -{ - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return array - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - /* @var ModuleOptions $options */ - $options = $container->get(ModuleOptions::class); - $guardsOptions = $options->getGuards(); - - if (empty($guardsOptions)) { - return []; - } - - /* @var GuardPluginManager $pluginManager */ - $pluginManager = $container->get(GuardPluginManager::class); - $guards = []; - - foreach ($guardsOptions as $type => $options) { - $guards[] = $pluginManager->get($type, $options); - } - - return $guards; - } - - /** - * {@inheritDoc} - * @return GuardInterface[]|array - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator, GuardInterface::class); - } -} diff --git a/src/Factory/HasRoleViewHelperFactory.php b/src/Factory/HasRoleViewHelperFactory.php deleted file mode 100644 index 2ab84721..00000000 --- a/src/Factory/HasRoleViewHelperFactory.php +++ /dev/null @@ -1,57 +0,0 @@ - - * @license MIT - */ -class HasRoleViewHelperFactory implements FactoryInterface -{ - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return HasRole - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - /* @var RoleService $roleService */ - $roleService = $container->get(RoleService::class); - - return new HasRole($roleService); - } - - /** - * {@inheritDoc} - * @return HasRole - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator->getServiceLocator(), HasRole::class); - } -} diff --git a/src/Factory/IsGrantedPluginFactory.php b/src/Factory/IsGrantedPluginFactory.php deleted file mode 100644 index 6841f118..00000000 --- a/src/Factory/IsGrantedPluginFactory.php +++ /dev/null @@ -1,57 +0,0 @@ - - * @license MIT - */ -class IsGrantedPluginFactory implements FactoryInterface -{ - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return IsGranted - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - /* @var AuthorizationService $authorizationService */ - $authorizationService = $container->get(AuthorizationService::class); - - return new IsGranted($authorizationService); - } - - /** - * {@inheritDoc} - * @return IsGranted - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator->getServiceLocator(), IsGranted::class); - } -} diff --git a/src/Factory/IsGrantedViewHelperFactory.php b/src/Factory/IsGrantedViewHelperFactory.php deleted file mode 100644 index 09c638a8..00000000 --- a/src/Factory/IsGrantedViewHelperFactory.php +++ /dev/null @@ -1,57 +0,0 @@ - - * @license MIT - */ -class IsGrantedViewHelperFactory implements FactoryInterface -{ - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return IsGranted - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - /* @var AuthorizationService $authorizationService */ - $authorizationService = $container->get(AuthorizationService::class); - - return new IsGranted($authorizationService); - } - - /** - * {@inheritDoc} - * @return IsGranted - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator->getServiceLocator(), IsGranted::class); - } -} diff --git a/src/Factory/ModuleOptionsFactory.php b/src/Factory/ModuleOptionsFactory.php deleted file mode 100644 index e240731e..00000000 --- a/src/Factory/ModuleOptionsFactory.php +++ /dev/null @@ -1,53 +0,0 @@ - - * @license MIT - */ -class ModuleOptionsFactory implements FactoryInterface -{ - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return ModuleOptions - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - return new ModuleOptions($container->get('Config')['lmc_rbac']); - } - - /** - * {@inheritDoc} - * @return ModuleOptions - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator, ModuleOptions::class); - } -} diff --git a/src/Factory/ObjectRepositoryRoleProviderFactory.php b/src/Factory/ObjectRepositoryRoleProviderFactory.php deleted file mode 100644 index 1605cbd6..00000000 --- a/src/Factory/ObjectRepositoryRoleProviderFactory.php +++ /dev/null @@ -1,101 +0,0 @@ - - * @license MIT - */ -class ObjectRepositoryRoleProviderFactory implements FactoryInterface -{ - /** - * @var array - */ - protected $options = []; - - /** - * {@inheritDoc} - */ - public function __construct(array $options = []) - { - $this->setCreationOptions($options); - } - - /** - * {@inheritDoc} - */ - public function setCreationOptions(array $options) - { - $this->options = $options; - } - - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return ObjectRepositoryRoleProvider - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - $objectRepository = null; - - if (!isset($options['role_name_property'])) { - throw new Exception\RuntimeException('The "role_name_property" option is missing'); - } - - if (isset($options['object_repository'])) { - /* @var ObjectRepository $objectRepository */ - $objectRepository = $container->get($options['object_repository']); - - return new ObjectRepositoryRoleProvider($objectRepository, $options['role_name_property']); - } - - if (isset($options['object_manager']) && isset($options['class_name'])) { - /* @var ObjectManager $objectManager */ - $objectManager = $container->get($options['object_manager']); - $objectRepository = $objectManager->getRepository($options['class_name']); - - return new ObjectRepositoryRoleProvider($objectRepository, $options['role_name_property']); - } - - throw new Exception\RuntimeException( - 'No object repository was found while creating the LmcRbacMvc object repository role provider. Are - you sure you specified either the "object_repository" option or "object_manager"/"class_name" options?' - ); - } - - /** - * {@inheritDoc} - * @return ObjectRepositoryRoleProvider - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator->getServiceLocator(), ObjectRepositoryRoleProvider::class, $this->options); - } -} diff --git a/src/Factory/RbacFactory.php b/src/Factory/RbacFactory.php deleted file mode 100644 index 2582e5ca..00000000 --- a/src/Factory/RbacFactory.php +++ /dev/null @@ -1,51 +0,0 @@ - - * @license MIT - */ -class RbacFactory implements FactoryInterface -{ - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return Rbac - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - return new Rbac(new GeneratorStrategy()); - } - - /** - * {@inheritDoc} - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator, Rbac::class); - } -} diff --git a/src/Factory/RedirectStrategyFactory.php b/src/Factory/RedirectStrategyFactory.php deleted file mode 100644 index 6eeffe67..00000000 --- a/src/Factory/RedirectStrategyFactory.php +++ /dev/null @@ -1,59 +0,0 @@ - - * @license MIT - */ -class RedirectStrategyFactory implements FactoryInterface -{ - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return RedirectStrategy - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - /* @var ModuleOptions $moduleOptions */ - $moduleOptions = $container->get(ModuleOptions::class); - /** @var AuthenticationService $authenticationService */ - $authenticationService = $container->get(AuthenticationService::class); - - return new RedirectStrategy($moduleOptions->getRedirectStrategy(), $authenticationService); - } - - /** - * {@inheritDoc} - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator, RedirectStrategy::class); - } -} diff --git a/src/Factory/RoleProviderPluginManagerFactory.php b/src/Factory/RoleProviderPluginManagerFactory.php deleted file mode 100644 index 86eda746..00000000 --- a/src/Factory/RoleProviderPluginManagerFactory.php +++ /dev/null @@ -1,55 +0,0 @@ - - * @license MIT - */ -class RoleProviderPluginManagerFactory implements FactoryInterface -{ - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return RoleProviderPluginManager - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - $config = $container->get('Config')['lmc_rbac']['role_provider_manager']; - - return new RoleProviderPluginManager($container, $config); - } - - /** - * {@inheritDoc} - * @return RoleProviderPluginManager - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator, RoleProviderPluginManager::class); - } -} diff --git a/src/Factory/RoleServiceFactory.php b/src/Factory/RoleServiceFactory.php deleted file mode 100644 index ff3f034e..00000000 --- a/src/Factory/RoleServiceFactory.php +++ /dev/null @@ -1,85 +0,0 @@ - - * @license MIT - */ -class RoleServiceFactory implements FactoryInterface -{ - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return RoleService - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - /* @var ModuleOptions $moduleOptions */ - $moduleOptions = $container->get(ModuleOptions::class); - - /* @var IdentityProviderInterface $identityProvider */ - $identityProvider = $container->get($moduleOptions->getIdentityProvider()); - - $roleProviderConfig = $moduleOptions->getRoleProvider(); - - if (empty($roleProviderConfig)) { - throw new RuntimeException('No role provider has been set for LmcRbacMvc'); - } - - /* @var RoleProviderPluginManager $pluginManager */ - $pluginManager = $container->get(RoleProviderPluginManager::class); - - /* @var RoleProviderInterface $roleProvider */ - reset($roleProviderConfig); - $roleProvider = $pluginManager->get(key($roleProviderConfig), current($roleProviderConfig)); - - /* @var TraversalStrategyInterface $traversalStrategy */ - $traversalStrategy = $container->get(Rbac::class)->getTraversalStrategy(); - - $roleService = new RoleService($identityProvider, $roleProvider, $traversalStrategy); - $roleService->setGuestRole($moduleOptions->getGuestRole()); - - return $roleService; - } - - /** - * {@inheritDoc} - * @return RoleService - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator, RoleService::class); - } -} diff --git a/src/Factory/RouteGuardFactory.php b/src/Factory/RouteGuardFactory.php deleted file mode 100644 index 177d9dfe..00000000 --- a/src/Factory/RouteGuardFactory.php +++ /dev/null @@ -1,90 +0,0 @@ - - * @license MIT - */ -class RouteGuardFactory implements FactoryInterface -{ - /** - * @var array - */ - protected $options = []; - - /** - * {@inheritDoc} - */ - public function __construct(array $options = []) - { - $this->setCreationOptions($options); - } - - /** - * {@inheritDoc} - */ - public function setCreationOptions(array $options) - { - $this->options = $options; - } - - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return RouteGuard - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - if (null === $options) { - $options = []; - } - - /* @var ModuleOptions $moduleOptions */ - $moduleOptions = $container->get(ModuleOptions::class); - - /* @var RoleService $roleService */ - $roleService = $container->get(RoleService::class); - - $routeGuard = new RouteGuard($roleService, $options); - $routeGuard->setProtectionPolicy($moduleOptions->getProtectionPolicy()); - - return $routeGuard; - } - - - /** - * {@inheritDoc} - * @return RouteGuard - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator->getServiceLocator(), RouteGuard::class, $this->options); - } -} diff --git a/src/Factory/RoutePermissionsGuardFactory.php b/src/Factory/RoutePermissionsGuardFactory.php deleted file mode 100644 index 9cf1ad90..00000000 --- a/src/Factory/RoutePermissionsGuardFactory.php +++ /dev/null @@ -1,91 +0,0 @@ - - * @author JM Lerouxw - * @license MIT - */ -class RoutePermissionsGuardFactory implements FactoryInterface -{ - /** - * @var array - */ - protected $options = []; - - /** - * {@inheritDoc} - */ - public function __construct(array $options = []) - { - $this->setCreationOptions($options); - } - - /** - * {@inheritDoc} - */ - public function setCreationOptions(array $options) - { - $this->options = $options; - } - - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return RoutePermissionsGuard - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - if (null === $options) { - $options = []; - } - - /* @var ModuleOptions $moduleOptions */ - $moduleOptions = $container->get(ModuleOptions::class); - - /* @var AuthorizationService $authorizationService */ - $authorizationService = $container->get(AuthorizationService::class); - - $routeGuard = new RoutePermissionsGuard($authorizationService, $options); - $routeGuard->setProtectionPolicy($moduleOptions->getProtectionPolicy()); - - return $routeGuard; - } - - /** - * @param \Laminas\ServiceManager\AbstractPluginManager|ServiceLocatorInterface $serviceLocator - * @return RouteGuard - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator->getServiceLocator(), RoutePermissionsGuard::class, $this->options); - } -} diff --git a/src/Factory/UnauthorizedStrategyFactory.php b/src/Factory/UnauthorizedStrategyFactory.php deleted file mode 100644 index 68799c58..00000000 --- a/src/Factory/UnauthorizedStrategyFactory.php +++ /dev/null @@ -1,56 +0,0 @@ - - * @license MIT - */ -class UnauthorizedStrategyFactory implements FactoryInterface -{ - /** - * @param ContainerInterface $container - * @param string $requestedName - * @param array|null $options - * @return UnauthorizedStrategy - */ - public function __invoke(ContainerInterface $container, $requestedName, array $options = null) - { - /* @var ModuleOptions $moduleOptions */ - $moduleOptions = $container->get(ModuleOptions::class); - - return new UnauthorizedStrategy($moduleOptions->getUnauthorizedStrategy()); - } - - /** - * {@inheritDoc} - */ - public function createService(ServiceLocatorInterface $serviceLocator) - { - return $this($serviceLocator, UnauthorizedStrategy::class); - } -} diff --git a/src/Guard/AbstractGuard.php b/src/Guard/AbstractGuard.php deleted file mode 100644 index bb61c0a3..00000000 --- a/src/Guard/AbstractGuard.php +++ /dev/null @@ -1,80 +0,0 @@ - - * @license MIT - */ -abstract class AbstractGuard implements GuardInterface -{ - use ListenerAggregateTrait; - - /** - * Event priority - */ - const EVENT_PRIORITY = -5; - - /** - * MVC event to listen - */ - const EVENT_NAME = MvcEvent::EVENT_ROUTE; - - /** - * {@inheritDoc} - */ - public function attach(EventManagerInterface $events, $priority = AbstractGuard::EVENT_PRIORITY) - { - $this->listeners[] = $events->attach(static::EVENT_NAME, [$this, 'onResult'], $priority); - } - - /** - * @private - * @param MvcEvent $event - * @return void - */ - public function onResult(MvcEvent $event) - { - if ($this->isGranted($event)) { - return; - } - - $event->setError(self::GUARD_UNAUTHORIZED); - $event->setParam('exception', new Exception\UnauthorizedException( - 'You are not authorized to access this resource', - 403 - )); - - $application = $event->getApplication(); - $eventManager = $application->getEventManager(); - - $event->setName(MvcEvent::EVENT_DISPATCH_ERROR); - $eventManager->triggerEvent($event); - - // just in case - $event->stopPropagation(true); - } -} diff --git a/src/Guard/ControllerGuard.php b/src/Guard/ControllerGuard.php deleted file mode 100644 index 8fd46180..00000000 --- a/src/Guard/ControllerGuard.php +++ /dev/null @@ -1,129 +0,0 @@ - - * @license MIT - */ -class ControllerGuard extends AbstractGuard -{ - use ProtectionPolicyTrait; - - /** - * Event priority - */ - const EVENT_PRIORITY = -10; - - /** - * @var RoleService - */ - protected $roleService; - - /** - * Controller guard rules - * - * @var array - */ - protected $rules = []; - - /** - * Constructor - * - * @param RoleService $roleService - * @param array $rules - */ - public function __construct(RoleService $roleService, array $rules = []) - { - $this->roleService = $roleService; - $this->setRules($rules); - } - - /** - * Set the rules - * - * A controller rule is made the following way: - * - * [ - * 'controller' => 'ControllerName', - * 'actions' => []/string - * 'roles' => []/string - * ] - * - * @param array $rules - * @return void - */ - public function setRules(array $rules) - { - $this->rules = []; - - foreach ($rules as $rule) { - $controller = strtolower($rule['controller']); - $actions = isset($rule['actions']) ? (array) $rule['actions'] : []; - $roles = (array) $rule['roles']; - - if (empty($actions)) { - $this->rules[$controller][0] = $roles; - continue; - } - - foreach ($actions as $action) { - $this->rules[$controller][strtolower($action)] = $roles; - } - } - } - - /** - * {@inheritDoc} - */ - public function isGranted(MvcEvent $event) - { - $routeMatch = $event->getRouteMatch(); - $controller = strtolower($routeMatch->getParam('controller')); - $action = strtolower($routeMatch->getParam('action')); - - // If no rules apply, it is considered as granted or not based on the protection policy - if (!isset($this->rules[$controller])) { - return $this->protectionPolicy === self::POLICY_ALLOW; - } - - // Algorithm is as follow: we first check if there is an exact match (controller + action), if not - // we check if there are rules set globally for the whole controllers (see the index "0"), and finally - // if nothing is matched, we fallback to the protection policy logic - - if (isset($this->rules[$controller][$action])) { - $allowedRoles = $this->rules[$controller][$action]; - } elseif (isset($this->rules[$controller][0])) { - $allowedRoles = $this->rules[$controller][0]; - } else { - return $this->protectionPolicy === self::POLICY_ALLOW; - } - - if (in_array('*', $allowedRoles)) { - return true; - } - - return $this->roleService->matchIdentityRoles($allowedRoles); - } -} diff --git a/src/Guard/ControllerPermissionsGuard.php b/src/Guard/ControllerPermissionsGuard.php deleted file mode 100644 index 607d5423..00000000 --- a/src/Guard/ControllerPermissionsGuard.php +++ /dev/null @@ -1,141 +0,0 @@ - - * @author JM Leroux - * @license MIT - */ -class ControllerPermissionsGuard extends AbstractGuard -{ - use ProtectionPolicyTrait; - - /** - * Event priority - */ - const EVENT_PRIORITY = -15; - - /** - * @var AuthorizationServiceInterface - */ - protected $authorizationService; - - /** - * Controller guard rules - * - * @var array - */ - protected $rules = []; - - /** - * Constructor - * - * @param AuthorizationServiceInterface $authorizationService - * @param array $rules - */ - public function __construct(AuthorizationServiceInterface $authorizationService, array $rules = []) - { - $this->authorizationService = $authorizationService; - $this->setRules($rules); - } - - /** - * Set the rules - * - * A controller rule is made the following way: - * - * [ - * 'controller' => 'ControllerName', - * 'actions' => []/string - * 'roles' => []/string - * ] - * - * @param array $rules - * @return void - */ - public function setRules(array $rules) - { - $this->rules = []; - - foreach ($rules as $rule) { - $controller = strtolower($rule['controller']); - $actions = isset($rule['actions']) ? (array)$rule['actions'] : []; - $permissions = (array)$rule['permissions']; - - if (empty($actions)) { - $this->rules[$controller][0] = $permissions; - continue; - } - - foreach ($actions as $action) { - $this->rules[$controller][strtolower($action)] = $permissions; - } - } - } - - /** - * {@inheritDoc} - */ - public function isGranted(MvcEvent $event) - { - $routeMatch = $event->getRouteMatch(); - $controller = strtolower($routeMatch->getParam('controller')); - $action = strtolower($routeMatch->getParam('action')); - - // If no rules apply, it is considered as granted or not based on the protection policy - if (!isset($this->rules[$controller])) { - return $this->protectionPolicy === self::POLICY_ALLOW; - } - - // Algorithm is as follow: we first check if there is an exact match (controller + action), if not - // we check if there are rules set globally for the whole controllers (see the index "0"), and finally - // if nothing is matched, we fallback to the protection policy logic - - if (isset($this->rules[$controller][$action])) { - $allowedPermissions = $this->rules[$controller][$action]; - } elseif (isset($this->rules[$controller][0])) { - $allowedPermissions = $this->rules[$controller][0]; - } else { - return $this->protectionPolicy === self::POLICY_ALLOW; - } - - // If no rules apply, it is considered as granted or not based on the protection policy - if (empty($allowedPermissions)) { - return $this->protectionPolicy === self::POLICY_ALLOW; - } - - if (in_array('*', $allowedPermissions)) { - return true; - } - - foreach ($allowedPermissions as $permission) { - if (!$this->authorizationService->isGranted($permission)) { - return false; - } - } - - return true; - } -} diff --git a/src/Guard/GuardInterface.php b/src/Guard/GuardInterface.php deleted file mode 100644 index 0aafa640..00000000 --- a/src/Guard/GuardInterface.php +++ /dev/null @@ -1,61 +0,0 @@ - - * @license MIT - */ -interface GuardInterface extends ListenerAggregateInterface -{ - /** - * Constant for guard that can be added to the MVC event result - */ - const GUARD_UNAUTHORIZED = 'guard-unauthorized'; - - /** - * Protection policy constants - */ - const POLICY_DENY = 'deny'; - const POLICY_ALLOW = 'allow'; - - /** - * Condition constants - */ - const CONDITION_OR = 'OR'; - const CONDITION_AND = 'AND'; - - /** - * @param MvcEvent $event - * @return bool - */ - public function isGranted(MvcEvent $event); -} diff --git a/src/Guard/GuardPluginManager.php b/src/Guard/GuardPluginManager.php deleted file mode 100644 index 08b86204..00000000 --- a/src/Guard/GuardPluginManager.php +++ /dev/null @@ -1,81 +0,0 @@ - - * @author JM Leroux - * @license MIT - */ -class GuardPluginManager extends AbstractPluginManager -{ - /** - * @var array - */ - protected $factories = [ - ControllerGuard::class => ControllerGuardFactory::class, - ControllerPermissionsGuard::class => ControllerPermissionsGuardFactory::class, - RouteGuard::class => RouteGuardFactory::class, - RoutePermissionsGuard::class => RoutePermissionsGuardFactory::class, - ]; - - /** - * {@inheritDoc} - */ - public function validate($plugin) - { - if ($plugin instanceof GuardInterface) { - return; // we're okay - } - - throw new Exception\RuntimeException(sprintf( - 'Guards must implement "LmcRbacMvc\Guard\GuardInterface", but "%s" was given', - is_object($plugin) ? get_class($plugin) : gettype($plugin) - )); - } - - /** - * {@inheritDoc} - * @throws ContainerException - */ - public function validatePlugin($plugin) - { - $this->validate($plugin); - } - - /** - * {@inheritDoc} - */ - protected function canonicalizeName($name) - { - return $name; - } -} diff --git a/src/Guard/ProtectionPolicyTrait.php b/src/Guard/ProtectionPolicyTrait.php deleted file mode 100644 index c100d661..00000000 --- a/src/Guard/ProtectionPolicyTrait.php +++ /dev/null @@ -1,54 +0,0 @@ - - * @license MIT - */ -trait ProtectionPolicyTrait -{ - /** - * @var string - */ - protected $protectionPolicy = GuardInterface::POLICY_DENY; - - /** - * Set the protection policy - * - * @param string $protectionPolicy - * @return void - */ - public function setProtectionPolicy($protectionPolicy) - { - $this->protectionPolicy = (string) $protectionPolicy; - } - - /** - * Get the protection policy - * - * @return string - */ - public function getProtectionPolicy() - { - return $this->protectionPolicy; - } -} diff --git a/src/Guard/RouteGuard.php b/src/Guard/RouteGuard.php deleted file mode 100644 index eadd2070..00000000 --- a/src/Guard/RouteGuard.php +++ /dev/null @@ -1,109 +0,0 @@ - - * @license MIT - */ -class RouteGuard extends AbstractGuard -{ - use ProtectionPolicyTrait; - - /** - * @var RoleService - */ - protected $roleService; - - /** - * Route guard rules - * - * Those rules are an associative array that map a rule with one or multiple roles - * - * @var array - */ - protected $rules = []; - - /** - * Constructor - * - * @param RoleService $roleService - * @param array $rules - */ - public function __construct(RoleService $roleService, array $rules = []) - { - $this->roleService = $roleService; - $this->setRules($rules); - } - - /** - * Set the rules (it overrides any existing rules) - * - * @param array $rules - * @return void - */ - public function setRules(array $rules) - { - $this->rules = []; - - foreach ($rules as $key => $value) { - if (is_int($key)) { - $routeRegex = $value; - $roles = []; - } else { - $routeRegex = $key; - $roles = (array) $value; - } - - $this->rules[$routeRegex] = $roles; - } - } - - /** - * {@inheritDoc} - */ - public function isGranted(MvcEvent $event) - { - $matchedRouteName = $event->getRouteMatch()->getMatchedRouteName(); - $allowedRoles = null; - - foreach (array_keys($this->rules) as $routeRule) { - if (fnmatch($routeRule, $matchedRouteName, FNM_CASEFOLD)) { - $allowedRoles = $this->rules[$routeRule]; - break; - } - } - - // If no rules apply, it is considered as granted or not based on the protection policy - if (null === $allowedRoles) { - return $this->protectionPolicy === self::POLICY_ALLOW; - } - - if (in_array('*', $allowedRoles)) { - return true; - } - - return $this->roleService->matchIdentityRoles($allowedRoles); - } -} diff --git a/src/Guard/RoutePermissionsGuard.php b/src/Guard/RoutePermissionsGuard.php deleted file mode 100644 index 3e44b9bc..00000000 --- a/src/Guard/RoutePermissionsGuard.php +++ /dev/null @@ -1,137 +0,0 @@ - - * @author JM Leroux - * @license MIT - */ -class RoutePermissionsGuard extends AbstractGuard -{ - use ProtectionPolicyTrait; - - const EVENT_PRIORITY = -8; - - /** - * @var AuthorizationServiceInterface - */ - protected $authorizationService; - - /** - * Route guard rules - * Those rules are an associative array that map a rule with one or multiple permissions - * @var array - */ - protected $rules = []; - - /** - * @param AuthorizationServiceInterface $authorizationService - * @param array $rules - */ - public function __construct(AuthorizationServiceInterface $authorizationService, array $rules = []) - { - $this->authorizationService = $authorizationService; - $this->setRules($rules); - } - - /** - * Set the rules (it overrides any existing rules) - * - * @param array $rules - * @return void - */ - public function setRules(array $rules) - { - $this->rules = []; - foreach ($rules as $key => $value) { - if (is_int($key)) { - $routeRegex = $value; - $permissions = []; - } else { - $routeRegex = $key; - $permissions = (array) $value; - } - $this->rules[$routeRegex] = $permissions; - } - } - - /** - * {@inheritDoc} - */ - public function isGranted(MvcEvent $event) - { - $matchedRouteName = $event->getRouteMatch()->getMatchedRouteName(); - $allowedPermissions = null; - - foreach (array_keys($this->rules) as $routeRule) { - if (fnmatch($routeRule, $matchedRouteName, FNM_CASEFOLD)) { - $allowedPermissions = $this->rules[$routeRule]; - break; - } - } - - // If no rules apply, it is considered as granted or not based on the protection policy - if (null === $allowedPermissions) { - return $this->protectionPolicy === self::POLICY_ALLOW; - } - - if (in_array('*', $allowedPermissions)) { - return true; - } - - $permissions = isset($allowedPermissions['permissions']) - ? $allowedPermissions['permissions'] - : $allowedPermissions; - - $condition = isset($allowedPermissions['condition']) - ? $allowedPermissions['condition'] - : GuardInterface::CONDITION_AND; - - if (GuardInterface::CONDITION_AND === $condition) { - foreach ($permissions as $permission) { - if (!$this->authorizationService->isGranted($permission)) { - return false; - } - } - - return true; - } - - if (GuardInterface::CONDITION_OR === $condition) { - foreach ($permissions as $permission) { - if ($this->authorizationService->isGranted($permission)) { - return true; - } - } - - return false; - } - - throw new InvalidArgumentException(sprintf( - 'Condition must be either "AND" or "OR", %s given', - is_object($condition) ? get_class($condition) : gettype($condition) - )); - } -} diff --git a/src/Identity/AuthenticationIdentityProvider.php b/src/Identity/AuthenticationIdentityProvider.php deleted file mode 100644 index b2fffbcb..00000000 --- a/src/Identity/AuthenticationIdentityProvider.php +++ /dev/null @@ -1,53 +0,0 @@ - - * @license MIT - */ -class AuthenticationIdentityProvider implements IdentityProviderInterface -{ - /** - * @var AuthenticationServiceInterface - */ - protected $authenticationService; - - /** - * Constructor - * - * @param AuthenticationServiceInterface $authenticationService - */ - public function __construct(AuthenticationServiceInterface $authenticationService) - { - $this->authenticationService = $authenticationService; - } - - /** - * {@inheritDoc} - */ - public function getIdentity() - { - return $this->authenticationService->getIdentity(); - } -} diff --git a/src/Identity/IdentityInterface.php b/src/Identity/IdentityInterface.php deleted file mode 100644 index c9c52b8a..00000000 --- a/src/Identity/IdentityInterface.php +++ /dev/null @@ -1,37 +0,0 @@ - - * @license MIT - */ -interface IdentityInterface -{ - /** - * Get the list of roles of this identity - * - * @return string[]|RoleInterface[] - */ - public function getRoles(); -} diff --git a/src/Identity/IdentityProviderInterface.php b/src/Identity/IdentityProviderInterface.php deleted file mode 100644 index d252ef37..00000000 --- a/src/Identity/IdentityProviderInterface.php +++ /dev/null @@ -1,35 +0,0 @@ - - * @license MIT - */ -interface IdentityProviderInterface -{ - /** - * Get the identity - * - * @return IdentityInterface|null - */ - public function getIdentity(); -} diff --git a/src/Initializer/AuthorizationServiceInitializer.php b/src/Initializer/AuthorizationServiceInitializer.php deleted file mode 100644 index b48c61a8..00000000 --- a/src/Initializer/AuthorizationServiceInitializer.php +++ /dev/null @@ -1,58 +0,0 @@ -get('LmcRbacMvc\Service\AuthorizationService'); - $instance->setAuthorizationService($authorizationService); - } - } - - /** - * @see \Laminas\ServiceManager\Initializer\InitializerInterface::initialize() - */ - public function initialize($instance, ServiceLocatorInterface $serviceLocator) - { - if ($serviceLocator instanceof AbstractPluginManager) { - $serviceLocator = $serviceLocator->getServiceLocator(); - } - - $this($serviceLocator, $instance); - } -} diff --git a/src/Module.php b/src/Module.php deleted file mode 100644 index b8fe223f..00000000 --- a/src/Module.php +++ /dev/null @@ -1,61 +0,0 @@ - - * @license MIT - */ -class Module implements BootstrapListenerInterface, ConfigProviderInterface -{ - /** - * {@inheritDoc} - */ - public function onBootstrap(EventInterface $event) - { - /* @var Application $application */ - $application = $event->getTarget(); - $serviceManager = $application->getServiceManager(); - $eventManager = $application->getEventManager(); - - /* @var GuardInterface[]|array $guards */ - $guards = $serviceManager->get('LmcRbacMvc\Guards'); - - // Register listeners, if any - foreach ($guards as $guard) { - $guard->attach($eventManager); - } - } - - /** - * {@inheritDoc} - */ - public function getConfig() - { - return include __DIR__ . '/../config/module.config.php'; - } -} diff --git a/src/Mvc/Controller/Plugin/IsGranted.php b/src/Mvc/Controller/Plugin/IsGranted.php deleted file mode 100644 index 1f55b67f..00000000 --- a/src/Mvc/Controller/Plugin/IsGranted.php +++ /dev/null @@ -1,58 +0,0 @@ - - * @license MIT - */ -class IsGranted extends AbstractPlugin -{ - /** - * @var AuthorizationServiceInterface - */ - private $authorizationService; - - /** - * Constructor - * - * @param AuthorizationServiceInterface $authorizationService - */ - public function __construct(AuthorizationServiceInterface $authorizationService) - { - $this->authorizationService = $authorizationService; - } - - /** - * Check against the given permission - * - * @param string $permission - * @param mixed $context - * @return bool - */ - public function __invoke($permission, $context = null) - { - return $this->authorizationService->isGranted($permission, $context); - } -} diff --git a/src/Options/ModuleOptions.php b/src/Options/ModuleOptions.php deleted file mode 100644 index ac4f87ac..00000000 --- a/src/Options/ModuleOptions.php +++ /dev/null @@ -1,287 +0,0 @@ - - * @license MIT - */ -class ModuleOptions extends AbstractOptions -{ - /** - * Key of the identity provider used to retrieve the identity - * - * @var string - */ - protected $identityProvider = 'LmcRbacMvc\Identity\AuthenticationIdentityProvider'; - - /** - * Guest role (used when no identity is found) - * - * @var string - */ - protected $guestRole = 'guest'; - - /** - * Guards - * - * @var array - */ - protected $guards = []; - - /** - * Assertion map - * - * @var array - */ - protected $assertionMap = []; - - /** - * Protection policy for guards (can be "deny" or "allow") - * - * @var string - */ - protected $protectionPolicy = GuardInterface::POLICY_ALLOW; - - /** - * A configuration for role provider - * - * @var array - */ - protected $roleProvider = []; - - /** - * Options for the unauthorized strategy - * - * @var UnauthorizedStrategyOptions|null - */ - protected $unauthorizedStrategy; - - /** - * Options for the redirect strategy - * - * @var RedirectStrategyOptions|null - */ - protected $redirectStrategy; - - /** - * Constructor - * - * {@inheritDoc} - */ - public function __construct($options = null) - { - $this->__strictMode__ = false; - parent::__construct($options); - } - - /** - * Set the key of the identity provider used to retrieve the identity - * - * @param string $identityProvider - * @return void - */ - public function setIdentityProvider($identityProvider) - { - $this->identityProvider = (string) $identityProvider; - } - - /** - * Get the key of the identity provider used to retrieve the identity - * - * @return string - */ - public function getIdentityProvider() - { - return $this->identityProvider; - } - - /** - * Set the assertions options - * - * @param array $assertionMap - * @return void - */ - public function setAssertionMap(array $assertionMap) - { - $this->assertionMap = $assertionMap; - } - - /** - * Get the assertions options - * - * @return array - */ - public function getAssertionMap() - { - return $this->assertionMap; - } - - /** - * Set the guest role (used when no identity is found) - * - * @param string $guestRole - * @return void - */ - public function setGuestRole($guestRole) - { - $this->guestRole = (string) $guestRole; - } - - /** - * Get the guest role (used when no identity is found) - * - * @return string - */ - public function getGuestRole() - { - return $this->guestRole; - } - - /** - * Set the guards options - * - * @param array $guards - * @return void - */ - public function setGuards(array $guards) - { - $this->guards = $guards; - } - - /** - * Get the guards options - * - * @return array - */ - public function getGuards() - { - return $this->guards; - } - - /** - * Set the protection policy for guards - * - * @param string $protectionPolicy - * @throws Exception\RuntimeException - * @return void - */ - public function setProtectionPolicy($protectionPolicy) - { - if ($protectionPolicy !== GuardInterface::POLICY_ALLOW && $protectionPolicy !== GuardInterface::POLICY_DENY) { - throw new Exception\RuntimeException(sprintf( - 'An invalid protection policy was set. Can only be "deny" or "allow", "%s" given', - $protectionPolicy - )); - } - - $this->protectionPolicy = (string) $protectionPolicy; - } - - /** - * Get the protection policy for guards - * - * @return string - */ - public function getProtectionPolicy() - { - return $this->protectionPolicy; - } - - /** - * Set the configuration for the role provider - * - * @param array $roleProvider - * @throws Exception\RuntimeException - */ - public function setRoleProvider(array $roleProvider) - { - if (count($roleProvider) > 1) { - throw new Exception\RuntimeException( - 'You can only have one role provider' - ); - } - - $this->roleProvider = $roleProvider; - } - - /** - * Get the configuration for the role provider - * - * @return array - */ - public function getRoleProvider() - { - return $this->roleProvider; - } - - /** - * Set the unauthorized strategy options - * - * @param array $unauthorizedStrategy - */ - public function setUnauthorizedStrategy(array $unauthorizedStrategy) - { - $this->unauthorizedStrategy = new UnauthorizedStrategyOptions($unauthorizedStrategy); - } - - /** - * Get the unauthorized strategy options - * - * @return UnauthorizedStrategyOptions - */ - public function getUnauthorizedStrategy() - { - if (null === $this->unauthorizedStrategy) { - $this->unauthorizedStrategy = new UnauthorizedStrategyOptions(); - } - - return $this->unauthorizedStrategy; - } - - /** - * Set the redirect strategy options - * - * @param array $redirectStrategy - */ - public function setRedirectStrategy(array $redirectStrategy) - { - $this->redirectStrategy = new RedirectStrategyOptions($redirectStrategy); - } - - /** - * Get the redirect strategy options - * - * @return RedirectStrategyOptions - */ - public function getRedirectStrategy() - { - if (null === $this->redirectStrategy) { - $this->redirectStrategy = new RedirectStrategyOptions(); - } - - return $this->redirectStrategy; - } -} diff --git a/src/Options/RedirectStrategyOptions.php b/src/Options/RedirectStrategyOptions.php deleted file mode 100644 index ddb2baf7..00000000 --- a/src/Options/RedirectStrategyOptions.php +++ /dev/null @@ -1,148 +0,0 @@ - - * @license MIT - */ -class RedirectStrategyOptions extends AbstractOptions -{ - /** - * Should the user be redirected when connected and not authorized - * - * @var bool - */ - protected $redirectWhenConnected = true; - - /** - * The name of the route to redirect when a user is connected and not authorized - * - * @var string - */ - protected $redirectToRouteConnected = 'home'; - - /** - * The name of the route to redirect when a user is disconnected and not authorized - * - * @var string - */ - protected $redirectToRouteDisconnected = 'login'; - - /** - * Should the previous URI should be appended as a query param? - * - * @var bool - */ - protected $appendPreviousUri = true; - - /** - * If appendPreviousUri is enabled, key to use in query params that hold the previous URI - * - * @var string - */ - protected $previousUriQueryKey = 'redirectTo'; - - /** - * @param bool $redirectWhenConnected - * @return void - */ - public function setRedirectWhenConnected($redirectWhenConnected) - { - $this->redirectWhenConnected = (bool) $redirectWhenConnected; - } - - /** - * @return bool - */ - public function getRedirectWhenConnected() - { - return $this->redirectWhenConnected; - } - - /** - * @param string $redirectToRouteConnected - * @return void - */ - public function setRedirectToRouteConnected($redirectToRouteConnected) - { - $this->redirectToRouteConnected = (string) $redirectToRouteConnected; - } - - /** - * @return string - */ - public function getRedirectToRouteConnected() - { - return $this->redirectToRouteConnected; - } - - /** - * @param string $redirectToRouteDisconnected - * @return void - */ - public function setRedirectToRouteDisconnected($redirectToRouteDisconnected) - { - $this->redirectToRouteDisconnected = (string) $redirectToRouteDisconnected; - } - - /** - * @return string - */ - public function getRedirectToRouteDisconnected() - { - return $this->redirectToRouteDisconnected; - } - - /** - * @param boolean $appendPreviousUri - */ - public function setAppendPreviousUri($appendPreviousUri) - { - $this->appendPreviousUri = (bool) $appendPreviousUri; - } - - /** - * @return boolean - */ - public function getAppendPreviousUri() - { - return $this->appendPreviousUri; - } - - /** - * @param string $previousUriQueryKey - */ - public function setPreviousUriQueryKey($previousUriQueryKey) - { - $this->previousUriQueryKey = (string) $previousUriQueryKey; - } - - /** - * @return string - */ - public function getPreviousUriQueryKey() - { - return $this->previousUriQueryKey; - } -} diff --git a/src/Options/UnauthorizedStrategyOptions.php b/src/Options/UnauthorizedStrategyOptions.php deleted file mode 100644 index 6f3ef396..00000000 --- a/src/Options/UnauthorizedStrategyOptions.php +++ /dev/null @@ -1,53 +0,0 @@ - - * @license MIT - */ -class UnauthorizedStrategyOptions extends AbstractOptions -{ - /** - * Template to use - * - * @var string - */ - protected $template = 'error/403'; - - /** - * @param string $template - */ - public function setTemplate($template) - { - $this->template = (string) $template; - } - - /** - * @return string - */ - public function getTemplate() - { - return $this->template; - } -} diff --git a/src/Permission/PermissionInterface.php b/src/Permission/PermissionInterface.php deleted file mode 100644 index b141c408..00000000 --- a/src/Permission/PermissionInterface.php +++ /dev/null @@ -1,35 +0,0 @@ - - * @license MIT - * TODO Remove deprecated interface - */ -interface PermissionInterface extends BasePermissionInterface -{ -} diff --git a/src/Role/InMemoryRoleProvider.php b/src/Role/InMemoryRoleProvider.php deleted file mode 100644 index 6ccbd7af..00000000 --- a/src/Role/InMemoryRoleProvider.php +++ /dev/null @@ -1,121 +0,0 @@ - [ - * 'children' => ['subRole1', 'subRole2'], // OPTIONAL - * 'permissions' => ['permission1'] // OPTIONAL - * ] - * ] - * - * For maximum performance, this provider DOES NOT do a lot of type check, so you must closely - * follow the format :) - * - * @author Michaël Gallego - * @license MIT - */ -class InMemoryRoleProvider implements RoleProviderInterface -{ - /** - * Role storage - * - * @var array - */ - private $roles = []; - - /** - * Roles config - * - * @var array - */ - private $rolesConfig = []; - - /** - * @param array - */ - public function __construct(array $rolesConfig) - { - $this->rolesConfig = $rolesConfig; - } - - /** - * {@inheritDoc} - */ - public function getRoles(array $roleNames) - { - $roles = []; - - foreach ($roleNames as $roleName) { - $roles[] = $this->getRole($roleName); - } - return $roles; - } - - /** - * Get role by role name - * - * @param $roleName - * @return RoleInterface - */ - protected function getRole($roleName) - { - if (isset($this->roles[$roleName])) { - return $this->roles[$roleName]; - } - - // If no config, we create a simple role with no permission - if (!isset($this->rolesConfig[$roleName])) { - $role = new Role($roleName); - $this->roles[$roleName] = $role; - return $role; - } - - $roleConfig = $this->rolesConfig[$roleName]; - - if (isset($roleConfig['children'])) { - $role = new HierarchicalRole($roleName); - $childRoles = (array)$roleConfig['children']; - foreach ($childRoles as $childRole) { - $childRole = $this->getRole($childRole); - $role->addChild($childRole); - } - } else { - $role = new Role($roleName); - } - - $permissions = isset($roleConfig['permissions']) ? $roleConfig['permissions'] : []; - foreach ($permissions as $permission) { - $role->addPermission($permission); - } - - $this->roles[$roleName] = $role; - - return $role; - } -} diff --git a/src/Role/ObjectRepositoryRoleProvider.php b/src/Role/ObjectRepositoryRoleProvider.php deleted file mode 100644 index 7758fb79..00000000 --- a/src/Role/ObjectRepositoryRoleProvider.php +++ /dev/null @@ -1,100 +0,0 @@ - - * @license MIT - */ -class ObjectRepositoryRoleProvider implements RoleProviderInterface -{ - /** - * @var ObjectRepository - */ - private $objectRepository; - - /** - * @var string - */ - private $roleNameProperty; - - /** - * @var array - */ - private $roleCache = []; - - /** - * Constructor - * - * @param ObjectRepository $objectRepository - * @param string $roleNameProperty - */ - public function __construct(ObjectRepository $objectRepository, $roleNameProperty) - { - $this->objectRepository = $objectRepository; - $this->roleNameProperty = $roleNameProperty; - } - - /** - * Clears the role cache - * - * @return void - */ - public function clearRoleCache() - { - $this->roleCache = []; - } - - /** - * {@inheritDoc} - */ - public function getRoles(array $roleNames) - { - $key = implode($roleNames); - - if (isset($this->roleCache[$key])) { - return $this->roleCache[$key]; - } - - $roles = $this->objectRepository->findBy([$this->roleNameProperty => $roleNames]); - - // We allow more roles to be loaded than asked (although this should not happen because - // role name should have a UNIQUE constraint in database... but just in case ;)) - if (count($roles) >= count($roleNames)) { - $this->roleCache[$key] = $roles; - - return $roles; - } - - // We have roles that were asked but couldn't be found in database... problem! - foreach ($roles as &$role) { - $role = $role->getName(); - } - - throw new RoleNotFoundException(sprintf( - 'Some roles were asked but could not be loaded from database: %s', - implode(', ', array_diff($roleNames, $roles)) - )); - } -} diff --git a/src/Role/RoleProviderInterface.php b/src/Role/RoleProviderInterface.php deleted file mode 100644 index 57e74437..00000000 --- a/src/Role/RoleProviderInterface.php +++ /dev/null @@ -1,41 +0,0 @@ - - * @license MIT - */ -interface RoleProviderInterface -{ - /** - * Get the roles from the provider - * - * @param string[] $roleNames - * @return RoleInterface[] - */ - public function getRoles(array $roleNames); -} diff --git a/src/Role/RoleProviderPluginManager.php b/src/Role/RoleProviderPluginManager.php deleted file mode 100644 index cdd83419..00000000 --- a/src/Role/RoleProviderPluginManager.php +++ /dev/null @@ -1,80 +0,0 @@ - - * @license MIT - */ -class RoleProviderPluginManager extends AbstractPluginManager -{ - /** - * @var array - */ - protected $invokableClasses = [ - 'LmcRbacMvc\Role\InMemoryRoleProvider' => InMemoryRoleProvider::class, - ]; - - /** - * @var array - */ - protected $factories = [ - ObjectRepositoryRoleProvider::class => ObjectRepositoryRoleProviderFactory::class, - ]; - - /** - * {@inheritDoc} - */ - public function validate($plugin) - { - if ($plugin instanceof RoleProviderInterface) { - return; // we're okay - } - - throw new Exception\RuntimeException(sprintf( - 'Role provider must implement "LmcRbacMvc\Role\RoleProviderInterface", but "%s" was given', - is_object($plugin) ? get_class($plugin) : gettype($plugin) - )); - } - - /** - * {@inheritDoc} - * @throws \Interop\Container\Exception\ContainerException - */ - public function validatePlugin($plugin) - { - $this->validate($plugin); - } - - /** - * {@inheritDoc} - */ - protected function canonicalizeName($name) - { - return $name; - } -} diff --git a/src/Service/AuthorizationService.php b/src/Service/AuthorizationService.php deleted file mode 100644 index fabafd61..00000000 --- a/src/Service/AuthorizationService.php +++ /dev/null @@ -1,164 +0,0 @@ - - * @license MIT - */ -class AuthorizationService implements AuthorizationServiceInterface -{ - /** - * @var Rbac - */ - protected $rbac; - - /** - * @var RoleService - */ - protected $roleService; - - /** - * @var AssertionPluginManager - */ - protected $assertionPluginManager; - - /** - * @var array - */ - protected $assertions = []; - - /** - * Constructor - * - * @param Rbac $rbac - * @param RoleService $roleService - * @param AssertionPluginManager $assertionPluginManager - */ - public function __construct(Rbac $rbac, RoleService $roleService, AssertionPluginManager $assertionPluginManager) - { - $this->rbac = $rbac; - $this->roleService = $roleService; - $this->assertionPluginManager = $assertionPluginManager; - } - - /** - * Set an assertion - * - * @param string|PermissionInterface $permission - * @param string|callable|AssertionInterface $assertion - * @return void - */ - public function setAssertion($permission, $assertion) - { - $this->assertions[(string) $permission] = $assertion; - } - - /** - * Set assertions - * - * @param array $assertions - * @return void - */ - public function setAssertions(array $assertions) - { - $this->assertions = $assertions; - } - - /** - * Checks if a assertion exists - * - * @param string|PermissionInterface $permission - * @return bool - */ - public function hasAssertion($permission) - { - return isset($this->assertions[(string) $permission]); - } - - /** - * Get the current identity from the role service - * - * @return IdentityInterface|null - */ - public function getIdentity() - { - return $this->roleService->getIdentity(); - } - - /** - * Check if the permission is granted to the current identity - * - * @param string|PermissionInterface $permission - * @param mixed $context - * @return bool - */ - public function isGranted($permission, $context = null) - { - $roles = $this->roleService->getIdentityRoles(); - - if (empty($roles)) { - return false; - } - - if (!$this->rbac->isGranted($roles, $permission)) { - return false; - } - - if ($this->hasAssertion($permission)) { - return $this->assert($this->assertions[(string) $permission], $context); - } - - return true; - } - - /** - * @param string|callable|AssertionInterface $assertion - * @param mixed $context - * @return bool - * @throws Exception\InvalidArgumentException If an invalid assertion is passed - */ - protected function assert($assertion, $context = null) - { - if (is_callable($assertion)) { - return $assertion($this, $context); - } elseif ($assertion instanceof AssertionInterface) { - return $assertion->assert($this, $context); - } elseif (is_string($assertion)) { - $assertion = $this->assertionPluginManager->get($assertion); - - return $assertion->assert($this, $context); - } - - throw new Exception\InvalidArgumentException(sprintf( - 'Assertion must be callable, string or implement ZfcRbac\Assertion\AssertionInterface, "%s" given', - is_object($assertion) ? get_class($assertion) : gettype($assertion) - )); - } -} diff --git a/src/Service/AuthorizationServiceAwareInterface.php b/src/Service/AuthorizationServiceAwareInterface.php deleted file mode 100644 index b5d05178..00000000 --- a/src/Service/AuthorizationServiceAwareInterface.php +++ /dev/null @@ -1,35 +0,0 @@ -authorizationService = $authorizationService; - } - - /** - * Return the AuthorizationService - * - * @return AuthorizationService - */ - public function getAuthorizationService() - { - return $this->authorizationService; - } -} diff --git a/src/Service/AuthorizationServiceInterface.php b/src/Service/AuthorizationServiceInterface.php deleted file mode 100644 index 2e395da1..00000000 --- a/src/Service/AuthorizationServiceInterface.php +++ /dev/null @@ -1,39 +0,0 @@ - - * @license MIT - */ -interface AuthorizationServiceInterface -{ - /** - * Check if the permission is granted to the current identity - * - * @param string|PermissionInterface $permission - * @param mixed $context - * @return bool - */ - public function isGranted($permission, $context = null); -} diff --git a/src/Service/RoleService.php b/src/Service/RoleService.php deleted file mode 100644 index 5e7e2396..00000000 --- a/src/Service/RoleService.php +++ /dev/null @@ -1,229 +0,0 @@ - - * @license MIT - */ -class RoleService -{ - /** - * @var IdentityProviderInterface - */ - protected $identityProvider; - - /** - * @var RoleProviderInterface - */ - protected $roleProvider; - - /** - * @var TraversalStrategyInterface - */ - protected $traversalStrategy; - - /** - * @var string - */ - protected $guestRole = ''; - - /** - * Constructor - * - * @param IdentityProviderInterface $identityProvider - * @param RoleProviderInterface $roleProvider - * @param TraversalStrategyInterface $traversalStrategy - */ - public function __construct( - IdentityProviderInterface $identityProvider, - RoleProviderInterface $roleProvider, - TraversalStrategyInterface $traversalStrategy - ) { - $this->identityProvider = $identityProvider; - $this->roleProvider = $roleProvider; - $this->traversalStrategy = $traversalStrategy; - } - - /** - * Set the identity provider - * - * @param IdentityProviderInterface $identityProvider - */ - public function setIdentityProvider(IdentityProviderInterface $identityProvider) - { - $this->identityProvider = $identityProvider; - } - - /** - * Set the role provider - * - * @param RoleProviderInterface $roleProvider - */ - public function setRoleProvider(RoleProviderInterface $roleProvider) - { - $this->roleProvider = $roleProvider; - } - - /** - * Set the guest role - * - * @param string $guestRole - * @return void - */ - public function setGuestRole($guestRole) - { - $this->guestRole = (string) $guestRole; - } - - /** - * Get the guest role - * - * @return string - */ - public function getGuestRole() - { - return $this->guestRole; - } - - /** - * Get the current identity from the identity provider - * - * @return IdentityInterface|null - */ - public function getIdentity() - { - return $this->identityProvider->getIdentity(); - } - - /** - * Get the identity roles from the current identity, applying some more logic - * - * @return RoleInterface[] - * @throws Exception\RuntimeException - */ - public function getIdentityRoles() - { - if (!$identity = $this->getIdentity()) { - return $this->convertRoles([$this->guestRole]); - } - - if (!$identity instanceof IdentityInterface) { - throw new Exception\RuntimeException(sprintf( - 'LmcRbacMvc expects your identity to implement LmcRbacMvc\Identity\IdentityInterface, "%s" given', - is_object($identity) ? get_class($identity) : gettype($identity) - )); - } - - return $this->convertRoles($identity->getRoles()); - } - - /** - * Check if the given roles match one of the identity roles - * - * This method is smart enough to automatically recursively extracts roles for hierarchical roles - * - * @param string[]|RoleInterface[] $roles - * @return bool - */ - public function matchIdentityRoles(array $roles) - { - $identityRoles = $this->getIdentityRoles(); - - // Too easy... - if (empty($identityRoles)) { - return false; - } - - $roleNames = []; - - foreach ($roles as $role) { - $roleNames[] = $role instanceof RoleInterface ? $role->getName() : (string) $role; - } - - $identityRoles = $this->flattenRoles($identityRoles); - - return count(array_intersect($roleNames, $identityRoles)) > 0; - } - - /** - * Convert the roles (potentially strings) to concrete RoleInterface objects using role provider - * - * @param array|Traversable $roles - * @return RoleInterface[] - */ - protected function convertRoles($roles) - { - if ($roles instanceof Traversable) { - $roles = iterator_to_array($roles); - } - - $collectedRoles = []; - $toCollect = []; - - foreach ((array) $roles as $role) { - // If it's already a RoleInterface, nothing to do as a RoleInterface contains everything already - if ($role instanceof RoleInterface) { - $collectedRoles[] = $role; - continue; - } - - // Otherwise, it's a string and hence we need to collect it - $toCollect[] = (string) $role; - } - - // Nothing to collect, we don't even need to hit the (potentially) costly role provider - if (empty($toCollect)) { - return $collectedRoles; - } - - return array_merge($collectedRoles, $this->roleProvider->getRoles($toCollect)); - } - - /** - * Flatten an array of role with role names - * - * This method iterates through the list of roles, and convert any RoleInterface to a string. For any - * role, it also extracts all the children - * - * @param array|RoleInterface[] $roles - * @return string[] - */ - protected function flattenRoles(array $roles) - { - $roleNames = []; - $iterator = $this->traversalStrategy->getRolesIterator($roles); - - foreach ($iterator as $role) { - $roleNames[] = $role->getName(); - } - - return array_unique($roleNames); - } -} diff --git a/src/View/Helper/HasRole.php b/src/View/Helper/HasRole.php deleted file mode 100644 index 6d894914..00000000 --- a/src/View/Helper/HasRole.php +++ /dev/null @@ -1,55 +0,0 @@ - - * @license MIT - */ -class HasRole extends AbstractHelper -{ - /** - * @var RoleService - */ - private $roleService; - - /** - * Constructor - * - * @param RoleService $roleService - */ - public function __construct(RoleService $roleService) - { - $this->roleService = $roleService; - } - - /** - * @param string|string[] $roleOrRoles - * @return bool - */ - public function __invoke($roleOrRoles) - { - return $this->roleService->matchIdentityRoles((array)$roleOrRoles); - } -} diff --git a/src/View/Helper/IsGranted.php b/src/View/Helper/IsGranted.php deleted file mode 100644 index f0f9df97..00000000 --- a/src/View/Helper/IsGranted.php +++ /dev/null @@ -1,58 +0,0 @@ - - * @license MIT - */ -class IsGranted extends AbstractHelper -{ - /** - * @var AuthorizationServiceInterface - */ - private $authorizationService; - - /** - * Constructor - * - * @param AuthorizationServiceInterface $authorizationService - */ - public function __construct(AuthorizationServiceInterface $authorizationService) - { - $this->authorizationService = $authorizationService; - } - - /** - * Check against the given permission - * - * @param string $permission - * @param mixed $context - * @return bool - */ - public function __invoke($permission, $context = null) - { - return $this->authorizationService->isGranted($permission, $context); - } -} diff --git a/src/View/Strategy/AbstractStrategy.php b/src/View/Strategy/AbstractStrategy.php deleted file mode 100644 index e78ae601..00000000 --- a/src/View/Strategy/AbstractStrategy.php +++ /dev/null @@ -1,47 +0,0 @@ - - * @license MIT - */ -abstract class AbstractStrategy extends AbstractListenerAggregate -{ - /** - * {@inheritDoc} - */ - public function attach(EventManagerInterface $events, $priority = 1) - { - $this->listeners[] = $events->attach(MvcEvent::EVENT_DISPATCH_ERROR, [$this, 'onError'], $priority); - } - - /** - * @private - * @param MvcEvent $event - * @return void - */ - abstract public function onError(MvcEvent $event); -} diff --git a/src/View/Strategy/RedirectStrategy.php b/src/View/Strategy/RedirectStrategy.php deleted file mode 100644 index 49503ecb..00000000 --- a/src/View/Strategy/RedirectStrategy.php +++ /dev/null @@ -1,107 +0,0 @@ - - * @license MIT - */ -class RedirectStrategy extends AbstractStrategy -{ - /** - * @var RedirectStrategyOptions - */ - protected $options; - - /** - * @var AuthenticationServiceInterface - */ - protected $authenticationService; - - /** - * Constructor - * - * @param RedirectStrategyOptions $options - * @param AuthenticationServiceInterface $authenticationService - */ - public function __construct(RedirectStrategyOptions $options, AuthenticationServiceInterface $authenticationService) - { - $this->options = $options; - $this->authenticationService = $authenticationService; - } - - /** - * @private - * @param MvcEvent $event - * @return void - */ - public function onError(MvcEvent $event) - { - // Do nothing if no error or if response is not HTTP response - if (!($event->getParam('exception') instanceof UnauthorizedExceptionInterface) - || ($event->getResult() instanceof HttpResponse) - || !($event->getResponse() instanceof HttpResponse) - ) { - return; - } - - $router = $event->getRouter(); - - if ($this->authenticationService->hasIdentity()) { - if (!$this->options->getRedirectWhenConnected()) { - return; - } - - $redirectRoute = $this->options->getRedirectToRouteConnected(); - } else { - $redirectRoute = $this->options->getRedirectToRouteDisconnected(); - } - - $uri = $router->assemble([], ['name' => $redirectRoute]); - - if ($this->options->getAppendPreviousUri()) { - $redirectKey = $this->options->getPreviousUriQueryKey(); - $previousUri = $event->getRequest()->getUriString(); - - $uri = $router->assemble( - [], - [ - 'name' => $redirectRoute, - 'query' => [$redirectKey => $previousUri] - ] - ); - } - - $response = $event->getResponse() ?: new HttpResponse(); - - $response->getHeaders()->addHeaderLine('Location', $uri); - $response->setStatusCode(302); - - $event->setResponse($response); - $event->setResult($response); - } -} diff --git a/src/View/Strategy/UnauthorizedStrategy.php b/src/View/Strategy/UnauthorizedStrategy.php deleted file mode 100644 index 26b16b5d..00000000 --- a/src/View/Strategy/UnauthorizedStrategy.php +++ /dev/null @@ -1,84 +0,0 @@ - - * @license MIT - */ -class UnauthorizedStrategy extends AbstractStrategy -{ - /** - * @var UnauthorizedStrategyOptions - */ - protected $options; - - /** - * Constructor - * - * @param UnauthorizedStrategyOptions $options - */ - public function __construct(UnauthorizedStrategyOptions $options) - { - $this->options = $options; - } - - /** - * @private - * @param MvcEvent $event - * @return void - */ - public function onError(MvcEvent $event) - { - // Do nothing if no error or if response is not HTTP response - if (!($event->getParam('exception') instanceof UnauthorizedExceptionInterface) - || ($event->getResult() instanceof HttpResponse) - || !($event->getResponse() instanceof HttpResponse) - ) { - return; - } - - $model = new ViewModel(); - $model->setTemplate($this->options->getTemplate()); - - switch ($event->getError()) { - case GuardInterface::GUARD_UNAUTHORIZED: - $model->setVariable('error', GuardInterface::GUARD_UNAUTHORIZED); - break; - - default: - break; - } - - $response = $event->getResponse() ?: new HttpResponse(); - $response->setStatusCode(403); - - $event->setResponse($response); - $event->setResult($model); - } -} diff --git a/tests/Asset/DummyGuard.php b/tests/Asset/DummyGuard.php deleted file mode 100644 index 7e93f011..00000000 --- a/tests/Asset/DummyGuard.php +++ /dev/null @@ -1,35 +0,0 @@ -name = (string) $name; - $this->permissions = new ArrayCollection(); - } - - /** - * Get the role identifier - * - * @return int - */ - public function getId() - { - return $this->id; - } - - /** - * Add a permission - * - * @param PermissionInterface|string $permission - * @return void - */ - public function addPermission($permission) - { - if (is_string($permission)) { - $name = $permission; - $permission = new Permission($name); - } - - $this->permissions[$permission->getName()] = $permission; - } -} diff --git a/tests/Asset/HierarchicalRole.php b/tests/Asset/HierarchicalRole.php deleted file mode 100644 index 3cd9f471..00000000 --- a/tests/Asset/HierarchicalRole.php +++ /dev/null @@ -1,96 +0,0 @@ -name = (string) $name; - $this->permissions = new ArrayCollection(); - } - - /** - * Get the role identifier - * - * @return int - */ - public function getId() - { - return $this->id; - } - - /** - * Add a permission - * - * @param PermissionInterface|string $permission - * @return void - */ - public function addPermission($permission) - { - if (is_string($permission)) { - $name = $permission; - $permission = new Permission($name); - } - - $this->permissions[$permission->getName()] = $permission; - } -} diff --git a/tests/Asset/MockRoleWithPermissionMethod.php b/tests/Asset/MockRoleWithPermissionMethod.php deleted file mode 100644 index 8f4013c0..00000000 --- a/tests/Asset/MockRoleWithPermissionMethod.php +++ /dev/null @@ -1,22 +0,0 @@ -name = (string) $name; - $this->roles = new ArrayCollection(); - } - - /** - * Get the permission identifier - * - * @return int - */ - public function getId() - { - return $this->id; - } - - /** - * Get the permission name - * - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * {@inheritDoc} - */ - public function __toString() - { - return $this->getName(); - } -} diff --git a/tests/Asset/SimpleAssertion.php b/tests/Asset/SimpleAssertion.php deleted file mode 100644 index 2d78adbf..00000000 --- a/tests/Asset/SimpleAssertion.php +++ /dev/null @@ -1,48 +0,0 @@ -called = true; - - return $context; - } - - /** - * @return bool - */ - public function getCalled() - { - return $this->called; - } -} diff --git a/tests/Bootstrap.php b/tests/Bootstrap.php deleted file mode 100644 index b796e09a..00000000 --- a/tests/Bootstrap.php +++ /dev/null @@ -1,45 +0,0 @@ -add('LmcRbacMvcTest\\', __DIR__); - -$config = require __DIR__ . '/TestConfiguration.php.dist'; -ServiceManagerFactory::setApplicationConfig($config); -unset($files, $file, $loader, $configFiles, $configFile, $config); diff --git a/tests/Collector/RbacCollectorTest.php b/tests/Collector/RbacCollectorTest.php deleted file mode 100644 index f50354f1..00000000 --- a/tests/Collector/RbacCollectorTest.php +++ /dev/null @@ -1,327 +0,0 @@ -assertSame(-100, $collector->getPriority()); - $this->assertSame('lmc_rbac', $collector->getName()); - } - - public function testSerialize() - { - $collector = new RbacCollector(); - $serialized = $collector->serialize(); - - $this->assertIsString($serialized); - - $unserialized = unserialize($serialized); - - $this->assertSame([], $unserialized['guards']); - $this->assertSame([], $unserialized['roles']); - $this->assertSame([], $unserialized['options']); - } - - public function testUnserialize() - { - $collector = new RbacCollector(); - $unserialized = [ - 'guards' => ['foo' => 'bar'], - 'roles' => ['foo' => 'bar'], - 'permissions' => ['foo' => 'bar'], - 'options' => ['foo' => 'bar'] - ]; - $serialized = serialize($unserialized); - - $collector->unserialize($serialized); - - $collection = $collector->getCollection(); - - $this->assertIsArray($collection); - $this->assertSame(['foo' => 'bar'], $collection['guards']); - $this->assertSame(['foo' => 'bar'], $collection['roles']); - $this->assertSame(['foo' => 'bar'], $collection['options']); - $this->assertSame(['foo' => 'bar'], $collection['permissions']); - } - - public function testUnserializeThrowsInvalidArgumentException() - { - $this->expectException('LmcRbacMvc\Exception\InvalidArgumentException'); - $collector = new RbacCollector(); - $unserialized = 'not_an_array'; - $serialized = serialize($unserialized); - - $collector->unserialize($serialized); - } - - - public function testCollectNothingIfNoApplicationIsSet() - { - $mvcEvent = new MvcEvent(); - $collector = new RbacCollector(); - - $this->assertNull($collector->collect($mvcEvent)); - } - - public function testCanCollect() - { - $dataToCollect = [ - 'module_options' => [ - 'guest_role' => 'guest', - 'protection_policy' => GuardInterface::POLICY_ALLOW, - 'guards' => [ - 'LmcRbacMvc\Guard\RouteGuard' => [ - 'admin*' => ['*'] - ], - 'LmcRbacMvc\Guard\ControllerGuard' => [ - [ - 'controller' => 'Foo', - 'roles' => ['*'] - ] - ] - ] - ], - 'role_config' => [ - 'member' => [ - 'children' => ['guest'], - 'permissions' => ['write', 'delete'] - ], - 'guest' => [ - 'permissions' => ['read'] - ] - ], - 'identity_role' => 'member' - ]; - - //$serviceManager = $this->getMockBuilder('Laminas\ServiceManager\ServiceLocatorInterface')->getMock(); - $serviceManager = new ServiceManager(); -// $serviceManager = $this->getMock('Laminas\ServiceManager\ServiceLocatorInterface'); - $application = $this->getMockBuilder('Laminas\Mvc\Application') - ->disableOriginalConstructor() - ->getMock(); - -// $application = $this->getMock('Laminas\Mvc\Application', [], [], '', false); - $application->expects($this->once())->method('getServiceManager')->will($this->returnValue($serviceManager)); - - $mvcEvent = new MvcEvent(); - $mvcEvent->setApplication($application); - - $identity = $this->createMock(IdentityInterface::class); -// $identity = $this->getMock('LmcRbacMvc\Identity\IdentityInterface'); - $identity->expects($this->once()) - ->method('getRoles') - ->will($this->returnValue($dataToCollect['identity_role'])); - -// $identityProvider = $this->getMock('LmcRbacMvc\Identity\IdentityProviderInterface'); - $identityProvider = $this->createMock(\LmcRbacMvc\Identity\IdentityProviderInterface::class); - $identityProvider->expects($this->once()) - ->method('getIdentity') - ->will($this->returnValue($identity)); - - $roleService = new RoleService($identityProvider, new InMemoryRoleProvider($dataToCollect['role_config']), new RecursiveRoleIteratorStrategy()); - - /* - $serviceManager->expects($this->at(0)) - ->method('get') - ->with('LmcRbacMvc\Service\RoleService') - ->will($this->returnValue($roleService)); - - $serviceManager->expects($this->at(1)) - ->method('get') - ->with('LmcRbacMvc\Options\ModuleOptions') - ->will($this->returnValue(new ModuleOptions($dataToCollect['module_options']))); -*/ - $serviceManager->setService('LmcRbacMvc\Service\RoleService', $roleService); - $serviceManager->setService('LmcRbacMvc\Options\ModuleOptions', new ModuleOptions($dataToCollect['module_options'])); - $collector = new RbacCollector(); - $collector->collect($mvcEvent); - - $collector->unserialize($collector->serialize()); - $collection = $collector->getCollection(); - - $expectedCollection = [ - 'options' => [ - 'guest_role' => 'guest', - 'protection_policy' => 'allow' - ], - 'guards' => [ - 'LmcRbacMvc\Guard\RouteGuard' => [ - 'admin*' => ['*'] - ], - 'LmcRbacMvc\Guard\ControllerGuard' => [ - [ - 'controller' => 'Foo', - 'roles' => ['*'] - ] - ] - ], - 'roles' => [ - 'member' => ['guest'] - ], - 'permissions' => [ - 'member' => ['write', 'delete'], - 'guest' => ['read'] - ] - ]; - - $this->assertEquals($expectedCollection, $collection); - } - - /** - * Tests the collectPermissions method when the role has a $permissions Property - */ - public function testCollectPermissionsProperty() - { - $expectedCollection = [ - 'guards' => [], - 'roles' => ['role-with-permission-property'], - 'permissions' => [ - 'role-with-permission-property' => ['permission-property-a', 'permission-property-b'], - ], - 'options' => [ - 'guest_role' => 'guest', - 'protection_policy' => GuardInterface::POLICY_ALLOW, - ], - ]; - - $collection = $this->collectPermissionsPropertyTestBase(new MockRoleWithPermissionProperty()); - $this->assertEquals($expectedCollection, $collection); - } - - /** - * Tests the collectPermissions method when the role has a getPermissions() method - */ - public function testCollectPermissionsMethod() - { - $expectedCollection = [ - 'guards' => [], - 'roles' => ['role-with-permission-method'], - 'permissions' => [ - 'role-with-permission-method' => ['permission-method-a', 'permission-method-b'], - ], - 'options' => [ - 'guest_role' => 'guest', - 'protection_policy' => GuardInterface::POLICY_ALLOW, - ], - ]; - - $collection = $this->collectPermissionsPropertyTestBase(new MockRoleWithPermissionMethod()); - $this->assertEquals($expectedCollection, $collection); - } - - /** - * Tests the collectPermissions method when the role implements Traversable - */ - public function testCollectPermissionsTraversable() - { - $expectedCollection = [ - 'guards' => [], - 'roles' => ['role-with-permission-traversable'], - 'permissions' => [ - 'role-with-permission-traversable' => ['permission-method-a', 'permission-method-b'], - ], - 'options' => [ - 'guest_role' => 'guest', - 'protection_policy' => GuardInterface::POLICY_ALLOW, - ], - ]; - - $collection = $this->collectPermissionsPropertyTestBase(new MockRoleWithPermissionTraversable()); - $this->assertEquals($expectedCollection, $collection); - } - - - /** - * Base method for the *collectPermissionProperty tests - * @param RoleInterface $role - * @return array|\string[] - */ - private function collectPermissionsPropertyTestBase(RoleInterface $role) - { -// $serviceManager = $this->createMock(\Laminas\ServiceManager\ServiceLocatorInterface::class); - $serviceManager = new ServiceManager(); - - $application = $this->getMockBuilder(\Laminas\Mvc\Application::class) - ->disableOriginalConstructor() - ->getMock(); - $application->expects($this->once())->method('getServiceManager')->will($this->returnValue($serviceManager)); - - $mvcEvent = new MvcEvent(); - $mvcEvent->setApplication($application); - - $identity = $this->createMock(\LmcRbacMvc\Identity\IdentityInterface::class); - $identity->expects($this->once()) - ->method('getRoles') - ->will($this->returnValue([$role])); - - $identityProvider = $this->createMock(\LmcRbacMvc\Identity\IdentityProviderInterface::class); - $identityProvider->expects($this->once()) - ->method('getIdentity') - ->will($this->returnValue($identity)); - - $roleProvider = $this->createMock(\LmcRbacMvc\Role\RoleProviderInterface::class); - - $roleService = new RoleService( - $identityProvider, - $roleProvider, - new RecursiveRoleIteratorStrategy() - ); - $serviceManager->setService('LmcRbacMvc\Service\RoleService', $roleService); - /* - $serviceManager->expects($this->at(0)) - ->method('get') - ->with('LmcRbacMvc\Service\RoleService') - ->will($this->returnValue($roleService)); - */ - $serviceManager->setService('LmcRbacMvc\Options\ModuleOptions', new ModuleOptions()); - /* - $serviceManager->expects($this->at(1)) - ->method('get') - ->with('LmcRbacMvc\Options\ModuleOptions') - ->will($this->returnValue(new ModuleOptions())); - */ - $collector = new RbacCollector(); - $collector->collect($mvcEvent); - - $collector->unserialize($collector->serialize()); - return $collector->getCollection(); - } -} diff --git a/tests/Factory/AssertionPluginManagerFactoryTest.php b/tests/Factory/AssertionPluginManagerFactoryTest.php deleted file mode 100644 index 5844ac47..00000000 --- a/tests/Factory/AssertionPluginManagerFactoryTest.php +++ /dev/null @@ -1,58 +0,0 @@ -setService('Config', [ - 'lmc_rbac' => [ - 'assertion_manager' => [] - ] - ]); - - $factory = new AssertionPluginManagerFactory(); - $pluginManager = $factory->createService($serviceManager); - - $this->assertInstanceOf('LmcRbacMvc\Assertion\AssertionPluginManager', $pluginManager); - } - - public function testFactoryInvokable() - { - $serviceManager = new ServiceManager(); - $serviceManager->setService('Config', [ - 'lmc_rbac' => [ - 'assertion_manager' => [] - ] - ]); - - $factory = new AssertionPluginManagerFactory(); - $pluginManager = $factory($serviceManager, 'notused'); - - $this->assertInstanceOf('LmcRbacMvc\Assertion\AssertionPluginManager', $pluginManager); - } -} diff --git a/tests/Factory/AuthenticationIdentityProviderFactoryTest.php b/tests/Factory/AuthenticationIdentityProviderFactoryTest.php deleted file mode 100644 index 13be4acf..00000000 --- a/tests/Factory/AuthenticationIdentityProviderFactoryTest.php +++ /dev/null @@ -1,42 +0,0 @@ -setService( - 'Laminas\Authentication\AuthenticationService', - $this->createMock('Laminas\Authentication\AuthenticationService') - ); - - $factory = new AuthenticationIdentityProviderFactory(); - $authenticationProvider = $factory->createService($serviceManager); - - $this->assertInstanceOf('LmcRbacMvc\Identity\AuthenticationIdentityProvider', $authenticationProvider); - } -} diff --git a/tests/Factory/AuthorizationServiceDelegatorTest.php b/tests/Factory/AuthorizationServiceDelegatorTest.php deleted file mode 100644 index 803cd969..00000000 --- a/tests/Factory/AuthorizationServiceDelegatorTest.php +++ /dev/null @@ -1,155 +0,0 @@ - - * @license MIT License - */ -class AuthorizationServiceDelegatorTest extends \PHPUnit\Framework\TestCase -{ - use ProphecyTrait; - - public function testDelegatorFactory() - { - $authServiceClassName = 'LmcRbacMvc\Service\AuthorizationService'; - $delegator = new AuthorizationServiceDelegatorFactory(); - $serviceLocator = $this->prophesize(ServiceLocatorInterface::class); - $serviceLocator->willImplement(ContainerInterface::class); - - - $authorizationService = $this->getMockBuilder('LmcRbacMvc\Service\AuthorizationService') - ->disableOriginalConstructor() - ->getMock(); - - $callback = function () { - return new AuthorizationAwareFake(); - }; - - $serviceLocator->get($authServiceClassName)->willReturn($authorizationService)->shouldBeCalled(); - - $decoratedInstance = $delegator->createDelegatorWithName($serviceLocator->reveal(), 'name', 'requestedName', $callback); - - $this->assertEquals($authorizationService, $decoratedInstance->getAuthorizationService()); - } - - public function testAuthorizationServiceIsNotInjectedWithoutDelegator() - { - $serviceManager = ServiceManagerFactory::getServiceManager(); - - $serviceManager->setAllowOverride(true); -// $authorizationService = $this->getMock('LmcRbacMvc\Service\AuthorizationService', [], [], '', false); - $authorizationService = $this->getMockBuilder('LmcRbacMvc\Service\AuthorizationService') - ->disableOriginalConstructor() - ->getMock(); - $serviceManager->setService( - 'LmcRbacMvc\Service\AuthorizationService', - $authorizationService - ); - $serviceManager->setAllowOverride(false); - - $serviceManager->setInvokableClass( - 'LmcRbacMvcTest\AuthorizationAware', - 'LmcRbacMvcTest\Initializer\AuthorizationAwareFake' - ); - $decoratedInstance = $serviceManager->get('LmcRbacMvcTest\AuthorizationAware'); - $this->assertNull($decoratedInstance->getAuthorizationService()); - } - - public function testAuthorizationServiceIsInjectedWithDelegatorV3() - { - $serviceManager = ServiceManagerFactory::getServiceManager(); - - if (! method_exists($serviceManager, 'build')) { - $this->markTestSkipped('this test is only for zend-servicemanager v3'); - } - - $serviceManager->setAllowOverride(true); -// $authorizationService = $this->getMock('LmcRbacMvc\Service\AuthorizationService', [], [], '', false); - $authorizationService = $this->getMockBuilder('LmcRbacMvc\Service\AuthorizationService') - ->disableOriginalConstructor() - ->getMock(); - $serviceManager->setService( - 'LmcRbacMvc\Service\AuthorizationService', - $authorizationService - ); - $serviceManager->setAllowOverride(false); - - $serviceManager->setInvokableClass( - 'LmcRbacMvcTest\AuthorizationAware', - 'LmcRbacMvcTest\Initializer\AuthorizationAwareFake' - ); - - $serviceManager->addDelegator( - 'LmcRbacMvcTest\Initializer\AuthorizationAwareFake', - 'LmcRbacMvc\Factory\AuthorizationServiceDelegatorFactory' - ); - - $decoratedInstance = $serviceManager->get('LmcRbacMvcTest\AuthorizationAware'); - $this->assertEquals($authorizationService, $decoratedInstance->getAuthorizationService()); - } - - public function testDelegatorThrowExceptionWhenBadInterface() - { - $serviceManager = ServiceManagerFactory::getServiceManager(); - - $serviceManager->setAllowOverride(true); -// $authorizationService = $this->getMock('LmcRbacMvc\Service\AuthorizationService', [], [], '', false); - $authorizationService = $this->getMockBuilder('LmcRbacMvc\Service\AuthorizationService') - ->disableOriginalConstructor() - ->getMock(); - $serviceManager->setService( - 'LmcRbacMvc\Service\AuthorizationService', - $authorizationService - ); - $serviceManager->setAllowOverride(false); - - $serviceManager->setFactory( - 'LmcRbacTest\AuthorizationAware', - function () { - return new \StdClass(); - } - ); - - $serviceManager->addDelegator( - 'LmcRbacTest\AuthorizationAware', - 'LmcRbacMvc\Factory\AuthorizationServiceDelegatorFactory' - ); - - $thrown = false; - try { - $serviceManager->get('LmcRbacTest\AuthorizationAware'); - } catch (\Exception $e) { - $thrown = true; - $this->assertStringEndsWith('The service LmcRbacTest\AuthorizationAware must implement AuthorizationServiceAwareInterface.', $e->getMessage()); - if ($e->getPrevious()) { - $this->assertInstanceOf('LmcRbacMvc\Exception\RuntimeException', $e->getPrevious()); - } - } - - $this->assertTrue($thrown); - } -} diff --git a/tests/Factory/AuthorizationServiceFactoryTest.php b/tests/Factory/AuthorizationServiceFactoryTest.php deleted file mode 100644 index a7427e96..00000000 --- a/tests/Factory/AuthorizationServiceFactoryTest.php +++ /dev/null @@ -1,55 +0,0 @@ -setService('Rbac\Rbac', $this->getMockBuilder('Rbac\Rbac')->disableOriginalConstructor()->getMock()); - - $serviceManager->setService( - 'LmcRbacMvc\Service\RoleService', - $this->getMockBuilder('LmcRbacMvc\Service\RoleService')->disableOriginalConstructor()->getMock() - ); - $serviceManager->setService( - 'LmcRbacMvc\Assertion\AssertionPluginManager', - $this->getMockBuilder('LmcRbacMvc\Assertion\AssertionPluginManager')->disableOriginalConstructor()->getMock() - - ); - $serviceManager->setService( - 'LmcRbacMvc\Options\ModuleOptions', - new ModuleOptions([]) - ); - - $factory = new AuthorizationServiceFactory(); - $authorizationService = $factory->createService($serviceManager); - - $this->assertInstanceOf('LmcRbacMvc\Service\AuthorizationService', $authorizationService); - } -} diff --git a/tests/Factory/ControllerGuardFactoryTest.php b/tests/Factory/ControllerGuardFactoryTest.php deleted file mode 100644 index 13d0b0ed..00000000 --- a/tests/Factory/ControllerGuardFactoryTest.php +++ /dev/null @@ -1,64 +0,0 @@ -markTestSkipped('this test is only vor zend-servicemanager v3'); - } - - $options = new ModuleOptions([ - 'identity_provider' => 'LmcRbacMvc\Identity\AuthenticationProvider', - 'guards' => [ - 'LmcRbacMvc\Guard\ControllerGuard' => [ - 'controller' => 'MyController', - 'actions' => 'edit', - 'roles' => 'member' - ] - ], - 'protection_policy' => GuardInterface::POLICY_ALLOW - ]); - - $serviceManager->setService('LmcRbacMvc\Options\ModuleOptions', $options); - $serviceManager->setService( - 'LmcRbacMvc\Service\RoleService', - $this->getMockBuilder('LmcRbacMvc\Service\RoleService')->disableOriginalConstructor()->getMock() - ); - - $factory = new ControllerGuardFactory(); - $controllerGuard = $factory($serviceManager, GuardPluginManager::class); - - $this->assertInstanceOf('LmcRbacMvc\Guard\ControllerGuard', $controllerGuard); - $this->assertEquals(GuardInterface::POLICY_ALLOW, $controllerGuard->getProtectionPolicy()); - } -} diff --git a/tests/Factory/ControllerPermissionsGuardFactoryTest.php b/tests/Factory/ControllerPermissionsGuardFactoryTest.php deleted file mode 100644 index 5a18be63..00000000 --- a/tests/Factory/ControllerPermissionsGuardFactoryTest.php +++ /dev/null @@ -1,65 +0,0 @@ -markTestSkipped('this test is only vor zend-servicemanager v3'); - } - - $creationOptions = [ - 'route' => 'permission' - ]; - - $options = new ModuleOptions([ - 'identity_provider' => 'LmcRbacMvc\Identity\AuthenticationProvider', - 'guards' => [ - 'LmcRbacMvc\Guard\ControllerPermissionsGuard' => $creationOptions - ], - 'protection_policy' => GuardInterface::POLICY_ALLOW, - ]); - - $serviceManager->setService('LmcRbacMvc\Options\ModuleOptions', $options); - $serviceManager->setService( - 'LmcRbacMvc\Service\AuthorizationService', - $this->getMockBuilder('LmcRbacMvc\Service\AuthorizationService')->disableOriginalConstructor()->getMock() - ); - - $factory = new ControllerPermissionsGuardFactory(); - $guard = $factory($serviceManager, GuardPluginManager::class); - - $this->assertInstanceOf('LmcRbacMvc\Guard\ControllerPermissionsGuard', $guard); - $this->assertEquals(GuardInterface::POLICY_ALLOW, $guard->getProtectionPolicy()); - } -} diff --git a/tests/Factory/GuardPluginManagerFactoryTest.php b/tests/Factory/GuardPluginManagerFactoryTest.php deleted file mode 100644 index 4bfd0342..00000000 --- a/tests/Factory/GuardPluginManagerFactoryTest.php +++ /dev/null @@ -1,44 +0,0 @@ -setService('Config', [ - 'lmc_rbac' => [ - 'guard_manager' => [] - ] - ]); - - $factory = new GuardPluginManagerFactory(); - $pluginManager = $factory->createService($serviceManager); - - $this->assertInstanceOf(GuardPluginManager::class, $pluginManager); - } -} diff --git a/tests/Factory/GuardsFactoryTest.php b/tests/Factory/GuardsFactoryTest.php deleted file mode 100644 index 3f9065f4..00000000 --- a/tests/Factory/GuardsFactoryTest.php +++ /dev/null @@ -1,98 +0,0 @@ - [ - 'LmcRbacMvc\Guard\RouteGuard' => [ - 'admin/*' => 'role1' - ], - 'LmcRbacMvc\Guard\RoutePermissionsGuard' => [ - 'admin/post' => 'post.manage' - ], - 'LmcRbacMvc\Guard\ControllerGuard' => [[ - 'controller' => 'MyController', - 'actions' => ['index', 'edit'], - 'roles' => ['role'] - ]], - 'LmcRbacMvc\Guard\ControllerPermissionsGuard' => [[ - 'controller' => 'PostController', - 'actions' => ['index', 'edit'], - 'permissions' => ['post.read'] - ]] - ] - ]); - - $serviceManager = new ServiceManager(); - $pluginManager = new GuardPluginManager($serviceManager); - - $serviceManager->setService('LmcRbacMvc\Options\ModuleOptions', $moduleOptions); - $serviceManager->setService('LmcRbacMvc\Guard\GuardPluginManager', $pluginManager); - $serviceManager->setService( - 'LmcRbacMvc\Service\RoleService', - $this->getMockBuilder('LmcRbacMvc\Service\RoleService')->disableOriginalConstructor()->getMock() - ); - $serviceManager->setService( - 'LmcRbacMvc\Service\AuthorizationService', - $this->getMockBuilder('LmcRbacMvc\Service\AuthorizationServiceInterface')->disableOriginalConstructor()->getMock() - ); - - $factory = new GuardsFactory(); - $guards = $factory->createService($serviceManager); - - $this->assertIsArray($guards); - - $this->assertCount(4, $guards); - $this->assertInstanceOf('LmcRbacMvc\Guard\RouteGuard', $guards[0]); - $this->assertInstanceOf('LmcRbacMvc\Guard\RoutePermissionsGuard', $guards[1]); - $this->assertInstanceOf('LmcRbacMvc\Guard\ControllerGuard', $guards[2]); - $this->assertInstanceOf('LmcRbacMvc\Guard\ControllerPermissionsGuard', $guards[3]); - } - - public function testReturnArrayIfNoConfig() - { - $moduleOptions = new ModuleOptions([ - 'guards' => [] - ]); - - $serviceManager = new ServiceManager(); - $pluginManager = new GuardPluginManager($serviceManager); - - $serviceManager->setService('LmcRbacMvc\Options\ModuleOptions', $moduleOptions); - - $factory = new GuardsFactory(); - $guards = $factory->createService($serviceManager); - - $this->assertIsArray($guards); - - $this->assertEmpty($guards); - } -} diff --git a/tests/Factory/HasRoleViewHelperFactoryTest.php b/tests/Factory/HasRoleViewHelperFactoryTest.php deleted file mode 100644 index 9b4ca3ff..00000000 --- a/tests/Factory/HasRoleViewHelperFactoryTest.php +++ /dev/null @@ -1,48 +0,0 @@ -markTestSkipped('this test is only vor zend-servicemanager v3'); - } - - $serviceManager->setService( - 'LmcRbacMvc\Service\RoleService', - $this->getMockBuilder('LmcRbacMvc\Service\RoleService')->disableOriginalConstructor()->getMock() - ); - - $factory = new HasRoleViewHelperFactory(); - $viewHelper = $factory($serviceManager, 'LmcRbacMvc\View\Helper\HasRole'); - - $this->assertInstanceOf('LmcRbacMvc\View\Helper\HasRole', $viewHelper); - } -} diff --git a/tests/Factory/IsGrantedPluginFactoryTest.php b/tests/Factory/IsGrantedPluginFactoryTest.php deleted file mode 100644 index 49f3445d..00000000 --- a/tests/Factory/IsGrantedPluginFactoryTest.php +++ /dev/null @@ -1,47 +0,0 @@ -markTestSkipped('this test is only vor zend-servicemanager v3'); - } - $serviceManager->setService( - 'LmcRbacMvc\Service\AuthorizationService', - $this->createMock('LmcRbacMvc\Service\AuthorizationServiceInterface') - ); - - $factory = new IsGrantedPluginFactory(); - $isGranted = $factory($serviceManager, 'LmcRbacMvc\Mvc\Controller\Plugin\IsGranted'); - - $this->assertInstanceOf('LmcRbacMvc\Mvc\Controller\Plugin\IsGranted', $isGranted); - } -} diff --git a/tests/Factory/IsGrantedViewHelperFactoryTest.php b/tests/Factory/IsGrantedViewHelperFactoryTest.php deleted file mode 100644 index d11f6da8..00000000 --- a/tests/Factory/IsGrantedViewHelperFactoryTest.php +++ /dev/null @@ -1,47 +0,0 @@ -markTestSkipped('this test is only vor zend-servicemanager v3'); - } - $serviceManager->setService( - 'LmcRbacMvc\Service\AuthorizationService', - $this->createMock('LmcRbacMvc\Service\AuthorizationServiceInterface') - ); - - $factory = new IsGrantedViewHelperFactory(); - $isGranted = $factory($serviceManager, 'LmcRbacMvc\View\Helper\IsGranted'); - - $this->assertInstanceOf('LmcRbacMvc\View\Helper\IsGranted', $isGranted); - } -} diff --git a/tests/Factory/ModuleOptionsFactoryTest.php b/tests/Factory/ModuleOptionsFactoryTest.php deleted file mode 100644 index c52663f7..00000000 --- a/tests/Factory/ModuleOptionsFactoryTest.php +++ /dev/null @@ -1,41 +0,0 @@ - []]; - - $serviceManager = new ServiceManager(); - $serviceManager->setService('Config', $config); - - $factory = new ModuleOptionsFactory(); - $options = $factory->createService($serviceManager); - - $this->assertInstanceOf('LmcRbacMvc\Options\ModuleOptions', $options); - } -} diff --git a/tests/Factory/ObjectRepositoryRoleProviderFactoryTest.php b/tests/Factory/ObjectRepositoryRoleProviderFactoryTest.php deleted file mode 100644 index 33be3412..00000000 --- a/tests/Factory/ObjectRepositoryRoleProviderFactoryTest.php +++ /dev/null @@ -1,121 +0,0 @@ - 'name', - 'object_repository' => 'RoleObjectRepository' - ]; - - $serviceManager->setService('RoleObjectRepository', $this->createMock('Doctrine\Persistence\ObjectRepository')); - - $roleProvider = $pluginManager->get('LmcRbacMvc\Role\ObjectRepositoryRoleProvider', $options); - $this->assertInstanceOf('LmcRbacMvc\Role\ObjectRepositoryRoleProvider', $roleProvider); - } - - public function testFactoryUsingObjectManager() - { - $serviceManager = new ServiceManager(); - $pluginManager = new RoleProviderPluginManager($serviceManager); - - $options = [ - 'role_name_property' => 'name', - 'object_manager' => 'ObjectManager', - 'class_name' => 'Role' - ]; - - $objectManager = $this->createMock('Doctrine\Persistence\ObjectManager'); - $objectManager->expects($this->once()) - ->method('getRepository') - ->with($options['class_name']) - ->will($this->returnValue($this->createMock('Doctrine\Persistence\ObjectRepository'))); - - $serviceManager->setService('ObjectManager', $objectManager); - - $roleProvider = $pluginManager->get('LmcRbacMvc\Role\ObjectRepositoryRoleProvider', $options); - $this->assertInstanceOf('LmcRbacMvc\Role\ObjectRepositoryRoleProvider', $roleProvider); - } - - /** - * This is required due to the fact that the ServiceManager catches ALL exceptions and throws it's own... - */ - public function testThrowExceptionIfNoRoleNamePropertyIsSet() - { - try { - $serviceManager = new ServiceManager(); - $pluginManager = new RoleProviderPluginManager($serviceManager); - - $pluginManager->get('LmcRbacMvc\Role\ObjectRepositoryRoleProvider', []); - } catch (ServiceNotCreatedException $smException) { - while ($e = $smException->getPrevious()) { - if ($e instanceof RuntimeException) { - $this->assertInstanceOf(RuntimeException::class, $e); - return true; - } - } - } - - $this->fail( - 'LmcRbacMvc\Factory\ObjectRepositoryRoleProviderFactory::createService() :: ' - .'LmcRbacMvc\Exception\RuntimeException was not found in the previous Exceptions' - ); - } - - /** - * This is required due to the fact that the ServiceManager catches ALL exceptions and throws it's own... - */ - public function testThrowExceptionIfNoObjectManagerNorObjectRepositoryIsSet() - { - try { - $serviceManager = new ServiceManager(); - $pluginManager = new RoleProviderPluginManager($serviceManager); - - $pluginManager->get('LmcRbacMvc\Role\ObjectRepositoryRoleProvider', [ - 'role_name_property' => 'name' - ]); - } catch (ServiceNotCreatedException $smException) { - while ($e = $smException->getPrevious()) { - if ($e instanceof RuntimeException) { - $this->assertInstanceOf(RuntimeException::class, $e); - return true; - } - } - } - - $this->fail( - 'LmcRbacMvc\Factory\ObjectRepositoryRoleProviderFactory::createService() :: ' - .'LmcRbacMvc\Exception\RuntimeException was not found in the previous Exceptions' - ); - } -} diff --git a/tests/Factory/RbacFactoryTest.php b/tests/Factory/RbacFactoryTest.php deleted file mode 100644 index 46843a12..00000000 --- a/tests/Factory/RbacFactoryTest.php +++ /dev/null @@ -1,39 +0,0 @@ -createService($serviceManager); - - $this->assertInstanceOf('Rbac\Rbac', $rbac); - $this->assertInstanceOf('Rbac\Traversal\Strategy\TraversalStrategyInterface', $rbac->getTraversalStrategy()); - } -} diff --git a/tests/Factory/RedirectStrategyFactoryTest.php b/tests/Factory/RedirectStrategyFactoryTest.php deleted file mode 100644 index f72f10f0..00000000 --- a/tests/Factory/RedirectStrategyFactoryTest.php +++ /dev/null @@ -1,58 +0,0 @@ -createMock('LmcRbacMvc\Options\RedirectStrategyOptions'); - - $moduleOptionsMock = $this->createMock('LmcRbacMvc\Options\ModuleOptions'); - $moduleOptionsMock->expects($this->once()) - ->method('getRedirectStrategy') - ->will($this->returnValue($redirectStrategyOptions)); - - $authenticationServiceMock = $this->createMock('Laminas\Authentication\AuthenticationService'); - - $serviceLocatorMock = $this->prophesize(ServiceLocatorInterface::class); - $serviceLocatorMock->willImplement(ContainerInterface::class); - $serviceLocatorMock->get('LmcRbacMvc\Options\ModuleOptions') - ->willReturn($moduleOptionsMock) - ->shouldBeCalled(); - $serviceLocatorMock->get('Laminas\Authentication\AuthenticationService') - ->willReturn($authenticationServiceMock) - ->shouldBeCalled(); - - $factory = new RedirectStrategyFactory(); - $redirectStrategy = $factory->createService($serviceLocatorMock->reveal()); - - $this->assertInstanceOf('LmcRbacMvc\View\Strategy\RedirectStrategy', $redirectStrategy); - } -} diff --git a/tests/Factory/RoleProviderPluginManagerFactoryTest.php b/tests/Factory/RoleProviderPluginManagerFactoryTest.php deleted file mode 100644 index cbc5bbb7..00000000 --- a/tests/Factory/RoleProviderPluginManagerFactoryTest.php +++ /dev/null @@ -1,44 +0,0 @@ -setService('Config', [ - 'lmc_rbac' => [ - 'role_provider_manager' => [] - ] - ]); - - $factory = new RoleProviderPluginManagerFactory(); - $pluginManager = $factory->createService($serviceManager); - - $this->assertInstanceOf(RoleProviderPluginManager::class, $pluginManager); - } -} diff --git a/tests/Factory/RoleServiceFactoryTest.php b/tests/Factory/RoleServiceFactoryTest.php deleted file mode 100644 index 808e2b5a..00000000 --- a/tests/Factory/RoleServiceFactoryTest.php +++ /dev/null @@ -1,141 +0,0 @@ - 'LmcRbacMvc\Identity\AuthenticationProvider', - 'guest_role' => 'guest', - 'role_provider' => [ - 'LmcRbacMvc\Role\InMemoryRoleProvider' => [ - 'foo' - ] - ] - ]); - - $traversalStrategy = $this->createMock('Rbac\Traversal\Strategy\TraversalStrategyInterface'); - $roleProvider = $this->createMock('\LmcRbacMvc\Role\RoleProviderInterface'); - - $rbac = $this->createMock('Rbac\Rbac'); - $rbac->expects($this->once()) - ->method('getTraversalStrategy') - ->will($this->returnValue( - $traversalStrategy - )); - - $pluginManager = $this->createMock('\LmcRbacMvc\Role\RoleProviderPluginManager'); - $pluginManager->expects($this->once()) - ->method('get') - ->with('LmcRbacMvc\Role\InMemoryRoleProvider', ['foo']) - ->will($this->returnValue( - $roleProvider - )); - - $serviceManager = new ServiceManager(); - $serviceManager->setService('LmcRbacMvc\Options\ModuleOptions', $options); - $serviceManager->setService('Rbac\Rbac', $rbac); - $serviceManager->setService('LmcRbacMvc\Role\RoleProviderPluginManager', $pluginManager); - $serviceManager->setService('LmcRbacMvc\Identity\AuthenticationProvider', $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface')); - - $factory = new RoleServiceFactory(); - $roleService = $factory->createService($serviceManager); - - $this->assertInstanceOf('LmcRbacMvc\Service\RoleService', $roleService); - $this->assertEquals('guest', $roleService->getGuestRole()); - //$this->assertAttributeSame($traversalStrategy, 'traversalStrategy', $roleService); - - } - - public function testIfRoleArrayPointerBeyondArrayEnd() - { - $options = new ModuleOptions([ - 'identity_provider' => 'LmcRbacMvc\Identity\AuthenticationProvider', - 'guest_role' => 'guest', - 'role_provider' => [ - 'LmcRbacMvc\Role\InMemoryRoleProvider' => [ - 'foo' - ] - ] - ]); - - // Simulate if array pointer beyond end of array. E.g after 'while(next($roleProvider)) { //do }' - $roleProvider = $options->getRoleProvider(); - next($roleProvider); - $options->setRoleProvider($roleProvider); - - $traversalStrategy = $this->createMock('Rbac\Traversal\Strategy\TraversalStrategyInterface'); - $roleProvider = $this->createMock('\LmcRbacMvc\Role\RoleProviderInterface'); - - $rbac = $this->createMock('Rbac\Rbac'); - $rbac->expects($this->once()) - ->method('getTraversalStrategy') - ->will($this->returnValue( - $traversalStrategy - )); - - $pluginManager = $this->createMock('\LmcRbacMvc\Role\RoleProviderPluginManager'); - $pluginManager->expects($this->once()) - ->method('get') - ->with('LmcRbacMvc\Role\InMemoryRoleProvider', ['foo']) - ->will($this->returnValue( - $roleProvider - )); - - $serviceManager = new ServiceManager(); - $serviceManager->setService('LmcRbacMvc\Options\ModuleOptions', $options); - $serviceManager->setService('Rbac\Rbac', $rbac); - $serviceManager->setService('LmcRbacMvc\Role\RoleProviderPluginManager', $pluginManager); - $serviceManager->setService('LmcRbacMvc\Identity\AuthenticationProvider', $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface')); - - $factory = new RoleServiceFactory(); - $factory->createService($serviceManager); - } - - public function testThrowExceptionIfNoRoleProvider() - { - $this->ExpectException(\LmcRbacMvc\Exception\RuntimeException::class); - - $options = new ModuleOptions([ - 'identity_provider' => 'LmcRbacMvc\Identity\AuthenticationProvider', - 'guest_role' => 'guest', - 'role_provider' => [] - ]); - - $serviceManager = new ServiceManager(); - $serviceManager->setService('LmcRbacMvc\Options\ModuleOptions', $options); - $serviceManager->setService( - 'LmcRbacMvc\Identity\AuthenticationProvider', - $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface') - ); - - $factory = new RoleServiceFactory(); - $factory->createService($serviceManager); - } -} diff --git a/tests/Factory/RouteGuardFactoryTest.php b/tests/Factory/RouteGuardFactoryTest.php deleted file mode 100644 index d0b598e7..00000000 --- a/tests/Factory/RouteGuardFactoryTest.php +++ /dev/null @@ -1,64 +0,0 @@ -markTestSkipped('this test is only vor zend-servicemanager v3'); - } - - $creationOptions = [ - 'route' => 'role' - ]; - - $options = new ModuleOptions([ - 'identity_provider' => 'LmcRbacMvc\Identity\AuthenticationProvider', - 'guards' => [ - 'LmcRbacMvc\Guard\RouteGuard' => $creationOptions - ], - 'protection_policy' => GuardInterface::POLICY_ALLOW, - ]); - - $serviceManager->setService('LmcRbacMvc\Options\ModuleOptions', $options); - $serviceManager->setService( - 'LmcRbacMvc\Service\RoleService', - $this->getMockBuilder('LmcRbacMvc\Service\RoleService')->disableOriginalConstructor()->getMock() - ); - - $factory = new RouteGuardFactory(); - $routeGuard = $factory($serviceManager, 'LmcRbacMvc\Guard\RouteGuard'); - - $this->assertInstanceOf('LmcRbacMvc\Guard\RouteGuard', $routeGuard); - $this->assertEquals(GuardInterface::POLICY_ALLOW, $routeGuard->getProtectionPolicy()); - } -} diff --git a/tests/Factory/RoutePermissionsGuardFactoryTest.php b/tests/Factory/RoutePermissionsGuardFactoryTest.php deleted file mode 100644 index 0d032321..00000000 --- a/tests/Factory/RoutePermissionsGuardFactoryTest.php +++ /dev/null @@ -1,64 +0,0 @@ -markTestSkipped('this test is only vor zend-servicemanager v3'); - } - - $creationOptions = [ - 'route' => 'role' - ]; - - $options = new ModuleOptions([ - 'identity_provider' => 'LmcRbacMvc\Identity\AuthenticationProvider', - 'guards' => [ - 'LmcRbacMvc\Guard\RoutePermissionsGuard' => $creationOptions - ], - 'protection_policy' => GuardInterface::POLICY_ALLOW, - ]); - - $serviceManager->setService('LmcRbacMvc\Options\ModuleOptions', $options); - $serviceManager->setService( - 'LmcRbacMvc\Service\AuthorizationService', - $this->getMockBuilder('LmcRbacMvc\Service\AuthorizationService')->disableOriginalConstructor()->getMock() - ); - - $factory = new RoutePermissionsGuardFactory(); - $routeGuard = $factory($serviceManager, 'LmcRbacMvc\Guard\RoutePermissionsGuard'); - - $this->assertInstanceOf('LmcRbacMvc\Guard\RoutePermissionsGuard', $routeGuard); - $this->assertEquals(GuardInterface::POLICY_ALLOW, $routeGuard->getProtectionPolicy()); - } -} diff --git a/tests/Factory/UnauthorizedStrategyFactoryTest.php b/tests/Factory/UnauthorizedStrategyFactoryTest.php deleted file mode 100644 index a2b5e286..00000000 --- a/tests/Factory/UnauthorizedStrategyFactoryTest.php +++ /dev/null @@ -1,50 +0,0 @@ -createMock('LmcRbacMvc\Options\UnauthorizedStrategyOptions'); - - $moduleOptionsMock = $this->createMock('LmcRbacMvc\Options\ModuleOptions'); - $moduleOptionsMock->expects($this->once()) - ->method('getUnauthorizedStrategy') - ->will($this->returnValue($unauthorizedStrategyOptions)); - - $serviceLocatorMock = $this->prophesize('Laminas\ServiceManager\ServiceLocatorInterface'); - $serviceLocatorMock->willImplement(ContainerInterface::class); - $serviceLocatorMock->get('LmcRbacMvc\Options\ModuleOptions')->willReturn($moduleOptionsMock)->shouldBeCalled(); - - $factory = new UnauthorizedStrategyFactory(); - $unauthorizedStrategy = $factory->createService($serviceLocatorMock->reveal()); - - $this->assertInstanceOf('LmcRbacMvc\View\Strategy\UnauthorizedStrategy', $unauthorizedStrategy); - } -} diff --git a/tests/Guard/AbstractGuardTest.php b/tests/Guard/AbstractGuardTest.php deleted file mode 100644 index a524284b..00000000 --- a/tests/Guard/AbstractGuardTest.php +++ /dev/null @@ -1,66 +0,0 @@ -prophesize(Application::class); - $application->getEventManager()->willReturn($eventManager); - - $event = new MvcEvent(); - $event->setApplication($application->reveal()); - - $guard = new DummyGuard(); - $guard->attach($eventManager); - - $eventManager->attach(MvcEvent::EVENT_DISPATCH_ERROR, function (MvcEvent $event) { - $event->setParam('first-listener', true); - }); - $eventManager->attach(MvcEvent::EVENT_DISPATCH_ERROR, function (MvcEvent $event) { - $event->setParam('second-listener', true); - }); - - // attach listener with lower priority than DummyGuard - $eventManager->attach(MvcEvent::EVENT_ROUTE, function (MvcEvent $event) { - $this->fail('should not be called, because guard should stop propagation'); - }, DummyGuard::EVENT_PRIORITY - 1); - - $event->setName(MvcEvent::EVENT_ROUTE); - $eventManager->triggerEvent($event); - - $this->assertTrue($event->getParam('first-listener')); - $this->assertTrue($event->getParam('second-listener')); - $this->assertTrue($event->propagationIsStopped()); - } -} diff --git a/tests/Guard/ControllerGuardTest.php b/tests/Guard/ControllerGuardTest.php deleted file mode 100644 index fd10ea4b..00000000 --- a/tests/Guard/ControllerGuardTest.php +++ /dev/null @@ -1,537 +0,0 @@ -getMockBuilder('LmcRbacMvc\Service\RoleService')->disableOriginalConstructor()->getMock()); - - $eventManager = $this->createMock('Laminas\EventManager\EventManagerInterface'); - $eventManager->expects($this->once()) - ->method('attach') - ->with(ControllerGuard::EVENT_NAME); - - $guard->attach($eventManager); - } - - public function rulesConversionProvider() - { - return [ - // Without actions - [ - 'rules' => [ - [ - 'controller' => 'MyController', - 'roles' => 'role1' - ], - [ - 'controller' => 'MyController2', - 'roles' => ['role2', 'role3'] - ], - new \ArrayIterator([ - 'controller' => 'MyController3', - 'roles' => new \ArrayIterator(['role4']) - ]) - ], - 'expected' => [ - 'mycontroller' => [0 => ['role1']], - 'mycontroller2' => [0 => ['role2', 'role3']], - 'mycontroller3' => [0 => ['role4']] - ] - ], - - // With one action - [ - 'rules' => [ - [ - 'controller' => 'MyController', - 'actions' => 'DELETE', - 'roles' => 'role1' - ], - [ - 'controller' => 'MyController2', - 'actions' => ['delete'], - 'roles' => 'role2' - ], - new \ArrayIterator([ - 'controller' => 'MyController3', - 'actions' => new \ArrayIterator(['DELETE']), - 'roles' => new \ArrayIterator(['role3']) - ]) - ], - 'expected' => [ - 'mycontroller' => [ - 'delete' => ['role1'] - ], - 'mycontroller2' => [ - 'delete' => ['role2'] - ], - 'mycontroller3' => [ - 'delete' => ['role3'] - ], - ] - ], - - // With multiple actions - [ - 'rules' => [ - [ - 'controller' => 'MyController', - 'actions' => ['EDIT', 'delete'], - 'roles' => 'role1' - ], - new \ArrayIterator([ - 'controller' => 'MyController2', - 'actions' => new \ArrayIterator(['edit', 'DELETE']), - 'roles' => new \ArrayIterator(['role2']) - ]) - ], - 'expected' => [ - 'mycontroller' => [ - 'edit' => ['role1'], - 'delete' => ['role1'] - ], - 'mycontroller2' => [ - 'edit' => ['role2'], - 'delete' => ['role2'] - ] - ] - ], - - // Test that that if a rule is set globally to the controller, it does not override any - // action specific rule that may have been specified before - [ - 'rules' => [ - [ - 'controller' => 'MyController', - 'actions' => ['edit'], - 'roles' => 'role1' - ], - [ - 'controller' => 'MyController', - 'roles' => 'role2' - ] - ], - 'expected' => [ - 'mycontroller' => [ - 'edit' => ['role1'], - 0 => ['role2'] - ] - ] - ] - ]; - } - - /** - * @dataProvider rulesConversionProvider - */ - public function testRulesConversions(array $rules, array $expected) - { - $roleService = $this->getMockBuilder('LmcRbacMvc\Service\RoleService')->disableOriginalConstructor()->getMock(); - $controllerGuard = new ControllerGuard($roleService, $rules); - - $reflProperty = new \ReflectionProperty($controllerGuard, 'rules'); - $reflProperty->setAccessible(true); - - $this->assertEquals($expected, $reflProperty->getValue($controllerGuard)); - } - - public function controllerDataProvider() - { - return [ - // Test simple guard with both policies - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'roles' => 'admin' - ] - ], - 'controller' => 'BlogController', - 'action' => 'edit', - 'rolesConfig' => [ - 'admin' - ], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'roles' => 'admin' - ] - ], - 'controller' => 'BlogController', - 'action' => 'edit', - 'rolesConfig' => [ - 'admin' - ], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Test with multiple rules - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'actions' => 'read', - 'roles' => 'admin' - ], - [ - 'controller' => 'BlogController', - 'actions' => 'edit', - 'roles' => 'admin' - ] - ], - 'controller' => 'BlogController', - 'action' => 'edit', - 'rolesConfig' => [ - 'admin' - ], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'actions' => 'read', - 'roles' => 'admin' - ], - [ - 'controller' => 'BlogController', - 'actions' => 'edit', - 'roles' => 'admin' - ] - ], - 'controller' => 'BlogController', - 'action' => 'edit', - 'rolesConfig' => [ - 'admin' - ], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Assert that policy can deny unspecified rules - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'roles' => 'member' - ], - ], - 'controller' => 'CommentController', - 'action' => 'edit', - 'rolesConfig' => [ - 'member' - ], - 'identityRole' => 'member', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'roles' => 'member' - ], - ], - 'controller' => 'CommentController', - 'action' => 'edit', - 'rolesConfig' => [ - 'member' - ], - 'identityRole' => 'member', - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Test assert policy can deny other actions from controller when only one is specified - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'actions' => 'edit', - 'roles' => 'member' - ], - ], - 'controller' => 'BlogController', - 'action' => 'read', - 'rolesConfig' => [ - 'member' - ], - 'identityRole' => 'member', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'actions' => 'edit', - 'roles' => 'member' - ], - ], - 'controller' => 'BlogController', - 'action' => 'read', - 'rolesConfig' => [ - 'member' - ], - 'identityRole' => 'member', - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Assert it can uses parent-children relationship - [ - 'rules' => [ - [ - 'controller' => 'IndexController', - 'actions' => 'index', - 'roles' => 'guest' - ] - ], - 'controller' => 'IndexController', - 'action' => 'index', - 'rolesConfig' => [ - 'admin' => [ - 'children' => ['guest'] - ], - 'guest' - ], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - [ - 'controller' => 'IndexController', - 'actions' => 'index', - 'roles' => 'guest' - ] - ], - 'controller' => 'IndexController', - 'action' => 'index', - 'rolesConfig' => [ - 'admin' => [ - 'children' => ['guest'] - ], - 'guest' - ], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Assert wildcard in roles - [ - 'rules' => [ - [ - 'controller' => 'IndexController', - 'roles' => '*' - ] - ], - 'controller' => 'IndexController', - 'action' => 'index', - 'rolesConfig' => [ - 'admin' - ], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - [ - 'controller' => 'IndexController', - 'roles' => '*' - ] - ], - 'controller' => 'IndexController', - 'action' => 'index', - 'rolesConfig' => [ - 'admin' - ], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - ]; - } - - /** - * @dataProvider controllerDataProvider - */ - public function testControllerGranted( - array $rules, - $controller, - $action, - array $rolesConfig, - $identityRole, - $isGranted, - $protectionPolicy - ) { - $event = new MvcEvent(); - $routeMatch = $this->createRouteMatch([ - 'controller' => $controller, - 'action' => $action, - ]); - - $event->setRouteMatch($routeMatch); - - $identity = $this->createMock('LmcRbacMvc\Identity\IdentityInterface'); - $identity->expects($this->any())->method('getRoles')->will($this->returnValue($identityRole)); - - $identityProvider = $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface'); - $identityProvider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue($identity)); - - $roleProvider = new InMemoryRoleProvider($rolesConfig); - $roleService = new RoleService($identityProvider, $roleProvider, new RecursiveRoleIteratorStrategy()); - - $controllerGuard = new ControllerGuard($roleService, $rules); - $controllerGuard->setProtectionPolicy($protectionPolicy); - - $this->assertEquals($isGranted, $controllerGuard->isGranted($event)); - } - - public function testProperlyFillEventOnAuthorization() - { - $event = new MvcEvent(); - $routeMatch = $this->createRouteMatch([ - 'controller' => 'MyController', - 'action' => 'edit', - ]); - - $application = $this->getMockBuilder('Laminas\Mvc\Application')->disableOriginalConstructor()->getMock(); - $eventManager = $this->createMock('Laminas\EventManager\EventManagerInterface'); - - $application->expects($this->never()) - ->method('getEventManager') - ->will($this->returnValue($eventManager)); - - $event->setRouteMatch($routeMatch); - $event->setApplication($application); - - $identity = $this->createMock('LmcRbacMvc\Identity\IdentityInterface'); - $identity->expects($this->any())->method('getRoles')->will($this->returnValue(['member'])); - - $identityProvider = $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface'); - $identityProvider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue($identity)); - - $roleProvider = new InMemoryRoleProvider([ - 'member' - ]); - - $roleService = new RoleService($identityProvider, $roleProvider, new RecursiveRoleIteratorStrategy()); - - $routeGuard = new ControllerGuard($roleService, [[ - 'controller' => 'MyController', - 'actions' => 'edit', - 'roles' => 'member' - ]]); - - $routeGuard->onResult($event); - - $this->assertEmpty($event->getError()); - $this->assertNull($event->getParam('exception')); - } - - public function testProperlySetUnauthorizedAndTriggerEventOnUnauthorization() - { - $event = new MvcEvent(); - $routeMatch = $this->createRouteMatch([ - 'controller' => 'MyController', - 'action' => 'delete', - ]); - - $application = $this->getMockBuilder('Laminas\Mvc\Application')->disableOriginalConstructor()->getMock(); - $eventManager = $this->createMock('Laminas\EventManager\EventManager'); - - $application->expects($this->once()) - ->method('getEventManager') - ->will($this->returnValue($eventManager)); - - $eventManager->expects($this->once()) - ->method('triggerEvent') - ->with($event); - - $routeMatch->setParam('controller', 'MyController'); - $routeMatch->setParam('action', 'delete'); - - $event->setRouteMatch($routeMatch); - $event->setApplication($application); - - $identityProvider = $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface'); - $identityProvider->expects($this->any()) -// ->method('getIdentityRoles') - ->method('getIdentity') - ->will($this->returnValue('member')); - - $roleProvider = new InMemoryRoleProvider([ - 'member' - ]); - - $roleService = new RoleService($identityProvider, $roleProvider, new RecursiveRoleIteratorStrategy()); - - $routeGuard = new ControllerGuard($roleService, [[ - 'controller' => 'MyController', - 'actions' => 'edit', - 'roles' => 'member' - ]]); - - $routeGuard->onResult($event); - - $this->assertTrue($event->propagationIsStopped()); - $this->assertEquals(ControllerGuard::GUARD_UNAUTHORIZED, $event->getError()); - $this->assertInstanceOf('LmcRbacMvc\Exception\UnauthorizedException', $event->getParam('exception')); - } - - public function createRouteMatch(array $params = []) - { - $class = class_exists(V2RouteMatch::class) ? V2RouteMatch::class : RouteMatch::class; - return new $class($params); - } -} diff --git a/tests/Guard/ControllerPermissionsGuardTest.php b/tests/Guard/ControllerPermissionsGuardTest.php deleted file mode 100644 index 07a5c6e4..00000000 --- a/tests/Guard/ControllerPermissionsGuardTest.php +++ /dev/null @@ -1,523 +0,0 @@ -createMock('LmcRbacMvc\Service\AuthorizationService'); - - return $authorizationService; - } - - public function testAttachToRightEvent() - { - $guard = new ControllerPermissionsGuard($this->getMockAuthorizationService()); - - $eventManager = $this->createMock('Laminas\EventManager\EventManagerInterface'); - $eventManager->expects($this->once()) - ->method('attach') - ->with(ControllerGuard::EVENT_NAME); - - $guard->attach($eventManager); - } - - public function rulesConversionProvider() - { - return [ - // Without actions - [ - 'rules' => [ - [ - 'controller' => 'MyController', - 'permissions' => 'post.manage' - ], - [ - 'controller' => 'MyController2', - 'permissions' => ['post.update', 'post.delete'] - ], - new \ArrayIterator([ - 'controller' => 'MyController3', - 'permissions' => new \ArrayIterator(['post.manage']) - ]) - ], - 'expected' => [ - 'mycontroller' => [0 => ['post.manage']], - 'mycontroller2' => [0 => ['post.update', 'post.delete']], - 'mycontroller3' => [0 => ['post.manage']] - ] - ], - // With one action - [ - 'rules' => [ - [ - 'controller' => 'MyController', - 'actions' => 'DELETE', - 'permissions' => 'permission1' - ], - [ - 'controller' => 'MyController2', - 'actions' => ['delete'], - 'permissions' => 'permission2' - ], - new \ArrayIterator([ - 'controller' => 'MyController3', - 'actions' => new \ArrayIterator(['DELETE']), - 'permissions' => new \ArrayIterator(['permission3']) - ]) - ], - 'expected' => [ - 'mycontroller' => [ - 'delete' => ['permission1'] - ], - 'mycontroller2' => [ - 'delete' => ['permission2'] - ], - 'mycontroller3' => [ - 'delete' => ['permission3'] - ], - ] - ], - // With multiple actions - [ - 'rules' => [ - [ - 'controller' => 'MyController', - 'actions' => ['EDIT', 'delete'], - 'permissions' => 'permission1' - ], - new \ArrayIterator([ - 'controller' => 'MyController2', - 'actions' => new \ArrayIterator(['edit', 'DELETE']), - 'permissions' => new \ArrayIterator(['permission2']) - ]) - ], - 'expected' => [ - 'mycontroller' => [ - 'edit' => ['permission1'], - 'delete' => ['permission1'] - ], - 'mycontroller2' => [ - 'edit' => ['permission2'], - 'delete' => ['permission2'] - ] - ] - ], - // Test that that if a rule is set globally to the controller, it does not override any - // action specific rule that may have been specified before - [ - 'rules' => [ - [ - 'controller' => 'MyController', - 'actions' => ['edit'], - 'permissions' => 'permission1' - ], - [ - 'controller' => 'MyController', - 'permissions' => 'permission2' - ] - ], - 'expected' => [ - 'mycontroller' => [ - 'edit' => ['permission1'], - 0 => ['permission2'] - ] - ] - ] - ]; - } - - /** - * @dataProvider rulesConversionProvider - */ - public function testRulesConversions(array $rules, array $expected) - { - $controllerGuard = new ControllerPermissionsGuard($this->getMockAuthorizationService(), $rules); - - $reflProperty = new \ReflectionProperty($controllerGuard, 'rules'); - $reflProperty->setAccessible(true); - - $this->assertEquals($expected, $reflProperty->getValue($controllerGuard)); - } - - public function controllerDataProvider() - { - return [ - // Test simple guard with both policies - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'permissions' => 'post.edit' - ] - ], - 'controller' => 'BlogController', - 'action' => 'edit', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'permissions' => 'post.edit' - ] - ], - 'controller' => 'BlogController', - 'action' => 'edit', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - // Test with multiple rules - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'actions' => 'read', - 'permissions' => 'post.read' - ], - [ - 'controller' => 'BlogController', - 'actions' => 'edit', - 'permissions' => 'post.edit' - ] - ], - 'controller' => 'BlogController', - 'action' => 'edit', - 'identityPermissions' => [ - ['post.edit', null, true] - ], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'actions' => 'read', - 'permissions' => 'post.read' - ], - [ - 'controller' => 'BlogController', - 'actions' => 'edit', - 'permissions' => 'post.edit' - ] - ], - 'controller' => 'BlogController', - 'action' => 'edit', - 'identityPermissions' => [ - ['post.edit', null, true] - ], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - // Test with multiple permissions. All must be authorized. - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'actions' => 'admin', - 'permissions' => ['post.update', 'post.delete'], - ], - ], - 'controller' => 'BlogController', - 'action' => 'admin', - 'identityPermissions' => [ - ['post.update', null, true], - ['post.delete', null, true], - ], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'actions' => 'admin', - 'permissions' => ['post.update', 'post.delete'], - ], - ], - 'controller' => 'BlogController', - 'action' => 'admin', - 'identityPermissions' => [ - ['post.update', null, false], - ['post.delete', null, true], - ], - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'actions' => 'admin', - 'permissions' => ['post.update', 'post.delete'], - ], - ], - 'controller' => 'BlogController', - 'action' => 'admin', - 'identityPermissions' => [ - ['post.update', null, true], - ['post.delete', null, false], - ], - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - // Assert that policy can deny unspecified rules - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'permissions' => 'post.edit' - ], - ], - 'controller' => 'CommentController', - 'action' => 'edit', - 'identityPermissions' => [ - ['post.edit', null, true] - ], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'permissions' => 'post.edit' - ], - ], - 'controller' => 'CommentController', - 'action' => 'edit', - 'identityPermissions' => [ - ['post.edit', null, true] - ], - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - // Test assert policy can deny other actions from controller when only one is specified - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'actions' => 'edit', - 'permissions' => 'post.edit' - ], - ], - 'controller' => 'BlogController', - 'action' => 'read', - 'identityPermissions' => [ - ['post.edit', null, true] - ], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - [ - 'controller' => 'BlogController', - 'actions' => 'edit', - 'permissions' => 'post.edit' - ], - ], - 'controller' => 'BlogController', - 'action' => 'read', - 'identityPermissions' => [ - ['post.edit', null, true] - ], - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - // Assert wildcard in permissions - [ - 'rules' => [ - [ - 'controller' => 'IndexController', - 'permissions' => '*' - ] - ], - 'controller' => 'IndexController', - 'action' => 'index', - 'identityPermissions' => [['post.edit', null, false]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - [ - 'controller' => 'IndexController', - 'permissions' => '*' - ] - ], - 'controller' => 'IndexController', - 'action' => 'index', - 'identityPermissions' => [['post.edit', null, false]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - ]; - } - - /** - * @dataProvider controllerDataProvider - */ - public function testControllerGranted( - array $rules, - $controller, - $action, - $identityPermissions, - $isGranted, - $protectionPolicy - ) { - $routeMatch = $this->createRouteMatch([ - 'controller' => $controller, - 'action' => $action, - ]); - - $authorizationService = $this->getMockAuthorizationService(); - $authorizationService->expects($this->any()) - ->method('isGranted') - ->will($this->returnValueMap($identityPermissions)); - - $controllerGuard = new ControllerPermissionsGuard($authorizationService, $rules); - $controllerGuard->setProtectionPolicy($protectionPolicy); - - $event = new MvcEvent(); - $event->setRouteMatch($routeMatch); - - $this->assertEquals($isGranted, $controllerGuard->isGranted($event)); - } - - public function testProperlyFillEventOnAuthorization() - { - $event = new MvcEvent(); - $routeMatch = $this->createRouteMatch([ - 'controller' => 'MyController', - 'action' => 'edit', - ]); - - $application = $this->createMock('Laminas\Mvc\Application'); - $eventManager = $this->createMock('Laminas\EventManager\EventManagerInterface'); - - $application->expects($this->never()) - ->method('getEventManager') - ->will($this->returnValue($eventManager)); - - $event->setRouteMatch($routeMatch); - $event->setApplication($application); - - $identity = $this->createMock('LmcRbacMvc\Identity\IdentityInterface'); - $identity->expects($this->any())->method('getRoles')->will($this->returnValue(['member'])); - - $identityProvider = $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface'); - $identityProvider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue($identity)); - - $roleProvider = new InMemoryRoleProvider([ - 'member' - ]); - - $roleService = new RoleService($identityProvider, $roleProvider, new RecursiveRoleIteratorStrategy()); - - $routeGuard = new ControllerGuard($roleService, [ - [ - 'controller' => 'MyController', - 'actions' => 'edit', - 'roles' => 'member' - ] - ]); - - $routeGuard->onResult($event); - - $this->assertEmpty($event->getError()); - $this->assertNull($event->getParam('exception')); - } - - public function testProperlySetUnauthorizedAndTriggerEventOnUnauthorization() - { - $event = new MvcEvent(); - $routeMatch = $this->createRouteMatch([ - 'controller' => 'MyController', - 'action' => 'delete', - ]); - - $application = $this->createMock('Laminas\Mvc\Application'); - $eventManager = $this->createMock('Laminas\EventManager\EventManager'); - - $application->expects($this->once()) - ->method('getEventManager') - ->will($this->returnValue($eventManager)); - - $eventManager->expects($this->once()) - ->method('triggerEvent') - ->with($event); - - $event->setRouteMatch($routeMatch); - $event->setApplication($application); - - $identityProvider = $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface'); - $identityProvider->expects($this->any()) -// ->method('getIdentityRoles') - ->method('getIdentity') - ->will($this->returnValue('member')); - - $roleProvider = new InMemoryRoleProvider([ - 'member' - ]); - - $roleService = new RoleService($identityProvider, $roleProvider, new RecursiveRoleIteratorStrategy()); - - $routeGuard = new ControllerGuard($roleService, [ - [ - 'controller' => 'MyController', - 'actions' => 'edit', - 'roles' => 'member' - ] - ]); - - $routeGuard->onResult($event); - - $this->assertTrue($event->propagationIsStopped()); - $this->assertEquals(ControllerGuard::GUARD_UNAUTHORIZED, $event->getError()); - $this->assertInstanceOf('LmcRbacMvc\Exception\UnauthorizedException', $event->getParam('exception')); - } - - public function createRouteMatch(array $params = []) - { - $class = class_exists(V2RouteMatch::class) ? V2RouteMatch::class : RouteMatch::class; - return new $class($params); - } -} diff --git a/tests/Guard/GuardPluginManagerTest.php b/tests/Guard/GuardPluginManagerTest.php deleted file mode 100644 index afd4c9fa..00000000 --- a/tests/Guard/GuardPluginManagerTest.php +++ /dev/null @@ -1,98 +0,0 @@ - 'foo' - ] - ], - [ - 'LmcRbacMvc\Guard\RoutePermissionsGuard', - [ - 'post/delete' => 'post.delete' - ] - ], - [ - 'LmcRbacMvc\Guard\ControllerGuard', - [ - [ - 'controller' => 'Foo', - 'actions' => 'bar', - 'roles' => 'baz' - ] - ] - ], - [ - 'LmcRbacMvc\Guard\ControllerPermissionsGuard', - [ - [ - 'controller' => 'Foo', - 'actions' => 'bar', - 'permissions' => 'baz' - ] - ] - ], - ]; - } - - /** - * @dataProvider guardProvider - */ - public function testCanCreateDefaultGuards($type, $options) - { - $serviceManager = new ServiceManager(); - $serviceManager->setService('LmcRbacMvc\Options\ModuleOptions', new ModuleOptions()); - $serviceManager->setService( - 'LmcRbacMvc\Service\RoleService', - $this->createMock('LmcRbacMvc\Service\RoleService') - ); - $serviceManager->setService( - 'LmcRbacMvc\Service\AuthorizationService', - $this->createMock('LmcRbacMvc\Service\AuthorizationService') - ); - - $pluginManager = new GuardPluginManager($serviceManager); - - $guard = $pluginManager->get($type, $options); - - $this->assertInstanceOf($type, $guard); - } - - public function testThrowExceptionForInvalidPlugin() - { - $this->expectException('LmcRbacMvc\Exception\RuntimeException'); - - $pluginManager = new GuardPluginManager(new ServiceManager()); - $pluginManager->get('stdClass'); - } -} diff --git a/tests/Guard/ProtectionPolicyTraitTest.php b/tests/Guard/ProtectionPolicyTraitTest.php deleted file mode 100644 index 96f04ff5..00000000 --- a/tests/Guard/ProtectionPolicyTraitTest.php +++ /dev/null @@ -1,35 +0,0 @@ -getObjectForTrait('LmcRbacMvc\Guard\ProtectionPolicyTrait'); - $trait->setProtectionPolicy(GuardInterface::POLICY_DENY); - - $this->assertEquals(GuardInterface::POLICY_DENY, $trait->getProtectionPolicy()); - } -} diff --git a/tests/Guard/RouteGuardTest.php b/tests/Guard/RouteGuardTest.php deleted file mode 100644 index 9e4cc30a..00000000 --- a/tests/Guard/RouteGuardTest.php +++ /dev/null @@ -1,477 +0,0 @@ -createMock('LmcRbacMvc\Service\RoleService')); - - $eventManager = $this->createMock('Laminas\EventManager\EventManagerInterface'); - $eventManager->expects($this->once()) - ->method('attach') - ->with(RouteGuard::EVENT_NAME); - - $guard->attach($eventManager); - } - - /** - * We want to ensure an order for guards - */ - public function testAssertRouteGuardPriority() - { - $this->assertGreaterThan(RoutePermissionsGuard::EVENT_PRIORITY, RouteGuard::EVENT_PRIORITY); - $this->assertGreaterThan(ControllerGuard::EVENT_PRIORITY, RouteGuard::EVENT_PRIORITY); - } - - public function rulesConversionProvider() - { - return [ - // Simple string to array conversion - [ - 'rules' => [ - 'route' => 'role1' - ], - 'expected' => [ - 'route' => ['role1'] - ] - ], - - // Array to array - [ - 'rules' => [ - 'route' => ['role1', 'role2'] - ], - 'expected' => [ - 'route' => ['role1', 'role2'] - ] - ], - - // Traversable to array - [ - 'rules' => [ - 'route' => new \ArrayIterator(['role1', 'role2']) - ], - 'expected' => [ - 'route' => ['role1', 'role2'] - ] - ], - - // Block a route for everyone - [ - 'rules' => [ - 'route' - ], - 'expected' => [ - 'route' => [] - ] - ], - ]; - } - - /** - * @dataProvider rulesConversionProvider - */ - public function testRulesConversions(array $rules, array $expected) - { - $roleService = $this->createMock('LmcRbacMvc\Service\RoleService'); - $routeGuard = new RouteGuard($roleService, $rules); - - $reflProperty = new \ReflectionProperty($routeGuard, 'rules'); - $reflProperty->setAccessible(true); - - $this->assertEquals($expected, $reflProperty->getValue($routeGuard)); - } - - public function routeDataProvider() - { - return [ - // Assert basic one-to-one mapping with both policies - [ - 'rules' => ['adminRoute' => 'admin'], - 'matchedRouteName' => 'adminRoute', - 'rolesConfig' => ['admin'], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['adminRoute' => 'admin'], - 'matchedRouteName' => 'adminRoute', - 'rolesConfig' => ['admin'], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Assert that policy changes result for non-specified route guards - [ - 'rules' => ['route' => 'member'], - 'matchedRouteName' => 'anotherRoute', - 'rolesConfig' => ['member'], - 'identityRole' => 'member', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['route' => 'member'], - 'matchedRouteName' => 'anotherRoute', - 'rolesConfig' => ['member'], - 'identityRole' => 'member', - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Assert that composed route work for both policies - [ - 'rules' => ['admin/dashboard' => 'admin'], - 'matchedRouteName' => 'admin/dashboard', - 'rolesConfig' => ['admin'], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['admin/dashboard' => 'admin'], - 'matchedRouteName' => 'admin/dashboard', - 'rolesConfig' => ['admin'], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Assert that wildcard route work for both policies - [ - 'rules' => ['admin/*' => 'admin'], - 'matchedRouteName' => 'admin/dashboard', - 'rolesConfig' => ['admin'], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['admin/*' => 'admin'], - 'matchedRouteName' => 'admin/dashboard', - 'rolesConfig' => ['admin'], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Assert that wildcard route does match (or not depending on the policy) if rules is after matched route name - [ - 'rules' => ['fooBar/*' => 'admin'], - 'matchedRouteName' => 'admin/fooBar/baz', - 'rolesConfig' => ['admin'], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['fooBar/*' => 'admin'], - 'matchedRouteName' => 'admin/fooBar/baz', - 'rolesConfig' => ['admin'], - 'identityRole' => 'admin', - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Assert that it can grant access with multiple rules - [ - 'rules' => [ - 'route1' => 'admin', - 'route2' => 'admin' - ], - 'matchedRouteName' => 'route1', - 'rolesConfig' => ['admin'], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - 'route1' => 'admin', - 'route2' => 'admin' - ], - 'matchedRouteName' => 'route1', - 'rolesConfig' => ['admin'], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Assert that it can grant/deny access with multiple rules based on the policy - [ - 'rules' => [ - 'route1' => 'admin', - 'route2' => 'admin' - ], - 'matchedRouteName' => 'route3', - 'rolesConfig' => ['admin'], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - 'route1' => 'admin', - 'route2' => 'admin' - ], - 'matchedRouteName' => 'route3', - 'rolesConfig' => ['admin'], - 'identityRole' => 'admin', - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Assert it can deny access if a role does not have access - [ - 'rules' => ['route' => 'admin'], - 'matchedRouteName' => 'route', - 'rolesConfig' => ['admin', 'guest'], - 'identityRole' => 'guest', - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['route' => 'admin'], - 'matchedRouteName' => 'route', - 'rolesConfig' => ['admin', 'guest'], - 'identityRole' => 'guest', - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Assert it can grant access using child-parent relationship between roles - [ - 'rules' => ['home' => 'guest'], - 'matchedRouteName' => 'home', - 'rolesConfig' => [ - 'admin' => [ - 'children' => ['member'] - ], - 'member' => [ - 'children' => ['guest'] - ], - 'guest' - ], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['home' => 'guest'], - 'matchedRouteName' => 'home', - 'rolesConfig' => [ - 'admin' => [ - 'children' => ['member'] - ], - 'member' => [ - 'children' => ['guest'] - ], - 'guest' - ], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Assert it can deny access using child-parent relationship between roles (just to be sure) - [ - 'rules' => ['route' => 'admin'], - 'matchedRouteName' => 'route', - 'rolesToCreate' => [ - 'admin' => [ - 'children' => 'member' - ], - 'member' - ], - 'identityRole' => 'member', - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['route' => 'admin'], - 'matchedRouteName' => 'route', - 'rolesToCreate' => [ - 'admin' => [ - 'children' => 'member' - ], - 'member' - ], - 'identityRole' => 'member', - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - - // Assert wildcard in role - [ - 'rules' => ['home' => '*'], - 'matchedRouteName' => 'home', - 'rolesConfig' => ['admin'], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['home' => '*'], - 'matchedRouteName' => 'home', - 'rolesConfig' => ['admin'], - 'identityRole' => 'admin', - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - ]; - } - - /** - * @dataProvider routeDataProvider - */ - public function testRouteGranted( - array $rules, - $matchedRouteName, - array $rolesConfig, - $identityRole, - $isGranted, - $protectionPolicy - ) { - $event = new MvcEvent(); - $routeMatch = $this->createRouteMatch(); - $routeMatch->setMatchedRouteName($matchedRouteName); - - $event->setRouteMatch($routeMatch); - - $identity = $this->createMock('LmcRbacMvc\Identity\IdentityInterface'); - $identity->expects($this->any())->method('getRoles')->will($this->returnValue($identityRole)); - - $identityProvider = $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface'); - $identityProvider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue($identity)); - - $roleProvider = new InMemoryRoleProvider($rolesConfig); - $roleService = new RoleService($identityProvider, $roleProvider, new RecursiveRoleIteratorStrategy()); - - $routeGuard = new RouteGuard($roleService, $rules); - $routeGuard->setProtectionPolicy($protectionPolicy); - - $this->assertEquals($isGranted, $routeGuard->isGranted($event)); - } - - public function testProperlyFillEventOnAuthorization() - { - $event = new MvcEvent(); - $routeMatch = $this->createRouteMatch(); - - $application = $this->createMock('Laminas\Mvc\Application'); - $eventManager = $this->createMock('Laminas\EventManager\EventManagerInterface'); - - $application->expects($this->never()) - ->method('getEventManager') - ->will($this->returnValue($eventManager)); - - $routeMatch->setMatchedRouteName('adminRoute'); - $event->setRouteMatch($routeMatch); - $event->setApplication($application); - - $identity = $this->createMock('LmcRbacMvc\Identity\IdentityInterface'); - $identity->expects($this->any())->method('getRoles')->will($this->returnValue(['member'])); - - $identityProvider = $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface'); - $identityProvider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue($identity)); - - $roleProvider = new InMemoryRoleProvider(['member']); - $roleService = new RoleService($identityProvider, $roleProvider, new RecursiveRoleIteratorStrategy()); - - $routeGuard = new RouteGuard($roleService, [ - 'adminRoute' => 'member' - ]); - - $routeGuard->onResult($event); - - $this->assertEmpty($event->getError()); - $this->assertNull($event->getParam('exception')); - } - - public function testProperlySetUnauthorizedAndTriggerEventOnUnauthorization() - { - $event = new MvcEvent(); - $routeMatch = $this->createRouteMatch(); - - $application = $this->createMock('Laminas\Mvc\Application'); - $eventManager = $this->createMock('Laminas\EventManager\EventManager'); - - $application->expects($this->once()) - ->method('getEventManager') - ->will($this->returnValue($eventManager)); - - $eventManager->expects($this->once()) - ->method('triggerEvent') - ->with($event); - - $routeMatch->setMatchedRouteName('adminRoute'); - $event->setRouteMatch($routeMatch); - $event->setApplication($application); - - $identity = $this->createMock('LmcRbacMvc\Identity\IdentityInterface'); - $identity->expects($this->any())->method('getRoles')->will($this->returnValue(['member'])); - - $identityProvider = $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface'); - $identityProvider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue($identity)); - - $roleProvider = new InMemoryRoleProvider(['member', 'guest']); - $roleService = new RoleService($identityProvider, $roleProvider, new RecursiveRoleIteratorStrategy()); - - $routeGuard = new RouteGuard($roleService, [ - 'adminRoute' => 'guest' - ]); - - $routeGuard->onResult($event); - - $this->assertTrue($event->propagationIsStopped()); - $this->assertEquals(RouteGuard::GUARD_UNAUTHORIZED, $event->getError()); - $this->assertInstanceOf('LmcRbacMvc\Exception\UnauthorizedException', $event->getParam('exception')); - } - - public function createRouteMatch(array $params = []) - { - $class = class_exists(V2RouteMatch::class) ? V2RouteMatch::class : RouteMatch::class; - return new $class($params); - } -} diff --git a/tests/Guard/RoutePermissionsGuardTest.php b/tests/Guard/RoutePermissionsGuardTest.php deleted file mode 100644 index 4e218658..00000000 --- a/tests/Guard/RoutePermissionsGuardTest.php +++ /dev/null @@ -1,452 +0,0 @@ -createMock('Laminas\EventManager\EventManagerInterface'); - $eventManager->expects($this->once()) - ->method('attach') - ->with(RouteGuard::EVENT_NAME); - - $guard = new RoutePermissionsGuard($this->createMock('LmcRbacMvc\Service\AuthorizationService')); - $guard->attach($eventManager); - } - - /** - * We want to ensure an order for guards - */ - public function testAssertRoutePermissionsGuardPriority() - { - $this->assertLessThan(RouteGuard::EVENT_PRIORITY, RoutePermissionsGuard::EVENT_PRIORITY); - $this->assertGreaterThan(ControllerGuard::EVENT_PRIORITY, RoutePermissionsGuard::EVENT_PRIORITY); - } - - public function rulesConversionProvider() - { - return [ - // Simple string to array conversion - [ - 'rules' => [ - 'route' => 'permission1' - ], - 'expected' => [ - 'route' => ['permission1'] - ] - ], - // Array to array - [ - 'rules' => [ - 'route' => ['permission1', 'permission2'] - ], - 'expected' => [ - 'route' => ['permission1', 'permission2'] - ] - ], - // Traversable to array - [ - 'rules' => [ - 'route' => new \ArrayIterator(['permission1', 'permission2']) - ], - 'expected' => [ - 'route' => ['permission1', 'permission2'] - ] - ], - // Block a route for everyone - [ - 'rules' => [ - 'route' - ], - 'expected' => [ - 'route' => [] - ] - ], - ]; - } - - /** - * @dataProvider rulesConversionProvider - */ - public function testRulesConversions(array $rules, array $expected) - { - $roleService = $this->createMock('LmcRbacMvc\Service\AuthorizationService'); - $routeGuard = new RoutePermissionsGuard($roleService, $rules); - $reflProperty = new \ReflectionProperty($routeGuard, 'rules'); - $reflProperty->setAccessible(true); - $this->assertEquals($expected, $reflProperty->getValue($routeGuard)); - } - - public function routeDataProvider() - { - return [ - // Assert basic one-to-one mapping with both policies - [ - 'rules' => ['adminRoute' => 'post.edit'], - 'matchedRouteName' => 'adminRoute', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['adminRoute' => 'post.edit'], - 'matchedRouteName' => 'adminRoute', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - // Assert that policy changes result for non-specified route guards - [ - 'rules' => ['route' => 'post.edit'], - 'matchedRouteName' => 'anotherRoute', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['route' => 'post.edit'], - 'matchedRouteName' => 'anotherRoute', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - // Assert that composed route work for both policies - [ - 'rules' => ['admin/dashboard' => 'post.edit'], - 'matchedRouteName' => 'admin/dashboard', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['admin/dashboard' => 'post.edit'], - 'matchedRouteName' => 'admin/dashboard', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - // Assert that wildcard route work for both policies - [ - 'rules' => ['admin/*' => 'post.edit'], - 'matchedRouteName' => 'admin/dashboard', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['admin/*' => 'post.edit'], - 'matchedRouteName' => 'admin/dashboard', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - // Assert that wildcard route does match (or not depending on the policy) if rules is after matched route name - [ - 'rules' => ['fooBar/*' => 'post.edit'], - 'matchedRouteName' => 'admin/fooBar/baz', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['fooBar/*' => 'post.edit'], - 'matchedRouteName' => 'admin/fooBar/baz', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - // Assert that it can grant access with multiple rules - [ - 'rules' => [ - 'route1' => 'post.edit', - 'route2' => 'post.edit' - ], - 'matchedRouteName' => 'route1', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - 'route1' => 'post.edit', - 'route2' => 'post.edit' - ], - 'matchedRouteName' => 'route2', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - 'route1' => 'post.edit', - 'route2' => 'post.edit' - ], - 'matchedRouteName' => 'route1', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - [ - 'rules' => [ - 'route1' => 'post.edit', - 'route2' => 'post.edit' - ], - 'matchedRouteName' => 'route2', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - // Assert that it can grant/deny access with multiple rules based on the policy - [ - 'rules' => [ - 'route1' => 'post.edit', - 'route2' => 'post.edit' - ], - 'matchedRouteName' => 'route3', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => [ - 'route1' => 'post.edit', - 'route2' => 'post.edit' - ], - 'matchedRouteName' => 'route3', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - // Assert it can deny access if the only permission does not have access - [ - 'rules' => ['route' => 'post.edit'], - 'matchedRouteName' => 'route', - 'identityPermissions' => [ - ['post.edit', null, false], - ['post.read', null, true] - ], - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['route' => 'post.edit'], - 'matchedRouteName' => 'route', - 'identityPermissions' => [ - ['post.edit', null, false], - ['post.read', null, true] - ], - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ], - // Assert it can deny access if one of the permission does not have access - [ - 'rules' => ['route' => ['post.edit', 'post.read']], - 'matchedRouteName' => 'route', - 'identityPermissions' => [ - ['post.edit', null, true], - ['post.read', null, true] - ], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['route' => ['post.edit', 'post.read']], - 'matchedRouteName' => 'route', - 'identityPermissions' => [ - ['post.edit', null, true], - ['post.read', null, false] - ], - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['route' => ['post.edit', 'post.read']], - 'matchedRouteName' => 'route', - 'identityPermissions' => [ - ['post.edit', null, false], - ['post.read', null, true] - ], - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_ALLOW - ], - // Assert wildcard in permission - [ - 'rules' => ['home' => '*'], - 'matchedRouteName' => 'home', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['home' => '*'], - 'matchedRouteName' => 'home', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - // Assert wildcard wins all - [ - 'rules' => ['home' => ['*', 'post.edit']], - 'matchedRouteName' => 'home', - 'identityPermissions' => [['post.edit', null, false]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_ALLOW - ], - [ - 'rules' => ['home' => ['*', 'post.edit']], - 'matchedRouteName' => 'home', - 'identityPermissions' => [['post.edit', null, false]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - [ - 'rules' => ['route' => [ - 'permissions' => ['post.edit', 'post.read'], - 'condition' => GuardInterface::CONDITION_OR - ]], - 'matchedRouteName' => 'route', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => true, - 'policy' => GuardInterface::POLICY_DENY - ], - [ - 'rules' => ['route' => [ - 'permissions' => ['post.edit', 'post.read'], - 'condition' => GuardInterface::CONDITION_AND - ]], - 'matchedRouteName' => 'route', - 'identityPermissions' => [['post.edit', null, true]], - 'isGranted' => false, - 'policy' => GuardInterface::POLICY_DENY - ] - ]; - } - - /** - * @dataProvider routeDataProvider - */ - public function testRoutePermissionGranted( - array $rules, - $matchedRouteName, - array $identityPermissions, - $isGranted, - $protectionPolicy - ) { - $routeMatch = $this->createRouteMatch(); - $routeMatch->setMatchedRouteName($matchedRouteName); - - $event = new MvcEvent(); - $event->setRouteMatch($routeMatch); - - $authorizationService = $this->createMock('LmcRbacMvc\Service\AuthorizationServiceInterface'); - $authorizationService->expects($this->any()) - ->method('isGranted') - ->will($this->returnValueMap($identityPermissions)); - - $routeGuard = new RoutePermissionsGuard($authorizationService, $rules); - $routeGuard->setProtectionPolicy($protectionPolicy); - - $this->assertEquals($isGranted, $routeGuard->isGranted($event)); - } - - public function testProperlyFillEventOnAuthorization() - { - $eventManager = $this->createMock('Laminas\EventManager\EventManagerInterface'); - - $application = $this->createMock('Laminas\Mvc\Application'); - $application->expects($this->never()) - ->method('getEventManager') - ->will($this->returnValue($eventManager)); - - $routeMatch = $this->createRouteMatch(); - $routeMatch->setMatchedRouteName('adminRoute'); - - $event = new MvcEvent(); - $event->setRouteMatch($routeMatch); - $event->setApplication($application); - - $authorizationService = $this->createMock('LmcRbacMvc\Service\AuthorizationServiceInterface'); - $authorizationService->expects($this->once()) - ->method('isGranted') - ->with('post.edit') - ->will($this->returnValue(true)); - - $routeGuard = new RoutePermissionsGuard($authorizationService, [ - 'adminRoute' => 'post.edit' - ]); - $routeGuard->onResult($event); - - $this->assertEmpty($event->getError()); - $this->assertNull($event->getParam('exception')); - } - - public function testProperlySetUnauthorizedAndTriggerEventOnUnauthorization() - { - $eventManager = $this->createMock('Laminas\EventManager\EventManager'); - - $application = $this->createMock('Laminas\Mvc\Application'); - $application->expects($this->once()) - ->method('getEventManager') - ->will($this->returnValue($eventManager)); - - $routeMatch = $this->createRouteMatch(); - $routeMatch->setMatchedRouteName('adminRoute'); - - $event = new MvcEvent(); - $event->setRouteMatch($routeMatch); - $event->setApplication($application); - - $eventManager->expects($this->once()) - ->method('triggerEvent') - ->with($event); - - $authorizationService = $this->createMock('LmcRbacMvc\Service\AuthorizationServiceInterface'); - $authorizationService->expects($this->once()) - ->method('isGranted') - ->with('post.edit') - ->will($this->returnValue(false)); - - $routeGuard = new RoutePermissionsGuard($authorizationService, [ - 'adminRoute' => 'post.edit' - ]); - $routeGuard->onResult($event); - - $this->assertTrue($event->propagationIsStopped()); - $this->assertEquals(RouteGuard::GUARD_UNAUTHORIZED, $event->getError()); - $this->assertInstanceOf('LmcRbacMvc\Exception\UnauthorizedException', $event->getParam('exception')); - } - - public function createRouteMatch(array $params = []) - { - $class = class_exists(V2RouteMatch::class) ? V2RouteMatch::class : RouteMatch::class; - return new $class($params); - } -} diff --git a/tests/Identity/AuthenticationIdentityProviderTest.php b/tests/Identity/AuthenticationIdentityProviderTest.php deleted file mode 100644 index 214b16bb..00000000 --- a/tests/Identity/AuthenticationIdentityProviderTest.php +++ /dev/null @@ -1,54 +0,0 @@ -authenticationService = $this->createMock('Laminas\Authentication\AuthenticationService'); - $this->identityProvider = new AuthenticationIdentityProvider($this->authenticationService); - } - - public function testCanReturnIdentity() - { - $identity = $this->createMock('LmcRbacMvc\Identity\IdentityInterface'); - - $this->authenticationService->expects($this->once()) - ->method('getIdentity') - ->will($this->returnValue($identity)); - - $this->assertSame($identity, $this->identityProvider->getIdentity()); - } -} diff --git a/tests/Initializer/AuthorizationAwareFake.php b/tests/Initializer/AuthorizationAwareFake.php deleted file mode 100644 index 7c97e6d4..00000000 --- a/tests/Initializer/AuthorizationAwareFake.php +++ /dev/null @@ -1,32 +0,0 @@ -prophesize(ServiceLocatorInterface::class)->willImplement(ContainerInterface::class); - $authorizationService = $this->createMock('LmcRbacMvc\Service\AuthorizationService'); - - $serviceLocator - ->get($authServiceClassName) - ->willReturn($authorizationService) - ->shouldBeCalled(); - - $initializer->initialize($instance, $serviceLocator->reveal()); - - $this->assertEquals($authorizationService, $instance->getAuthorizationService()); - } -} diff --git a/tests/ModuleTest.php b/tests/ModuleTest.php deleted file mode 100644 index ebc5a0d9..00000000 --- a/tests/ModuleTest.php +++ /dev/null @@ -1,57 +0,0 @@ -assertIsArray($module->getConfig()); - } - - public function testCanRegisterGuards() - { - $module = new Module(); - $mvcEvent = $this->createMock('Laminas\Mvc\MvcEvent'); - $application = $this->createMock('Laminas\Mvc\Application'); - $eventManager = $this->createMock('Laminas\EventManager\EventManagerInterface'); - $serviceManager = $this->createMock('Laminas\ServiceManager\ServiceManager'); - - $mvcEvent->expects($this->once())->method('getTarget')->will($this->returnValue($application)); - $application->expects($this->once())->method('getEventManager')->will($this->returnValue($eventManager)); - $application->expects($this->once())->method('getServiceManager')->will($this->returnValue($serviceManager)); - - $guards = [ - $this->createMock('LmcRbacMvc\Guard\GuardInterface') - ]; - - $serviceManager->expects($this->once()) - ->method('get') - ->with('LmcRbacMvc\Guards') - ->will($this->returnValue($guards)); - - $module->onBootstrap($mvcEvent); - } -} diff --git a/tests/Mvc/Controller/Plugin/IsGrantedTest.php b/tests/Mvc/Controller/Plugin/IsGrantedTest.php deleted file mode 100644 index f9f273c3..00000000 --- a/tests/Mvc/Controller/Plugin/IsGrantedTest.php +++ /dev/null @@ -1,41 +0,0 @@ -createMock('LmcRbacMvc\Service\AuthorizationServiceInterface'); - - $authorizationService->expects($this->once()) - ->method('isGranted') - ->with('edit') - ->will($this->returnValue(true)); - - $helper = new IsGranted($authorizationService); - - $this->assertTrue($helper('edit')); - } -} diff --git a/tests/Options/ModuleOptionsTest.php b/tests/Options/ModuleOptionsTest.php deleted file mode 100644 index ce0d0a85..00000000 --- a/tests/Options/ModuleOptionsTest.php +++ /dev/null @@ -1,89 +0,0 @@ -get('LmcRbacMvc\Options\ModuleOptions'); - - $this->assertEquals('LmcRbacMvc\Identity\AuthenticationIdentityProvider', $moduleOptions->getIdentityProvider()); - $this->assertEquals('guest', $moduleOptions->getGuestRole()); - $this->assertEquals('allow', $moduleOptions->getProtectionPolicy()); - $this->assertIsArray($moduleOptions->getGuards()); - $this->assertIsArray($moduleOptions->getRoleProvider()); - $this->assertIsArray($moduleOptions->getAssertionMap()); - $this->assertInstanceOf('LmcRbacMvc\Options\UnauthorizedStrategyOptions', $moduleOptions->getUnauthorizedStrategy()); - $this->assertInstanceOf('LmcRbacMvc\Options\RedirectStrategyOptions', $moduleOptions->getRedirectStrategy()); - } - - public function testSettersAndGetters() - { - $moduleOptions = new ModuleOptions([ - 'identity_provider' => 'IdentityProvider', - 'guest_role' => 'unknown', - 'guards' => [], - 'protection_policy' => 'deny', - 'role_provider' => [], - 'assertion_map' => [ - 'foo' => 'bar' - ], - 'unauthorized_strategy' => [ - 'template' => 'error/unauthorized' - ], - 'redirect_strategy' => [ - 'redirect_to_route_connected' => 'home', - 'redirect_to_route_disconnected' => 'login' - ] - ]); - - $this->assertEquals('IdentityProvider', $moduleOptions->getIdentityProvider()); - $this->assertEquals('unknown', $moduleOptions->getGuestRole()); - $this->assertEquals([], $moduleOptions->getGuards()); - $this->assertEquals('deny', $moduleOptions->getProtectionPolicy()); - $this->assertEquals([], $moduleOptions->getRoleProvider()); - $this->assertEquals(['foo' => 'bar'], $moduleOptions->getAssertionMap()); - $this->assertInstanceOf('LmcRbacMvc\Options\UnauthorizedStrategyOptions', $moduleOptions->getUnauthorizedStrategy()); - $this->assertInstanceOf('LmcRbacMvc\Options\RedirectStrategyOptions', $moduleOptions->getRedirectStrategy()); - } - - public function testThrowExceptionForInvalidProtectionPolicy() - { - $this->expectException(\LmcRbacMvc\Exception\RuntimeException::class); - - $moduleOptions = new ModuleOptions(); - $moduleOptions->setProtectionPolicy('invalid'); - } - - public function testThrowExceptionIfMoreThanOneRoleProviderIsSet() - { - $this->expectException(\LmcRbacMvc\Exception\RuntimeException::class); - - $moduleOptions = new ModuleOptions(); - $moduleOptions->setRoleProvider(['foo', 'bar']); - } -} diff --git a/tests/Options/RedirectStrategyOptionsTest.php b/tests/Options/RedirectStrategyOptionsTest.php deleted file mode 100644 index 8b0fd95d..00000000 --- a/tests/Options/RedirectStrategyOptionsTest.php +++ /dev/null @@ -1,55 +0,0 @@ -assertTrue($redirectStrategyOptions->getRedirectWhenConnected()); - $this->assertEquals('login', $redirectStrategyOptions->getRedirectToRouteDisconnected()); - $this->assertEquals('home', $redirectStrategyOptions->getRedirectToRouteConnected()); - $this->assertTrue($redirectStrategyOptions->getAppendPreviousUri()); - $this->assertEquals('redirectTo', $redirectStrategyOptions->getPreviousUriQueryKey()); - } - - public function testSettersAndGetters() - { - $redirectStrategyOptions = new RedirectStrategyOptions([ - 'redirect_when_connected' => false, - 'redirect_to_route_connected' => 'foo', - 'redirect_to_route_disconnected' => 'bar', - 'append_previous_uri' => false, - 'previous_uri_query_key' => 'redirect-to' - ]); - - $this->assertFalse($redirectStrategyOptions->getRedirectWhenConnected()); - $this->assertEquals('foo', $redirectStrategyOptions->getRedirectToRouteConnected()); - $this->assertEquals('bar', $redirectStrategyOptions->getRedirectToRouteDisconnected()); - $this->assertFalse($redirectStrategyOptions->getAppendPreviousUri()); - $this->assertEquals('redirect-to', $redirectStrategyOptions->getPreviousUriQueryKey()); - } -} diff --git a/tests/Options/UnauthorizedStrategyOptionsTest.php b/tests/Options/UnauthorizedStrategyOptionsTest.php deleted file mode 100644 index 59ccbf52..00000000 --- a/tests/Options/UnauthorizedStrategyOptionsTest.php +++ /dev/null @@ -1,43 +0,0 @@ -assertEquals('error/403', $unauthorizedStrategyOptions->getTemplate()); - } - - public function testSettersAndGetters() - { - $unauthorizedStrategyOptions = new UnauthorizedStrategyOptions([ - 'template' => 'error/unauthorized' - ]); - - $this->assertEquals('error/unauthorized', $unauthorizedStrategyOptions->getTemplate()); - } -} diff --git a/tests/Role/InMemoryRoleProviderTest.php b/tests/Role/InMemoryRoleProviderTest.php deleted file mode 100644 index e5d7d35c..00000000 --- a/tests/Role/InMemoryRoleProviderTest.php +++ /dev/null @@ -1,82 +0,0 @@ - [ - 'permissions' => ['all'], - ], - 'admin' => [ - 'children' => ['member'], - 'permissions' => ['delete'] - ], - 'member' => [ - 'children' => ['guest'], - 'permissions' => ['write'] - ], - 'guest' - ]); - - $roles = $inMemoryProvider->getRoles(['admin', 'member', 'guest', 'system']); - - $this->assertCount(4, $roles); - - // Test admin role - $adminRole = $roles[0]; - $this->assertInstanceOf('Rbac\Role\HierarchicalRoleInterface', $adminRole); - $this->assertEquals('admin', $adminRole->getName()); - $this->assertTrue($adminRole->hasPermission('delete')); - - // Test member role - $memberRole = $roles[1]; - $this->assertInstanceOf('Rbac\Role\HierarchicalRoleInterface', $memberRole); - $this->assertEquals('member', $memberRole->getName()); - $this->assertTrue($memberRole->hasPermission('write')); - $this->assertFalse($memberRole->hasPermission('delete')); - - // Test guest role - $guestRole = $roles[2]; - $this->assertInstanceOf('Rbac\Role\RoleInterface', $guestRole); - $this->assertNotInstanceOf('Rbac\Role\HierarchicalRoleInterface', $guestRole); - $this->assertEquals('guest', $guestRole->getName()); - $this->assertFalse($guestRole->hasPermission('write')); - $this->assertFalse($guestRole->hasPermission('delete')); - - // Test system role - $systemRole = $roles[3]; - $this->assertInstanceOf('Rbac\Role\RoleInterface', $systemRole); - $this->assertNotInstanceOf('Rbac\Role\HierarchicalRoleInterface', $systemRole); - $this->assertEquals('system', $systemRole->getName()); - $this->assertTrue($systemRole->hasPermission('all')); - $this->assertFalse($systemRole->hasPermission('write')); - $this->assertFalse($systemRole->hasPermission('delete')); - - $this->assertSame($adminRole->getChildren()['member'], $memberRole); - $this->assertSame($memberRole->getChildren()['guest'], $guestRole); - } -} diff --git a/tests/Role/ObjectRepositoryRoleProviderTest.php b/tests/Role/ObjectRepositoryRoleProviderTest.php deleted file mode 100644 index cc1bdc3c..00000000 --- a/tests/Role/ObjectRepositoryRoleProviderTest.php +++ /dev/null @@ -1,173 +0,0 @@ -serviceManager = ServiceManagerFactory::getServiceManager(); - $objectManager = $this->getObjectManager(); - - // Let's add some roles - $adminRole = new FlatRole('admin'); - $objectManager->persist($adminRole); - - $memberRole = new FlatRole('member'); - $objectManager->persist($memberRole); - - $objectManager->flush(); - - $objectRepository = $objectManager->getRepository('LmcRbacMvcTest\Asset\FlatRole'); - - $objectRepositoryRoleProvider = new ObjectRepositoryRoleProvider($objectRepository, 'name'); - - // Get only the admin role - $roles = $objectRepositoryRoleProvider->getRoles(['admin']); - - $this->assertCount(1, $roles); - $this->assertIsArray($roles); - - $this->assertInstanceOf('Rbac\Role\RoleInterface', $roles[0]); - $this->assertEquals('admin', $roles[0]->getName()); - } - - public function testObjectRepositoryProviderForHierarchicalRole() - { - $this->serviceManager = ServiceManagerFactory::getServiceManager(); - $objectManager = $this->getObjectManager(); - - // Let's add some roles - $guestRole = new HierarchicalRole('guest'); - $objectManager->persist($guestRole); - - $memberRole = new HierarchicalRole('member'); - $memberRole->addChild($guestRole); - $objectManager->persist($memberRole); - - $adminRole = new HierarchicalRole('admin'); - $adminRole->addChild($memberRole); - $objectManager->persist($adminRole); - - $objectManager->flush(); - - $objectRepository = $objectManager->getRepository('LmcRbacMvcTest\Asset\HierarchicalRole'); - - $objectRepositoryRoleProvider = new ObjectRepositoryRoleProvider($objectRepository, 'name'); - - // Get only the admin role - $roles = $objectRepositoryRoleProvider->getRoles(['admin']); - - $this->assertCount(1, $roles); - $this->assertIsArray($roles); - - $this->assertInstanceOf('Rbac\Role\HierarchicalRoleInterface', $roles[0]); - $this->assertEquals('admin', $roles[0]->getName()); - - $iteratorIterator = new \RecursiveIteratorIterator( - new RecursiveRoleIterator($roles[0]->getChildren()), - \RecursiveIteratorIterator::SELF_FIRST - ); - - $childRolesString = ''; - - foreach ($iteratorIterator as $childRole) { - $this->assertInstanceOf('Rbac\Role\HierarchicalRoleInterface', $childRole); - $childRolesString .= $childRole->getName(); - } - - $this->assertEquals('memberguest', $childRolesString); - } - - public function testRoleCacheOnConsecutiveCalls() - { - $objectRepository = $this->createMock('Doctrine\ORM\EntityRepository'); - $memberRole = new FlatRole('member'); - $provider = new ObjectRepositoryRoleProvider($objectRepository, 'name'); - $result = [$memberRole]; - - $objectRepository->expects($this->once())->method('findBy')->will($this->returnValue($result)); - - $this->assertEquals($result, $provider->getRoles(['member'])); - $this->assertEquals($result, $provider->getRoles(['member'])); - } - - public function testClearRoleCache() - { - $objectRepository = $this->createMock('Doctrine\ORM\EntityRepository'); - $memberRole = new FlatRole('member'); - $provider = new ObjectRepositoryRoleProvider($objectRepository, 'name'); - $result = [$memberRole]; - - $objectRepository->expects($this->exactly(2))->method('findBy')->will($this->returnValue($result)); - - $this->assertEquals($result, $provider->getRoles(['member'])); - $provider->clearRoleCache(); - $this->assertEquals($result, $provider->getRoles(['member'])); - } - - public function testThrowExceptionIfAskedRoleIsNotFound() - { - $this->serviceManager = ServiceManagerFactory::getServiceManager(); - - $objectManager = $this->getObjectManager(); - $objectRepository = $objectManager->getRepository('LmcRbacMvcTest\Asset\FlatRole'); - $objectRepositoryRoleProvider = new ObjectRepositoryRoleProvider($objectRepository, 'name'); - - $this->expectException( - \LmcRbacMvc\Exception\RoleNotFoundException::class, - 'Some roles were asked but could not be loaded from database: guest, admin' - ); - - $objectRepositoryRoleProvider->getRoles(['guest', 'admin']); - } - - /** - * @return ObjectManager - * @throws ToolsException - */ - private function getObjectManager() - { - /* @var $entityManager \Doctrine\ORM\EntityManager */ - $entityManager = $this->serviceManager->get('Doctrine\\ORM\\EntityManager'); - $schemaTool = new SchemaTool($entityManager); - $schemaTool->dropDatabase(); - $schemaTool->createSchema($entityManager->getMetadataFactory()->getAllMetadata()); - - return $entityManager; - } -} diff --git a/tests/Role/RoleProviderPluginManagerTest.php b/tests/Role/RoleProviderPluginManagerTest.php deleted file mode 100644 index d9f2bf75..00000000 --- a/tests/Role/RoleProviderPluginManagerTest.php +++ /dev/null @@ -1,44 +0,0 @@ -createMock('LmcRbacMvc\Role\RoleProviderInterface'); - $pluginManager = new RoleProviderPluginManager(new ServiceManager()); - - $this->assertNull($pluginManager->validatePlugin($pluginMock)); - } - - public function testValidationOfPluginFailsIfRoleProviderInterfaceIsNotImplemented() - { - $this->expectException('LmcRbacMvc\Exception\RuntimeException'); - - $pluginManager = new RoleProviderPluginManager(new ServiceManager()); - $pluginManager->get('stdClass', []); - } -} diff --git a/tests/Service/AuthorizationServiceAwareTraitTest.php b/tests/Service/AuthorizationServiceAwareTraitTest.php deleted file mode 100644 index d1b47015..00000000 --- a/tests/Service/AuthorizationServiceAwareTraitTest.php +++ /dev/null @@ -1,36 +0,0 @@ -getObjectForTrait('LmcRbacMvc\Service\AuthorizationServiceAwareTrait'); - $authorizationService = $this->createMock('LmcRbacMvc\Service\AuthorizationService'); - - $trait->setAuthorizationService($authorizationService); - - $this->assertEquals($authorizationService, $trait->getAuthorizationService()); - } -} diff --git a/tests/Service/AuthorizationServiceTest.php b/tests/Service/AuthorizationServiceTest.php deleted file mode 100644 index efdbf41b..00000000 --- a/tests/Service/AuthorizationServiceTest.php +++ /dev/null @@ -1,254 +0,0 @@ - 'LmcRbacMvcTest\Asset\SimpleAssertion' - ] - ], - - // Simple is accepted from assertion map - [ - 'admin', - 'delete', - true, - true, - [ - 'delete' => 'LmcRbacMvcTest\Asset\SimpleAssertion' - ] - ], - - // Simple is refused from no role - [ - [], - 'read', - null, - false - ], - ]; - } - - /** - * @dataProvider grantedProvider - */ - public function testGranted($role, $permission, $context, $isGranted, $assertions = []) - { - $roleConfig = [ - 'admin' => [ - 'children' => ['member'], - 'permissions' => ['delete'] - ], - 'member' => [ - 'children' => ['guest'], - 'permissions' => ['write'] - ], - 'guest' => [ - 'permissions' => ['read'] - ] - ]; - - $assertionPluginConfig = [ - 'invokables' => [ - 'LmcRbacMvcTest\Asset\SimpleAssertion' => 'LmcRbacMvcTest\Asset\SimpleAssertion' - ] - ]; - - $identity = $this->createMock('LmcRbacMvc\Identity\IdentityInterface'); - $identity->expects($this->once())->method('getRoles')->will($this->returnValue((array) $role)); - - $identityProvider = $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface'); - $identityProvider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue($identity)); - - $rbac = new Rbac(new RecursiveRoleIteratorStrategy()); - $roleService = new RoleService( - $identityProvider, - new InMemoryRoleProvider($roleConfig), - $rbac->getTraversalStrategy() - ); - $assertionPluginManager = new AssertionPluginManager(new ServiceManager(), $assertionPluginConfig); - $authorizationService = new AuthorizationService($rbac, $roleService, $assertionPluginManager); - - $authorizationService->setAssertions($assertions); - - $this->assertEquals($isGranted, $authorizationService->isGranted($permission, $context)); - } - - public function testDoNotCallAssertionIfThePermissionIsNotGranted() - { - $role = $this->createMock('Rbac\Role\RoleInterface'); - $rbac = $this->createMock('Rbac\Rbac'); - - $roleService = $this->createMock('LmcRbacMvc\Service\RoleService'); - $roleService->expects($this->once())->method('getIdentityRoles')->will($this->returnValue([$role])); - - $assertionPluginManager = $this->createMock('LmcRbacMvc\Assertion\AssertionPluginManager'); - $assertionPluginManager->expects($this->never())->method('get'); - - $authorizationService = new AuthorizationService($rbac, $roleService, $assertionPluginManager); - - $this->assertFalse($authorizationService->isGranted('foo')); - } - - public function testThrowExceptionForInvalidAssertion() - { - $role = $this->createMock('Rbac\Role\RoleInterface'); - $rbac = $this->createMock('Rbac\Rbac'); - - $rbac->expects($this->once())->method('isGranted')->will($this->returnValue(true)); - - $roleService = $this->createMock('LmcRbacMvc\Service\RoleService'); - $roleService->expects($this->once())->method('getIdentityRoles')->will($this->returnValue([$role])); - - $assertionPluginManager = $this->createMock('LmcRbacMvc\Assertion\AssertionPluginManager'); - $authorizationService = new AuthorizationService($rbac, $roleService, $assertionPluginManager); - - $this->expectException('LmcRbacMvc\Exception\InvalidArgumentException'); - - $authorizationService->setAssertion('foo', new \stdClass()); - $authorizationService->isGranted('foo'); - } - - public function testDynamicAssertions() - { - $role = $this->createMock('Rbac\Role\RoleInterface'); - $rbac = $this->createMock('Rbac\Rbac'); - - $rbac->expects($this->exactly(2))->method('isGranted')->will($this->returnValue(true)); - - $roleService = $this->createMock('LmcRbacMvc\Service\RoleService'); - $roleService->expects($this->exactly(2))->method('getIdentityRoles')->will($this->returnValue([$role])); - - $assertionPluginManager = $this->createMock('LmcRbacMvc\Assertion\AssertionPluginManager'); - $authorizationService = new AuthorizationService($rbac, $roleService, $assertionPluginManager); - - // Using a callable - $called = false; - - $authorizationService->setAssertion( - 'foo', - function (AuthorizationService $injectedService) use ($authorizationService, &$called) { - $this->assertSame($injectedService, $authorizationService); - - $called = true; - - return false; - } - ); - - $this->assertFalse($authorizationService->isGranted('foo')); - $this->assertTrue($called); - - // Using an assertion object - $assertion = new SimpleAssertion(); - $authorizationService->setAssertion('foo', $assertion); - - $this->assertFalse($authorizationService->isGranted('foo', false)); - $this->assertTrue($assertion->getCalled()); - } - - public function testAssertionMap() - { - $rbac = $this->createMock('Rbac\Rbac'); - $roleService = $this->createMock('LmcRbacMvc\Service\RoleService'); - $assertionPluginManager = $this->createMock('LmcRbacMvc\Assertion\AssertionPluginManager'); - $authorizationService = new AuthorizationService($rbac, $roleService, $assertionPluginManager); - - $authorizationService->setAssertions(['foo' => 'bar', 'bar' => 'foo']); - - $this->assertTrue($authorizationService->hasAssertion('foo')); - $this->assertTrue($authorizationService->hasAssertion('bar')); - - $authorizationService->setAssertion('bar', null); - - $this->assertFalse($authorizationService->hasAssertion('bar')); - } - - /** - * @covers LmcRbacMvc\Service\AuthorizationService::getIdentity - */ - public function testGetIdentity() - { - $rbac = $this->createMock('Rbac\Rbac'); - $identity = $this->createMock('LmcRbacMvc\Identity\IdentityInterface'); - $roleService = $this->createMock('LmcRbacMvc\Service\RoleService'); - $assertionManager = $this->createMock('LmcRbacMvc\Assertion\AssertionPluginManager'); - $authorization = new AuthorizationService($rbac, $roleService, $assertionManager); - - $roleService->expects($this->once())->method('getIdentity')->will($this->returnValue($identity)); - - $this->assertSame($authorization->getIdentity(), $identity); - } -} diff --git a/tests/Service/RoleServiceTest.php b/tests/Service/RoleServiceTest.php deleted file mode 100644 index cf46f89f..00000000 --- a/tests/Service/RoleServiceTest.php +++ /dev/null @@ -1,311 +0,0 @@ - [], - 'identityRoles' => [], - 'rolesToCheck' => [ - 'member' - ], - 'doesMatch' => false - ], - - // Simple - [ - 'rolesConfig' => [ - 'member' => [ - 'children' => ['guest'] - ], - 'guest' - ], - 'identityRoles' => [ - 'guest' - ], - 'rolesToCheck' => [ - 'member' - ], - 'doesMatch' => false - ], - [ - 'rolesConfig' => [ - 'member' => [ - 'children' => ['guest'] - ], - 'guest' - ], - 'identityRoles' => [ - 'member' - ], - 'rolesToCheck' => [ - 'member' - ], - 'doesMatch' => true - ], - - // Complex role inheritance - [ - 'rolesConfig' => [ - 'admin' => [ - 'children' => ['moderator'] - ], - 'moderator' => [ - 'children' => ['member'] - ], - 'member' => [ - 'children' => ['guest'] - ], - 'guest' - ], - 'identityRoles' => [ - 'member', - 'moderator' - ], - 'rolesToCheck' => [ - 'admin' - ], - 'doesMatch' => false - ], - [ - 'rolesConfig' => [ - 'admin' => [ - 'children' => ['moderator'] - ], - 'moderator' => [ - 'children' => ['member'] - ], - 'member' => [ - 'children' => ['guest'] - ], - 'guest' - ], - 'identityRoles' => [ - 'member', - 'admin' - ], - 'rolesToCheck' => [ - 'moderator' - ], - 'doesMatch' => true - ], - - // Complex role inheritance and multiple check - [ - 'rolesConfig' => [ - 'sysadmin' => [ - 'children' => ['siteadmin', 'admin'] - ], - 'siteadmin', - 'admin' => [ - 'children' => ['moderator'] - ], - 'moderator' => [ - 'children' => ['member'] - ], - 'member' => [ - 'children' => ['guest'] - ], - 'guest' - ], - 'identityRoles' => [ - 'member', - 'moderator' - ], - 'rolesToCheck' => [ - 'admin', - 'sysadmin' - ], - 'doesMatch' => false - ], - [ - 'rolesConfig' => [ - 'sysadmin' => [ - 'children' => ['siteadmin', 'admin'] - ], - 'siteadmin', - 'admin' => [ - 'children' => ['moderator'] - ], - 'moderator' => [ - 'children' => ['member'] - ], - 'member' => [ - 'children' => ['guest'] - ], - 'guest' - ], - 'identityRoles' => [ - 'moderator', - 'admin' - ], - 'rolesToCheck' => [ - 'sysadmin', - 'siteadmin', - 'member' - ], - 'doesMatch' => true - ] - ]; - } - - /** - * @dataProvider roleProvider - */ - public function testMatchIdentityRoles(array $rolesConfig, array $identityRoles, array $rolesToCheck, $doesMatch) - { - $identity = $this->createMock('LmcRbacMvc\Identity\IdentityInterface'); - $identity->expects($this->once())->method('getRoles')->will($this->returnValue($identityRoles)); - - $identityProvider = $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface'); - $identityProvider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue($identity)); - - $roleService = new RoleService($identityProvider, new InMemoryRoleProvider($rolesConfig), new RecursiveRoleIteratorStrategy()); - - $this->assertEquals($doesMatch, $roleService->matchIdentityRoles($rolesToCheck)); - } - - public function testReturnGuestRoleIfNoIdentityIsFound() - { - $identityProvider = $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface'); - $identityProvider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue(null)); - - $roleService = new RoleService( - $identityProvider, - new InMemoryRoleProvider([]), - $this->createMock('Rbac\Traversal\Strategy\TraversalStrategyInterface') - ); - - $roleService->setGuestRole('guest'); - - $result = $roleService->getIdentityRoles(); - - $this->assertEquals('guest', $roleService->getGuestRole()); - $this->assertCount(1, $result); - $this->assertInstanceOf('Rbac\Role\RoleInterface', $result[0]); - $this->assertEquals('guest', $result[0]->getName()); - } - - public function testSetIdentityProvider() - { - $identityProvider = $this->createMock(IdentityProviderInterface::class); - $identityProvider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue('test')); - $roleService = new RoleService( - $this->createMock(IdentityProviderInterface::class), - new InMemoryRoleProvider([]), - $this->createMock(TraversalStrategyInterface::class) - ); - $roleService->setIdentityProvider($identityProvider); - $this->assertEquals('test', $roleService->getIdentity()); - } - - public function testSetRoleProvider() - { - $role = $this->createMock(RoleInterface::class); - $identity = $this->createMock(IdentityInterface::class); - $identity->expects($this->once())->method('getRoles')->will($this->returnValue(new \ArrayObject([$role]))); - - $identityProvider = $this->createMock(IdentityProviderInterface::class); - $identityProvider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue($identity)); - $roleProvider = new InMemoryRoleProvider([ - 'member' => [ - 'children' => ['guest'], - ], - 'guest' - ]); - $roleService = new RoleService( - $identityProvider, - $roleProvider, - new RecursiveRoleIteratorStrategy() - ); - $roleService->setRoleProvider($roleProvider); - $roles = $roleService->getIdentityRoles(); - $this->assertEquals($role, $roles[0]); - } - - public function testConvertRolesTraversable() - { - $identity = $this->createMock(IdentityInterface::class); - $identity->expects($this->once())->method('getRoles')->will($this->returnValue(['guest'])); - - $identityProvider = $this->createMock(IdentityProviderInterface::class); - $identityProvider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue($identity)); - $roleService = new RoleService( - $identityProvider, - $this->createMock(RoleProviderInterface::class), - new RecursiveRoleIteratorStrategy() - ); - $roleProvider = new InMemoryRoleProvider([ - 'member' => [ - 'children' => ['guest'] - ], - 'guest' - ]); - $roleService->setRoleProvider($roleProvider); - $this->assertEquals(false, $roleService->matchIdentityRoles(['member'])); - } - - - public function testThrowExceptionIfIdentityIsWrongType() - { - $this->expectException('LmcRbacMvc\Exception\RuntimeException'); - $this->expectExceptionMessage('LmcRbacMvc expects your identity to implement LmcRbacMvc\Identity\IdentityInterface, "stdClass" given'); - - $identityProvider = $this->createMock('LmcRbacMvc\Identity\IdentityProviderInterface'); - $identityProvider->expects($this->any()) - ->method('getIdentity') - ->will($this->returnValue(new \stdClass())); - - $roleService = new RoleService( - $identityProvider, - $this->createMock('LmcRbacMvc\Role\RoleProviderInterface'), - $this->createMock('Rbac\Traversal\Strategy\TraversalStrategyInterface') - ); - - $roleService->getIdentityRoles(); - } -} diff --git a/tests/TestConfiguration.php.dist b/tests/TestConfiguration.php.dist deleted file mode 100644 index feb4ebc3..00000000 --- a/tests/TestConfiguration.php.dist +++ /dev/null @@ -1,33 +0,0 @@ - [ - 'Laminas\Router', - 'LmcRbacMvc', - 'DoctrineModule', - 'DoctrineORMModule', - ], - 'module_listener_options' => [ - 'config_glob_paths' => [ - __DIR__ . '/testing.config.php', - ], - 'module_paths' => [ - ], - ], -]; diff --git a/tests/Util/ServiceManagerFactory.php b/tests/Util/ServiceManagerFactory.php deleted file mode 100644 index 719f7dab..00000000 --- a/tests/Util/ServiceManagerFactory.php +++ /dev/null @@ -1,77 +0,0 @@ - - */ -abstract class ServiceManagerFactory -{ - /** - * @var array - */ - private static $config = []; - - /** - * @static - * @param array $config - */ - public static function setApplicationConfig(array $config) - { - static::$config = $config; - } - - /** - * @static - * @return array - */ - public static function getApplicationConfig() - { - return static::$config; - } - - /** - * @param array|null $config - * @return ServiceManager - */ - public static function getServiceManager(array $config = null) - { - $config = $config ?: static::getApplicationConfig(); - $serviceManagerConfig = new ServiceManagerConfig( - isset($config['service_manager']) ? $config['service_manager'] : [] - ); - $serviceManager = new ServiceManager(); - $serviceManagerConfig->configureServiceManager($serviceManager); - $serviceManager->setService('ApplicationConfig', $config); - $serviceManager->setAllowOverride(true); - - /* @var $moduleManager \Laminas\ModuleManager\ModuleManagerInterface */ - $moduleManager = $serviceManager->get('ModuleManager'); - - $moduleManager->loadModules(); - - return $serviceManager; - } -} diff --git a/tests/View/Helper/HasRoleTest.php b/tests/View/Helper/HasRoleTest.php deleted file mode 100644 index 011f44c9..00000000 --- a/tests/View/Helper/HasRoleTest.php +++ /dev/null @@ -1,59 +0,0 @@ -get('Config'); - $this->assertArrayHasKey('view_helpers', $config); - $viewHelpersConfig = $config['view_helpers']; - $this->assertEquals('LmcRbacMvc\View\Helper\HasRole', $viewHelpersConfig['aliases']['hasRole']); - $this->assertEquals( - 'LmcRbacMvc\Factory\HasRoleViewHelperFactory', - $viewHelpersConfig['factories']['LmcRbacMvc\View\Helper\HasRole'] - ); - } - - public function testCallAuthorizationService() - { - $rolesConfig = [ - ['member', true], - [['member'], true], - ]; - - $authorizationService = $this->createMock('LmcRbacMvc\Service\RoleService'); - $authorizationService->expects($this->any()) - ->method('matchIdentityRoles') - ->will($this->returnValueMap($rolesConfig)); - - $helper = new HasRole($authorizationService); - - $this->assertTrue($helper('member')); - $this->assertTrue($helper(['member'])); - } -} diff --git a/tests/View/Helper/IsGrantedTest.php b/tests/View/Helper/IsGrantedTest.php deleted file mode 100644 index 6450247f..00000000 --- a/tests/View/Helper/IsGrantedTest.php +++ /dev/null @@ -1,55 +0,0 @@ -get('Config'); - $this->assertArrayHasKey('view_helpers', $config); - $viewHelpersConfig = $config['view_helpers']; - $this->assertEquals('LmcRbacMvc\View\Helper\IsGranted', $viewHelpersConfig['aliases']['isGranted']); - $this->assertEquals( - 'LmcRbacMvc\Factory\IsGrantedViewHelperFactory', - $viewHelpersConfig['factories']['LmcRbacMvc\View\Helper\IsGranted'] - ); - } - - public function testCallAuthorizationService() - { - $authorizationService = $this->createMock('LmcRbacMvc\Service\AuthorizationServiceInterface'); - - $authorizationService->expects($this->once()) - ->method('isGranted') - ->with('edit') - ->will($this->returnValue(true)); - - $helper = new IsGranted($authorizationService); - - $this->assertTrue($helper('edit')); - } -} diff --git a/tests/View/Strategy/RedirectStrategyTest.php b/tests/View/Strategy/RedirectStrategyTest.php deleted file mode 100644 index dc843c5f..00000000 --- a/tests/View/Strategy/RedirectStrategyTest.php +++ /dev/null @@ -1,204 +0,0 @@ -createMock('Laminas\EventManager\EventManagerInterface'); - $eventManager->expects($this->once()) - ->method('attach') - ->with(MvcEvent::EVENT_DISPATCH_ERROR); - - $strategyListener->attach($eventManager); - } - - public function testReturnNullIfNotRightException() - { - $redirectStrategy = new RedirectStrategy(new RedirectStrategyOptions(), new AuthenticationService()); - $event = new MvcEvent(); - $event->setParam('exception', new \RuntimeException()); - - $this->assertNull($redirectStrategy->onError($event)); - } - - public function testCanRedirectWhenDisconnected() - { - $response = new HttpResponse(); - - $router = $this->createTreeRouteStack(); - $router->addRoute('login', [ - 'type' => 'literal', - 'options' => [ - 'route' => '/login' - ] - ]); - - $mvcEvent = new MvcEvent(); - $mvcEvent->setParam('exception', new UnauthorizedException()); - $mvcEvent->setResponse($response); - $mvcEvent->setRouter($router); - - $options = new RedirectStrategyOptions([ - 'redirect_to_route_disconnected' => 'login', - 'append_previous_uri' => false - ]); - - $authenticationService = $this->createMock('Laminas\Authentication\AuthenticationService'); - $authenticationService->expects($this->once())->method('hasIdentity')->will($this->returnValue(false)); - - $redirectStrategy = new RedirectStrategy($options, $authenticationService); - - $redirectStrategy->onError($mvcEvent); - - $this->assertInstanceOf('Laminas\Stdlib\ResponseInterface', $mvcEvent->getResult()); - $this->assertEquals(302, $mvcEvent->getResponse()->getStatusCode()); - $this->assertEquals('/login', $mvcEvent->getResponse()->getHeaders()->get('Location')->getFieldValue()); - } - - public function testCanRedirectWhenConnected() - { - $response = new HttpResponse(); - - $router = $this->createTreeRouteStack(); - $router->addRoute('home', [ - 'type' => 'literal', - 'options' => [ - 'route' => '/home' - ] - ]); - - $mvcEvent = new MvcEvent(); - $mvcEvent->setParam('exception', new UnauthorizedException()); - $mvcEvent->setResponse($response); - $mvcEvent->setRouter($router); - - $options = new RedirectStrategyOptions([ - 'redirect_to_route_connected' => 'home', - 'append_previous_uri' => false - ]); - - $authenticationService = $this->createMock('Laminas\Authentication\AuthenticationService'); - $authenticationService->expects($this->once())->method('hasIdentity')->will($this->returnValue(true)); - - $redirectStrategy = new RedirectStrategy($options, $authenticationService); - - $redirectStrategy->onError($mvcEvent); - - $this->assertInstanceOf('Laminas\Stdlib\ResponseInterface', $mvcEvent->getResult()); - $this->assertEquals(302, $mvcEvent->getResponse()->getStatusCode()); - $this->assertEquals('/home', $mvcEvent->getResponse()->getHeaders()->get('Location')->getFieldValue()); - } - - public function testWontRedirectWhenConnectedAndOptionDisabled() - { - $response = new HttpResponse(); - - $router = $this->createTreeRouteStack(); - $router->addRoute('home', [ - 'type' => 'literal', - 'options' => [ - 'route' => '/home' - ] - ]); - - $mvcEvent = new MvcEvent(); - $mvcEvent->setParam('exception', new UnauthorizedException()); - $mvcEvent->setResponse($response); - $mvcEvent->setRouter($router); - - $options = new RedirectStrategyOptions([ - 'redirect_when_connected' => false - ]); - - $authenticationService = $this->createMock('Laminas\Authentication\AuthenticationService'); - $authenticationService->expects($this->once())->method('hasIdentity')->will($this->returnValue(true)); - - $redirectStrategy = new RedirectStrategy($options, $authenticationService); - - $redirectStrategy->onError($mvcEvent); - - $this->assertNotEquals(302, $mvcEvent->getResponse()->getStatusCode()); - } - - public function testCanAppendPreviousUri() - { - $response = new HttpResponse(); - - $request = new HttpRequest(); - $request->setUri('http://example.com'); - - $router = $this->createTreeRouteStack(); - $router->addRoute('login', [ - 'type' => 'literal', - 'options' => [ - 'route' => '/login' - ] - ]); - - $mvcEvent = new MvcEvent(); - $mvcEvent->setParam('exception', new UnauthorizedException()); - $mvcEvent->setResponse($response); - $mvcEvent->setRequest($request); - $mvcEvent->setRouter($router); - - $options = new RedirectStrategyOptions([ - 'redirect_to_route_disconnected' => 'login', - 'append_previous_uri' => true, - 'previous_uri_query_key' => 'redirect-uri' - ]); - - $authenticationService = $this->createMock('Laminas\Authentication\AuthenticationService'); - $authenticationService->expects($this->once())->method('hasIdentity')->will($this->returnValue(false)); - - $redirectStrategy = new RedirectStrategy($options, $authenticationService); - - $redirectStrategy->onError($mvcEvent); - - $this->assertInstanceOf('Laminas\Stdlib\ResponseInterface', $mvcEvent->getResult()); - $this->assertEquals(302, $mvcEvent->getResponse()->getStatusCode()); - $this->assertEquals( - '/login?redirect-uri=http://example.com/', - $mvcEvent->getResponse()->getHeaders()->get('Location')->getFieldValue() - ); - } - - public function createTreeRouteStack($routePluginManager = null) - { - $class = class_exists(V2TreeRouteStack::class) ? V2TreeRouteStack::class : TreeRouteStack::class; - return new $class($routePluginManager); - } -} diff --git a/tests/View/Strategy/UnauthorizedStrategyTest.php b/tests/View/Strategy/UnauthorizedStrategyTest.php deleted file mode 100644 index 888470b5..00000000 --- a/tests/View/Strategy/UnauthorizedStrategyTest.php +++ /dev/null @@ -1,120 +0,0 @@ -createMock('Laminas\EventManager\EventManagerInterface'); - $eventManager->expects($this->once()) - ->method('attach') - ->with(MvcEvent::EVENT_DISPATCH_ERROR); - - $strategyListener->attach($eventManager); - } - - public function testFillEvent() - { - $response = new HttpResponse(); - - $mvcEvent = new MvcEvent(); - $mvcEvent->setParam('exception', new UnauthorizedException()); - $mvcEvent->setResponse($response); - - $options = new UnauthorizedStrategyOptions([ - 'template' => 'error/403' - ]); - - $unauthorizedStrategy = new UnauthorizedStrategy($options); - - $unauthorizedStrategy->onError($mvcEvent); - - $this->assertEquals(403, $mvcEvent->getResponse()->getStatusCode()); - $this->assertInstanceOf('Laminas\View\Model\ModelInterface', $mvcEvent->getResult()); - } - - public function testNoErrorEvent() - { - $mvcEvent = new MvcEvent(); - $mvcEvent->setResponse(new HttpResponse()); - $mvcEvent->setParam('exception', null); - - $options = new UnauthorizedStrategyOptions([ - 'template' => 'error/403' - ]); - - $unauthorizedStrategy = new UnauthorizedStrategy($options); - - $unauthorizedStrategy->onError($mvcEvent); - $this->assertEquals(200, $mvcEvent->getResponse()->getStatusCode()); - } - - public function testResultIsResponseEvent() - { - $mvcEvent = new MvcEvent(); - $mvcEvent->setResponse(new HttpResponse()); - $mvcEvent->setParam('exception', null); - - $options = new UnauthorizedStrategyOptions([ - 'template' => 'error/403' - ]); - - $unauthorizedStrategy = new UnauthorizedStrategy($options); - - $unauthorizedStrategy->onError($mvcEvent); - $this->assertEquals(200, $mvcEvent->getResponse()->getStatusCode()); - } - - public function testGuardInterfaceErrorEvent() - { - $mvcEvent = new MvcEvent(); - $mvcEvent->setResponse(new HttpResponse()); - $mvcEvent->setParam('exception', new UnauthorizedException()); - $mvcEvent->setParam('error', GuardInterface::GUARD_UNAUTHORIZED); - - $options = new UnauthorizedStrategyOptions([ - 'template' => 'error/403' - ]); - - $unauthorizedStrategy = new UnauthorizedStrategy($options); - - $unauthorizedStrategy->onError($mvcEvent); - $this->assertEquals(403, $mvcEvent->getResponse()->getStatusCode()); - $this->assertInstanceOf('Laminas\View\Model\ModelInterface', $mvcEvent->getResult()); - /** @var ViewModel $model */ - $model = $mvcEvent->getResult(); - $this->assertEquals( GuardInterface::GUARD_UNAUTHORIZED, $model->getVariable('error')); - } - -} diff --git a/tests/testing.config.php b/tests/testing.config.php deleted file mode 100644 index ca2c8660..00000000 --- a/tests/testing.config.php +++ /dev/null @@ -1,51 +0,0 @@ - [], - 'doctrine' => [ - 'driver' => [ - 'application_driver' => [ - 'class' => 'Doctrine\ORM\Mapping\Driver\AnnotationDriver', - 'cache' => 'array', - 'paths' => [__DIR__ . '/Asset'] - ], - 'orm_default' => [ - 'drivers' => [ - 'LmcRbacMvcTest\Asset' => 'application_driver' - ] - ] - ], - - 'connection' => [ - 'orm_default' => [ - 'driverClass' => 'Doctrine\DBAL\Driver\PDO\SQLite\Driver', - 'params' => [ - 'host' => null, - 'port' => null, - 'user' => null, - 'password' => null, - 'dbname' => 'test', - 'driver' => 'pdo_sqlite', - 'path' => null, - 'memory' => true, - ], - ], - ], - ], -]; diff --git a/view/error/403.phtml b/view/error/403.phtml deleted file mode 100644 index 8a3c28cc..00000000 --- a/view/error/403.phtml +++ /dev/null @@ -1,15 +0,0 @@ -

A 403 error occurred

- -

You are not allowed to access this resource

- -

Details

- -error) { - case GuardInterface::GUARD_UNAUTHORIZED: - echo '

Request was blocked by a LmcRbacMvc guard

'; - break; - } -?> diff --git a/view/laminas-developer-tools/toolbar/lmc-rbac.phtml b/view/laminas-developer-tools/toolbar/lmc-rbac.phtml deleted file mode 100644 index 1fb56b81..00000000 --- a/view/laminas-developer-tools/toolbar/lmc-rbac.phtml +++ /dev/null @@ -1,102 +0,0 @@ -collector->getCollection(); -?> - -
-
- - ZfcRbac - Settings -
- -
- Settings Details - - - Guest role -   - - - - Guard protection policy -   - - -
-
- -
-
- - ZfcRbac - Guards -
-
- Guards details - - 0): ?> - $rules): ?> - - Type -   - - - - - Rules -   - - - - - - - - No guards - - -
-
- -
-
- - ZfcRbac - Roles -
-
- Loaded identity roles - - 0): ?> - $value): ?> - - - -   - - - - - - - - - No identity roles - - - - Permissions for loaded identity roles - - $permissions): ?> - - -   - - - -
-