]> git.immae.eu Git - github/wallabag/wallabag.git/blob - src/Wallabag/ApiBundle/Controller/WallabagRestController.php
Merge pull request #2314 from wallabag/assets
[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: '.$user->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)
131 {
132 $this->validateAuthentication();
133 $this->validateUserAccess($entry->getUser()->getId());
134
135 $json = $this->get('serializer')->serialize($entry, 'json');
136
137 return (new JsonResponse())->setJson($json);
138 }
139
140 /**
141 * Create an entry.
142 *
143 * @ApiDoc(
144 * parameters={
145 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."},
146 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
147 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
148 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"},
149 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"},
150 * }
151 * )
152 *
153 * @return JsonResponse
154 */
155 public function postEntriesAction(Request $request)
156 {
157 $this->validateAuthentication();
158
159 $url = $request->request->get('url');
160 $title = $request->request->get('title');
161 $isArchived = $request->request->get('archive');
162 $isStarred = $request->request->get('starred');
163
164 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId());
165
166 if (false === $entry) {
167 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
168 new Entry($this->getUser()),
169 $url
170 );
171 }
172
173 if (!is_null($title)) {
174 $entry->setTitle($title);
175 }
176
177 $tags = $request->request->get('tags', '');
178 if (!empty($tags)) {
179 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
180 }
181
182 if (!is_null($isStarred)) {
183 $entry->setStarred((bool) $isStarred);
184 }
185
186 if (!is_null($isArchived)) {
187 $entry->setArchived((bool) $isArchived);
188 }
189
190 $em = $this->getDoctrine()->getManager();
191 $em->persist($entry);
192
193 $em->flush();
194
195 $json = $this->get('serializer')->serialize($entry, 'json');
196
197 return (new JsonResponse())->setJson($json);
198 }
199
200 /**
201 * Change several properties of an entry.
202 *
203 * @ApiDoc(
204 * requirements={
205 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
206 * },
207 * parameters={
208 * {"name"="title", "dataType"="string", "required"=false},
209 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
210 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."},
211 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."},
212 * }
213 * )
214 *
215 * @return JsonResponse
216 */
217 public function patchEntriesAction(Entry $entry, Request $request)
218 {
219 $this->validateAuthentication();
220 $this->validateUserAccess($entry->getUser()->getId());
221
222 $title = $request->request->get('title');
223 $isArchived = $request->request->get('archive');
224 $isStarred = $request->request->get('starred');
225
226 if (!is_null($title)) {
227 $entry->setTitle($title);
228 }
229
230 if (!is_null($isArchived)) {
231 $entry->setArchived((bool) $isArchived);
232 }
233
234 if (!is_null($isStarred)) {
235 $entry->setStarred((bool) $isStarred);
236 }
237
238 $tags = $request->request->get('tags', '');
239 if (!empty($tags)) {
240 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
241 }
242
243 $em = $this->getDoctrine()->getManager();
244 $em->flush();
245
246 $json = $this->get('serializer')->serialize($entry, 'json');
247
248 return (new JsonResponse())->setJson($json);
249 }
250
251 /**
252 * Delete **permanently** an entry.
253 *
254 * @ApiDoc(
255 * requirements={
256 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
257 * }
258 * )
259 *
260 * @return JsonResponse
261 */
262 public function deleteEntriesAction(Entry $entry)
263 {
264 $this->validateAuthentication();
265 $this->validateUserAccess($entry->getUser()->getId());
266
267 $em = $this->getDoctrine()->getManager();
268 $em->remove($entry);
269 $em->flush();
270
271 $json = $this->get('serializer')->serialize($entry, 'json');
272
273 return (new JsonResponse())->setJson($json);
274 }
275
276 /**
277 * Retrieve all tags for an entry.
278 *
279 * @ApiDoc(
280 * requirements={
281 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
282 * }
283 * )
284 *
285 * @return JsonResponse
286 */
287 public function getEntriesTagsAction(Entry $entry)
288 {
289 $this->validateAuthentication();
290 $this->validateUserAccess($entry->getUser()->getId());
291
292 $json = $this->get('serializer')->serialize($entry->getTags(), 'json');
293
294 return (new JsonResponse())->setJson($json);
295 }
296
297 /**
298 * Add one or more tags to an entry.
299 *
300 * @ApiDoc(
301 * requirements={
302 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
303 * },
304 * parameters={
305 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
306 * }
307 * )
308 *
309 * @return JsonResponse
310 */
311 public function postEntriesTagsAction(Request $request, Entry $entry)
312 {
313 $this->validateAuthentication();
314 $this->validateUserAccess($entry->getUser()->getId());
315
316 $tags = $request->request->get('tags', '');
317 if (!empty($tags)) {
318 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
319 }
320
321 $em = $this->getDoctrine()->getManager();
322 $em->persist($entry);
323 $em->flush();
324
325 $json = $this->get('serializer')->serialize($entry, 'json');
326
327 return (new JsonResponse())->setJson($json);
328 }
329
330 /**
331 * Permanently remove one tag for an entry.
332 *
333 * @ApiDoc(
334 * requirements={
335 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag ID"},
336 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
337 * }
338 * )
339 *
340 * @return JsonResponse
341 */
342 public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
343 {
344 $this->validateAuthentication();
345 $this->validateUserAccess($entry->getUser()->getId());
346
347 $entry->removeTag($tag);
348 $em = $this->getDoctrine()->getManager();
349 $em->persist($entry);
350 $em->flush();
351
352 $json = $this->get('serializer')->serialize($entry, 'json');
353
354 return (new JsonResponse())->setJson($json);
355 }
356
357 /**
358 * Retrieve all tags.
359 *
360 * @ApiDoc()
361 *
362 * @return JsonResponse
363 */
364 public function getTagsAction()
365 {
366 $this->validateAuthentication();
367
368 $tags = $this->getDoctrine()
369 ->getRepository('WallabagCoreBundle:Tag')
370 ->findAllTagsWithEntries($this->getUser()->getId());
371
372 $json = $this->get('serializer')->serialize($tags, 'json');
373
374 return (new JsonResponse())->setJson($json);
375 }
376
377 /**
378 * Permanently remove one tag from **every** entry.
379 *
380 * @ApiDoc(
381 * requirements={
382 * {"name"="tag", "dataType"="string", "required"=true, "requirement"="\w+", "description"="Tag as a string"}
383 * }
384 * )
385 *
386 * @return JsonResponse
387 */
388 public function deleteTagLabelAction(Request $request)
389 {
390 $this->validateAuthentication();
391 $label = $request->request->get('tag', '');
392
393 $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label);
394
395 if (empty($tag)) {
396 throw $this->createNotFoundException('Tag not found');
397 }
398
399 $this->getDoctrine()
400 ->getRepository('WallabagCoreBundle:Entry')
401 ->removeTag($this->getUser()->getId(), $tag);
402
403 $json = $this->get('serializer')->serialize($tag, 'json');
404
405 return (new JsonResponse())->setJson($json);
406 }
407
408 /**
409 * Permanently remove some tags from **every** entry.
410 *
411 * @ApiDoc(
412 * requirements={
413 * {"name"="tags", "dataType"="string", "required"=true, "format"="tag1,tag2", "description"="Tags as strings (comma splitted)"}
414 * }
415 * )
416 *
417 * @return JsonResponse
418 */
419 public function deleteTagsLabelAction(Request $request)
420 {
421 $this->validateAuthentication();
422
423 $tagsLabels = $request->request->get('tags', '');
424
425 $tags = [];
426
427 foreach (explode(',', $tagsLabels) as $tagLabel) {
428 $tagEntity = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($tagLabel);
429
430 if (!empty($tagEntity)) {
431 $tags[] = $tagEntity;
432 }
433 }
434
435 if (empty($tags)) {
436 throw $this->createNotFoundException('Tags not found');
437 }
438
439 $this->getDoctrine()
440 ->getRepository('WallabagCoreBundle:Entry')
441 ->removeTags($this->getUser()->getId(), $tags);
442
443 $json = $this->get('serializer')->serialize($tags, 'json');
444
445 return (new JsonResponse())->setJson($json);
446 }
447
448 /**
449 * Permanently remove one tag from **every** entry.
450 *
451 * @ApiDoc(
452 * requirements={
453 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag"}
454 * }
455 * )
456 *
457 * @return JsonResponse
458 */
459 public function deleteTagAction(Tag $tag)
460 {
461 $this->validateAuthentication();
462
463 $this->getDoctrine()
464 ->getRepository('WallabagCoreBundle:Entry')
465 ->removeTag($this->getUser()->getId(), $tag);
466
467 $json = $this->get('serializer')->serialize($tag, 'json');
468
469 return (new JsonResponse())->setJson($json);
470 }
471
472 /**
473 * Retrieve version number.
474 *
475 * @ApiDoc()
476 *
477 * @return JsonResponse
478 */
479 public function getVersionAction()
480 {
481 $version = $this->container->getParameter('wallabag_core.version');
482
483 $json = $this->get('serializer')->serialize($version, 'json');
484
485 return (new JsonResponse())->setJson($json);
486 }
487
488 /**
489 * Validate that the first id is equal to the second one.
490 * If not, throw exception. It means a user try to access information from an other user.
491 *
492 * @param int $requestUserId User id from the requested source
493 */
494 private function validateUserAccess($requestUserId)
495 {
496 $user = $this->get('security.token_storage')->getToken()->getUser();
497 if ($requestUserId != $user->getId()) {
498 throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$requestUserId.', logged user id: '.$user->getId());
499 }
500 }
501 }