Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
2 / 2
CRAP
100.00% covered (success)
100.00%
1 / 1
GlossaryController
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
2 / 2
6
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
 list
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3/**
4 * The Glossary 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 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     2026-01-03
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Controller\Api;
21
22use Exception;
23use OpenApi\Attributes as OA;
24use phpMyFAQ\Glossary;
25use phpMyFAQ\Language;
26use Symfony\Component\HttpFoundation\JsonResponse;
27use Symfony\Component\HttpFoundation\Request;
28use Symfony\Component\Routing\Attribute\Route;
29
30final class GlossaryController extends AbstractApiController
31{
32    public function __construct(
33        private readonly Glossary $glossary,
34        private readonly Language $language,
35    ) {
36        parent::__construct();
37    }
38
39    /**
40     * @throws Exception
41     */
42    #[OA\Get(
43        path: '/api/v4.0/glossary',
44        operationId: 'getGlossary',
45        description: 'Returns paginated glossary items.',
46        tags: ['Public Endpoints'],
47    )]
48    #[OA\Header(
49        header: 'Accept-Language',
50        description: 'The language code for the glossary items.',
51        schema: new OA\Schema(type: 'string'),
52    )]
53    #[OA\Parameter(
54        name: 'page',
55        description: 'Page number for pagination (page-based)',
56        in: 'query',
57        required: false,
58        schema: new OA\Schema(type: 'integer', default: 1),
59    )]
60    #[OA\Parameter(
61        name: 'per_page',
62        description: 'Items per page (page-based, max 100)',
63        in: 'query',
64        required: false,
65        schema: new OA\Schema(type: 'integer', default: 25),
66    )]
67    #[OA\Parameter(
68        name: 'limit',
69        description: 'Number of items to return (offset-based, max 100)',
70        in: 'query',
71        required: false,
72        schema: new OA\Schema(type: 'integer', default: 25),
73    )]
74    #[OA\Parameter(
75        name: 'offset',
76        description: 'Starting offset (offset-based)',
77        in: 'query',
78        required: false,
79        schema: new OA\Schema(type: 'integer', default: 0),
80    )]
81    #[OA\Parameter(
82        name: 'sort',
83        description: 'Field to sort by',
84        in: 'query',
85        required: false,
86        schema: new OA\Schema(type: 'string', default: 'item', enum: ['id', 'item', 'definition']),
87    )]
88    #[OA\Parameter(
89        name: 'order',
90        description: 'Sort direction',
91        in: 'query',
92        required: false,
93        schema: new OA\Schema(type: 'string', default: 'asc', enum: ['asc', 'desc']),
94    )]
95    #[OA\Response(
96        response: 200,
97        description: 'Returns paginated glossary items.',
98        content: new OA\JsonContent(example: [
99            'success' => true,
100            'data' => [
101                ['id' => 1, 'language' => 'en', 'item' => 'API', 'definition' => 'Application Programming Interface'],
102                ['id' => 2, 'language' => 'en', 'item' => 'FAQ', 'definition' => 'Frequently Asked Questions'],
103            ],
104            'meta' => [
105                'pagination' => [
106                    'total' => 50,
107                    'count' => 25,
108                    'per_page' => 25,
109                    'current_page' => 1,
110                    'total_pages' => 2,
111                    'links' => [
112                        'first' => '/api/v4.0/glossary?page=1&per_page=25',
113                        'last' => '/api/v4.0/glossary?page=2&per_page=25',
114                        'prev' => null,
115                        'next' => '/api/v4.0/glossary?page=2&per_page=25',
116                    ],
117                ],
118                'sorting' => [
119                    'field' => 'item',
120                    'order' => 'asc',
121                ],
122            ],
123        ]),
124    )]
125    #[Route(path: 'v4.0/glossary', name: 'api.glossary.list', methods: ['GET'])]
126    public function list(Request $request): JsonResponse
127    {
128        $currentLanguage = $this->language->setLanguageByAcceptLanguage();
129
130        if ($currentLanguage !== '') {
131            $this->glossary->setLanguage($currentLanguage);
132        }
133
134        // Get pagination and sorting parameters
135        $pagination = $this->getPaginationRequest($request);
136        $sort = $this->getSortRequest(
137            $request,
138            allowedFields: ['id', 'item', 'definition'],
139            defaultField: 'item',
140            defaultOrder: 'asc',
141        );
142
143        // Get all glossary items
144        $allItems = $this->glossary->fetchAll();
145        $total = is_countable($allItems) ? count($allItems) : 0;
146
147        // Apply sorting if needed
148        if ($sort->getField()) {
149            usort($allItems, static function ($a, $b) use ($sort) {
150                $field = (string) $sort->getField();
151                $aVal = $a[$field] ?? '';
152                $bVal = $b[$field] ?? '';
153                $result = $aVal <=> $bVal;
154                return $sort->getOrderSql() === 'DESC' ? -$result : $result;
155            });
156        }
157
158        // Apply pagination
159        $result = array_slice($allItems, $pagination->offset, $pagination->limit);
160
161        return $this->paginatedResponse(
162            $request,
163            data: array_values($result),
164            total: $total,
165            pagination: $pagination,
166            options: new PaginatedResponseOptions(sort: $sort),
167        );
168    }
169}