Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.15% covered (success)
93.15%
68 / 73
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
MainMenuBuilder
93.15% covered (success)
93.15%
68 / 73
75.00% covered (warning)
75.00%
3 / 4
17.09
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 buildMenu
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
4
 buildMenuForBoundedContext
100.00% covered (success)
100.00%
40 / 40
100.00% covered (success)
100.00%
1 / 1
7
 createUrlList
61.54% covered (warning)
61.54%
8 / 13
0.00% covered (danger)
0.00%
0 / 1
6.42
1<?php
2namespace Apie\Cms\MenuStructure;
3
4use Apie\Cms\IconResolver;
5use Apie\Cms\RouteDefinitions\AbstractCmsRouteDefinition;
6use Apie\Cms\RouteDefinitions\CmsRouteDefinitionProvider;
7use Apie\Common\MenuStructure\MenuBuilder;
8use Apie\Common\MenuStructure\MenuNode;
9use Apie\Core\BoundedContext\BoundedContext;
10use Apie\Core\BoundedContext\BoundedContextHashmap;
11use Apie\Core\BoundedContext\BoundedContextId;
12use Apie\Core\Context\ApieContext;
13use Apie\Core\ContextConstants;
14use Apie\Core\Enums\RequestMethod;
15use Apie\Core\Translator\ValueObjects\MenuHeader;
16use Apie\Core\ValueObjects\UrlRouteDefinition;
17use Apie\HtmlBuilders\Configuration\ApplicationConfiguration;
18
19class MainMenuBuilder
20{
21    public function __construct(
22        private readonly CmsRouteDefinitionProvider $routeDefinitionProvider,
23        private readonly BoundedContextHashmap $boundedContextHashmap,
24        private readonly ApplicationConfiguration $applicationConfiguration,
25        private readonly IconResolver $iconResolver
26    ) {
27    }
28
29    public function buildMenu(
30        ApieContext $apieContext,
31        ?BoundedContextId $prio = null
32    ): MenuNode {
33        $menu = new MenuNode(
34            name: MenuHeader::createRoot($apieContext),
35            route: null,
36            icon: null,
37        );
38        $children = $menu->children;
39        $prioBoundedContext = $this->boundedContextHashmap[$prio?->toNative()] ?? null;
40        if ($prioBoundedContext) {
41            $subcontext = $apieContext->withContext(ContextConstants::BOUNDED_CONTEXT_ID, $prio?->toNative())
42                ->registerInstance($prioBoundedContext);
43            $children[$prio?->toNative()] = $this->buildMenuForBoundedContext($prioBoundedContext, $subcontext);
44        }
45        foreach ($this->boundedContextHashmap as $key => $boundedContext) {
46            if ($key === $prio?->toNative()) {
47                continue;
48            }
49            $subcontext = $apieContext->withContext(ContextConstants::BOUNDED_CONTEXT_ID, $key)
50                ->registerInstance($boundedContext);
51            $children[$key] = $this->buildMenuForBoundedContext($boundedContext, $subcontext);
52        }
53        $menu->children = $children;
54        return $menu->prune();
55    }
56
57    public function buildMenuForBoundedContext(BoundedContext $boundedContext, ApieContext $apieContext): MenuNode
58    {
59        $prefix = $boundedContext->getId()->toNative() . '.';
60        $menuBuilder = new MenuBuilder($prefix);
61        $apieContext = $apieContext->withContext(
62            ContextConstants::MAIN_MENU_BUILDER,
63            1
64        )->registerInstance($this);
65        $routeDefinitions = $this->routeDefinitionProvider->getActionsForBoundedContext(
66            $boundedContext,
67            $apieContext,
68        );
69        $configuration = $this->applicationConfiguration->createConfiguration(
70            $apieContext,
71            $this->boundedContextHashmap,
72            $boundedContext->getId()
73        );
74
75        /** @var AbstractCmsRouteDefinition $routeDefinition */
76        foreach ($routeDefinitions as $routeDefinition) {
77            $url = $routeDefinition->getMainMenuUri();
78            if (!$url || !in_array($routeDefinition->getMethod(), [RequestMethod::ANY, RequestMethod::GET])) {
79                continue;
80            }
81            $action = $routeDefinition->getAction();
82            $subcontext = $apieContext->withMultipleContext($routeDefinition->getRouteAttributes());
83            $allowed = $action::isAuthorized($subcontext, true);
84            $resourceName = $subcontext->getContext(ContextConstants::RESOURCE_NAME, false);
85            $icon = null;
86            if ($resourceName && class_exists($resourceName)) {
87                $icon = $this->iconResolver->resolve($resourceName);
88            }
89            foreach ($this->createUrlList($boundedContext, $url) as $createdUrl) {
90                $createdList = $createdUrl->toStringList();
91                // $apieContext is deliberate choice as we want to be able to change translations per bounded context
92                $menuBuilder->addLeaf($createdList, new MenuNode(
93                    name: MenuHeader::createRoot($apieContext, $boundedContext->getId() . '.' . $createdList->join('.')),
94                    route: $configuration->getContextUrl($routeDefinition->getUrl()->toNative()),
95                    icon: $icon,
96                    allowed: $allowed
97                ));
98            }
99        }
100        $root = $menuBuilder->getRoot();
101        $root->allowed = array_any($root->children->toArray(), function (MenuNode $node) {
102            return $node->allowed;
103        });
104        $root->icon = $this->iconResolver->resolve($boundedContext->getId());
105        return $menuBuilder->getRoot();
106    }
107
108    /**
109     * @return UrlRouteDefinition[]
110     */
111    private function createUrlList(BoundedContext $boundedContext, UrlRouteDefinition $url): array
112    {
113        $placeholders = $url->getPlaceholders();
114        $urlList = [];
115        $replacements = [
116            'extension' => 'csv',
117        ];
118        foreach ($placeholders as $placeholder) {
119            if ($placeholder === ContextConstants::RESOURCE_NAME) {
120                foreach ($boundedContext->resources as $resource) {
121                    $urlList[$boundedContext->getId() . '.' . $resource->getShortName()] = $url->withFilledInPlaceholders([
122                        ContextConstants::RESOURCE_NAME => $resource->getShortName(),
123                        ...$replacements,
124                    ]);
125                }
126            }
127        }
128        return $urlList ? : ['' => $url];
129    }
130}