Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.83% covered (success)
97.83%
45 / 46
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
FilterMetadata
100.00% covered (success)
100.00%
45 / 45
100.00% covered (success)
100.00%
4 / 4
18
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isFieldVisible
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getOrderedVisibleFields
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
6
 fromRow
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
1 / 1
10
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\Domain\Model;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Engine\Domain\Model\RecordStatus;
12
13/**
14 * Filter Metadata Value Object.
15 *
16 * Immutable value object representing a module view/filter configuration
17 * from a_core_filter_records. Defines which fields are visible in a grid
18 * and default sorting/pagination parameters.
19 *
20 * @package App\Core\Engine\Domain\Model
21 */
22final readonly class FilterMetadata
23{
24    /**
25     * FilterMetadata constructor.
26     *
27     * @param int                  $id            Filter primary key.
28     * @param int                  $moduleId      FK to a_core_module_records.
29     * @param string               $name          Machine-readable name.
30     * @param string               $label         Human-readable display label.
31     * @param array<int, string>   $visibleFields Ordered list of visible field keys.
32     * @param string               $defaultSort   Default sort column.
33     * @param string               $defaultOrder  Default sort direction ('ASC' or 'DESC').
34     * @param int                  $perPage       Rows per page.
35     * @param bool                 $isDefault     Whether this is the default filter.
36     * @param bool                 $isSystem      Whether this is a non-deletable system filter.
37     * @param string               $scope         Access scope ('private' or 'public').
38     * @param int|null             $owner         Record owner user ID.
39     * @param array<int, int>      $coOwners      Array of co-owner user IDs.
40     * @param array<int, mixed>    $conditions    Array of filter criteria rules.
41     * @param string               $iconClass     Bootstrap icon class.
42     * @param int                  $recordStatus  Special access level (0=None, 1=All Read, 2=All Write, 3=All Delete).
43     */
44    public function __construct(
45        public readonly int    $id,
46        public readonly int    $moduleId,
47        public readonly string $name,
48        public readonly string $label,
49        public readonly array  $visibleFields,
50        public readonly string $defaultSort = 'id',
51        public readonly string $defaultOrder = 'DESC',
52        public readonly int    $perPage = 15,
53        public readonly bool   $isDefault = false,
54        public readonly bool   $isSystem = false,
55        public readonly string $scope = 'private',
56        public readonly ?int   $owner = null,
57        public readonly array  $coOwners = [],
58        public readonly array  $conditions = [],
59        public readonly string $iconClass = 'bi bi-filter',
60        public readonly int    $recordStatus = 0,
61    ) {
62    }
63
64    /**
65     * Checks whether a given field key should be visible in this filter.
66     *
67     * @param string $fieldKey The field key to check.
68     * @return bool True if the field is in the visible fields list.
69     */
70    public function isFieldVisible(string $fieldKey): bool
71    {
72        return in_array($fieldKey, $this->visibleFields, true);
73    }
74
75    /**
76     * Orders the given fields list according to the sequence of visibleFields.
77     *
78     * @param array<int|string, FieldMetadata> $fields Available module fields.
79     * @return array<int, FieldMetadata> Ordered visible fields.
80     */
81    public function getOrderedVisibleFields(array $fields): array
82    {
83        $fieldMap = [];
84        foreach ($fields as $field) {
85            if ($field->isVisibleInList()) {
86                $fieldMap[$field->fieldKey] = $field;
87            }
88        }
89
90        $ordered = [];
91        foreach ($this->visibleFields as $key) {
92            if (isset($fieldMap[$key])) {
93                $ordered[] = $fieldMap[$key];
94            }
95        }
96
97        if (empty($ordered)) {
98            return array_values($fieldMap);
99        }
100
101        return $ordered;
102    }
103
104    use \App\Core\Engine\Domain\Model\Support\FilterAccessPolicyTrait;
105
106
107    /**
108     * Creates a FilterMetadata instance from a raw database row array.
109     *
110     * @param array<string, mixed> $row Raw database row from a_core_filter_records.
111     * @return self Hydrated FilterMetadata value object.
112     */
113    public static function fromRow(array $row): self
114    {
115        $visibleFields = [];
116        if (isset($row['visible_fields']) && is_string($row['visible_fields'])) {
117            $decoded = json_decode($row['visible_fields'], true);
118            $visibleFields = is_array($decoded) ? $decoded : [];
119        }
120
121        $coOwners = \App\Shared\Utils\JsonArrayHelper::toIntList($row['co_owners'] ?? []);
122
123        $conditions = [];
124        if (isset($row['conditions']) && is_string($row['conditions'])) {
125            $decoded = json_decode($row['conditions'], true);
126            $conditions = is_array($decoded) ? $decoded : [];
127        } elseif (is_array($row['conditions'] ?? null)) {
128            $conditions = $row['conditions'];
129        }
130
131        $iconClass = (string) ($row['icon_class'] ?? '');
132        if ($iconClass === '') {
133            $iconClass = 'bi bi-filter';
134        }
135
136        return new self(
137            id:            (int)    $row['id'],
138            moduleId:      (int)    $row['module_id'],
139            name:          (string) $row['name'],
140            label:         (string) $row['label'],
141            visibleFields: $visibleFields,
142            defaultSort:   (string) ($row['default_sort']  ?? 'id'),
143            defaultOrder:  (string) ($row['default_order'] ?? 'DESC'),
144            perPage:       (int)    ($row['per_page']      ?? 15),
145            isDefault:     (bool)   ($row['is_default']    ?? false),
146            isSystem:      (bool)   ($row['is_system']     ?? false),
147            scope:         (string) ($row['scope']         ?? 'private'),
148            owner:         isset($row['owner']) ? (int) $row['owner'] : null,
149            coOwners:      $coOwners,
150            conditions:    $conditions,
151            iconClass:     $iconClass,
152            recordStatus:  (int) ($row['record_status'] ?? 0),
153        );
154    }
155}