]> git.immae.eu Git - github/wallabag/wallabag.git/blob - src/Wallabag/ApiBundle/Controller/WallabagRestController.php
Merge pull request #2220 from Rurik19/master
[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\Response;
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 * Retrieve all entries. It could be filtered by many options.
27 *
28 * @ApiDoc(
29 * parameters={
30 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by archived status."},
31 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by starred status."},
32 * {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated', default 'created'", "description"="sort entries by date."},
33 * {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."},
34 * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
35 * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."},
36 * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."},
37 * }
38 * )
39 *
40 * @return Response
41 */
42 public function getEntriesAction(Request $request)
43 {
44 $this->validateAuthentication();
45
46 $isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive');
47 $isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred');
48 $sort = $request->query->get('sort', 'created');
49 $order = $request->query->get('order', 'desc');
50 $page = (int) $request->query->get('page', 1);
51 $perPage = (int) $request->query->get('perPage', 30);
52
53 $pager = $this->getDoctrine()
54 ->getRepository('WallabagCoreBundle:Entry')
55 ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order);
56
57 $pager->setCurrentPage($page);
58 $pager->setMaxPerPage($perPage);
59
60 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
61 $paginatedCollection = $pagerfantaFactory->createRepresentation(
62 $pager,
63 new Route('api_get_entries', [], UrlGeneratorInterface::ABSOLUTE_URL)
64 );
65
66 $json = $this->get('serializer')->serialize($paginatedCollection, 'json');
67
68 return $this->renderJsonResponse($json);
69 }
70
71 /**
72 * Retrieve a single entry.
73 *
74 * @ApiDoc(
75 * requirements={
76 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
77 * }
78 * )
79 *
80 * @return Response
81 */
82 public function getEntryAction(Entry $entry)
83 {
84 $this->validateAuthentication();
85 $this->validateUserAccess($entry->getUser()->getId());
86
87 $json = $this->get('serializer')->serialize($entry, 'json');
88
89 return $this->renderJsonResponse($json);
90 }
91
92 /**
93 * Create an entry.
94 *
95 * @ApiDoc(
96 * parameters={
97 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."},
98 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
99 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
100 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"},
101 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"},
102 * }
103 * )
104 *
105 * @return Response
106 */
107 public function postEntriesAction(Request $request)
108 {
109 $this->validateAuthentication();
110
111 $url = $request->request->get('url');
112 $title = $request->request->get('title');
113 $isArchived = $request->request->get('archive');
114 $isStarred = $request->request->get('starred');
115
116 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId());
117
118 if (false === $entry) {
119 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
120 new Entry($this->getUser()),
121 $url
122 );
123 }
124
125 if (!is_null($title)) {
126 $entry->setTitle($title);
127 }
128
129 $tags = $request->request->get('tags', '');
130 if (!empty($tags)) {
131 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
132 }
133
134 if (!is_null($isStarred)) {
135 $entry->setStarred((bool) $isStarred);
136 }
137
138 if (!is_null($isArchived)) {
139 $entry->setArchived((bool) $isArchived);
140 }
141
142 $em = $this->getDoctrine()->getManager();
143 $em->persist($entry);
144
145 $em->flush();
146
147 $json = $this->get('serializer')->serialize($entry, 'json');
148
149 return $this->renderJsonResponse($json);
150 }
151
152 /**
153 * Change several properties of an entry.
154 *
155 * @ApiDoc(
156 * requirements={
157 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
158 * },
159 * parameters={
160 * {"name"="title", "dataType"="string", "required"=false},
161 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
162 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."},
163 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."},
164 * }
165 * )
166 *
167 * @return Response
168 */
169 public function patchEntriesAction(Entry $entry, Request $request)
170 {
171 $this->validateAuthentication();
172 $this->validateUserAccess($entry->getUser()->getId());
173
174 $title = $request->request->get('title');
175 $isArchived = $request->request->get('archive');
176 $isStarred = $request->request->get('starred');
177
178 if (!is_null($title)) {
179 $entry->setTitle($title);
180 }
181
182 if (!is_null($isArchived)) {
183 $entry->setArchived((bool) $isArchived);
184 }
185
186 if (!is_null($isStarred)) {
187 $entry->setStarred((bool) $isStarred);
188 }
189
190 $tags = $request->request->get('tags', '');
191 if (!empty($tags)) {
192 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
193 }
194
195 $em = $this->getDoctrine()->getManager();
196 $em->flush();
197
198 $json = $this->get('serializer')->serialize($entry, 'json');
199
200 return $this->renderJsonResponse($json);
201 }
202
203 /**
204 * Delete **permanently** an entry.
205 *
206 * @ApiDoc(
207 * requirements={
208 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
209 * }
210 * )
211 *
212 * @return Response
213 */
214 public function deleteEntriesAction(Entry $entry)
215 {
216 $this->validateAuthentication();
217 $this->validateUserAccess($entry->getUser()->getId());
218
219 $em = $this->getDoctrine()->getManager();
220 $em->remove($entry);
221 $em->flush();
222
223 $json = $this->get('serializer')->serialize($entry, 'json');
224
225 return $this->renderJsonResponse($json);
226 }
227
228 /**
229 * Retrieve all tags for an entry.
230 *
231 * @ApiDoc(
232 * requirements={
233 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
234 * }
235 * )
236 *
237 * @return Response
238 */
239 public function getEntriesTagsAction(Entry $entry)
240 {
241 $this->validateAuthentication();
242 $this->validateUserAccess($entry->getUser()->getId());
243
244 $json = $this->get('serializer')->serialize($entry->getTags(), 'json');
245
246 return $this->renderJsonResponse($json);
247 }
248
249 /**
250 * Add one or more tags to an entry.
251 *
252 * @ApiDoc(
253 * requirements={
254 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
255 * },
256 * parameters={
257 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
258 * }
259 * )
260 *
261 * @return Response
262 */
263 public function postEntriesTagsAction(Request $request, Entry $entry)
264 {
265 $this->validateAuthentication();
266 $this->validateUserAccess($entry->getUser()->getId());
267
268 $tags = $request->request->get('tags', '');
269 if (!empty($tags)) {
270 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
271 }
272
273 $em = $this->getDoctrine()->getManager();
274 $em->persist($entry);
275 $em->flush();
276
277 $json = $this->get('serializer')->serialize($entry, 'json');
278
279 return $this->renderJsonResponse($json);
280 }
281
282 /**
283 * Permanently remove one tag for an entry.
284 *
285 * @ApiDoc(
286 * requirements={
287 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag ID"},
288 * {"name"="entry", "dataType"="integer", "requirement"="\w+", "description"="The entry ID"}
289 * }
290 * )
291 *
292 * @return Response
293 */
294 public function deleteEntriesTagsAction(Entry $entry, Tag $tag)
295 {
296 $this->validateAuthentication();
297 $this->validateUserAccess($entry->getUser()->getId());
298
299 $entry->removeTag($tag);
300 $em = $this->getDoctrine()->getManager();
301 $em->persist($entry);
302 $em->flush();
303
304 $json = $this->get('serializer')->serialize($entry, 'json');
305
306 return $this->renderJsonResponse($json);
307 }
308
309 /**
310 * Retrieve all tags.
311 *
312 * @ApiDoc()
313 *
314 * @return Response
315 */
316 public function getTagsAction()
317 {
318 $this->validateAuthentication();
319
320 $tags = $this->getDoctrine()
321 ->getRepository('WallabagCoreBundle:Tag')
322 ->findAllTags($this->getUser()->getId());
323
324 $json = $this->get('serializer')->serialize($tags, 'json');
325
326 return $this->renderJsonResponse($json);
327 }
328
329 /**
330 * Permanently remove one tag from **every** entry.
331 *
332 * @ApiDoc(
333 * requirements={
334 * {"name"="tag", "dataType"="integer", "requirement"="\w+", "description"="The tag"}
335 * }
336 * )
337 *
338 * @return Response
339 */
340 public function deleteTagAction(Tag $tag)
341 {
342 $this->validateAuthentication();
343
344 $this->getDoctrine()
345 ->getRepository('WallabagCoreBundle:Entry')
346 ->removeTag($this->getUser()->getId(), $tag);
347
348 $json = $this->get('serializer')->serialize($tag, 'json');
349
350 return $this->renderJsonResponse($json);
351 }
352 /**
353 * Retrieve version number.
354 *
355 * @ApiDoc()
356 *
357 * @return Response
358 */
359 public function getVersionAction()
360 {
361 $version = $this->container->getParameter('wallabag_core.version');
362
363 $json = $this->get('serializer')->serialize($version, 'json');
364
365 return $this->renderJsonResponse($json);
366 }
367
368 /**
369 * Validate that the first id is equal to the second one.
370 * If not, throw exception. It means a user try to access information from an other user.
371 *
372 * @param int $requestUserId User id from the requested source
373 */
374 private function validateUserAccess($requestUserId)
375 {
376 $user = $this->get('security.token_storage')->getToken()->getUser();
377 if ($requestUserId != $user->getId()) {
378 throw $this->createAccessDeniedException('Access forbidden. Entry user id: '.$requestUserId.', logged user id: '.$user->getId());
379 }
380 }
381
382 /**
383 * Send a JSON Response.
384 * We don't use the Symfony JsonRespone, because it takes an array as parameter instead of a JSON string.
385 *
386 * @param string $json
387 *
388 * @return Response
389 */
390 private function renderJsonResponse($json)
391 {
392 return new Response($json, 200, ['application/json']);
393 }
394 }