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