Skip to content

Commit 345a669

Browse files
authored
hlapi add reminders, rss feeds, and reservations
1 parent b142b13 commit 345a669

File tree

6 files changed

+541
-1
lines changed

6 files changed

+541
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The present file will list all changes made to the project; according to the
1212
- `approver`, `approval_followup`, `date_creation`, `date_mod`, and `date_approval` properties for the Solution schema in the High-Level API v2.1.
1313
- `timeline_position` property for the TicketValidation, ChangeValidation and Document_Item schemas in the High-Level API v2.1.
1414
- `date_solve`, `date_close`, and `global_validation` properties for the applicable Ticket, Change and Problem schemas in the High-Level API v2.1.
15+
- New schemas/endpoints for Reminders, RSS Feeds, and Reservations in the High-Level API v2.1.
1516

1617
### Changed
1718
- Added High-Level API version 2.1. Make sure you are pinning your requests to a specific version (Ex: `/api.php/v2.0`) if needed to exclude endpoints/properties added in later versions. See version pinning in the getting started documentation `/api.php/getting-started`.
Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
<?php
2+
3+
/**
4+
* ---------------------------------------------------------------------
5+
*
6+
* GLPI - Gestionnaire Libre de Parc Informatique
7+
*
8+
* http://glpi-project.org
9+
*
10+
* @copyright 2015-2025 Teclib' and contributors.
11+
* @copyright 2003-2014 by the INDEPNET Development Team.
12+
* @licence https://www.gnu.org/licenses/gpl-3.0.html
13+
*
14+
* ---------------------------------------------------------------------
15+
*
16+
* LICENSE
17+
*
18+
* This file is part of GLPI.
19+
*
20+
* This program is free software: you can redistribute it and/or modify
21+
* it under the terms of the GNU General Public License as published by
22+
* the Free Software Foundation, either version 3 of the License, or
23+
* (at your option) any later version.
24+
*
25+
* This program is distributed in the hope that it will be useful,
26+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
27+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28+
* GNU General Public License for more details.
29+
*
30+
* You should have received a copy of the GNU General Public License
31+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
32+
*
33+
* ---------------------------------------------------------------------
34+
*/
35+
36+
namespace Glpi\Api\HL\Controller;
37+
38+
use CommonDBTM;
39+
use Entity;
40+
use Glpi\Api\HL\Doc as Doc;
41+
use Glpi\Api\HL\Middleware\ResultFormatterMiddleware;
42+
use Glpi\Api\HL\ResourceAccessor;
43+
use Glpi\Api\HL\Route;
44+
use Glpi\Api\HL\RouteVersion;
45+
use Glpi\Http\JSONResponse;
46+
use Glpi\Http\Request;
47+
use Glpi\Http\Response;
48+
use Planning;
49+
use Reminder;
50+
use ReservationItem;
51+
use RSSFeed;
52+
use User;
53+
54+
#[Route(path: '/Tools', tags: ['Tools'])]
55+
final class ToolController extends AbstractController
56+
{
57+
public static function getRawKnownSchemas(): array
58+
{
59+
return [
60+
'Reminder' => [
61+
'x-version-introduced' => '2.1.0',
62+
'x-itemtype' => Reminder::class,
63+
'type' => Doc\Schema::TYPE_OBJECT,
64+
'properties' => [
65+
'id' => [
66+
'type' => Doc\Schema::TYPE_INTEGER,
67+
'format' => Doc\Schema::FORMAT_INTEGER_INT64,
68+
'readOnly' => true,
69+
],
70+
'uuid' => [
71+
'type' => Doc\Schema::TYPE_STRING,
72+
'pattern' => Doc\Schema::PATTERN_UUIDV4,
73+
'readOnly' => true,
74+
],
75+
'name' => ['type' => Doc\Schema::TYPE_STRING],
76+
'text' => ['type' => Doc\Schema::TYPE_STRING],
77+
'date' => [
78+
'type' => Doc\Schema::TYPE_STRING,
79+
'format' => Doc\Schema::FORMAT_STRING_DATE_TIME,
80+
],
81+
'user' => self::getDropdownTypeSchema(class: User::class, full_schema: 'User'),
82+
'date_begin' => [
83+
'type' => Doc\Schema::TYPE_STRING,
84+
'format' => Doc\Schema::FORMAT_STRING_DATE_TIME,
85+
'x-field' => 'begin',
86+
],
87+
'date_end' => [
88+
'type' => Doc\Schema::TYPE_STRING,
89+
'format' => Doc\Schema::FORMAT_STRING_DATE_TIME,
90+
'x-field' => 'end',
91+
],
92+
'is_planned' => ['type' => Doc\Schema::TYPE_BOOLEAN],
93+
'state' => [
94+
'type' => Doc\Schema::TYPE_INTEGER,
95+
'enum' => [Planning::INFO, Planning::TODO, Planning::DONE],
96+
'description' => <<<EOT
97+
The state of the event.
98+
- 1: Information
99+
- 2: To do
100+
- 3: Done
101+
EOT,
102+
],
103+
'date_view_begin' => [
104+
'type' => Doc\Schema::TYPE_STRING,
105+
'format' => Doc\Schema::FORMAT_STRING_DATE_TIME,
106+
'x-field' => 'begin_view_date',
107+
],
108+
'date_view_end' => [
109+
'type' => Doc\Schema::TYPE_STRING,
110+
'format' => Doc\Schema::FORMAT_STRING_DATE_TIME,
111+
'x-field' => 'end_view_date',
112+
],
113+
'date_creation' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME],
114+
'date_mod' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME],
115+
],
116+
],
117+
'RSSFeed' => [
118+
'x-version-introduced' => '2.1.0',
119+
'x-itemtype' => RSSFeed::class,
120+
'type' => Doc\Schema::TYPE_OBJECT,
121+
'properties' => [
122+
'id' => [
123+
'type' => Doc\Schema::TYPE_INTEGER,
124+
'format' => Doc\Schema::FORMAT_INTEGER_INT64,
125+
'readOnly' => true,
126+
],
127+
'comment' => ['type' => Doc\Schema::TYPE_STRING],
128+
'url' => [
129+
'type' => Doc\Schema::TYPE_STRING,
130+
'required' => true,
131+
],
132+
'refresh_interval' => [
133+
'type' => Doc\Schema::TYPE_INTEGER,
134+
'description' => 'Refresh interval in seconds',
135+
'x-field' => 'refresh_rate',
136+
'min' => HOUR_TIMESTAMP,
137+
'max' => DAY_TIMESTAMP,
138+
'multipleOf' => HOUR_TIMESTAMP,
139+
],
140+
'max_items' => [
141+
'type' => Doc\Schema::TYPE_INTEGER,
142+
'description' => 'Maximum number of items to fetch',
143+
],
144+
'have_error' => [
145+
'type' => Doc\Schema::TYPE_BOOLEAN,
146+
'readOnly' => true,
147+
'description' => 'Whether the last fetch had errors',
148+
],
149+
'is_active' => ['type' => Doc\Schema::TYPE_BOOLEAN],
150+
'user' => self::getDropdownTypeSchema(class: User::class, full_schema: 'User'),
151+
'date_creation' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME],
152+
'date_mod' => ['type' => Doc\Schema::TYPE_STRING, 'format' => Doc\Schema::FORMAT_STRING_DATE_TIME],
153+
],
154+
],
155+
'ReservableItem' => [
156+
'x-version-introduced' => '2.1.0',
157+
'x-itemtype' => ReservationItem::class,
158+
'type' => Doc\Schema::TYPE_OBJECT,
159+
'properties' => [
160+
'id' => [
161+
'type' => Doc\Schema::TYPE_INTEGER,
162+
'format' => Doc\Schema::FORMAT_INTEGER_INT64,
163+
'readOnly' => true,
164+
],
165+
'itemtype' => [
166+
'type' => Doc\Schema::TYPE_STRING,
167+
'description' => 'The itemtype of the reservable item',
168+
],
169+
'items_id' => [
170+
'type' => Doc\Schema::TYPE_INTEGER,
171+
'format' => Doc\Schema::FORMAT_INTEGER_INT64,
172+
'description' => 'The ID of the reservable item',
173+
],
174+
'comment' => ['type' => Doc\Schema::TYPE_STRING],
175+
'entity' => self::getDropdownTypeSchema(class: Entity::class, full_schema: 'Entity'),
176+
'is_recursive' => ['type' => Doc\Schema::TYPE_BOOLEAN],
177+
'is_active' => [
178+
'type' => Doc\Schema::TYPE_BOOLEAN,
179+
'description' => 'Whether the item is currently active for reservations',
180+
],
181+
],
182+
],
183+
'Reservation' => [
184+
'x-version-introduced' => '2.1.0',
185+
'x-itemtype' => ReservationItem::class,
186+
'type' => Doc\Schema::TYPE_OBJECT,
187+
'properties' => [
188+
'id' => [
189+
'type' => Doc\Schema::TYPE_INTEGER,
190+
'format' => Doc\Schema::FORMAT_INTEGER_INT64,
191+
'readOnly' => true,
192+
],
193+
'reservable_item' => [
194+
'type' => Doc\Schema::TYPE_OBJECT,
195+
'x-field' => 'reservationitems_id',
196+
'x-itemtype' => ReservationItem::class,
197+
'x-join' => [
198+
'table' => ReservationItem::getTable(),
199+
'fkey' => 'reservationitems_id',
200+
'field' => 'id',
201+
],
202+
'properties' => [
203+
'id' => [
204+
'type' => Doc\Schema::TYPE_INTEGER,
205+
'format' => Doc\Schema::FORMAT_INTEGER_INT64,
206+
'readOnly' => true,
207+
],
208+
'itemtype' => [
209+
'type' => Doc\Schema::TYPE_STRING,
210+
'description' => 'The itemtype of the reservable item',
211+
],
212+
'items_id' => [
213+
'type' => Doc\Schema::TYPE_INTEGER,
214+
'format' => Doc\Schema::FORMAT_INTEGER_INT64,
215+
'description' => 'The ID of the reservable item',
216+
],
217+
],
218+
],
219+
'comment' => ['type' => Doc\Schema::TYPE_STRING],
220+
'user' => self::getDropdownTypeSchema(class: User::class, full_schema: 'User'),
221+
'group' => [
222+
'type' => Doc\Schema::TYPE_INTEGER,
223+
'format' => Doc\Schema::FORMAT_INTEGER_INT64,
224+
'description' => 'A random number used to identify reservations that are part of the same series (recurring reservations)',
225+
],
226+
'date_begin' => [
227+
'type' => Doc\Schema::TYPE_STRING,
228+
'format' => Doc\Schema::FORMAT_STRING_DATE_TIME,
229+
'x-field' => 'begin',
230+
],
231+
'date_end' => [
232+
'type' => Doc\Schema::TYPE_STRING,
233+
'format' => Doc\Schema::FORMAT_STRING_DATE_TIME,
234+
'x-field' => 'end',
235+
],
236+
],
237+
],
238+
];
239+
}
240+
241+
/**
242+
* @param bool $types_only If true, only the type names are returned. If false, the type name => localized name pairs are returned.
243+
* @return array<class-string<CommonDBTM>, string>
244+
*/
245+
public static function getToolTypes(bool $types_only = true): array
246+
{
247+
static $types = null;
248+
249+
if ($types === null) {
250+
$types = [
251+
'Reminder' => Reminder::getTypeName(1),
252+
'RSSFeed' => RSSFeed::getTypeName(1),
253+
];
254+
}
255+
return $types_only ? array_keys($types) : $types;
256+
}
257+
258+
#[Route(path: '/', methods: ['GET'], middlewares: [ResultFormatterMiddleware::class])]
259+
#[RouteVersion(introduced: '2.1')]
260+
#[Doc\Route(
261+
description: 'Get all available tool types',
262+
responses: [
263+
new Doc\Response(new Doc\Schema(
264+
type: Doc\Schema::TYPE_ARRAY,
265+
items: new Doc\Schema(
266+
type: Doc\Schema::TYPE_OBJECT,
267+
properties: [
268+
'itemtype' => new Doc\Schema(Doc\Schema::TYPE_STRING),
269+
'name' => new Doc\Schema(Doc\Schema::TYPE_STRING),
270+
'href' => new Doc\Schema(Doc\Schema::TYPE_STRING),
271+
]
272+
)
273+
)),
274+
]
275+
)]
276+
public function index(Request $request): Response
277+
{
278+
$tool_types = self::getToolTypes(false);
279+
$tool_paths = [];
280+
foreach ($tool_types as $tool_type => $tool_name) {
281+
$tool_paths[] = [
282+
'itemtype' => $tool_type,
283+
'name' => $tool_name,
284+
'href' => self::getAPIPathForRouteFunction(self::class, 'search', ['itemtype' => $tool_type]),
285+
];
286+
}
287+
return new JSONResponse($tool_paths);
288+
}
289+
290+
#[Route(path: '/{itemtype}', methods: ['GET'], requirements: [
291+
'itemtype' => [self::class, 'getToolTypes'],
292+
], middlewares: [ResultFormatterMiddleware::class])]
293+
#[RouteVersion(introduced: '2.1')]
294+
#[Doc\SearchRoute(schema_name: '{itemtype}')]
295+
public function search(Request $request): Response
296+
{
297+
$itemtype = $request->getAttribute('itemtype');
298+
return ResourceAccessor::searchBySchema($this->getKnownSchema($itemtype, $this->getAPIVersion($request)), $request->getParameters());
299+
}
300+
301+
#[Route(path: '/{itemtype}/{id}', methods: ['GET'], requirements: [
302+
'itemtype' => [self::class, 'getToolTypes'],
303+
'id' => '\d+',
304+
], middlewares: [ResultFormatterMiddleware::class])]
305+
#[RouteVersion(introduced: '2.1')]
306+
#[Doc\GetRoute(schema_name: '{itemtype}')]
307+
public function getItem(Request $request): Response
308+
{
309+
$itemtype = $request->getAttribute('itemtype');
310+
return ResourceAccessor::getOneBySchema($this->getKnownSchema($itemtype, $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
311+
}
312+
313+
#[Route(path: '/{itemtype}', methods: ['POST'], requirements: [
314+
'itemtype' => [self::class, 'getToolTypes'],
315+
])]
316+
#[RouteVersion(introduced: '2.1')]
317+
#[Doc\CreateRoute(schema_name: '{itemtype}')]
318+
public function createItem(Request $request): Response
319+
{
320+
$itemtype = $request->getAttribute('itemtype');
321+
return ResourceAccessor::createBySchema($this->getKnownSchema($itemtype, $this->getAPIVersion($request)), $request->getParameters() + ['itemtype' => $itemtype], [self::class, 'getItem']);
322+
}
323+
324+
#[Route(path: '/{itemtype}/{id}', methods: ['PATCH'], requirements: [
325+
'itemtype' => [self::class, 'getToolTypes'],
326+
'id' => '\d+',
327+
])]
328+
#[RouteVersion(introduced: '2.1')]
329+
#[Doc\UpdateRoute(schema_name: '{itemtype}')]
330+
public function updateItem(Request $request): Response
331+
{
332+
$itemtype = $request->getAttribute('itemtype');
333+
return ResourceAccessor::updateBySchema($this->getKnownSchema($itemtype, $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
334+
}
335+
336+
#[Route(path: '/{itemtype}/{id}', methods: ['DELETE'], requirements: [
337+
'itemtype' => [self::class, 'getToolTypes'],
338+
'id' => '\d+',
339+
])]
340+
#[RouteVersion(introduced: '2.1')]
341+
#[Doc\DeleteRoute(schema_name: '{itemtype}')]
342+
public function deleteItem(Request $request): Response
343+
{
344+
$itemtype = $request->getAttribute('itemtype');
345+
return ResourceAccessor::deleteBySchema($this->getKnownSchema($itemtype, $this->getAPIVersion($request)), $request->getAttributes(), $request->getParameters());
346+
}
347+
}

