Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
47 / 47
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
SectionMetadata
100.00% covered (success)
100.00%
46 / 46
100.00% covered (success)
100.00%
6 / 6
23
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
 fromRow
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
3
 getIconClass
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
7
 getVisibleFields
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
10
 hasVisibleFields
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 toArray
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
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
11/**
12 * Section Metadata Value Object.
13 *
14 * Immutable value object representing a logical field partition (section/tab)
15 * within a module, driving field organization across record detail, edit, and create views.
16 *
17 * @package App\Core\Engine\Domain\Model
18 */
19final readonly class SectionMetadata
20{
21    /** @var string Default fallback section icon. */
22    public const string DEFAULT_ICON = 'bi bi-folder2-open';
23
24    /**
25     * SectionMetadata constructor.
26     *
27     * @param int         $id        Section primary key.
28     * @param int         $moduleId  FK to a_core_module_records.
29     * @param string      $name      Machine-readable slug (e.g. 'basic', 'additional', 'system').
30     * @param string      $label     Human-readable display label (e.g. 'Podstawowe').
31     * @param string|null $iconClass Optional Bootstrap Icon CSS class.
32     * @param int         $sortOrder Display tab order sequence.
33     * @param bool        $isActive  Whether the section is active.
34     * @param bool        $isSystem  Whether section is protected from deletion.
35     * @param bool        $isDefault Whether this is the default active tab on load.
36     */
37    public function __construct(
38        public int     $id,
39        public int     $moduleId,
40        public string  $name,
41        public string  $label,
42        public ?string $iconClass = null,
43        public int     $sortOrder = 10,
44        public bool    $isActive = true,
45        public bool    $isSystem = false,
46        public bool    $isDefault = false
47    ) {
48    }
49
50    /**
51     * Factory method creating SectionMetadata from raw database row.
52     *
53     * @param array<string, mixed> $row Database associative array.
54     * @return self Populated SectionMetadata instance.
55     */
56    public static function fromRow(array $row): self
57    {
58        return new self(
59            id: (int) ($row['id'] ?? 0),
60            moduleId: (int) ($row['module_id'] ?? 0),
61            name: (string) ($row['name'] ?? ''),
62            label: (string) ($row['label'] ?? ''),
63            iconClass: isset($row['icon_class']) && $row['icon_class'] !== ''
64                ? (string) $row['icon_class']
65                : null,
66            sortOrder: (int) ($row['sort_order'] ?? 10),
67            isActive: (bool) ($row['is_active'] ?? true),
68            isSystem: (bool) ($row['is_system'] ?? false),
69            isDefault: (bool) ($row['is_default'] ?? false)
70        );
71    }
72
73    /**
74     * Returns an effective Bootstrap Icon class for UI rendering.
75     *
76     * @return string CSS class string.
77     */
78    public function getIconClass(): string
79    {
80        return $this->iconClass ?? match ($this->name) {
81            'basic', 'general', 'podstawowe' => 'bi bi-info-circle',
82            'additional', 'dodatkowe'        => 'bi bi-plus-circle',
83            'system', 'systemowe'            => 'bi bi-gear',
84            'contact_info', 'kontakt'        => 'bi bi-telephone',
85            'address_info', 'adres'          => 'bi bi-geo-alt',
86            default                          => self::DEFAULT_ICON,
87        };
88    }
89
90    /**
91     * Filters and returns fields belonging to this section for a given view mode.
92     *
93     * @param array<FieldMetadata> $fields All module fields.
94     * @param string $viewMode Current view mode ('detail', 'edit', 'create').
95     * @param bool $isFirstSection Whether this is the first section (takes unassigned fields).
96     * @return array<FieldMetadata> List of visible fields in this section.
97     */
98    public function getVisibleFields(array $fields, string $viewMode, bool $isFirstSection = false): array
99    {
100        $visible = [];
101        foreach ($fields as $field) {
102            $isUnassigned = $field->sectionId === null || $field->sectionId === 0;
103            $matches = $field->sectionId === $this->id || ($isFirstSection && $isUnassigned);
104            if (!$matches) {
105                continue;
106            }
107
108            $isVisible = match ($viewMode) {
109                'detail' => $field->isVisibleInDetail(),
110                'edit'   => $field->isVisibleInEdit(),
111                default  => $field->isVisibleInCreate(),
112            };
113
114            if ($isVisible) {
115                $visible[] = $field;
116            }
117        }
118
119        return $visible;
120    }
121
122    /**
123     * Checks if this section contains any visible fields for the given view mode.
124     *
125     * @param array<FieldMetadata> $fields All module fields.
126     * @param string $viewMode Current view mode ('detail', 'edit', 'create').
127     * @param bool $isFirstSection Whether this is the first section.
128     * @return bool True if section has visible fields.
129     */
130    public function hasVisibleFields(array $fields, string $viewMode, bool $isFirstSection = false): bool
131    {
132        return $this->getVisibleFields($fields, $viewMode, $isFirstSection) !== [];
133    }
134
135    /**
136     * Converts value object to serializable array representation.
137     *
138     * @return array<string, mixed> Array representation.
139     */
140    public function toArray(): array
141    {
142        return [
143            'id'         => $this->id,
144            'module_id'  => $this->moduleId,
145            'name'       => $this->name,
146            'label'      => $this->label,
147            'icon_class' => $this->getIconClass(),
148            'sort_order' => $this->sortOrder,
149            'is_active'  => $this->isActive,
150            'is_system'  => $this->isSystem,
151            'is_default' => $this->isDefault,
152        ];
153    }
154}