menuRepository = Mockery::mock(MenuRepositoryInterface::class); $this->roleRepository = Mockery::mock(RoleRepositoryInterface::class); $this->helper = new ExtensionMenuSyncHelper($this->menuRepository, $this->roleRepository); } /** * Eloquent 모델 Mock 생성 헬퍼 * * makePartial()로 Eloquent의 setAttribute가 정상 동작하도록 합니다. * * @param string $modelClass 모델 클래스명 * @param array $attributes 설정할 속성 * @return \Mockery\MockInterface */ private function createModelMock(string $modelClass, array $attributes = []): \Mockery\MockInterface { $mock = Mockery::mock($modelClass)->makePartial(); foreach ($attributes as $key => $value) { $mock->{$key} = $value; } return $mock; } // ========== syncMenu 테스트 ========== /** * 신규 메뉴 생성 시 user_overrides 없이 생성되는지 테스트 */ public function test_sync_menu_creates_new_menu_without_user_overrides(): void { $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->with('board-management', ExtensionOwnerType::Module, 'sirsoft-board') ->once() ->andReturn(null); $name = ['ko' => '게시판 관리', 'en' => 'Board Management']; $newMenu = $this->createModelMock(Menu::class, ['id' => 1]); $this->menuRepository ->shouldReceive('updateOrCreate') ->once() ->with( Mockery::on(fn ($conditions) => $conditions['slug'] === 'board-management' && $conditions['extension_type'] === ExtensionOwnerType::Module && $conditions['extension_identifier'] === 'sirsoft-board'), Mockery::on(fn ($values) => $values['name'] === $name && $values['icon'] === 'fas fa-list' && $values['order'] === 10 && $values['url'] === '/admin/boards' && $values['is_active'] === true && ! array_key_exists('user_overrides', $values)) ) ->andReturn($newMenu); $result = $this->helper->syncMenu( slug: 'board-management', extensionType: ExtensionOwnerType::Module, extensionIdentifier: 'sirsoft-board', newAttributes: [ 'name' => $name, 'icon' => 'fas fa-list', 'order' => 10, 'url' => '/admin/boards', ], ); $this->assertSame($newMenu, $result); } /** * user_overrides에 "name"이 있으면 name 갱신을 건너뛰는지 테스트 */ public function test_sync_menu_skips_user_modified_name(): void { $existingMenu = $this->createModelMock(Menu::class, [ 'id' => 1, 'name' => ['ko' => '우리 게시판', 'en' => 'Our Board'], 'icon' => 'fas fa-list', 'order' => 10, 'url' => '/admin/boards', 'user_overrides' => ['name'], // 유저가 name을 수정한 적 있음 ]); $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->once() ->andReturn($existingMenu); $this->menuRepository ->shouldReceive('update') ->once() ->with( $existingMenu, Mockery::on(function ($data) { // name은 user_overrides에 있으므로 포함되지 않아야 함 return ! array_key_exists('name', $data) && $data['icon'] === 'fas fa-clipboard' && $data['order'] === 20 && $data['url'] === '/admin/boards/v2' && $data['parent_id'] === null && $data['is_active'] === true; }) ) ->andReturn(true); $freshMenu = $this->createModelMock(Menu::class, ['id' => 1]); $existingMenu->shouldReceive('fresh')->once()->andReturn($freshMenu); $this->helper->syncMenu( slug: 'board-management', extensionType: ExtensionOwnerType::Module, extensionIdentifier: 'sirsoft-board', newAttributes: [ 'name' => ['ko' => 'Board 관리', 'en' => 'Board Admin'], 'icon' => 'fas fa-clipboard', 'order' => 20, 'url' => '/admin/boards/v2', ], ); } /** * user_overrides에 "icon"이 있으면 icon 갱신을 건너뛰는지 테스트 */ public function test_sync_menu_skips_user_modified_icon(): void { $existingMenu = $this->createModelMock(Menu::class, [ 'id' => 1, 'name' => ['ko' => '게시판', 'en' => 'Board'], 'icon' => 'fas fa-star', 'order' => 10, 'url' => '/admin/boards', 'user_overrides' => ['icon'], ]); $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->once() ->andReturn($existingMenu); $newName = ['ko' => 'Board 관리', 'en' => 'Board Admin']; $this->menuRepository ->shouldReceive('update') ->once() ->with( $existingMenu, Mockery::on(function ($data) use ($newName) { return $data['name'] === $newName && ! array_key_exists('icon', $data) && $data['order'] === 20 && $data['url'] === '/admin/boards/v2'; }) ) ->andReturn(true); $freshMenu = $this->createModelMock(Menu::class, ['id' => 1]); $existingMenu->shouldReceive('fresh')->once()->andReturn($freshMenu); $this->helper->syncMenu( slug: 'board-management', extensionType: ExtensionOwnerType::Module, extensionIdentifier: 'sirsoft-board', newAttributes: [ 'name' => $newName, 'icon' => 'fas fa-clipboard', 'order' => 20, 'url' => '/admin/boards/v2', ], ); } /** * user_overrides에 "order"가 있으면 order 갱신을 건너뛰는지 테스트 */ public function test_sync_menu_skips_user_modified_order(): void { $existingMenu = $this->createModelMock(Menu::class, [ 'id' => 1, 'name' => ['ko' => '게시판', 'en' => 'Board'], 'icon' => 'fas fa-list', 'order' => 99, 'url' => '/admin/boards', 'user_overrides' => ['order'], ]); $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->once() ->andReturn($existingMenu); $newName = ['ko' => 'Board 관리', 'en' => 'Board Admin']; $this->menuRepository ->shouldReceive('update') ->once() ->with( $existingMenu, Mockery::on(function ($data) use ($newName) { return $data['name'] === $newName && $data['icon'] === 'fas fa-clipboard' && ! array_key_exists('order', $data) && $data['url'] === '/admin/boards/v2'; }) ) ->andReturn(true); $freshMenu = $this->createModelMock(Menu::class, ['id' => 1]); $existingMenu->shouldReceive('fresh')->once()->andReturn($freshMenu); $this->helper->syncMenu( slug: 'board-management', extensionType: ExtensionOwnerType::Module, extensionIdentifier: 'sirsoft-board', newAttributes: [ 'name' => $newName, 'icon' => 'fas fa-clipboard', 'order' => 20, 'url' => '/admin/boards/v2', ], ); } /** * user_overrides에 "url"이 있으면 url 갱신을 건너뛰는지 테스트 */ public function test_sync_menu_skips_user_modified_url(): void { $existingMenu = $this->createModelMock(Menu::class, [ 'id' => 1, 'name' => ['ko' => '게시판', 'en' => 'Board'], 'icon' => 'fas fa-list', 'order' => 10, 'url' => '/admin/custom-boards', 'user_overrides' => ['url'], ]); $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->once() ->andReturn($existingMenu); $newName = ['ko' => 'Board 관리', 'en' => 'Board Admin']; $this->menuRepository ->shouldReceive('update') ->once() ->with( $existingMenu, Mockery::on(function ($data) use ($newName) { return $data['name'] === $newName && $data['icon'] === 'fas fa-clipboard' && $data['order'] === 20 && ! array_key_exists('url', $data); }) ) ->andReturn(true); $freshMenu = $this->createModelMock(Menu::class, ['id' => 1]); $existingMenu->shouldReceive('fresh')->once()->andReturn($freshMenu); $this->helper->syncMenu( slug: 'board-management', extensionType: ExtensionOwnerType::Module, extensionIdentifier: 'sirsoft-board', newAttributes: [ 'name' => $newName, 'icon' => 'fas fa-clipboard', 'order' => 20, 'url' => '/admin/boards/v2', ], ); } /** * user_overrides에 없는 필드만 갱신되는지 테스트 */ public function test_sync_menu_updates_unmodified_fields(): void { $newName = ['ko' => 'Board 관리', 'en' => 'Board Admin']; $existingMenu = $this->createModelMock(Menu::class, [ 'id' => 1, 'name' => ['ko' => '게시판 관리', 'en' => 'Board Management'], 'icon' => 'fas fa-list', 'order' => 10, 'url' => '/admin/boards', 'user_overrides' => [], // 아무것도 수정 안 함 ]); $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->once() ->andReturn($existingMenu); $this->menuRepository ->shouldReceive('update') ->once() ->with( $existingMenu, Mockery::on(fn ($data) => $data['name'] === $newName && $data['icon'] === 'fas fa-clipboard' && $data['order'] === 20 && $data['url'] === '/admin/boards/v2' && $data['parent_id'] === null && $data['is_active'] === true) ) ->andReturn(true); $freshMenu = $this->createModelMock(Menu::class, ['id' => 1]); $existingMenu->shouldReceive('fresh')->once()->andReturn($freshMenu); $this->helper->syncMenu( slug: 'board-management', extensionType: ExtensionOwnerType::Module, extensionIdentifier: 'sirsoft-board', newAttributes: [ 'name' => $newName, 'icon' => 'fas fa-clipboard', 'order' => 20, 'url' => '/admin/boards/v2', ], ); } /** * sync 호출 시 update 시점에 user_overrides.seeding 플래그가 true 로 설정되는지 테스트. * * HasUserOverrides::bootHasUserOverrides 의 updating 이벤트 hook 이 이 플래그를 보고 * 자동 마킹을 건너뛴다. 미설정 시 동일 정의값을 적용해도 trackable 필드 (icon 포함) 가 dirty * 로 잡혀 user_overrides 에 자동 추가되어 이후 sync 가 차단되는 결함이 발생. */ public function test_sync_menu_disables_user_overrides_auto_marking_during_update(): void { $existingMenu = $this->createModelMock(Menu::class, [ 'id' => 1, 'name' => ['ko' => '게시판 관리', 'en' => 'Board Management'], 'icon' => 'fas fa-list', 'order' => 10, 'url' => '/admin/boards', 'user_overrides' => [], ]); $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->once() ->andReturn($existingMenu); $seedingFlagDuringUpdate = null; $this->menuRepository ->shouldReceive('update') ->once() ->andReturnUsing(function () use (&$seedingFlagDuringUpdate) { $seedingFlagDuringUpdate = app()->bound('user_overrides.seeding') && app('user_overrides.seeding') === true; return true; }); $freshMenu = $this->createModelMock(Menu::class, ['id' => 1]); $existingMenu->shouldReceive('fresh')->once()->andReturn($freshMenu); $this->helper->syncMenu( slug: 'board-management', extensionType: ExtensionOwnerType::Module, extensionIdentifier: 'sirsoft-board', newAttributes: [ 'name' => ['ko' => '게시판 관리', 'en' => 'Board Management'], 'icon' => 'fas fa-list', 'order' => 10, 'url' => '/admin/boards', ], ); $this->assertTrue( $seedingFlagDuringUpdate, 'update 시점에 user_overrides.seeding 플래그가 true 여야 자동 마킹 hook 이 skip 된다.' ); $this->assertFalse( app()->bound('user_overrides.seeding'), 'syncMenu 종료 후 seeding 플래그는 컨테이너에서 정리되어야 한다.' ); } /** * parent_id와 is_active는 항상 확장 정의값으로 업데이트되는지 테스트 */ public function test_sync_menu_always_updates_parent_and_active(): void { $name = ['ko' => '게시판 관리', 'en' => 'Board Management']; $existingMenu = $this->createModelMock(Menu::class, [ 'id' => 1, 'name' => ['ko' => '커스텀', 'en' => 'Custom'], 'icon' => 'fas fa-star', 'order' => 99, 'url' => '/admin/custom', 'user_overrides' => ['name', 'icon', 'order', 'url'], // 모든 필드 수정됨 ]); $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->once() ->andReturn($existingMenu); $this->menuRepository ->shouldReceive('update') ->once() ->with( $existingMenu, Mockery::on(function ($data) { // 보호된 필드는 포함되지 않아야 함 $noName = ! array_key_exists('name', $data); $noIcon = ! array_key_exists('icon', $data); $noOrder = ! array_key_exists('order', $data); $noUrl = ! array_key_exists('url', $data); // parent_id와 is_active는 항상 포함 $hasParent = $data['parent_id'] === 5; $hasActive = $data['is_active'] === true; return $noName && $noIcon && $noOrder && $noUrl && $hasParent && $hasActive; }) ) ->andReturn(true); $freshMenu = $this->createModelMock(Menu::class, ['id' => 1]); $existingMenu->shouldReceive('fresh')->once()->andReturn($freshMenu); $this->helper->syncMenu( slug: 'board-management', extensionType: ExtensionOwnerType::Module, extensionIdentifier: 'sirsoft-board', newAttributes: [ 'name' => $name, 'icon' => 'fas fa-list', 'order' => 10, 'url' => '/admin/boards', ], parentId: 5, ); } /** * config 정의의 is_active=false 가 신규 메뉴 생성 시 그대로 적용되는지 테스트 * * 회귀: ExtensionMenuSyncHelper 가 hardcoded `is_active=true` 로 강제하여 개발 중인 * 메뉴(예: config/core.php 의 admin-schedules `is_active: false`) 가 운영 환경에서 * 자동 활성화되던 결함 차단. */ public function test_sync_menu_honors_is_active_false_from_definition_on_create(): void { $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->once() ->andReturn(null); $newMenu = $this->createModelMock(Menu::class, ['id' => 1]); $this->menuRepository ->shouldReceive('updateOrCreate') ->once() ->with( Mockery::any(), Mockery::on(fn ($values) => $values['is_active'] === false) ) ->andReturn($newMenu); $this->helper->syncMenu( slug: 'admin-schedules', extensionType: ExtensionOwnerType::Core, extensionIdentifier: 'core', newAttributes: [ 'name' => ['ko' => '스케쥴 관리', 'en' => 'Schedule Management'], 'icon' => 'fas fa-clock', 'order' => 12, 'url' => '/admin/schedules', 'is_active' => false, ], ); } /** * config 정의의 is_active=false 가 기존 메뉴 업데이트 시에도 적용되는지 테스트 * * 운영자가 user_overrides 로 마킹하지 않았다면 정의값이 우선. */ public function test_sync_menu_honors_is_active_false_from_definition_on_update(): void { $existingMenu = $this->createModelMock(Menu::class, [ 'id' => 1, 'is_active' => true, // 이전 동기화 때 잘못 활성화된 상태 'user_overrides' => [], ]); $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->once() ->andReturn($existingMenu); $this->menuRepository ->shouldReceive('update') ->once() ->with( $existingMenu, Mockery::on(fn ($data) => array_key_exists('is_active', $data) && $data['is_active'] === false) ) ->andReturn(true); $existingMenu->shouldReceive('fresh')->once()->andReturn($existingMenu); $this->helper->syncMenu( slug: 'admin-schedules', extensionType: ExtensionOwnerType::Core, extensionIdentifier: 'core', newAttributes: [ 'name' => ['ko' => '스케쥴 관리', 'en' => 'Schedule Management'], 'icon' => 'fas fa-clock', 'order' => 12, 'url' => '/admin/schedules', 'is_active' => false, ], ); } /** * 운영자가 user_overrides 에 is_active 를 마킹한 경우 정의값이 무시되고 기존 상태가 보존되는지 테스트 * * 시나리오: 정의는 is_active=false 지만 운영자가 UI 에서 활성화 후 user_overrides 에 마킹. * 재시드 시 운영자 선택이 보존되어야 한다. */ public function test_sync_menu_preserves_user_overridden_is_active(): void { $existingMenu = $this->createModelMock(Menu::class, [ 'id' => 1, 'is_active' => true, // 운영자가 활성화 'user_overrides' => ['is_active'], ]); $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->once() ->andReturn($existingMenu); $this->menuRepository ->shouldReceive('update') ->once() ->with( $existingMenu, Mockery::on(fn ($data) => ! array_key_exists('is_active', $data)) ) ->andReturn(true); $existingMenu->shouldReceive('fresh')->once()->andReturn($existingMenu); $this->helper->syncMenu( slug: 'admin-schedules', extensionType: ExtensionOwnerType::Core, extensionIdentifier: 'core', newAttributes: [ 'name' => ['ko' => '스케쥴 관리', 'en' => 'Schedule Management'], 'icon' => 'fas fa-clock', 'order' => 12, 'url' => '/admin/schedules', 'is_active' => false, // 정의는 false 지만 user_overrides 가 우선 ], ); } /** * syncMenuRecursive 가 menuData 의 is_active 를 newAttributes 로 전달하는지 테스트 */ public function test_sync_menu_recursive_passes_is_active_to_sync_menu(): void { $newMenu = $this->createModelMock(Menu::class, ['id' => 1]); $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->once() ->andReturn(null); $this->menuRepository ->shouldReceive('updateOrCreate') ->once() ->with( Mockery::any(), Mockery::on(fn ($values) => $values['is_active'] === false) ) ->andReturn($newMenu); $this->helper->syncMenuRecursive( menuData: [ 'slug' => 'admin-schedules', 'name' => ['ko' => '스케쥴 관리', 'en' => 'Schedule Management'], 'icon' => 'fas fa-clock', 'order' => 12, 'url' => '/admin/schedules', 'is_active' => false, ], extensionType: ExtensionOwnerType::Core, extensionIdentifier: 'core', ); } // ========== syncMenuRecursive 테스트 ========== /** * 다국어 문자열 name을 배열로 역호환 변환하는지 테스트 */ public function test_sync_menu_recursive_converts_string_name_to_array(): void { $newMenu = $this->createModelMock(Menu::class, ['id' => 1]); $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->once() ->andReturn(null); $this->menuRepository ->shouldReceive('updateOrCreate') ->once() ->with( Mockery::any(), Mockery::on(function ($values) { // 문자열 name이 다국어 배열로 변환되었는지 확인 return is_array($values['name']) && $values['name']['ko'] === '게시판 관리' && $values['name']['en'] === '게시판 관리'; }) ) ->andReturn($newMenu); $result = $this->helper->syncMenuRecursive( menuData: [ 'slug' => 'board-management', 'name' => '게시판 관리', // 문자열 name 'icon' => 'fas fa-list', 'order' => 10, ], extensionType: ExtensionOwnerType::Module, extensionIdentifier: 'sirsoft-board', ); $this->assertSame($newMenu, $result); } /** * children 재귀 처리 테스트 */ public function test_sync_menu_recursive_handles_children(): void { $parentMenu = $this->createModelMock(Menu::class, ['id' => 10]); $childMenu = $this->createModelMock(Menu::class, ['id' => 11]); // 부모 메뉴 생성 $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->with('board-management', ExtensionOwnerType::Module, 'sirsoft-board') ->once() ->andReturn(null); $this->menuRepository ->shouldReceive('updateOrCreate') ->once() ->with( Mockery::on(fn ($c) => $c['slug'] === 'board-management'), Mockery::any() ) ->andReturn($parentMenu); // 자식 메뉴 생성 (parent_id = 10) $this->menuRepository ->shouldReceive('findBySlugAndExtension') ->with('board-list', ExtensionOwnerType::Module, 'sirsoft-board') ->once() ->andReturn(null); $this->menuRepository ->shouldReceive('updateOrCreate') ->once() ->with( Mockery::on(fn ($c) => $c['slug'] === 'board-list'), Mockery::on(fn ($v) => $v['parent_id'] === 10) ) ->andReturn($childMenu); $result = $this->helper->syncMenuRecursive( menuData: [ 'slug' => 'board-management', 'name' => ['ko' => '게시판', 'en' => 'Board'], 'icon' => 'fas fa-list', 'order' => 10, 'children' => [ [ 'slug' => 'board-list', 'name' => ['ko' => '게시판 목록', 'en' => 'Board List'], 'url' => '/admin/boards', 'order' => 1, ], ], ], extensionType: ExtensionOwnerType::Module, extensionIdentifier: 'sirsoft-board', ); $this->assertEquals(10, $result->id); } // ========== cleanupStaleMenus 테스트 ========== /** * stale 메뉴가 삭제되는지 테스트 (자식 포함) */ public function test_cleanup_stale_menus_deletes_stale_menus_with_children(): void { // stale 자식 메뉴 $staleChildRoles = Mockery::mock(BelongsToMany::class); $staleChildRoles->shouldReceive('detach')->once(); $staleChild = $this->createModelMock(Menu::class, [ 'id' => 11, 'slug' => 'stale-child', ]); $staleChild->shouldReceive('roles')->once()->andReturn($staleChildRoles); // stale 부모 메뉴 $staleMenuRoles = Mockery::mock(BelongsToMany::class); $staleMenuRoles->shouldReceive('detach')->once(); $staleMenuChildren = new Collection([$staleChild]); $staleMenu = $this->createModelMock(Menu::class, [ 'id' => 10, 'slug' => 'stale-menu', ]); $staleMenu->shouldReceive('roles')->once()->andReturn($staleMenuRoles); $staleMenu->children = $staleMenuChildren; // 현재 유효한 메뉴 $activeMenu = $this->createModelMock(Menu::class, [ 'id' => 1, 'slug' => 'active-menu', ]); $this->menuRepository ->shouldReceive('getMenusByExtension') ->with(ExtensionOwnerType::Module, 'sirsoft-board') ->once() ->andReturn(new Collection([$activeMenu, $staleMenu])); $this->menuRepository ->shouldReceive('delete') ->with($staleChild) ->once() ->andReturn(true); $this->menuRepository ->shouldReceive('delete') ->with($staleMenu) ->once() ->andReturn(true); $deleted = $this->helper->cleanupStaleMenus( ExtensionOwnerType::Module, 'sirsoft-board', ['active-menu'], ); // stale 부모 1 + stale 자식 1 = 2 $this->assertEquals(2, $deleted); } /** * 현재 유효한 메뉴는 보존되는지 테스트 */ public function test_cleanup_stale_menus_preserves_current_menus(): void { $menu1 = $this->createModelMock(Menu::class, ['id' => 1, 'slug' => 'menu-a']); $menu2 = $this->createModelMock(Menu::class, ['id' => 2, 'slug' => 'menu-b']); $this->menuRepository ->shouldReceive('getMenusByExtension') ->once() ->andReturn(new Collection([$menu1, $menu2])); // delete는 호출되지 않아야 함 $this->menuRepository->shouldNotReceive('delete'); $deleted = $this->helper->cleanupStaleMenus( ExtensionOwnerType::Module, 'sirsoft-board', ['menu-a', 'menu-b'], ); $this->assertEquals(0, $deleted); } // ========== collectSlugsRecursive 테스트 ========== /** * 재귀 slug 수집 테스트 */ public function test_collect_slugs_recursive(): void { $menuDataArray = [ [ 'slug' => 'board-management', 'name' => ['ko' => '게시판', 'en' => 'Board'], 'children' => [ [ 'slug' => 'board-list', 'name' => ['ko' => '목록', 'en' => 'List'], ], [ 'slug' => 'board-settings', 'name' => ['ko' => '설정', 'en' => 'Settings'], ], ], ], [ 'slug' => 'product-management', 'name' => ['ko' => '상품', 'en' => 'Product'], ], ]; $slugs = $this->helper->collectSlugsRecursive($menuDataArray); $this->assertCount(4, $slugs); $this->assertContains('board-management', $slugs); $this->assertContains('board-list', $slugs); $this->assertContains('board-settings', $slugs); $this->assertContains('product-management', $slugs); } /** * slug 없는 경우 name에서 slug 추출 테스트 */ public function test_collect_slugs_recursive_falls_back_to_name(): void { $menuDataArray = [ [ 'name' => ['ko' => '게시판', 'en' => 'Board'], // slug 없음 → name 배열의 첫 번째 값 사용 ], ]; $slugs = $this->helper->collectSlugsRecursive($menuDataArray); $this->assertCount(1, $slugs); $this->assertEquals('게시판', $slugs[0]); } /** * 문자열 name에서 slug 추출 (역호환성) */ public function test_collect_slugs_recursive_with_string_name(): void { $menuDataArray = [ [ 'name' => 'Board Management', // slug 없음 → 문자열 name 기반 배열 변환 후 첫 번째 값 ], ]; $slugs = $this->helper->collectSlugsRecursive($menuDataArray); $this->assertCount(1, $slugs); $this->assertEquals('Board Management', $slugs[0]); } }