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
;
17 use Wallabag\CoreBundle\Event\EntrySavedEvent
;
18 use Wallabag\CoreBundle\Event\EntryDeletedEvent
;
20 class WallabagRestController
extends FOSRestController
22 private function validateAuthentication()
24 if (false === $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
25 throw new AccessDeniedException();
30 * Check if an entry exist by url.
34 * {"name"="url", "dataType"="string", "required"=true, "format"="An url", "description"="Url to check if it exists"},
35 * {"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"}
39 * @return JsonResponse
41 public function getEntriesExistsAction(Request
$request)
43 $this->validateAuthentication();
45 $urls = $request->query
->get('urls', []);
47 // handle multiple urls first
50 foreach ($urls as $url) {
51 $res = $this->getDoctrine()
52 ->getRepository('WallabagCoreBundle:Entry')
53 ->findByUrlAndUserId($url, $this->getUser()->getId());
55 $results[$url] = false === $res ? false : true;
58 $json = $this->get('serializer')->serialize($results, 'json');
60 return (new JsonResponse())->setJson($json);
63 // let's see if it is a simple url?
64 $url = $request->query
->get('url', '');
67 throw $this->createAccessDeniedException('URL is empty?, logged user id: '.$this->getUser()->getId());
70 $res = $this->getDoctrine()
71 ->getRepository('WallabagCoreBundle:Entry')
72 ->findByUrlAndUserId($url, $this->getUser()->getId());
74 $exists = false === $res ? false : true;
76 $json = $this->get('serializer')->serialize(['exists' => $exists], 'json');
78 return (new JsonResponse())->setJson($json);
82 * Retrieve all entries. It could be filtered by many options.
86 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by archived status."},
87 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by starred status."},
88 * {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated', default 'created'", "description"="sort entries by date."},
89 * {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."},
90 * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
91 * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."},
92 * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."},
93 * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."},
97 * @return JsonResponse
99 public function getEntriesAction(Request
$request)
101 $this->validateAuthentication();
103 $isArchived = (null === $request->query
->get('archive')) ? null : (bool) $request->query
->get('archive');
104 $isStarred = (null === $request->query
->get('starred')) ? null : (bool) $request->query
->get('starred');
105 $sort = $request->query
->get('sort', 'created');
106 $order = $request->query
->get('order', 'desc');
107 $page = (int) $request->query
->get('page', 1);
108 $perPage = (int) $request->query
->get('perPage', 30);
109 $tags = $request->query
->get('tags', '');
110 $since = $request->query
->get('since', 0);
112 $pager = $this->getDoctrine()
113 ->getRepository('WallabagCoreBundle:Entry')
114 ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order, $since, $tags);
116 $pager->setCurrentPage($page);
117 $pager->setMaxPerPage($perPage);
119 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
120 $paginatedCollection = $pagerfantaFactory->createRepresentation(
125 'archive' => $isArchived,
126 'starred' => $isStarred,
130 'perPage' => $perPage,
134 UrlGeneratorInterface
::ABSOLUTE_URL
138 $json = $this->get('serializer')->serialize($paginatedCollection, 'json');
140 return (new JsonResponse())->setJson($json);
144 * Retrieve a single entry.
148 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
152 * @return JsonResponse
154 public function getEntryAction(Entry
$entry)
156 $this->validateAuthentication();
157 $this->validateUserAccess($entry->getUser()->getId());
159 $json = $this->get('serializer')->serialize($entry, 'json');
161 return (new JsonResponse())->setJson($json);
165 * Retrieve a single entry as a predefined format.
169 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
175 public function getEntryExportAction(Entry
$entry, Request
$request)
177 $this->validateAuthentication();
178 $this->validateUserAccess($entry->getUser()->getId());
180 return $this->get('wallabag_core.helper.entries_export')
182 ->updateTitle('entry')
183 ->exportAs($request->attributes
->get('_format'));
191 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."},
192 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
193 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
194 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"},
195 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"},
199 * @return JsonResponse
201 public function postEntriesAction(Request
$request)
203 $this->validateAuthentication();
205 $url = $request->request
->get('url');
206 $title = $request->request
->get('title');
207 $isArchived = $request->request
->get('archive');
208 $isStarred = $request->request
->get('starred');
210 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId());
212 if (false === $entry) {
213 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
214 new Entry($this->getUser()),
219 if (!is_null($title)) {
220 $entry->setTitle($title);
223 $tags = $request->request
->get('tags', '');
225 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
228 if (!is_null($isStarred)) {
229 $entry->setStarred((bool) $isStarred);
232 if (!is_null($isArchived)) {
233 $entry->setArchived((bool) $isArchived);
236 $em = $this->getDoctrine()->getManager();
237 $em->persist($entry);
240 // entry saved, dispatch event about it!
241 $this->get('event_dispatcher')->dispatch(EntrySavedEvent
::NAME
, new EntrySavedEvent($entry));
243 $json = $this->get('serializer')->serialize($entry, 'json');
245 return (new JsonResponse())->setJson($json);
249 * Change several properties of an entry.
253 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
256 * {"name"="title", "dataType"="string", "required"=false},
257 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
258 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."},
259 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."},
263 * @return JsonResponse
265 public function patchEntriesAction(Entry
$entry, Request
$request)
267 $this->validateAuthentication();
268 $this->validateUserAccess($entry->getUser()->getId());
270 $title = $request->request
->get('title');
271 $isArchived = $request->request
->get('archive');
272 $isStarred = $request->request
->get('starred');
274 if (!is_null($title)) {
275 $entry->setTitle($title);
278 if (!is_null($isArchived)) {
279 $entry->setArchived((bool) $isArchived);
282 if (!is_null($isStarred)) {
283 $entry->setStarred((bool) $isStarred);
286 $tags = $request->request
->get('tags', '');
288 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
291 $em = $this->getDoctrine()->getManager();
294 $json = $this->get('serializer')->serialize($entry, 'json');
296 return (new JsonResponse())->setJson($json);
300 * Delete **permanently** an entry.
304 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
308 * @return JsonResponse
310 public function deleteEntriesAction(Entry
$entry)
312 $this->validateAuthentication();
313 $this->validateUserAccess($entry->getUser()->getId());
315 // entry deleted, dispatch event about it!
316 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent
::NAME
, new EntryDeletedEvent($entry));
318 $em = $this->getDoctrine()->getManager();
322 $json = $this->get('serializer')->serialize($entry, 'json');
324 return (new JsonResponse())->setJson($json);
328 * Retrieve all tags for an entry.
332 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
336 * @return JsonResponse
338 public function getEntriesTagsAction(Entry
$entry)
340 $this->validateAuthentication();
341 $this->validateUserAccess($entry->getUser()->getId());
343 $json = $this->get('serializer')->serialize($entry->getTags(), 'json');
345 return (new JsonResponse())->setJson($json);
349 * Add one or more tags to an entry.
353 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
356 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
360 * @return JsonResponse
362 public function postEntriesTagsAction(Request
$request, Entry
$entry)
364 $this->validateAuthentication();
365 $this->validateUserAccess($entry->getUser()->getId());
367 $tags = $request->request
->get('tags', '');
369 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
372 $em = $this->getDoctrine()->getManager();
373 $em->persist($entry);
376 $json = $this->get('serializer')->serialize($entry, 'json');
378 return (new JsonResponse())->setJson($json);
382 * Permanently remove one tag for an entry.
386 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag ID"},
387 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
391 * @return JsonResponse
393 public function deleteEntriesTagsAction(Entry
$entry, Tag
$tag)
395 $this->validateAuthentication();
396 $this->validateUserAccess($entry->getUser()->getId());
398 $entry->removeTag($tag);
399 $em = $this->getDoctrine()->getManager();
400 $em->persist($entry);
403 $json = $this->get('serializer')->serialize($entry, 'json');
405 return (new JsonResponse())->setJson($json);
413 * @return JsonResponse
415 public function getTagsAction()
417 $this->validateAuthentication();
419 $tags = $this->getDoctrine()
420 ->getRepository('WallabagCoreBundle:Tag')
421 ->findAllTags($this->getUser()->getId());
423 $json = $this->get('serializer')->serialize($tags, 'json');
425 return (new JsonResponse())->setJson($json);
429 * Permanently remove one tag from **every** entry.
433 * {"name"="tag", "dataType"="string", "required"=true, "requirement"="\w+", "description"="Tag as a string"}
437 * @return JsonResponse
439 public function deleteTagLabelAction(Request
$request)
441 $this->validateAuthentication();
442 $label = $request->request
->get('tag', '');
444 $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label);
447 throw $this->createNotFoundException('Tag not found');
451 ->getRepository('WallabagCoreBundle:Entry')
452 ->removeTag($this->getUser()->getId(), $tag);
454 $this->cleanOrphanTag($tag);
456 $json = $this->get('serializer')->serialize($tag, 'json');
458 return (new JsonResponse())->setJson($json);
462 * Permanently remove some tags from **every** entry.
466 * {"name"="tags", "dataType"="string", "required"=true, "format"="tag1,tag2", "description"="Tags as strings (comma splitted)"}
470 * @return JsonResponse
472 public function deleteTagsLabelAction(Request
$request)
474 $this->validateAuthentication();
476 $tagsLabels = $request->request
->get('tags', '');
480 foreach (explode(',', $tagsLabels) as $tagLabel) {
481 $tagEntity = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($tagLabel);
483 if (!empty($tagEntity)) {
484 $tags[] = $tagEntity;
489 throw $this->createNotFoundException('Tags not found');
493 ->getRepository('WallabagCoreBundle:Entry')
494 ->removeTags($this->getUser()->getId(), $tags);
496 $this->cleanOrphanTag($tags);
498 $json = $this->get('serializer')->serialize($tags, 'json');
500 return (new JsonResponse())->setJson($json);
504 * Permanently remove one tag from **every** entry.
508 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag"}
512 * @return JsonResponse
514 public function deleteTagAction(Tag
$tag)
516 $this->validateAuthentication();
519 ->getRepository('WallabagCoreBundle:Entry')
520 ->removeTag($this->getUser()->getId(), $tag);
522 $this->cleanOrphanTag($tag);
524 $json = $this->get('serializer')->serialize($tag, 'json');
526 return (new JsonResponse())->setJson($json);
530 * Retrieve annotations for an entry.
534 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
538 * @param Entry $entry
540 * @return JsonResponse
542 public function getAnnotationsAction(Entry
$entry)
544 $this->validateAuthentication();
546 return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:getAnnotations', [
552 * Creates a new annotation.
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"=""},
562 * @param Request $request
563 * @param Entry $entry
565 * @return JsonResponse
567 public function postAnnotationAction(Request
$request, Entry
$entry)
569 $this->validateAuthentication();
571 return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:postAnnotation', [
572 'request' => $request,
578 * Updates an annotation.
582 * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
586 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
588 * @param Annotation $annotation
589 * @param Request $request
591 * @return JsonResponse
593 public function putAnnotationAction(Annotation
$annotation, Request
$request)
595 $this->validateAuthentication();
597 return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:putAnnotation', [
598 'annotation' => $annotation,
599 'request' => $request,
604 * Removes an annotation.
608 * {"name"="annotation", "dataType"="string", "requirement"="\w+", "description"="The annotation ID"}
612 * @ParamConverter("annotation", class="WallabagAnnotationBundle:Annotation")
614 * @param Annotation $annotation
616 * @return JsonResponse
618 public function deleteAnnotationAction(Annotation
$annotation)
620 $this->validateAuthentication();
622 return $this->forward('WallabagAnnotationBundle:WallabagAnnotation:deleteAnnotation', [
623 'annotation' => $annotation,
628 * Retrieve version number.
632 * @return JsonResponse
634 public function getVersionAction()
636 $version = $this->container
->getParameter('wallabag_core.version');
638 $json = $this->get('serializer')->serialize($version, 'json');
640 return (new JsonResponse())->setJson($json);
644 * Remove orphan tag in case no entries are associated to it.
646 * @param Tag|array $tags
648 private function cleanOrphanTag($tags)
650 if (!is_array($tags)) {
654 $em = $this->getDoctrine()->getManager();
656 foreach ($tags as $tag) {
657 if (count($tag->getEntries()) === 0) {
666 * Validate that the first id is equal to the second one.
667 * If not, throw exception. It means a user try to access information from an other user.
669 * @param int $requestUserId User id from the requested source
671 private function validateUserAccess($requestUserId)
673 $user = $this->get('security.token_storage')->getToken()->getUser();
674 if ($requestUserId != $user->getId()) {
675 throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$requestUserId.', logged user id: '.$user->getId());