]> git.immae.eu Git - github/wallabag/wallabag.git/blob - src/Wallabag/CoreBundle/Controller/EntryController.php
Merge pull request #2180 from wallabag/download-pictures
[github/wallabag/wallabag.git] / src / Wallabag / CoreBundle / Controller / EntryController.php
1 <?php
2
3 namespace Wallabag\CoreBundle\Controller;
4
5 use Pagerfanta\Adapter\DoctrineORMAdapter;
6 use Pagerfanta\Exception\OutOfRangeCurrentPageException;
7 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
8 use Symfony\Bundle\FrameworkBundle\Controller\Controller;
9 use Symfony\Component\HttpFoundation\Request;
10 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
11 use Wallabag\CoreBundle\Entity\Entry;
12 use Wallabag\CoreBundle\Form\Type\EntryFilterType;
13 use Wallabag\CoreBundle\Form\Type\EditEntryType;
14 use Wallabag\CoreBundle\Form\Type\NewEntryType;
15 use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
16 use Wallabag\CoreBundle\Event\EntrySavedEvent;
17 use Wallabag\CoreBundle\Event\EntryDeletedEvent;
18
19 class EntryController extends Controller
20 {
21 /**
22 * Fetch content and update entry.
23 * In case it fails, entry will return to avod loosing the data.
24 *
25 * @param Entry $entry
26 * @param string $prefixMessage Should be the translation key: entry_saved or entry_reloaded
27 *
28 * @return Entry
29 */
30 private function updateEntry(Entry $entry, $prefixMessage = 'entry_saved')
31 {
32 // put default title in case of fetching content failed
33 $entry->setTitle('No title found');
34
35 $message = 'flashes.entry.notice.'.$prefixMessage;
36
37 try {
38 $entry = $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl());
39 } catch (\Exception $e) {
40 $this->get('logger')->error('Error while saving an entry', [
41 'exception' => $e,
42 'entry' => $entry,
43 ]);
44
45 $message = 'flashes.entry.notice.'.$prefixMessage.'_failed';
46 }
47
48 $this->get('session')->getFlashBag()->add('notice', $message);
49
50 return $entry;
51 }
52
53 /**
54 * @param Request $request
55 *
56 * @Route("/new-entry", name="new_entry")
57 *
58 * @return \Symfony\Component\HttpFoundation\Response
59 */
60 public function addEntryFormAction(Request $request)
61 {
62 $entry = new Entry($this->getUser());
63
64 $form = $this->createForm(NewEntryType::class, $entry);
65
66 $form->handleRequest($request);
67
68 if ($form->isValid()) {
69 $existingEntry = $this->checkIfEntryAlreadyExists($entry);
70
71 if (false !== $existingEntry) {
72 $this->get('session')->getFlashBag()->add(
73 'notice',
74 $this->get('translator')->trans('flashes.entry.notice.entry_already_saved', ['%date%' => $existingEntry->getCreatedAt()->format('d-m-Y')])
75 );
76
77 return $this->redirect($this->generateUrl('view', ['id' => $existingEntry->getId()]));
78 }
79
80 $this->updateEntry($entry);
81
82 $em = $this->getDoctrine()->getManager();
83 $em->persist($entry);
84 $em->flush();
85
86 // entry saved, dispatch event about it!
87 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
88
89 return $this->redirect($this->generateUrl('homepage'));
90 }
91
92 return $this->render('WallabagCoreBundle:Entry:new_form.html.twig', [
93 'form' => $form->createView(),
94 ]);
95 }
96
97 /**
98 * @param Request $request
99 *
100 * @Route("/bookmarklet", name="bookmarklet")
101 *
102 * @return \Symfony\Component\HttpFoundation\Response
103 */
104 public function addEntryViaBookmarkletAction(Request $request)
105 {
106 $entry = new Entry($this->getUser());
107 $entry->setUrl($request->get('url'));
108
109 if (false === $this->checkIfEntryAlreadyExists($entry)) {
110 $this->updateEntry($entry);
111
112 $em = $this->getDoctrine()->getManager();
113 $em->persist($entry);
114 $em->flush();
115
116 // entry saved, dispatch event about it!
117 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
118 }
119
120 return $this->redirect($this->generateUrl('homepage'));
121 }
122
123 /**
124 * @Route("/new", name="new")
125 *
126 * @return \Symfony\Component\HttpFoundation\Response
127 */
128 public function addEntryAction()
129 {
130 return $this->render('WallabagCoreBundle:Entry:new.html.twig');
131 }
132
133 /**
134 * Edit an entry content.
135 *
136 * @param Request $request
137 * @param Entry $entry
138 *
139 * @Route("/edit/{id}", requirements={"id" = "\d+"}, name="edit")
140 *
141 * @return \Symfony\Component\HttpFoundation\Response
142 */
143 public function editEntryAction(Request $request, Entry $entry)
144 {
145 $this->checkUserAction($entry);
146
147 $form = $this->createForm(EditEntryType::class, $entry);
148
149 $form->handleRequest($request);
150
151 if ($form->isValid()) {
152 $em = $this->getDoctrine()->getManager();
153 $em->persist($entry);
154 $em->flush();
155
156 $this->get('session')->getFlashBag()->add(
157 'notice',
158 'flashes.entry.notice.entry_updated'
159 );
160
161 return $this->redirect($this->generateUrl('view', ['id' => $entry->getId()]));
162 }
163
164 return $this->render('WallabagCoreBundle:Entry:edit.html.twig', [
165 'form' => $form->createView(),
166 ]);
167 }
168
169 /**
170 * Shows all entries for current user.
171 *
172 * @param Request $request
173 * @param int $page
174 *
175 * @Route("/all/list/{page}", name="all", defaults={"page" = "1"})
176 *
177 * @return \Symfony\Component\HttpFoundation\Response
178 */
179 public function showAllAction(Request $request, $page)
180 {
181 return $this->showEntries('all', $request, $page);
182 }
183
184 /**
185 * Shows unread entries for current user.
186 *
187 * @param Request $request
188 * @param int $page
189 *
190 * @Route("/unread/list/{page}", name="unread", defaults={"page" = "1"})
191 *
192 * @return \Symfony\Component\HttpFoundation\Response
193 */
194 public function showUnreadAction(Request $request, $page)
195 {
196 // load the quickstart if no entry in database
197 if ($page == 1 && $this->get('wallabag_core.entry_repository')->countAllEntriesByUsername($this->getUser()->getId()) == 0) {
198 return $this->redirect($this->generateUrl('quickstart'));
199 }
200
201 return $this->showEntries('unread', $request, $page);
202 }
203
204 /**
205 * Shows read entries for current user.
206 *
207 * @param Request $request
208 * @param int $page
209 *
210 * @Route("/archive/list/{page}", name="archive", defaults={"page" = "1"})
211 *
212 * @return \Symfony\Component\HttpFoundation\Response
213 */
214 public function showArchiveAction(Request $request, $page)
215 {
216 return $this->showEntries('archive', $request, $page);
217 }
218
219 /**
220 * Shows starred entries for current user.
221 *
222 * @param Request $request
223 * @param int $page
224 *
225 * @Route("/starred/list/{page}", name="starred", defaults={"page" = "1"})
226 *
227 * @return \Symfony\Component\HttpFoundation\Response
228 */
229 public function showStarredAction(Request $request, $page)
230 {
231 return $this->showEntries('starred', $request, $page);
232 }
233
234 /**
235 * Global method to retrieve entries depending on the given type
236 * It returns the response to be send.
237 *
238 * @param string $type Entries type: unread, starred or archive
239 * @param Request $request
240 * @param int $page
241 *
242 * @return \Symfony\Component\HttpFoundation\Response
243 */
244 private function showEntries($type, Request $request, $page)
245 {
246 $repository = $this->get('wallabag_core.entry_repository');
247
248 switch ($type) {
249 case 'untagged':
250 $qb = $repository->getBuilderForUntaggedByUser($this->getUser()->getId());
251
252 break;
253 case 'starred':
254 $qb = $repository->getBuilderForStarredByUser($this->getUser()->getId());
255 break;
256
257 case 'archive':
258 $qb = $repository->getBuilderForArchiveByUser($this->getUser()->getId());
259 break;
260
261 case 'unread':
262 $qb = $repository->getBuilderForUnreadByUser($this->getUser()->getId());
263 break;
264
265 case 'all':
266 $qb = $repository->getBuilderForAllByUser($this->getUser()->getId());
267 break;
268
269 default:
270 throw new \InvalidArgumentException(sprintf('Type "%s" is not implemented.', $type));
271 }
272
273 $form = $this->createForm(EntryFilterType::class);
274
275 if ($request->query->has($form->getName())) {
276 // manually bind values from the request
277 $form->submit($request->query->get($form->getName()));
278
279 // build the query from the given form object
280 $this->get('lexik_form_filter.query_builder_updater')->addFilterConditions($form, $qb);
281 }
282
283 $pagerAdapter = new DoctrineORMAdapter($qb->getQuery());
284
285 $entries = $this->get('wallabag_core.helper.prepare_pager_for_entries')
286 ->prepare($pagerAdapter, $page);
287
288 try {
289 $entries->setCurrentPage($page);
290 } catch (OutOfRangeCurrentPageException $e) {
291 if ($page > 1) {
292 return $this->redirect($this->generateUrl($type, ['page' => $entries->getNbPages()]), 302);
293 }
294 }
295
296 return $this->render(
297 'WallabagCoreBundle:Entry:entries.html.twig',
298 [
299 'form' => $form->createView(),
300 'entries' => $entries,
301 'currentPage' => $page,
302 ]
303 );
304 }
305
306 /**
307 * Shows entry content.
308 *
309 * @param Entry $entry
310 *
311 * @Route("/view/{id}", requirements={"id" = "\d+"}, name="view")
312 *
313 * @return \Symfony\Component\HttpFoundation\Response
314 */
315 public function viewAction(Entry $entry)
316 {
317 $this->checkUserAction($entry);
318
319 return $this->render(
320 'WallabagCoreBundle:Entry:entry.html.twig',
321 ['entry' => $entry]
322 );
323 }
324
325 /**
326 * Reload an entry.
327 * Refetch content from the website and make it readable again.
328 *
329 * @param Entry $entry
330 *
331 * @Route("/reload/{id}", requirements={"id" = "\d+"}, name="reload_entry")
332 *
333 * @return \Symfony\Component\HttpFoundation\RedirectResponse
334 */
335 public function reloadAction(Entry $entry)
336 {
337 $this->checkUserAction($entry);
338
339 $this->updateEntry($entry, 'entry_reloaded');
340
341 // if refreshing entry failed, don't save it
342 if ($this->getParameter('wallabag_core.fetching_error_message') === $entry->getContent()) {
343 $bag = $this->get('session')->getFlashBag();
344 $bag->clear();
345 $bag->add('notice', 'flashes.entry.notice.entry_reloaded_failed');
346
347 return $this->redirect($this->generateUrl('view', ['id' => $entry->getId()]));
348 }
349
350 $em = $this->getDoctrine()->getManager();
351 $em->persist($entry);
352 $em->flush();
353
354 // entry saved, dispatch event about it!
355 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
356
357 return $this->redirect($this->generateUrl('view', ['id' => $entry->getId()]));
358 }
359
360 /**
361 * Changes read status for an entry.
362 *
363 * @param Request $request
364 * @param Entry $entry
365 *
366 * @Route("/archive/{id}", requirements={"id" = "\d+"}, name="archive_entry")
367 *
368 * @return \Symfony\Component\HttpFoundation\RedirectResponse
369 */
370 public function toggleArchiveAction(Request $request, Entry $entry)
371 {
372 $this->checkUserAction($entry);
373
374 $entry->toggleArchive();
375 $this->getDoctrine()->getManager()->flush();
376
377 $message = 'flashes.entry.notice.entry_unarchived';
378 if ($entry->isArchived()) {
379 $message = 'flashes.entry.notice.entry_archived';
380 }
381
382 $this->get('session')->getFlashBag()->add(
383 'notice',
384 $message
385 );
386
387 $redirectUrl = $this->get('wallabag_core.helper.redirect')->to($request->headers->get('referer'));
388
389 return $this->redirect($redirectUrl);
390 }
391
392 /**
393 * Changes starred status for an entry.
394 *
395 * @param Request $request
396 * @param Entry $entry
397 *
398 * @Route("/star/{id}", requirements={"id" = "\d+"}, name="star_entry")
399 *
400 * @return \Symfony\Component\HttpFoundation\RedirectResponse
401 */
402 public function toggleStarAction(Request $request, Entry $entry)
403 {
404 $this->checkUserAction($entry);
405
406 $entry->toggleStar();
407 $this->getDoctrine()->getManager()->flush();
408
409 $message = 'flashes.entry.notice.entry_unstarred';
410 if ($entry->isStarred()) {
411 $message = 'flashes.entry.notice.entry_starred';
412 }
413
414 $this->get('session')->getFlashBag()->add(
415 'notice',
416 $message
417 );
418
419 $redirectUrl = $this->get('wallabag_core.helper.redirect')->to($request->headers->get('referer'));
420
421 return $this->redirect($redirectUrl);
422 }
423
424 /**
425 * Deletes entry and redirect to the homepage or the last viewed page.
426 *
427 * @param Entry $entry
428 *
429 * @Route("/delete/{id}", requirements={"id" = "\d+"}, name="delete_entry")
430 *
431 * @return \Symfony\Component\HttpFoundation\RedirectResponse
432 */
433 public function deleteEntryAction(Request $request, Entry $entry)
434 {
435 $this->checkUserAction($entry);
436
437 // generates the view url for this entry to check for redirection later
438 // to avoid redirecting to the deleted entry. Ugh.
439 $url = $this->generateUrl(
440 'view',
441 ['id' => $entry->getId()],
442 UrlGeneratorInterface::ABSOLUTE_PATH
443 );
444
445 // entry deleted, dispatch event about it!
446 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
447
448 $em = $this->getDoctrine()->getManager();
449 $em->remove($entry);
450 $em->flush();
451
452 $this->get('session')->getFlashBag()->add(
453 'notice',
454 'flashes.entry.notice.entry_deleted'
455 );
456
457 // don't redirect user to the deleted entry (check that the referer doesn't end with the same url)
458 $referer = $request->headers->get('referer');
459 $to = (1 !== preg_match('#'.$url.'$#i', $referer) ? $referer : null);
460
461 $redirectUrl = $this->get('wallabag_core.helper.redirect')->to($to);
462
463 return $this->redirect($redirectUrl);
464 }
465
466 /**
467 * Check if the logged user can manage the given entry.
468 *
469 * @param Entry $entry
470 */
471 private function checkUserAction(Entry $entry)
472 {
473 if (null === $this->getUser() || $this->getUser()->getId() != $entry->getUser()->getId()) {
474 throw $this->createAccessDeniedException('You can not access this entry.');
475 }
476 }
477
478 /**
479 * Check for existing entry, if it exists, redirect to it with a message.
480 *
481 * @param Entry $entry
482 *
483 * @return Entry|bool
484 */
485 private function checkIfEntryAlreadyExists(Entry $entry)
486 {
487 return $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($entry->getUrl(), $this->getUser()->getId());
488 }
489
490 /**
491 * Get public URL for entry (and generate it if necessary).
492 *
493 * @param Entry $entry
494 *
495 * @Route("/share/{id}", requirements={"id" = "\d+"}, name="share")
496 *
497 * @return \Symfony\Component\HttpFoundation\Response
498 */
499 public function shareAction(Entry $entry)
500 {
501 $this->checkUserAction($entry);
502
503 if (null === $entry->getUuid()) {
504 $entry->generateUuid();
505
506 $em = $this->getDoctrine()->getManager();
507 $em->persist($entry);
508 $em->flush();
509 }
510
511 return $this->redirect($this->generateUrl('share_entry', [
512 'uuid' => $entry->getUuid(),
513 ]));
514 }
515
516 /**
517 * Disable public sharing for an entry.
518 *
519 * @param Entry $entry
520 *
521 * @Route("/share/delete/{id}", requirements={"id" = "\d+"}, name="delete_share")
522 *
523 * @return \Symfony\Component\HttpFoundation\Response
524 */
525 public function deleteShareAction(Entry $entry)
526 {
527 $this->checkUserAction($entry);
528
529 $entry->cleanUuid();
530
531 $em = $this->getDoctrine()->getManager();
532 $em->persist($entry);
533 $em->flush();
534
535 return $this->redirect($this->generateUrl('view', [
536 'id' => $entry->getId(),
537 ]));
538 }
539
540 /**
541 * Ability to view a content publicly.
542 *
543 * @param Entry $entry
544 *
545 * @Route("/share/{uuid}", requirements={"uuid" = ".+"}, name="share_entry")
546 * @Cache(maxage="25200", smaxage="25200", public=true)
547 *
548 * @return \Symfony\Component\HttpFoundation\Response
549 */
550 public function shareEntryAction(Entry $entry)
551 {
552 if (!$this->get('craue_config')->get('share_public')) {
553 throw $this->createAccessDeniedException('Sharing an entry is disabled for this user.');
554 }
555
556 return $this->render(
557 '@WallabagCore/themes/common/Entry/share.html.twig',
558 ['entry' => $entry]
559 );
560 }
561
562 /**
563 * Shows untagged articles for current user.
564 *
565 * @param Request $request
566 * @param int $page
567 *
568 * @Route("/untagged/list/{page}", name="untagged", defaults={"page" = "1"})
569 *
570 * @return \Symfony\Component\HttpFoundation\Response
571 */
572 public function showUntaggedEntriesAction(Request $request, $page)
573 {
574 return $this->showEntries('untagged', $request, $page);
575 }
576 }