Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
CRAP
100.00% covered (success)
100.00%
1 / 1
GroupController
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
1 / 1
 list
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3/**
4 * The Group Controller for the REST API
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2023-2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2023-07-29
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Controller\Api;
21
22use OpenApi\Attributes as OA;
23use phpMyFAQ\Permission\MediumPermission;
24use Symfony\Component\HttpFoundation\JsonResponse;
25use Symfony\Component\HttpFoundation\Request;
26use Symfony\Component\Routing\Attribute\Route;
27
28final class GroupController extends AbstractApiController
29{
30    #[OA\Get(
31        path: '/api/v4.0/groups',
32        operationId: 'getGroups',
33        description: 'Returns paginated list of group IDs.',
34        tags: ['Endpoints with Authentication'],
35    )]
36    #[OA\Header(
37        header: 'Accept-Language',
38        description: 'The language code for the login.',
39        schema: new OA\Schema(type: 'string'),
40    )]
41    #[OA\Parameter(
42        name: 'page',
43        description: 'Page number for pagination (page-based)',
44        in: 'query',
45        required: false,
46        schema: new OA\Schema(type: 'integer', default: 1),
47    )]
48    #[OA\Parameter(
49        name: 'per_page',
50        description: 'Items per page (page-based, max 100)',
51        in: 'query',
52        required: false,
53        schema: new OA\Schema(type: 'integer', default: 25),
54    )]
55    #[OA\Parameter(
56        name: 'limit',
57        description: 'Number of items to return (offset-based, max 100)',
58        in: 'query',
59        required: false,
60        schema: new OA\Schema(type: 'integer', default: 25),
61    )]
62    #[OA\Parameter(
63        name: 'offset',
64        description: 'Starting offset (offset-based)',
65        in: 'query',
66        required: false,
67        schema: new OA\Schema(type: 'integer', default: 0),
68    )]
69    #[OA\Parameter(
70        name: 'sort',
71        description: 'Field to sort by',
72        in: 'query',
73        required: false,
74        schema: new OA\Schema(type: 'string', default: 'group-id', enum: ['group-id']),
75    )]
76    #[OA\Parameter(
77        name: 'order',
78        description: 'Sort direction',
79        in: 'query',
80        required: false,
81        schema: new OA\Schema(type: 'string', default: 'asc', enum: ['asc', 'desc']),
82    )]
83    #[OA\Response(
84        response: 200,
85        description: 'Returns paginated list of group IDs.',
86        content: new OA\JsonContent(example: [
87            'success' => true,
88            'data' => [
89                ['group-id' => 1],
90                ['group-id' => 2],
91            ],
92            'meta' => [
93                'pagination' => [
94                    'total' => 50,
95                    'count' => 25,
96                    'per_page' => 25,
97                    'current_page' => 1,
98                    'total_pages' => 2,
99                    'links' => [
100                        'first' => '/api/v4.0/groups?page=1&per_page=25',
101                        'last' => '/api/v4.0/groups?page=2&per_page=25',
102                        'prev' => null,
103                        'next' => '/api/v4.0/groups?page=2&per_page=25',
104                    ],
105                ],
106                'sorting' => [
107                    'field' => 'group-id',
108                    'order' => 'asc',
109                ],
110            ],
111        ]),
112    )]
113    #[OA\Response(response: 401, description: 'If the user is not authenticated.')]
114    #[Route(path: 'v4.0/groups', name: 'api.groups.list', methods: ['GET'])]
115    public function list(?Request $request = null): JsonResponse
116    {
117        $this->userIsAuthenticated();
118        $request ??= Request::createFromGlobals();
119
120        $mediumPermission = new MediumPermission($this->configuration);
121        $allGroups = $mediumPermission->getAllGroups($this->currentUser);
122
123        // Get pagination and sorting parameters
124        $pagination = $this->getPaginationRequest($request);
125        $sort = $this->getSortRequest(
126            $request,
127            allowedFields: ['group-id'],
128            defaultField: 'group-id',
129            defaultOrder: 'asc',
130        );
131
132        $total = is_countable($allGroups) ? count($allGroups) : 0;
133
134        // Apply sorting if needed
135        if ($sort->getOrderSql() === 'DESC') {
136            usort($allGroups, static fn($a, $b) => (int) ($b['group-id'] ?? 0) <=> (int) ($a['group-id'] ?? 0));
137        }
138
139        // Apply pagination
140        $result = array_slice($allGroups, $pagination->offset, $pagination->limit);
141
142        return $this->paginatedResponse(
143            $request,
144            data: array_values($result),
145            total: $total,
146            pagination: $pagination,
147            options: new PaginatedResponseOptions(sort: $sort),
148        );
149    }
150}