Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.31% covered (warning)
84.31%
86 / 102
57.14% covered (warning)
57.14%
4 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
DocumentAndPdfRouteDispatcher
84.16% covered (warning)
84.16%
85 / 101
57.14% covered (warning)
57.14%
4 / 7
45.05
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%
4 / 4
100.00% covered (success)
100.00%
1 / 1
4
 dispatch
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
6.05
 dispatchHtmxPdfRoutes
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
12
 dispatchPdfWebRoute
96.00% covered (success)
96.00%
24 / 25
0.00% covered (danger)
0.00%
0 / 1
12
 dispatchDocumentDownloadRoute
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
2
 dispatchCommentAttachmentDownloadRoute
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
6
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\Engine\Application\Security\PermissionContextFactory;
12use App\Modules\User\Presentation\Middleware\AuthenticatedOnlyMiddleware;
13use App\Modules\Comments\Presentation\Web\CommentAttachmentDownloadWebController;
14use App\Modules\Documents\Presentation\Web\DocumentDownloadWebController;
15use App\Modules\Pdf\Presentation\Htmx\PdfHtmxController;
16use App\Modules\Pdf\Presentation\Htmx\PdfQueueHtmxController;
17use App\Modules\Pdf\Presentation\Htmx\PdfSignatureHtmxController;
18use App\Modules\Pdf\Presentation\Web\PdfWebController;
19use Nyholm\Psr7\Factory\Psr17Factory;
20use Psr\Http\Message\ResponseInterface;
21use Psr\Http\Message\ServerRequestInterface;
22use Psr\Http\Server\RequestHandlerInterface;
23
24/**
25 * Modular route dispatcher for PDF generation, templates, queues, and document/attachment downloads.
26 */
27final readonly class DocumentAndPdfRouteDispatcher implements ModuleRouteDispatcherInterface
28{
29    public function __construct(
30        private ?PdfHtmxController $pdfHtmxController,
31        private ?PdfSignatureHtmxController $pdfSignatureHtmxController,
32        private ?PdfQueueHtmxController $pdfQueueHtmxController,
33        private ?PdfWebController $pdfWebController,
34        private ?DocumentDownloadWebController $documentDownloadWebController,
35        private ?CommentAttachmentDownloadWebController $commentAttachmentDownloadWebController,
36        private PermissionContextFactory $contextFactory,
37        private AuthenticatedOnlyMiddleware $authMiddleware,
38        private Psr17Factory $psr17Factory
39    ) {
40    }
41
42    public function canDispatch(string $path): bool
43    {
44        return str_starts_with($path, '/htmx/pdf/')
45            || str_starts_with($path, '/pdf/')
46            || (bool) preg_match('#^/documents/(\d+)/files/(\d+)/download$#', $path)
47            || (bool) preg_match('#^/comments/attachments/([a-zA-Z0-9_-]+)/download$#', $path);
48    }
49
50    public function dispatch(string $path, ServerRequestInterface $request): ?ResponseInterface
51    {
52        return match (true) {
53            str_starts_with($path, '/htmx/pdf/') => $this->dispatchHtmxPdfRoutes($path, $request),
54            str_starts_with($path, '/pdf/') => $this->dispatchPdfWebRoute($path, $request),
55            (bool) preg_match('#^/documents/(\d+)/files/(\d+)/download$#', $path, $m) =>
56                $this->dispatchDocumentDownloadRoute((int) $m[1], (int) $m[2], $request),
57            (bool) preg_match('#^/comments/attachments/([a-zA-Z0-9_-]+)/download$#', $path, $m) =>
58                $this->dispatchCommentAttachmentDownloadRoute((string) $m[1], $request),
59            default => null,
60        };
61    }
62
63    private function dispatchHtmxPdfRoutes(string $path, ServerRequestInterface $request): ResponseInterface
64    {
65        if ($this->pdfHtmxController === null) {
66            return $this->psr17Factory->createResponse(404);
67        }
68
69        $sigCtrl = $this->pdfSignatureHtmxController;
70        $qCtrl = $this->pdfQueueHtmxController;
71
72        return $this->authMiddleware->process(
73            $request,
74            new class(
75                $this->pdfHtmxController,
76                $sigCtrl,
77                $qCtrl,
78                $path,
79                $this->psr17Factory
80            ) implements RequestHandlerInterface {
81                public function __construct(
82                    private PdfHtmxController $controller,
83                    private ?PdfSignatureHtmxController $sigCtrl,
84                    private ?PdfQueueHtmxController $qCtrl,
85                    private string $path,
86                    private Psr17Factory $psr17Factory
87                ) {
88                }
89
90                public function handle(ServerRequestInterface $req): ResponseInterface
91                {
92                    return match (true) {
93                        $this->path === '/htmx/pdf/signature-modal' =>
94                            $this->sigCtrl?->modalSignature($req) ?? $this->psr17Factory->createResponse(404),
95                        $this->path === '/htmx/pdf/signature-save' && $req->getMethod() === 'POST' =>
96                            $this->sigCtrl?->saveSignature($req) ?? $this->psr17Factory->createResponse(404),
97                        $this->path === '/htmx/pdf/queue/create' && $req->getMethod() === 'POST' =>
98                            $this->qCtrl?->createQueue($req) ?? $this->psr17Factory->createResponse(404),
99                        (bool) preg_match('#^/htmx/pdf/queue/status/(\d+)$#', $this->path, $m) =>
100                            $this->qCtrl?->status((int) $m[1]) ?? $this->psr17Factory->createResponse(404),
101                        (bool) preg_match('#^/htmx/pdf/([a-zA-Z0-9_-]+)/print-modal/(\d+)$#', $this->path, $m) =>
102                            $this->controller->modalPrintRecord($m[1], (int) $m[2]),
103                        (bool) preg_match('#^/htmx/pdf/([a-zA-Z0-9_-]+)/bulk-modal$#', $this->path, $m) =>
104                            $this->controller->modalBulkPdf($req, $m[1]),
105                        (bool) preg_match('#^/htmx/pdf/preview/(\d+)/(\d+)$#', $this->path, $m) =>
106                            $this->controller->preview($req, (int) $m[1], (int) $m[2]),
107                        default => $this->psr17Factory->createResponse(404),
108                    };
109                }
110            }
111        );
112    }
113
114    private function dispatchPdfWebRoute(string $path, ServerRequestInterface $request): ResponseInterface
115    {
116        if ($this->pdfWebController === null) {
117            return $this->psr17Factory->createResponse(404);
118        }
119
120        return $this->authMiddleware->process(
121            $request,
122            new class($this->pdfWebController, $path, $this->psr17Factory) implements RequestHandlerInterface {
123                public function __construct(
124                    private PdfWebController $controller,
125                    private string $path,
126                    private Psr17Factory $psr17Factory
127                ) {
128                }
129
130                public function handle(ServerRequestInterface $req): ResponseInterface
131                {
132                    return match (true) {
133                        (bool) preg_match('#^/pdf/export/([a-zA-Z0-9_-]+)/(\d+)$#', $this->path, $m) =>
134                            $this->controller->exportRecord($req, (string)$m[1], (int)$m[2]),
135                        (bool) preg_match('#^/pdf/download/(\d+)/(\d+)$#', $this->path, $m) =>
136                            $this->controller->download($req, (int) $m[1], (int) $m[2]),
137                        $this->path === '/pdf/bulk-download' && $req->getMethod() === 'POST' =>
138                            $this->controller->bulkDownload($req),
139                        (bool) preg_match('#^/pdf/(?:builder/(\d+)|templates/builder/(\d+))$#', $this->path, $m) =>
140                            $this->controller->builder((int) ($m[1] !== '' ? $m[1] : $m[2])),
141                        (bool) preg_match(
142                            '#^/pdf/(?:templates/save-blocks/(\d+)|save-blocks/(\d+))$#',
143                            $this->path,
144                            $m
145                        ) && $req->getMethod() === 'POST' =>
146                            $this->controller->saveBlocks($req, (int) ($m[1] !== '' ? $m[1] : $m[2])),
147                        default => $this->psr17Factory->createResponse(404),
148                    };
149                }
150            }
151        );
152    }
153
154    private function dispatchDocumentDownloadRoute(
155        int $documentId,
156        int $fileId,
157        ServerRequestInterface $request
158    ): ResponseInterface {
159        if ($this->documentDownloadWebController === null) {
160            return $this->psr17Factory->createResponse(404);
161        }
162
163        return $this->authMiddleware->process(
164            $request,
165            new class(
166                $this->documentDownloadWebController,
167                $documentId,
168                $fileId,
169                $this->contextFactory
170            ) implements RequestHandlerInterface {
171                public function __construct(
172                    private DocumentDownloadWebController $controller,
173                    private int $documentId,
174                    private int $fileId,
175                    private PermissionContextFactory $contextFactory
176                ) {
177                }
178
179                public function handle(ServerRequestInterface $req): ResponseInterface
180                {
181                    $context = $this->contextFactory->createFromRequest($req);
182                    return $this->controller->actionDownload($this->documentId, $this->fileId, $context);
183                }
184            }
185        );
186    }
187
188    private function dispatchCommentAttachmentDownloadRoute(
189        string $token,
190        ServerRequestInterface $request
191    ): ResponseInterface {
192        if ($this->commentAttachmentDownloadWebController === null) {
193            return $this->psr17Factory->createResponse(404);
194        }
195
196        return $this->authMiddleware->process(
197            $request,
198            new class(
199                $this->commentAttachmentDownloadWebController,
200                $token,
201                $this->contextFactory
202            ) implements RequestHandlerInterface {
203                public function __construct(
204                    private CommentAttachmentDownloadWebController $controller,
205                    private string $token,
206                    private PermissionContextFactory $contextFactory
207                ) {
208                }
209
210                public function handle(ServerRequestInterface $req): ResponseInterface
211                {
212                    $context = $this->contextFactory->createFromRequest($req);
213                    return $this->controller->actionDownload($this->token, $context);
214                }
215            }
216        );
217    }
218}