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