Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.83% covered (success)
95.83%
46 / 48
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
AccessWebController
95.74% covered (success)
95.74%
45 / 47
75.00% covered (warning)
75.00%
3 / 4
11
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
 index
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
4
 resolveUsersForScope
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
6.00
 rulesDrawer
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Core\Access\Presentation\Web;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Access\Application\Service\AccessManagerServiceInterface;
12use App\Core\Engine\Application\Security\PermissionContextFactory;
13use App\Core\Instance\Application\Service\InstanceContextManagerInterface;
14use App\Modules\Structure\Domain\Repository\StructureRepositoryInterface;
15use App\Modules\User\Domain\Repository\UserRepositoryInterface;
16use App\Modules\User\Infrastructure\Repository\SqlUserRepository;
17use Psr\Http\Message\ResponseFactoryInterface;
18use Psr\Http\Message\ResponseInterface;
19use Psr\Http\Message\ServerRequestInterface;
20use Throwable;
21use Twig\Environment;
22
23/**
24 * Access Management Web Controller.
25 *
26 * Renders full interactive management console for module permissions matrix and sharing rules.
27 *
28 * @package App\Core\Access\Presentation\Web
29 */
30final readonly class AccessWebController
31{
32    /**
33     * AccessWebController constructor.
34     *
35     * @param AccessManagerServiceInterface   $accessManager   Access manager service.
36     * @param InstanceContextManagerInterface $instanceManager Instance context manager.
37     * @param StructureRepositoryInterface    $structureRepo   Organizational structure repo.
38     * @param UserRepositoryInterface         $userRepo        User repository.
39     * @param PermissionContextFactory        $contextFactory  Security context factory.
40     * @param Environment                     $twig            Twig template engine.
41     * @param ResponseFactoryInterface        $responseFactory PSR-7 response factory.
42     */
43    public function __construct(
44        private AccessManagerServiceInterface $accessManager,
45        private InstanceContextManagerInterface $instanceManager,
46        private StructureRepositoryInterface $structureRepo,
47        private UserRepositoryInterface $userRepo,
48        private PermissionContextFactory $contextFactory,
49        private Environment $twig,
50        private ResponseFactoryInterface $responseFactory
51    ) {
52    }
53
54    /**
55     * Renders main Access Management matrix dashboard.
56     *
57     * @param ServerRequestInterface $request HTTP request.
58     * @return ResponseInterface Rendered HTML response.
59     */
60    public function index(ServerRequestInterface $request): ResponseInterface
61    {
62        $context = $this->contextFactory->createFromRequest($request);
63        if (!$context->isSuperuser) {
64            $resp = $this->responseFactory->createResponse(403);
65            $resp->getBody()->write('Forbidden');
66            return $resp;
67        }
68
69        $path = $request->getUri()->getPath();
70        $params = $request->getQueryParams();
71        $scope = (string) ($params['scope'] ?? '');
72
73        if ($scope === '') {
74            $scope = str_ends_with($path, '/client') ? 'client' : 'admin';
75        }
76
77        $matrix = $this->accessManager->getModuleMatrix($scope);
78        $structures = $this->structureRepo->findAllActive();
79        $users = $this->resolveUsersForScope($scope);
80
81        $html = $this->twig->render('access/index.twig', [
82            'matrix'     => $matrix,
83            'structures' => $structures,
84            'users'      => $users,
85            'context'    => $context,
86            'scope'      => $scope,
87            'is_remote'  => $this->instanceManager->isRemote(),
88        ]);
89
90        $response = $this->responseFactory->createResponse(200);
91        $response->getBody()->write($html);
92
93        return $response->withHeader('Content-Type', 'text/html; charset=UTF-8');
94    }
95
96    /**
97     * Resolves appropriate users list depending on active scope.
98     *
99     * @param string $scope Active scope ('admin' or 'client').
100     * @return list<array<string, mixed>> List of matching users.
101     */
102    private function resolveUsersForScope(string $scope): array
103    {
104        if ($scope === 'client' && $this->userRepo instanceof SqlUserRepository) {
105            try {
106                return $this->userRepo->forPrefix('c_')->searchAutocomplete('', 1000);
107            } catch (Throwable) {
108                // fallback to default user repository
109            }
110        }
111
112        return $this->userRepo->searchAutocomplete('', 1000);
113    }
114
115    /**
116     * Renders HTMX partial drawer for module rules.
117     *
118     * @param ServerRequestInterface $request HTTP request.
119     * @param string                 $moduleName Target module name.
120     * @return ResponseInterface Partial HTML response.
121     */
122    public function rulesDrawer(ServerRequestInterface $request, string $moduleName): ResponseInterface
123    {
124        $context = $this->contextFactory->createFromRequest($request);
125        if (!$context->isSuperuser) {
126            $resp = $this->responseFactory->createResponse(403);
127            $resp->getBody()->write('Forbidden');
128            return $resp;
129        }
130
131        $rules = $this->accessManager->getModuleRules($moduleName);
132        $structures = $this->structureRepo->findAllActive();
133        $users = $this->userRepo->searchAutocomplete('', 1000);
134
135        $html = $this->twig->render('access/partials/rules_drawer.twig', [
136            'module_name' => $moduleName,
137            'rules'       => $rules,
138            'structures'  => $structures,
139            'users'       => $users,
140            'is_remote'   => $this->instanceManager->isRemote(),
141        ]);
142
143        $response = $this->responseFactory->createResponse(200);
144        $response->getBody()->write($html);
145
146        return $response->withHeader('Content-Type', 'text/html; charset=UTF-8');
147    }
148}