Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.73% covered (success)
95.73%
112 / 117
37.50% covered (danger)
37.50%
3 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
SqlPdfTemplateRepository
95.69% covered (success)
95.69%
111 / 116
37.50% covered (danger)
37.50%
3 / 8
22
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
 findById
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
 findByCode
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
3.01
 findAllActiveByModule
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
4.00
 save
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
 delete
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
2.02
 insertRecord
100.00% covered (success)
100.00%
36 / 36
100.00% covered (success)
100.00%
1 / 1
3
 updateRecord
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
3
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\Modules\Pdf\Infrastructure\Repository;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Database\FallbackPdoResolver;
12use App\Modules\Pdf\Domain\Model\PdfTemplate;
13use App\Modules\Pdf\Domain\Repository\PdfTemplateRepositoryInterface;
14use PDO;
15
16/**
17 * SQL-Backed Repository Implementation for PDF Templates.
18 *
19 * @package App\Modules\Pdf\Infrastructure\Repository
20 */
21final class SqlPdfTemplateRepository implements PdfTemplateRepositoryInterface
22{
23    private const SELECT_FIELDS = '`id`, `code`, `name`, `target_module`, `category`, `template_scope`, '
24        . '`page_format`, `page_orientation`, `margin_top`, `margin_bottom`, `margin_left`, `margin_right`, '
25        . '`header_html`, `footer_html`, `body_html`, `blocks_json`, `css_styles`, `filename_pattern`, '
26        . '`watermark_text`, `language_code`, `description`, `status`, `special_access`, '
27        . '`created_at`, `updated_at`, `created_by`, `owner`, `co_owners`';
28
29    private const string BASE_SELECT = 'SELECT ' . self::SELECT_FIELDS . ' FROM `a_mod_pdf_template_records` ';
30
31    /**
32     * SqlPdfTemplateRepository constructor.
33     *
34     * @param PDO|null $pdo Active PDO connection instance.
35     */
36    public function __construct(
37        private ?PDO $pdo = null
38    ) {
39        $this->pdo = $pdo ?? FallbackPdoResolver::resolveDefaultConnection();
40    }
41
42    /**
43     * Finds a PDF template by its primary key ID.
44     *
45     * @param int $id Template identifier.
46     * @return PdfTemplate|null Template model or null if not found.
47     */
48    public function findById(int $id): ?PdfTemplate
49    {
50        if ($this->pdo === null) {
51            return null;
52        }
53
54        $stmt = $this->pdo->prepare(
55            self::BASE_SELECT . 'WHERE `id` = :id AND `status` = "active" AND `special_access` = 1 LIMIT 1'
56        );
57        $stmt->execute([':id' => $id]);
58        $row = $stmt->fetch(PDO::FETCH_ASSOC);
59
60        return is_array($row) ? PdfTemplate::fromRow($row) : null;
61    }
62
63    /**
64     * Finds an active PDF template by its machine code and language.
65     *
66     * @param string $code         Unique template code.
67     * @param string $languageCode Locale code.
68     * @return PdfTemplate|null Matching active template or null.
69     */
70    public function findByCode(string $code, string $languageCode = 'pl'): ?PdfTemplate
71    {
72        if ($this->pdo === null) {
73            return null;
74        }
75
76        $stmt = $this->pdo->prepare(
77            self::BASE_SELECT
78            . 'WHERE `code` = :code AND `language_code` = :lang '
79            . 'AND `status` = "active" AND `special_access` = 1 LIMIT 1'
80        );
81        $stmt->execute([':code' => $code, ':lang' => $languageCode]);
82        $row = $stmt->fetch(PDO::FETCH_ASSOC);
83
84        return is_array($row) ? PdfTemplate::fromRow($row) : null;
85    }
86
87    /**
88     * Finds all active PDF templates applicable to a given module.
89     *
90     * @param string $moduleName   Module name or 'global'.
91     * @param string $languageCode Locale code.
92     * @return array<int, PdfTemplate> List of active templates.
93     */
94    public function findAllActiveByModule(string $moduleName, string $languageCode = 'pl'): array
95    {
96        if ($this->pdo === null) {
97            return [];
98        }
99
100        $stmt = $this->pdo->prepare(
101            self::BASE_SELECT
102            . 'WHERE (LOWER(`target_module`) = LOWER(:mod) OR `target_module` = "global") '
103            . 'AND `language_code` = :lang AND `status` = "active" AND `special_access` = 1 '
104            . 'ORDER BY `name` ASC'
105        );
106        $stmt->execute([':mod' => $moduleName, ':lang' => $languageCode]);
107        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
108
109        $templates = [];
110        if (is_array($rows)) {
111            foreach ($rows as $row) {
112                $templates[] = PdfTemplate::fromRow($row);
113            }
114        }
115
116        return $templates;
117    }
118
119    /**
120     * Persists or updates a PDF template record.
121     *
122     * @param PdfTemplate $template Aggregate to persist.
123     * @return int Primary key ID of saved record.
124     */
125    public function save(PdfTemplate $template): int
126    {
127        if ($this->pdo === null) {
128            return 0;
129        }
130
131        if ($template->id > 0) {
132            return $this->updateRecord($template);
133        }
134
135        return $this->insertRecord($template);
136    }
137
138    /**
139     * Marks template as deleted.
140     *
141     * @param int $id Primary ID.
142     * @return bool True on successful deletion.
143     */
144    public function delete(int $id): bool
145    {
146        if ($this->pdo === null) {
147            return false;
148        }
149
150        $stmt = $this->pdo->prepare(
151            'UPDATE `a_mod_pdf_template_records` SET `special_access` = 3 WHERE `id` = :id'
152        );
153
154        return $stmt->execute([':id' => $id]);
155    }
156
157    /**
158     * Inserts new template record.
159     *
160     * @param PdfTemplate $template Template model.
161     * @return int Inserted ID.
162     */
163    private function insertRecord(PdfTemplate $template): int
164    {
165        $stmt = $this->pdo->prepare(
166            'INSERT INTO `a_mod_pdf_template_records` '
167            . '(`code`, `name`, `target_module`, `category`, `template_scope`, `page_format`, `page_orientation`, '
168            . ' `margin_top`, `margin_bottom`, `margin_left`, `margin_right`, `header_html`, `footer_html`, '
169            . ' `body_html`, `blocks_json`, `css_styles`, `filename_pattern`, `watermark_text`, `language_code`, '
170            . ' `description`, `status`, `special_access`, `created_by`, `owner`) '
171            . 'VALUES (:code, :name, :target_mod, :cat, :scope, :pfmt, :pori, :mtop, :mbot, :mleft, :mright, :hdr, '
172            . ' :ftr, :bdy, :blks, :css, :fn, :wm, :lang, :desc, :status, :spec, :cb, :own)'
173        );
174
175        $stmt->execute([
176            ':code'       => $template->code,
177            ':name'       => $template->name,
178            ':target_mod' => $template->targetModule,
179            ':cat'        => $template->category,
180            ':scope'      => $template->templateScope,
181            ':pfmt'       => $template->pageFormat,
182            ':pori'       => $template->pageOrientation,
183            ':mtop'       => $template->marginTop,
184            ':mbot'       => $template->marginBottom,
185            ':mleft'      => $template->marginLeft,
186            ':mright'     => $template->marginRight,
187            ':hdr'        => $template->headerHtml,
188            ':ftr'        => $template->footerHtml,
189            ':bdy'        => $template->bodyHtml,
190            ':blks'       => $template->blocksJson !== null ? json_encode($template->blocksJson) : null,
191            ':css'        => $template->cssStyles,
192            ':fn'         => $template->filenamePattern,
193            ':wm'         => $template->watermarkText,
194            ':lang'       => $template->languageCode,
195            ':desc'       => $template->description,
196            ':status'     => $template->isActive ? 'active' : 'inactive',
197            ':spec'       => $template->recordStatus,
198            ':cb'         => $template->createdBy,
199            ':own'        => $template->owner,
200        ]);
201
202        return (int)$this->pdo->lastInsertId();
203    }
204
205    /**
206     * Updates existing template record.
207     *
208     * @param PdfTemplate $template Template model.
209     * @return int Template ID.
210     */
211    private function updateRecord(PdfTemplate $template): int
212    {
213        $stmt = $this->pdo->prepare(
214            'UPDATE `a_mod_pdf_template_records` SET '
215            . '`code` = :code, `name` = :name, `target_module` = :target_mod, `category` = :cat, '
216            . '`template_scope` = :scope, `page_format` = :pfmt, `page_orientation` = :pori, '
217            . '`margin_top` = :mtop, `margin_bottom` = :mbot, `margin_left` = :mleft, `margin_right` = :mright, '
218            . '`header_html` = :hdr, `footer_html` = :ftr, `body_html` = :bdy, `blocks_json` = :blks, '
219            . '`css_styles` = :css, `filename_pattern` = :fn, `watermark_text` = :wm, `language_code` = :lang, '
220            . '`description` = :desc, `status` = :status, `special_access` = :spec WHERE `id` = :id'
221        );
222
223        $stmt->execute([
224            ':id'         => $template->id,
225            ':code'       => $template->code,
226            ':name'       => $template->name,
227            ':target_mod' => $template->targetModule,
228            ':cat'        => $template->category,
229            ':scope'      => $template->templateScope,
230            ':pfmt'       => $template->pageFormat,
231            ':pori'       => $template->pageOrientation,
232            ':mtop'       => $template->marginTop,
233            ':mbot'       => $template->marginBottom,
234            ':mleft'      => $template->marginLeft,
235            ':mright'     => $template->marginRight,
236            ':hdr'        => $template->headerHtml,
237            ':ftr'        => $template->footerHtml,
238            ':bdy'        => $template->bodyHtml,
239            ':blks'       => $template->blocksJson !== null ? json_encode($template->blocksJson) : null,
240            ':css'        => $template->cssStyles,
241            ':fn'         => $template->filenamePattern,
242            ':wm'         => $template->watermarkText,
243            ':lang'       => $template->languageCode,
244            ':desc'       => $template->description,
245            ':status'     => $template->isActive ? 'active' : 'inactive',
246            ':spec'       => $template->recordStatus,
247        ]);
248
249        return $template->id;
250    }
251}