src/Controller/Admin/AdminDashbordController.php line 386

Open in your IDE?
  1. <?php
  2. namespace App\Controller\Admin;
  3. use App\Entity\BlackList;
  4. use App\Entity\ControllerUser;
  5. use App\Entity\DictionaryImport;
  6. use App\Entity\Eg\Admin\Role;
  7. use App\Entity\Eg\Admin\User;
  8. use App\Entity\Eg\Admin\UserGroup;
  9. use App\Entity\Eg\Osm\City;
  10. use App\Entity\Eg\Osm\DataAggregator;
  11. use App\Entity\Eg\Osm\Device;
  12. use App\Entity\Eg\Osm\Organization;
  13. use App\Entity\Eg\Osm\Parking;
  14. use App\Entity\Eg\Osm\ParkingType;
  15. use App\Entity\Eg\Osm\Zone;
  16. use App\Entity\ElectricList;
  17. use App\Entity\Fine;
  18. use App\Entity\FineVerificationRule;
  19. use App\Entity\LapPair;
  20. use App\Entity\RequestLog;
  21. use App\Entity\Route as RouteEntity;
  22. use App\Entity\RouteLap;
  23. use App\Entity\VerificationStatus;
  24. use App\Menu\FineTabs;
  25. use App\Module\AppModule;
  26. use App\Module\Attribute\RequiresModule;
  27. use App\Security\Scope\FineScopeResolver;
  28. use App\Security\Scope\RequestLogScopeResolver;
  29. use App\Service\ModuleAccessService;
  30. use Doctrine\ORM\EntityManagerInterface;
  31. use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
  32. use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
  33. use EasyCorp\Bundle\EasyAdminBundle\Config\Assets;
  34. use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
  35. use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
  36. use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
  37. use EasyCorp\Bundle\EasyAdminBundle\Config\UserMenu;
  38. use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
  39. use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
  40. use Symfony\Component\HttpFoundation\Request;
  41. use Symfony\Component\HttpFoundation\RequestStack;
  42. use Symfony\Component\HttpFoundation\Response;
  43. use Symfony\Component\Routing\Annotation\Route;
  44. use Symfony\Component\Security\Core\Security;
  45. use Symfony\Component\Security\Core\User\UserInterface;
  46. use Symfony\Contracts\Translation\TranslatorInterface;
  47. class AdminDashbordController extends AbstractDashboardController
  48. {
  49.     protected ?Request $request;
  50.     public function __construct(
  51.         protected Security $security,
  52.         RequestStack $requestStack,
  53.         protected EntityManagerInterface $manager,
  54.         protected ModuleAccessService $moduleAccess,
  55.         protected FineScopeResolver $fineScopeResolver,
  56.         protected RequestLogScopeResolver $requestLogScopeResolver,
  57.         protected TranslatorInterface $translator
  58.     ) {
  59.         $this->request = $requestStack->getCurrentRequest();
  60.     }
  61.     public function configureAssets(): Assets
  62.     {
  63.         return Assets::new()
  64.             ->addWebpackEncoreEntry('app');
  65.     }
  66.     public function configureActions(): Actions
  67.     {
  68.         return Actions::new()
  69.             ->add(Crud::PAGE_INDEX, Action::EDIT)
  70.             ->add(Crud::PAGE_INDEX, Action::DETAIL)
  71.             ->add(Crud::PAGE_DETAIL, Action::INDEX)
  72.             ->add(Crud::PAGE_EDIT, Action::INDEX)
  73.             ->add(Crud::PAGE_NEW, Action::INDEX)
  74.             ->update(Crud::PAGE_INDEX, Action::DETAIL, fn (Action $action): Action => $action->setLabel('Pokaż')->setIcon('far fa-eye'))
  75.             ->update(Crud::PAGE_INDEX, Action::EDIT, fn (Action $action): Action => $action->setLabel('Edytuj')->setIcon('fas fa-pencil-alt'))
  76.             ->update(Crud::PAGE_DETAIL, Action::INDEX, fn (Action $action): Action => $action->setLabel('Wróć do listy')->setIcon('fas fa-list'))
  77.             ->update(Crud::PAGE_EDIT, Action::INDEX, fn (Action $action): Action => $action->setLabel('Wróć do listy')->setIcon('fas fa-list'))
  78.             ->update(Crud::PAGE_NEW, Action::INDEX, fn (Action $action): Action => $action->setLabel('Wróć do listy')->setIcon('fas fa-list'))
  79.             ->add(Crud::PAGE_EDIT, Action::SAVE_AND_CONTINUE);
  80.     }
  81.     public function configureCrud(): Crud
  82.     {
  83.         $crud = Crud::new()
  84.             ->setDefaultSort(['id' => 'DESC'])
  85.             ->setDateTimeFormat('y-MM-dd HH:mm')
  86.             ->overrideTemplate('crud/index', 'index.html.twig')
  87.             ->overrideTemplate('crud/detail', 'detail.html.twig')
  88.             ->overrideTemplate('crud/new', 'new.html.twig')
  89.             ->overrideTemplate('crud/edit', 'edit.html.twig')
  90.             ->overrideTemplate('crud/field/association', 'fields/association_edit.html.twig')
  91.             ->overrideTemplate('layout', 'layout.html.twig')
  92.             ->setPageTitle('detail', '%entity_label_singular% #%entity_id%')
  93.             ->setFormThemes(['form.html.twig', '@EasyAdmin/crud/form_theme.html.twig'])
  94.             ->setSearchFields(null)
  95.             ->showEntityActionsInlined();
  96.         if ($this->request->query->has('indexPageTitle')) {
  97.             $crud->setPageTitle(Crud::PAGE_INDEX, $this->request->query->get('indexPageTitle'));
  98.         }
  99.         return $crud;
  100.     }
  101.     /**
  102.      * Funkcja odpowiadająca za elementy znajdujące się w wyświetlanym menu
  103.      */
  104.     public function configureMenuItems(): iterable
  105.     {
  106.         $menuItems = [MenuItem::linkToDashboard('Pulpit', 'fa fa-home')];
  107.         if ($this->moduleAccess->isEnabled(AppModule::DATA)) {
  108.             $dataItems = [];
  109.             $requestLogLinks = [];
  110.             if ($this->requestLogScopeResolver->canSeeAllTab()) {
  111.                 $requestLogLinks[] = MenuItem::linkToCrud('Wszystkie', null, RequestLog::class);
  112.             }
  113.             if ($this->requestLogScopeResolver->can(RequestLogScopeResolver::VERB_VIEW, RequestLogScopeResolver::SCOPE_UNVERIFIED)) {
  114.                 $requestLogLinks[] = MenuItem::linkToCrud('Niezweryfikowane', null, RequestLog::class)
  115.                     ->setQueryParameter('isVerified', false)
  116.                     ->setQueryParameter('indexPageTitle', RequestLogCrudController::INDEX_PAGE_TITLES['unverified']);
  117.             }
  118.             if ($this->requestLogScopeResolver->can(RequestLogScopeResolver::VERB_VIEW, RequestLogScopeResolver::SCOPE_VERIFIED)) {
  119.                 $requestLogLinks[] = MenuItem::linkToCrud('Zweryfikowane', null, RequestLog::class)
  120.                     ->setQueryParameter('isVerified', true)
  121.                     ->setQueryParameter('indexPageTitle', RequestLogCrudController::INDEX_PAGE_TITLES['verified']);
  122.             }
  123.             if (\count($requestLogLinks) > 0) {
  124.                 $dataItems[] = MenuItem::subMenu('Pojedyncze odczyty', 'fas fa-camera')->setSubItems($requestLogLinks);
  125.             }
  126.             if ($this->isGranted(Role::ROLE_LAPS_VIEW)) {
  127.                 $dataItems[] = MenuItem::linkToCrud('Przejazdy', 'fas fa-map-marker-alt', RouteLap::class);
  128.             }
  129.             if ($this->isGranted(Role::ROLE_LAPS_PAIRS_VIEW)) {
  130.                 $dataItems[] = MenuItem::linkToCrud('Pary przejazdów', 'fas fa-route', LapPair::class);
  131.             }
  132.             // widoczność zakładek zawiadomień pochodzą w całości z FineTabs
  133.             $fineLinks = [];
  134.             foreach (FineTabs::visibleFor($this->fineScopeResolver) as $tab) {
  135.                 $link = MenuItem::linkToCrud($tab['label'], null, Fine::class);
  136.                 if ($tab['status'] !== null) {
  137.                     $link
  138.                         ->setQueryParameter('verificationStatus', $tab['status'])
  139.                         ->setQueryParameter('indexPageTitle', $tab['title']);
  140.                 }
  141.                 $fineLinks[] = $link;
  142.             }
  143.             if (\count($fineLinks) > 0) {
  144.                 $dataItems[] = MenuItem::subMenu('Zawiadomienia', 'fas fa-comment-dollar')->setSubItems($fineLinks);
  145.             }
  146.             // nagłówek sekcji tylko wtedy, gdy cokolwiek pod nim jest
  147.             if (\count($dataItems) > 0) {
  148.                 $menuItems[] = MenuItem::section('Dane');
  149.                 $menuItems = \array_merge($menuItems, $dataItems);
  150.             }
  151.         }
  152.         if ($this->moduleAccess->isEnabled(AppModule::PARKING)) {
  153.             $parkingItems = [
  154.                 [Role::ROLE_ORG_VIEW, 'Organizacje', 'fa fa-building', Organization::class],
  155.                 [Role::ROLE_CITY_VIEW, 'Miasta', 'fa fa-city', City::class],
  156.                 [Role::ROLE_ZONES_VIEW, 'Strefy', 'fa fa-draw-polygon', Zone::class],
  157.                 [Role::ROLE_AGG_VIEW, 'Agregatory', 'fa fa-road', DataAggregator::class],
  158.                 [Role::ROLE_PARK_TYPE_VIEW, 'Typy parkingów', 'fa fa-wheelchair', ParkingType::class],
  159.                 [Role::ROLE_PARKING_VIEW, 'Parkingi', 'fa fa-parking', Parking::class],
  160.             ];
  161.             $visibleParkingItems = [];
  162.             foreach ($parkingItems as [$role, $label, $icon, $entityFqcn]) {
  163.                 if ($this->isGranted($role)) {
  164.                     $visibleParkingItems[] = MenuItem::linkToCrud($label, $icon, $entityFqcn);
  165.                 }
  166.             }
  167.             if (\count($visibleParkingItems) > 0) {
  168.                 $menuItems[] = MenuItem::section('Strefy i Parkingi');
  169.                 $menuItems = \array_merge($menuItems, $visibleParkingItems);
  170.             }
  171.         }
  172.         if ($this->moduleAccess->isEnabled(AppModule::ROUTES) && $this->isGranted(Role::ROLE_ROUTES_VIEW)) {
  173.             $menuItems[] = MenuItem::section('Trasy');
  174.             $menuItems[] = MenuItem::linkToCrud('Zarządzanie trasami', 'fas fa-road', RouteEntity::class);
  175.         }
  176.         if ($this->moduleAccess->isEnabled(AppModule::MAP) && $this->isGranted(Role::ROLE_MAP_VIEW)) {
  177.             $menuItems[] = MenuItem::section('');
  178.             $menuItems[] = MenuItem::linkToRoute('Mapa', 'fa fa-map', 'map');
  179.         }
  180.         if ($this->moduleAccess->isEnabled(AppModule::STATISTICS)) {
  181.             $statsItems = [];
  182.             if ($this->isGranted(Role::ROLE_STATS_VIEW)) {
  183.                 $statsItems[] = MenuItem::linktoRoute('Statystyki', 'fa fa-chart-bar', 'statistics');
  184.             }
  185.             if ($this->isGranted(Role::ROLE_USER_STATS_VIEW)) {
  186.                 $statsItems[] = MenuItem::linktoRoute('Zestawienia zawiadomień', 'fa fa-chart-bar', 'fine_statements');
  187.                 $statsItems[] = MenuItem::linktoRoute('Zestawienia odczytów', 'fa fa-chart-bar', 'request_log_statements');
  188.             }
  189.             if ($this->isGranted(Role::ROLE_STATS_VIEW)) {
  190.                 $statsItems[] = MenuItem::linktoRoute('Zestawienia eksportów', 'fa fa-chart-bar', 'log_exports_statements');
  191.             }
  192.             if (\count($statsItems) > 0) {
  193.                 $menuItems[] = MenuItem::section('Statystyki i zestawienia');
  194.                 $menuItems = [...$menuItems, ...$statsItems];
  195.             }
  196.         }
  197.         if ($this->moduleAccess->isEnabled(AppModule::CONFIG)) {
  198.             $canSeeSettings = $this->isGranted(Role::ROLE_CONFIG_VIEW);
  199.             $canSeeJsonConfig = $this->isGranted(Role::ROLE_CONFIG_CREATOR);
  200.             if ($canSeeSettings || $canSeeJsonConfig) {
  201.                 $menuItems[] = MenuItem::section('Konfiguracja');
  202.             }
  203.             if ($canSeeSettings) {
  204.                 $menuItems[] = MenuItem::linkToRoute('Ustawienia', 'fas fa-sliders-h', 'config_settings_index');
  205.             }
  206.             if ($canSeeJsonConfig) {
  207.                 $menuItems[] = MenuItem::linkToRoute(
  208.                     $this->translator->trans('config.json.menu_label', [], 'config'),
  209.                     'fas fa-file-code',
  210.                     'config_json_index',
  211.                 );
  212.             }
  213.         }
  214.         // nagłówek sekcji "Administracja" tylko raz i tylko gdy pod nim faktycznie pojawi się jakaś pozycja
  215.         // brak pustej sekcji, gdy użytkownik nie ma żadnej z ról administracyjnych
  216.         $adminSectionShown = false;
  217.         $showAdminSection = function () use (&$menuItems, &$adminSectionShown): void {
  218.             if (!$adminSectionShown) {
  219.                 $menuItems[] = MenuItem::section('Administracja');
  220.                 $adminSectionShown = true;
  221.             }
  222.         };
  223.         if ($this->moduleAccess->isEnabled(AppModule::ADMIN)) {
  224.             if ($this->isGranted(Role::ROLE_USERS_VIEW)) {
  225.                 $showAdminSection();
  226.                 $menuItems[] = MenuItem::linkToCrud('Użytkownicy', 'fas fa-user', User::class);
  227.             }
  228.             if ($this->isGranted(Role::ROLE_CONTROLLERS_VIEW)) {
  229.                 $showAdminSection();
  230.                 $menuItems[] = MenuItem::linkToCrud('Kontrolerzy', 'fas fa-mobile-alt', ControllerUser::class);
  231.             }
  232.             if ($this->isGranted(Role::ROLE_R_GROUPS_VIEW)) {
  233.                 $showAdminSection();
  234.                 $menuItems[] = MenuItem::linkToCrud('Grupy Uprawnień', 'fas fa-users-cog', UserGroup::class);
  235.             }
  236.             if ($this->isGranted(Role::ROLE_IMPORT_VIEW)) {
  237.                 $showAdminSection();
  238.                 $menuItems[] = MenuItem::linkToCrud('Import danych', 'fas fa-file-import', DictionaryImport::class);
  239.             }
  240.             if ($this->isGranted(Role::ROLE_CONTROL_BOX_VIEW)) {
  241.                 $showAdminSection();
  242.                 $menuItems[] = MenuItem::linkToCrud('Zestawy kontrolerskie', 'fa fa-video-camera', Device::class);
  243.             }
  244.         }
  245.         if ($this->moduleAccess->isEnabled(AppModule::ELECTRIC) && $this->isGranted(Role::ROLE_ELECTRIC_VIEW)) {
  246.             $showAdminSection();
  247.             $menuItems[] = MenuItem::linkToCrud('Lista samochodów elektrycznych', 'fas fa-charging-station', ElectricList::class);
  248.         }
  249.         if ($this->moduleAccess->isEnabled(AppModule::BLACKLIST) && $this->isGranted(Role::ROLE_BLACK_LIST_VIEW)) {
  250.             $showAdminSection();
  251.             $menuItems[] = MenuItem::linkToCrud('Czarna lista rejestracji', 'fas fa-ban', BlackList::class);
  252.         }
  253.         // reguły klasyfikacji zawiadomień - moduł danych, bo bez zawiadomień nie mają czego klasyfikować
  254.         if ($this->moduleAccess->isEnabled(AppModule::DATA) && $this->isGranted(Role::ROLE_FINE_VERIFICATION_RULES_VIEW)) {
  255.             $showAdminSection();
  256.             $menuItems[] = MenuItem::linkToCrud(
  257.                 $this->translator->trans('fine_verification_rule.menu_label', [], 'fine_verification_rule'),
  258.                 'fas fa-code-branch',
  259.                 FineVerificationRule::class
  260.             );
  261.         }
  262.         return $menuItems;
  263.     }
  264.     public function configureUserMenu(UserInterface $user): UserMenu
  265.     {
  266.         $url = $this->container->get(AdminUrlGenerator::class)
  267.             ->setAction('changePassword')
  268.             ->setController(UserCrudController::class)
  269.             ->setEntityId($user->getId())
  270.             ->set('referrer', urlencode($this->request->getRequestUri()))
  271.             ->generateUrl();
  272.         return parent::configureUserMenu($user)
  273.             ->addMenuItems([
  274.                 MenuItem::linkToUrl('Zmień hasło', null, $url)
  275.             ]);
  276.     }
  277.     /**
  278.      * Metoda do konfiguracji widoku pulpitu
  279.      */
  280.     public function configureDashboard(): Dashboard
  281.     {
  282.         return Dashboard::new()
  283.             ->setTitle('Admin kamery')
  284.             ->setFaviconPath('image/logo/favicon.png')
  285.             ->disableDarkMode();
  286.     }
  287.     #[Route(path: '/admin', name: 'admin')]
  288.     public function index(): Response
  289.     {
  290.         $requestLogIndexes = $this->determineMenuIndex('Pojedyncze odczyty');
  291.         $routeLapIndexes = $this->determineMenuIndex('Przejazdy');
  292.         $lapPairIndexes = $this->determineMenuIndex('Pary przejazdów');
  293.         $fineTab = $this->defaultFineTab();
  294.         $fineIndexes = $this->determineMenuIndex('Zawiadomienia', $fineTab['label'] ?? null);
  295.         $requestLogUrl = $this->container->get(AdminUrlGenerator::class)
  296.             ->setAction('index')
  297.             ->setController(RequestLogCrudController::class)
  298.             ->generateUrl();
  299.         $routeLapUrl = $this->container->get(AdminUrlGenerator::class)
  300.             ->setAction('index')
  301.             ->setController(RouteLapCrudController::class)
  302.             ->generateUrl();
  303.         $lapPairUrl = $this->container->get(AdminUrlGenerator::class)
  304.             ->setAction('index')
  305.             ->setController(LapPairCrudController::class)
  306.             ->generateUrl();
  307.         $fineUrlBuilder = $this->container->get(AdminUrlGenerator::class)
  308.             ->setAction('index')
  309.             ->setController(FineCrudController::class)
  310.             ->set('menuIndex', $fineIndexes['menu'])
  311.             ->set('submenuIndex', $fineIndexes['submenu']);
  312.         // zakladka "Wszystkie" nie filtruje po statusie, wiec nie dokladamy jej parametrow
  313.         if ($fineTab !== null && $fineTab['status'] !== null) {
  314.             $fineUrlBuilder
  315.                 ->set('verificationStatus', $fineTab['status'])
  316.                 ->set('indexPageTitle', $fineTab['title']);
  317.         }
  318.         $fineUrl = $fineUrlBuilder->generateUrl();
  319.         $mapUrl = $this->redirectToRoute('map')->getTargetUrl();
  320.         return $this->render('start.html.twig', [
  321.             'requestLogUrl' => $requestLogUrl,
  322.             'routeLapUrl' => $routeLapUrl,
  323.             'lapPairUrl' => $lapPairUrl,
  324.             'fineUrl' => $fineUrl,
  325.             'mapUrl' => $mapUrl,
  326.             'serverData' => json_encode($_SERVER)
  327.         ]);
  328.     }
  329.     #[Route(path: '/', name: 'blank')]
  330.     public function blank(): Response
  331.     {
  332.         return $this->redirectToRoute('admin');
  333.     }
  334.     #[Route(path: '/admin/map', name: 'map')]
  335.     #[RequiresModule(AppModule::MAP)]
  336.     public function showMap(): Response
  337.     {
  338.         $this->denyAccessUnlessGranted(Role::ROLE_MAP_VIEW);
  339.         // URL do edycji pojedynczych odczytów
  340.         // Aby podmienić wystarczy złapać parametr 'entityId=0' i podmienić go na odpowiednią wartość
  341.         $url = $this->container->get(AdminUrlGenerator::class)
  342.             ->setAction(Crud::PAGE_EDIT)
  343.             ->setController(RequestLogCrudController::class)
  344.             ->setEntityId(0)
  345.             ->set('referrer', '/admin/map')
  346.             ->generateUrl();
  347.         return $this->render('map.html.twig', [
  348.             'requestLogURL' => $url
  349.         ]);
  350.     }
  351.     #[Route(path: '/admin/statistics', name: 'statistics')]
  352.     #[RequiresModule(AppModule::STATISTICS)]
  353.     public function renderStatistics(): Response
  354.     {
  355.         $this->denyAccessUnlessGranted(Role::ROLE_STATS_VIEW);
  356.         return $this->render('stats/statistics.html.twig');
  357.     }
  358.     #[Route(path: '/admin/fine-statements', name: 'fine_statements')]
  359.     #[RequiresModule(AppModule::STATISTICS)]
  360.     public function renderFineStatements(): Response
  361.     {
  362.         $this->denyAccessUnlessGranted(Role::ROLE_USER_STATS_VIEW);
  363.         return $this->render('stats/fine_statements.html.twig', [
  364.             'selectableUsers' => $this->manager->getRepository(User::class)->getAllUsernames(true)
  365.         ]);
  366.     }
  367.     #[Route(path: '/admin/request-log-statements', name: 'request_log_statements')]
  368.     #[RequiresModule(AppModule::STATISTICS)]
  369.     public function renderRequestLogStatements(): Response
  370.     {
  371.         $this->denyAccessUnlessGranted(Role::ROLE_USER_STATS_VIEW);
  372.         return $this->render('stats/request_log_statements.html.twig', [
  373.             'selectableUsers' => $this->manager->getRepository(User::class)->getAllUsernames(true)
  374.         ]);
  375.     }
  376.     #[Route(path: '/admin/log-exports-statements', name: 'log_exports_statements')]
  377.     #[RequiresModule(AppModule::STATISTICS)]
  378.     public function renderLogExportsStatements(): Response
  379.     {
  380.         $this->denyAccessUnlessGranted(Role::ROLE_STATS_VIEW);
  381.         return $this->render('stats/log_exports_statements.html.twig');
  382.     }
  383.     /**
  384.      * Zakładka zawiadomień, na którą prowadzi kafelek "Zawiadomienia" na pulpicie
  385.      *
  386.      * "Do weryfikacji" zostaje domyślna, ale rola o wąskim zakresie jej nie widzi - wtedy kafelek
  387.      * prowadzi na pierwszą zakładkę dostępną dla konta, zamiast na komunikat o braku uprawnień
  388.      *
  389.      * @return array{status: int|null, label: string, title: string}|null
  390.      */
  391.     private function defaultFineTab(): ?array
  392.     {
  393.         $tabs = FineTabs::visibleFor($this->fineScopeResolver);
  394.         foreach ($tabs as $tab) {
  395.             if ($tab['status'] === VerificationStatus::TO_VERIFY) {
  396.                 return $tab;
  397.             }
  398.         }
  399.         return $tabs[0] ?? null;
  400.     }
  401.     /**
  402.      * Metoda służąca do obliczania indexu danej encji w menu
  403.      */
  404.     private function determineMenuIndex(string $entityLabel, ?string $subEntityLabel = null)
  405.     {
  406.         $indexes = [
  407.             'menu' => -1,
  408.             'submenu' => -1
  409.         ];
  410.         foreach ($this->configureMenuItems() as $index => $item) {
  411.             if ($item->getAsDto()->getLabel() == $entityLabel) {
  412.                 $indexes['menu'] = $index;
  413.                 if ($subEntityLabel) {
  414.                     foreach ($item->getAsDto()->getSubItems() as $subIndex => $subItem) {
  415.                         if ($subItem->getLabel() == $subEntityLabel) {
  416.                             $indexes['submenu'] = $subIndex;
  417.                         }
  418.                     }
  419.                 }
  420.             }
  421.         }
  422.         return $indexes;
  423.     }
  424. }