]> git.immae.eu Git - github/wallabag/wallabag.git/blame - src/Wallabag/ApiBundle/Controller/EntryRestController.php
Hash the urls to check if they exist
[github/wallabag/wallabag.git] / src / Wallabag / ApiBundle / Controller / EntryRestController.php
CommitLineData
900c8448
NL
1<?php
2
3namespace Wallabag\ApiBundle\Controller;
4
5use Hateoas\Configuration\Route;
6use Hateoas\Representation\Factory\PagerfantaFactory;
7use Nelmio\ApiDocBundle\Annotation\ApiDoc;
72db15ca 8use Symfony\Component\HttpKernel\Exception\HttpException;
900c8448
NL
9use Symfony\Component\HttpFoundation\Request;
10use Symfony\Component\HttpFoundation\JsonResponse;
11use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
12use Wallabag\CoreBundle\Entity\Entry;
13use Wallabag\CoreBundle\Entity\Tag;
5a619812
JB
14use Wallabag\CoreBundle\Event\EntrySavedEvent;
15use Wallabag\CoreBundle\Event\EntryDeletedEvent;
900c8448
NL
16
17class EntryRestController extends WallabagRestController
18{
19 /**
20 * Check if an entry exist by url.
21 *
22 * @ApiDoc(
23 * parameters={
d5fbb570
TC
24 * {"name"="hashedurl", "dataType"="string", "required"=true, "format"="An url", "description"="SHA512 Url to check if it exists"},
25 * {"name"="hashedurls", "dataType"="string", "required"=false, "format"="An array of urls (?urls[]=http...&urls[]=http...)", "description"="SHA512 Urls (as an array) to check if it exists"}
900c8448
NL
26 * }
27 * )
28 *
29 * @return JsonResponse
30 */
31 public function getEntriesExistsAction(Request $request)
32 {
33 $this->validateAuthentication();
34
d5fbb570 35 $hashedUrls = $request->query->get('hashedurls', []);
900c8448
NL
36
37 // handle multiple urls first
d5fbb570 38 if (!empty($hashedUrls)) {
900c8448 39 $results = [];
d5fbb570 40 foreach ($hashedUrls as $hashedUrl) {
900c8448
NL
41 $res = $this->getDoctrine()
42 ->getRepository('WallabagCoreBundle:Entry')
d5fbb570
TC
43 ->findOneBy([
44 'hashedUrl' => $hashedUrl,
45 'user' => $this->getUser()->getId(),
46 ]);
900c8448 47
d5fbb570 48 $results[$hashedUrl] = $res instanceof Entry ? $res->getId() : false;
900c8448
NL
49 }
50
72db15ca 51 return $this->sendResponse($results);
900c8448
NL
52 }
53
54 // let's see if it is a simple url?
d5fbb570 55 $hashedUrl = $request->query->get('hashedurl', '');
900c8448 56
d5fbb570 57 if (empty($hashedUrl)) {
900c8448
NL
58 throw $this->createAccessDeniedException('URL is empty?, logged user id: '.$this->getUser()->getId());
59 }
60
61 $res = $this->getDoctrine()
62 ->getRepository('WallabagCoreBundle:Entry')
d5fbb570
TC
63 ->findOneBy([
64 'hashedUrl' => $hashedUrl,
65 'user' => $this->getUser()->getId(),
66 ]);
ca9a83ee 67 $exists = $res instanceof Entry ? $res->getId() : false;
900c8448 68
72db15ca 69 return $this->sendResponse(['exists' => $exists]);
900c8448
NL
70 }
71
72 /**
73 * Retrieve all entries. It could be filtered by many options.
74 *
75 * @ApiDoc(
76 * parameters={
77 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by archived status."},
78 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by starred status."},
79 * {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated', default 'created'", "description"="sort entries by date."},
80 * {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."},
81 * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
82 * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."},
83 * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."},
84 * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."},
85 * }
86 * )
87 *
88 * @return JsonResponse
89 */
90 public function getEntriesAction(Request $request)
91 {
92 $this->validateAuthentication();
93
94 $isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive');
95 $isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred');
96 $sort = $request->query->get('sort', 'created');
97 $order = $request->query->get('order', 'desc');
98 $page = (int) $request->query->get('page', 1);
99 $perPage = (int) $request->query->get('perPage', 30);
100 $tags = $request->query->get('tags', '');
101 $since = $request->query->get('since', 0);
102
b60a666d 103 /** @var \Pagerfanta\Pagerfanta $pager */
900c8448
NL
104 $pager = $this->getDoctrine()
105 ->getRepository('WallabagCoreBundle:Entry')
106 ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order, $since, $tags);
107
900c8448 108 $pager->setMaxPerPage($perPage);
b60a666d 109 $pager->setCurrentPage($page);
900c8448
NL
110
111 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
112 $paginatedCollection = $pagerfantaFactory->createRepresentation(
113 $pager,
114 new Route(
115 'api_get_entries',
116 [
117 'archive' => $isArchived,
118 'starred' => $isStarred,
119 'sort' => $sort,
120 'order' => $order,
121 'page' => $page,
122 'perPage' => $perPage,
123 'tags' => $tags,
124 'since' => $since,
125 ],
126 UrlGeneratorInterface::ABSOLUTE_URL
127 )
128 );
129
72db15ca 130 return $this->sendResponse($paginatedCollection);
900c8448
NL
131 }
132
133 /**
134 * Retrieve a single entry.
135 *
136 * @ApiDoc(
137 * requirements={
138 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
139 * }
140 * )
141 *
142 * @return JsonResponse
143 */
144 public function getEntryAction(Entry $entry)
145 {
146 $this->validateAuthentication();
147 $this->validateUserAccess($entry->getUser()->getId());
148
72db15ca 149 return $this->sendResponse($entry);
900c8448
NL
150 }
151
864c1dd2
JB
152 /**
153 * Retrieve a single entry as a predefined format.
154 *
155 * @ApiDoc(
156 * requirements={
157 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
158 * }
159 * )
160 *
161 * @return Response
162 */
163 public function getEntryExportAction(Entry $entry, Request $request)
164 {
165 $this->validateAuthentication();
166 $this->validateUserAccess($entry->getUser()->getId());
167
168 return $this->get('wallabag_core.helper.entries_export')
169 ->setEntries($entry)
170 ->updateTitle('entry')
171 ->exportAs($request->attributes->get('_format'));
172 }
173
1eca7831 174 /**
a7abcc7b 175 * Handles an entries list and delete URL.
1eca7831
NL
176 *
177 * @ApiDoc(
178 * parameters={
a7abcc7b 179 * {"name"="urls", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...'}, {'url': 'http://...'}]", "description"="Urls (as an array) to delete."}
1eca7831
NL
180 * }
181 * )
182 *
183 * @return JsonResponse
184 */
a7abcc7b 185 public function deleteEntriesListAction(Request $request)
1eca7831
NL
186 {
187 $this->validateAuthentication();
188
a7abcc7b 189 $urls = json_decode($request->query->get('urls', []));
72db15ca
JB
190
191 if (empty($urls)) {
192 return $this->sendResponse([]);
193 }
194
1eca7831
NL
195 $results = [];
196
197 // handle multiple urls
72db15ca
JB
198 foreach ($urls as $key => $url) {
199 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
200 $url,
201 $this->getUser()->getId()
202 );
a7abcc7b 203
72db15ca 204 $results[$key]['url'] = $url;
a7abcc7b 205
72db15ca
JB
206 if (false !== $entry) {
207 $em = $this->getDoctrine()->getManager();
208 $em->remove($entry);
209 $em->flush();
1eca7831 210
72db15ca
JB
211 // entry deleted, dispatch event about it!
212 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
a7abcc7b 213 }
1eca7831 214
72db15ca
JB
215 $results[$key]['entry'] = $entry instanceof Entry ? true : false;
216 }
1eca7831 217
72db15ca 218 return $this->sendResponse($results);
a7abcc7b 219 }
1eca7831 220
a7abcc7b
NL
221 /**
222 * Handles an entries list and create URL.
223 *
224 * @ApiDoc(
225 * parameters={
226 * {"name"="urls", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...'}, {'url': 'http://...'}]", "description"="Urls (as an array) to create."}
227 * }
228 * )
229 *
230 * @return JsonResponse
efd351c9 231 *
72db15ca 232 * @throws HttpException When limit is reached
a7abcc7b
NL
233 */
234 public function postEntriesListAction(Request $request)
235 {
236 $this->validateAuthentication();
1eca7831 237
a7abcc7b
NL
238 $urls = json_decode($request->query->get('urls', []));
239 $results = [];
1eca7831 240
efd351c9
NL
241 $limit = $this->container->getParameter('wallabag_core.api_limit_mass_actions');
242
243 if (count($urls) > $limit) {
72db15ca 244 throw new HttpException(400, 'API limit reached');
efd351c9
NL
245 }
246
a7abcc7b
NL
247 // handle multiple urls
248 if (!empty($urls)) {
a7abcc7b
NL
249 foreach ($urls as $key => $url) {
250 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
251 $url,
252 $this->getUser()->getId()
253 );
1eca7831 254
a7abcc7b 255 $results[$key]['url'] = $url;
1eca7831 256
a7abcc7b
NL
257 if (false === $entry) {
258 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
259 new Entry($this->getUser()),
260 $url
261 );
1eca7831 262 }
a7abcc7b
NL
263
264 $em = $this->getDoctrine()->getManager();
265 $em->persist($entry);
266 $em->flush();
267
268 $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
269
270 // entry saved, dispatch event about it!
271 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
1eca7831
NL
272 }
273 }
274
72db15ca 275 return $this->sendResponse($results);
1eca7831
NL
276 }
277
900c8448
NL
278 /**
279 * Create an entry.
280 *
281 * @ApiDoc(
282 * parameters={
283 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."},
284 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
285 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
286 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"},
287 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"},
288 * }
289 * )
290 *
291 * @return JsonResponse
292 */
293 public function postEntriesAction(Request $request)
294 {
295 $this->validateAuthentication();
296
297 $url = $request->request->get('url');
298 $title = $request->request->get('title');
299 $isArchived = $request->request->get('archive');
300 $isStarred = $request->request->get('starred');
301
302 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId());
303
304 if (false === $entry) {
08f29ae7 305 $entry = new Entry($this->getUser());
306 try {
307 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
308 $entry,
309 $url
310 );
311 } catch (\Exception $e) {
312 $this->get('logger')->error('Error while saving an entry', [
313 'exception' => $e,
314 'entry' => $entry,
315 ]);
316 $entry->setUrl($url);
317 }
900c8448
NL
318 }
319
320 if (!is_null($title)) {
321 $entry->setTitle($title);
322 }
323
324 $tags = $request->request->get('tags', '');
325 if (!empty($tags)) {
326 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
327 }
328
329 if (!is_null($isStarred)) {
330 $entry->setStarred((bool) $isStarred);
331 }
332
333 if (!is_null($isArchived)) {
334 $entry->setArchived((bool) $isArchived);
335 }
336
337 $em = $this->getDoctrine()->getManager();
338 $em->persist($entry);
900c8448
NL
339 $em->flush();
340
5a619812
JB
341 // entry saved, dispatch event about it!
342 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
343
72db15ca 344 return $this->sendResponse($entry);
900c8448
NL
345 }
346
347 /**
348 * Change several properties of an entry.
349 *
350 * @ApiDoc(
351 * requirements={
352 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
353 * },
354 * parameters={
355 * {"name"="title", "dataType"="string", "required"=false},
356 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
357 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."},
358 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."},
359 * }
360 * )
361 *
362 * @return JsonResponse
363 */
364 public function patchEntriesAction(Entry $entry, Request $request)
365 {
366 $this->validateAuthentication();
367 $this->validateUserAccess($entry->getUser()->getId());
368
369 $title = $request->request->get('title');
370 $isArchived = $request->request->get('archive');
371 $isStarred = $request->request->get('starred');
372
373 if (!is_null($title)) {
374 $entry->setTitle($title);
375 }
376
377 if (!is_null($isArchived)) {
378 $entry->setArchived((bool) $isArchived);
379 }
380
381 if (!is_null($isStarred)) {
382 $entry->setStarred((bool) $isStarred);
383 }
384
385 $tags = $request->request->get('tags', '');
386 if (!empty($tags)) {
387 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
388 }
389
390 $em = $this->getDoctrine()->getManager();
391 $em->flush();
392
72db15ca 393 return $this->sendResponse($entry);
900c8448
NL
394 }
395
0a6f4568
JB
396 /**
397 * Reload an entry.
5cd0857e 398 * An empty response with HTTP Status 304 will be send if we weren't able to update the content (because it hasn't changed or we got an error).
0a6f4568
JB
399 *
400 * @ApiDoc(
401 * requirements={
402 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
403 * }
404 * )
405 *
406 * @return JsonResponse
407 */
408 public function patchEntriesReloadAction(Entry $entry)
409 {
410 $this->validateAuthentication();
411 $this->validateUserAccess($entry->getUser()->getId());
412
0a6f4568
JB
413 try {
414 $entry = $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl());
415 } catch (\Exception $e) {
416 $this->get('logger')->error('Error while saving an entry', [
417 'exception' => $e,
418 'entry' => $entry,
419 ]);
420
5cd0857e 421 return new JsonResponse([], 304);
0a6f4568
JB
422 }
423
424 // if refreshing entry failed, don't save it
425 if ($this->getParameter('wallabag_core.fetching_error_message') === $entry->getContent()) {
5cd0857e 426 return new JsonResponse([], 304);
0a6f4568
JB
427 }
428
429 $em = $this->getDoctrine()->getManager();
430 $em->persist($entry);
431 $em->flush();
432
433 // entry saved, dispatch event about it!
434 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
435
72db15ca 436 return $this->sendResponse($entry);
0a6f4568
JB
437 }
438
900c8448
NL
439 /**
440 * Delete **permanently** an entry.
441 *
442 * @ApiDoc(
443 * requirements={
444 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
445 * }
446 * )
447 *
448 * @return JsonResponse
449 */
450 public function deleteEntriesAction(Entry $entry)
451 {
452 $this->validateAuthentication();
453 $this->validateUserAccess($entry->getUser()->getId());
454
455 $em = $this->getDoctrine()->getManager();
456 $em->remove($entry);
457 $em->flush();
458
5a619812
JB
459 // entry deleted, dispatch event about it!
460 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
461
72db15ca 462 return $this->sendResponse($entry);
900c8448
NL
463 }
464
465 /**
466 * Retrieve all tags for an entry.
467 *
468 * @ApiDoc(
469 * requirements={
470 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
471 * }
472 * )
473 *
474 * @return JsonResponse
475 */
476 public function getEntriesTagsAction(Entry $entry)
477 {
478 $this->validateAuthentication();
479 $this->validateUserAccess($entry->getUser()->getId());
480
72db15ca 481 return $this->sendResponse($entry->getTags());
900c8448
NL
482 }
483
484 /**
485 * Add one or more tags to an entry.
486 *
487 * @ApiDoc(
488 * requirements={
489 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
490 * },
491 * parameters={
492 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
493 * }
494 * )
495 *
496 * @return JsonResponse
497 */
498 public function postEntriesTagsAction(Request $request, Entry $entry)
499 {
500 $this->validateAuthentication();
501 $this->validateUserAccess($entry->getUser()->getId());
502
503 $tags = $request->request->get('tags', '');
504 if (!empty($tags)) {
505 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
506 }
507
508 $em = $this->getDoctrine()->getManager();
509 $em->persist($entry);
510 $em->flush();
511
72db15ca 512 return $this->sendResponse($entry);
900c8448
NL
513 }
514
515 /**
516 * Permanently remove one tag for an entry.
517 *
518 * @ApiDoc(
519 * requirements={
520 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag ID"},
521 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
522 * }
523 * )
524 *
525 * @return JsonResponse
526 */
527 public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
528 {
529 $this->validateAuthentication();
530 $this->validateUserAccess($entry->getUser()->getId());
531
532 $entry->removeTag($tag);
533 $em = $this->getDoctrine()->getManager();
534 $em->persist($entry);
535 $em->flush();
536
72db15ca 537 return $this->sendResponse($entry);
900c8448 538 }
d1fc5902
NL
539
540 /**
80299ed2 541 * Handles an entries list delete tags from them.
d1fc5902
NL
542 *
543 * @ApiDoc(
544 * parameters={
80299ed2 545 * {"name"="list", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...','tags': 'tag1, tag2'}, {'url': 'http://...','tags': 'tag1, tag2'}]", "description"="Urls (as an array) to handle."}
d1fc5902
NL
546 * }
547 * )
548 *
549 * @return JsonResponse
550 */
80299ed2 551 public function deleteEntriesTagsListAction(Request $request)
d1fc5902
NL
552 {
553 $this->validateAuthentication();
554
555 $list = json_decode($request->query->get('list', []));
72db15ca
JB
556
557 if (empty($list)) {
558 return $this->sendResponse([]);
559 }
d1fc5902
NL
560
561 // handle multiple urls
72db15ca 562 $results = [];
d1fc5902 563
72db15ca
JB
564 foreach ($list as $key => $element) {
565 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
566 $element->url,
567 $this->getUser()->getId()
568 );
d1fc5902 569
72db15ca
JB
570 $results[$key]['url'] = $element->url;
571 $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
d1fc5902 572
72db15ca 573 $tags = $element->tags;
80299ed2 574
72db15ca
JB
575 if (false !== $entry && !(empty($tags))) {
576 $tags = explode(',', $tags);
577 foreach ($tags as $label) {
578 $label = trim($label);
80299ed2 579
72db15ca
JB
580 $tag = $this->getDoctrine()
581 ->getRepository('WallabagCoreBundle:Tag')
582 ->findOneByLabel($label);
d1fc5902 583
72db15ca
JB
584 if (false !== $tag) {
585 $entry->removeTag($tag);
586 }
d1fc5902 587 }
72db15ca
JB
588
589 $em = $this->getDoctrine()->getManager();
590 $em->persist($entry);
591 $em->flush();
d1fc5902
NL
592 }
593 }
594
72db15ca 595 return $this->sendResponse($results);
d1fc5902 596 }
80299ed2
NL
597
598 /**
599 * Handles an entries list and add tags to them.
600 *
601 * @ApiDoc(
602 * parameters={
603 * {"name"="list", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...','tags': 'tag1, tag2'}, {'url': 'http://...','tags': 'tag1, tag2'}]", "description"="Urls (as an array) to handle."}
604 * }
605 * )
606 *
607 * @return JsonResponse
608 */
609 public function postEntriesTagsListAction(Request $request)
610 {
611 $this->validateAuthentication();
612
613 $list = json_decode($request->query->get('list', []));
72db15ca
JB
614
615 if (empty($list)) {
616 return $this->sendResponse([]);
617 }
618
80299ed2
NL
619 $results = [];
620
621 // handle multiple urls
72db15ca
JB
622 foreach ($list as $key => $element) {
623 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
624 $element->url,
625 $this->getUser()->getId()
626 );
80299ed2 627
72db15ca
JB
628 $results[$key]['url'] = $element->url;
629 $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
80299ed2 630
72db15ca 631 $tags = $element->tags;
80299ed2 632
72db15ca
JB
633 if (false !== $entry && !(empty($tags))) {
634 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
80299ed2 635
72db15ca
JB
636 $em = $this->getDoctrine()->getManager();
637 $em->persist($entry);
638 $em->flush();
80299ed2
NL
639 }
640 }
641
72db15ca
JB
642 return $this->sendResponse($results);
643 }
644
645 /**
646 * Shortcut to send data serialized in json.
647 *
648 * @param mixed $data
649 *
650 * @return JsonResponse
651 */
652 private function sendResponse($data)
653 {
654 $json = $this->get('serializer')->serialize($data, 'json');
80299ed2
NL
655
656 return (new JsonResponse())->setJson($json);
657 }
900c8448 658}