Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
ReminderBuilderTransformer
100.00% covered (success)
100.00%
37 / 37
100.00% covered (success)
100.00%
6 / 6
25
100.00% covered (success)
100.00%
1 / 1
 supports
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 transformRead
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 transformWrite
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
6
 parseReminders
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 decodeItems
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
6
 sanitizeItem
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 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\Engine\Application\Transformer;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Engine\Domain\Model\FieldMetadata;
12
13/**
14 * Reminder Builder UiType Transformer.
15 *
16 * Handles UiType: reminder_builder.
17 * Read: converts raw database JSON value to a normalized JSON string.
18 * Write: validates and serializes reminder rules into valid JSON array string.
19 *
20 * @package App\Core\Engine\Application\Transformer
21 */
22final class ReminderBuilderTransformer implements UiTypeTransformerInterface
23{
24    /** Supported reminder types. */
25    public const array VALID_TYPES = ['notification', 'email'];
26
27    /** Supported reminder units. */
28    public const array VALID_UNITS = ['min', 'hours', 'days', 'weeks'];
29
30    /** {@inheritdoc} */
31    public function supports(string $uitypeName): bool
32    {
33        return $uitypeName === 'reminder_builder';
34    }
35
36    /** {@inheritdoc} */
37    public function transformRead(mixed $rawValue, FieldMetadata $field): string
38    {
39        $parsed = self::parseReminders($rawValue);
40        if ($parsed === []) {
41            return '[]';
42        }
43
44        $encoded = json_encode($parsed, JSON_UNESCAPED_UNICODE);
45        return $encoded !== false ? $encoded : '[]';
46    }
47
48    /** {@inheritdoc} */
49    public function transformWrite(mixed $inputValue, FieldMetadata $field): ?string
50    {
51        if ($inputValue === null || $inputValue === '' || $inputValue === '[]') {
52            return null;
53        }
54
55        $filtered = self::parseReminders($inputValue);
56        if ($filtered === []) {
57            return null;
58        }
59
60        $encoded = json_encode($filtered, JSON_UNESCAPED_UNICODE);
61        return $encoded !== false ? $encoded : null;
62    }
63
64    /**
65     * Parses and sanitizes array of reminder items from string or array.
66     *
67     * @param mixed $raw Raw input value (JSON string or array).
68     * @return array<int, array{type: string, value: int, unit: string}> Valid reminder items.
69     */
70    public static function parseReminders(mixed $raw): array
71    {
72        $items = self::decodeItems($raw);
73        $result = [];
74
75        foreach ($items as $item) {
76            if (is_array($item)) {
77                $result[] = self::sanitizeItem($item);
78            }
79        }
80
81        return $result;
82    }
83
84    /**
85     * Decodes raw input into list of raw reminder items or empty array.
86     *
87     * @return array<mixed>
88     */
89    private static function decodeItems(mixed $raw): array
90    {
91        if (is_array($raw)) {
92            return $raw;
93        }
94        if (is_string($raw) && $raw !== '' && $raw !== '[]') {
95            $decoded = json_decode($raw, true);
96            return is_array($decoded) ? $decoded : [];
97        }
98
99        return [];
100    }
101
102    /**
103     * Sanitizes single reminder item values against supported types and units.
104     *
105     * @param array<string, mixed> $item
106     * @return array{type: string, value: int, unit: string}
107     */
108    private static function sanitizeItem(array $item): array
109    {
110        $type = isset($item['type']) && in_array((string) $item['type'], self::VALID_TYPES, true)
111            ? (string) $item['type']
112            : 'notification';
113
114        $value = isset($item['value']) ? max(1, (int) $item['value']) : 10;
115
116        $unit = isset($item['unit']) && in_array((string) $item['unit'], self::VALID_UNITS, true)
117            ? (string) $item['unit']
118            : 'min';
119
120        return [
121            'type'  => $type,
122            'value' => $value,
123            'unit'  => $unit,
124        ];
125    }
126}