|
22 | 22 | use TYPO3\CMS\Backend\Module\ModuleProvider; |
23 | 23 | use TYPO3\CMS\Backend\Module\ModuleRegistry; |
24 | 24 | use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; |
| 25 | +use TYPO3\CMS\Core\Database\ConnectionPool; |
25 | 26 | use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase; |
26 | 27 |
|
27 | 28 | final class ModuleProviderTest extends FunctionalTestCase |
@@ -327,4 +328,194 @@ public function modulesWithDependsOnSubmodulesAreHiddenWhenAllSubmodulesAreDenie |
327 | 328 | $moduleMenuItems = $moduleProvider->getModulesForModuleMenu($user); |
328 | 329 | self::assertArrayNotHasKey('main_module', $moduleMenuItems); |
329 | 330 | } |
| 331 | + |
| 332 | + #[Test] |
| 333 | + public function deepNestedModulesAreFilteredRecursivelyInGetModule(): void |
| 334 | + { |
| 335 | + // Test recursive access filtering at 5 levels deep |
| 336 | + $level1 = $this->get(ModuleFactory::class)->createModule('level1', []); |
| 337 | + $level2 = $this->get(ModuleFactory::class)->createModule('level2', ['parent' => 'level1']); |
| 338 | + $level3Accessible = $this->get(ModuleFactory::class)->createModule('level3_accessible', ['parent' => 'level2']); |
| 339 | + $level3Denied = $this->get(ModuleFactory::class)->createModule('level3_denied', ['parent' => 'level2', 'access' => 'user']); |
| 340 | + $level4Accessible = $this->get(ModuleFactory::class)->createModule('level4_accessible', ['parent' => 'level3_accessible']); |
| 341 | + $level4Denied = $this->get(ModuleFactory::class)->createModule('level4_denied', ['parent' => 'level3_accessible', 'access' => 'admin']); |
| 342 | + $level5Accessible = $this->get(ModuleFactory::class)->createModule('level5_accessible', ['parent' => 'level4_accessible', 'access' => 'user']); |
| 343 | + $level5Denied = $this->get(ModuleFactory::class)->createModule('level5_denied', ['parent' => 'level4_accessible', 'access' => 'user']); |
| 344 | + |
| 345 | + $moduleRegistry = new ModuleRegistry([ |
| 346 | + $level1, $level2, $level3Accessible, $level3Denied, |
| 347 | + $level4Accessible, $level4Denied, $level5Accessible, $level5Denied, |
| 348 | + ]); |
| 349 | + $moduleProvider = new ModuleProvider($moduleRegistry); |
| 350 | + |
| 351 | + $user = new BackendUserAuthentication(); |
| 352 | + $user->workspace = 0; |
| 353 | + $user->groupData['modules'] = 'level5_accessible'; // Only access to level5_accessible |
| 354 | + |
| 355 | + // Get the top-level module with user context - should recursively filter all levels |
| 356 | + $module = $moduleProvider->getModule('level1', $user); |
| 357 | + self::assertNotNull($module); |
| 358 | + |
| 359 | + // Level 2 should be present |
| 360 | + self::assertTrue($module->hasSubModule('level2')); |
| 361 | + $level2Module = $module->getSubModule('level2'); |
| 362 | + |
| 363 | + // Level 3: accessible should be present, denied should be removed |
| 364 | + self::assertTrue($level2Module->hasSubModule('level3_accessible')); |
| 365 | + self::assertFalse($level2Module->hasSubModule('level3_denied'), 'level3_denied should be removed due to user access restrictions'); |
| 366 | + |
| 367 | + $level3Module = $level2Module->getSubModule('level3_accessible'); |
| 368 | + |
| 369 | + // Level 4: accessible should be present, denied (admin only) should be removed |
| 370 | + self::assertTrue($level3Module->hasSubModule('level4_accessible')); |
| 371 | + self::assertFalse($level3Module->hasSubModule('level4_denied'), 'level4_denied should be removed due to admin access requirement'); |
| 372 | + |
| 373 | + $level4Module = $level3Module->getSubModule('level4_accessible'); |
| 374 | + |
| 375 | + // Level 5: accessible should be present, denied should be removed |
| 376 | + self::assertTrue($level4Module->hasSubModule('level5_accessible')); |
| 377 | + self::assertFalse($level4Module->hasSubModule('level5_denied'), 'level5_denied should be removed due to user access restrictions'); |
| 378 | + } |
| 379 | + |
| 380 | + #[Test] |
| 381 | + public function deepNestedModulesAreFilteredRecursivelyInGetModules(): void |
| 382 | + { |
| 383 | + // Test recursive filtering in getModules() with grouped=true |
| 384 | + $main1 = $this->get(ModuleFactory::class)->createModule('main1', []); |
| 385 | + $main2 = $this->get(ModuleFactory::class)->createModule('main2', []); |
| 386 | + $sub1 = $this->get(ModuleFactory::class)->createModule('sub1', ['parent' => 'main1']); |
| 387 | + $sub2 = $this->get(ModuleFactory::class)->createModule('sub2', ['parent' => 'main2', 'access' => 'admin']); |
| 388 | + $subsub1 = $this->get(ModuleFactory::class)->createModule('subsub1', ['parent' => 'sub1']); |
| 389 | + $subsub2 = $this->get(ModuleFactory::class)->createModule('subsub2', ['parent' => 'sub1', 'access' => 'user']); |
| 390 | + $subsubsub1 = $this->get(ModuleFactory::class)->createModule('subsubsub1', ['parent' => 'subsub1', 'access' => 'user']); |
| 391 | + $subsubsub2 = $this->get(ModuleFactory::class)->createModule('subsubsub2', ['parent' => 'subsub1', 'access' => 'user']); |
| 392 | + |
| 393 | + $moduleRegistry = new ModuleRegistry([ |
| 394 | + $main1, $main2, $sub1, $sub2, $subsub1, $subsub2, $subsubsub1, $subsubsub2, |
| 395 | + ]); |
| 396 | + $moduleProvider = new ModuleProvider($moduleRegistry); |
| 397 | + |
| 398 | + $user = new BackendUserAuthentication(); |
| 399 | + $user->workspace = 0; |
| 400 | + $user->groupData['modules'] = 'subsubsub1'; // Only access to subsubsub1 |
| 401 | + |
| 402 | + $modules = $moduleProvider->getModules($user, true, true); |
| 403 | + |
| 404 | + // main1 should be present |
| 405 | + self::assertArrayHasKey('main1', $modules); |
| 406 | + $main1Module = $modules['main1']; |
| 407 | + |
| 408 | + // main2 should be present (even though sub2 is admin-only) |
| 409 | + self::assertArrayHasKey('main2', $modules); |
| 410 | + $main2Module = $modules['main2']; |
| 411 | + |
| 412 | + // sub1 should be present, but sub2 (admin) should be removed from main2 |
| 413 | + self::assertTrue($main1Module->hasSubModule('sub1')); |
| 414 | + self::assertFalse($main2Module->hasSubModule('sub2'), 'sub2 should be removed due to admin access requirement'); |
| 415 | + |
| 416 | + $sub1Module = $main1Module->getSubModule('sub1'); |
| 417 | + |
| 418 | + // subsub1 should be present, subsub2 (user without access) should be removed |
| 419 | + self::assertTrue($sub1Module->hasSubModule('subsub1')); |
| 420 | + self::assertFalse($sub1Module->hasSubModule('subsub2'), 'subsub2 should be removed due to user access restrictions'); |
| 421 | + |
| 422 | + $subsub1Module = $sub1Module->getSubModule('subsub1'); |
| 423 | + |
| 424 | + // subsubsub1 should be present (user has access), subsubsub2 should be removed |
| 425 | + self::assertTrue($subsub1Module->hasSubModule('subsubsub1')); |
| 426 | + self::assertFalse($subsub1Module->hasSubModule('subsubsub2'), 'subsubsub2 should be removed due to user access restrictions'); |
| 427 | + } |
| 428 | + |
| 429 | + #[Test] |
| 430 | + public function deepNestedModulesWithHideModulesInTSConfigAreFilteredInMenuMethods(): void |
| 431 | + { |
| 432 | + // Test that TSConfig hideModules works recursively at all levels |
| 433 | + $main = $this->get(ModuleFactory::class)->createModule('main', []); |
| 434 | + $level2 = $this->get(ModuleFactory::class)->createModule('level2', ['parent' => 'main']); |
| 435 | + $level3a = $this->get(ModuleFactory::class)->createModule('level3a', ['parent' => 'level2']); |
| 436 | + $level3b = $this->get(ModuleFactory::class)->createModule('level3b', ['parent' => 'level2']); |
| 437 | + $level4a = $this->get(ModuleFactory::class)->createModule('level4a', ['parent' => 'level3a']); |
| 438 | + $level4b = $this->get(ModuleFactory::class)->createModule('level4b', ['parent' => 'level3b']); |
| 439 | + |
| 440 | + $moduleRegistry = new ModuleRegistry([$main, $level2, $level3a, $level3b, $level4a, $level4b]); |
| 441 | + $moduleProvider = new ModuleProvider($moduleRegistry); |
| 442 | + |
| 443 | + // Set TSConfig via database |
| 444 | + $this->importCSVDataSet(__DIR__ . '/../Fixtures/be_users.csv'); |
| 445 | + $this->get(ConnectionPool::class) |
| 446 | + ->getConnectionForTable('be_users') |
| 447 | + ->update( |
| 448 | + 'be_users', |
| 449 | + ['TSconfig' => 'options.hideModules = level3b,level4a'], |
| 450 | + ['uid' => 1] |
| 451 | + ); |
| 452 | + $user = $this->setUpBackendUser(1); |
| 453 | + $user->workspace = 0; |
| 454 | + |
| 455 | + // Test getModuleForMenu |
| 456 | + $menuModule = $moduleProvider->getModuleForMenu('main', $user); |
| 457 | + self::assertNotNull($menuModule); |
| 458 | + self::assertTrue($menuModule->hasSubModule('level2')); |
| 459 | + |
| 460 | + $level2Menu = $menuModule->getSubModule('level2'); |
| 461 | + // level3a should be present, level3b should be hidden by TSConfig |
| 462 | + self::assertTrue($level2Menu->hasSubModule('level3a')); |
| 463 | + self::assertFalse($level2Menu->hasSubModule('level3b'), 'level3b should be hidden by TSConfig'); |
| 464 | + |
| 465 | + $level3aMenu = $level2Menu->getSubModule('level3a'); |
| 466 | + // level4a should be hidden by TSConfig |
| 467 | + self::assertFalse($level3aMenu->hasSubModule('level4a'), 'level4a should be hidden by TSConfig'); |
| 468 | + |
| 469 | + // Test getModulesForModuleMenu |
| 470 | + $moduleMenuItems = $moduleProvider->getModulesForModuleMenu($user); |
| 471 | + self::assertArrayHasKey('main', $moduleMenuItems); |
| 472 | + $mainMenuItem = $moduleMenuItems['main']; |
| 473 | + self::assertTrue($mainMenuItem->hasSubModule('level2')); |
| 474 | + $level2MenuItem = $mainMenuItem->getSubModule('level2'); |
| 475 | + self::assertTrue($level2MenuItem->hasSubModule('level3a')); |
| 476 | + self::assertFalse($level2MenuItem->hasSubModule('level3b'), 'level3b should be hidden by TSConfig in module menu'); |
| 477 | + } |
| 478 | + |
| 479 | + #[Test] |
| 480 | + public function deepNestedModulesWithDependsOnSubmodulesAtMultipleLevels(): void |
| 481 | + { |
| 482 | + // Test dependsOnSubmodules at multiple nesting levels |
| 483 | + $main = $this->get(ModuleFactory::class)->createModule('main', []); |
| 484 | + $level2 = $this->get(ModuleFactory::class)->createModule('level2', [ |
| 485 | + 'parent' => 'main', |
| 486 | + 'dependsOnSubmodules' => true, |
| 487 | + ]); |
| 488 | + $level3 = $this->get(ModuleFactory::class)->createModule('level3', [ |
| 489 | + 'parent' => 'level2', |
| 490 | + 'dependsOnSubmodules' => true, |
| 491 | + ]); |
| 492 | + $level4accessible = $this->get(ModuleFactory::class)->createModule('level4accessible', [ |
| 493 | + 'parent' => 'level3', |
| 494 | + 'access' => 'user', |
| 495 | + ]); |
| 496 | + $level4denied = $this->get(ModuleFactory::class)->createModule('level4denied', [ |
| 497 | + 'parent' => 'level3', |
| 498 | + 'access' => 'user', |
| 499 | + ]); |
| 500 | + |
| 501 | + $moduleRegistry = new ModuleRegistry([$main, $level2, $level3, $level4accessible, $level4denied]); |
| 502 | + $moduleProvider = new ModuleProvider($moduleRegistry); |
| 503 | + |
| 504 | + $user = new BackendUserAuthentication(); |
| 505 | + $user->workspace = 0; |
| 506 | + $user->groupData['modules'] = 'level4accessible'; // Only access to level4accessible |
| 507 | + |
| 508 | + // With access to level4accessible, the entire chain should be visible |
| 509 | + $menuModule = $moduleProvider->getModuleForMenu('main', $user); |
| 510 | + self::assertNotNull($menuModule); |
| 511 | + self::assertTrue($menuModule->hasSubModule('level2'), 'level2 with dependsOnSubmodules should be visible when nested accessible modules exist'); |
| 512 | + |
| 513 | + $level2Menu = $menuModule->getSubModule('level2'); |
| 514 | + self::assertTrue($level2Menu->hasSubModule('level3'), 'level3 with dependsOnSubmodules should be visible when nested accessible modules exist'); |
| 515 | + |
| 516 | + // Now test with no access - the entire chain should be hidden |
| 517 | + $user->groupData['modules'] = ''; |
| 518 | + $menuModuleNoAccess = $moduleProvider->getModuleForMenu('main', $user); |
| 519 | + self::assertNull($menuModuleNoAccess, 'main module should be null when nested dependsOnSubmodules modules have no accessible children'); |
| 520 | + } |
330 | 521 | } |
0 commit comments