3 namespace Wallabag\ApiBundle\Controller
;
5 use FOS\RestBundle\Controller\FOSRestController
;
6 use Hateoas\Configuration\Route
as HateoasRoute
;
7 use Hateoas\Representation\Factory\PagerfantaFactory
;
8 use Nelmio\ApiDocBundle\Annotation\ApiDoc
;
9 use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter
;
10 use Symfony\Component\HttpFoundation\Request
;
11 use Symfony\Component\HttpFoundation\JsonResponse
;
12 use Symfony\Component\Routing\Generator\UrlGeneratorInterface
;
13 use Symfony\Component\Security\Core\Exception\AccessDeniedException
;
14 use Wallabag\CoreBundle\Entity\Entry
;
15 use Wallabag\CoreBundle\Entity\Tag
;
16 use Wallabag\AnnotationBundle\Entity\Annotation
;
18 class WallabagRestController
extends FOSRestController
20 private function validateAuthentication()
22 if (false === $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
23 throw new AccessDeniedException();
28 * Check if an entry exist by url.
32 * {"name"="url", "dataType"="string", "required"=true, "format"="An url", "description"="Url to check if it exists"},
33 * {"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"}
37 * @return JsonResponse
39 public function getEntriesExistsAction(Request
$request)
41 $this->validateAuthentication();
43 $urls = $request->query
->get('urls', []);
45 // handle multiple urls first
48 foreach ($urls as $url) {
49 $res = $this->getDoctrine()
50 ->getRepository('WallabagCoreBundle:Entry')
51 ->findByUrlAndUserId($url, $this->getUser()->getId());
53 $results[$url] = false === $res ? false : true;
56 $json = $this->get('serializer')->serialize($results, 'json');
58 return (new JsonResponse())->setJson($json);
61 // let's see if it is a simple url?
62 $url = $request->query
->get('url', '');
65 throw $this->createAccessDeniedException('URL is empty?, logged user id: '.$this->getUser()->getId());
68 $res = $this->getDoctrine()
69 ->getRepository('WallabagCoreBundle:Entry')
70 ->findByUrlAndUserId($url, $this->getUser()->getId());
72 $exists = false === $res ? false : true;
74 $json = $this->get('serializer')->serialize(['exists' => $exists], 'json');
76 return (new JsonResponse())->setJson($json);
80 * Retrieve all entries. It could be filtered by many options.
84 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by archived status."},
85 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by starred status."},
86 * {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated', default 'created'", "description"="sort entries by date."},
87 * {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."},
88 * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
89 * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."},
90 * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."},
91 * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."},
95 * @return JsonResponse
97 public function getEntriesAction(Request
$request)
99 $this->validateAuthentication();
101 $isArchived = (null === $request->query
->get('archive')) ? null : (bool) $request->query
->get('archive');
102 $isStarred = (null === $request->query
->get('starred')) ? null : (bool) $request->query
->get('starred');
103 $sort = $request->query
->get('sort', 'created');
104 $order = $request->query
->get('order', 'desc');
105 $page = (int) $request->query
->get('page', 1);
106 $perPage = (int) $request->query
->get('perPage', 30);
107 $tags = $request->query
->get('tags', '');
108 $since = $request->query
->get('since', 0);
110 $pager = $this->getDoctrine()
111 ->getRepository('WallabagCoreBundle:Entry')
112 ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order, $since, $tags);
114 $pager->setCurrentPage($page);
115 $pager->setMaxPerPage($perPage);
117 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
118 $paginatedCollection = $pagerfantaFactory->createRepresentation(
123 'archive' => $isArchived,
124 'starred' => $isStarred,
128 'perPage' => $perPage,
132 UrlGeneratorInterface
::ABSOLUTE_URL
136 $json = $this->get('serializer')->serialize($paginatedCollection, 'json');
138 return (new JsonResponse())->setJson($json);
142 * Retrieve a single entry.
146 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
150 * @return JsonResponse
152 public function getEntryAction(Entry
$entry)
154 $this->validateAuthentication();
155 $this->validateUserAccess($entry->getUser()->getId());
157 $json = $this->get('serializer')->serialize($entry, 'json');
159 return (new JsonResponse())->setJson($json);
163 * Retrieve a single entry as a predefined format.
167 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
173 public function getEntryExportAction(Entry
$entry, Request
$request)
175 $this->validateAuthentication();
176 $this->validateUserAccess($entry->getUser()->getId());
178 return $this->get('wallabag_core.helper.entries_export')
180 ->updateTitle('entry')
181 ->exportAs($request->attributes
->get('_format'));
189 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."},
190 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
191 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
192 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"},
193 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"},
197 * @return JsonResponse
199 public function postEntriesAction(Request
$request)
201 $this->validateAuthentication();
203 $url = $request->request
->get('url');
204 $title = $request->request
->get('title');
205 $isArchived = $request->request
->get('archive');
206 $isStarred = $request->request
->get('starred');
208 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId());
210 if (false === $entry) {
211 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
212 new Entry($this->getUser()),
217 if (!is_null($title)) {
218 $entry->setTitle($title);
221 $tags = $request->request
->get('tags', '');
223 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
226 if (!is_null($isStarred)) {
227 $entry->setStarred((bool) $isStarred);
230 if (!is_null($isArchived)) {
231 $entry->setArchived((bool) $isArchived);
234 $em = $this->getDoctrine()->getManager();
235 $em->persist($entry);
239 $json = $this->get('serializer')->serialize($entry, 'json');
241 return (new JsonResponse())->setJson($json);
245 * Change several properties of an entry.
249 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
252 * {"name"="title", "dataType"="string", "required"=false},
253 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
254 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."},
255 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."},
259 * @return JsonResponse
261 public function patchEntriesAction(Entry
$entry, Request
$request)
263 $this->validateAuthentication();
264 $this->validateUserAccess($entry->getUser()->getId());
266 $title = $request->request
->get('title');
267 $isArchived = $request->request
->get('archive');
268 $isStarred = $request->request
->get('starred');
270 if (!is_null($title)) {
271 $entry->setTitle($title);
274 if (!is_null($isArchived)) {
275 $entry->setArchived((bool) $isArchived);
278 if (!is_null($isStarred)) {
279 $entry->setStarred((bool) $isStarred);
282 $tags = $request->request
->get('tags', '');
284 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
287 $em = $this->getDoctrine()->getManager();
290 $json = $this->get('serializer')->serialize($entry, 'json');
292 return (new JsonResponse())->setJson($json);
296 * Delete **permanently** an entry.
300 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
304 * @return JsonResponse
306 public function deleteEntriesAction(Entry
$entry)
308 $this->validateAuthentication();
309 $this->validateUserAccess($entry->getUser()->getId());
311 $em = $this->getDoctrine()->getManager();
315 $json = $this->get('serializer')->serialize($entry, 'json');
317 return (new JsonResponse())->setJson($json);
321 * Retrieve all tags for an entry.
325 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
329 * @return JsonResponse
331 public function getEntriesTagsAction(Entry
$entry)
333 $this->validateAuthentication();
334 $this->validateUserAccess($entry->getUser()->getId());
336 $json = $this->get('serializer')->serialize($entry->getTags(), 'json');
338 return (new JsonResponse())->setJson($json);
342 * Add one or more tags to an entry.
346 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
349 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
353 * @return JsonResponse
355 public function postEntriesTagsAction(Request
$request, Entry
$entry)
357 $this->validateAuthentication();
358 $this->validateUserAccess($entry->getUser()->getId());
360 $tags = $request->request
->get('tags', '');
362 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
365 $em = $this->getDoctrine()->getManager();
366 $em->persist($entry);
369 $json = $this->get('serializer')->serialize($entry, 'json');
371 return (new JsonResponse())->setJson($json);
375 * Permanently remove one tag for an entry.
379 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag ID"},
380 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
384 * @return JsonResponse
386 public function deleteEntriesTagsAction(Entry
$entry, Tag
$tag)
388 $this->validateAuthentication();
389 $this->validateUserAccess($entry->getUser()->getId());
391 $entry->removeTag($tag);
392 $em = $this->getDoctrine()->getManager();
393 $em->persist($entry);
396 $json = $this->get('serializer')->serialize($entry, 'json');
398 return (new JsonResponse())->setJson($json);
406 * @return JsonResponse
408 public function getTagsAction()
410 $this->validateAuthentication();
412 $tags = $this->getDoctrine()
413 ->getRepository('WallabagCoreBundle:Tag')
414 ->findAllTags($this->getUser()->getId());
416 $json = $this->get('serializer')->serialize($tags, 'json');
418 return (new JsonResponse())->setJson($json);
422 * Permanently remove one tag from **every** entry.
426 * {"name"="tag", "dataType"="string", "required"=true, "requirement"="\w+", "description"="Tag as a string"}
430 * @return JsonResponse
432 public function deleteTagLabelAction(Request
$request)
434 $this->validateAuthentication();
435 $label = $request->request
->get('tag', '');
437 $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label);
440 throw $this->createNotFoundException('Tag not found');
444 ->getRepository('WallabagCoreBundle:Entry')
445 ->removeTag($this->getUser()->getId(), $tag);
447 $this->cleanOrphanTag($tag);
449 $json = $this->get('serializer')->serialize($tag, 'json');
451 return (new JsonResponse())->setJson($json);
455 * Permanently remove some tags from **every** entry.
459 * {"name"="tags", "dataType"="string", "required"=true, "format"="tag1,tag2", "description"="Tags as strings (comma splitted)"}
463 * @return JsonResponse
465 public function deleteTagsLabelAction(Request
$request)
467 $this->validateAuthentication();
469 $tagsLabels = $request->request
->get('tags', '');
473 foreach (explode(',', $tagsLabels) as $tagLabel) {
474 $tagEntity = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($tagLabel);
476 if (!empty($tagEntity)) {
477 $tags[] = $tagEntity;
482 throw $this->createNotFoundException('Tags not found');
486 ->getRepository('WallabagCoreBundle:Entry')
487 ->removeTags($this->getUser()->getId(), $tags);
489 $this->cleanOrphanTag($tags);
491 $json = $this->get('serializer')->serialize($tags, 'json');
493 return (new JsonResponse())->setJson($json);
497 * Permanently remove one tag from **every** entry.
501 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag"}
505 * @return JsonResponse
507 public function deleteTagAction(Tag
$tag)
509 $this->validateAuthentication();
512 ->getRepository('WallabagCoreBundle:Entry')
513 ->removeTag($this->getUser()->getId(), $tag);
515 $this->cleanOrphanTag($tag);
517 $json = $this->get('serializer')->serialize($tag, 'json');
519 return (new JsonResponse())->setJson($json);
523 * Retrieve annotations for an entry.
527 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
533 public function getAnnotationsAction(Entry
$entry)
535 $this->validateAuthentication();
537 $annotationRows = $this
539 ->getRepository('WallabagAnnotationBundle:Annotation')
540 ->findAnnotationsByPageId($entry->getId(), $this->getUser()->getId());
541 $total = count($annotationRows);
542 $annotations = array('total' => $total, 'rows' => $annotationRows);
544 $json = $this->get('serializer')->serialize($annotations, 'json');
546 return $this->renderJsonResponse($json);
550 * Creates a new annotation.
552 * @param Entry $entry
556 * {"name"="ranges", "dataType"="array", "requirement"="\w+", "description"="The range array for the annotation"},
557 * {"name"="quote", "dataType"="string", "required"=false, "description"="Optional, quote for the annotation"},
558 * {"name"="text", "dataType"="string", "required"=true, "description"=""},
564 public function postAnnotationAction(Request
$request, Entry
$entry)
566 $this->validateAuthentication();
568 $data = json_decode($request->getContent(), true);
570 $em = $this->getDoctrine()->getManager();
572 $annotation = new Annotation($this->getUser());
574 $annotation->setText($data['text']);
575 if (array_key_exists('quote', $data)) {
576 $annotation->setQuote($data['quote']);
578 if (array_key_exists('ranges', $data)) {
579 $annotation->setRanges($data['ranges']);
582 $annotation->setEntry($entry);
584 $em->persist($annotation);
587 $json = $this->get('serializer')->serialize($annotation, 'json');
589 return $this->renderJsonResponse($json);
593 * Updates an annotation.
597 * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
601 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
605 public function putAnnotationAction(Annotation
$annotation, Request
$request)
607 $this->validateAuthentication();
609 $data = json_decode($request->getContent(), true);
611 if (!is_null($data['text'])) {
612 $annotation->setText($data['text']);
615 $em = $this->getDoctrine()->getManager();
618 $json = $this->get('serializer')->serialize($annotation, 'json');
620 return $this->renderJsonResponse($json);
624 * Removes an annotation.
628 * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
632 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
636 public function deleteAnnotationAction(Annotation
$annotation)
638 $this->validateAuthentication();
640 $em = $this->getDoctrine()->getManager();
641 $em->remove($annotation);
644 $json = $this->get('serializer')->serialize($annotation, 'json');
646 return $this->renderJsonResponse($json);
650 * Retrieve version number.
654 * @return JsonResponse
656 public function getVersionAction()
658 $version = $this->container
->getParameter('wallabag_core.version');
660 $json = $this->get('serializer')->serialize($version, 'json');
662 return (new JsonResponse())->setJson($json);
666 * Remove orphan tag in case no entries are associated to it.
668 * @param Tag|array $tags
670 private function cleanOrphanTag($tags)
672 if (!is_array($tags)) {
676 $em = $this->getDoctrine()->getManager();
678 foreach ($tags as $tag) {
679 if (count($tag->getEntries()) === 0) {
688 * Validate that the first id is equal to the second one.
689 * If not, throw exception. It means a user try to access information from an other user.
691 * @param int $requestUserId User id from the requested source
693 private function validateUserAccess($requestUserId)
695 $user = $this->get('security.token_storage')->getToken()->getUser();
696 if ($requestUserId != $user->getId()) {
697 throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$requestUserId.', logged user id: '.$user->getId());