Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
60.87% covered (warning)
60.87%
28 / 46
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
MailTemplate
60.00% covered (warning)
60.00%
27 / 45
66.67% covered (warning)
66.67%
4 / 6
32.38
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
 fromRow
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
3
 parseBlocksJson
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
8.12
 nullableString
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 resolveIsActive
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 toArray
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 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\Modules\Mail\Domain\Model;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11/**
12 * Email Template Aggregate Model.
13 *
14 * @package App\Modules\Mail\Domain\Model
15 */
16final readonly class MailTemplate
17{
18    /**
19     * MailTemplate constructor.
20     *
21     * @param int                       $id           Primary ID.
22     * @param string                    $code         Unique template machine code.
23     * @param string                    $name         Display title.
24     * @param string                    $subject      Email subject with placeholder tags.
25     * @param string                    $bodyHtml     HTML body template content.
26     * @param string|null               $bodyText     Optional plain text fallback.
27     * @param array<string, mixed>|null $blocksJson   Modular email block tree configuration.
28     * @param string|null               $cssStyles    Optional custom stylesheet for email inliner.
29     * @param int|null                  $smtpId       Optional dedicated SMTP server ID.
30     * @param string                    $languageCode Language locale code (e.g. 'en', 'pl').
31     * @param string                    $dispatchMode Dispatch mode (automatic, manual).
32     * @param string|null               $description  Optional description notes.
33     * @param bool                      $isActive     Active flag.
34     */
35    public function __construct(
36        public int $id,
37        public string $code,
38        public string $name,
39        public string $subject,
40        public string $bodyHtml,
41        public ?string $bodyText = null,
42        public ?array $blocksJson = null,
43        public ?string $cssStyles = null,
44        public ?int $smtpId = null,
45        public string $languageCode = 'en',
46        public string $dispatchMode = 'automatic',
47        public ?string $description = null,
48        public bool $isActive = true
49    ) {
50    }
51
52    /**
53     * Instantiates MailTemplate from database row.
54     *
55     * @param array<string, mixed> $row Database associative record.
56     * @return self Populated MailTemplate instance.
57     */
58    public static function fromRow(array $row): self
59    {
60        return new self(
61            id: (int)($row['id'] ?? 0),
62            code: (string)($row['code'] ?? ''),
63            name: (string)($row['name'] ?? ''),
64            subject: (string)($row['subject'] ?? ''),
65            bodyHtml: (string)($row['body_html'] ?? ''),
66            bodyText: self::nullableString($row['body_text'] ?? null),
67            blocksJson: self::parseBlocksJson($row['blocks_json'] ?? null),
68            cssStyles: self::nullableString($row['css_styles'] ?? null),
69            smtpId: isset($row['smtp_id']) && (int)$row['smtp_id'] > 0 ? (int)$row['smtp_id'] : null,
70            languageCode: (string)($row['language_code'] ?? 'en'),
71            dispatchMode: (string)($row['dispatch_mode'] ?? 'automatic'),
72            description: self::nullableString($row['description'] ?? null),
73            isActive: self::resolveIsActive($row)
74        );
75    }
76
77    /**
78     * Parses blocks json into an array structure if valid.
79     *
80     * @param mixed $blocksJson Raw input.
81     * @return array<int, mixed>|null Decoded block structure or null.
82     */
83    private static function parseBlocksJson(mixed $blocksJson): ?array
84    {
85        if (is_array($blocksJson)) {
86            return $blocksJson;
87        }
88        if (is_string($blocksJson) && trim($blocksJson) !== '') {
89            $decoded = json_decode($blocksJson, true);
90            return is_array($decoded) ? $decoded : null;
91        }
92        return null;
93    }
94
95    /**
96     * Resolves nullable non-empty string.
97     *
98     * @param mixed $val Input value.
99     * @return string|null Resolved string or null.
100     */
101    private static function nullableString(mixed $val): ?string
102    {
103        if ($val !== null && (string)$val !== '') {
104            return (string)$val;
105        }
106        return null;
107    }
108
109    /**
110     * Resolves active status for template row.
111     *
112     * @param array<string, mixed> $row Database record.
113     * @return bool Whether template is active.
114     */
115    private static function resolveIsActive(array $row): bool
116    {
117        if (isset($row['is_active'])) {
118            return (bool)$row['is_active'];
119        }
120        $statusActive = ($row['status'] ?? 'active') === 'active';
121        $accessGranted = ((int)($row['special_access'] ?? 1) === 1);
122        return $statusActive && $accessGranted;
123    }
124
125    /**
126     * Serializes aggregate to array.
127     *
128     * @return array<string, mixed> Serialized dictionary.
129     */
130    public function toArray(): array
131    {
132        return [
133            'id'            => $this->id,
134            'code'          => $this->code,
135            'name'          => $this->name,
136            'subject'       => $this->subject,
137            'body_html'     => $this->bodyHtml,
138            'body_text'     => $this->bodyText,
139            'blocks_json'   => $this->blocksJson,
140            'css_styles'    => $this->cssStyles,
141            'smtp_id'       => $this->smtpId,
142            'language_code' => $this->languageCode,
143            'dispatch_mode' => $this->dispatchMode,
144            'description'   => $this->description,
145            'is_active'     => $this->isActive,
146        ];
147    }
148}