]> git.immae.eu Git - github/wallabag/wallabag.git/blame - src/Wallabag/ApiBundle/Controller/WallabagRestController.php
Merge pull request #2453 from wallabag/fix-api-export
[github/wallabag/wallabag.git] / src / Wallabag / ApiBundle / Controller / WallabagRestController.php
CommitLineData
f8bf8952
NL
1<?php
2
769e19dc 3namespace Wallabag\ApiBundle\Controller;
f8bf8952 4
fcb1fba5 5use FOS\RestBundle\Controller\FOSRestController;
3f3a6087 6use Hateoas\Configuration\Route as HateoasRoute;
619cc453 7use Hateoas\Representation\Factory\PagerfantaFactory;
f8bf8952 8use Nelmio\ApiDocBundle\Annotation\ApiDoc;
27f15aa4 9use Symfony\Component\HttpFoundation\Request;
60faee00 10use Symfony\Component\HttpFoundation\JsonResponse;
1d76102a 11use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
b0b893ea 12use Symfony\Component\Security\Core\Exception\AccessDeniedException;
be463487 13use Wallabag\CoreBundle\Entity\Entry;
653e8be4 14use Wallabag\CoreBundle\Entity\Tag;
f8bf8952 15
fcb1fba5 16class WallabagRestController extends FOSRestController
f8bf8952 17{
77273253
NL
18 private function validateAuthentication()
19 {
18f8f32f 20 if (false === $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
77273253
NL
21 throw new AccessDeniedException();
22 }
23 }
24
6273fefd 25 /**
3583cadf 26 * Check if an entry exist by url.
6273fefd
JB
27 *
28 * @ApiDoc(
29 * parameters={
f0abc22d
JB
30 * {"name"="url", "dataType"="string", "required"=true, "format"="An url", "description"="Url to check if it exists"},
31 * {"name"="urls", "dataType"="string", "required"=false, "format"="An array of urls (?urls[]=http...&urls[]=http...)", "description"="Urls (as an array) to check if it exists"}
6273fefd
JB
32 * }
33 * )
34 *
35 * @return JsonResponse
36 */
37 public function getEntriesExistsAction(Request $request)
38 {
39 $this->validateAuthentication();
40
f0abc22d
JB
41 $urls = $request->query->get('urls', []);
42
43 // handle multiple urls first
44 if (!empty($urls)) {
45 $results = [];
46 foreach ($urls as $url) {
47 $res = $this->getDoctrine()
48 ->getRepository('WallabagCoreBundle:Entry')
49 ->findByUrlAndUserId($url, $this->getUser()->getId());
50
51 $results[$url] = false === $res ? false : true;
52 }
53
54 $json = $this->get('serializer')->serialize($results, 'json');
55
56 return (new JsonResponse())->setJson($json);
57 }
58
59 // let's see if it is a simple url?
6273fefd
JB
60 $url = $request->query->get('url', '');
61
62 if (empty($url)) {
0b174d69 63 throw $this->createAccessDeniedException('URL is empty?, logged user id: '.$this->getUser()->getId());
6273fefd
JB
64 }
65
66 $res = $this->getDoctrine()
67 ->getRepository('WallabagCoreBundle:Entry')
68 ->findByUrlAndUserId($url, $this->getUser()->getId());
69
70 $exists = false === $res ? false : true;
71
72 $json = $this->get('serializer')->serialize(['exists' => $exists], 'json');
73
74 return (new JsonResponse())->setJson($json);
75 }
76
f8bf8952 77 /**
a8c90c5c 78 * Retrieve all entries. It could be filtered by many options.
f8bf8952
NL
79 *
80 * @ApiDoc(
a8c90c5c 81 * parameters={
189ef634
TC
82 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by archived status."},
83 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by starred status."},
a8c90c5c
NL
84 * {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated', default 'created'", "description"="sort entries by date."},
85 * {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."},
86 * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
87 * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."},
189ef634 88 * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."},
e5fb89e5 89 * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."},
a8c90c5c 90 * }
f8bf8952 91 * )
4346a860 92 *
60faee00 93 * @return JsonResponse
f8bf8952 94 */
27f15aa4 95 public function getEntriesAction(Request $request)
f8bf8952 96 {
77273253
NL
97 $this->validateAuthentication();
98
0135c98b
NL
99 $isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive');
100 $isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred');
8ce32af6
JB
101 $sort = $request->query->get('sort', 'created');
102 $order = $request->query->get('order', 'desc');
103 $page = (int) $request->query->get('page', 1);
104 $perPage = (int) $request->query->get('perPage', 30);
28803f10 105 $tags = $request->query->get('tags', '');
c3f8b428 106 $since = $request->query->get('since', 0);
a8c90c5c 107
fc732227 108 $pager = $this->getDoctrine()
be463487 109 ->getRepository('WallabagCoreBundle:Entry')
28803f10 110 ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order, $since, $tags);
a8c90c5c 111
6e22bd73
WD
112 $pager->setCurrentPage($page);
113 $pager->setMaxPerPage($perPage);
114
8ce32af6 115 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
6e22bd73
WD
116 $paginatedCollection = $pagerfantaFactory->createRepresentation(
117 $pager,
3f3a6087 118 new HateoasRoute(
c3f8b428
JB
119 'api_get_entries',
120 [
121 'archive' => $isArchived,
122 'starred' => $isStarred,
123 'sort' => $sort,
124 'order' => $order,
125 'page' => $page,
126 'perPage' => $perPage,
127 'tags' => $tags,
128 'since' => $since,
129 ],
130 UrlGeneratorInterface::ABSOLUTE_URL
131 )
6e22bd73
WD
132 );
133
134 $json = $this->get('serializer')->serialize($paginatedCollection, 'json');
0f006880 135
60faee00 136 return (new JsonResponse())->setJson($json);
f8bf8952
NL
137 }
138
139 /**
4346a860 140 * Retrieve a single entry.
f8bf8952
NL
141 *
142 * @ApiDoc(
143 * requirements={
144 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
145 * }
146 * )
4346a860 147 *
60faee00 148 * @return JsonResponse
f8bf8952 149 */
be463487 150 public function getEntryAction(Entry $entry)
f8bf8952 151 {
77273253 152 $this->validateAuthentication();
fcb1fba5 153 $this->validateUserAccess($entry->getUser()->getId());
092ca707 154
aa4d6562 155 $json = $this->get('serializer')->serialize($entry, 'json');
0f006880 156
60faee00 157 return (new JsonResponse())->setJson($json);
f8bf8952
NL
158 }
159
3f3a6087
JB
160 /**
161 * Retrieve a single entry as a predefined format.
162 *
163 * @ApiDoc(
164 * requirements={
165 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
166 * }
167 * )
168 *
3f3a6087
JB
169 * @return Response
170 */
171 public function getEntryExportAction(Entry $entry, Request $request)
172 {
173 $this->validateAuthentication();
174 $this->validateUserAccess($entry->getUser()->getId());
175
176 return $this->get('wallabag_core.helper.entries_export')
177 ->setEntries($entry)
178 ->updateTitle('entry')
179 ->exportAs($request->attributes->get('_format'));
180 }
181
f8bf8952 182 /**
4346a860 183 * Create an entry.
f8bf8952
NL
184 *
185 * @ApiDoc(
a8c90c5c
NL
186 * parameters={
187 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."},
188 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
189 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
189ef634
TC
190 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"},
191 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"},
a8c90c5c 192 * }
f8bf8952 193 * )
4346a860 194 *
60faee00 195 * @return JsonResponse
f8bf8952 196 */
843dbe51 197 public function postEntriesAction(Request $request)
f8bf8952 198 {
77273253
NL
199 $this->validateAuthentication();
200
c3235553 201 $url = $request->request->get('url');
51a15609 202 $title = $request->request->get('title');
873e3806
YE
203 $isArchived = $request->request->get('archive');
204 $isStarred = $request->request->get('starred');
c3235553 205
3107f92a
TC
206 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId());
207
208 if (false === $entry) {
209 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
210 new Entry($this->getUser()),
211 $url
212 );
213 }
092ca707 214
51a15609
NL
215 if (!is_null($title)) {
216 $entry->setTitle($title);
217 }
218
0ca374e6
NL
219 $tags = $request->request->get('tags', '');
220 if (!empty($tags)) {
c2656f96 221 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
0ca374e6 222 }
092ca707 223
bc2b947c
TC
224 if (!is_null($isStarred)) {
225 $entry->setStarred((bool) $isStarred);
226 }
816ad405 227
bc2b947c
TC
228 if (!is_null($isArchived)) {
229 $entry->setArchived((bool) $isArchived);
230 }
816ad405 231
843dbe51
NL
232 $em = $this->getDoctrine()->getManager();
233 $em->persist($entry);
816ad405 234
843dbe51
NL
235 $em->flush();
236
aa4d6562
NL
237 $json = $this->get('serializer')->serialize($entry, 'json');
238
60faee00 239 return (new JsonResponse())->setJson($json);
f8bf8952
NL
240 }
241
242 /**
4346a860 243 * Change several properties of an entry.
f8bf8952
NL
244 *
245 * @ApiDoc(
246 * requirements={
247 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
a8c90c5c
NL
248 * },
249 * parameters={
250 * {"name"="title", "dataType"="string", "required"=false},
251 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
189ef634
TC
252 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."},
253 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."},
1d147791 254 * }
f8bf8952 255 * )
4346a860 256 *
60faee00 257 * @return JsonResponse
f8bf8952 258 */
be463487 259 public function patchEntriesAction(Entry $entry, Request $request)
f8bf8952 260 {
77273253 261 $this->validateAuthentication();
fcb1fba5 262 $this->validateUserAccess($entry->getUser()->getId());
092ca707 263
8ce32af6 264 $title = $request->request->get('title');
614a0bfd
YE
265 $isArchived = $request->request->get('archive');
266 $isStarred = $request->request->get('starred');
2c093b03
NL
267
268 if (!is_null($title)) {
269 $entry->setTitle($title);
270 }
271
272 if (!is_null($isArchived)) {
189ef634 273 $entry->setArchived((bool) $isArchived);
2c093b03
NL
274 }
275
2c093b03 276 if (!is_null($isStarred)) {
189ef634 277 $entry->setStarred((bool) $isStarred);
2c093b03
NL
278 }
279
0ca374e6
NL
280 $tags = $request->request->get('tags', '');
281 if (!empty($tags)) {
c2656f96 282 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
0ca374e6 283 }
092ca707 284
2c093b03 285 $em = $this->getDoctrine()->getManager();
2c093b03
NL
286 $em->flush();
287
0ca374e6
NL
288 $json = $this->get('serializer')->serialize($entry, 'json');
289
60faee00 290 return (new JsonResponse())->setJson($json);
f8bf8952
NL
291 }
292
293 /**
4346a860 294 * Delete **permanently** an entry.
f8bf8952
NL
295 *
296 * @ApiDoc(
a8c90c5c
NL
297 * requirements={
298 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
299 * }
f8bf8952 300 * )
4346a860 301 *
60faee00 302 * @return JsonResponse
f8bf8952 303 */
be463487 304 public function deleteEntriesAction(Entry $entry)
f8bf8952 305 {
77273253 306 $this->validateAuthentication();
fcb1fba5 307 $this->validateUserAccess($entry->getUser()->getId());
092ca707 308
42a90646 309 $em = $this->getDoctrine()->getManager();
1d147791 310 $em->remove($entry);
42a90646
NL
311 $em->flush();
312
1d147791
NL
313 $json = $this->get('serializer')->serialize($entry, 'json');
314
60faee00 315 return (new JsonResponse())->setJson($json);
f8bf8952
NL
316 }
317
318 /**
4346a860 319 * Retrieve all tags for an entry.
f8bf8952
NL
320 *
321 * @ApiDoc(
322 * requirements={
323 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
324 * }
325 * )
8eedc8cf 326 *
60faee00 327 * @return JsonResponse
f8bf8952 328 */
be463487 329 public function getEntriesTagsAction(Entry $entry)
7df80cb3 330 {
77273253 331 $this->validateAuthentication();
fcb1fba5 332 $this->validateUserAccess($entry->getUser()->getId());
092ca707 333
1bd12b62 334 $json = $this->get('serializer')->serialize($entry->getTags(), 'json');
0a018fe0 335
60faee00 336 return (new JsonResponse())->setJson($json);
f8bf8952
NL
337 }
338
339 /**
4346a860 340 * Add one or more tags to an entry.
f8bf8952
NL
341 *
342 * @ApiDoc(
343 * requirements={
344 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
a8c90c5c
NL
345 * },
346 * parameters={
347 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
348 * }
f8bf8952 349 * )
8eedc8cf 350 *
60faee00 351 * @return JsonResponse
f8bf8952 352 */
a36737f4 353 public function postEntriesTagsAction(Request $request, Entry $entry)
7df80cb3 354 {
77273253 355 $this->validateAuthentication();
fcb1fba5 356 $this->validateUserAccess($entry->getUser()->getId());
a36737f4 357
0ca374e6
NL
358 $tags = $request->request->get('tags', '');
359 if (!empty($tags)) {
c2656f96 360 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
0ca374e6 361 }
092ca707 362
a36737f4
NL
363 $em = $this->getDoctrine()->getManager();
364 $em->persist($entry);
365 $em->flush();
366
367 $json = $this->get('serializer')->serialize($entry, 'json');
368
60faee00 369 return (new JsonResponse())->setJson($json);
f8bf8952
NL
370 }
371
372 /**
4346a860 373 * Permanently remove one tag for an entry.
f8bf8952
NL
374 *
375 * @ApiDoc(
376 * requirements={
769e19dc 377 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag ID"},
f8bf8952
NL
378 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
379 * }
380 * )
8eedc8cf 381 *
60faee00 382 * @return JsonResponse
f8bf8952 383 */
653e8be4 384 public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
f8bf8952 385 {
77273253 386 $this->validateAuthentication();
fcb1fba5 387 $this->validateUserAccess($entry->getUser()->getId());
092ca707
NL
388
389 $entry->removeTag($tag);
390 $em = $this->getDoctrine()->getManager();
391 $em->persist($entry);
392 $em->flush();
393
394 $json = $this->get('serializer')->serialize($entry, 'json');
395
60faee00 396 return (new JsonResponse())->setJson($json);
f8bf8952
NL
397 }
398
399 /**
4346a860 400 * Retrieve all tags.
f8bf8952 401 *
092ca707 402 * @ApiDoc()
8eedc8cf 403 *
60faee00 404 * @return JsonResponse
f8bf8952 405 */
092ca707 406 public function getTagsAction()
7df80cb3 407 {
77273253 408 $this->validateAuthentication();
fc732227
JB
409
410 $tags = $this->getDoctrine()
411 ->getRepository('WallabagCoreBundle:Tag')
28bb4890 412 ->findAllTags($this->getUser()->getId());
fc732227
JB
413
414 $json = $this->get('serializer')->serialize($tags, 'json');
092ca707 415
60faee00 416 return (new JsonResponse())->setJson($json);
f8bf8952
NL
417 }
418
f8bf8952 419 /**
4346a860 420 * Permanently remove one tag from **every** entry.
f8bf8952
NL
421 *
422 * @ApiDoc(
423 * requirements={
a0e1eafc 424 * {"name"="tag", "dataType"="string", "required"=true, "requirement"="\w+", "description"="Tag as a string"}
f8bf8952
NL
425 * }
426 * )
8eedc8cf 427 *
60faee00 428 * @return JsonResponse
f8bf8952 429 */
a0e1eafc 430 public function deleteTagLabelAction(Request $request)
f8bf8952 431 {
77273253 432 $this->validateAuthentication();
a0e1eafc
JB
433 $label = $request->request->get('tag', '');
434
435 $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label);
436
437 if (empty($tag)) {
438 throw $this->createNotFoundException('Tag not found');
439 }
fc732227
JB
440
441 $this->getDoctrine()
442 ->getRepository('WallabagCoreBundle:Entry')
443 ->removeTag($this->getUser()->getId(), $tag);
092ca707 444
ac8cf632
JB
445 $this->cleanOrphanTag($tag);
446
092ca707
NL
447 $json = $this->get('serializer')->serialize($tag, 'json');
448
60faee00 449 return (new JsonResponse())->setJson($json);
769e19dc 450 }
4da01f49
TC
451
452 /**
a0e1eafc 453 * Permanently remove some tags from **every** entry.
4da01f49
TC
454 *
455 * @ApiDoc(
456 * requirements={
a0e1eafc 457 * {"name"="tags", "dataType"="string", "required"=true, "format"="tag1,tag2", "description"="Tags as strings (comma splitted)"}
4da01f49
TC
458 * }
459 * )
460 *
60faee00 461 * @return JsonResponse
4da01f49 462 */
a0e1eafc 463 public function deleteTagsLabelAction(Request $request)
4da01f49
TC
464 {
465 $this->validateAuthentication();
4da01f49 466
a0e1eafc
JB
467 $tagsLabels = $request->request->get('tags', '');
468
469 $tags = [];
470
471 foreach (explode(',', $tagsLabels) as $tagLabel) {
472 $tagEntity = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($tagLabel);
473
474 if (!empty($tagEntity)) {
475 $tags[] = $tagEntity;
476 }
477 }
478
479 if (empty($tags)) {
480 throw $this->createNotFoundException('Tags not found');
481 }
482
4da01f49
TC
483 $this->getDoctrine()
484 ->getRepository('WallabagCoreBundle:Entry')
a0e1eafc 485 ->removeTags($this->getUser()->getId(), $tags);
4da01f49 486
ac8cf632
JB
487 $this->cleanOrphanTag($tags);
488
a0e1eafc 489 $json = $this->get('serializer')->serialize($tags, 'json');
4da01f49 490
60faee00 491 return (new JsonResponse())->setJson($json);
4da01f49
TC
492 }
493
494 /**
a0e1eafc 495 * Permanently remove one tag from **every** entry.
4da01f49
TC
496 *
497 * @ApiDoc(
498 * requirements={
a0e1eafc 499 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag"}
4da01f49
TC
500 * }
501 * )
502 *
60faee00 503 * @return JsonResponse
4da01f49 504 */
a0e1eafc 505 public function deleteTagAction(Tag $tag)
4da01f49
TC
506 {
507 $this->validateAuthentication();
508
4da01f49
TC
509 $this->getDoctrine()
510 ->getRepository('WallabagCoreBundle:Entry')
a0e1eafc 511 ->removeTag($this->getUser()->getId(), $tag);
4da01f49 512
ac8cf632
JB
513 $this->cleanOrphanTag($tag);
514
a0e1eafc 515 $json = $this->get('serializer')->serialize($tag, 'json');
4da01f49 516
60faee00 517 return (new JsonResponse())->setJson($json);
4da01f49
TC
518 }
519
2b477030 520 /**
6f8310b4
TC
521 * Retrieve version number.
522 *
523 * @ApiDoc()
2b477030 524 *
60faee00 525 * @return JsonResponse
2b477030
V
526 */
527 public function getVersionAction()
528 {
529 $version = $this->container->getParameter('wallabag_core.version');
530
531 $json = $this->get('serializer')->serialize($version, 'json');
532
60faee00 533 return (new JsonResponse())->setJson($json);
2b477030 534 }
769e19dc 535
ac8cf632
JB
536 /**
537 * Remove orphan tag in case no entries are associated to it.
538 *
539 * @param Tag|array $tags
540 */
541 private function cleanOrphanTag($tags)
542 {
543 if (!is_array($tags)) {
544 $tags = [$tags];
545 }
546
547 $em = $this->getDoctrine()->getManager();
548
549 foreach ($tags as $tag) {
550 if (count($tag->getEntries()) === 0) {
551 $em->remove($tag);
552 }
553 }
554
555 $em->flush();
556 }
557
769e19dc
J
558 /**
559 * Validate that the first id is equal to the second one.
4346a860 560 * If not, throw exception. It means a user try to access information from an other user.
769e19dc 561 *
4346a860 562 * @param int $requestUserId User id from the requested source
769e19dc 563 */
fcb1fba5 564 private function validateUserAccess($requestUserId)
769e19dc 565 {
18f8f32f 566 $user = $this->get('security.token_storage')->getToken()->getUser();
fcb1fba5
NL
567 if ($requestUserId != $user->getId()) {
568 throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$requestUserId.', logged user id: '.$user->getId());
769e19dc
J
569 }
570 }
7df80cb3 571}