aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/Wallabag
diff options
context:
space:
mode:
Diffstat (limited to 'src/Wallabag')
-rw-r--r--src/Wallabag/ApiBundle/Controller/EntryRestController.php254
-rw-r--r--src/Wallabag/ApiBundle/Form/Type/ClientType.php6
-rw-r--r--src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php119
-rw-r--r--src/Wallabag/CoreBundle/Command/InstallCommand.php23
-rw-r--r--src/Wallabag/CoreBundle/DependencyInjection/Configuration.php3
-rw-r--r--src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php1
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/ConfigType.php4
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/RssType.php1
-rw-r--r--src/Wallabag/CoreBundle/Repository/EntryRepository.php30
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig14
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entry.html.twig2
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig6
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/common/Static/about.html.twig4
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig20
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_reading_time.html.twig4
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig4
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig92
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig10
-rw-r--r--src/Wallabag/UserBundle/Resources/views/Security/login.html.twig2
19 files changed, 436 insertions, 163 deletions
diff --git a/src/Wallabag/ApiBundle/Controller/EntryRestController.php b/src/Wallabag/ApiBundle/Controller/EntryRestController.php
index 7590efbb..632b16d9 100644
--- a/src/Wallabag/ApiBundle/Controller/EntryRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/EntryRestController.php
@@ -5,6 +5,7 @@ namespace Wallabag\ApiBundle\Controller;
5use Hateoas\Configuration\Route; 5use Hateoas\Configuration\Route;
6use Hateoas\Representation\Factory\PagerfantaFactory; 6use Hateoas\Representation\Factory\PagerfantaFactory;
7use Nelmio\ApiDocBundle\Annotation\ApiDoc; 7use Nelmio\ApiDocBundle\Annotation\ApiDoc;
8use Symfony\Component\HttpKernel\Exception\HttpException;
8use Symfony\Component\HttpFoundation\Request; 9use Symfony\Component\HttpFoundation\Request;
9use Symfony\Component\HttpFoundation\JsonResponse; 10use Symfony\Component\HttpFoundation\JsonResponse;
10use Symfony\Component\Routing\Generator\UrlGeneratorInterface; 11use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
@@ -44,9 +45,7 @@ class EntryRestController extends WallabagRestController
44 $results[$url] = $res instanceof Entry ? $res->getId() : false; 45 $results[$url] = $res instanceof Entry ? $res->getId() : false;
45 } 46 }
46 47
47 $json = $this->get('serializer')->serialize($results, 'json'); 48 return $this->sendResponse($results);
48
49 return (new JsonResponse())->setJson($json);
50 } 49 }
51 50
52 // let's see if it is a simple url? 51 // let's see if it is a simple url?
@@ -62,9 +61,7 @@ class EntryRestController extends WallabagRestController
62 61
63 $exists = $res instanceof Entry ? $res->getId() : false; 62 $exists = $res instanceof Entry ? $res->getId() : false;
64 63
65 $json = $this->get('serializer')->serialize(['exists' => $exists], 'json'); 64 return $this->sendResponse(['exists' => $exists]);
66
67 return (new JsonResponse())->setJson($json);
68 } 65 }
69 66
70 /** 67 /**
@@ -98,12 +95,13 @@ class EntryRestController extends WallabagRestController
98 $tags = $request->query->get('tags', ''); 95 $tags = $request->query->get('tags', '');
99 $since = $request->query->get('since', 0); 96 $since = $request->query->get('since', 0);
100 97
98 /** @var \Pagerfanta\Pagerfanta $pager */
101 $pager = $this->getDoctrine() 99 $pager = $this->getDoctrine()
102 ->getRepository('WallabagCoreBundle:Entry') 100 ->getRepository('WallabagCoreBundle:Entry')
103 ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order, $since, $tags); 101 ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order, $since, $tags);
104 102
105 $pager->setCurrentPage($page);
106 $pager->setMaxPerPage($perPage); 103 $pager->setMaxPerPage($perPage);
104 $pager->setCurrentPage($page);
107 105
108 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage'); 106 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
109 $paginatedCollection = $pagerfantaFactory->createRepresentation( 107 $paginatedCollection = $pagerfantaFactory->createRepresentation(
@@ -124,9 +122,7 @@ class EntryRestController extends WallabagRestController
124 ) 122 )
125 ); 123 );
126 124
127 $json = $this->get('serializer')->serialize($paginatedCollection, 'json'); 125 return $this->sendResponse($paginatedCollection);
128
129 return (new JsonResponse())->setJson($json);
130 } 126 }
131 127
132 /** 128 /**
@@ -145,9 +141,7 @@ class EntryRestController extends WallabagRestController
145 $this->validateAuthentication(); 141 $this->validateAuthentication();
146 $this->validateUserAccess($entry->getUser()->getId()); 142 $this->validateUserAccess($entry->getUser()->getId());
147 143
148 $json = $this->get('serializer')->serialize($entry, 'json'); 144 return $this->sendResponse($entry);
149
150 return (new JsonResponse())->setJson($json);
151 } 145 }
152 146
153 /** 147 /**
@@ -173,6 +167,110 @@ class EntryRestController extends WallabagRestController
173 } 167 }
174 168
175 /** 169 /**
170 * Handles an entries list and delete URL.
171 *
172 * @ApiDoc(
173 * parameters={
174 * {"name"="urls", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...'}, {'url': 'http://...'}]", "description"="Urls (as an array) to delete."}
175 * }
176 * )
177 *
178 * @return JsonResponse
179 */
180 public function deleteEntriesListAction(Request $request)
181 {
182 $this->validateAuthentication();
183
184 $urls = json_decode($request->query->get('urls', []));
185
186 if (empty($urls)) {
187 return $this->sendResponse([]);
188 }
189
190 $results = [];
191
192 // handle multiple urls
193 foreach ($urls as $key => $url) {
194 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
195 $url,
196 $this->getUser()->getId()
197 );
198
199 $results[$key]['url'] = $url;
200
201 if (false !== $entry) {
202 $em = $this->getDoctrine()->getManager();
203 $em->remove($entry);
204 $em->flush();
205
206 // entry deleted, dispatch event about it!
207 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
208 }
209
210 $results[$key]['entry'] = $entry instanceof Entry ? true : false;
211 }
212
213 return $this->sendResponse($results);
214 }
215
216 /**
217 * Handles an entries list and create URL.
218 *
219 * @ApiDoc(
220 * parameters={
221 * {"name"="urls", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...'}, {'url': 'http://...'}]", "description"="Urls (as an array) to create."}
222 * }
223 * )
224 *
225 * @return JsonResponse
226 *
227 * @throws HttpException When limit is reached
228 */
229 public function postEntriesListAction(Request $request)
230 {
231 $this->validateAuthentication();
232
233 $urls = json_decode($request->query->get('urls', []));
234 $results = [];
235
236 $limit = $this->container->getParameter('wallabag_core.api_limit_mass_actions');
237
238 if (count($urls) > $limit) {
239 throw new HttpException(400, 'API limit reached');
240 }
241
242 // handle multiple urls
243 if (!empty($urls)) {
244 foreach ($urls as $key => $url) {
245 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
246 $url,
247 $this->getUser()->getId()
248 );
249
250 $results[$key]['url'] = $url;
251
252 if (false === $entry) {
253 $entry = $this->get('wallabag_core.content_proxy')->updateEntry(
254 new Entry($this->getUser()),
255 $url
256 );
257 }
258
259 $em = $this->getDoctrine()->getManager();
260 $em->persist($entry);
261 $em->flush();
262
263 $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
264
265 // entry saved, dispatch event about it!
266 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
267 }
268 }
269
270 return $this->sendResponse($results);
271 }
272
273 /**
176 * Create an entry. 274 * Create an entry.
177 * 275 *
178 * @ApiDoc( 276 * @ApiDoc(
@@ -229,9 +327,7 @@ class EntryRestController extends WallabagRestController
229 // entry saved, dispatch event about it! 327 // entry saved, dispatch event about it!
230 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); 328 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
231 329
232 $json = $this->get('serializer')->serialize($entry, 'json'); 330 return $this->sendResponse($entry);
233
234 return (new JsonResponse())->setJson($json);
235 } 331 }
236 332
237 /** 333 /**
@@ -280,9 +376,7 @@ class EntryRestController extends WallabagRestController
280 $em = $this->getDoctrine()->getManager(); 376 $em = $this->getDoctrine()->getManager();
281 $em->flush(); 377 $em->flush();
282 378
283 $json = $this->get('serializer')->serialize($entry, 'json'); 379 return $this->sendResponse($entry);
284
285 return (new JsonResponse())->setJson($json);
286 } 380 }
287 381
288 /** 382 /**
@@ -325,9 +419,7 @@ class EntryRestController extends WallabagRestController
325 // entry saved, dispatch event about it! 419 // entry saved, dispatch event about it!
326 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); 420 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
327 421
328 $json = $this->get('serializer')->serialize($entry, 'json'); 422 return $this->sendResponse($entry);
329
330 return (new JsonResponse())->setJson($json);
331 } 423 }
332 424
333 /** 425 /**
@@ -353,9 +445,7 @@ class EntryRestController extends WallabagRestController
353 // entry deleted, dispatch event about it! 445 // entry deleted, dispatch event about it!
354 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry)); 446 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
355 447
356 $json = $this->get('serializer')->serialize($entry, 'json'); 448 return $this->sendResponse($entry);
357
358 return (new JsonResponse())->setJson($json);
359 } 449 }
360 450
361 /** 451 /**
@@ -374,9 +464,7 @@ class EntryRestController extends WallabagRestController
374 $this->validateAuthentication(); 464 $this->validateAuthentication();
375 $this->validateUserAccess($entry->getUser()->getId()); 465 $this->validateUserAccess($entry->getUser()->getId());
376 466
377 $json = $this->get('serializer')->serialize($entry->getTags(), 'json'); 467 return $this->sendResponse($entry->getTags());
378
379 return (new JsonResponse())->setJson($json);
380 } 468 }
381 469
382 /** 470 /**
@@ -407,9 +495,7 @@ class EntryRestController extends WallabagRestController
407 $em->persist($entry); 495 $em->persist($entry);
408 $em->flush(); 496 $em->flush();
409 497
410 $json = $this->get('serializer')->serialize($entry, 'json'); 498 return $this->sendResponse($entry);
411
412 return (new JsonResponse())->setJson($json);
413 } 499 }
414 500
415 /** 501 /**
@@ -434,9 +520,7 @@ class EntryRestController extends WallabagRestController
434 $em->persist($entry); 520 $em->persist($entry);
435 $em->flush(); 521 $em->flush();
436 522
437 $json = $this->get('serializer')->serialize($entry, 'json'); 523 return $this->sendResponse($entry);
438
439 return (new JsonResponse())->setJson($json);
440 } 524 }
441 525
442 /** 526 /**
@@ -455,45 +539,46 @@ class EntryRestController extends WallabagRestController
455 $this->validateAuthentication(); 539 $this->validateAuthentication();
456 540
457 $list = json_decode($request->query->get('list', [])); 541 $list = json_decode($request->query->get('list', []));
458 $results = []; 542
543 if (empty($list)) {
544 return $this->sendResponse([]);
545 }
459 546
460 // handle multiple urls 547 // handle multiple urls
461 if (!empty($list)) { 548 $results = [];
462 foreach ($list as $key => $element) {
463 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
464 $element->url,
465 $this->getUser()->getId()
466 );
467 549
468 $results[$key]['url'] = $element->url; 550 foreach ($list as $key => $element) {
469 $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false; 551 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
552 $element->url,
553 $this->getUser()->getId()
554 );
470 555
471 $tags = $element->tags; 556 $results[$key]['url'] = $element->url;
557 $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
472 558
473 if (false !== $entry && !(empty($tags))) { 559 $tags = $element->tags;
474 $tags = explode(',', $tags);
475 foreach ($tags as $label) {
476 $label = trim($label);
477 560
478 $tag = $this->getDoctrine() 561 if (false !== $entry && !(empty($tags))) {
479 ->getRepository('WallabagCoreBundle:Tag') 562 $tags = explode(',', $tags);
480 ->findOneByLabel($label); 563 foreach ($tags as $label) {
564 $label = trim($label);
481 565
482 if (false !== $tag) { 566 $tag = $this->getDoctrine()
483 $entry->removeTag($tag); 567 ->getRepository('WallabagCoreBundle:Tag')
484 } 568 ->findOneByLabel($label);
485 }
486 569
487 $em = $this->getDoctrine()->getManager(); 570 if (false !== $tag) {
488 $em->persist($entry); 571 $entry->removeTag($tag);
489 $em->flush(); 572 }
490 } 573 }
574
575 $em = $this->getDoctrine()->getManager();
576 $em->persist($entry);
577 $em->flush();
491 } 578 }
492 } 579 }
493 580
494 $json = $this->get('serializer')->serialize($results, 'json'); 581 return $this->sendResponse($results);
495
496 return (new JsonResponse())->setJson($json);
497 } 582 }
498 583
499 /** 584 /**
@@ -512,32 +597,47 @@ class EntryRestController extends WallabagRestController
512 $this->validateAuthentication(); 597 $this->validateAuthentication();
513 598
514 $list = json_decode($request->query->get('list', [])); 599 $list = json_decode($request->query->get('list', []));
600
601 if (empty($list)) {
602 return $this->sendResponse([]);
603 }
604
515 $results = []; 605 $results = [];
516 606
517 // handle multiple urls 607 // handle multiple urls
518 if (!empty($list)) { 608 foreach ($list as $key => $element) {
519 foreach ($list as $key => $element) { 609 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
520 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId( 610 $element->url,
521 $element->url, 611 $this->getUser()->getId()
522 $this->getUser()->getId() 612 );
523 );
524 613
525 $results[$key]['url'] = $element->url; 614 $results[$key]['url'] = $element->url;
526 $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false; 615 $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
527 616
528 $tags = $element->tags; 617 $tags = $element->tags;
529 618
530 if (false !== $entry && !(empty($tags))) { 619 if (false !== $entry && !(empty($tags))) {
531 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags); 620 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
532 621
533 $em = $this->getDoctrine()->getManager(); 622 $em = $this->getDoctrine()->getManager();
534 $em->persist($entry); 623 $em->persist($entry);
535 $em->flush(); 624 $em->flush();
536 }
537 } 625 }
538 } 626 }
539 627
540 $json = $this->get('serializer')->serialize($results, 'json'); 628 return $this->sendResponse($results);
629 }
630
631 /**
632 * Shortcut to send data serialized in json.
633 *
634 * @param mixed $data
635 *
636 * @return JsonResponse
637 */
638 private function sendResponse($data)
639 {
640 $json = $this->get('serializer')->serialize($data, 'json');
541 641
542 return (new JsonResponse())->setJson($json); 642 return (new JsonResponse())->setJson($json);
543 } 643 }
diff --git a/src/Wallabag/ApiBundle/Form/Type/ClientType.php b/src/Wallabag/ApiBundle/Form/Type/ClientType.php
index 0ea1a9c5..eaea4feb 100644
--- a/src/Wallabag/ApiBundle/Form/Type/ClientType.php
+++ b/src/Wallabag/ApiBundle/Form/Type/ClientType.php
@@ -16,7 +16,11 @@ class ClientType extends AbstractType
16 { 16 {
17 $builder 17 $builder
18 ->add('name', TextType::class, ['label' => 'developer.client.form.name_label']) 18 ->add('name', TextType::class, ['label' => 'developer.client.form.name_label'])
19 ->add('redirect_uris', UrlType::class, ['required' => false, 'label' => 'developer.client.form.redirect_uris_label']) 19 ->add('redirect_uris', UrlType::class, [
20 'required' => false,
21 'label' => 'developer.client.form.redirect_uris_label',
22 'property_path' => 'redirectUris',
23 ])
20 ->add('save', SubmitType::class, ['label' => 'developer.client.form.save_label']) 24 ->add('save', SubmitType::class, ['label' => 'developer.client.form.save_label'])
21 ; 25 ;
22 26
diff --git a/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php b/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php
new file mode 100644
index 00000000..65f35d8e
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php
@@ -0,0 +1,119 @@
1<?php
2
3namespace Wallabag\CoreBundle\Command;
4
5use Doctrine\ORM\NoResultException;
6use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
7use Symfony\Component\Console\Input\InputArgument;
8use Symfony\Component\Console\Input\InputInterface;
9use Symfony\Component\Console\Output\OutputInterface;
10use Wallabag\CoreBundle\Entity\Entry;
11use Wallabag\UserBundle\Entity\User;
12
13class CleanDuplicatesCommand extends ContainerAwareCommand
14{
15 /** @var OutputInterface */
16 protected $output;
17
18 protected $duplicates = 0;
19
20 protected function configure()
21 {
22 $this
23 ->setName('wallabag:clean-duplicates')
24 ->setDescription('Cleans the database for duplicates')
25 ->setHelp('This command helps you to clean your articles list in case of duplicates')
26 ->addArgument(
27 'username',
28 InputArgument::OPTIONAL,
29 'User to clean'
30 );
31 }
32
33 protected function execute(InputInterface $input, OutputInterface $output)
34 {
35 $this->output = $output;
36
37 $username = $input->getArgument('username');
38
39 if ($username) {
40 try {
41 $user = $this->getUser($username);
42 $this->cleanDuplicates($user);
43 } catch (NoResultException $e) {
44 $output->writeln(sprintf('<error>User "%s" not found.</error>', $username));
45
46 return 1;
47 }
48 } else {
49 $users = $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findAll();
50
51 $output->writeln(sprintf('Cleaning through %d user accounts', count($users)));
52
53 foreach ($users as $user) {
54 $output->writeln(sprintf('Processing user %s', $user->getUsername()));
55 $this->cleanDuplicates($user);
56 }
57 $output->writeln(sprintf('Finished cleaning. %d duplicates found in total', $this->duplicates));
58 }
59
60 return 0;
61 }
62
63 /**
64 * @param User $user
65 */
66 private function cleanDuplicates(User $user)
67 {
68 $em = $this->getContainer()->get('doctrine.orm.entity_manager');
69 $repo = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
70
71 $entries = $repo->getAllEntriesIdAndUrl($user->getId());
72
73 $duplicatesCount = 0;
74 $urls = [];
75 foreach ($entries as $entry) {
76 $url = $this->similarUrl($entry['url']);
77
78 /* @var $entry Entry */
79 if (in_array($url, $urls)) {
80 ++$duplicatesCount;
81
82 $em->remove($repo->find($entry['id']));
83 $em->flush(); // Flushing at the end of the loop would require the instance not being online
84 } else {
85 $urls[] = $entry['url'];
86 }
87 }
88
89 $this->duplicates += $duplicatesCount;
90
91 $this->output->writeln(sprintf('Cleaned %d duplicates for user %s', $duplicatesCount, $user->getUserName()));
92 }
93
94 private function similarUrl($url)
95 {
96 if (in_array(substr($url, -1), ['/', '#'])) { // get rid of "/" and "#" and the end of urls
97 return substr($url, 0, strlen($url));
98 }
99
100 return $url;
101 }
102
103 /**
104 * Fetches a user from its username.
105 *
106 * @param string $username
107 *
108 * @return \Wallabag\UserBundle\Entity\User
109 */
110 private function getUser($username)
111 {
112 return $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findOneByUserName($username);
113 }
114
115 private function getDoctrine()
116 {
117 return $this->getContainer()->get('doctrine');
118 }
119}
diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php
index 3c4d3f25..0d9364f6 100644
--- a/src/Wallabag/CoreBundle/Command/InstallCommand.php
+++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php
@@ -63,6 +63,7 @@ class InstallCommand extends ContainerAwareCommand
63 ->setupDatabase() 63 ->setupDatabase()
64 ->setupAdmin() 64 ->setupAdmin()
65 ->setupConfig() 65 ->setupConfig()
66 ->runMigrations()
66 ; 67 ;
67 68
68 $output->writeln('<info>wallabag has been successfully installed.</info>'); 69 $output->writeln('<info>wallabag has been successfully installed.</info>');
@@ -71,7 +72,7 @@ class InstallCommand extends ContainerAwareCommand
71 72
72 protected function checkRequirements() 73 protected function checkRequirements()
73 { 74 {
74 $this->defaultOutput->writeln('<info><comment>Step 1 of 4.</comment> Checking system requirements.</info>'); 75 $this->defaultOutput->writeln('<info><comment>Step 1 of 5.</comment> Checking system requirements.</info>');
75 $doctrineManager = $this->getContainer()->get('doctrine')->getManager(); 76 $doctrineManager = $this->getContainer()->get('doctrine')->getManager();
76 77
77 $rows = []; 78 $rows = [];
@@ -175,11 +176,11 @@ class InstallCommand extends ContainerAwareCommand
175 176
176 protected function setupDatabase() 177 protected function setupDatabase()
177 { 178 {
178 $this->defaultOutput->writeln('<info><comment>Step 2 of 4.</comment> Setting up database.</info>'); 179 $this->defaultOutput->writeln('<info><comment>Step 2 of 5.</comment> Setting up database.</info>');
179 180
180 // user want to reset everything? Don't care about what is already here 181 // user want to reset everything? Don't care about what is already here
181 if (true === $this->defaultInput->getOption('reset')) { 182 if (true === $this->defaultInput->getOption('reset')) {
182 $this->defaultOutput->writeln('Droping database, creating database and schema, clearing the cache'); 183 $this->defaultOutput->writeln('Dropping database, creating database and schema, clearing the cache');
183 184
184 $this 185 $this
185 ->runCommand('doctrine:database:drop', ['--force' => true]) 186 ->runCommand('doctrine:database:drop', ['--force' => true])
@@ -211,7 +212,7 @@ class InstallCommand extends ContainerAwareCommand
211 $question = new ConfirmationQuestion('It appears that your database already exists. Would you like to reset it? (y/N)', false); 212 $question = new ConfirmationQuestion('It appears that your database already exists. Would you like to reset it? (y/N)', false);
212 213
213 if ($questionHelper->ask($this->defaultInput, $this->defaultOutput, $question)) { 214 if ($questionHelper->ask($this->defaultInput, $this->defaultOutput, $question)) {
214 $this->defaultOutput->writeln('Droping database, creating database and schema'); 215 $this->defaultOutput->writeln('Dropping database, creating database and schema');
215 216
216 $this 217 $this
217 ->runCommand('doctrine:database:drop', ['--force' => true]) 218 ->runCommand('doctrine:database:drop', ['--force' => true])
@@ -221,7 +222,7 @@ class InstallCommand extends ContainerAwareCommand
221 } elseif ($this->isSchemaPresent()) { 222 } elseif ($this->isSchemaPresent()) {
222 $question = new ConfirmationQuestion('Seems like your database contains schema. Do you want to reset it? (y/N)', false); 223 $question = new ConfirmationQuestion('Seems like your database contains schema. Do you want to reset it? (y/N)', false);
223 if ($questionHelper->ask($this->defaultInput, $this->defaultOutput, $question)) { 224 if ($questionHelper->ask($this->defaultInput, $this->defaultOutput, $question)) {
224 $this->defaultOutput->writeln('Droping schema and creating schema'); 225 $this->defaultOutput->writeln('Dropping schema and creating schema');
225 226
226 $this 227 $this
227 ->runCommand('doctrine:schema:drop', ['--force' => true]) 228 ->runCommand('doctrine:schema:drop', ['--force' => true])
@@ -246,7 +247,7 @@ class InstallCommand extends ContainerAwareCommand
246 247
247 protected function setupAdmin() 248 protected function setupAdmin()
248 { 249 {
249 $this->defaultOutput->writeln('<info><comment>Step 3 of 4.</comment> Administration setup.</info>'); 250 $this->defaultOutput->writeln('<info><comment>Step 3 of 5.</comment> Administration setup.</info>');
250 251
251 $questionHelper = $this->getHelperSet()->get('question'); 252 $questionHelper = $this->getHelperSet()->get('question');
252 $question = new ConfirmationQuestion('Would you like to create a new admin user (recommended) ? (Y/n)', true); 253 $question = new ConfirmationQuestion('Would you like to create a new admin user (recommended) ? (Y/n)', true);
@@ -285,7 +286,7 @@ class InstallCommand extends ContainerAwareCommand
285 286
286 protected function setupConfig() 287 protected function setupConfig()
287 { 288 {
288 $this->defaultOutput->writeln('<info><comment>Step 4 of 4.</comment> Config setup.</info>'); 289 $this->defaultOutput->writeln('<info><comment>Step 4 of 5.</comment> Config setup.</info>');
289 $em = $this->getContainer()->get('doctrine.orm.entity_manager'); 290 $em = $this->getContainer()->get('doctrine.orm.entity_manager');
290 291
291 // cleanup before insert new stuff 292 // cleanup before insert new stuff
@@ -464,6 +465,14 @@ class InstallCommand extends ContainerAwareCommand
464 return $this; 465 return $this;
465 } 466 }
466 467
468 protected function runMigrations()
469 {
470 $this->defaultOutput->writeln('<info><comment>Step 5 of 5.</comment> Run migrations.</info>');
471
472 $this
473 ->runCommand('doctrine:migrations:migrate', ['--no-interaction' => true]);
474 }
475
467 /** 476 /**
468 * Run a command. 477 * Run a command.
469 * 478 *
diff --git a/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php b/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php
index 006a18c3..75b37729 100644
--- a/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php
+++ b/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php
@@ -47,6 +47,9 @@ class Configuration implements ConfigurationInterface
47 ->scalarNode('list_mode') 47 ->scalarNode('list_mode')
48 ->defaultValue(1) 48 ->defaultValue(1)
49 ->end() 49 ->end()
50 ->scalarNode('api_limit_mass_actions')
51 ->defaultValue(10)
52 ->end()
50 ->end() 53 ->end()
51 ; 54 ;
52 55
diff --git a/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php b/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php
index aa9ee339..c075c19f 100644
--- a/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php
+++ b/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php
@@ -26,6 +26,7 @@ class WallabagCoreExtension extends Extension
26 $container->setParameter('wallabag_core.action_mark_as_read', $config['action_mark_as_read']); 26 $container->setParameter('wallabag_core.action_mark_as_read', $config['action_mark_as_read']);
27 $container->setParameter('wallabag_core.list_mode', $config['list_mode']); 27 $container->setParameter('wallabag_core.list_mode', $config['list_mode']);
28 $container->setParameter('wallabag_core.fetching_error_message', $config['fetching_error_message']); 28 $container->setParameter('wallabag_core.fetching_error_message', $config['fetching_error_message']);
29 $container->setParameter('wallabag_core.api_limit_mass_actions', $config['api_limit_mass_actions']);
29 30
30 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 31 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
31 $loader->load('services.yml'); 32 $loader->load('services.yml');
diff --git a/src/Wallabag/CoreBundle/Form/Type/ConfigType.php b/src/Wallabag/CoreBundle/Form/Type/ConfigType.php
index 7e3b9dd4..1714ce74 100644
--- a/src/Wallabag/CoreBundle/Form/Type/ConfigType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/ConfigType.php
@@ -39,9 +39,11 @@ class ConfigType extends AbstractType
39 ]) 39 ])
40 ->add('items_per_page', null, [ 40 ->add('items_per_page', null, [
41 'label' => 'config.form_settings.items_per_page_label', 41 'label' => 'config.form_settings.items_per_page_label',
42 'property_path' => 'itemsPerPage',
42 ]) 43 ])
43 ->add('reading_speed', ChoiceType::class, [ 44 ->add('reading_speed', ChoiceType::class, [
44 'label' => 'config.form_settings.reading_speed.label', 45 'label' => 'config.form_settings.reading_speed.label',
46 'property_path' => 'readingSpeed',
45 'choices' => [ 47 'choices' => [
46 'config.form_settings.reading_speed.100_word' => '0.5', 48 'config.form_settings.reading_speed.100_word' => '0.5',
47 'config.form_settings.reading_speed.200_word' => '1', 49 'config.form_settings.reading_speed.200_word' => '1',
@@ -51,6 +53,7 @@ class ConfigType extends AbstractType
51 ]) 53 ])
52 ->add('action_mark_as_read', ChoiceType::class, [ 54 ->add('action_mark_as_read', ChoiceType::class, [
53 'label' => 'config.form_settings.action_mark_as_read.label', 55 'label' => 'config.form_settings.action_mark_as_read.label',
56 'property_path' => 'actionMarkAsRead',
54 'choices' => [ 57 'choices' => [
55 'config.form_settings.action_mark_as_read.redirect_homepage' => Config::REDIRECT_TO_HOMEPAGE, 58 'config.form_settings.action_mark_as_read.redirect_homepage' => Config::REDIRECT_TO_HOMEPAGE,
56 'config.form_settings.action_mark_as_read.redirect_current_page' => Config::REDIRECT_TO_CURRENT_PAGE, 59 'config.form_settings.action_mark_as_read.redirect_current_page' => Config::REDIRECT_TO_CURRENT_PAGE,
@@ -61,6 +64,7 @@ class ConfigType extends AbstractType
61 'label' => 'config.form_settings.language_label', 64 'label' => 'config.form_settings.language_label',
62 ]) 65 ])
63 ->add('pocket_consumer_key', null, [ 66 ->add('pocket_consumer_key', null, [
67 'property_path' => 'pocketConsumerKey',
64 'label' => 'config.form_settings.pocket_consumer_key_label', 68 'label' => 'config.form_settings.pocket_consumer_key_label',
65 ]) 69 ])
66 ->add('save', SubmitType::class, [ 70 ->add('save', SubmitType::class, [
diff --git a/src/Wallabag/CoreBundle/Form/Type/RssType.php b/src/Wallabag/CoreBundle/Form/Type/RssType.php
index 94324fed..49b31c1e 100644
--- a/src/Wallabag/CoreBundle/Form/Type/RssType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/RssType.php
@@ -14,6 +14,7 @@ class RssType extends AbstractType
14 $builder 14 $builder
15 ->add('rss_limit', null, [ 15 ->add('rss_limit', null, [
16 'label' => 'config.form_rss.rss_limit', 16 'label' => 'config.form_rss.rss_limit',
17 'property_path' => 'rssLimit',
17 ]) 18 ])
18 ->add('save', SubmitType::class, [ 19 ->add('save', SubmitType::class, [
19 'label' => 'config.form.save', 20 'label' => 'config.form.save',
diff --git a/src/Wallabag/CoreBundle/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
index 1f22e901..6972e974 100644
--- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
@@ -379,4 +379,34 @@ class EntryRepository extends EntityRepository
379 ->setParameter('userId', $userId) 379 ->setParameter('userId', $userId)
380 ->execute(); 380 ->execute();
381 } 381 }
382
383 /**
384 * Get id and url from all entries
385 * Used for the clean-duplicates command.
386 */
387 public function getAllEntriesIdAndUrl($userId)
388 {
389 $qb = $this->createQueryBuilder('e')
390 ->select('e.id, e.url')
391 ->where('e.user = :userid')->setParameter(':userid', $userId);
392
393 return $qb->getQuery()->getArrayResult();
394 }
395
396 /**
397 * Find all entries by url and owner.
398 *
399 * @param $url
400 * @param $userId
401 *
402 * @return array
403 */
404 public function findAllByUrlAndUserId($url, $userId)
405 {
406 return $this->createQueryBuilder('e')
407 ->where('e.url = :url')->setParameter('url', urldecode($url))
408 ->andWhere('e.user = :user_id')->setParameter('user_id', $userId)
409 ->getQuery()
410 ->getResult();
411 }
382} 412}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig
index 859b166b..bdd44b54 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig
@@ -17,9 +17,9 @@
17 <div class="results"> 17 <div class="results">
18 <div class="nb-results">{{ 'entry.list.number_on_the_page'|transchoice(entries.count) }}</div> 18 <div class="nb-results">{{ 'entry.list.number_on_the_page'|transchoice(entries.count) }}</div>
19 <div class="pagination"> 19 <div class="pagination">
20 <a href="{{ path('switch_view_mode') }}"><i class="listMode-btn material-icons md-36">{% if listMode == 0 %}list{% else %}view_module{% endif %}</i></a> 20 <a href="{{ path('switch_view_mode') }}"><i class="listMode-btn material-icons md-24">{% if listMode == 0 %}list{% else %}view_module{% endif %}</i></a>
21 <i class="btn-clickable download-btn material-icons md-36 js-export-action">file_download</i> 21 <i class="btn-clickable download-btn material-icons md-24 js-export-action">file_download</i>
22 <i class="btn-clickable filter-btn material-icons md-36 js-filters-action">filter_list</i> 22 <i class="btn-clickable filter-btn material-icons md-24 js-filters-action">filter_list</i>
23 {% if entries.getNbPages > 1 %} 23 {% if entries.getNbPages > 1 %}
24 {{ pagerfanta(entries, 'twitter_bootstrap_translated', {'proximity': 1}) }} 24 {{ pagerfanta(entries, 'twitter_bootstrap_translated', {'proximity': 1}) }}
25 {% endif %} 25 {% endif %}
@@ -47,10 +47,10 @@
47 </div> 47 </div>
48 48
49 <ul class="tools links"> 49 <ul class="tools links">
50 <li><a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool icon-check icon {% if entry.isArchived == 0 %}archive-off{% else %}archive{% endif %}" href="{{ path('archive_entry', { 'id': entry.id }) }}"><span>{{ 'entry.list.toogle_as_read'|trans }}</span></a></li> 50 <li><a href="{{ entry.url|e }}" target="_blank" title="{{ 'entry.list.original_article'|trans }} : {{ entry.title|e }}"><span>{{ entry.domainName|removeWww }}</span></a></li>
51 <li><a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool icon-star icon {% if entry.isStarred == 0 %}fav-off{% else %}fav{% endif %}" href="{{ path('star_entry', { 'id': entry.id }) }}"><span>{{ 'entry.list.toogle_as_star'|trans }}</span></a></li> 51 <li><a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool icon {% if entry.isArchived == 0 %}archive-off{% else %}archive{% endif %}" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons md-24 vertical-align-middle">check</i><span>{{ 'entry.list.toogle_as_read'|trans }}</span></a></li>
52 <li><a title="{{ 'entry.list.delete'|trans }}" class="tool delete icon-trash icon" href="{{ path('delete_entry', { 'id': entry.id }) }}"><span>{{ 'entry.list.delete'|trans }}</span></a></li> 52 <li><a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool icon {% if entry.isStarred == 0 %}fav-off{% else %}fav{% endif %}" href="{{ path('star_entry', { 'id': entry.id }) }}"><i class="material-icons md-24 vertical-align-middle">star_rate</i><span>{{ 'entry.list.toogle_as_star'|trans }}</span></a></li>
53 <li><a href="{{ entry.url|e }}" target="_blank" title="{{ 'entry.list.original_article'|trans }} : {{ entry.title|e }}" class="tool link icon-link icon"><span>{{ entry.domainName|removeWww }}</span></a></li> 53 <li><a title="{{ 'entry.list.delete'|trans }}" class="tool icon" href="{{ path('delete_entry', { 'id': entry.id }) }}"><i class="material-icons md-24 vertical-align-middle">delete</i><span>{{ 'entry.list.delete'|trans }}</span></a></li>
54 </ul> 54 </ul>
55 {% if (entry.previewPicture is null or listMode == 1) %} 55 {% if (entry.previewPicture is null or listMode == 1) %}
56 <ul class="card-entry-tags"> 56 <ul class="card-entry-tags">
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entry.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entry.html.twig
index 426ce91c..660211f2 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entry.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entry.html.twig
@@ -77,7 +77,7 @@
77 <span class="label-outline"><i class="material-icons">label_outline</i> <a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{ tag.label }}</a> <a href="{{ path('remove_tag', { 'entry': entry.id, 'tag': tag.id }) }}" class="nostyle"><i>✘</i></a></span> 77 <span class="label-outline"><i class="material-icons">label_outline</i> <a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{ tag.label }}</a> <a href="{{ path('remove_tag', { 'entry': entry.id, 'tag': tag.id }) }}" class="nostyle"><i>✘</i></a></span>
78 {% endfor %} 78 {% endfor %}
79 </div> 79 </div>
80 <div class="input-field nav-panel-add-tag" style="display: none"> 80 <div class="input-field baggy-add-tag" style="display: none">
81 {{ render(controller( "WallabagCoreBundle:Tag:addTagForm", { 'id': entry.id } )) }} 81 {{ render(controller( "WallabagCoreBundle:Tag:addTagForm", { 'id': entry.id } )) }}
82 </div> 82 </div>
83 </aside> 83 </aside>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig
index 07ff8e14..42aeace9 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig
@@ -2,12 +2,14 @@
2 2
3{% block css %} 3{% block css %}
4 {{ parent() }} 4 {{ parent() }}
5 <link rel="stylesheet" href="{{ asset('bundles/wallabagcore/themes/baggy/css/style.min.css') }}" media="screen,projection,print"/> 5 {% if not app.debug %}
6 <link rel="stylesheet" href="{{ asset('bundles/wallabagcore/baggy.css') }}">
7 {% endif %}
6{% endblock %} 8{% endblock %}
7 9
8{% block scripts %} 10{% block scripts %}
9 {{ parent() }} 11 {{ parent() }}
10 <script src="{{ asset('bundles/wallabagcore/themes/baggy/js/baggy.min.js') }}"></script> 12 <script src="{{ asset('bundles/wallabagcore/baggy' ~ (app.debug ? '.dev' : '') ~ '.js') }}"></script>
11{% endblock %} 13{% endblock %}
12 14
13{% block header %} 15{% block header %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/common/Static/about.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/common/Static/about.html.twig
index db193e81..f82e5dc5 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/common/Static/about.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/common/Static/about.html.twig
@@ -106,8 +106,8 @@
106 <tr><td>hoa/zformat</td><td>BSD-3-Clause</td></tr> 106 <tr><td>hoa/zformat</td><td>BSD-3-Clause</td></tr>
107 <tr><td>htmlawed/htmlawed</td><td>GPL-2.0+ or LGPL-3.0</td></tr> 107 <tr><td>htmlawed/htmlawed</td><td>GPL-2.0+ or LGPL-3.0</td></tr>
108 <tr><td>incenteev/composer-parameter-handler</td><td>MIT</td></tr> 108 <tr><td>incenteev/composer-parameter-handler</td><td>MIT</td></tr>
109 <tr><td>j0k3r/graby</td><td>AGPL-3.0</td></tr> 109 <tr><td>j0k3r/graby</td><td>MIT</td></tr>
110 <tr><td>j0k3r/graby-site-config</td><td>AGPL-3.0</td></tr> 110 <tr><td>j0k3r/graby-site-config</td><td>Public domain</td></tr>
111 <tr><td>j0k3r/php-readability</td><td>Apache-2.0</td></tr> 111 <tr><td>j0k3r/php-readability</td><td>Apache-2.0</td></tr>
112 <tr><td>j0k3r/safecurl</td><td>MIT</td></tr> 112 <tr><td>j0k3r/safecurl</td><td>MIT</td></tr>
113 <tr><td>jdorn/sql-formatter</td><td>MIT</td></tr> 113 <tr><td>jdorn/sql-formatter</td><td>MIT</td></tr>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
index d6e414e9..9b0816eb 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig
@@ -25,9 +25,9 @@
25 25
26 <div class="row"> 26 <div class="row">
27 <div class="input-field col s11"> 27 <div class="input-field col s11">
28 {{ form_label(form.config.theme) }}
29 {{ form_errors(form.config.theme) }} 28 {{ form_errors(form.config.theme) }}
30 {{ form_widget(form.config.theme) }} 29 {{ form_widget(form.config.theme) }}
30 {{ form_label(form.config.theme) }}
31 </div> 31 </div>
32 <div class="input-field col s1"> 32 <div class="input-field col s1">
33 <a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_theme'|trans }}"> 33 <a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_theme'|trans }}">
@@ -38,9 +38,9 @@
38 38
39 <div class="row"> 39 <div class="row">
40 <div class="input-field col s11"> 40 <div class="input-field col s11">
41 {{ form_label(form.config.items_per_page) }}
42 {{ form_errors(form.config.items_per_page) }} 41 {{ form_errors(form.config.items_per_page) }}
43 {{ form_widget(form.config.items_per_page) }} 42 {{ form_widget(form.config.items_per_page) }}
43 {{ form_label(form.config.items_per_page) }}
44 </div> 44 </div>
45 <div class="input-field col s1"> 45 <div class="input-field col s1">
46 <a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_items_per_page'|trans }}"> 46 <a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_items_per_page'|trans }}">
@@ -51,9 +51,9 @@
51 51
52 <div class="row"> 52 <div class="row">
53 <div class="input-field col s11"> 53 <div class="input-field col s11">
54 {{ form_label(form.config.reading_speed) }}
55 {{ form_errors(form.config.reading_speed) }} 54 {{ form_errors(form.config.reading_speed) }}
56 {{ form_widget(form.config.reading_speed) }} 55 {{ form_widget(form.config.reading_speed) }}
56 {{ form_label(form.config.reading_speed) }}
57 <p> 57 <p>
58 {{ 'config.form_settings.reading_speed.help_message'|trans }} 58 {{ 'config.form_settings.reading_speed.help_message'|trans }}
59 <a href="http://www.myreadspeed.com/calculate/">myreadspeed</a> 59 <a href="http://www.myreadspeed.com/calculate/">myreadspeed</a>
@@ -68,17 +68,17 @@
68 68
69 <div class="row"> 69 <div class="row">
70 <div class="input-field col s12"> 70 <div class="input-field col s12">
71 {{ form_label(form.config.action_mark_as_read) }}
72 {{ form_errors(form.config.action_mark_as_read) }} 71 {{ form_errors(form.config.action_mark_as_read) }}
73 {{ form_widget(form.config.action_mark_as_read) }} 72 {{ form_widget(form.config.action_mark_as_read) }}
73 {{ form_label(form.config.action_mark_as_read) }}
74 </div> 74 </div>
75 </div> 75 </div>
76 76
77 <div class="row"> 77 <div class="row">
78 <div class="input-field col s11"> 78 <div class="input-field col s11">
79 {{ form_label(form.config.language) }}
80 {{ form_errors(form.config.language) }} 79 {{ form_errors(form.config.language) }}
81 {{ form_widget(form.config.language) }} 80 {{ form_widget(form.config.language) }}
81 {{ form_label(form.config.language) }}
82 </div> 82 </div>
83 <div class="input-field col s1"> 83 <div class="input-field col s1">
84 <a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_language'|trans }}"> 84 <a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_settings.help_language'|trans }}">
@@ -89,9 +89,9 @@
89 89
90 <div class="row"> 90 <div class="row">
91 <div class="input-field col s11"> 91 <div class="input-field col s11">
92 {{ form_label(form.config.pocket_consumer_key) }}
93 {{ form_errors(form.config.pocket_consumer_key) }} 92 {{ form_errors(form.config.pocket_consumer_key) }}
94 {{ form_widget(form.config.pocket_consumer_key) }} 93 {{ form_widget(form.config.pocket_consumer_key) }}
94 {{ form_label(form.config.pocket_consumer_key) }}
95 <p> 95 <p>
96 &raquo; 96 &raquo;
97 <a href="https://getpocket.com/developer/docs/authentication">https://getpocket.com/developer/docs/authentication</a> 97 <a href="https://getpocket.com/developer/docs/authentication">https://getpocket.com/developer/docs/authentication</a>
@@ -132,8 +132,8 @@
132 </div> 132 </div>
133 133
134 <div class="row"> 134 <div class="row">
135 <div class="input-field col s12"> 135 <div class="col s12">
136 <label>{{ 'config.form_rss.token_label'|trans }}</label> 136 <h6 class="grey-text">{{ 'config.form_rss.token_label'|trans }}</h6>
137 <div> 137 <div>
138 {% if rss.token %} 138 {% if rss.token %}
139 {{ rss.token }} 139 {{ rss.token }}
@@ -151,8 +151,8 @@
151 </div> 151 </div>
152 {% if rss.token %} 152 {% if rss.token %}
153 <div class="row"> 153 <div class="row">
154 <div class="input-field col s12"> 154 <div class="col s12">
155 <label>{{ 'config.form_rss.rss_links'|trans }}</label> 155 <h6 class="grey-text">{{ 'config.form_rss.rss_links'|trans }}</h6>
156 <ul> 156 <ul>
157 <li><a href="{{ path('unread_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.unread'|trans }}</a></li> 157 <li><a href="{{ path('unread_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.unread'|trans }}</a></li>
158 <li><a href="{{ path('starred_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.starred'|trans }}</a></li> 158 <li><a href="{{ path('starred_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.starred'|trans }}</a></li>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_reading_time.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_reading_time.html.twig
index 1a932a9f..6ba18768 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_reading_time.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_reading_time.html.twig
@@ -1,7 +1,7 @@
1{% set readingTime = entry.readingTime / app.user.config.readingSpeed %} 1{% set readingTime = entry.readingTime / app.user.config.readingSpeed %}
2<i class="material-icons">timer</i> 2<i class="material-icons">timer</i>
3{% if readingTime > 0 %} 3{% if readingTime > 0 %}
4 {{ 'entry.list.reading_time_minutes_short'|trans({'%readingTime%': readingTime|round}) }} 4 <span>{{ 'entry.list.reading_time_minutes_short'|trans({'%readingTime%': readingTime|round}) }}</span>
5{% else %} 5{% else %}
6 {{ 'entry.list.reading_time_less_one_minute_short'|trans|raw }} 6 <span>{{ 'entry.list.reading_time_less_one_minute_short'|trans|raw }}</span>
7{% endif %} 7{% endif %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig
index 5fca53ae..b2d91c9c 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig
@@ -45,7 +45,7 @@
45 {% endif %} 45 {% endif %}
46 46
47 <!-- Export --> 47 <!-- Export -->
48 <div id="export" class="side-nav fixed right-aligned"> 48 <div id="export" class="side-nav right-aligned">
49 {% set currentRoute = app.request.attributes.get('_route') %} 49 {% set currentRoute = app.request.attributes.get('_route') %}
50 {% set currentTag = '' %} 50 {% set currentTag = '' %}
51 {% if tag is defined %} 51 {% if tag is defined %}
@@ -68,7 +68,7 @@
68 68
69 <!-- Filters --> 69 <!-- Filters -->
70 {% if form is not null %} 70 {% if form is not null %}
71 <div id="filters" class="side-nav fixed right-aligned"> 71 <div id="filters" class="side-nav right-aligned">
72 <form action="{{ path('all') }}"> 72 <form action="{{ path('all') }}">
73 73
74 <h4 class="center">{{ 'entry.filters.title'|trans }}</h4> 74 <h4 class="center">{{ 'entry.filters.title'|trans }}</h4>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig
index 3c4ad024..47e6e8c3 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig
@@ -125,47 +125,43 @@
125 {% endif %} 125 {% endif %}
126 {% if craue_setting('share_shaarli') %} 126 {% if craue_setting('share_shaarli') %}
127 <li> 127 <li>
128 <a href="{{ craue_setting('shaarli_url') }}/index.php?post={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}&amp;tags={{ entry.tags|join(',')|striptags|url_encode }}" target="_blank"> 128 <a href="{{ craue_setting('shaarli_url') }}/index.php?post={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}&amp;tags={{ entry.tags|join(',')|striptags|url_encode }}" target="_blank" title="shaarli" class="tool icon-image shaarli">
129 <i class="tool icon-image icon-image--shaarli" title="shaarli"></i>
130 <span>shaarli</span> 129 <span>shaarli</span>
131 </a> 130 </a>
132 </li> 131 </li>
133 {% endif %} 132 {% endif %}
134 {% if craue_setting('share_scuttle') %} 133 {% if craue_setting('share_scuttle') %}
135 <li> 134 <li>
136 <a href="{{ craue_setting('scuttle_url') }}/bookmarks.php?action=add&amp;address={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}&amp;tags={{ entry.tags|join(',')|striptags|url_encode }}" target="_blank"> 135 <a href="{{ craue_setting('scuttle_url') }}/bookmarks.php?action=add&amp;address={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}&amp;tags={{ entry.tags|join(',')|striptags|url_encode }}" target="_blank" title="scuttle" class="tool icon-image scuttle">
137 <i class="tool icon-image icon-image--scuttle" title="scuttle"></i>
138 <span>scuttle</span> 136 <span>scuttle</span>
139 </a> 137 </a>
140 </li> 138 </li>
141 {% endif %} 139 {% endif %}
142 {% if craue_setting('share_diaspora') %} 140 {% if craue_setting('share_diaspora') %}
143 <li> 141 <li>
144 <a href="{{ craue_setting('diaspora_url') }}/bookmarklet?url={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}&amp;notes=&amp;v=1&amp;noui=1&amp;jump=doclose" target="_blank"> 142 <a href="{{ craue_setting('diaspora_url') }}/bookmarklet?url={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}&amp;notes=&amp;v=1&amp;noui=1&amp;jump=doclose" target="_blank" class="tool icon-image diaspora" title="diaspora">
145 <i class="tool icon-image icon-image--diaspora" title="diaspora"></i>
146 <span>diaspora*</span> 143 <span>diaspora*</span>
147 </a> 144 </a>
148 </li> 145 </li>
149 {% endif %} 146 {% endif %}
150 {% if craue_setting('share_unmark') %} 147 {% if craue_setting('share_unmark') %}
151 <li> 148 <li>
152 <a href="{{ craue_setting('unmark_url') }}/mark/add?url={{ entry.url|url_encode }}&amp;title={{entry.title|striptags|url_encode}}&amp;v=6" target="_blank"> 149 <a href="{{ craue_setting('unmark_url') }}/mark/add?url={{ entry.url|url_encode }}&amp;title={{entry.title|striptags|url_encode}}&amp;v=6" target="_blank" class="tool icon-image unmark" title="unmark">
153 <i class="tool icon-image icon-image--unmark" title="unmark"></i>
154 <span>unmark.it</span> 150 <span>unmark.it</span>
155 </a> 151 </a>
156 </li> 152 </li>
157 {% endif %} 153 {% endif %}
158 {% if craue_setting('carrot') %} 154 {% if craue_setting('carrot') %}
159 <li> 155 <li>
160 <a href="https://secure.carrot.org/GiveAndGetBack.do?url={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}" target="_blank" title="carrot"> 156 <a href="https://secure.carrot.org/GiveAndGetBack.do?url={{ entry.url|url_encode }}&amp;title={{ entry.title|striptags|url_encode }}" target="_blank" title="carrot" class="tool icon-image carrot">
161 <i class="tool icon-image icon-image--carrot"></i>
162 <span>Carrot</span> 157 <span>Carrot</span>
163 </a> 158 </a>
164 </li> 159 </li>
165 {% endif %} 160 {% endif %}
166 {% if craue_setting('share_mail') %} 161 {% if craue_setting('share_mail') %}
167 <li> 162 <li>
168 <a href="mailto:?subject={{ entry.title|striptags|url_encode }}&amp;body={{ entry.url|url_encode }}%20via%20@wallabagapp" title="{{ 'entry.view.left_menu.share_email_label'|trans }}" class="tool email icon icon-mail"> 163 <a href="mailto:?subject={{ entry.title|striptags|url_encode }}&amp;body={{ entry.url|url_encode }}%20via%20@wallabagapp" title="{{ 'entry.view.left_menu.share_email_label'|trans }}" class="tool icon">
164 <i class="material-icons vertical-align-middle">mail</i>
169 <span>{{ 'entry.view.left_menu.share_email_label'|trans }}</span> 165 <span>{{ 'entry.view.left_menu.share_email_label'|trans }}</span>
170 </a> 166 </a>
171 </li> 167 </li>
@@ -220,46 +216,48 @@
220 <h1>{{ entry.title|striptags|raw }} <a href="{{ path('edit', { 'id': entry.id }) }}" title="{{ 'entry.view.edit_title'|trans }}">✎</a></h1> 216 <h1>{{ entry.title|striptags|raw }} <a href="{{ path('edit', { 'id': entry.id }) }}" title="{{ 'entry.view.edit_title'|trans }}">✎</a></h1>
221 </header> 217 </header>
222 <aside> 218 <aside>
223 <ul class="tools"> 219 <div class="tools">
224 <li> 220 <ul class="stats">
225 {% include "@WallabagCore/themes/material/Entry/_reading_time.html.twig" with {'entry': entry} only %}
226 </li>
227 <li>
228 <i class="material-icons" title="{{ 'entry.view.created_at'|trans }}">today</i>
229 {{ entry.createdAt|date('Y-m-d H:i') }}
230 </li>
231 {% if entry.publishedAt is not null %}
232 <li>
233 <i class="material-icons" title="{{ 'entry.view.published_at'|trans }}">create</i>
234 {{ entry.publishedAt|date('Y-m-d H:i') }}
235 </li>
236 {% endif %}
237 {% if entry.publishedBy is not empty %}
238 <li> 221 <li>
239 <i class="material-icons" title="{{ 'entry.view.published_by'|trans }}">person</i> 222 {% include "@WallabagCore/themes/material/Entry/_reading_time.html.twig" with {'entry': entry} only %}
240 {% for author in entry.publishedBy %}
241 {{ author }}{% if not loop.last %}, {% endif %}
242 {% endfor %}
243 </li> 223 </li>
244 {% endif %} 224 <li>
245 <li> 225 <i class="material-icons" title="{{ 'entry.view.created_at'|trans }}">today</i>
246 <i class="material-icons link">link</i> 226 {{ entry.createdAt|date('Y-m-d H:i') }}
247 <a href="{{ entry.url|e }}" target="_blank" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|striptags }}" class="tool"> 227 </li>
248 {{ entry.domainName|removeWww }} 228 {% if entry.publishedAt is not null %}
249 </a> 229 <li>
250 </li> 230 <i class="material-icons" title="{{ 'entry.view.published_at'|trans }}">create</i>
251 <li> 231 {{ entry.publishedAt|date('Y-m-d H:i') }}
252 <i class="material-icons link">comment</i> 232 </li>
253 {{ 'entry.view.annotations_on_the_entry'|transchoice(entry.annotations | length) }} 233 {% endif %}
254 </li> 234 {% if entry.publishedBy is not empty %}
255 <li id="list"> 235 <li>
236 <i class="material-icons" title="{{ 'entry.view.published_by'|trans }}">person</i>
237 {% for author in entry.publishedBy %}
238 {{ author }}{% if not loop.last %}, {% endif %}
239 {% endfor %}
240 </li>
241 {% endif %}
242 <li>
243 <i class="material-icons link">link</i>
244 <a href="{{ entry.url|e }}" target="_blank" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|striptags }}" class="tool">
245 {{ entry.domainName|removeWww }}
246 </a>
247 </li>
248 <li>
249 <i class="material-icons link">comment</i>
250 {{ 'entry.view.annotations_on_the_entry'|transchoice(entry.annotations | length) }}
251 </li>
252 </ul>
253 <ul class="tags">
256 {% for tag in entry.tags %} 254 {% for tag in entry.tags %}
257 <div class="chip"> 255 <li class="chip">
258 <a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{ tag.label }}</a> <a href="{{ path('remove_tag', { 'entry': entry.id, 'tag': tag.id }) }}"><i class="material-icons">delete</i></a> 256 <a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{ tag.label }}</a> <a href="{{ path('remove_tag', { 'entry': entry.id, 'tag': tag.id }) }}"><i class="material-icons vertical-align-middle">delete</i></a>
259 </div> 257 </li>
260 {% endfor %} 258 {% endfor %}
261 </li> 259 </ul>
262 </ul> 260 </div>
263 261
264 <div class="input-field nav-panel-add-tag" style="display: none"> 262 <div class="input-field nav-panel-add-tag" style="display: none">
265 {{ render(controller( "WallabagCoreBundle:Tag:addTagForm", { 'id': entry.id } )) }} 263 {{ render(controller( "WallabagCoreBundle:Tag:addTagForm", { 'id': entry.id } )) }}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig
index 3c169c04..2dab1c18 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig
@@ -2,12 +2,14 @@
2 2
3{% block css %} 3{% block css %}
4 {{ parent() }} 4 {{ parent() }}
5 <link rel="stylesheet" href="{{ asset('bundles/wallabagcore/themes/material/css/style.min.css') }}" media="screen,projection,print"/> 5 {% if not app.debug %}
6 <link rel="stylesheet" href="{{ asset('bundles/wallabagcore/material.css') }}">
7 {% endif %}
6{% endblock %} 8{% endblock %}
7 9
8{% block scripts %} 10{% block scripts %}
9 {{ parent() }} 11 {{ parent() }}
10 <script src="{{ asset('bundles/wallabagcore/themes/material/js/material.min.js') }}"></script> 12 <script src="{{ asset('bundles/wallabagcore/material' ~ (app.debug ? '.dev' : '') ~ '.js') }}"></script>
11{% endblock %} 13{% endblock %}
12 14
13{% block header %} 15{% block header %}
@@ -116,12 +118,12 @@
116 </ul> 118 </ul>
117 <div class="input-field nav-panel-search" style="display: none"> 119 <div class="input-field nav-panel-search" style="display: none">
118 {{ render(controller("WallabagCoreBundle:Entry:searchForm", {'currentRoute': app.request.attributes.get('_route')})) }} 120 {{ render(controller("WallabagCoreBundle:Entry:searchForm", {'currentRoute': app.request.attributes.get('_route')})) }}
119 <label for="search" class="active"><i class="material-icons search">search</i></label> 121 <label for="search"><i class="material-icons search">search</i></label>
120 <i class="material-icons close">clear</i> 122 <i class="material-icons close">clear</i>
121 </div> 123 </div>
122 <div class="input-field nav-panel-add" style="display: none"> 124 <div class="input-field nav-panel-add" style="display: none">
123 {{ render(controller("WallabagCoreBundle:Entry:addEntryForm")) }} 125 {{ render(controller("WallabagCoreBundle:Entry:addEntryForm")) }}
124 <label for="add" class="active"><i class="material-icons add">add</i></label> 126 <label for="add"><i class="material-icons add">add</i></label>
125 <i class="material-icons close">clear</i> 127 <i class="material-icons close">clear</i>
126 </div> 128 </div>
127 </div> 129 </div>
diff --git a/src/Wallabag/UserBundle/Resources/views/Security/login.html.twig b/src/Wallabag/UserBundle/Resources/views/Security/login.html.twig
index fc0d97e7..32984c84 100644
--- a/src/Wallabag/UserBundle/Resources/views/Security/login.html.twig
+++ b/src/Wallabag/UserBundle/Resources/views/Security/login.html.twig
@@ -5,7 +5,7 @@
5 <div class="card-content"> 5 <div class="card-content">
6 6
7 {% if error %} 7 {% if error %}
8 <script>Materialize.toast('{{ error.message }}', 4000)</script> 8 <script>Materialize.toast('{{ error.messageKey|trans(error.messageData, 'security') }}', 4000)</script>
9 {% endif %} 9 {% endif %}
10 10
11 {% for flashMessage in app.session.flashbag.get('notice') %} 11 {% for flashMessage in app.session.flashbag.get('notice') %}