src/Glpi/Api/HL/OpenAPIGenerator.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,32 @@ public static function getComponentSchemas(string $api_version): array
168168
$itemtype = $known_schema['x-itemtype'];
169169
$known_schema['description'] = $itemtype::getTypeName(1);
170170
}
171+
172+
// Add properties that have 'required' flags to a 'required' array on the nearest parent object
173+
// We add the 'required' on individual properties so that it works well with the API version filtering
174+
$fn_hoist_required_flags = static function (&$schema_part) use (&$fn_hoist_required_flags) {
175+
if (is_array($schema_part)) {
176+
if (isset($schema_part['properties']) && is_array($schema_part['properties'])) {
177+
$required_fields = [];
178+
foreach ($schema_part['properties'] as $prop_name => &$prop_value) {
179+
if (is_array($prop_value)) {
180+
if (isset($prop_value['required']) && $prop_value['required'] === true) {
181+
$required_fields[] = $prop_name;
182+
unset($prop_value['required']);
183+
}
184+
// Recurse into the property value
185+
$fn_hoist_required_flags($prop_value);
186+
}
187+
}
188+
unset($prop_value);
189+
if (count($required_fields) > 0) {
190+
$schema_part['required'] = $required_fields;
191+
}
192+
}
193+
}
194+
};
195+
$fn_hoist_required_flags($known_schema);
196+
171197
$schemas[$calculated_name] = $known_schema;
172198
$schemas[$calculated_name]['x-controller'] = $controller::class;
173199
$schemas[$calculated_name]['x-schemaname'] = $schema_name;

0 commit comments

Comments
 (0)