Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.70% covered (success)
98.70%
76 / 77
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
WidgetListQueryService
98.68% covered (success)
98.68%
75 / 76
50.00% covered (danger)
50.00%
1 / 2
12
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
 fetchListWidgetData
98.67% covered (success)
98.67%
74 / 75
0.00% covered (danger)
0.00%
0 / 1
11
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\Grid\Widget\Application;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Engine\Application\Query\UniversalQueryBuilder;
12use App\Core\Engine\Domain\Model\FieldMetadata;
13use App\Core\Engine\Domain\Model\FilterMetadata;
14use App\Core\Engine\Domain\Model\PermissionContext;
15use App\Core\Engine\Domain\Repository\MetadataRepositoryInterface;
16use App\Core\Grid\GridRequest;
17use App\Core\Grid\GridResult;
18
19/**
20 * Widget List Query Service.
21 *
22 * Fetches structured record sets for custom mini-list dashboard and grid tiles.
23 *
24 * @package App\Core\Grid\Widget\Application
25 */
26final readonly class WidgetListQueryService implements WidgetListQueryServiceInterface
27{
28    /**
29     * WidgetListQueryService constructor.
30     *
31     * @param MetadataRepositoryInterface $metadataRepository Metadata repository.
32     * @param UniversalQueryBuilder       $queryBuilder       Universal query builder.
33     */
34    public function __construct(
35        private MetadataRepositoryInterface $metadataRepository,
36        private UniversalQueryBuilder       $queryBuilder
37    ) {
38    }
39
40    /**
41     * Executes query and returns records with metadata for widget list rendering.
42     *
43     * @param array<string, mixed> $params  Widget parameters.
44     * @param PermissionContext   $context Security context.
45     * @return array{
46     *     records: list<array<string, mixed>>,
47     *     total: int,
48     *     columns: list<FieldMetadata>,
49     *     module_name: string,
50     *     route_url: string
51     * }
52     */
53    public function fetchListWidgetData(array $params, PermissionContext $context): array
54    {
55        $moduleName = (string) ($params['source_module'] ?? $params['module'] ?? '');
56        if ($moduleName === '') {
57            return [
58                'records'     => [],
59                'total'       => 0,
60                'columns'     => [],
61                'module_name' => '',
62                'route_url'   => '',
63            ];
64        }
65
66        try {
67            $module = $this->metadataRepository->findModule($moduleName);
68            $allFields = $this->metadataRepository->findFields($module->id);
69
70            $filterId = isset($params['filter_id']) ? (int) $params['filter_id'] : null;
71            $filter = $this->metadataRepository->findFilter($module->id, $filterId);
72
73            $limit = max(1, min(25, (int) ($params['limit'] ?? 5)));
74            $page = max(1, (int) ($params['page'] ?? 1));
75            $gridRequest = new GridRequest($page, $limit);
76
77            $userScope = (string) ($params['user_scope'] ?? 'all');
78            $applyOwner = ($userScope === 'current_user' || $userScope === 'owner_and_co_owners');
79
80            if ($userScope === 'owner_only') {
81                $conditions = $filter->conditions;
82                $conditions[] = [
83                    'field'    => 'owner',
84                    'operator' => 'eq',
85                    'value'    => $context->actorUserId,
86                ];
87                $filter = new FilterMetadata(
88                    id: $filter->id,
89                    moduleId: $filter->moduleId,
90                    name: $filter->name,
91                    label: $filter->label,
92                    visibleFields: $filter->visibleFields,
93                    defaultSort: $filter->defaultSort,
94                    defaultOrder: $filter->defaultOrder,
95                    perPage: $filter->perPage,
96                    isDefault: $filter->isDefault,
97                    isSystem: $filter->isSystem,
98                    scope: $filter->scope,
99                    owner: $filter->owner,
100                    coOwners: $filter->coOwners,
101                    conditions: $conditions,
102                    iconClass: $filter->iconClass,
103                    recordStatus: $filter->recordStatus,
104                );
105            }
106
107            /** @var GridResult $gridResult */
108            $gridResult = $this->queryBuilder->buildAndExecute(
109                $module,
110                $allFields,
111                $filter,
112                $gridRequest,
113                $context,
114                $applyOwner
115            );
116
117            $visibleColKeys = is_array($params['columns'] ?? null) && !empty($params['columns'])
118                ? $params['columns']
119                : array_slice($filter->visibleFields, 0, 4);
120
121            $selectedFields = [];
122            foreach ($allFields as $f) {
123                if (in_array($f->fieldKey, $visibleColKeys, true)) {
124                    $selectedFields[] = $f;
125                }
126            }
127
128            if (empty($selectedFields)) {
129                $selectedFields = array_slice($allFields, 0, 3);
130            }
131
132            return [
133                'records'     => $gridResult->rows,
134                'total'       => $gridResult->totalRecords,
135                'columns'     => $selectedFields,
136                'module_name' => $module->name,
137                'route_url'   => $module->routeUrl ?? ('/' . str_replace('_', '-', $module->name)),
138            ];
139        } catch (\Throwable) {
140            return [
141                'records'     => [],
142                'total'       => 0,
143                'columns'     => [],
144                'module_name' => '',
145                'route_url'   => '',
146            ];
147        }
148    }
149}