router = $router; $this->menuFactory = $menuFactory; $this->translator = $translator; } public function addLocalMenuBuilder(LocalMenuBuilderInterface $menuBuilder, $menuId) { $this->localMenuBuilders[$menuId][] = $menuBuilder; } public function getMenuFor($menuId, array $parameters = []) { $routes = $this->getRoutesFor($menuId, $parameters); $menu = $this->menuFactory->createItem($menuId); // build menu from routes foreach ($routes as $order => $route) { $menu->addChild($this->translator->trans($route['label']), [ 'route' => $route['key'], 'routeParameters' => $parameters['args'], 'order' => $order, ]) ->setExtras([ //'icon' => $route['icon'], // sf4 check: commented to avoid error: `An exception has been thrown during the rendering of a template ("Notice: Undefined index: icon").` 'order' => $order, ]); } if ($this->hasLocalMenuBuilder($menuId)) { foreach ($this->localMenuBuilders[$menuId] as $builder) { /* @var $builder LocalMenuBuilderInterface */ $builder->buildMenu($menuId, $menu, $parameters['args']); } } $this->reorderMenu($menu); return $menu; } /** * Return an array of routes added to $menuId, * The array is aimed to build route with MenuTwig. * * @param string $menuId * @param array $parameters see https://redmine.champs-libres.coop/issues/179 * * @return array */ public function getRoutesFor($menuId, array $parameters = []) { $routes = []; $routeCollection = $this->router->getRouteCollection(); foreach ($routeCollection->all() as $routeKey => $route) { if ($route->hasOption('menus')) { if (array_key_exists($menuId, $route->getOption('menus'))) { $route = $route->getOption('menus')[$menuId]; $route['key'] = $routeKey; $order = $this->resolveOrder($routes, $route['order']); //we do not want to duplicate order information unset($route['order']); $routes[$order] = $route; } } } ksort($routes); return $routes; } /** * Return true if the menu has at least one builder. * * This function is a helper to determine if the method `getMenuFor` * should be used, or `getRouteFor`. The method `getMenuFor` should be used * if the result is true (it **does** exists at least one menu builder. * * @param string $menuId */ public function hasLocalMenuBuilder($menuId): bool { return array_key_exists($menuId, $this->localMenuBuilders); } /** * Set the route Collection * This function is needed for testing purpose: routeCollection is not * available as a service (RouterInterface is provided as a service and * added to this class as paramater in __construct). */ public function setRouteCollection(RouteCollection $routeCollection) { $this->routeCollection = $routeCollection; } private function reorderMenu(ItemInterface $menu) { $ordered = []; $unordered = []; foreach ($menu->getChildren() as $name => $item) { $order = $item->getExtra('order'); if (null !== $order) { $ordered[$this->resolveOrder($ordered, $order)] = $name; } else { $unordered = $name; } } ksort($ordered); $menus = array_merge(array_values($ordered), $unordered); $menu->reorderChildren($menus); } /** * recursive function to resolve the order of a array of routes. * If the order chosen in routing.yml is already in used, find the * first next order available. * * @param array $routes the routes previously added * @param int $order * * @return int */ private function resolveOrder($routes, $order) { if (isset($routes[$order])) { return $this->resolveOrder($routes, $order + 1); } return $order; } }