Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.67% covered (success)
93.67%
74 / 79
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
WebmailRouteDispatcher
93.59% covered (success)
93.59%
73 / 78
83.33% covered (warning)
83.33%
5 / 6
51.69
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
 canDispatch
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 dispatch
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
5
 dispatchWebmailRoute
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 dispatchMailTemplatesWebRoute
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
8
 dispatchHtmxMailRoutes
88.64% covered (warning)
88.64%
39 / 44
0.00% covered (danger)
0.00%
0 / 1
33.50
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\Bootstrap\Dispatcher;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Security\Middleware\CsrfMiddleware;
12use App\Modules\Mail\Presentation\Htmx\WebmailHtmxController;
13use App\Modules\Mail\Presentation\Web\MailTemplateWebController;
14use App\Modules\Mail\Presentation\Web\WebmailWebController;
15use App\Modules\User\Presentation\Middleware\AuthenticatedOnlyMiddleware;
16use Nyholm\Psr7\Factory\Psr17Factory;
17use Psr\Http\Message\ResponseInterface;
18use Psr\Http\Message\ServerRequestInterface;
19use Psr\Http\Server\RequestHandlerInterface;
20
21/**
22 * Modular route dispatcher for Webmail and Mail Templates subsystem.
23 */
24final readonly class WebmailRouteDispatcher implements ModuleRouteDispatcherInterface
25{
26    public function __construct(
27        private ?WebmailWebController $webmailWebController,
28        private ?MailTemplateWebController $mailTemplateWebController,
29        private ?WebmailHtmxController $webmailHtmxController,
30        private AuthenticatedOnlyMiddleware $authMiddleware,
31        private CsrfMiddleware $csrfMiddleware,
32        private Psr17Factory $psr17Factory
33    ) {
34    }
35
36    public function canDispatch(string $path): bool
37    {
38        return $path === '/mail'
39            || str_starts_with($path, '/mail/templates/')
40            || str_starts_with($path, '/htmx/mail/');
41    }
42
43    public function dispatch(string $path, ServerRequestInterface $request): ?ResponseInterface
44    {
45        return match (true) {
46            $path === '/mail' => $this->dispatchWebmailRoute($request),
47            str_starts_with($path, '/mail/templates/') => $this->dispatchMailTemplatesWebRoute($path, $request),
48            str_starts_with($path, '/htmx/mail/') => $this->dispatchHtmxMailRoutes($path, $request),
49            default => null,
50        };
51    }
52
53    private function dispatchWebmailRoute(ServerRequestInterface $request): ResponseInterface
54    {
55        if ($this->webmailWebController === null) {
56            return $this->psr17Factory->createResponse(404);
57        }
58
59        return $this->authMiddleware->process(
60            $request,
61            new class($this->webmailWebController) implements RequestHandlerInterface {
62                public function __construct(private WebmailWebController $controller)
63                {
64                }
65
66                public function handle(ServerRequestInterface $req): ResponseInterface
67                {
68                    return $this->controller->actionIndex($req);
69                }
70            }
71        );
72    }
73
74    private function dispatchMailTemplatesWebRoute(
75        string $path,
76        ServerRequestInterface $request
77    ): ?ResponseInterface {
78        if ($this->mailTemplateWebController === null) {
79            return null;
80        }
81
82        $ctrl = $this->mailTemplateWebController;
83        $handler = match (true) {
84            (bool) preg_match('#^/mail/templates/(?:builder/(\d+)|(\d+)/builder)$#', $path, $m) =>
85                new class($ctrl, (int) ($m[1] !== '' ? $m[1] : $m[2])) implements RequestHandlerInterface {
86                    public function __construct(private MailTemplateWebController $c, private int $id)
87                    {
88                    }
89                    public function handle(ServerRequestInterface $req): ResponseInterface
90                    {
91                        return $this->c->builder($this->id);
92                    }
93                },
94            (bool) preg_match('#^/mail/templates/save-blocks/(\d+)$#', $path, $m) && $request->getMethod() === 'POST' =>
95                new class($ctrl, (int) $m[1]) implements RequestHandlerInterface {
96                    public function __construct(private MailTemplateWebController $c, private int $id)
97                    {
98                    }
99                    public function handle(ServerRequestInterface $req): ResponseInterface
100                    {
101                        return $this->c->saveBlocks($req, $this->id);
102                    }
103                },
104            default => null,
105        };
106
107        return $handler !== null ? $this->authMiddleware->process($request, $handler) : null;
108    }
109
110    private function dispatchHtmxMailRoutes(string $path, ServerRequestInterface $request): ResponseInterface
111    {
112        if ($this->webmailHtmxController === null) {
113            return $this->psr17Factory->createResponse(404);
114        }
115
116        $ctrl = $this->webmailHtmxController;
117        $psr17 = $this->psr17Factory;
118
119        $innerHandler = new class($ctrl, $path, $psr17) implements RequestHandlerInterface {
120            public function __construct(
121                private WebmailHtmxController $ctrl,
122                private string $path,
123                private Psr17Factory $psr17
124            ) {
125            }
126
127            public function handle(ServerRequestInterface $req): ResponseInterface
128            {
129                return match ($this->path) {
130                    '/htmx/mail/folders' => $this->ctrl->foldersTree($req),
131                    '/htmx/mail/folders/account' => $this->ctrl->accountFolders($req),
132                    '/htmx/mail/messages' => $this->ctrl->messagesList($req),
133                    '/htmx/mail/preview' => $this->ctrl->messagePreview($req),
134                    '/htmx/mail/flags' => $this->ctrl->toggleFlag($req),
135                    '/htmx/mail/compose' => $this->ctrl->composeModal($req),
136                    '/htmx/mail/send' => $this->ctrl->sendMessage($req),
137                    '/htmx/mail/login' => $this->ctrl->loginSubmit($req),
138                    '/htmx/mail/settings' => $this->ctrl->settingsModal($req),
139                    '/htmx/mail/settings/save' => $this->ctrl->saveSettings($req),
140                    '/htmx/mail/accounts/reorder' => $this->ctrl->reorderMailboxes($req),
141                    '/htmx/mail/accounts/move' => $this->ctrl->moveMailbox($req),
142                    '/htmx/mail/account/unassign-modal' => $this->ctrl->unassignMailboxModal($req),
143                    '/htmx/mail/account/unassign' => $this->ctrl->unassignMailboxConfirm($req),
144                    '/htmx/mail/folder/modal' => $this->ctrl->folderModal($req),
145                    '/htmx/mail/folder/create' => $this->ctrl->createFolder($req),
146                    '/htmx/mail/folder/reorder' => $this->ctrl->reorderFolder($req),
147                    '/htmx/mail/folder/nest' => $this->ctrl->nestFolder($req),
148                    '/htmx/mail/folder/rename' => $this->ctrl->renameFolder($req),
149                    '/htmx/mail/folder/delete' => $this->ctrl->deleteFolder($req),
150                    '/htmx/mail/folder/delete-modal' => $this->ctrl->deleteFolderModal($req),
151                    '/htmx/mail/folder/delete-confirm' => $this->ctrl->deleteFolderConfirm($req),
152                    '/htmx/mail/info' => $this->ctrl->mailboxInfoModal($req),
153                    '/htmx/mail/messages/action' => $this->ctrl->messageAction($req),
154                    '/htmx/mail/message/source' => $this->ctrl->messageSource($req),
155                    '/htmx/mail/message/security-check' => $this->ctrl->messageSecurityCheck($req),
156                    '/htmx/mail/message/relations' => $this->ctrl->messageRelations($req),
157                    '/htmx/mail/message/relations/detect' => $this->ctrl->messageRelationsDetect($req),
158                    '/htmx/mail/message/relations/unlink' => $this->ctrl->messageRelationsUnlink($req),
159                    default => $this->psr17->createResponse(404),
160                };
161            }
162        };
163
164        $authProtectedHandler = new class($this->authMiddleware, $innerHandler) implements RequestHandlerInterface {
165            public function __construct(
166                private AuthenticatedOnlyMiddleware $authMiddleware,
167                private RequestHandlerInterface $next
168            ) {
169            }
170
171            public function handle(ServerRequestInterface $req): ResponseInterface
172            {
173                return $this->authMiddleware->process($req, $this->next);
174            }
175        };
176
177        return $this->csrfMiddleware->process($request, $authProtectedHandler);
178    }
179}