aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php49
-rw-r--r--src/Wallabag/AnnotationBundle/Entity/Annotation.php7
-rw-r--r--src/Wallabag/AnnotationBundle/Form/EditAnnotationType.php18
-rw-r--r--src/Wallabag/AnnotationBundle/Form/NewAnnotationType.php35
-rw-r--r--src/Wallabag/AnnotationBundle/Form/RangeType.php19
-rw-r--r--src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php17
-rw-r--r--src/Wallabag/ApiBundle/Controller/DeveloperController.php2
-rw-r--r--src/Wallabag/ApiBundle/Controller/EntryRestController.php446
-rw-r--r--src/Wallabag/ApiBundle/Controller/TagRestController.php8
-rw-r--r--src/Wallabag/ApiBundle/Controller/UserRestController.php157
-rw-r--r--src/Wallabag/ApiBundle/Entity/Client.php23
-rw-r--r--src/Wallabag/ApiBundle/Resources/config/routing_rest.yml5
-rw-r--r--src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php119
-rw-r--r--src/Wallabag/CoreBundle/Command/ExportCommand.php5
-rw-r--r--src/Wallabag/CoreBundle/Command/InstallCommand.php191
-rw-r--r--src/Wallabag/CoreBundle/Command/ShowUserCommand.php77
-rw-r--r--src/Wallabag/CoreBundle/Command/TagAllCommand.php2
-rw-r--r--src/Wallabag/CoreBundle/Controller/ConfigController.php76
-rw-r--r--src/Wallabag/CoreBundle/Controller/EntryController.php16
-rw-r--r--src/Wallabag/CoreBundle/Controller/ExportController.php14
-rw-r--r--src/Wallabag/CoreBundle/Controller/RssController.php106
-rw-r--r--src/Wallabag/CoreBundle/Controller/SiteCredentialController.php174
-rw-r--r--src/Wallabag/CoreBundle/Controller/TagController.php27
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php164
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSiteCredentialData.php34
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTaggingRuleData.php7
-rw-r--r--src/Wallabag/CoreBundle/DependencyInjection/Configuration.php18
-rw-r--r--src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php4
-rw-r--r--src/Wallabag/CoreBundle/Entity/Entry.php129
-rw-r--r--src/Wallabag/CoreBundle/Entity/SiteCredential.php195
-rw-r--r--src/Wallabag/CoreBundle/Entity/TaggingRule.php4
-rw-r--r--src/Wallabag/CoreBundle/Event/Subscriber/SQLiteCascadeDeleteSubscriber.php5
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/EditEntryType.php6
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/EntryFilterType.php14
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/SiteCredentialType.php44
-rw-r--r--src/Wallabag/CoreBundle/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php97
-rw-r--r--src/Wallabag/CoreBundle/Helper/ContentProxy.php200
-rw-r--r--src/Wallabag/CoreBundle/Helper/CryptoProxy.php86
-rw-r--r--src/Wallabag/CoreBundle/Helper/DownloadImages.php57
-rw-r--r--src/Wallabag/CoreBundle/Helper/HttpClientFactory.php29
-rw-r--r--src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php11
-rw-r--r--src/Wallabag/CoreBundle/Helper/Redirect.php10
-rw-r--r--src/Wallabag/CoreBundle/Helper/RuleBasedTagger.php10
-rw-r--r--src/Wallabag/CoreBundle/Helper/TagsAssigner.php75
-rw-r--r--src/Wallabag/CoreBundle/Operator/Doctrine/NotMatches.php25
-rw-r--r--src/Wallabag/CoreBundle/Operator/PHP/NotMatches.php21
-rw-r--r--src/Wallabag/CoreBundle/Repository/EntryRepository.php51
-rw-r--r--src/Wallabag/CoreBundle/Repository/SiteCredentialRepository.php47
-rw-r--r--src/Wallabag/CoreBundle/Repository/TagRepository.php20
-rw-r--r--src/Wallabag/CoreBundle/Resources/config/services.yml46
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.da.yml49
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.de.yml43
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.en.yml49
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.es.yml41
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml43
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml181
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.it.yml61
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml63
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml47
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml47
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml49
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml27
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.da.yml1
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.de.yml1
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.en.yml1
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.es.yml1
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.fa.yml1
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.fr.yml1
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.it.yml1
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.oc.yml1
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.pl.yml1
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.pt.yml1
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.ro.yml1
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.tr.yml1
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig16
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig32
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entry.html.twig29
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/SiteCredential/edit.html.twig60
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/SiteCredential/index.html.twig42
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/SiteCredential/new.html.twig53
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Tag/tags.html.twig7
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/layout.html.twig9
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/index.html.twig2
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_rss_link.html.twig6
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/entries.xml.twig6
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/share.html.twig38
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig48
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_actions.html.twig2
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_list.html.twig40
-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/edit.html.twig5
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig35
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entry.html.twig103
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/SiteCredential/edit.html.twig60
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/SiteCredential/index.html.twig42
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/SiteCredential/new.html.twig53
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Tag/tags.html.twig3
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig15
-rw-r--r--src/Wallabag/ImportBundle/Command/ImportCommand.php18
-rw-r--r--src/Wallabag/ImportBundle/Import/AbstractImport.php27
-rw-r--r--src/Wallabag/ImportBundle/Import/BrowserImport.php5
-rw-r--r--src/Wallabag/ImportBundle/Import/InstapaperImport.php19
-rw-r--r--src/Wallabag/ImportBundle/Import/PinboardImport.php4
-rw-r--r--src/Wallabag/ImportBundle/Import/PocketImport.php5
-rw-r--r--src/Wallabag/ImportBundle/Import/ReadabilityImport.php2
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagImport.php4
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagV1Import.php18
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagV2Import.php4
-rw-r--r--src/Wallabag/ImportBundle/Resources/config/services.yml10
-rw-r--r--src/Wallabag/UserBundle/Controller/ManageController.php71
-rw-r--r--src/Wallabag/UserBundle/Entity/User.php64
-rw-r--r--src/Wallabag/UserBundle/EventListener/AuthenticationFailureListener.php40
-rw-r--r--src/Wallabag/UserBundle/EventListener/CreateConfigListener.php3
-rw-r--r--src/Wallabag/UserBundle/Form/SearchUserType.php29
-rw-r--r--src/Wallabag/UserBundle/Repository/UserRepository.php13
-rw-r--r--src/Wallabag/UserBundle/Resources/config/services.yml10
-rw-r--r--src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig77
117 files changed, 3810 insertions, 1037 deletions
diff --git a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php
index c13a034f..2b4b0e8d 100644
--- a/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php
+++ b/src/Wallabag/AnnotationBundle/Controller/WallabagAnnotationController.php
@@ -7,6 +7,8 @@ use Symfony\Component\HttpFoundation\JsonResponse;
7use Symfony\Component\HttpFoundation\Request; 7use Symfony\Component\HttpFoundation\Request;
8use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; 8use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
9use Wallabag\AnnotationBundle\Entity\Annotation; 9use Wallabag\AnnotationBundle\Entity\Annotation;
10use Wallabag\AnnotationBundle\Form\EditAnnotationType;
11use Wallabag\AnnotationBundle\Form\NewAnnotationType;
10use Wallabag\CoreBundle\Entity\Entry; 12use Wallabag\CoreBundle\Entity\Entry;
11 13
12class WallabagAnnotationController extends FOSRestController 14class WallabagAnnotationController extends FOSRestController
@@ -49,25 +51,25 @@ class WallabagAnnotationController extends FOSRestController
49 $data = json_decode($request->getContent(), true); 51 $data = json_decode($request->getContent(), true);
50 52
51 $em = $this->getDoctrine()->getManager(); 53 $em = $this->getDoctrine()->getManager();
52
53 $annotation = new Annotation($this->getUser()); 54 $annotation = new Annotation($this->getUser());
55 $annotation->setEntry($entry);
54 56
55 $annotation->setText($data['text']); 57 $form = $this->get('form.factory')->createNamed('', NewAnnotationType::class, $annotation, [
56 if (array_key_exists('quote', $data)) { 58 'csrf_protection' => false,
57 $annotation->setQuote($data['quote']); 59 'allow_extra_fields' => true,
58 } 60 ]);
59 if (array_key_exists('ranges', $data)) { 61 $form->submit($data);
60 $annotation->setRanges($data['ranges']);
61 }
62 62
63 $annotation->setEntry($entry); 63 if ($form->isValid()) {
64 $em->persist($annotation);
65 $em->flush();
64 66
65 $em->persist($annotation); 67 $json = $this->get('serializer')->serialize($annotation, 'json');
66 $em->flush();
67 68
68 $json = $this->get('serializer')->serialize($annotation, 'json'); 69 return JsonResponse::fromJsonString($json);
70 }
69 71
70 return (new JsonResponse())->setJson($json); 72 return $form;
71 } 73 }
72 74
73 /** 75 /**
@@ -86,16 +88,23 @@ class WallabagAnnotationController extends FOSRestController
86 { 88 {
87 $data = json_decode($request->getContent(), true); 89 $data = json_decode($request->getContent(), true);
88 90
89 if (!is_null($data['text'])) { 91 $form = $this->get('form.factory')->createNamed('', EditAnnotationType::class, $annotation, [
90 $annotation->setText($data['text']); 92 'csrf_protection' => false,
91 } 93 'allow_extra_fields' => true,
94 ]);
95 $form->submit($data);
92 96
93 $em = $this->getDoctrine()->getManager(); 97 if ($form->isValid()) {
94 $em->flush(); 98 $em = $this->getDoctrine()->getManager();
99 $em->persist($annotation);
100 $em->flush();
95 101
96 $json = $this->get('serializer')->serialize($annotation, 'json'); 102 $json = $this->get('serializer')->serialize($annotation, 'json');
97 103
98 return (new JsonResponse())->setJson($json); 104 return JsonResponse::fromJsonString($json);
105 }
106
107 return $form;
99 } 108 }
100 109
101 /** 110 /**
diff --git a/src/Wallabag/AnnotationBundle/Entity/Annotation.php b/src/Wallabag/AnnotationBundle/Entity/Annotation.php
index 0838f5aa..c8e41649 100644
--- a/src/Wallabag/AnnotationBundle/Entity/Annotation.php
+++ b/src/Wallabag/AnnotationBundle/Entity/Annotation.php
@@ -8,6 +8,7 @@ use JMS\Serializer\Annotation\Exclude;
8use JMS\Serializer\Annotation\VirtualProperty; 8use JMS\Serializer\Annotation\VirtualProperty;
9use JMS\Serializer\Annotation\SerializedName; 9use JMS\Serializer\Annotation\SerializedName;
10use JMS\Serializer\Annotation\Groups; 10use JMS\Serializer\Annotation\Groups;
11use Symfony\Component\Validator\Constraints as Assert;
11use Wallabag\UserBundle\Entity\User; 12use Wallabag\UserBundle\Entity\User;
12use Wallabag\CoreBundle\Entity\Entry; 13use Wallabag\CoreBundle\Entity\Entry;
13 14
@@ -56,7 +57,11 @@ class Annotation
56 /** 57 /**
57 * @var string 58 * @var string
58 * 59 *
59 * @ORM\Column(name="quote", type="string") 60 * @Assert\Length(
61 * max = 10000,
62 * maxMessage = "validator.quote_length_too_high"
63 * )
64 * @ORM\Column(name="quote", type="text")
60 * 65 *
61 * @Groups({"entries_for_user", "export_all"}) 66 * @Groups({"entries_for_user", "export_all"})
62 */ 67 */
diff --git a/src/Wallabag/AnnotationBundle/Form/EditAnnotationType.php b/src/Wallabag/AnnotationBundle/Form/EditAnnotationType.php
new file mode 100644
index 00000000..3b587478
--- /dev/null
+++ b/src/Wallabag/AnnotationBundle/Form/EditAnnotationType.php
@@ -0,0 +1,18 @@
1<?php
2
3namespace Wallabag\AnnotationBundle\Form;
4
5use Symfony\Component\Form\AbstractType;
6use Symfony\Component\Form\FormBuilderInterface;
7
8class EditAnnotationType extends AbstractType
9{
10 public function buildForm(FormBuilderInterface $builder, array $options)
11 {
12 $builder
13 ->add('text', null, [
14 'empty_data' => '',
15 ])
16 ;
17 }
18}
diff --git a/src/Wallabag/AnnotationBundle/Form/NewAnnotationType.php b/src/Wallabag/AnnotationBundle/Form/NewAnnotationType.php
new file mode 100644
index 00000000..c73c3ded
--- /dev/null
+++ b/src/Wallabag/AnnotationBundle/Form/NewAnnotationType.php
@@ -0,0 +1,35 @@
1<?php
2
3namespace Wallabag\AnnotationBundle\Form;
4
5use Symfony\Component\Form\AbstractType;
6use Symfony\Component\Form\Extension\Core\Type\CollectionType;
7use Symfony\Component\Form\FormBuilderInterface;
8use Symfony\Component\OptionsResolver\OptionsResolver;
9use Wallabag\AnnotationBundle\Entity\Annotation;
10
11class NewAnnotationType extends AbstractType
12{
13 public function buildForm(FormBuilderInterface $builder, array $options)
14 {
15 $builder
16 ->add('text', null, [
17 'empty_data' => '',
18 ])
19 ->add('quote', null, [
20 'empty_data' => null,
21 ])
22 ->add('ranges', CollectionType::class, [
23 'entry_type' => RangeType::class,
24 'allow_add' => true,
25 ])
26 ;
27 }
28
29 public function configureOptions(OptionsResolver $resolver)
30 {
31 $resolver->setDefaults([
32 'data_class' => Annotation::class,
33 ]);
34 }
35}
diff --git a/src/Wallabag/AnnotationBundle/Form/RangeType.php b/src/Wallabag/AnnotationBundle/Form/RangeType.php
new file mode 100644
index 00000000..0647375e
--- /dev/null
+++ b/src/Wallabag/AnnotationBundle/Form/RangeType.php
@@ -0,0 +1,19 @@
1<?php
2
3namespace Wallabag\AnnotationBundle\Form;
4
5use Symfony\Component\Form\AbstractType;
6use Symfony\Component\Form\FormBuilderInterface;
7
8class RangeType extends AbstractType
9{
10 public function buildForm(FormBuilderInterface $builder, array $options)
11 {
12 $builder
13 ->add('start')
14 ->add('startOffset')
15 ->add('end')
16 ->add('endOffset')
17 ;
18 }
19}
diff --git a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php
index 8d3f07ee..da361308 100644
--- a/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php
+++ b/src/Wallabag/AnnotationBundle/Repository/AnnotationRepository.php
@@ -122,4 +122,21 @@ class AnnotationRepository extends EntityRepository
122 ->setParameter('userId', $userId) 122 ->setParameter('userId', $userId)
123 ->execute(); 123 ->execute();
124 } 124 }
125
126 /**
127 * Find all annotations related to archived entries.
128 *
129 * @param $userId
130 *
131 * @return mixed
132 */
133 public function findAllArchivedEntriesByUser($userId)
134 {
135 return $this->createQueryBuilder('a')
136 ->leftJoin('a.entry', 'e')
137 ->where('a.user = :userid')->setParameter(':userid', $userId)
138 ->andWhere('e.isArchived = true')
139 ->getQuery()
140 ->getResult();
141 }
125} 142}
diff --git a/src/Wallabag/ApiBundle/Controller/DeveloperController.php b/src/Wallabag/ApiBundle/Controller/DeveloperController.php
index 9cb1b626..9cb73f4c 100644
--- a/src/Wallabag/ApiBundle/Controller/DeveloperController.php
+++ b/src/Wallabag/ApiBundle/Controller/DeveloperController.php
@@ -43,7 +43,7 @@ class DeveloperController extends Controller
43 $clientForm->handleRequest($request); 43 $clientForm->handleRequest($request);
44 44
45 if ($clientForm->isSubmitted() && $clientForm->isValid()) { 45 if ($clientForm->isSubmitted() && $clientForm->isValid()) {
46 $client->setAllowedGrantTypes(['token', 'authorization_code', 'password', 'refresh_token']); 46 $client->setAllowedGrantTypes(['client_credentials', 'token', 'authorization_code', 'password', 'refresh_token']);
47 $em->persist($client); 47 $em->persist($client);
48 $em->flush(); 48 $em->flush();
49 49
diff --git a/src/Wallabag/ApiBundle/Controller/EntryRestController.php b/src/Wallabag/ApiBundle/Controller/EntryRestController.php
index 54c1747c..768c4fdc 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;
@@ -41,12 +42,10 @@ class EntryRestController extends WallabagRestController
41 ->getRepository('WallabagCoreBundle:Entry') 42 ->getRepository('WallabagCoreBundle:Entry')
42 ->findByUrlAndUserId($url, $this->getUser()->getId()); 43 ->findByUrlAndUserId($url, $this->getUser()->getId());
43 44
44 $results[$url] = false === $res ? false : true; 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?
@@ -60,11 +59,9 @@ class EntryRestController extends WallabagRestController
60 ->getRepository('WallabagCoreBundle:Entry') 59 ->getRepository('WallabagCoreBundle:Entry')
61 ->findByUrlAndUserId($url, $this->getUser()->getId()); 60 ->findByUrlAndUserId($url, $this->getUser()->getId());
62 61
63 $exists = false === $res ? false : true; 62 $exists = $res instanceof Entry ? $res->getId() : false;
64
65 $json = $this->get('serializer')->serialize(['exists' => $exists], 'json');
66 63
67 return (new JsonResponse())->setJson($json); 64 return $this->sendResponse(['exists' => $exists]);
68 } 65 }
69 66
70 /** 67 /**
@@ -80,6 +77,7 @@ class EntryRestController extends WallabagRestController
80 * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."}, 77 * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."},
81 * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."}, 78 * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."},
82 * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."}, 79 * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."},
80 * {"name"="public", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by entries with a public link"},
83 * } 81 * }
84 * ) 82 * )
85 * 83 *
@@ -91,6 +89,7 @@ class EntryRestController extends WallabagRestController
91 89
92 $isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive'); 90 $isArchived = (null === $request->query->get('archive')) ? null : (bool) $request->query->get('archive');
93 $isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred'); 91 $isStarred = (null === $request->query->get('starred')) ? null : (bool) $request->query->get('starred');
92 $isPublic = (null === $request->query->get('public')) ? null : (bool) $request->query->get('public');
94 $sort = $request->query->get('sort', 'created'); 93 $sort = $request->query->get('sort', 'created');
95 $order = $request->query->get('order', 'desc'); 94 $order = $request->query->get('order', 'desc');
96 $page = (int) $request->query->get('page', 1); 95 $page = (int) $request->query->get('page', 1);
@@ -99,9 +98,16 @@ class EntryRestController extends WallabagRestController
99 $since = $request->query->get('since', 0); 98 $since = $request->query->get('since', 0);
100 99
101 /** @var \Pagerfanta\Pagerfanta $pager */ 100 /** @var \Pagerfanta\Pagerfanta $pager */
102 $pager = $this->getDoctrine() 101 $pager = $this->get('wallabag_core.entry_repository')->findEntries(
103 ->getRepository('WallabagCoreBundle:Entry') 102 $this->getUser()->getId(),
104 ->findEntries($this->getUser()->getId(), $isArchived, $isStarred, $sort, $order, $since, $tags); 103 $isArchived,
104 $isStarred,
105 $isPublic,
106 $sort,
107 $order,
108 $since,
109 $tags
110 );
105 111
106 $pager->setMaxPerPage($perPage); 112 $pager->setMaxPerPage($perPage);
107 $pager->setCurrentPage($page); 113 $pager->setCurrentPage($page);
@@ -114,6 +120,7 @@ class EntryRestController extends WallabagRestController
114 [ 120 [
115 'archive' => $isArchived, 121 'archive' => $isArchived,
116 'starred' => $isStarred, 122 'starred' => $isStarred,
123 'public' => $isPublic,
117 'sort' => $sort, 124 'sort' => $sort,
118 'order' => $order, 125 'order' => $order,
119 'page' => $page, 126 'page' => $page,
@@ -125,9 +132,7 @@ class EntryRestController extends WallabagRestController
125 ) 132 )
126 ); 133 );
127 134
128 $json = $this->get('serializer')->serialize($paginatedCollection, 'json'); 135 return $this->sendResponse($paginatedCollection);
129
130 return (new JsonResponse())->setJson($json);
131 } 136 }
132 137
133 /** 138 /**
@@ -146,9 +151,7 @@ class EntryRestController extends WallabagRestController
146 $this->validateAuthentication(); 151 $this->validateAuthentication();
147 $this->validateUserAccess($entry->getUser()->getId()); 152 $this->validateUserAccess($entry->getUser()->getId());
148 153
149 $json = $this->get('serializer')->serialize($entry, 'json'); 154 return $this->sendResponse($entry);
150
151 return (new JsonResponse())->setJson($json);
152 } 155 }
153 156
154 /** 157 /**
@@ -174,74 +177,153 @@ class EntryRestController extends WallabagRestController
174 } 177 }
175 178
176 /** 179 /**
177 * Create an entry. 180 * Handles an entries list and delete URL.
178 * 181 *
179 * @ApiDoc( 182 * @ApiDoc(
180 * parameters={ 183 * parameters={
181 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."}, 184 * {"name"="urls", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...'}, {'url': 'http://...'}]", "description"="Urls (as an array) to delete."}
182 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
183 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
184 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"},
185 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"},
186 * } 185 * }
187 * ) 186 * )
188 * 187 *
189 * @return JsonResponse 188 * @return JsonResponse
190 */ 189 */
191 public function postEntriesAction(Request $request) 190 public function deleteEntriesListAction(Request $request)
192 { 191 {
193 $this->validateAuthentication(); 192 $this->validateAuthentication();
194 193
195 $url = $request->request->get('url'); 194 $urls = json_decode($request->query->get('urls', []));
196 $title = $request->request->get('title'); 195
197 $isArchived = $request->request->get('archive'); 196 if (empty($urls)) {
198 $isStarred = $request->request->get('starred'); 197 return $this->sendResponse([]);
198 }
199 199
200 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId($url, $this->getUser()->getId()); 200 $results = [];
201 201
202 if (false === $entry) { 202 // handle multiple urls
203 $entry = new Entry($this->getUser()); 203 foreach ($urls as $key => $url) {
204 try { 204 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
205 $entry = $this->get('wallabag_core.content_proxy')->updateEntry( 205 $url,
206 $entry, 206 $this->getUser()->getId()
207 $url 207 );
208 ); 208
209 } catch (\Exception $e) { 209 $results[$key]['url'] = $url;
210 $this->get('logger')->error('Error while saving an entry', [ 210
211 'exception' => $e, 211 if (false !== $entry) {
212 'entry' => $entry, 212 $em = $this->getDoctrine()->getManager();
213 ]); 213 $em->remove($entry);
214 $entry->setUrl($url); 214 $em->flush();
215
216 // entry deleted, dispatch event about it!
217 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
215 } 218 }
216 }
217 219
218 if (!is_null($title)) { 220 $results[$key]['entry'] = $entry instanceof Entry ? true : false;
219 $entry->setTitle($title);
220 } 221 }
221 222
222 $tags = $request->request->get('tags', ''); 223 return $this->sendResponse($results);
223 if (!empty($tags)) { 224 }
224 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags); 225
226 /**
227 * Handles an entries list and create URL.
228 *
229 * @ApiDoc(
230 * parameters={
231 * {"name"="urls", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...'}, {'url': 'http://...'}]", "description"="Urls (as an array) to create."}
232 * }
233 * )
234 *
235 * @return JsonResponse
236 *
237 * @throws HttpException When limit is reached
238 */
239 public function postEntriesListAction(Request $request)
240 {
241 $this->validateAuthentication();
242
243 $urls = json_decode($request->query->get('urls', []));
244
245 $limit = $this->container->getParameter('wallabag_core.api_limit_mass_actions');
246
247 if (count($urls) > $limit) {
248 throw new HttpException(400, 'API limit reached');
225 } 249 }
226 250
227 if (!is_null($isStarred)) { 251 $results = [];
228 $entry->setStarred((bool) $isStarred); 252 if (empty($urls)) {
253 return $this->sendResponse($results);
229 } 254 }
230 255
231 if (!is_null($isArchived)) { 256 // handle multiple urls
232 $entry->setArchived((bool) $isArchived); 257 foreach ($urls as $key => $url) {
258 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
259 $url,
260 $this->getUser()->getId()
261 );
262
263 $results[$key]['url'] = $url;
264
265 if (false === $entry) {
266 $entry = new Entry($this->getUser());
267
268 $this->get('wallabag_core.content_proxy')->updateEntry($entry, $url);
269 }
270
271 $em = $this->getDoctrine()->getManager();
272 $em->persist($entry);
273 $em->flush();
274
275 $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
276
277 // entry saved, dispatch event about it!
278 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
233 } 279 }
234 280
235 $em = $this->getDoctrine()->getManager(); 281 return $this->sendResponse($results);
236 $em->persist($entry); 282 }
237 $em->flush();
238 283
239 // entry saved, dispatch event about it! 284 /**
240 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); 285 * Create an entry.
286 *
287 * If you want to provide the HTML content (which means wallabag won't fetch it from the url), you must provide `content`, `title` & `url` fields **non-empty**.
288 * Otherwise, content will be fetched as normal from the url and values will be overwritten.
289 *
290 * @ApiDoc(
291 * parameters={
292 * {"name"="url", "dataType"="string", "required"=true, "format"="http://www.test.com/article.html", "description"="Url for the entry."},
293 * {"name"="title", "dataType"="string", "required"=false, "description"="Optional, we'll get the title from the page."},
294 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
295 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already starred"},
296 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="entry already archived"},
297 * {"name"="content", "dataType"="string", "required"=false, "description"="Content of the entry"},
298 * {"name"="language", "dataType"="string", "required"=false, "description"="Language of the entry"},
299 * {"name"="preview_picture", "dataType"="string", "required"=false, "description"="Preview picture of the entry"},
300 * {"name"="published_at", "dataType"="datetime|integer", "format"="YYYY-MM-DDTHH:II:SS+TZ or a timestamp", "required"=false, "description"="Published date of the entry"},
301 * {"name"="authors", "dataType"="string", "format"="Name Firstname,author2,author3", "required"=false, "description"="Authors of the entry"},
302 * {"name"="public", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="will generate a public link for the entry"},
303 * }
304 * )
305 *
306 * @return JsonResponse
307 */
308 public function postEntriesAction(Request $request)
309 {
310 $this->validateAuthentication();
241 311
242 $json = $this->get('serializer')->serialize($entry, 'json'); 312 $url = $request->request->get('url');
243 313
244 return (new JsonResponse())->setJson($json); 314 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
315 $url,
316 $this->getUser()->getId()
317 );
318
319 if (false === $entry) {
320 $entry = new Entry($this->getUser());
321 $entry->setUrl($url);
322 }
323
324 $this->upsertEntry($entry, $request);
325
326 return $this->sendResponse($entry);
245 } 327 }
246 328
247 /** 329 /**
@@ -256,6 +338,12 @@ class EntryRestController extends WallabagRestController
256 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."}, 338 * {"name"="tags", "dataType"="string", "required"=false, "format"="tag1,tag2,tag3", "description"="a comma-separated list of tags."},
257 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."}, 339 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="archived the entry."},
258 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."}, 340 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="starred the entry."},
341 * {"name"="content", "dataType"="string", "required"=false, "description"="Content of the entry"},
342 * {"name"="language", "dataType"="string", "required"=false, "description"="Language of the entry"},
343 * {"name"="preview_picture", "dataType"="string", "required"=false, "description"="Preview picture of the entry"},
344 * {"name"="published_at", "dataType"="datetime|integer", "format"="YYYY-MM-DDTHH:II:SS+TZ or a timestamp", "required"=false, "description"="Published date of the entry"},
345 * {"name"="authors", "dataType"="string", "format"="Name Firstname,author2,author3", "required"=false, "description"="Authors of the entry"},
346 * {"name"="public", "dataType"="integer", "required"=false, "format"="1 or 0", "description"="will generate a public link for the entry"},
259 * } 347 * }
260 * ) 348 * )
261 * 349 *
@@ -266,33 +354,9 @@ class EntryRestController extends WallabagRestController
266 $this->validateAuthentication(); 354 $this->validateAuthentication();
267 $this->validateUserAccess($entry->getUser()->getId()); 355 $this->validateUserAccess($entry->getUser()->getId());
268 356
269 $title = $request->request->get('title'); 357 $this->upsertEntry($entry, $request, true);
270 $isArchived = $request->request->get('archive');
271 $isStarred = $request->request->get('starred');
272
273 if (!is_null($title)) {
274 $entry->setTitle($title);
275 }
276
277 if (!is_null($isArchived)) {
278 $entry->setArchived((bool) $isArchived);
279 }
280
281 if (!is_null($isStarred)) {
282 $entry->setStarred((bool) $isStarred);
283 }
284
285 $tags = $request->request->get('tags', '');
286 if (!empty($tags)) {
287 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags);
288 }
289
290 $em = $this->getDoctrine()->getManager();
291 $em->flush();
292
293 $json = $this->get('serializer')->serialize($entry, 'json');
294 358
295 return (new JsonResponse())->setJson($json); 359 return $this->sendResponse($entry);
296 } 360 }
297 361
298 /** 362 /**
@@ -313,7 +377,7 @@ class EntryRestController extends WallabagRestController
313 $this->validateUserAccess($entry->getUser()->getId()); 377 $this->validateUserAccess($entry->getUser()->getId());
314 378
315 try { 379 try {
316 $entry = $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl()); 380 $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl());
317 } catch (\Exception $e) { 381 } catch (\Exception $e) {
318 $this->get('logger')->error('Error while saving an entry', [ 382 $this->get('logger')->error('Error while saving an entry', [
319 'exception' => $e, 383 'exception' => $e,
@@ -335,9 +399,7 @@ class EntryRestController extends WallabagRestController
335 // entry saved, dispatch event about it! 399 // entry saved, dispatch event about it!
336 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry)); 400 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
337 401
338 $json = $this->get('serializer')->serialize($entry, 'json'); 402 return $this->sendResponse($entry);
339
340 return (new JsonResponse())->setJson($json);
341 } 403 }
342 404
343 /** 405 /**
@@ -363,9 +425,7 @@ class EntryRestController extends WallabagRestController
363 // entry deleted, dispatch event about it! 425 // entry deleted, dispatch event about it!
364 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry)); 426 $this->get('event_dispatcher')->dispatch(EntryDeletedEvent::NAME, new EntryDeletedEvent($entry));
365 427
366 $json = $this->get('serializer')->serialize($entry, 'json'); 428 return $this->sendResponse($entry);
367
368 return (new JsonResponse())->setJson($json);
369 } 429 }
370 430
371 /** 431 /**
@@ -384,9 +444,7 @@ class EntryRestController extends WallabagRestController
384 $this->validateAuthentication(); 444 $this->validateAuthentication();
385 $this->validateUserAccess($entry->getUser()->getId()); 445 $this->validateUserAccess($entry->getUser()->getId());
386 446
387 $json = $this->get('serializer')->serialize($entry->getTags(), 'json'); 447 return $this->sendResponse($entry->getTags());
388
389 return (new JsonResponse())->setJson($json);
390 } 448 }
391 449
392 /** 450 /**
@@ -410,16 +468,14 @@ class EntryRestController extends WallabagRestController
410 468
411 $tags = $request->request->get('tags', ''); 469 $tags = $request->request->get('tags', '');
412 if (!empty($tags)) { 470 if (!empty($tags)) {
413 $this->get('wallabag_core.content_proxy')->assignTagsToEntry($entry, $tags); 471 $this->get('wallabag_core.tags_assigner')->assignTagsToEntry($entry, $tags);
414 } 472 }
415 473
416 $em = $this->getDoctrine()->getManager(); 474 $em = $this->getDoctrine()->getManager();
417 $em->persist($entry); 475 $em->persist($entry);
418 $em->flush(); 476 $em->flush();
419 477
420 $json = $this->get('serializer')->serialize($entry, 'json'); 478 return $this->sendResponse($entry);
421
422 return (new JsonResponse())->setJson($json);
423 } 479 }
424 480
425 /** 481 /**
@@ -444,8 +500,198 @@ class EntryRestController extends WallabagRestController
444 $em->persist($entry); 500 $em->persist($entry);
445 $em->flush(); 501 $em->flush();
446 502
447 $json = $this->get('serializer')->serialize($entry, 'json'); 503 return $this->sendResponse($entry);
504 }
505
506 /**
507 * Handles an entries list delete tags from them.
508 *
509 * @ApiDoc(
510 * parameters={
511 * {"name"="list", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...','tags': 'tag1, tag2'}, {'url': 'http://...','tags': 'tag1, tag2'}]", "description"="Urls (as an array) to handle."}
512 * }
513 * )
514 *
515 * @return JsonResponse
516 */
517 public function deleteEntriesTagsListAction(Request $request)
518 {
519 $this->validateAuthentication();
520
521 $list = json_decode($request->query->get('list', []));
522
523 if (empty($list)) {
524 return $this->sendResponse([]);
525 }
526
527 // handle multiple urls
528 $results = [];
529
530 foreach ($list as $key => $element) {
531 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
532 $element->url,
533 $this->getUser()->getId()
534 );
535
536 $results[$key]['url'] = $element->url;
537 $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
538
539 $tags = $element->tags;
540
541 if (false !== $entry && !(empty($tags))) {
542 $tags = explode(',', $tags);
543 foreach ($tags as $label) {
544 $label = trim($label);
545
546 $tag = $this->getDoctrine()
547 ->getRepository('WallabagCoreBundle:Tag')
548 ->findOneByLabel($label);
549
550 if (false !== $tag) {
551 $entry->removeTag($tag);
552 }
553 }
554
555 $em = $this->getDoctrine()->getManager();
556 $em->persist($entry);
557 $em->flush();
558 }
559 }
560
561 return $this->sendResponse($results);
562 }
563
564 /**
565 * Handles an entries list and add tags to them.
566 *
567 * @ApiDoc(
568 * parameters={
569 * {"name"="list", "dataType"="string", "required"=true, "format"="A JSON array of urls [{'url': 'http://...','tags': 'tag1, tag2'}, {'url': 'http://...','tags': 'tag1, tag2'}]", "description"="Urls (as an array) to handle."}
570 * }
571 * )
572 *
573 * @return JsonResponse
574 */
575 public function postEntriesTagsListAction(Request $request)
576 {
577 $this->validateAuthentication();
578
579 $list = json_decode($request->query->get('list', []));
580
581 if (empty($list)) {
582 return $this->sendResponse([]);
583 }
584
585 $results = [];
586
587 // handle multiple urls
588 foreach ($list as $key => $element) {
589 $entry = $this->get('wallabag_core.entry_repository')->findByUrlAndUserId(
590 $element->url,
591 $this->getUser()->getId()
592 );
593
594 $results[$key]['url'] = $element->url;
595 $results[$key]['entry'] = $entry instanceof Entry ? $entry->getId() : false;
596
597 $tags = $element->tags;
598
599 if (false !== $entry && !(empty($tags))) {
600 $this->get('wallabag_core.tags_assigner')->assignTagsToEntry($entry, $tags);
601
602 $em = $this->getDoctrine()->getManager();
603 $em->persist($entry);
604 $em->flush();
605 }
606 }
607
608 return $this->sendResponse($results);
609 }
610
611 /**
612 * Shortcut to send data serialized in json.
613 *
614 * @param mixed $data
615 *
616 * @return JsonResponse
617 */
618 private function sendResponse($data)
619 {
620 $json = $this->get('serializer')->serialize($data, 'json');
448 621
449 return (new JsonResponse())->setJson($json); 622 return (new JsonResponse())->setJson($json);
450 } 623 }
624
625 /**
626 * Update or Insert a new entry.
627 *
628 * @param Entry $entry
629 * @param Request $request
630 * @param bool $disableContentUpdate If we don't want the content to be update by fetching the url (used when patching instead of posting)
631 */
632 private function upsertEntry(Entry $entry, Request $request, $disableContentUpdate = false)
633 {
634 $title = $request->request->get('title');
635 $tags = $request->request->get('tags', []);
636 $isArchived = $request->request->get('archive');
637 $isStarred = $request->request->get('starred');
638 $isPublic = $request->request->get('public');
639 $content = $request->request->get('content');
640 $language = $request->request->get('language');
641 $picture = $request->request->get('preview_picture');
642 $publishedAt = $request->request->get('published_at');
643 $authors = $request->request->get('authors', '');
644
645 try {
646 $this->get('wallabag_core.content_proxy')->updateEntry(
647 $entry,
648 $entry->getUrl(),
649 [
650 'title' => !empty($title) ? $title : $entry->getTitle(),
651 'html' => !empty($content) ? $content : $entry->getContent(),
652 'url' => $entry->getUrl(),
653 'language' => !empty($language) ? $language : $entry->getLanguage(),
654 'date' => !empty($publishedAt) ? $publishedAt : $entry->getPublishedAt(),
655 // faking the open graph preview picture
656 'open_graph' => [
657 'og_image' => !empty($picture) ? $picture : $entry->getPreviewPicture(),
658 ],
659 'authors' => is_string($authors) ? explode(',', $authors) : $entry->getPublishedBy(),
660 ],
661 $disableContentUpdate
662 );
663 } catch (\Exception $e) {
664 $this->get('logger')->error('Error while saving an entry', [
665 'exception' => $e,
666 'entry' => $entry,
667 ]);
668 }
669
670 if (!is_null($isArchived)) {
671 $entry->setArchived((bool) $isArchived);
672 }
673
674 if (!is_null($isStarred)) {
675 $entry->setStarred((bool) $isStarred);
676 }
677
678 if (!empty($tags)) {
679 $this->get('wallabag_core.tags_assigner')->assignTagsToEntry($entry, $tags);
680 }
681
682 if (!is_null($isPublic)) {
683 if (true === (bool) $isPublic && null === $entry->getUid()) {
684 $entry->generateUid();
685 } elseif (false === (bool) $isPublic) {
686 $entry->cleanUid();
687 }
688 }
689
690 $em = $this->getDoctrine()->getManager();
691 $em->persist($entry);
692 $em->flush();
693
694 // entry saved, dispatch event about it!
695 $this->get('event_dispatcher')->dispatch(EntrySavedEvent::NAME, new EntrySavedEvent($entry));
696 }
451} 697}
diff --git a/src/Wallabag/ApiBundle/Controller/TagRestController.php b/src/Wallabag/ApiBundle/Controller/TagRestController.php
index bc6d4e64..354187a0 100644
--- a/src/Wallabag/ApiBundle/Controller/TagRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/TagRestController.php
@@ -31,7 +31,7 @@ class TagRestController extends WallabagRestController
31 } 31 }
32 32
33 /** 33 /**
34 * Permanently remove one tag from **every** entry. 34 * Permanently remove one tag from **every** entry by passing the Tag label.
35 * 35 *
36 * @ApiDoc( 36 * @ApiDoc(
37 * requirements={ 37 * requirements={
@@ -44,7 +44,7 @@ class TagRestController extends WallabagRestController
44 public function deleteTagLabelAction(Request $request) 44 public function deleteTagLabelAction(Request $request)
45 { 45 {
46 $this->validateAuthentication(); 46 $this->validateAuthentication();
47 $label = $request->request->get('tag', ''); 47 $label = $request->get('tag', '');
48 48
49 $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label); 49 $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneByLabel($label);
50 50
@@ -78,7 +78,7 @@ class TagRestController extends WallabagRestController
78 { 78 {
79 $this->validateAuthentication(); 79 $this->validateAuthentication();
80 80
81 $tagsLabels = $request->request->get('tags', ''); 81 $tagsLabels = $request->get('tags', '');
82 82
83 $tags = []; 83 $tags = [];
84 84
@@ -106,7 +106,7 @@ class TagRestController extends WallabagRestController
106 } 106 }
107 107
108 /** 108 /**
109 * Permanently remove one tag from **every** entry. 109 * Permanently remove one tag from **every** entry by passing the Tag ID.
110 * 110 *
111 * @ApiDoc( 111 * @ApiDoc(
112 * requirements={ 112 * requirements={
diff --git a/src/Wallabag/ApiBundle/Controller/UserRestController.php b/src/Wallabag/ApiBundle/Controller/UserRestController.php
new file mode 100644
index 00000000..7471f5f6
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Controller/UserRestController.php
@@ -0,0 +1,157 @@
1<?php
2
3namespace Wallabag\ApiBundle\Controller;
4
5use FOS\UserBundle\Event\UserEvent;
6use FOS\UserBundle\FOSUserEvents;
7use JMS\Serializer\SerializationContext;
8use Nelmio\ApiDocBundle\Annotation\ApiDoc;
9use Symfony\Component\HttpFoundation\Request;
10use Symfony\Component\HttpFoundation\JsonResponse;
11use Wallabag\UserBundle\Entity\User;
12use Wallabag\ApiBundle\Entity\Client;
13
14class UserRestController extends WallabagRestController
15{
16 /**
17 * Retrieve current logged in user informations.
18 *
19 * @ApiDoc()
20 *
21 * @return JsonResponse
22 */
23 public function getUserAction()
24 {
25 $this->validateAuthentication();
26
27 return $this->sendUser($this->getUser());
28 }
29
30 /**
31 * Register an user and create a client.
32 *
33 * @ApiDoc(
34 * requirements={
35 * {"name"="username", "dataType"="string", "required"=true, "description"="The user's username"},
36 * {"name"="password", "dataType"="string", "required"=true, "description"="The user's password"},
37 * {"name"="email", "dataType"="string", "required"=true, "description"="The user's email"},
38 * {"name"="client_name", "dataType"="string", "required"=true, "description"="The client name (to be used by your app)"}
39 * }
40 * )
41 *
42 * @todo Make this method (or the whole API) accessible only through https
43 *
44 * @return JsonResponse
45 */
46 public function putUserAction(Request $request)
47 {
48 if (!$this->getParameter('fosuser_registration') || !$this->get('craue_config')->get('api_user_registration')) {
49 $json = $this->get('serializer')->serialize(['error' => "Server doesn't allow registrations"], 'json');
50
51 return (new JsonResponse())
52 ->setJson($json)
53 ->setStatusCode(JsonResponse::HTTP_FORBIDDEN);
54 }
55
56 $userManager = $this->get('fos_user.user_manager');
57 $user = $userManager->createUser();
58 // user will be disabled BY DEFAULT to avoid spamming account to be enabled
59 $user->setEnabled(false);
60
61 $form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user, [
62 'csrf_protection' => false,
63 ]);
64
65 // simulate form submission
66 $form->submit([
67 'username' => $request->request->get('username'),
68 'plainPassword' => [
69 'first' => $request->request->get('password'),
70 'second' => $request->request->get('password'),
71 ],
72 'email' => $request->request->get('email'),
73 ]);
74
75 if ($form->isSubmitted() && false === $form->isValid()) {
76 $view = $this->view($form, 400);
77 $view->setFormat('json');
78
79 // handle errors in a more beautiful way than the default view
80 $data = json_decode($this->handleView($view)->getContent(), true)['children'];
81 $errors = [];
82
83 if (isset($data['username']['errors'])) {
84 $errors['username'] = $this->translateErrors($data['username']['errors']);
85 }
86
87 if (isset($data['email']['errors'])) {
88 $errors['email'] = $this->translateErrors($data['email']['errors']);
89 }
90
91 if (isset($data['plainPassword']['children']['first']['errors'])) {
92 $errors['password'] = $this->translateErrors($data['plainPassword']['children']['first']['errors']);
93 }
94
95 $json = $this->get('serializer')->serialize(['error' => $errors], 'json');
96
97 return (new JsonResponse())
98 ->setJson($json)
99 ->setStatusCode(JsonResponse::HTTP_BAD_REQUEST);
100 }
101
102 // create a default client
103 $client = new Client($user);
104 $client->setName($request->request->get('client_name', 'Default client'));
105
106 $this->getDoctrine()->getManager()->persist($client);
107
108 $user->addClient($client);
109
110 $userManager->updateUser($user);
111
112 // dispatch a created event so the associated config will be created
113 $event = new UserEvent($user, $request);
114 $this->get('event_dispatcher')->dispatch(FOSUserEvents::USER_CREATED, $event);
115
116 return $this->sendUser($user, 'user_api_with_client', JsonResponse::HTTP_CREATED);
117 }
118
119 /**
120 * Send user response.
121 *
122 * @param User $user
123 * @param string $group Used to define with serialized group might be used
124 * @param int $status HTTP Status code to send
125 *
126 * @return JsonResponse
127 */
128 private function sendUser(User $user, $group = 'user_api', $status = JsonResponse::HTTP_OK)
129 {
130 $json = $this->get('serializer')->serialize(
131 $user,
132 'json',
133 SerializationContext::create()->setGroups([$group])
134 );
135
136 return (new JsonResponse())
137 ->setJson($json)
138 ->setStatusCode($status);
139 }
140
141 /**
142 * Translate errors message.
143 *
144 * @param array $errors
145 *
146 * @return array
147 */
148 private function translateErrors($errors)
149 {
150 $translatedErrors = [];
151 foreach ($errors as $error) {
152 $translatedErrors[] = $this->get('translator')->trans($error);
153 }
154
155 return $translatedErrors;
156 }
157}
diff --git a/src/Wallabag/ApiBundle/Entity/Client.php b/src/Wallabag/ApiBundle/Entity/Client.php
index 9ed9f980..c15fd3fa 100644
--- a/src/Wallabag/ApiBundle/Entity/Client.php
+++ b/src/Wallabag/ApiBundle/Entity/Client.php
@@ -5,6 +5,9 @@ namespace Wallabag\ApiBundle\Entity;
5use Doctrine\ORM\Mapping as ORM; 5use Doctrine\ORM\Mapping as ORM;
6use FOS\OAuthServerBundle\Entity\Client as BaseClient; 6use FOS\OAuthServerBundle\Entity\Client as BaseClient;
7use Wallabag\UserBundle\Entity\User; 7use Wallabag\UserBundle\Entity\User;
8use JMS\Serializer\Annotation\Groups;
9use JMS\Serializer\Annotation\SerializedName;
10use JMS\Serializer\Annotation\VirtualProperty;
8 11
9/** 12/**
10 * @ORM\Table("oauth2_clients") 13 * @ORM\Table("oauth2_clients")
@@ -23,6 +26,8 @@ class Client extends BaseClient
23 * @var string 26 * @var string
24 * 27 *
25 * @ORM\Column(name="name", type="text", nullable=false) 28 * @ORM\Column(name="name", type="text", nullable=false)
29 *
30 * @Groups({"user_api_with_client"})
26 */ 31 */
27 protected $name; 32 protected $name;
28 33
@@ -37,6 +42,14 @@ class Client extends BaseClient
37 protected $accessTokens; 42 protected $accessTokens;
38 43
39 /** 44 /**
45 * @var string
46 *
47 * @SerializedName("client_secret")
48 * @Groups({"user_api_with_client"})
49 */
50 protected $secret;
51
52 /**
40 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="clients") 53 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="clients")
41 */ 54 */
42 private $user; 55 private $user;
@@ -78,4 +91,14 @@ class Client extends BaseClient
78 { 91 {
79 return $this->user; 92 return $this->user;
80 } 93 }
94
95 /**
96 * @VirtualProperty
97 * @SerializedName("client_id")
98 * @Groups({"user_api_with_client"})
99 */
100 public function getClientId()
101 {
102 return $this->getId().'_'.$this->getRandomId();
103 }
81} 104}
diff --git a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
index 57d37f4b..c0283e71 100644
--- a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
+++ b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
@@ -17,3 +17,8 @@ misc:
17 type: rest 17 type: rest
18 resource: "WallabagApiBundle:WallabagRest" 18 resource: "WallabagApiBundle:WallabagRest"
19 name_prefix: api_ 19 name_prefix: api_
20
21user:
22 type: rest
23 resource: "WallabagApiBundle:UserRest"
24 name_prefix: api_
diff --git a/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php b/src/Wallabag/CoreBundle/Command/CleanDuplicatesCommand.php
new file mode 100644
index 00000000..74da1e5f
--- /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->getContainer()->get('wallabag_user.user_repository')->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->getContainer()->get('wallabag_core.entry_repository');
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->getContainer()->get('wallabag_user.user_repository')->findOneByUserName($username);
113 }
114
115 private function getDoctrine()
116 {
117 return $this->getContainer()->get('doctrine');
118 }
119}
diff --git a/src/Wallabag/CoreBundle/Command/ExportCommand.php b/src/Wallabag/CoreBundle/Command/ExportCommand.php
index e3d3b399..ebb2b4cf 100644
--- a/src/Wallabag/CoreBundle/Command/ExportCommand.php
+++ b/src/Wallabag/CoreBundle/Command/ExportCommand.php
@@ -32,15 +32,14 @@ class ExportCommand extends ContainerAwareCommand
32 protected function execute(InputInterface $input, OutputInterface $output) 32 protected function execute(InputInterface $input, OutputInterface $output)
33 { 33 {
34 try { 34 try {
35 $user = $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findOneByUserName($input->getArgument('username')); 35 $user = $this->getContainer()->get('wallabag_user.user_repository')->findOneByUserName($input->getArgument('username'));
36 } catch (NoResultException $e) { 36 } catch (NoResultException $e) {
37 $output->writeln(sprintf('<error>User "%s" not found.</error>', $input->getArgument('username'))); 37 $output->writeln(sprintf('<error>User "%s" not found.</error>', $input->getArgument('username')));
38 38
39 return 1; 39 return 1;
40 } 40 }
41 41
42 $entries = $this->getDoctrine() 42 $entries = $this->getContainer()->get('wallabag_core.entry_repository')
43 ->getRepository('WallabagCoreBundle:Entry')
44 ->getBuilderForAllByUser($user->getId()) 43 ->getBuilderForAllByUser($user->getId())
45 ->getQuery() 44 ->getQuery()
46 ->getResult(); 45 ->getResult();
diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php
index f0738b91..eb725a59 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,161 +286,13 @@ 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
292 $em->createQuery('DELETE FROM CraueConfigBundle:Setting')->execute(); 293 $em->createQuery('DELETE FROM CraueConfigBundle:Setting')->execute();
293 294
294 $settings = [ 295 foreach ($this->getContainer()->getParameter('wallabag_core.default_internal_settings') as $setting) {
295 [
296 'name' => 'share_public',
297 'value' => '1',
298 'section' => 'entry',
299 ],
300 [
301 'name' => 'carrot',
302 'value' => '1',
303 'section' => 'entry',
304 ],
305 [
306 'name' => 'share_diaspora',
307 'value' => '1',
308 'section' => 'entry',
309 ],
310 [
311 'name' => 'diaspora_url',
312 'value' => 'http://diasporapod.com',
313 'section' => 'entry',
314 ],
315 [
316 'name' => 'share_unmark',
317 'value' => '1',
318 'section' => 'entry',
319 ],
320 [
321 'name' => 'unmark_url',
322 'value' => 'https://unmark.it',
323 'section' => 'entry',
324 ],
325 [
326 'name' => 'share_shaarli',
327 'value' => '1',
328 'section' => 'entry',
329 ],
330 [
331 'name' => 'shaarli_url',
332 'value' => 'http://myshaarli.com',
333 'section' => 'entry',
334 ],
335 [
336 'name' => 'share_mail',
337 'value' => '1',
338 'section' => 'entry',
339 ],
340 [
341 'name' => 'share_twitter',
342 'value' => '1',
343 'section' => 'entry',
344 ],
345 [
346 'name' => 'export_epub',
347 'value' => '1',
348 'section' => 'export',
349 ],
350 [
351 'name' => 'export_mobi',
352 'value' => '1',
353 'section' => 'export',
354 ],
355 [
356 'name' => 'export_pdf',
357 'value' => '1',
358 'section' => 'export',
359 ],
360 [
361 'name' => 'export_csv',
362 'value' => '1',
363 'section' => 'export',
364 ],
365 [
366 'name' => 'export_json',
367 'value' => '1',
368 'section' => 'export',
369 ],
370 [
371 'name' => 'export_txt',
372 'value' => '1',
373 'section' => 'export',
374 ],
375 [
376 'name' => 'export_xml',
377 'value' => '1',
378 'section' => 'export',
379 ],
380 [
381 'name' => 'import_with_redis',
382 'value' => '0',
383 'section' => 'import',
384 ],
385 [
386 'name' => 'import_with_rabbitmq',
387 'value' => '0',
388 'section' => 'import',
389 ],
390 [
391 'name' => 'show_printlink',
392 'value' => '1',
393 'section' => 'entry',
394 ],
395 [
396 'name' => 'wallabag_support_url',
397 'value' => 'https://www.wallabag.org/pages/support.html',
398 'section' => 'misc',
399 ],
400 [
401 'name' => 'wallabag_url',
402 'value' => '',
403 'section' => 'misc',
404 ],
405 [
406 'name' => 'piwik_enabled',
407 'value' => '0',
408 'section' => 'analytics',
409 ],
410 [
411 'name' => 'piwik_host',
412 'value' => 'v2.wallabag.org',
413 'section' => 'analytics',
414 ],
415 [
416 'name' => 'piwik_site_id',
417 'value' => '1',
418 'section' => 'analytics',
419 ],
420 [
421 'name' => 'demo_mode_enabled',
422 'value' => '0',
423 'section' => 'misc',
424 ],
425 [
426 'name' => 'demo_mode_username',
427 'value' => 'wallabag',
428 'section' => 'misc',
429 ],
430 [
431 'name' => 'download_images_enabled',
432 'value' => '0',
433 'section' => 'misc',
434 ],
435 [
436 'name' => 'restricted_access',
437 'value' => '0',
438 'section' => 'entry',
439 ],
440 ];
441
442 foreach ($settings as $setting) {
443 $newSetting = new Setting(); 296 $newSetting = new Setting();
444 $newSetting->setName($setting['name']); 297 $newSetting->setName($setting['name']);
445 $newSetting->setValue($setting['value']); 298 $newSetting->setValue($setting['value']);
@@ -454,6 +307,16 @@ class InstallCommand extends ContainerAwareCommand
454 return $this; 307 return $this;
455 } 308 }
456 309
310 protected function runMigrations()
311 {
312 $this->defaultOutput->writeln('<info><comment>Step 5 of 5.</comment> Run migrations.</info>');
313
314 $this
315 ->runCommand('doctrine:migrations:migrate', ['--no-interaction' => true]);
316
317 return $this;
318 }
319
457 /** 320 /**
458 * Run a command. 321 * Run a command.
459 * 322 *
@@ -480,20 +343,18 @@ class InstallCommand extends ContainerAwareCommand
480 $output = new BufferedOutput(); 343 $output = new BufferedOutput();
481 $exitCode = $this->getApplication()->run(new ArrayInput($parameters), $output); 344 $exitCode = $this->getApplication()->run(new ArrayInput($parameters), $output);
482 345
346 // PDO does not always close the connection after Doctrine commands.
347 // See https://github.com/symfony/symfony/issues/11750.
348 $this->getContainer()->get('doctrine')->getManager()->getConnection()->close();
349
483 if (0 !== $exitCode) { 350 if (0 !== $exitCode) {
484 $this->getApplication()->setAutoExit(true); 351 $this->getApplication()->setAutoExit(true);
485 352
486 $this->defaultOutput->writeln(''); 353 throw new \RuntimeException(
487 $this->defaultOutput->writeln('<error>The command "'.$command.'" generates some errors: </error>'); 354 'The command "'.$command."\" generates some errors: \n\n"
488 $this->defaultOutput->writeln($output->fetch()); 355 .$output->fetch());
489
490 die();
491 } 356 }
492 357
493 // PDO does not always close the connection after Doctrine commands.
494 // See https://github.com/symfony/symfony/issues/11750.
495 $this->getContainer()->get('doctrine')->getManager()->getConnection()->close();
496
497 return $this; 358 return $this;
498 } 359 }
499 360
diff --git a/src/Wallabag/CoreBundle/Command/ShowUserCommand.php b/src/Wallabag/CoreBundle/Command/ShowUserCommand.php
new file mode 100644
index 00000000..eef04988
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Command/ShowUserCommand.php
@@ -0,0 +1,77 @@
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\UserBundle\Entity\User;
11
12class ShowUserCommand extends ContainerAwareCommand
13{
14 /** @var OutputInterface */
15 protected $output;
16
17 protected function configure()
18 {
19 $this
20 ->setName('wallabag:user:show')
21 ->setDescription('Show user details')
22 ->setHelp('This command shows the details for an user')
23 ->addArgument(
24 'username',
25 InputArgument::REQUIRED,
26 'User to show details for'
27 );
28 }
29
30 protected function execute(InputInterface $input, OutputInterface $output)
31 {
32 $this->output = $output;
33
34 $username = $input->getArgument('username');
35
36 try {
37 $user = $this->getUser($username);
38 $this->showUser($user);
39 } catch (NoResultException $e) {
40 $output->writeln(sprintf('<error>User "%s" not found.</error>', $username));
41
42 return 1;
43 }
44
45 return 0;
46 }
47
48 /**
49 * @param User $user
50 */
51 private function showUser(User $user)
52 {
53 $this->output->writeln(sprintf('Username : %s', $user->getUsername()));
54 $this->output->writeln(sprintf('Email : %s', $user->getEmail()));
55 $this->output->writeln(sprintf('Display name : %s', $user->getName()));
56 $this->output->writeln(sprintf('Creation date : %s', $user->getCreatedAt()->format('Y-m-d H:i:s')));
57 $this->output->writeln(sprintf('Last login : %s', $user->getLastLogin() !== null ? $user->getLastLogin()->format('Y-m-d H:i:s') : 'never'));
58 $this->output->writeln(sprintf('2FA activated: %s', $user->isTwoFactorAuthentication() ? 'yes' : 'no'));
59 }
60
61 /**
62 * Fetches a user from its username.
63 *
64 * @param string $username
65 *
66 * @return \Wallabag\UserBundle\Entity\User
67 */
68 private function getUser($username)
69 {
70 return $this->getContainer()->get('wallabag_user.user_repository')->findOneByUserName($username);
71 }
72
73 private function getDoctrine()
74 {
75 return $this->getContainer()->get('doctrine');
76 }
77}
diff --git a/src/Wallabag/CoreBundle/Command/TagAllCommand.php b/src/Wallabag/CoreBundle/Command/TagAllCommand.php
index 3f9bb04d..9843674e 100644
--- a/src/Wallabag/CoreBundle/Command/TagAllCommand.php
+++ b/src/Wallabag/CoreBundle/Command/TagAllCommand.php
@@ -59,7 +59,7 @@ class TagAllCommand extends ContainerAwareCommand
59 */ 59 */
60 private function getUser($username) 60 private function getUser($username)
61 { 61 {
62 return $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findOneByUserName($username); 62 return $this->getContainer()->get('wallabag_user.user_repository')->findOneByUserName($username);
63 } 63 }
64 64
65 private function getDoctrine() 65 private function getDoctrine()
diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php
index 907bf78e..d4170d39 100644
--- a/src/Wallabag/CoreBundle/Controller/ConfigController.php
+++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php
@@ -151,9 +151,8 @@ class ConfigController extends Controller
151 'token' => $config->getRssToken(), 151 'token' => $config->getRssToken(),
152 ], 152 ],
153 'twofactor_auth' => $this->getParameter('twofactor_auth'), 153 'twofactor_auth' => $this->getParameter('twofactor_auth'),
154 'wallabag_url' => $this->get('craue_config')->get('wallabag_url'), 154 'wallabag_url' => $this->getParameter('domain_name'),
155 'enabled_users' => $this->getDoctrine() 155 'enabled_users' => $this->get('wallabag_user.user_repository')
156 ->getRepository('WallabagUserBundle:User')
157 ->getSumEnabledUsers(), 156 ->getSumEnabledUsers(),
158 ]); 157 ]);
159 } 158 }
@@ -248,18 +247,27 @@ class ConfigController extends Controller
248 break; 247 break;
249 248
250 case 'entries': 249 case 'entries':
251 // SQLite doesn't care about cascading remove, so we need to manually remove associated stuf 250 // SQLite doesn't care about cascading remove, so we need to manually remove associated stuff
252 // otherwise they won't be removed ... 251 // otherwise they won't be removed ...
253 if ($this->get('doctrine')->getConnection()->getDriver() instanceof \Doctrine\DBAL\Driver\PDOSqlite\Driver) { 252 if ($this->get('doctrine')->getConnection()->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\SqlitePlatform) {
254 $this->getDoctrine()->getRepository('WallabagAnnotationBundle:Annotation')->removeAllByUserId($this->getUser()->getId()); 253 $this->getDoctrine()->getRepository('WallabagAnnotationBundle:Annotation')->removeAllByUserId($this->getUser()->getId());
255 } 254 }
256 255
257 // manually remove tags to avoid orphan tag 256 // manually remove tags to avoid orphan tag
258 $this->removeAllTagsByUserId($this->getUser()->getId()); 257 $this->removeAllTagsByUserId($this->getUser()->getId());
259 258
260 $this->getDoctrine() 259 $this->get('wallabag_core.entry_repository')->removeAllByUserId($this->getUser()->getId());
261 ->getRepository('WallabagCoreBundle:Entry') 260 break;
262 ->removeAllByUserId($this->getUser()->getId()); 261 case 'archived':
262 if ($this->get('doctrine')->getConnection()->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\SqlitePlatform) {
263 $this->removeAnnotationsForArchivedByUserId($this->getUser()->getId());
264 }
265
266 // manually remove tags to avoid orphan tag
267 $this->removeTagsForArchivedByUserId($this->getUser()->getId());
268
269 $this->get('wallabag_core.entry_repository')->removeArchivedByUserId($this->getUser()->getId());
270 break;
263 } 271 }
264 272
265 $this->get('session')->getFlashBag()->add( 273 $this->get('session')->getFlashBag()->add(
@@ -271,20 +279,18 @@ class ConfigController extends Controller
271 } 279 }
272 280
273 /** 281 /**
274 * Remove all tags for a given user and cleanup orphan tags. 282 * Remove all tags for given tags and a given user and cleanup orphan tags.
275 * 283 *
276 * @param int $userId 284 * @param array $tags
285 * @param int $userId
277 */ 286 */
278 private function removeAllTagsByUserId($userId) 287 private function removeAllTagsByStatusAndUserId($tags, $userId)
279 { 288 {
280 $tags = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findAllTags($userId);
281
282 if (empty($tags)) { 289 if (empty($tags)) {
283 return; 290 return;
284 } 291 }
285 292
286 $this->getDoctrine() 293 $this->get('wallabag_core.entry_repository')
287 ->getRepository('WallabagCoreBundle:Entry')
288 ->removeTags($userId, $tags); 294 ->removeTags($userId, $tags);
289 295
290 // cleanup orphan tags 296 // cleanup orphan tags
@@ -300,6 +306,43 @@ class ConfigController extends Controller
300 } 306 }
301 307
302 /** 308 /**
309 * Remove all tags for a given user and cleanup orphan tags.
310 *
311 * @param int $userId
312 */
313 private function removeAllTagsByUserId($userId)
314 {
315 $tags = $this->get('wallabag_core.tag_repository')->findAllTags($userId);
316 $this->removeAllTagsByStatusAndUserId($tags, $userId);
317 }
318
319 /**
320 * Remove all tags for a given user and cleanup orphan tags.
321 *
322 * @param int $userId
323 */
324 private function removeTagsForArchivedByUserId($userId)
325 {
326 $tags = $this->get('wallabag_core.tag_repository')->findForArchivedArticlesByUser($userId);
327 $this->removeAllTagsByStatusAndUserId($tags, $userId);
328 }
329
330 private function removeAnnotationsForArchivedByUserId($userId)
331 {
332 $em = $this->getDoctrine()->getManager();
333
334 $archivedEntriesAnnotations = $this->getDoctrine()
335 ->getRepository('WallabagAnnotationBundle:Annotation')
336 ->findAllArchivedEntriesByUser($userId);
337
338 foreach ($archivedEntriesAnnotations as $archivedEntriesAnnotation) {
339 $em->remove($archivedEntriesAnnotation);
340 }
341
342 $em->flush();
343 }
344
345 /**
303 * Validate that a rule can be edited/deleted by the current user. 346 * Validate that a rule can be edited/deleted by the current user.
304 * 347 *
305 * @param TaggingRule $rule 348 * @param TaggingRule $rule
@@ -344,8 +387,7 @@ class ConfigController extends Controller
344 */ 387 */
345 public function deleteAccountAction(Request $request) 388 public function deleteAccountAction(Request $request)
346 { 389 {
347 $enabledUsers = $this->getDoctrine() 390 $enabledUsers = $this->get('wallabag_user.user_repository')
348 ->getRepository('WallabagUserBundle:User')
349 ->getSumEnabledUsers(); 391 ->getSumEnabledUsers();
350 392
351 if ($enabledUsers <= 1) { 393 if ($enabledUsers <= 1) {
diff --git a/src/Wallabag/CoreBundle/Controller/EntryController.php b/src/Wallabag/CoreBundle/Controller/EntryController.php
index f7398e69..fafa49f1 100644
--- a/src/Wallabag/CoreBundle/Controller/EntryController.php
+++ b/src/Wallabag/CoreBundle/Controller/EntryController.php
@@ -53,22 +53,17 @@ class EntryController extends Controller
53 53
54 /** 54 /**
55 * Fetch content and update entry. 55 * Fetch content and update entry.
56 * In case it fails, entry will return to avod loosing the data. 56 * In case it fails, $entry->getContent will return an error message.
57 * 57 *
58 * @param Entry $entry 58 * @param Entry $entry
59 * @param string $prefixMessage Should be the translation key: entry_saved or entry_reloaded 59 * @param string $prefixMessage Should be the translation key: entry_saved or entry_reloaded
60 *
61 * @return Entry
62 */ 60 */
63 private function updateEntry(Entry $entry, $prefixMessage = 'entry_saved') 61 private function updateEntry(Entry $entry, $prefixMessage = 'entry_saved')
64 { 62 {
65 // put default title in case of fetching content failed
66 $entry->setTitle('No title found');
67
68 $message = 'flashes.entry.notice.'.$prefixMessage; 63 $message = 'flashes.entry.notice.'.$prefixMessage;
69 64
70 try { 65 try {
71 $entry = $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl()); 66 $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl());
72 } catch (\Exception $e) { 67 } catch (\Exception $e) {
73 $this->get('logger')->error('Error while saving an entry', [ 68 $this->get('logger')->error('Error while saving an entry', [
74 'exception' => $e, 69 'exception' => $e,
@@ -79,8 +74,6 @@ class EntryController extends Controller
79 } 74 }
80 75
81 $this->get('session')->getFlashBag()->add('notice', $message); 76 $this->get('session')->getFlashBag()->add('notice', $message);
82
83 return $entry;
84 } 77 }
85 78
86 /** 79 /**
@@ -227,7 +220,7 @@ class EntryController extends Controller
227 public function showUnreadAction(Request $request, $page) 220 public function showUnreadAction(Request $request, $page)
228 { 221 {
229 // load the quickstart if no entry in database 222 // load the quickstart if no entry in database
230 if ($page == 1 && $this->get('wallabag_core.entry_repository')->countAllEntriesByUsername($this->getUser()->getId()) == 0) { 223 if ($page == 1 && $this->get('wallabag_core.entry_repository')->countAllEntriesByUser($this->getUser()->getId()) == 0) {
231 return $this->redirect($this->generateUrl('quickstart')); 224 return $this->redirect($this->generateUrl('quickstart'));
232 } 225 }
233 226
@@ -321,8 +314,7 @@ class EntryController extends Controller
321 314
322 $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); 315 $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false);
323 316
324 $entries = $this->get('wallabag_core.helper.prepare_pager_for_entries') 317 $entries = $this->get('wallabag_core.helper.prepare_pager_for_entries')->prepare($pagerAdapter);
325 ->prepare($pagerAdapter, $page);
326 318
327 try { 319 try {
328 $entries->setCurrentPage($page); 320 $entries->setCurrentPage($page);
diff --git a/src/Wallabag/CoreBundle/Controller/ExportController.php b/src/Wallabag/CoreBundle/Controller/ExportController.php
index abc3336a..fda04cfb 100644
--- a/src/Wallabag/CoreBundle/Controller/ExportController.php
+++ b/src/Wallabag/CoreBundle/Controller/ExportController.php
@@ -7,7 +7,6 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\Request; 7use Symfony\Component\HttpFoundation\Request;
8use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 8use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
9use Wallabag\CoreBundle\Entity\Entry; 9use Wallabag\CoreBundle\Entity\Entry;
10use Wallabag\CoreBundle\Entity\Tag;
11 10
12/** 11/**
13 * The try/catch can be removed once all formats will be implemented. 12 * The try/catch can be removed once all formats will be implemented.
@@ -57,16 +56,17 @@ class ExportController extends Controller
57 { 56 {
58 $method = ucfirst($category); 57 $method = ucfirst($category);
59 $methodBuilder = 'getBuilderFor'.$method.'ByUser'; 58 $methodBuilder = 'getBuilderFor'.$method.'ByUser';
59 $repository = $this->get('wallabag_core.entry_repository');
60 60
61 if ($category == 'tag_entries') { 61 if ($category == 'tag_entries') {
62 $tag = $this->getDoctrine()->getRepository('WallabagCoreBundle:Tag')->findOneBySlug($request->query->get('tag')); 62 $tag = $this->get('wallabag_core.tag_repository')->findOneBySlug($request->query->get('tag'));
63 63
64 $entries = $this->getDoctrine() 64 $entries = $repository->findAllByTagId(
65 ->getRepository('WallabagCoreBundle:Entry') 65 $this->getUser()->getId(),
66 ->findAllByTagId($this->getUser()->getId(), $tag->getId()); 66 $tag->getId()
67 );
67 } else { 68 } else {
68 $entries = $this->getDoctrine() 69 $entries = $repository
69 ->getRepository('WallabagCoreBundle:Entry')
70 ->$methodBuilder($this->getUser()->getId()) 70 ->$methodBuilder($this->getUser()->getId())
71 ->getQuery() 71 ->getQuery()
72 ->getResult(); 72 ->getResult();
diff --git a/src/Wallabag/CoreBundle/Controller/RssController.php b/src/Wallabag/CoreBundle/Controller/RssController.php
index 92f18707..e87dd9a1 100644
--- a/src/Wallabag/CoreBundle/Controller/RssController.php
+++ b/src/Wallabag/CoreBundle/Controller/RssController.php
@@ -3,13 +3,16 @@
3namespace Wallabag\CoreBundle\Controller; 3namespace Wallabag\CoreBundle\Controller;
4 4
5use Pagerfanta\Adapter\DoctrineORMAdapter; 5use Pagerfanta\Adapter\DoctrineORMAdapter;
6use Pagerfanta\Adapter\ArrayAdapter;
6use Pagerfanta\Exception\OutOfRangeCurrentPageException; 7use Pagerfanta\Exception\OutOfRangeCurrentPageException;
7use Pagerfanta\Pagerfanta; 8use Pagerfanta\Pagerfanta;
8use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; 9use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
9use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 10use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
10use Symfony\Component\HttpFoundation\Request; 11use Symfony\Component\HttpFoundation\Request;
12use Symfony\Component\HttpFoundation\Response;
11use Symfony\Bundle\FrameworkBundle\Controller\Controller; 13use Symfony\Bundle\FrameworkBundle\Controller\Controller;
12use Wallabag\CoreBundle\Entity\Entry; 14use Wallabag\CoreBundle\Entity\Entry;
15use Wallabag\CoreBundle\Entity\Tag;
13use Wallabag\UserBundle\Entity\User; 16use Wallabag\UserBundle\Entity\User;
14use Symfony\Component\Routing\Generator\UrlGeneratorInterface; 17use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
15 18
@@ -23,7 +26,7 @@ class RssController extends Controller
23 * 26 *
24 * @return \Symfony\Component\HttpFoundation\Response 27 * @return \Symfony\Component\HttpFoundation\Response
25 */ 28 */
26 public function showUnreadAction(Request $request, User $user) 29 public function showUnreadRSSAction(Request $request, User $user)
27 { 30 {
28 return $this->showEntries('unread', $user, $request->query->get('page', 1)); 31 return $this->showEntries('unread', $user, $request->query->get('page', 1));
29 } 32 }
@@ -31,12 +34,12 @@ class RssController extends Controller
31 /** 34 /**
32 * Shows read entries for current user. 35 * Shows read entries for current user.
33 * 36 *
34 * @Route("/{username}/{token}/archive.xml", name="archive_rss") 37 * @Route("/{username}/{token}/archive.xml", name="archive_rss", defaults={"_format"="xml"})
35 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") 38 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter")
36 * 39 *
37 * @return \Symfony\Component\HttpFoundation\Response 40 * @return \Symfony\Component\HttpFoundation\Response
38 */ 41 */
39 public function showArchiveAction(Request $request, User $user) 42 public function showArchiveRSSAction(Request $request, User $user)
40 { 43 {
41 return $this->showEntries('archive', $user, $request->query->get('page', 1)); 44 return $this->showEntries('archive', $user, $request->query->get('page', 1));
42 } 45 }
@@ -44,17 +47,89 @@ class RssController extends Controller
44 /** 47 /**
45 * Shows starred entries for current user. 48 * Shows starred entries for current user.
46 * 49 *
47 * @Route("/{username}/{token}/starred.xml", name="starred_rss") 50 * @Route("/{username}/{token}/starred.xml", name="starred_rss", defaults={"_format"="xml"})
48 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") 51 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter")
49 * 52 *
50 * @return \Symfony\Component\HttpFoundation\Response 53 * @return \Symfony\Component\HttpFoundation\Response
51 */ 54 */
52 public function showStarredAction(Request $request, User $user) 55 public function showStarredRSSAction(Request $request, User $user)
53 { 56 {
54 return $this->showEntries('starred', $user, $request->query->get('page', 1)); 57 return $this->showEntries('starred', $user, $request->query->get('page', 1));
55 } 58 }
56 59
57 /** 60 /**
61 * Shows all entries for current user.
62 *
63 * @Route("/{username}/{token}/all.xml", name="all_rss", defaults={"_format"="xml"})
64 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter")
65 *
66 * @return \Symfony\Component\HttpFoundation\Response
67 */
68 public function showAllRSSAction(Request $request, User $user)
69 {
70 return $this->showEntries('all', $user, $request->query->get('page', 1));
71 }
72
73 /**
74 * Shows entries associated to a tag for current user.
75 *
76 * @Route("/{username}/{token}/tags/{slug}.xml", name="tag_rss", defaults={"_format"="xml"})
77 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter")
78 * @ParamConverter("tag", options={"mapping": {"slug": "slug"}})
79 *
80 * @return \Symfony\Component\HttpFoundation\Response
81 */
82 public function showTagsAction(Request $request, User $user, Tag $tag)
83 {
84 $page = $request->query->get('page', 1);
85
86 $url = $this->generateUrl(
87 'tag_rss',
88 [
89 'username' => $user->getUsername(),
90 'token' => $user->getConfig()->getRssToken(),
91 'slug' => $tag->getSlug(),
92 ],
93 UrlGeneratorInterface::ABSOLUTE_URL
94 );
95
96 $entriesByTag = $this->get('wallabag_core.entry_repository')->findAllByTagId(
97 $user->getId(),
98 $tag->getId()
99 );
100
101 $pagerAdapter = new ArrayAdapter($entriesByTag);
102
103 $entries = $this->get('wallabag_core.helper.prepare_pager_for_entries')->prepare(
104 $pagerAdapter,
105 $user
106 );
107
108 if (null === $entries) {
109 throw $this->createNotFoundException('No entries found?');
110 }
111
112 try {
113 $entries->setCurrentPage($page);
114 } catch (OutOfRangeCurrentPageException $e) {
115 if ($page > 1) {
116 return $this->redirect($url.'?page='.$entries->getNbPages(), 302);
117 }
118 }
119
120 return $this->render(
121 '@WallabagCore/themes/common/Entry/entries.xml.twig',
122 [
123 'url_html' => $this->generateUrl('tag_entries', ['slug' => $tag->getSlug()], UrlGeneratorInterface::ABSOLUTE_URL),
124 'type' => 'tag ('.$tag->getLabel().')',
125 'url' => $url,
126 'entries' => $entries,
127 ],
128 new Response('', 200, ['Content-Type' => 'application/rss+xml'])
129 );
130 }
131
132 /**
58 * Global method to retrieve entries depending on the given type 133 * Global method to retrieve entries depending on the given type
59 * It returns the response to be send. 134 * It returns the response to be send.
60 * 135 *
@@ -66,7 +141,7 @@ class RssController extends Controller
66 */ 141 */
67 private function showEntries($type, User $user, $page = 1) 142 private function showEntries($type, User $user, $page = 1)
68 { 143 {
69 $repository = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry'); 144 $repository = $this->get('wallabag_core.entry_repository');
70 145
71 switch ($type) { 146 switch ($type) {
72 case 'starred': 147 case 'starred':
@@ -81,6 +156,10 @@ class RssController extends Controller
81 $qb = $repository->getBuilderForUnreadByUser($user->getId()); 156 $qb = $repository->getBuilderForUnreadByUser($user->getId());
82 break; 157 break;
83 158
159 case 'all':
160 $qb = $repository->getBuilderForAllByUser($user->getId());
161 break;
162
84 default: 163 default:
85 throw new \InvalidArgumentException(sprintf('Type "%s" is not implemented.', $type)); 164 throw new \InvalidArgumentException(sprintf('Type "%s" is not implemented.', $type));
86 } 165 }
@@ -108,10 +187,15 @@ class RssController extends Controller
108 } 187 }
109 } 188 }
110 189
111 return $this->render('@WallabagCore/themes/common/Entry/entries.xml.twig', [ 190 return $this->render(
112 'type' => $type, 191 '@WallabagCore/themes/common/Entry/entries.xml.twig',
113 'url' => $url, 192 [
114 'entries' => $entries, 193 'url_html' => $this->generateUrl($type, [], UrlGeneratorInterface::ABSOLUTE_URL),
115 ]); 194 'type' => $type,
195 'url' => $url,
196 'entries' => $entries,
197 ],
198 new Response('', 200, ['Content-Type' => 'application/rss+xml'])
199 );
116 } 200 }
117} 201}
diff --git a/src/Wallabag/CoreBundle/Controller/SiteCredentialController.php b/src/Wallabag/CoreBundle/Controller/SiteCredentialController.php
new file mode 100644
index 00000000..98781dab
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Controller/SiteCredentialController.php
@@ -0,0 +1,174 @@
1<?php
2
3namespace Wallabag\CoreBundle\Controller;
4
5use Symfony\Component\HttpFoundation\Request;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
8use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
9use Wallabag\UserBundle\Entity\User;
10use Wallabag\CoreBundle\Entity\SiteCredential;
11
12/**
13 * SiteCredential controller.
14 *
15 * @Route("/site-credentials")
16 */
17class SiteCredentialController extends Controller
18{
19 /**
20 * Lists all User entities.
21 *
22 * @Route("/", name="site_credentials_index")
23 * @Method("GET")
24 */
25 public function indexAction()
26 {
27 $credentials = $this->get('wallabag_core.site_credential_repository')->findByUser($this->getUser());
28
29 return $this->render('WallabagCoreBundle:SiteCredential:index.html.twig', [
30 'credentials' => $credentials,
31 ]);
32 }
33
34 /**
35 * Creates a new site credential entity.
36 *
37 * @Route("/new", name="site_credentials_new")
38 * @Method({"GET", "POST"})
39 *
40 * @param Request $request
41 *
42 * @return \Symfony\Component\HttpFoundation\Response
43 */
44 public function newAction(Request $request)
45 {
46 $credential = new SiteCredential($this->getUser());
47
48 $form = $this->createForm('Wallabag\CoreBundle\Form\Type\SiteCredentialType', $credential);
49 $form->handleRequest($request);
50
51 if ($form->isSubmitted() && $form->isValid()) {
52 $credential->setUsername($this->get('wallabag_core.helper.crypto_proxy')->crypt($credential->getUsername()));
53 $credential->setPassword($this->get('wallabag_core.helper.crypto_proxy')->crypt($credential->getPassword()));
54
55 $em = $this->getDoctrine()->getManager();
56 $em->persist($credential);
57 $em->flush();
58
59 $this->get('session')->getFlashBag()->add(
60 'notice',
61 $this->get('translator')->trans('flashes.site_credential.notice.added', ['%host%' => $credential->getHost()])
62 );
63
64 return $this->redirectToRoute('site_credentials_index');
65 }
66
67 return $this->render('WallabagCoreBundle:SiteCredential:new.html.twig', [
68 'credential' => $credential,
69 'form' => $form->createView(),
70 ]);
71 }
72
73 /**
74 * Displays a form to edit an existing site credential entity.
75 *
76 * @Route("/{id}/edit", name="site_credentials_edit")
77 * @Method({"GET", "POST"})
78 *
79 * @param Request $request
80 * @param SiteCredential $siteCredential
81 *
82 * @return \Symfony\Component\HttpFoundation\Response
83 */
84 public function editAction(Request $request, SiteCredential $siteCredential)
85 {
86 $this->checkUserAction($siteCredential);
87
88 $deleteForm = $this->createDeleteForm($siteCredential);
89 $editForm = $this->createForm('Wallabag\CoreBundle\Form\Type\SiteCredentialType', $siteCredential);
90 $editForm->handleRequest($request);
91
92 if ($editForm->isSubmitted() && $editForm->isValid()) {
93 $siteCredential->setUsername($this->get('wallabag_core.helper.crypto_proxy')->crypt($siteCredential->getUsername()));
94 $siteCredential->setPassword($this->get('wallabag_core.helper.crypto_proxy')->crypt($siteCredential->getPassword()));
95
96 $em = $this->getDoctrine()->getManager();
97 $em->persist($siteCredential);
98 $em->flush();
99
100 $this->get('session')->getFlashBag()->add(
101 'notice',
102 $this->get('translator')->trans('flashes.site_credential.notice.updated', ['%host%' => $siteCredential->getHost()])
103 );
104
105 return $this->redirectToRoute('site_credentials_index');
106 }
107
108 return $this->render('WallabagCoreBundle:SiteCredential:edit.html.twig', [
109 'credential' => $siteCredential,
110 'edit_form' => $editForm->createView(),
111 'delete_form' => $deleteForm->createView(),
112 ]);
113 }
114
115 /**
116 * Deletes a site credential entity.
117 *
118 * @Route("/{id}", name="site_credentials_delete")
119 * @Method("DELETE")
120 *
121 * @param Request $request
122 * @param SiteCredential $siteCredential
123 *
124 * @return \Symfony\Component\HttpFoundation\RedirectResponse
125 */
126 public function deleteAction(Request $request, SiteCredential $siteCredential)
127 {
128 $this->checkUserAction($siteCredential);
129
130 $form = $this->createDeleteForm($siteCredential);
131 $form->handleRequest($request);
132
133 if ($form->isSubmitted() && $form->isValid()) {
134 $this->get('session')->getFlashBag()->add(
135 'notice',
136 $this->get('translator')->trans('flashes.site_credential.notice.deleted', ['%host%' => $siteCredential->getHost()])
137 );
138
139 $em = $this->getDoctrine()->getManager();
140 $em->remove($siteCredential);
141 $em->flush();
142 }
143
144 return $this->redirectToRoute('site_credentials_index');
145 }
146
147 /**
148 * Creates a form to delete a site credential entity.
149 *
150 * @param SiteCredential $siteCredential The site credential entity
151 *
152 * @return \Symfony\Component\Form\Form The form
153 */
154 private function createDeleteForm(SiteCredential $siteCredential)
155 {
156 return $this->createFormBuilder()
157 ->setAction($this->generateUrl('site_credentials_delete', ['id' => $siteCredential->getId()]))
158 ->setMethod('DELETE')
159 ->getForm()
160 ;
161 }
162
163 /**
164 * Check if the logged user can manage the given site credential.
165 *
166 * @param SiteCredential $siteCredential The site credential entity
167 */
168 private function checkUserAction(SiteCredential $siteCredential)
169 {
170 if (null === $this->getUser() || $this->getUser()->getId() != $siteCredential->getUser()->getId()) {
171 throw $this->createAccessDeniedException('You can not access this site credential.');
172 }
173 }
174}
diff --git a/src/Wallabag/CoreBundle/Controller/TagController.php b/src/Wallabag/CoreBundle/Controller/TagController.php
index 8a093289..a8b1eadd 100644
--- a/src/Wallabag/CoreBundle/Controller/TagController.php
+++ b/src/Wallabag/CoreBundle/Controller/TagController.php
@@ -28,7 +28,7 @@ class TagController extends Controller
28 $form->handleRequest($request); 28 $form->handleRequest($request);
29 29
30 if ($form->isSubmitted() && $form->isValid()) { 30 if ($form->isSubmitted() && $form->isValid()) {
31 $this->get('wallabag_core.content_proxy')->assignTagsToEntry( 31 $this->get('wallabag_core.tags_assigner')->assignTagsToEntry(
32 $entry, 32 $entry,
33 $form->get('label')->getData() 33 $form->get('label')->getData()
34 ); 34 );
@@ -70,7 +70,7 @@ class TagController extends Controller
70 $em->flush(); 70 $em->flush();
71 } 71 }
72 72
73 $redirectUrl = $this->get('wallabag_core.helper.redirect')->to($request->headers->get('referer')); 73 $redirectUrl = $this->get('wallabag_core.helper.redirect')->to($request->headers->get('referer'), '', true);
74 74
75 return $this->redirect($redirectUrl); 75 return $this->redirect($redirectUrl);
76 } 76 }
@@ -84,16 +84,17 @@ class TagController extends Controller
84 */ 84 */
85 public function showTagAction() 85 public function showTagAction()
86 { 86 {
87 $tags = $this->getDoctrine() 87 $repository = $this->get('wallabag_core.entry_repository');
88 ->getRepository('WallabagCoreBundle:Tag') 88 $tags = $this->get('wallabag_core.tag_repository')
89 ->findAllTags($this->getUser()->getId()); 89 ->findAllTags($this->getUser()->getId());
90 90
91 $flatTags = []; 91 $flatTags = [];
92 92
93 foreach ($tags as $tag) { 93 foreach ($tags as $tag) {
94 $nbEntries = $this->getDoctrine() 94 $nbEntries = $repository->countAllEntriesByUserIdAndTagId(
95 ->getRepository('WallabagCoreBundle:Entry') 95 $this->getUser()->getId(),
96 ->countAllEntriesByUserIdAndTagId($this->getUser()->getId(), $tag->getId()); 96 $tag->getId()
97 );
97 98
98 $flatTags[] = [ 99 $flatTags[] = [
99 'id' => $tag->getId(), 100 'id' => $tag->getId(),
@@ -119,14 +120,14 @@ class TagController extends Controller
119 */ 120 */
120 public function showEntriesForTagAction(Tag $tag, $page, Request $request) 121 public function showEntriesForTagAction(Tag $tag, $page, Request $request)
121 { 122 {
122 $entriesByTag = $this->getDoctrine() 123 $entriesByTag = $this->get('wallabag_core.entry_repository')->findAllByTagId(
123 ->getRepository('WallabagCoreBundle:Entry') 124 $this->getUser()->getId(),
124 ->findAllByTagId($this->getUser()->getId(), $tag->getId()); 125 $tag->getId()
126 );
125 127
126 $pagerAdapter = new ArrayAdapter($entriesByTag); 128 $pagerAdapter = new ArrayAdapter($entriesByTag);
127 129
128 $entries = $this->get('wallabag_core.helper.prepare_pager_for_entries') 130 $entries = $this->get('wallabag_core.helper.prepare_pager_for_entries')->prepare($pagerAdapter);
129 ->prepare($pagerAdapter, $page);
130 131
131 try { 132 try {
132 $entries->setCurrentPage($page); 133 $entries->setCurrentPage($page);
@@ -143,7 +144,7 @@ class TagController extends Controller
143 'form' => null, 144 'form' => null,
144 'entries' => $entries, 145 'entries' => $entries,
145 'currentPage' => $page, 146 'currentPage' => $page,
146 'tag' => $tag->getSlug(), 147 'tag' => $tag,
147 ]); 148 ]);
148 } 149 }
149} 150}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php
index a723656e..a52288e6 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php
@@ -6,163 +6,27 @@ use Doctrine\Common\DataFixtures\AbstractFixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface; 6use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager; 7use Doctrine\Common\Persistence\ObjectManager;
8use Craue\ConfigBundle\Entity\Setting; 8use Craue\ConfigBundle\Entity\Setting;
9use Symfony\Component\DependencyInjection\ContainerAwareInterface;
10use Symfony\Component\DependencyInjection\ContainerInterface;
9 11
10class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface 12class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
11{ 13{
12 /** 14 /**
15 * @var ContainerInterface
16 */
17 private $container;
18
19 public function setContainer(ContainerInterface $container = null)
20 {
21 $this->container = $container;
22 }
23
24 /**
13 * {@inheritdoc} 25 * {@inheritdoc}
14 */ 26 */
15 public function load(ObjectManager $manager) 27 public function load(ObjectManager $manager)
16 { 28 {
17 $settings = [ 29 foreach ($this->container->getParameter('wallabag_core.default_internal_settings') as $setting) {
18 [
19 'name' => 'share_public',
20 'value' => '1',
21 'section' => 'entry',
22 ],
23 [
24 'name' => 'carrot',
25 'value' => '1',
26 'section' => 'entry',
27 ],
28 [
29 'name' => 'share_diaspora',
30 'value' => '1',
31 'section' => 'entry',
32 ],
33 [
34 'name' => 'diaspora_url',
35 'value' => 'http://diasporapod.com',
36 'section' => 'entry',
37 ],
38 [
39 'name' => 'share_unmark',
40 'value' => '1',
41 'section' => 'entry',
42 ],
43 [
44 'name' => 'unmark_url',
45 'value' => 'https://unmark.it',
46 'section' => 'entry',
47 ],
48 [
49 'name' => 'share_shaarli',
50 'value' => '1',
51 'section' => 'entry',
52 ],
53 [
54 'name' => 'shaarli_url',
55 'value' => 'http://myshaarli.com',
56 'section' => 'entry',
57 ],
58 [
59 'name' => 'share_mail',
60 'value' => '1',
61 'section' => 'entry',
62 ],
63 [
64 'name' => 'share_twitter',
65 'value' => '1',
66 'section' => 'entry',
67 ],
68 [
69 'name' => 'export_epub',
70 'value' => '1',
71 'section' => 'export',
72 ],
73 [
74 'name' => 'export_mobi',
75 'value' => '1',
76 'section' => 'export',
77 ],
78 [
79 'name' => 'export_pdf',
80 'value' => '1',
81 'section' => 'export',
82 ],
83 [
84 'name' => 'export_csv',
85 'value' => '1',
86 'section' => 'export',
87 ],
88 [
89 'name' => 'export_json',
90 'value' => '1',
91 'section' => 'export',
92 ],
93 [
94 'name' => 'export_txt',
95 'value' => '1',
96 'section' => 'export',
97 ],
98 [
99 'name' => 'export_xml',
100 'value' => '1',
101 'section' => 'export',
102 ],
103 [
104 'name' => 'import_with_redis',
105 'value' => '0',
106 'section' => 'import',
107 ],
108 [
109 'name' => 'import_with_rabbitmq',
110 'value' => '0',
111 'section' => 'import',
112 ],
113 [
114 'name' => 'show_printlink',
115 'value' => '1',
116 'section' => 'entry',
117 ],
118 [
119 'name' => 'wallabag_support_url',
120 'value' => 'https://www.wallabag.org/pages/support.html',
121 'section' => 'misc',
122 ],
123 [
124 'name' => 'wallabag_url',
125 'value' => 'http://v2.wallabag.org',
126 'section' => 'misc',
127 ],
128 [
129 'name' => 'piwik_enabled',
130 'value' => '0',
131 'section' => 'analytics',
132 ],
133 [
134 'name' => 'piwik_host',
135 'value' => 'v2.wallabag.org',
136 'section' => 'analytics',
137 ],
138 [
139 'name' => 'piwik_site_id',
140 'value' => '1',
141 'section' => 'analytics',
142 ],
143 [
144 'name' => 'demo_mode_enabled',
145 'value' => '0',
146 'section' => 'misc',
147 ],
148 [
149 'name' => 'demo_mode_username',
150 'value' => 'wallabag',
151 'section' => 'misc',
152 ],
153 [
154 'name' => 'download_images_enabled',
155 'value' => '0',
156 'section' => 'misc',
157 ],
158 [
159 'name' => 'restricted_access',
160 'value' => '0',
161 'section' => 'entry',
162 ],
163 ];
164
165 foreach ($settings as $setting) {
166 $newSetting = new Setting(); 30 $newSetting = new Setting();
167 $newSetting->setName($setting['name']); 31 $newSetting->setName($setting['name']);
168 $newSetting->setValue($setting['value']); 32 $newSetting->setValue($setting['value']);
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSiteCredentialData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSiteCredentialData.php
new file mode 100644
index 00000000..866f55a4
--- /dev/null
+++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSiteCredentialData.php
@@ -0,0 +1,34 @@
1<?php
2
3namespace Wallabag\CoreBundle\DataFixtures\ORM;
4
5use Doctrine\Common\DataFixtures\AbstractFixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager;
8use Wallabag\CoreBundle\Entity\SiteCredential;
9
10class LoadSiteCredentialData extends AbstractFixture implements OrderedFixtureInterface
11{
12 /**
13 * {@inheritdoc}
14 */
15 public function load(ObjectManager $manager)
16 {
17 $credential = new SiteCredential($this->getReference('admin-user'));
18 $credential->setHost('example.com');
19 $credential->setUsername('foo');
20 $credential->setPassword('bar');
21
22 $manager->persist($credential);
23
24 $manager->flush();
25 }
26
27 /**
28 * {@inheritdoc}
29 */
30 public function getOrder()
31 {
32 return 50;
33 }
34}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTaggingRuleData.php b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTaggingRuleData.php
index 7efe6356..55abd63c 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTaggingRuleData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTaggingRuleData.php
@@ -36,6 +36,13 @@ class LoadTaggingRuleData extends AbstractFixture implements OrderedFixtureInter
36 36
37 $manager->persist($tr3); 37 $manager->persist($tr3);
38 38
39 $tr4 = new TaggingRule();
40 $tr4->setRule('content notmatches "basket"');
41 $tr4->setTags(['foot']);
42 $tr4->setConfig($this->getReference('admin-config'));
43
44 $manager->persist($tr4);
45
39 $manager->flush(); 46 $manager->flush();
40 } 47 }
41 48
diff --git a/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php b/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php
index 006a18c3..a9791f6b 100644
--- a/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php
+++ b/src/Wallabag/CoreBundle/DependencyInjection/Configuration.php
@@ -41,12 +41,30 @@ class Configuration implements ConfigurationInterface
41 ->end() 41 ->end()
42 ->scalarNode('fetching_error_message') 42 ->scalarNode('fetching_error_message')
43 ->end() 43 ->end()
44 ->scalarNode('fetching_error_message_title')
45 ->end()
44 ->scalarNode('action_mark_as_read') 46 ->scalarNode('action_mark_as_read')
45 ->defaultValue(1) 47 ->defaultValue(1)
46 ->end() 48 ->end()
47 ->scalarNode('list_mode') 49 ->scalarNode('list_mode')
48 ->defaultValue(1) 50 ->defaultValue(1)
49 ->end() 51 ->end()
52 ->scalarNode('api_limit_mass_actions')
53 ->defaultValue(10)
54 ->end()
55 ->arrayNode('default_internal_settings')
56 ->prototype('array')
57 ->children()
58 ->scalarNode('name')->end()
59 ->scalarNode('value')->end()
60 ->enumNode('section')
61 ->values(['entry', 'misc', 'api', 'analytics', 'export', 'import'])
62 ->end()
63 ->end()
64 ->end()
65 ->end()
66 ->scalarNode('encryption_key_path')
67 ->end()
50 ->end() 68 ->end()
51 ; 69 ;
52 70
diff --git a/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php b/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php
index aa9ee339..532ce238 100644
--- a/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php
+++ b/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php
@@ -26,6 +26,10 @@ 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.fetching_error_message_title', $config['fetching_error_message_title']);
30 $container->setParameter('wallabag_core.api_limit_mass_actions', $config['api_limit_mass_actions']);
31 $container->setParameter('wallabag_core.default_internal_settings', $config['default_internal_settings']);
32 $container->setParameter('wallabag_core.site_credentials.encryption_key_path', $config['encryption_key_path']);
29 33
30 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); 34 $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
31 $loader->load('services.yml'); 35 $loader->load('services.yml');
diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php
index 7276b437..a0503c39 100644
--- a/src/Wallabag/CoreBundle/Entity/Entry.php
+++ b/src/Wallabag/CoreBundle/Entity/Entry.php
@@ -122,6 +122,24 @@ class Entry
122 private $updatedAt; 122 private $updatedAt;
123 123
124 /** 124 /**
125 * @var \DateTime
126 *
127 * @ORM\Column(name="published_at", type="datetime", nullable=true)
128 *
129 * @Groups({"entries_for_user", "export_all"})
130 */
131 private $publishedAt;
132
133 /**
134 * @var array
135 *
136 * @ORM\Column(name="published_by", type="array", nullable=true)
137 *
138 * @Groups({"entries_for_user", "export_all"})
139 */
140 private $publishedBy;
141
142 /**
125 * @ORM\OneToMany(targetEntity="Wallabag\AnnotationBundle\Entity\Annotation", mappedBy="entry", cascade={"persist", "remove"}) 143 * @ORM\OneToMany(targetEntity="Wallabag\AnnotationBundle\Entity\Annotation", mappedBy="entry", cascade={"persist", "remove"})
126 * @ORM\JoinTable 144 * @ORM\JoinTable
127 * 145 *
@@ -175,22 +193,22 @@ class Entry
175 private $previewPicture; 193 private $previewPicture;
176 194
177 /** 195 /**
178 * @var bool 196 * @var string
179 * 197 *
180 * @ORM\Column(name="is_public", type="boolean", nullable=true, options={"default" = false}) 198 * @ORM\Column(name="http_status", type="string", length=3, nullable=true)
181 * 199 *
182 * @Groups({"export_all"}) 200 * @Groups({"entries_for_user", "export_all"})
183 */ 201 */
184 private $isPublic; 202 private $httpStatus;
185 203
186 /** 204 /**
187 * @var string 205 * @var array
188 * 206 *
189 * @ORM\Column(name="http_status", type="string", length=3, nullable=true) 207 * @ORM\Column(name="headers", type="array", nullable=true)
190 * 208 *
191 * @Groups({"entries_for_user", "export_all"}) 209 * @Groups({"entries_for_user", "export_all"})
192 */ 210 */
193 private $httpStatus; 211 private $headers;
194 212
195 /** 213 /**
196 * @Exclude 214 * @Exclude
@@ -532,23 +550,7 @@ class Entry
532 } 550 }
533 551
534 /** 552 /**
535 * @return bool 553 * @return ArrayCollection
536 */
537 public function isPublic()
538 {
539 return $this->isPublic;
540 }
541
542 /**
543 * @param bool $isPublic
544 */
545 public function setIsPublic($isPublic)
546 {
547 $this->isPublic = $isPublic;
548 }
549
550 /**
551 * @return ArrayCollection<Tag>
552 */ 554 */
553 public function getTags() 555 public function getTags()
554 { 556 {
@@ -683,7 +685,22 @@ class Entry
683 } 685 }
684 686
685 /** 687 /**
686 * @return int 688 * Used in the entries filter so it's more explicit for the end user than the uid.
689 * Also used in the API.
690 *
691 * @VirtualProperty
692 * @SerializedName("is_public")
693 * @Groups({"entries_for_user"})
694 *
695 * @return bool
696 */
697 public function isPublic()
698 {
699 return null !== $this->uid;
700 }
701
702 /**
703 * @return string
687 */ 704 */
688 public function getHttpStatus() 705 public function getHttpStatus()
689 { 706 {
@@ -691,7 +708,7 @@ class Entry
691 } 708 }
692 709
693 /** 710 /**
694 * @param int $httpStatus 711 * @param string $httpStatus
695 * 712 *
696 * @return Entry 713 * @return Entry
697 */ 714 */
@@ -701,4 +718,64 @@ class Entry
701 718
702 return $this; 719 return $this;
703 } 720 }
721
722 /**
723 * @return \Datetime
724 */
725 public function getPublishedAt()
726 {
727 return $this->publishedAt;
728 }
729
730 /**
731 * @param \Datetime $publishedAt
732 *
733 * @return Entry
734 */
735 public function setPublishedAt(\Datetime $publishedAt)
736 {
737 $this->publishedAt = $publishedAt;
738
739 return $this;
740 }
741
742 /**
743 * @return array
744 */
745 public function getPublishedBy()
746 {
747 return $this->publishedBy;
748 }
749
750 /**
751 * @param array $publishedBy
752 *
753 * @return Entry
754 */
755 public function setPublishedBy($publishedBy)
756 {
757 $this->publishedBy = $publishedBy;
758
759 return $this;
760 }
761
762 /**
763 * @return array
764 */
765 public function getHeaders()
766 {
767 return $this->headers;
768 }
769
770 /**
771 * @param array $headers
772 *
773 * @return Entry
774 */
775 public function setHeaders($headers)
776 {
777 $this->headers = $headers;
778
779 return $this;
780 }
704} 781}
diff --git a/src/Wallabag/CoreBundle/Entity/SiteCredential.php b/src/Wallabag/CoreBundle/Entity/SiteCredential.php
new file mode 100644
index 00000000..58075e92
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Entity/SiteCredential.php
@@ -0,0 +1,195 @@
1<?php
2
3namespace Wallabag\CoreBundle\Entity;
4
5use Doctrine\ORM\Mapping as ORM;
6use Symfony\Component\Validator\Constraints as Assert;
7use Wallabag\UserBundle\Entity\User;
8
9/**
10 * SiteCredential.
11 *
12 * @ORM\Entity(repositoryClass="Wallabag\CoreBundle\Repository\SiteCredentialRepository")
13 * @ORM\Table(name="`site_credential`")
14 * @ORM\HasLifecycleCallbacks()
15 */
16class SiteCredential
17{
18 /**
19 * @var int
20 *
21 * @ORM\Column(name="id", type="integer")
22 * @ORM\Id
23 * @ORM\GeneratedValue(strategy="AUTO")
24 */
25 private $id;
26
27 /**
28 * @var string
29 *
30 * @Assert\NotBlank()
31 * @Assert\Length(max=255)
32 * @ORM\Column(name="host", type="string", length=255)
33 */
34 private $host;
35
36 /**
37 * @var string
38 *
39 * @Assert\NotBlank()
40 * @ORM\Column(name="username", type="text")
41 */
42 private $username;
43
44 /**
45 * @var string
46 *
47 * @Assert\NotBlank()
48 * @ORM\Column(name="password", type="text")
49 */
50 private $password;
51
52 /**
53 * @var \DateTime
54 *
55 * @ORM\Column(name="createdAt", type="datetime")
56 */
57 private $createdAt;
58
59 /**
60 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="site_credentials")
61 */
62 private $user;
63
64 /*
65 * @param User $user
66 */
67 public function __construct(User $user)
68 {
69 $this->user = $user;
70 }
71
72 /**
73 * Get id.
74 *
75 * @return int
76 */
77 public function getId()
78 {
79 return $this->id;
80 }
81
82 /**
83 * Set host.
84 *
85 * @param string $host
86 *
87 * @return SiteCredential
88 */
89 public function setHost($host)
90 {
91 $this->host = $host;
92
93 return $this;
94 }
95
96 /**
97 * Get host.
98 *
99 * @return string
100 */
101 public function getHost()
102 {
103 return $this->host;
104 }
105
106 /**
107 * Set username.
108 *
109 * @param string $username
110 *
111 * @return SiteCredential
112 */
113 public function setUsername($username)
114 {
115 $this->username = $username;
116
117 return $this;
118 }
119
120 /**
121 * Get username.
122 *
123 * @return string
124 */
125 public function getUsername()
126 {
127 return $this->username;
128 }
129
130 /**
131 * Set password.
132 *
133 * @param string $password
134 *
135 * @return SiteCredential
136 */
137 public function setPassword($password)
138 {
139 $this->password = $password;
140
141 return $this;
142 }
143
144 /**
145 * Get password.
146 *
147 * @return string
148 */
149 public function getPassword()
150 {
151 return $this->password;
152 }
153
154 /**
155 * Set createdAt.
156 *
157 * @param \DateTime $createdAt
158 *
159 * @return SiteCredential
160 */
161 public function setCreatedAt($createdAt)
162 {
163 $this->createdAt = $createdAt;
164
165 return $this;
166 }
167
168 /**
169 * Get createdAt.
170 *
171 * @return \DateTime
172 */
173 public function getCreatedAt()
174 {
175 return $this->createdAt;
176 }
177
178 /**
179 * @return User
180 */
181 public function getUser()
182 {
183 return $this->user;
184 }
185
186 /**
187 * @ORM\PrePersist
188 */
189 public function timestamps()
190 {
191 if (is_null($this->createdAt)) {
192 $this->createdAt = new \DateTime();
193 }
194 }
195}
diff --git a/src/Wallabag/CoreBundle/Entity/TaggingRule.php b/src/Wallabag/CoreBundle/Entity/TaggingRule.php
index 72651b19..84e11e26 100644
--- a/src/Wallabag/CoreBundle/Entity/TaggingRule.php
+++ b/src/Wallabag/CoreBundle/Entity/TaggingRule.php
@@ -31,7 +31,7 @@ class TaggingRule
31 * @Assert\Length(max=255) 31 * @Assert\Length(max=255)
32 * @RulerZAssert\ValidRule( 32 * @RulerZAssert\ValidRule(
33 * allowed_variables={"title", "url", "isArchived", "isStared", "content", "language", "mimetype", "readingTime", "domainName"}, 33 * allowed_variables={"title", "url", "isArchived", "isStared", "content", "language", "mimetype", "readingTime", "domainName"},
34 * allowed_operators={">", "<", ">=", "<=", "=", "is", "!=", "and", "not", "or", "matches"} 34 * allowed_operators={">", "<", ">=", "<=", "=", "is", "!=", "and", "not", "or", "matches", "notmatches"}
35 * ) 35 * )
36 * @ORM\Column(name="rule", type="string", nullable=false) 36 * @ORM\Column(name="rule", type="string", nullable=false)
37 */ 37 */
@@ -87,7 +87,7 @@ class TaggingRule
87 /** 87 /**
88 * Set tags. 88 * Set tags.
89 * 89 *
90 * @param array<string> $tags 90 * @param array <string> $tags
91 * 91 *
92 * @return TaggingRule 92 * @return TaggingRule
93 */ 93 */
diff --git a/src/Wallabag/CoreBundle/Event/Subscriber/SQLiteCascadeDeleteSubscriber.php b/src/Wallabag/CoreBundle/Event/Subscriber/SQLiteCascadeDeleteSubscriber.php
index 3b4c4cf9..5e6af8cc 100644
--- a/src/Wallabag/CoreBundle/Event/Subscriber/SQLiteCascadeDeleteSubscriber.php
+++ b/src/Wallabag/CoreBundle/Event/Subscriber/SQLiteCascadeDeleteSubscriber.php
@@ -45,9 +45,8 @@ class SQLiteCascadeDeleteSubscriber implements EventSubscriber
45 public function preRemove(LifecycleEventArgs $args) 45 public function preRemove(LifecycleEventArgs $args)
46 { 46 {
47 $entity = $args->getEntity(); 47 $entity = $args->getEntity();
48 48 if (!$this->doctrine->getConnection()->getDatabasePlatform() instanceof \Doctrine\DBAL\Platforms\SqlitePlatform
49 if (!$this->doctrine->getConnection()->getDriver() instanceof \Doctrine\DBAL\Driver\PDOSqlite\Driver || 49 || !$entity instanceof Entry) {
50 !$entity instanceof Entry) {
51 return; 50 return;
52 } 51 }
53 52
diff --git a/src/Wallabag/CoreBundle/Form/Type/EditEntryType.php b/src/Wallabag/CoreBundle/Form/Type/EditEntryType.php
index c3715646..1627cc44 100644
--- a/src/Wallabag/CoreBundle/Form/Type/EditEntryType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/EditEntryType.php
@@ -3,7 +3,6 @@
3namespace Wallabag\CoreBundle\Form\Type; 3namespace Wallabag\CoreBundle\Form\Type;
4 4
5use Symfony\Component\Form\AbstractType; 5use Symfony\Component\Form\AbstractType;
6use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
7use Symfony\Component\Form\Extension\Core\Type\SubmitType; 6use Symfony\Component\Form\Extension\Core\Type\SubmitType;
8use Symfony\Component\Form\Extension\Core\Type\TextType; 7use Symfony\Component\Form\Extension\Core\Type\TextType;
9use Symfony\Component\Form\FormBuilderInterface; 8use Symfony\Component\Form\FormBuilderInterface;
@@ -18,11 +17,6 @@ class EditEntryType extends AbstractType
18 'required' => true, 17 'required' => true,
19 'label' => 'entry.edit.title_label', 18 'label' => 'entry.edit.title_label',
20 ]) 19 ])
21 ->add('is_public', CheckboxType::class, [
22 'required' => false,
23 'label' => 'entry.edit.is_public_label',
24 'property_path' => 'isPublic',
25 ])
26 ->add('url', TextType::class, [ 20 ->add('url', TextType::class, [
27 'disabled' => true, 21 'disabled' => true,
28 'required' => false, 22 'required' => false,
diff --git a/src/Wallabag/CoreBundle/Form/Type/EntryFilterType.php b/src/Wallabag/CoreBundle/Form/Type/EntryFilterType.php
index 556578d1..6a4c485f 100644
--- a/src/Wallabag/CoreBundle/Form/Type/EntryFilterType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/EntryFilterType.php
@@ -150,6 +150,20 @@ class EntryFilterType extends AbstractType
150 }, 150 },
151 'label' => 'entry.filters.preview_picture_label', 151 'label' => 'entry.filters.preview_picture_label',
152 ]) 152 ])
153 ->add('isPublic', CheckboxFilterType::class, [
154 'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
155 if (false === $values['value']) {
156 return;
157 }
158
159 // is_public isn't a real field
160 // we should use the "uid" field to determine if the entry has been made public
161 $expression = $filterQuery->getExpr()->isNotNull($values['alias'].'.uid');
162
163 return $filterQuery->createCondition($expression);
164 },
165 'label' => 'entry.filters.is_public_label',
166 ])
153 ->add('language', ChoiceFilterType::class, [ 167 ->add('language', ChoiceFilterType::class, [
154 'choices' => array_flip($this->repository->findDistinctLanguageByUser($this->user->getId())), 168 'choices' => array_flip($this->repository->findDistinctLanguageByUser($this->user->getId())),
155 'label' => 'entry.filters.language_label', 169 'label' => 'entry.filters.language_label',
diff --git a/src/Wallabag/CoreBundle/Form/Type/SiteCredentialType.php b/src/Wallabag/CoreBundle/Form/Type/SiteCredentialType.php
new file mode 100644
index 00000000..fd409ad2
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Form/Type/SiteCredentialType.php
@@ -0,0 +1,44 @@
1<?php
2
3namespace Wallabag\CoreBundle\Form\Type;
4
5use Symfony\Component\Form\AbstractType;
6use Symfony\Component\Form\Extension\Core\Type\PasswordType;
7use Symfony\Component\Form\Extension\Core\Type\SubmitType;
8use Symfony\Component\Form\Extension\Core\Type\TextType;
9use Symfony\Component\Form\FormBuilderInterface;
10use Symfony\Component\OptionsResolver\OptionsResolver;
11
12class SiteCredentialType extends AbstractType
13{
14 public function buildForm(FormBuilderInterface $builder, array $options)
15 {
16 $builder
17 ->add('host', TextType::class, [
18 'label' => 'site_credential.form.host_label',
19 ])
20 ->add('username', TextType::class, [
21 'label' => 'site_credential.form.username_label',
22 'data' => '',
23 ])
24 ->add('password', PasswordType::class, [
25 'label' => 'site_credential.form.password_label',
26 ])
27 ->add('save', SubmitType::class, [
28 'label' => 'config.form.save',
29 ])
30 ;
31 }
32
33 public function configureOptions(OptionsResolver $resolver)
34 {
35 $resolver->setDefaults([
36 'data_class' => 'Wallabag\CoreBundle\Entity\SiteCredential',
37 ]);
38 }
39
40 public function getBlockPrefix()
41 {
42 return 'site_credential';
43 }
44}
diff --git a/src/Wallabag/CoreBundle/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php b/src/Wallabag/CoreBundle/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php
index 6d4129e8..a79e6ebe 100644
--- a/src/Wallabag/CoreBundle/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php
+++ b/src/Wallabag/CoreBundle/GuzzleSiteAuthenticator/GrabySiteConfigBuilder.php
@@ -5,39 +5,53 @@ namespace Wallabag\CoreBundle\GuzzleSiteAuthenticator;
5use BD\GuzzleSiteAuthenticator\SiteConfig\SiteConfig; 5use BD\GuzzleSiteAuthenticator\SiteConfig\SiteConfig;
6use BD\GuzzleSiteAuthenticator\SiteConfig\SiteConfigBuilder; 6use BD\GuzzleSiteAuthenticator\SiteConfig\SiteConfigBuilder;
7use Graby\SiteConfig\ConfigBuilder; 7use Graby\SiteConfig\ConfigBuilder;
8use OutOfRangeException; 8use Psr\Log\LoggerInterface;
9use Wallabag\CoreBundle\Repository\SiteCredentialRepository;
10use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorage;
9 11
10class GrabySiteConfigBuilder implements SiteConfigBuilder 12class GrabySiteConfigBuilder implements SiteConfigBuilder
11{ 13{
12 /** 14 /**
13 * @var \Graby\SiteConfig\ConfigBuilder 15 * @var ConfigBuilder
14 */ 16 */
15 private $grabyConfigBuilder; 17 private $grabyConfigBuilder;
18
19 /**
20 * @var SiteCredentialRepository
21 */
22 private $credentialRepository;
23
16 /** 24 /**
17 * @var array 25 * @var LoggerInterface
18 */ 26 */
19 private $credentials; 27 private $logger;
28
29 /**
30 * @var Wallabag\UserBundle\Entity\User|null
31 */
32 private $currentUser;
20 33
21 /** 34 /**
22 * GrabySiteConfigBuilder constructor. 35 * GrabySiteConfigBuilder constructor.
23 * 36 *
24 * @param \Graby\SiteConfig\ConfigBuilder $grabyConfigBuilder 37 * @param ConfigBuilder $grabyConfigBuilder
25 * @param array $credentials 38 * @param TokenStorage $token
39 * @param SiteCredentialRepository $credentialRepository
40 * @param LoggerInterface $logger
26 */ 41 */
27 public function __construct(ConfigBuilder $grabyConfigBuilder, array $credentials = []) 42 public function __construct(ConfigBuilder $grabyConfigBuilder, TokenStorage $token, SiteCredentialRepository $credentialRepository, LoggerInterface $logger)
28 { 43 {
29 $this->grabyConfigBuilder = $grabyConfigBuilder; 44 $this->grabyConfigBuilder = $grabyConfigBuilder;
30 $this->credentials = $credentials; 45 $this->credentialRepository = $credentialRepository;
46 $this->logger = $logger;
47
48 if ($token->getToken()) {
49 $this->currentUser = $token->getToken()->getUser();
50 }
31 } 51 }
32 52
33 /** 53 /**
34 * Builds the SiteConfig for a host. 54 * {@inheritdoc}
35 *
36 * @param string $host The "www." prefix is ignored
37 *
38 * @return SiteConfig
39 *
40 * @throws OutOfRangeException If there is no config for $host
41 */ 55 */
42 public function buildForHost($host) 56 public function buildForHost($host)
43 { 57 {
@@ -47,6 +61,17 @@ class GrabySiteConfigBuilder implements SiteConfigBuilder
47 $host = substr($host, 4); 61 $host = substr($host, 4);
48 } 62 }
49 63
64 $credentials = null;
65 if ($this->currentUser) {
66 $credentials = $this->credentialRepository->findOneByHostAndUser($host, $this->currentUser->getId());
67 }
68
69 if (null === $credentials) {
70 $this->logger->debug('Auth: no credentials available for host.', ['host' => $host]);
71
72 return false;
73 }
74
50 $config = $this->grabyConfigBuilder->buildForHost($host); 75 $config = $this->grabyConfigBuilder->buildForHost($host);
51 $parameters = [ 76 $parameters = [
52 'host' => $host, 77 'host' => $host,
@@ -54,15 +79,47 @@ class GrabySiteConfigBuilder implements SiteConfigBuilder
54 'loginUri' => $config->login_uri ?: null, 79 'loginUri' => $config->login_uri ?: null,
55 'usernameField' => $config->login_username_field ?: null, 80 'usernameField' => $config->login_username_field ?: null,
56 'passwordField' => $config->login_password_field ?: null, 81 'passwordField' => $config->login_password_field ?: null,
57 'extraFields' => is_array($config->login_extra_fields) ? $config->login_extra_fields : [], 82 'extraFields' => $this->processExtraFields($config->login_extra_fields),
58 'notLoggedInXpath' => $config->not_logged_in_xpath ?: null, 83 'notLoggedInXpath' => $config->not_logged_in_xpath ?: null,
84 'username' => $credentials['username'],
85 'password' => $credentials['password'],
59 ]; 86 ];
60 87
61 if (isset($this->credentials[$host])) { 88 $config = new SiteConfig($parameters);
62 $parameters['username'] = $this->credentials[$host]['username']; 89
63 $parameters['password'] = $this->credentials[$host]['password']; 90 // do not leak usernames and passwords in log
91 $parameters['username'] = '**masked**';
92 $parameters['password'] = '**masked**';
93
94 $this->logger->debug('Auth: add parameters.', ['host' => $host, 'parameters' => $parameters]);
95
96 return $config;
97 }
98
99 /**
100 * Processes login_extra_fields config, transforming an '=' separated array of strings
101 * into a key/value array.
102 *
103 * @param array|mixed $extraFieldsStrings
104 *
105 * @return array
106 */
107 protected function processExtraFields($extraFieldsStrings)
108 {
109 if (!is_array($extraFieldsStrings)) {
110 return [];
111 }
112
113 $extraFields = [];
114 foreach ($extraFieldsStrings as $extraField) {
115 if (strpos($extraField, '=') === false) {
116 continue;
117 }
118
119 list($fieldName, $fieldValue) = explode('=', $extraField, 2);
120 $extraFields[$fieldName] = $fieldValue;
64 } 121 }
65 122
66 return new SiteConfig($parameters); 123 return $extraFields;
67 } 124 }
68} 125}
diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php
index f222dd88..51bb2ca2 100644
--- a/src/Wallabag/CoreBundle/Helper/ContentProxy.php
+++ b/src/Wallabag/CoreBundle/Helper/ContentProxy.php
@@ -5,10 +5,11 @@ namespace Wallabag\CoreBundle\Helper;
5use Graby\Graby; 5use Graby\Graby;
6use Psr\Log\LoggerInterface; 6use Psr\Log\LoggerInterface;
7use Wallabag\CoreBundle\Entity\Entry; 7use Wallabag\CoreBundle\Entity\Entry;
8use Wallabag\CoreBundle\Entity\Tag;
9use Wallabag\CoreBundle\Tools\Utils; 8use Wallabag\CoreBundle\Tools\Utils;
10use Wallabag\CoreBundle\Repository\TagRepository;
11use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeExtensionGuesser; 9use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeExtensionGuesser;
10use Symfony\Component\Validator\Constraints\Locale as LocaleConstraint;
11use Symfony\Component\Validator\Constraints\Url as UrlConstraint;
12use Symfony\Component\Validator\Validator\ValidatorInterface;
12 13
13/** 14/**
14 * This kind of proxy class take care of getting the content from an url 15 * This kind of proxy class take care of getting the content from an url
@@ -18,38 +19,37 @@ class ContentProxy
18{ 19{
19 protected $graby; 20 protected $graby;
20 protected $tagger; 21 protected $tagger;
22 protected $validator;
21 protected $logger; 23 protected $logger;
22 protected $tagRepository;
23 protected $mimeGuesser; 24 protected $mimeGuesser;
24 protected $fetchingErrorMessage; 25 protected $fetchingErrorMessage;
26 protected $eventDispatcher;
25 27
26 public function __construct(Graby $graby, RuleBasedTagger $tagger, TagRepository $tagRepository, LoggerInterface $logger, $fetchingErrorMessage) 28 public function __construct(Graby $graby, RuleBasedTagger $tagger, ValidatorInterface $validator, LoggerInterface $logger, $fetchingErrorMessage)
27 { 29 {
28 $this->graby = $graby; 30 $this->graby = $graby;
29 $this->tagger = $tagger; 31 $this->tagger = $tagger;
32 $this->validator = $validator;
30 $this->logger = $logger; 33 $this->logger = $logger;
31 $this->tagRepository = $tagRepository;
32 $this->mimeGuesser = new MimeTypeExtensionGuesser(); 34 $this->mimeGuesser = new MimeTypeExtensionGuesser();
33 $this->fetchingErrorMessage = $fetchingErrorMessage; 35 $this->fetchingErrorMessage = $fetchingErrorMessage;
34 } 36 }
35 37
36 /** 38 /**
37 * Fetch content using graby and hydrate given entry with results information. 39 * Update entry using either fetched or provided content.
38 * In case we couldn't find content, we'll try to use Open Graph data.
39 * 40 *
40 * We can also force the content, in case of an import from the v1 for example, so the function won't 41 * @param Entry $entry Entry to update
41 * fetch the content from the website but rather use information given with the $content parameter. 42 * @param string $url Url of the content
42 * 43 * @param array $content Array with content provided for import with AT LEAST keys title, html, url to skip the fetchContent from the url
43 * @param Entry $entry Entry to update 44 * @param bool $disableContentUpdate Whether to skip trying to fetch content using Graby
44 * @param string $url Url to grab content for
45 * @param array $content An array with AT LEAST keys title, html, url, language & content_type to skip the fetchContent from the url
46 *
47 * @return Entry
48 */ 45 */
49 public function updateEntry(Entry $entry, $url, array $content = []) 46 public function updateEntry(Entry $entry, $url, array $content = [], $disableContentUpdate = false)
50 { 47 {
51 // do we have to fetch the content or the provided one is ok? 48 if (!empty($content['html'])) {
52 if (empty($content) || false === $this->validateContent($content)) { 49 $content['html'] = $this->graby->cleanupHtml($content['html'], $url);
50 }
51
52 if ((empty($content) || false === $this->validateContent($content)) && false === $disableContentUpdate) {
53 $fetchedContent = $this->graby->fetchContent($url); 53 $fetchedContent = $this->graby->fetchContent($url);
54 54
55 // when content is imported, we have information in $content 55 // when content is imported, we have information in $content
@@ -59,8 +59,24 @@ class ContentProxy
59 } 59 }
60 } 60 }
61 61
62 // be sure to keep the url in case of error
63 // so we'll be able to refetch it in the future
64 $content['url'] = !empty($content['url']) ? $content['url'] : $url;
65
66 $this->stockEntry($entry, $content);
67 }
68
69 /**
70 * Stock entry with fetched or imported content.
71 * Will fall back to OpenGraph data if available.
72 *
73 * @param Entry $entry Entry to stock
74 * @param array $content Array with at least title, url & html
75 */
76 private function stockEntry(Entry $entry, array $content)
77 {
62 $title = $content['title']; 78 $title = $content['title'];
63 if (!$title && isset($content['open_graph']['og_title'])) { 79 if (!$title && !empty($content['open_graph']['og_title'])) {
64 $title = $content['open_graph']['og_title']; 80 $title = $content['open_graph']['og_title'];
65 } 81 }
66 82
@@ -68,18 +84,58 @@ class ContentProxy
68 if (false === $html) { 84 if (false === $html) {
69 $html = $this->fetchingErrorMessage; 85 $html = $this->fetchingErrorMessage;
70 86
71 if (isset($content['open_graph']['og_description'])) { 87 if (!empty($content['open_graph']['og_description'])) {
72 $html .= '<p><i>But we found a short description: </i></p>'; 88 $html .= '<p><i>But we found a short description: </i></p>';
73 $html .= $content['open_graph']['og_description']; 89 $html .= $content['open_graph']['og_description'];
74 } 90 }
75 } 91 }
76 92
77 $entry->setUrl($content['url'] ?: $url); 93 $entry->setUrl($content['url']);
78 $entry->setTitle($title); 94 $entry->setTitle($title);
79 $entry->setContent($html); 95 $entry->setContent($html);
80 $entry->setHttpStatus(isset($content['status']) ? $content['status'] : ''); 96 $entry->setHttpStatus(isset($content['status']) ? $content['status'] : '');
81 97
82 $entry->setLanguage(isset($content['language']) ? $content['language'] : ''); 98 if (!empty($content['date'])) {
99 $date = $content['date'];
100
101 // is it a timestamp?
102 if (filter_var($date, FILTER_VALIDATE_INT) !== false) {
103 $date = '@'.$content['date'];
104 }
105
106 try {
107 $entry->setPublishedAt(new \DateTime($date));
108 } catch (\Exception $e) {
109 $this->logger->warning('Error while defining date', ['e' => $e, 'url' => $content['url'], 'date' => $content['date']]);
110 }
111 }
112
113 if (!empty($content['authors']) && is_array($content['authors'])) {
114 $entry->setPublishedBy($content['authors']);
115 }
116
117 if (!empty($content['all_headers'])) {
118 $entry->setHeaders($content['all_headers']);
119 }
120
121 $this->validateAndSetLanguage(
122 $entry,
123 isset($content['language']) ? $content['language'] : null
124 );
125
126 $this->validateAndSetPreviewPicture(
127 $entry,
128 isset($content['open_graph']['og_image']) ? $content['open_graph']['og_image'] : null
129 );
130
131 // if content is an image, define it as a preview too
132 if (!empty($content['content_type']) && in_array($this->mimeGuesser->guess($content['content_type']), ['jpeg', 'jpg', 'gif', 'png'], true)) {
133 $this->validateAndSetPreviewPicture(
134 $entry,
135 $content['url']
136 );
137 }
138
83 $entry->setMimetype(isset($content['content_type']) ? $content['content_type'] : ''); 139 $entry->setMimetype(isset($content['content_type']) ? $content['content_type'] : '');
84 $entry->setReadingTime(Utils::getReadingTime($html)); 140 $entry->setReadingTime(Utils::getReadingTime($html));
85 141
@@ -88,85 +144,73 @@ class ContentProxy
88 $entry->setDomainName($domainName); 144 $entry->setDomainName($domainName);
89 } 145 }
90 146
91 if (isset($content['open_graph']['og_image']) && $content['open_graph']['og_image']) {
92 $entry->setPreviewPicture($content['open_graph']['og_image']);
93 }
94
95 // if content is an image define as a preview too
96 if (isset($content['content_type']) && in_array($this->mimeGuesser->guess($content['content_type']), ['jpeg', 'jpg', 'gif', 'png'], true)) {
97 $entry->setPreviewPicture($content['url']);
98 }
99
100 try { 147 try {
101 $this->tagger->tag($entry); 148 $this->tagger->tag($entry);
102 } catch (\Exception $e) { 149 } catch (\Exception $e) {
103 $this->logger->error('Error while trying to automatically tag an entry.', [ 150 $this->logger->error('Error while trying to automatically tag an entry.', [
104 'entry_url' => $url, 151 'entry_url' => $content['url'],
105 'error_msg' => $e->getMessage(), 152 'error_msg' => $e->getMessage(),
106 ]); 153 ]);
107 } 154 }
108
109 return $entry;
110 } 155 }
111 156
112 /** 157 /**
113 * Assign some tags to an entry. 158 * Validate that the given content has at least a title, an html and a url.
159 *
160 * @param array $content
114 * 161 *
115 * @param Entry $entry 162 * @return bool true if valid otherwise false
116 * @param array|string $tags An array of tag or a string coma separated of tag
117 * @param array $entitiesReady Entities from the EntityManager which are persisted but not yet flushed
118 * It is mostly to fix duplicate tag on import @see http://stackoverflow.com/a/7879164/569101
119 */ 163 */
120 public function assignTagsToEntry(Entry $entry, $tags, array $entitiesReady = []) 164 private function validateContent(array $content)
121 { 165 {
122 if (!is_array($tags)) { 166 return !empty($content['title']) && !empty($content['html']) && !empty($content['url']);
123 $tags = explode(',', $tags); 167 }
124 }
125
126 // keeps only Tag entity from the "not yet flushed entities"
127 $tagsNotYetFlushed = [];
128 foreach ($entitiesReady as $entity) {
129 if ($entity instanceof Tag) {
130 $tagsNotYetFlushed[$entity->getLabel()] = $entity;
131 }
132 }
133
134 foreach ($tags as $label) {
135 $label = trim($label);
136 168
137 // avoid empty tag 169 /**
138 if (0 === strlen($label)) { 170 * Use a Symfony validator to ensure the language is well formatted.
139 continue; 171 *
140 } 172 * @param Entry $entry
173 * @param string $value Language to validate
174 */
175 private function validateAndSetLanguage($entry, $value)
176 {
177 // some lang are defined as fr-FR, es-ES.
178 // replacing - by _ might increase language support
179 $value = str_replace('-', '_', $value);
141 180
142 if (isset($tagsNotYetFlushed[$label])) { 181 $errors = $this->validator->validate(
143 $tagEntity = $tagsNotYetFlushed[$label]; 182 $value,
144 } else { 183 (new LocaleConstraint())
145 $tagEntity = $this->tagRepository->findOneByLabel($label); 184 );
146 185
147 if (is_null($tagEntity)) { 186 if (0 === count($errors)) {
148 $tagEntity = new Tag(); 187 $entry->setLanguage($value);
149 $tagEntity->setLabel($label);
150 }
151 }
152 188
153 // only add the tag on the entry if the relation doesn't exist 189 return;
154 if (false === $entry->getTags()->contains($tagEntity)) {
155 $entry->addTag($tagEntity);
156 }
157 } 190 }
191
192 $this->logger->warning('Language validation failed. '.(string) $errors);
158 } 193 }
159 194
160 /** 195 /**
161 * Validate that the given content as enough value to be used 196 * Use a Symfony validator to ensure the preview picture is a real url.
162 * instead of fetch the content from the url.
163 *
164 * @param array $content
165 * 197 *
166 * @return bool true if valid otherwise false 198 * @param Entry $entry
199 * @param string $value URL to validate
167 */ 200 */
168 private function validateContent(array $content) 201 private function validateAndSetPreviewPicture($entry, $value)
169 { 202 {
170 return isset($content['title']) && isset($content['html']) && isset($content['url']) && isset($content['language']) && isset($content['content_type']); 203 $errors = $this->validator->validate(
204 $value,
205 (new UrlConstraint())
206 );
207
208 if (0 === count($errors)) {
209 $entry->setPreviewPicture($value);
210
211 return;
212 }
213
214 $this->logger->warning('PreviewPicture validation failed. '.(string) $errors);
171 } 215 }
172} 216}
diff --git a/src/Wallabag/CoreBundle/Helper/CryptoProxy.php b/src/Wallabag/CoreBundle/Helper/CryptoProxy.php
new file mode 100644
index 00000000..e8b19cb9
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Helper/CryptoProxy.php
@@ -0,0 +1,86 @@
1<?php
2
3namespace Wallabag\CoreBundle\Helper;
4
5use Psr\Log\LoggerInterface;
6use Defuse\Crypto\Key;
7use Defuse\Crypto\Crypto;
8use Defuse\Crypto\Exception\WrongKeyOrModifiedCiphertextException;
9
10/**
11 * This is a proxy to crypt and decrypt password used by SiteCredential entity.
12 * BTW, It might be re-use for sth else.
13 */
14class CryptoProxy
15{
16 private $logger;
17 private $encryptionKey;
18
19 public function __construct($encryptionKeyPath, LoggerInterface $logger)
20 {
21 $this->logger = $logger;
22
23 if (!file_exists($encryptionKeyPath)) {
24 $key = Key::createNewRandomKey();
25
26 file_put_contents($encryptionKeyPath, $key->saveToAsciiSafeString());
27 chmod($encryptionKeyPath, 0600);
28 }
29
30 $this->encryptionKey = file_get_contents($encryptionKeyPath);
31 }
32
33 /**
34 * Ensure the given value will be crypted.
35 *
36 * @param string $secretValue Secret valye to crypt
37 *
38 * @return string
39 */
40 public function crypt($secretValue)
41 {
42 $this->logger->debug('Crypto: crypting value: '.$this->mask($secretValue));
43
44 return Crypto::encrypt($secretValue, $this->loadKey());
45 }
46
47 /**
48 * Ensure the given crypted value will be decrypted.
49 *
50 * @param string $cryptedValue The value to be decrypted
51 *
52 * @return string
53 */
54 public function decrypt($cryptedValue)
55 {
56 $this->logger->debug('Crypto: decrypting value: '.$this->mask($cryptedValue));
57
58 try {
59 return Crypto::decrypt($cryptedValue, $this->loadKey());
60 } catch (WrongKeyOrModifiedCiphertextException $e) {
61 throw new \RuntimeException('Decrypt fail: '.$e->getMessage());
62 }
63 }
64
65 /**
66 * Load the private key.
67 *
68 * @return Key
69 */
70 private function loadKey()
71 {
72 return Key::loadFromAsciiSafeString($this->encryptionKey);
73 }
74
75 /**
76 * Keep first and last character and put some stars in between.
77 *
78 * @param string $value Value to mask
79 *
80 * @return string
81 */
82 private function mask($value)
83 {
84 return strlen($value) > 0 ? $value[0].'*****'.$value[strlen($value) - 1] : 'Empty value';
85 }
86}
diff --git a/src/Wallabag/CoreBundle/Helper/DownloadImages.php b/src/Wallabag/CoreBundle/Helper/DownloadImages.php
index 0d330d2a..ed888cdb 100644
--- a/src/Wallabag/CoreBundle/Helper/DownloadImages.php
+++ b/src/Wallabag/CoreBundle/Helper/DownloadImages.php
@@ -5,6 +5,7 @@ namespace Wallabag\CoreBundle\Helper;
5use Psr\Log\LoggerInterface; 5use Psr\Log\LoggerInterface;
6use Symfony\Component\DomCrawler\Crawler; 6use Symfony\Component\DomCrawler\Crawler;
7use GuzzleHttp\Client; 7use GuzzleHttp\Client;
8use GuzzleHttp\Message\Response;
8use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeExtensionGuesser; 9use Symfony\Component\HttpFoundation\File\MimeType\MimeTypeExtensionGuesser;
9use Symfony\Component\Finder\Finder; 10use Symfony\Component\Finder\Finder;
10 11
@@ -54,7 +55,7 @@ class DownloadImages
54 $crawler = new Crawler($html); 55 $crawler = new Crawler($html);
55 $result = $crawler 56 $result = $crawler
56 ->filterXpath('//img') 57 ->filterXpath('//img')
57 ->extract(array('src')); 58 ->extract(['src']);
58 59
59 $relativePath = $this->getRelativePath($entryId); 60 $relativePath = $this->getRelativePath($entryId);
60 61
@@ -66,6 +67,11 @@ class DownloadImages
66 continue; 67 continue;
67 } 68 }
68 69
70 // if image contains "&" and we can't find it in the html it might be because it's encoded as &amp;
71 if (false !== stripos($image, '&') && false === stripos($html, $image)) {
72 $image = str_replace('&', '&amp;', $image);
73 }
74
69 $html = str_replace($image, $imagePath, $html); 75 $html = str_replace($image, $imagePath, $html);
70 } 76 }
71 77
@@ -111,13 +117,11 @@ class DownloadImages
111 return false; 117 return false;
112 } 118 }
113 119
114 $ext = $this->mimeGuesser->guess($res->getHeader('content-type')); 120 $ext = $this->getExtensionFromResponse($res, $imagePath);
115 $this->logger->debug('DownloadImages: Checking extension', ['ext' => $ext, 'header' => $res->getHeader('content-type')]); 121 if (false === $res) {
116 if (!in_array($ext, ['jpeg', 'jpg', 'gif', 'png'], true)) {
117 $this->logger->error('DownloadImages: Processed image with not allowed extension. Skipping '.$imagePath);
118
119 return false; 122 return false;
120 } 123 }
124
121 $hashImage = hash('crc32', $absolutePath); 125 $hashImage = hash('crc32', $absolutePath);
122 $localPath = $folderPath.'/'.$hashImage.'.'.$ext; 126 $localPath = $folderPath.'/'.$hashImage.'.'.$ext;
123 127
@@ -232,4 +236,45 @@ class DownloadImages
232 236
233 return false; 237 return false;
234 } 238 }
239
240 /**
241 * Retrieve and validate the extension from the response of the url of the image.
242 *
243 * @param Response $res Guzzle Response
244 * @param string $imagePath Path from the src image from the content (used for log only)
245 *
246 * @return string|false Extension name or false if validation failed
247 */
248 private function getExtensionFromResponse(Response $res, $imagePath)
249 {
250 $ext = $this->mimeGuesser->guess($res->getHeader('content-type'));
251 $this->logger->debug('DownloadImages: Checking extension', ['ext' => $ext, 'header' => $res->getHeader('content-type')]);
252
253 // ok header doesn't have the extension, try a different way
254 if (empty($ext)) {
255 $types = [
256 'jpeg' => "\xFF\xD8\xFF",
257 'gif' => 'GIF',
258 'png' => "\x89\x50\x4e\x47\x0d\x0a",
259 ];
260 $bytes = substr((string) $res->getBody(), 0, 8);
261
262 foreach ($types as $type => $header) {
263 if (0 === strpos($bytes, $header)) {
264 $ext = $type;
265 break;
266 }
267 }
268
269 $this->logger->debug('DownloadImages: Checking extension (alternative)', ['ext' => $ext]);
270 }
271
272 if (!in_array($ext, ['jpeg', 'jpg', 'gif', 'png'], true)) {
273 $this->logger->error('DownloadImages: Processed image with not allowed extension. Skipping: '.$imagePath);
274
275 return false;
276 }
277
278 return $ext;
279 }
235} 280}
diff --git a/src/Wallabag/CoreBundle/Helper/HttpClientFactory.php b/src/Wallabag/CoreBundle/Helper/HttpClientFactory.php
index 1ac8feb1..43f5b119 100644
--- a/src/Wallabag/CoreBundle/Helper/HttpClientFactory.php
+++ b/src/Wallabag/CoreBundle/Helper/HttpClientFactory.php
@@ -13,8 +13,8 @@ use Psr\Log\LoggerInterface;
13 */ 13 */
14class HttpClientFactory 14class HttpClientFactory
15{ 15{
16 /** @var \GuzzleHttp\Event\SubscriberInterface */ 16 /** @var [\GuzzleHttp\Event\SubscriberInterface] */
17 private $authenticatorSubscriber; 17 private $subscribers = [];
18 18
19 /** @var \GuzzleHttp\Cookie\CookieJar */ 19 /** @var \GuzzleHttp\Cookie\CookieJar */
20 private $cookieJar; 20 private $cookieJar;
@@ -25,14 +25,12 @@ class HttpClientFactory
25 /** 25 /**
26 * HttpClientFactory constructor. 26 * HttpClientFactory constructor.
27 * 27 *
28 * @param \GuzzleHttp\Event\SubscriberInterface $authenticatorSubscriber 28 * @param \GuzzleHttp\Cookie\CookieJar $cookieJar
29 * @param \GuzzleHttp\Cookie\CookieJar $cookieJar 29 * @param string $restrictedAccess This param is a kind of boolean. Values: 0 or 1
30 * @param string $restrictedAccess this param is a kind of boolean. Values: 0 or 1 30 * @param LoggerInterface $logger
31 * @param LoggerInterface $logger
32 */ 31 */
33 public function __construct(SubscriberInterface $authenticatorSubscriber, CookieJar $cookieJar, $restrictedAccess, LoggerInterface $logger) 32 public function __construct(CookieJar $cookieJar, $restrictedAccess, LoggerInterface $logger)
34 { 33 {
35 $this->authenticatorSubscriber = $authenticatorSubscriber;
36 $this->cookieJar = $cookieJar; 34 $this->cookieJar = $cookieJar;
37 $this->restrictedAccess = $restrictedAccess; 35 $this->restrictedAccess = $restrictedAccess;
38 $this->logger = $logger; 36 $this->logger = $logger;
@@ -53,8 +51,21 @@ class HttpClientFactory
53 $this->cookieJar->clear(); 51 $this->cookieJar->clear();
54 // need to set the (shared) cookie jar 52 // need to set the (shared) cookie jar
55 $client = new Client(['handler' => new SafeCurlHandler(), 'defaults' => ['cookies' => $this->cookieJar]]); 53 $client = new Client(['handler' => new SafeCurlHandler(), 'defaults' => ['cookies' => $this->cookieJar]]);
56 $client->getEmitter()->attach($this->authenticatorSubscriber); 54
55 foreach ($this->subscribers as $subscriber) {
56 $client->getEmitter()->attach($subscriber);
57 }
57 58
58 return $client; 59 return $client;
59 } 60 }
61
62 /**
63 * Adds a subscriber to the HTTP client.
64 *
65 * @param SubscriberInterface $subscriber
66 */
67 public function addSubscriber(SubscriberInterface $subscriber)
68 {
69 $this->subscribers[] = $subscriber;
70 }
60} 71}
diff --git a/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php b/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php
index 7d3798b9..231a0b52 100644
--- a/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php
+++ b/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php
@@ -4,6 +4,7 @@ namespace Wallabag\CoreBundle\Helper;
4 4
5use Pagerfanta\Adapter\AdapterInterface; 5use Pagerfanta\Adapter\AdapterInterface;
6use Pagerfanta\Pagerfanta; 6use Pagerfanta\Pagerfanta;
7use Wallabag\UserBundle\Entity\User;
7use Symfony\Component\Routing\Router; 8use Symfony\Component\Routing\Router;
8use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface; 9use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
9 10
@@ -20,16 +21,18 @@ class PreparePagerForEntries
20 21
21 /** 22 /**
22 * @param AdapterInterface $adapter 23 * @param AdapterInterface $adapter
23 * @param int $page 24 * @param User $user If user isn't logged in, we can force it (like for rss)
24 * 25 *
25 * @return null|Pagerfanta 26 * @return null|Pagerfanta
26 */ 27 */
27 public function prepare(AdapterInterface $adapter, $page = 1) 28 public function prepare(AdapterInterface $adapter, User $user = null)
28 { 29 {
29 $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null; 30 if (null === $user) {
31 $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
32 }
30 33
31 if (null === $user || !is_object($user)) { 34 if (null === $user || !is_object($user)) {
32 return null; 35 return;
33 } 36 }
34 37
35 $entries = new Pagerfanta($adapter); 38 $entries = new Pagerfanta($adapter);
diff --git a/src/Wallabag/CoreBundle/Helper/Redirect.php b/src/Wallabag/CoreBundle/Helper/Redirect.php
index f78b7fe0..abc84d08 100644
--- a/src/Wallabag/CoreBundle/Helper/Redirect.php
+++ b/src/Wallabag/CoreBundle/Helper/Redirect.php
@@ -21,12 +21,13 @@ class Redirect
21 } 21 }
22 22
23 /** 23 /**
24 * @param string $url URL to redirect 24 * @param string $url URL to redirect
25 * @param string $fallback Fallback URL if $url is null 25 * @param string $fallback Fallback URL if $url is null
26 * @param bool $ignoreActionMarkAsRead Ignore configured action when mark as read
26 * 27 *
27 * @return string 28 * @return string
28 */ 29 */
29 public function to($url, $fallback = '') 30 public function to($url, $fallback = '', $ignoreActionMarkAsRead = false)
30 { 31 {
31 $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null; 32 $user = $this->tokenStorage->getToken() ? $this->tokenStorage->getToken()->getUser() : null;
32 33
@@ -34,7 +35,8 @@ class Redirect
34 return $url; 35 return $url;
35 } 36 }
36 37
37 if (Config::REDIRECT_TO_HOMEPAGE === $user->getConfig()->getActionMarkAsRead()) { 38 if (!$ignoreActionMarkAsRead &&
39 Config::REDIRECT_TO_HOMEPAGE === $user->getConfig()->getActionMarkAsRead()) {
38 return $this->router->generate('homepage'); 40 return $this->router->generate('homepage');
39 } 41 }
40 42
diff --git a/src/Wallabag/CoreBundle/Helper/RuleBasedTagger.php b/src/Wallabag/CoreBundle/Helper/RuleBasedTagger.php
index b490e209..509d0dec 100644
--- a/src/Wallabag/CoreBundle/Helper/RuleBasedTagger.php
+++ b/src/Wallabag/CoreBundle/Helper/RuleBasedTagger.php
@@ -8,18 +8,21 @@ use Wallabag\CoreBundle\Entity\Tag;
8use Wallabag\CoreBundle\Repository\EntryRepository; 8use Wallabag\CoreBundle\Repository\EntryRepository;
9use Wallabag\CoreBundle\Repository\TagRepository; 9use Wallabag\CoreBundle\Repository\TagRepository;
10use Wallabag\UserBundle\Entity\User; 10use Wallabag\UserBundle\Entity\User;
11use Psr\Log\LoggerInterface;
11 12
12class RuleBasedTagger 13class RuleBasedTagger
13{ 14{
14 private $rulerz; 15 private $rulerz;
15 private $tagRepository; 16 private $tagRepository;
16 private $entryRepository; 17 private $entryRepository;
18 private $logger;
17 19
18 public function __construct(RulerZ $rulerz, TagRepository $tagRepository, EntryRepository $entryRepository) 20 public function __construct(RulerZ $rulerz, TagRepository $tagRepository, EntryRepository $entryRepository, LoggerInterface $logger)
19 { 21 {
20 $this->rulerz = $rulerz; 22 $this->rulerz = $rulerz;
21 $this->tagRepository = $tagRepository; 23 $this->tagRepository = $tagRepository;
22 $this->entryRepository = $entryRepository; 24 $this->entryRepository = $entryRepository;
25 $this->logger = $logger;
23 } 26 }
24 27
25 /** 28 /**
@@ -36,6 +39,11 @@ class RuleBasedTagger
36 continue; 39 continue;
37 } 40 }
38 41
42 $this->logger->info('Matching rule.', [
43 'rule' => $rule->getRule(),
44 'tags' => $rule->getTags(),
45 ]);
46
39 foreach ($rule->getTags() as $label) { 47 foreach ($rule->getTags() as $label) {
40 $tag = $this->getTag($label); 48 $tag = $this->getTag($label);
41 49
diff --git a/src/Wallabag/CoreBundle/Helper/TagsAssigner.php b/src/Wallabag/CoreBundle/Helper/TagsAssigner.php
new file mode 100644
index 00000000..a2fb0b9a
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Helper/TagsAssigner.php
@@ -0,0 +1,75 @@
1<?php
2
3namespace Wallabag\CoreBundle\Helper;
4
5use Wallabag\CoreBundle\Entity\Entry;
6use Wallabag\CoreBundle\Entity\Tag;
7use Wallabag\CoreBundle\Repository\TagRepository;
8
9class TagsAssigner
10{
11 /**
12 * @var TagRepository
13 */
14 protected $tagRepository;
15
16 public function __construct(TagRepository $tagRepository)
17 {
18 $this->tagRepository = $tagRepository;
19 }
20
21 /**
22 * Assign some tags to an entry.
23 *
24 * @param Entry $entry
25 * @param array|string $tags An array of tag or a string coma separated of tag
26 * @param array $entitiesReady Entities from the EntityManager which are persisted but not yet flushed
27 * It is mostly to fix duplicate tag on import @see http://stackoverflow.com/a/7879164/569101
28 *
29 * @return Tag[]
30 */
31 public function assignTagsToEntry(Entry $entry, $tags, array $entitiesReady = [])
32 {
33 $tagsEntities = [];
34
35 if (!is_array($tags)) {
36 $tags = explode(',', $tags);
37 }
38
39 // keeps only Tag entity from the "not yet flushed entities"
40 $tagsNotYetFlushed = [];
41 foreach ($entitiesReady as $entity) {
42 if ($entity instanceof Tag) {
43 $tagsNotYetFlushed[$entity->getLabel()] = $entity;
44 }
45 }
46
47 foreach ($tags as $label) {
48 $label = trim($label);
49
50 // avoid empty tag
51 if (0 === strlen($label)) {
52 continue;
53 }
54
55 if (isset($tagsNotYetFlushed[$label])) {
56 $tagEntity = $tagsNotYetFlushed[$label];
57 } else {
58 $tagEntity = $this->tagRepository->findOneByLabel($label);
59
60 if (null === $tagEntity) {
61 $tagEntity = new Tag();
62 $tagEntity->setLabel($label);
63 }
64 }
65
66 // only add the tag on the entry if the relation doesn't exist
67 if (false === $entry->getTags()->contains($tagEntity)) {
68 $entry->addTag($tagEntity);
69 $tagsEntities[] = $tagEntity;
70 }
71 }
72
73 return $tagsEntities;
74 }
75}
diff --git a/src/Wallabag/CoreBundle/Operator/Doctrine/NotMatches.php b/src/Wallabag/CoreBundle/Operator/Doctrine/NotMatches.php
new file mode 100644
index 00000000..b7f9da57
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Operator/Doctrine/NotMatches.php
@@ -0,0 +1,25 @@
1<?php
2
3namespace Wallabag\CoreBundle\Operator\Doctrine;
4
5/**
6 * Provides a "notmatches" operator used for tagging rules.
7 *
8 * It asserts that a given pattern is not contained in a subject, in a
9 * case-insensitive way.
10 *
11 * This operator will be used to compile tagging rules in DQL, usable
12 * by Doctrine ORM.
13 * It's registered in RulerZ using a service (wallabag.operator.doctrine.notmatches);
14 */
15class NotMatches
16{
17 public function __invoke($subject, $pattern)
18 {
19 if ($pattern[0] === "'") {
20 $pattern = sprintf("'%%%s%%'", substr($pattern, 1, -1));
21 }
22
23 return sprintf('UPPER(%s) NOT LIKE UPPER(%s)', $subject, $pattern);
24 }
25}
diff --git a/src/Wallabag/CoreBundle/Operator/PHP/NotMatches.php b/src/Wallabag/CoreBundle/Operator/PHP/NotMatches.php
new file mode 100644
index 00000000..68b2676f
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Operator/PHP/NotMatches.php
@@ -0,0 +1,21 @@
1<?php
2
3namespace Wallabag\CoreBundle\Operator\PHP;
4
5/**
6 * Provides a "notmatches" operator used for tagging rules.
7 *
8 * It asserts that a given pattern is not contained in a subject, in a
9 * case-insensitive way.
10 *
11 * This operator will be used to compile tagging rules in PHP, usable
12 * directly on Entry objects for instance.
13 * It's registered in RulerZ using a service (wallabag.operator.array.notmatches);
14 */
15class NotMatches
16{
17 public function __invoke($subject, $pattern)
18 {
19 return stripos($subject, $pattern) === false;
20 }
21}
diff --git a/src/Wallabag/CoreBundle/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
index 4071301d..9bda4e15 100644
--- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
@@ -135,6 +135,7 @@ class EntryRepository extends EntityRepository
135 * @param int $userId 135 * @param int $userId
136 * @param bool $isArchived 136 * @param bool $isArchived
137 * @param bool $isStarred 137 * @param bool $isStarred
138 * @param bool $isPublic
138 * @param string $sort 139 * @param string $sort
139 * @param string $order 140 * @param string $order
140 * @param int $since 141 * @param int $since
@@ -142,18 +143,22 @@ class EntryRepository extends EntityRepository
142 * 143 *
143 * @return array 144 * @return array
144 */ 145 */
145 public function findEntries($userId, $isArchived = null, $isStarred = null, $sort = 'created', $order = 'ASC', $since = 0, $tags = '') 146 public function findEntries($userId, $isArchived = null, $isStarred = null, $isPublic = null, $sort = 'created', $order = 'ASC', $since = 0, $tags = '')
146 { 147 {
147 $qb = $this->createQueryBuilder('e') 148 $qb = $this->createQueryBuilder('e')
148 ->leftJoin('e.tags', 't') 149 ->leftJoin('e.tags', 't')
149 ->where('e.user =:userId')->setParameter('userId', $userId); 150 ->where('e.user =:userId')->setParameter('userId', $userId);
150 151
151 if (null !== $isArchived) { 152 if (null !== $isArchived) {
152 $qb->andWhere('e.isArchived =:isArchived')->setParameter('isArchived', (bool) $isArchived); 153 $qb->andWhere('e.isArchived = :isArchived')->setParameter('isArchived', (bool) $isArchived);
153 } 154 }
154 155
155 if (null !== $isStarred) { 156 if (null !== $isStarred) {
156 $qb->andWhere('e.isStarred =:isStarred')->setParameter('isStarred', (bool) $isStarred); 157 $qb->andWhere('e.isStarred = :isStarred')->setParameter('isStarred', (bool) $isStarred);
158 }
159
160 if (null !== $isPublic) {
161 $qb->andWhere('e.uid IS '.(true === $isPublic ? 'NOT' : '').' NULL');
157 } 162 }
158 163
159 if ($since > 0) { 164 if ($since > 0) {
@@ -328,7 +333,7 @@ class EntryRepository extends EntityRepository
328 * 333 *
329 * @return int 334 * @return int
330 */ 335 */
331 public function countAllEntriesByUsername($userId) 336 public function countAllEntriesByUser($userId)
332 { 337 {
333 $qb = $this->createQueryBuilder('e') 338 $qb = $this->createQueryBuilder('e')
334 ->select('count(e)') 339 ->select('count(e)')
@@ -371,4 +376,42 @@ class EntryRepository extends EntityRepository
371 ->setParameter('userId', $userId) 376 ->setParameter('userId', $userId)
372 ->execute(); 377 ->execute();
373 } 378 }
379
380 public function removeArchivedByUserId($userId)
381 {
382 $this->getEntityManager()
383 ->createQuery('DELETE FROM Wallabag\CoreBundle\Entity\Entry e WHERE e.user = :userId AND e.isArchived = TRUE')
384 ->setParameter('userId', $userId)
385 ->execute();
386 }
387
388 /**
389 * Get id and url from all entries
390 * Used for the clean-duplicates command.
391 */
392 public function getAllEntriesIdAndUrl($userId)
393 {
394 $qb = $this->createQueryBuilder('e')
395 ->select('e.id, e.url')
396 ->where('e.user = :userid')->setParameter(':userid', $userId);
397
398 return $qb->getQuery()->getArrayResult();
399 }
400
401 /**
402 * Find all entries by url and owner.
403 *
404 * @param $url
405 * @param $userId
406 *
407 * @return array
408 */
409 public function findAllByUrlAndUserId($url, $userId)
410 {
411 return $this->createQueryBuilder('e')
412 ->where('e.url = :url')->setParameter('url', urldecode($url))
413 ->andWhere('e.user = :user_id')->setParameter('user_id', $userId)
414 ->getQuery()
415 ->getResult();
416 }
374} 417}
diff --git a/src/Wallabag/CoreBundle/Repository/SiteCredentialRepository.php b/src/Wallabag/CoreBundle/Repository/SiteCredentialRepository.php
new file mode 100644
index 00000000..36906761
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Repository/SiteCredentialRepository.php
@@ -0,0 +1,47 @@
1<?php
2
3namespace Wallabag\CoreBundle\Repository;
4
5use Wallabag\CoreBundle\Helper\CryptoProxy;
6
7/**
8 * SiteCredentialRepository.
9 */
10class SiteCredentialRepository extends \Doctrine\ORM\EntityRepository
11{
12 private $cryptoProxy;
13
14 public function setCrypto(CryptoProxy $cryptoProxy)
15 {
16 $this->cryptoProxy = $cryptoProxy;
17 }
18
19 /**
20 * Retrieve one username/password for the given host and userId.
21 *
22 * @param string $host
23 * @param int $userId
24 *
25 * @return null|array
26 */
27 public function findOneByHostAndUser($host, $userId)
28 {
29 $res = $this->createQueryBuilder('s')
30 ->select('s.username', 's.password')
31 ->where('s.host = :hostname')->setParameter('hostname', $host)
32 ->andWhere('s.user = :userId')->setParameter('userId', $userId)
33 ->setMaxResults(1)
34 ->getQuery()
35 ->getOneOrNullResult();
36
37 if (null === $res) {
38 return;
39 }
40
41 // decrypt user & password before returning them
42 $res['username'] = $this->cryptoProxy->decrypt($res['username']);
43 $res['password'] = $this->cryptoProxy->decrypt($res['password']);
44
45 return $res;
46 }
47}
diff --git a/src/Wallabag/CoreBundle/Repository/TagRepository.php b/src/Wallabag/CoreBundle/Repository/TagRepository.php
index 2182df25..6c63a6a2 100644
--- a/src/Wallabag/CoreBundle/Repository/TagRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/TagRepository.php
@@ -76,4 +76,24 @@ class TagRepository extends EntityRepository
76 ->getQuery() 76 ->getQuery()
77 ->getSingleResult(); 77 ->getSingleResult();
78 } 78 }
79
80 public function findForArchivedArticlesByUser($userId)
81 {
82 $ids = $this->createQueryBuilder('t')
83 ->select('t.id')
84 ->leftJoin('t.entries', 'e')
85 ->where('e.user = :userId')->setParameter('userId', $userId)
86 ->andWhere('e.isArchived = true')
87 ->groupBy('t.id')
88 ->orderBy('t.slug')
89 ->getQuery()
90 ->getArrayResult();
91
92 $tags = [];
93 foreach ($ids as $id) {
94 $tags[] = $this->find($id);
95 }
96
97 return $tags;
98 }
79} 99}
diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml
index 51d6ab47..e09b0f18 100644
--- a/src/Wallabag/CoreBundle/Resources/config/services.yml
+++ b/src/Wallabag/CoreBundle/Resources/config/services.yml
@@ -41,6 +41,7 @@ services:
41 arguments: 41 arguments:
42 - 42 -
43 error_message: '%wallabag_core.fetching_error_message%' 43 error_message: '%wallabag_core.fetching_error_message%'
44 error_message_title: '%wallabag_core.fetching_error_message_title%'
44 - "@wallabag_core.guzzle.http_client" 45 - "@wallabag_core.guzzle.http_client"
45 - "@wallabag_core.graby.config_builder" 46 - "@wallabag_core.graby.config_builder"
46 calls: 47 calls:
@@ -62,7 +63,11 @@ services:
62 class: Wallabag\CoreBundle\GuzzleSiteAuthenticator\GrabySiteConfigBuilder 63 class: Wallabag\CoreBundle\GuzzleSiteAuthenticator\GrabySiteConfigBuilder
63 arguments: 64 arguments:
64 - "@wallabag_core.graby.config_builder" 65 - "@wallabag_core.graby.config_builder"
65 - "%sites_credentials%" 66 - "@security.token_storage"
67 - "@wallabag_core.site_credential_repository"
68 - '@logger'
69 tags:
70 - { name: monolog.logger, channel: graby }
66 71
67 # service alias override 72 # service alias override
68 bd_guzzle_site_authenticator.site_config_builder: 73 bd_guzzle_site_authenticator.site_config_builder:
@@ -71,10 +76,11 @@ services:
71 wallabag_core.guzzle.http_client_factory: 76 wallabag_core.guzzle.http_client_factory:
72 class: Wallabag\CoreBundle\Helper\HttpClientFactory 77 class: Wallabag\CoreBundle\Helper\HttpClientFactory
73 arguments: 78 arguments:
74 - "@bd_guzzle_site_authenticator.authenticator_subscriber"
75 - "@wallabag_core.guzzle.cookie_jar" 79 - "@wallabag_core.guzzle.cookie_jar"
76 - '@=service(''craue_config'').get(''restricted_access'')' 80 - '@=service(''craue_config'').get(''restricted_access'')'
77 - '@logger' 81 - '@logger'
82 calls:
83 - ["addSubscriber", ["@bd_guzzle_site_authenticator.authenticator_subscriber"]]
78 84
79 wallabag_core.guzzle.cookie_jar: 85 wallabag_core.guzzle.cookie_jar:
80 class: GuzzleHttp\Cookie\FileCookieJar 86 class: GuzzleHttp\Cookie\FileCookieJar
@@ -85,16 +91,22 @@ services:
85 arguments: 91 arguments:
86 - "@wallabag_core.graby" 92 - "@wallabag_core.graby"
87 - "@wallabag_core.rule_based_tagger" 93 - "@wallabag_core.rule_based_tagger"
88 - "@wallabag_core.tag_repository" 94 - "@validator"
89 - "@logger" 95 - "@logger"
90 - '%wallabag_core.fetching_error_message%' 96 - '%wallabag_core.fetching_error_message%'
91 97
98 wallabag_core.tags_assigner:
99 class: Wallabag\CoreBundle\Helper\TagsAssigner
100 arguments:
101 - "@wallabag_core.tag_repository"
102
92 wallabag_core.rule_based_tagger: 103 wallabag_core.rule_based_tagger:
93 class: Wallabag\CoreBundle\Helper\RuleBasedTagger 104 class: Wallabag\CoreBundle\Helper\RuleBasedTagger
94 arguments: 105 arguments:
95 - "@rulerz" 106 - "@rulerz"
96 - "@wallabag_core.tag_repository" 107 - "@wallabag_core.tag_repository"
97 - "@wallabag_core.entry_repository" 108 - "@wallabag_core.entry_repository"
109 - "@logger"
98 110
99 # repository as a service 111 # repository as a service
100 wallabag_core.entry_repository: 112 wallabag_core.entry_repository:
@@ -109,10 +121,18 @@ services:
109 arguments: 121 arguments:
110 - WallabagCoreBundle:Tag 122 - WallabagCoreBundle:Tag
111 123
124 wallabag_core.site_credential_repository:
125 class: Wallabag\CoreBundle\Repository\SiteCredentialRepository
126 factory: [ "@doctrine.orm.default_entity_manager", getRepository ]
127 arguments:
128 - WallabagCoreBundle:SiteCredential
129 calls:
130 - [ setCrypto, [ "@wallabag_core.helper.crypto_proxy" ] ]
131
112 wallabag_core.helper.entries_export: 132 wallabag_core.helper.entries_export:
113 class: Wallabag\CoreBundle\Helper\EntriesExport 133 class: Wallabag\CoreBundle\Helper\EntriesExport
114 arguments: 134 arguments:
115 - '@=service(''craue_config'').get(''wallabag_url'')' 135 - '%domain_name%'
116 - src/Wallabag/CoreBundle/Resources/public/themes/_global/img/appicon/apple-touch-icon-152.png 136 - src/Wallabag/CoreBundle/Resources/public/themes/_global/img/appicon/apple-touch-icon-152.png
117 137
118 wallabag.operator.array.matches: 138 wallabag.operator.array.matches:
@@ -125,6 +145,16 @@ services:
125 tags: 145 tags:
126 - { name: rulerz.operator, target: doctrine, operator: matches, inline: true } 146 - { name: rulerz.operator, target: doctrine, operator: matches, inline: true }
127 147
148 wallabag.operator.array.notmatches:
149 class: Wallabag\CoreBundle\Operator\PHP\NotMatches
150 tags:
151 - { name: rulerz.operator, target: native, operator: notmatches }
152
153 wallabag.operator.doctrine.notmatches:
154 class: Wallabag\CoreBundle\Operator\Doctrine\NotMatches
155 tags:
156 - { name: rulerz.operator, target: doctrine, operator: notmatches, inline: true }
157
128 wallabag_core.helper.redirect: 158 wallabag_core.helper.redirect:
129 class: Wallabag\CoreBundle\Helper\Redirect 159 class: Wallabag\CoreBundle\Helper\Redirect
130 arguments: 160 arguments:
@@ -175,8 +205,14 @@ services:
175 arguments: 205 arguments:
176 - "@wallabag_core.entry.download_images.client" 206 - "@wallabag_core.entry.download_images.client"
177 - "%kernel.root_dir%/../web/assets/images" 207 - "%kernel.root_dir%/../web/assets/images"
178 - '@=service(''craue_config'').get(''wallabag_url'')' 208 - '%domain_name%'
179 - "@logger" 209 - "@logger"
180 210
181 wallabag_core.entry.download_images.client: 211 wallabag_core.entry.download_images.client:
182 class: GuzzleHttp\Client 212 class: GuzzleHttp\Client
213
214 wallabag_core.helper.crypto_proxy:
215 class: Wallabag\CoreBundle\Helper\CryptoProxy
216 arguments:
217 - "%wallabag_core.site_credentials.encryption_key_path%"
218 - "@logger"
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
index a52b579a..5229ac73 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
@@ -32,6 +32,7 @@ menu:
32 # save_link: 'Save a link' 32 # save_link: 'Save a link'
33 back_to_unread: 'Tilbage til de ulæste artikler' 33 back_to_unread: 'Tilbage til de ulæste artikler'
34 # users_management: 'Users management' 34 # users_management: 'Users management'
35 # site_credentials: 'Site credentials'
35 top: 36 top:
36 add_new_entry: 'Tilføj ny artikel' 37 add_new_entry: 'Tilføj ny artikel'
37 search: 'Søg' 38 search: 'Søg'
@@ -76,6 +77,7 @@ config:
76 # redirect_current_page: 'To the current page' 77 # redirect_current_page: 'To the current page'
77 pocket_consumer_key_label: Brugers nøgle til Pocket for at importere materialer 78 pocket_consumer_key_label: Brugers nøgle til Pocket for at importere materialer
78 # android_configuration: Configure your Android application 79 # android_configuration: Configure your Android application
80 # android_instruction: "Touch here to prefill your Android application"
79 # help_theme: "wallabag is customizable. You can choose your prefered theme here." 81 # help_theme: "wallabag is customizable. You can choose your prefered theme here."
80 # help_items_per_page: "You can change the number of articles displayed on each page." 82 # help_items_per_page: "You can change the number of articles displayed on each page."
81 # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article." 83 # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article."
@@ -89,9 +91,10 @@ config:
89 token_reset: 'Nulstil token' 91 token_reset: 'Nulstil token'
90 rss_links: 'RSS-Links' 92 rss_links: 'RSS-Links'
91 rss_link: 93 rss_link:
92 unread: 'ulæst' 94 unread: 'Ulæst'
93 starred: 'favoritter' 95 starred: 'Favoritter'
94 archive: 'arkiv' 96 archive: 'Arkiv'
97 # all: 'All'
95 # rss_limit: 'Number of items in the feed' 98 # rss_limit: 'Number of items in the feed'
96 form_user: 99 form_user:
97 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion" 100 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion"
@@ -110,6 +113,7 @@ config:
110 # annotations: Remove ALL annotations 113 # annotations: Remove ALL annotations
111 # tags: Remove ALL tags 114 # tags: Remove ALL tags
112 # entries: Remove ALL entries 115 # entries: Remove ALL entries
116 # archived: Remove ALL archived entries
113 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE) 117 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
114 form_password: 118 form_password:
115 # description: "You can change your password here. Your new password should by at least 8 characters long." 119 # description: "You can change your password here. Your new password should by at least 8 characters long."
@@ -154,6 +158,7 @@ config:
154 # or: 'One rule OR another' 158 # or: 'One rule OR another'
155 # and: 'One rule AND another' 159 # and: 'One rule AND another'
156 # matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 160 # matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
161 # notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
157 162
158entry: 163entry:
159 page_titles: 164 page_titles:
@@ -185,6 +190,8 @@ entry:
185 unread_label: 'Ulæst' 190 unread_label: 'Ulæst'
186 preview_picture_label: 'Har et vist billede' 191 preview_picture_label: 'Har et vist billede'
187 preview_picture_help: 'Forhåndsvis billede' 192 preview_picture_help: 'Forhåndsvis billede'
193 # is_public_label: 'Has a public link'
194 # is_public_help: 'Public link'
188 language_label: 'Sprog' 195 language_label: 'Sprog'
189 # http_status_label: 'HTTP status' 196 # http_status_label: 'HTTP status'
190 reading_time: 197 reading_time:
@@ -223,6 +230,8 @@ entry:
223 original_article: 'original' 230 original_article: 'original'
224 # annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations' 231 # annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations'
225 created_at: 'Oprettelsesdato' 232 created_at: 'Oprettelsesdato'
233 # published_at: 'Publication date'
234 # published_by: 'Published by'
226 new: 235 new:
227 page_title: 'Gem ny artikel' 236 page_title: 'Gem ny artikel'
228 placeholder: 'http://website.com' 237 placeholder: 'http://website.com'
@@ -234,10 +243,12 @@ entry:
234 # page_title: 'Edit an entry' 243 # page_title: 'Edit an entry'
235 # title_label: 'Title' 244 # title_label: 'Title'
236 url_label: 'Url' 245 url_label: 'Url'
237 # is_public_label: 'Public'
238 save_label: 'Gem' 246 save_label: 'Gem'
239 public: 247 public:
240 # shared_by_wallabag: "This article has been shared by <a href=%wallabag_instance%'>wallabag</a>" 248 # shared_by_wallabag: "This article has been shared by %username% with <a href='%wallabag_instance%'>wallabag</a>"
249 confirm:
250 # delete: "Are you sure you want to remove that article?"
251 # delete_tag: "Are you sure you want to remove that tag from that article?"
241 252
242about: 253about:
243 page_title: 'Om' 254 page_title: 'Om'
@@ -510,6 +521,28 @@ user:
510 # delete: Delete 521 # delete: Delete
511 # delete_confirm: Are you sure? 522 # delete_confirm: Are you sure?
512 # back_to_list: Back to list 523 # back_to_list: Back to list
524 search:
525 # placeholder: Filter by username or email
526
527site_credential:
528 # page_title: Site credentials management
529 # new_site_credential: Create a credential
530 # edit_site_credential: Edit an existing credential
531 # description: "Here you can manage all credentials for sites which required them (create, edit and delete), like a paywall, an authentication, etc."
532 # list:
533 # actions: Actions
534 # edit_action: Edit
535 # yes: Yes
536 # no: No
537 # create_new_one: Create a new credential
538 # form:
539 # username_label: 'Username'
540 # host_label: 'Host'
541 # password_label: 'Password'
542 # save: Save
543 # delete: Delete
544 # delete_confirm: Are you sure?
545 # back_to_list: Back to list
513 546
514error: 547error:
515 # page_title: An error occurred 548 # page_title: An error occurred
@@ -528,6 +561,7 @@ flashes:
528 # annotations_reset: Annotations reset 561 # annotations_reset: Annotations reset
529 # tags_reset: Tags reset 562 # tags_reset: Tags reset
530 # entries_reset: Entries reset 563 # entries_reset: Entries reset
564 # archived_reset: Archived entries deleted
531 entry: 565 entry:
532 notice: 566 notice:
533 # entry_already_saved: 'Entry already saved on %date%' 567 # entry_already_saved: 'Entry already saved on %date%'
@@ -562,3 +596,8 @@ flashes:
562 # added: 'User "%username%" added' 596 # added: 'User "%username%" added'
563 # updated: 'User "%username%" updated' 597 # updated: 'User "%username%" updated'
564 # deleted: 'User "%username%" deleted' 598 # deleted: 'User "%username%" deleted'
599 site_credential:
600 notice:
601 # added: 'Site credential for "%host%" added'
602 # updated: 'Site credential for "%host%" updated'
603 # deleted: 'Site credential for "%host%" deleted'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
index 35cb4b5b..996f173a 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
@@ -32,6 +32,7 @@ menu:
32 save_link: 'Link speichern' 32 save_link: 'Link speichern'
33 back_to_unread: 'Zurück zu ungelesenen Artikeln' 33 back_to_unread: 'Zurück zu ungelesenen Artikeln'
34 users_management: 'Benutzerverwaltung' 34 users_management: 'Benutzerverwaltung'
35 # site_credentials: 'Site credentials'
35 top: 36 top:
36 add_new_entry: 'Neuen Artikel hinzufügen' 37 add_new_entry: 'Neuen Artikel hinzufügen'
37 search: 'Suche' 38 search: 'Suche'
@@ -76,6 +77,7 @@ config:
76 redirect_current_page: 'Zur aktuellen Seite' 77 redirect_current_page: 'Zur aktuellen Seite'
77 pocket_consumer_key_label: Consumer-Key für Pocket, um Inhalte zu importieren 78 pocket_consumer_key_label: Consumer-Key für Pocket, um Inhalte zu importieren
78 android_configuration: Konfiguriere deine Android Application 79 android_configuration: Konfiguriere deine Android Application
80 # android_instruction: "Touch here to prefill your Android application"
79 help_theme: "wallabag ist anpassbar. Du kannst dein bevorzugtes Theme hier auswählen." 81 help_theme: "wallabag ist anpassbar. Du kannst dein bevorzugtes Theme hier auswählen."
80 help_items_per_page: "Du kannst die Nummer von Artikeln pro Seite anpassen." 82 help_items_per_page: "Du kannst die Nummer von Artikeln pro Seite anpassen."
81 help_reading_speed: "wallabag berechnet eine Lesezeit pro Artikel. Hier kannst du definieren, ob du ein schneller oder langsamer Leser bist. wallabag wird die Lesezeiten danach neu berechnen." 83 help_reading_speed: "wallabag berechnet eine Lesezeit pro Artikel. Hier kannst du definieren, ob du ein schneller oder langsamer Leser bist. wallabag wird die Lesezeiten danach neu berechnen."
@@ -92,6 +94,7 @@ config:
92 unread: 'Ungelesene' 94 unread: 'Ungelesene'
93 starred: 'Favoriten' 95 starred: 'Favoriten'
94 archive: 'Archivierte' 96 archive: 'Archivierte'
97 # all: 'All'
95 rss_limit: 'Anzahl der Einträge pro Feed' 98 rss_limit: 'Anzahl der Einträge pro Feed'
96 form_user: 99 form_user:
97 two_factor_description: "Wenn du die Zwei-Faktor-Authentifizierung aktivierst, erhältst du eine E-Mail mit einem Code bei jeder nicht vertrauenswürdigen Verbindung" 100 two_factor_description: "Wenn du die Zwei-Faktor-Authentifizierung aktivierst, erhältst du eine E-Mail mit einem Code bei jeder nicht vertrauenswürdigen Verbindung"
@@ -110,6 +113,7 @@ config:
110 annotations: Entferne ALLE Annotationen 113 annotations: Entferne ALLE Annotationen
111 tags: Entferne ALLE Tags 114 tags: Entferne ALLE Tags
112 entries: Entferne ALLE Einträge 115 entries: Entferne ALLE Einträge
116 archived: Entferne ALLE archivierten Einträge
113 confirm: Bist du wirklich sicher? (DIES KANN NICHT RÜCKGÄNGIG GEMACHT WERDEN) 117 confirm: Bist du wirklich sicher? (DIES KANN NICHT RÜCKGÄNGIG GEMACHT WERDEN)
114 form_password: 118 form_password:
115 description: "Hier kannst du dein Kennwort ändern. Dieses sollte mindestens acht Zeichen enthalten." 119 description: "Hier kannst du dein Kennwort ändern. Dieses sollte mindestens acht Zeichen enthalten."
@@ -154,6 +158,7 @@ config:
154 or: 'Eine Regel ODER die andere' 158 or: 'Eine Regel ODER die andere'
155 and: 'Eine Regel UND eine andere' 159 and: 'Eine Regel UND eine andere'
156 matches: 'Testet, ob eine <i>Variable</i> auf eine <i>Suche</i> zutrifft (Groß- und Kleinschreibung wird nicht berücksichtigt).<br />Beispiel: <code>title matches "Fußball"</code>' 160 matches: 'Testet, ob eine <i>Variable</i> auf eine <i>Suche</i> zutrifft (Groß- und Kleinschreibung wird nicht berücksichtigt).<br />Beispiel: <code>title matches "Fußball"</code>'
161 notmatches: 'Testet, ob ein <i>Titel</i> nicht auf eine <i>Suche</i> zutrifft (Groß- und Kleinschreibung wird nicht berücksichtigt).<br />Beispiel: <code>title notmatches "Fußball"</code>'
157 162
158entry: 163entry:
159 page_titles: 164 page_titles:
@@ -185,6 +190,8 @@ entry:
185 unread_label: 'Ungelesene' 190 unread_label: 'Ungelesene'
186 preview_picture_label: 'Vorschaubild vorhanden' 191 preview_picture_label: 'Vorschaubild vorhanden'
187 preview_picture_help: 'Vorschaubild' 192 preview_picture_help: 'Vorschaubild'
193 # is_public_label: 'Has a public link'
194 # is_public_help: 'Public link'
188 language_label: 'Sprache' 195 language_label: 'Sprache'
189 http_status_label: 'HTTP-Status' 196 http_status_label: 'HTTP-Status'
190 reading_time: 197 reading_time:
@@ -223,6 +230,8 @@ entry:
223 original_article: 'original' 230 original_article: 'original'
224 annotations_on_the_entry: '{0} Keine Anmerkungen|{1} Eine Anmerkung|]1,Inf[ %count% Anmerkungen' 231 annotations_on_the_entry: '{0} Keine Anmerkungen|{1} Eine Anmerkung|]1,Inf[ %count% Anmerkungen'
225 created_at: 'Erstellungsdatum' 232 created_at: 'Erstellungsdatum'
233 published_at: 'Erscheinungsdatum'
234 published_by: 'Veröffentlicht von'
226 new: 235 new:
227 page_title: 'Neuen Artikel speichern' 236 page_title: 'Neuen Artikel speichern'
228 placeholder: 'https://website.de' 237 placeholder: 'https://website.de'
@@ -234,10 +243,12 @@ entry:
234 page_title: 'Eintrag bearbeiten' 243 page_title: 'Eintrag bearbeiten'
235 title_label: 'Titel' 244 title_label: 'Titel'
236 url_label: 'URL' 245 url_label: 'URL'
237 is_public_label: 'Öffentlich'
238 save_label: 'Speichern' 246 save_label: 'Speichern'
239 public: 247 public:
240 shared_by_wallabag: "Dieser Artikel wurde mittels <a href='%wallabag_instance%'>wallabag</a> geteilt" 248 shared_by_wallabag: "Dieser Artikel wurde von %username% mittels <a href='%wallabag_instance%'>wallabag</a> geteilt"
249 confirm:
250 delete: "Bist du sicher, dass du diesen Artikel löschen möchtest?"
251 delete_tag: "Bist du sicher, dass du diesen Tag vom Artikel entfernen möchtest?"
241 252
242about: 253about:
243 page_title: 'Über' 254 page_title: 'Über'
@@ -510,6 +521,28 @@ user:
510 delete: Löschen 521 delete: Löschen
511 delete_confirm: Bist du sicher? 522 delete_confirm: Bist du sicher?
512 back_to_list: Zurück zur Liste 523 back_to_list: Zurück zur Liste
524 search:
525 placeholder: Filtere nach Benutzer oder E-Mail-Adresse
526
527site_credential:
528 # page_title: Site credentials management
529 # new_site_credential: Create a credential
530 # edit_site_credential: Edit an existing credential
531 # description: "Here you can manage all credentials for sites which required them (create, edit and delete), like a paywall, an authentication, etc."
532 list:
533 actions: Aktionen
534 edit_action: Bearbeiten
535 yes: Ja
536 no: Nein
537 # create_new_one: Create a new credential
538 form:
539 # username_label: 'Username'
540 # host_label: 'Host'
541 # password_label: 'Password'
542 save: Speichern
543 delete: Löschen
544 delete_confirm: Bist du sicher?
545 back_to_list: Zurück zur Liste
513 546
514error: 547error:
515 page_title: Ein Fehler ist aufgetreten 548 page_title: Ein Fehler ist aufgetreten
@@ -528,6 +561,7 @@ flashes:
528 annotations_reset: Anmerkungen zurücksetzen 561 annotations_reset: Anmerkungen zurücksetzen
529 tags_reset: Tags zurücksetzen 562 tags_reset: Tags zurücksetzen
530 entries_reset: Einträge zurücksetzen 563 entries_reset: Einträge zurücksetzen
564 archived_reset: Archiverte Einträge zurücksetzen
531 entry: 565 entry:
532 notice: 566 notice:
533 entry_already_saved: 'Eintrag bereits am %date% gespeichert' 567 entry_already_saved: 'Eintrag bereits am %date% gespeichert'
@@ -562,3 +596,8 @@ flashes:
562 added: 'Benutzer "%username%" hinzugefügt' 596 added: 'Benutzer "%username%" hinzugefügt'
563 updated: 'Benutzer "%username%" aktualisiert' 597 updated: 'Benutzer "%username%" aktualisiert'
564 deleted: 'Benutzer "%username%" gelöscht' 598 deleted: 'Benutzer "%username%" gelöscht'
599 site_credential:
600 notice:
601 # added: 'Site credential for "%host%" added'
602 # updated: 'Site credential for "%host%" updated'
603 # deleted: 'Site credential for "%host%" deleted'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
index c72e5958..aa1cd1a9 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
@@ -32,6 +32,7 @@ menu:
32 save_link: 'Save a link' 32 save_link: 'Save a link'
33 back_to_unread: 'Back to unread articles' 33 back_to_unread: 'Back to unread articles'
34 users_management: 'Users management' 34 users_management: 'Users management'
35 site_credentials: 'Site credentials'
35 top: 36 top:
36 add_new_entry: 'Add a new entry' 37 add_new_entry: 'Add a new entry'
37 search: 'Search' 38 search: 'Search'
@@ -76,6 +77,7 @@ config:
76 redirect_current_page: 'To the current page' 77 redirect_current_page: 'To the current page'
77 pocket_consumer_key_label: Consumer key for Pocket to import contents 78 pocket_consumer_key_label: Consumer key for Pocket to import contents
78 android_configuration: Configure your Android application 79 android_configuration: Configure your Android application
80 android_instruction: "Touch here to prefill your Android application"
79 help_theme: "wallabag is customizable. You can choose your prefered theme here." 81 help_theme: "wallabag is customizable. You can choose your prefered theme here."
80 help_items_per_page: "You can change the number of articles displayed on each page." 82 help_items_per_page: "You can change the number of articles displayed on each page."
81 help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article." 83 help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article."
@@ -89,9 +91,10 @@ config:
89 token_reset: 'Regenerate your token' 91 token_reset: 'Regenerate your token'
90 rss_links: 'RSS links' 92 rss_links: 'RSS links'
91 rss_link: 93 rss_link:
92 unread: 'unread' 94 unread: 'Unread'
93 starred: 'starred' 95 starred: 'Starred'
94 archive: 'archived' 96 archive: 'Archived'
97 all: 'All'
95 rss_limit: 'Number of items in the feed' 98 rss_limit: 'Number of items in the feed'
96 form_user: 99 form_user:
97 two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connection." 100 two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connection."
@@ -110,6 +113,7 @@ config:
110 annotations: Remove ALL annotations 113 annotations: Remove ALL annotations
111 tags: Remove ALL tags 114 tags: Remove ALL tags
112 entries: Remove ALL entries 115 entries: Remove ALL entries
116 archived: Remove ALL archived entries
113 confirm: Are you really sure? (THIS CAN'T BE UNDONE) 117 confirm: Are you really sure? (THIS CAN'T BE UNDONE)
114 form_password: 118 form_password:
115 description: "You can change your password here. Your new password should by at least 8 characters long." 119 description: "You can change your password here. Your new password should by at least 8 characters long."
@@ -154,6 +158,7 @@ config:
154 or: 'One rule OR another' 158 or: 'One rule OR another'
155 and: 'One rule AND another' 159 and: 'One rule AND another'
156 matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 160 matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
161 notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
157 162
158entry: 163entry:
159 page_titles: 164 page_titles:
@@ -185,6 +190,8 @@ entry:
185 unread_label: 'Unread' 190 unread_label: 'Unread'
186 preview_picture_label: 'Has a preview picture' 191 preview_picture_label: 'Has a preview picture'
187 preview_picture_help: 'Preview picture' 192 preview_picture_help: 'Preview picture'
193 is_public_label: 'Has a public link'
194 is_public_help: 'Public link'
188 language_label: 'Language' 195 language_label: 'Language'
189 http_status_label: 'HTTP status' 196 http_status_label: 'HTTP status'
190 reading_time: 197 reading_time:
@@ -223,6 +230,8 @@ entry:
223 original_article: 'original' 230 original_article: 'original'
224 annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations' 231 annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations'
225 created_at: 'Creation date' 232 created_at: 'Creation date'
233 published_at: 'Publication date'
234 published_by: 'Published by'
226 new: 235 new:
227 page_title: 'Save new entry' 236 page_title: 'Save new entry'
228 placeholder: 'http://website.com' 237 placeholder: 'http://website.com'
@@ -234,10 +243,12 @@ entry:
234 page_title: 'Edit an entry' 243 page_title: 'Edit an entry'
235 title_label: 'Title' 244 title_label: 'Title'
236 url_label: 'Url' 245 url_label: 'Url'
237 is_public_label: 'Public'
238 save_label: 'Save' 246 save_label: 'Save'
239 public: 247 public:
240 shared_by_wallabag: "This article has been shared by <a href='%wallabag_instance%'>wallabag</a>" 248 shared_by_wallabag: "This article has been shared by %username% with <a href='%wallabag_instance%'>wallabag</a>"
249 confirm:
250 delete: "Are you sure you want to remove that article?"
251 delete_tag: "Are you sure you want to remove that tag from that article?"
241 252
242about: 253about:
243 page_title: 'About' 254 page_title: 'About'
@@ -510,6 +521,28 @@ user:
510 delete: Delete 521 delete: Delete
511 delete_confirm: Are you sure? 522 delete_confirm: Are you sure?
512 back_to_list: Back to list 523 back_to_list: Back to list
524 search:
525 placeholder: Filter by username or email
526
527site_credential:
528 page_title: Site credentials management
529 new_site_credential: Create a credential
530 edit_site_credential: Edit an existing credential
531 description: "Here you can manage all credentials for sites which required them (create, edit and delete), like a paywall, an authentication, etc."
532 list:
533 actions: Actions
534 edit_action: Edit
535 yes: Yes
536 no: No
537 create_new_one: Create a new credential
538 form:
539 username_label: 'Username'
540 host_label: 'Host'
541 password_label: 'Password'
542 save: Save
543 delete: Delete
544 delete_confirm: Are you sure?
545 back_to_list: Back to list
513 546
514error: 547error:
515 page_title: An error occurred 548 page_title: An error occurred
@@ -528,6 +561,7 @@ flashes:
528 annotations_reset: Annotations reset 561 annotations_reset: Annotations reset
529 tags_reset: Tags reset 562 tags_reset: Tags reset
530 entries_reset: Entries reset 563 entries_reset: Entries reset
564 archived_reset: Archived entries deleted
531 entry: 565 entry:
532 notice: 566 notice:
533 entry_already_saved: 'Entry already saved on %date%' 567 entry_already_saved: 'Entry already saved on %date%'
@@ -562,3 +596,8 @@ flashes:
562 added: 'User "%username%" added' 596 added: 'User "%username%" added'
563 updated: 'User "%username%" updated' 597 updated: 'User "%username%" updated'
564 deleted: 'User "%username%" deleted' 598 deleted: 'User "%username%" deleted'
599 site_credential:
600 notice:
601 added: 'Site credential for "%host%" added'
602 updated: 'Site credential for "%host%" updated'
603 deleted: 'Site credential for "%host%" deleted'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
index 2f769b7e..96998f53 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
@@ -32,6 +32,7 @@ menu:
32 save_link: 'Guardar un enlace' 32 save_link: 'Guardar un enlace'
33 back_to_unread: 'Volver a los artículos sin leer' 33 back_to_unread: 'Volver a los artículos sin leer'
34 users_management: 'Configuración de usuarios' 34 users_management: 'Configuración de usuarios'
35 # site_credentials: 'Site credentials'
35 top: 36 top:
36 add_new_entry: 'Añadir un nuevo artículo' 37 add_new_entry: 'Añadir un nuevo artículo'
37 search: 'Buscar' 38 search: 'Buscar'
@@ -76,6 +77,7 @@ config:
76 redirect_current_page: 'A la página actual' 77 redirect_current_page: 'A la página actual'
77 pocket_consumer_key_label: Clave de consumidor para importar contenidos de Pocket 78 pocket_consumer_key_label: Clave de consumidor para importar contenidos de Pocket
78 android_configuration: Configura tu aplicación Android 79 android_configuration: Configura tu aplicación Android
80 # android_instruction: "Touch here to prefill your Android application"
79 help_theme: "wallabag es personalizable. Puedes elegir tu tema preferido aquí." 81 help_theme: "wallabag es personalizable. Puedes elegir tu tema preferido aquí."
80 help_items_per_page: "Puedes cambiar el número de artículos mostrados en cada página." 82 help_items_per_page: "Puedes cambiar el número de artículos mostrados en cada página."
81 help_reading_speed: "wallabag calcula un tiempo de lectura para cada artículo. Puedes definir aquí, gracias a esta lista, si eres un lector rápido o lento. wallabag recalculará el tiempo de lectura para cada artículo." 83 help_reading_speed: "wallabag calcula un tiempo de lectura para cada artículo. Puedes definir aquí, gracias a esta lista, si eres un lector rápido o lento. wallabag recalculará el tiempo de lectura para cada artículo."
@@ -92,6 +94,7 @@ config:
92 unread: 'sin leer' 94 unread: 'sin leer'
93 starred: 'favoritos' 95 starred: 'favoritos'
94 archive: 'archivados' 96 archive: 'archivados'
97 # all: 'All'
95 rss_limit: 'Límite de artículos en feed RSS' 98 rss_limit: 'Límite de artículos en feed RSS'
96 form_user: 99 form_user:
97 two_factor_description: "Con la autenticación en dos pasos recibirá código por e-mail en cada nueva conexión que no sea de confianza." 100 two_factor_description: "Con la autenticación en dos pasos recibirá código por e-mail en cada nueva conexión que no sea de confianza."
@@ -110,6 +113,7 @@ config:
110 annotations: Eliminar TODAS las anotaciones 113 annotations: Eliminar TODAS las anotaciones
111 tags: Eliminar TODAS las etiquetas 114 tags: Eliminar TODAS las etiquetas
112 entries: Eliminar TODOS los artículos 115 entries: Eliminar TODOS los artículos
116 # archived: Remove ALL archived entries
113 confirm: ¿Estás completamente seguro? (NO SE PUEDE DESHACER) 117 confirm: ¿Estás completamente seguro? (NO SE PUEDE DESHACER)
114 form_password: 118 form_password:
115 description: "Puedes cambiar la contraseña aquí. Tu nueva contraseña debe tener al menos 8 caracteres." 119 description: "Puedes cambiar la contraseña aquí. Tu nueva contraseña debe tener al menos 8 caracteres."
@@ -154,6 +158,7 @@ config:
154 or: 'Una regla U otra' 158 or: 'Una regla U otra'
155 and: 'Una regla Y la otra' 159 and: 'Una regla Y la otra'
156 matches: 'Prueba si un <i>sujeto</i> corresponde a una <i>búsqueda</i> (insensible a mayusculas).<br />Ejemplo : <code>title matches "fútbol"</code>' 160 matches: 'Prueba si un <i>sujeto</i> corresponde a una <i>búsqueda</i> (insensible a mayusculas).<br />Ejemplo : <code>title matches "fútbol"</code>'
161 # notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
157 162
158entry: 163entry:
159 page_titles: 164 page_titles:
@@ -185,6 +190,8 @@ entry:
185 unread_label: 'Sin leer' 190 unread_label: 'Sin leer'
186 preview_picture_label: 'Tiene imagen de previsualización' 191 preview_picture_label: 'Tiene imagen de previsualización'
187 preview_picture_help: 'Imagen de previsualización' 192 preview_picture_help: 'Imagen de previsualización'
193 # is_public_label: 'Has a public link'
194 # is_public_help: 'Public link'
188 language_label: 'Idioma' 195 language_label: 'Idioma'
189 http_status_label: 'Código de estado HTTP' 196 http_status_label: 'Código de estado HTTP'
190 reading_time: 197 reading_time:
@@ -223,6 +230,8 @@ entry:
223 original_article: 'original' 230 original_article: 'original'
224 annotations_on_the_entry: '{0} Sin anotaciones|{1} Una anotación|]1,Inf[ %count% anotaciones' 231 annotations_on_the_entry: '{0} Sin anotaciones|{1} Una anotación|]1,Inf[ %count% anotaciones'
225 created_at: 'Fecha de creación' 232 created_at: 'Fecha de creación'
233 # published_at: 'Publication date'
234 # published_by: 'Published by'
226 new: 235 new:
227 page_title: 'Guardar un nuevo artículo' 236 page_title: 'Guardar un nuevo artículo'
228 placeholder: 'http://sitioweb.com' 237 placeholder: 'http://sitioweb.com'
@@ -234,10 +243,12 @@ entry:
234 page_title: 'Editar un artículo' 243 page_title: 'Editar un artículo'
235 title_label: 'Título' 244 title_label: 'Título'
236 url_label: 'URL' 245 url_label: 'URL'
237 is_public_label: 'Es público'
238 save_label: 'Guardar' 246 save_label: 'Guardar'
239 public: 247 public:
240 shared_by_wallabag: "Este artículo se ha compartido con <a href='%wallabag_instance%'>wallabag</a>" 248 shared_by_wallabag: "Este artículo se ha compartido con <a href='%wallabag_instance%'>wallabag</a>"
249 confirm:
250 # delete: "Are you sure you want to remove that article?"
251 # delete_tag: "Are you sure you want to remove that tag from that article?"
241 252
242about: 253about:
243 page_title: 'Acerca de' 254 page_title: 'Acerca de'
@@ -510,6 +521,28 @@ user:
510 delete: Eliminar 521 delete: Eliminar
511 delete_confirm: ¿Estás seguro? 522 delete_confirm: ¿Estás seguro?
512 back_to_list: Volver a la lista 523 back_to_list: Volver a la lista
524 search:
525 # placeholder: Filter by username or email
526
527site_credential:
528 # page_title: Site credentials management
529 # new_site_credential: Create a credential
530 # edit_site_credential: Edit an existing credential
531 # description: "Here you can manage all credentials for sites which required them (create, edit and delete), like a paywall, an authentication, etc."
532 # list:
533 # actions: Actions
534 # edit_action: Edit
535 # yes: Yes
536 # no: No
537 # create_new_one: Create a new credential
538 # form:
539 # username_label: 'Username'
540 # host_label: 'Host'
541 # password_label: 'Password'
542 # save: Save
543 # delete: Delete
544 # delete_confirm: Are you sure?
545 # back_to_list: Back to list
513 546
514error: 547error:
515 page_title: Ha ocurrido un error 548 page_title: Ha ocurrido un error
@@ -528,6 +561,7 @@ flashes:
528 annotations_reset: Anotaciones reiniciadas 561 annotations_reset: Anotaciones reiniciadas
529 tags_reset: Etiquetas reiniciadas 562 tags_reset: Etiquetas reiniciadas
530 entries_reset: Artículos reiniciados 563 entries_reset: Artículos reiniciados
564 # archived_reset: Archived entries deleted
531 entry: 565 entry:
532 notice: 566 notice:
533 entry_already_saved: 'Artículo ya guardado el %fecha%' 567 entry_already_saved: 'Artículo ya guardado el %fecha%'
@@ -562,3 +596,8 @@ flashes:
562 added: 'Añadido el usuario "%username%"' 596 added: 'Añadido el usuario "%username%"'
563 updated: 'Actualizado el usuario "%username%"' 597 updated: 'Actualizado el usuario "%username%"'
564 deleted: 'Eliminado el usuario "%username%"' 598 deleted: 'Eliminado el usuario "%username%"'
599 site_credential:
600 notice:
601 # added: 'Site credential for "%host%" added'
602 # updated: 'Site credential for "%host%" updated'
603 # deleted: 'Site credential for "%host%" deleted'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
index 647f12ad..57e6c029 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
@@ -32,6 +32,7 @@ menu:
32 save_link: 'ذخیرهٔ یک پیوند' 32 save_link: 'ذخیرهٔ یک پیوند'
33 back_to_unread: 'بازگشت به خوانده‌نشده‌ها' 33 back_to_unread: 'بازگشت به خوانده‌نشده‌ها'
34 # users_management: 'Users management' 34 # users_management: 'Users management'
35 # site_credentials: 'Site credentials'
35 top: 36 top:
36 add_new_entry: 'افزودن مقالهٔ تازه' 37 add_new_entry: 'افزودن مقالهٔ تازه'
37 search: 'جستجو' 38 search: 'جستجو'
@@ -77,6 +78,7 @@ config:
77 pocket_consumer_key_label: کلید کاربری Pocket برای درون‌ریزی مطالب 78 pocket_consumer_key_label: کلید کاربری Pocket برای درون‌ریزی مطالب
78 # android_configuration: Configure your Android application 79 # android_configuration: Configure your Android application
79 # help_theme: "wallabag is customizable. You can choose your prefered theme here." 80 # help_theme: "wallabag is customizable. You can choose your prefered theme here."
81 # android_instruction: "Touch here to prefill your Android application"
80 # help_items_per_page: "You can change the number of articles displayed on each page." 82 # help_items_per_page: "You can change the number of articles displayed on each page."
81 # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article." 83 # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article."
82 # help_language: "You can change the language of wallabag interface." 84 # help_language: "You can change the language of wallabag interface."
@@ -92,6 +94,7 @@ config:
92 unread: 'خوانده‌نشده' 94 unread: 'خوانده‌نشده'
93 starred: 'برگزیده' 95 starred: 'برگزیده'
94 archive: 'بایگانی' 96 archive: 'بایگانی'
97 # all: 'All'
95 rss_limit: 'محدودیت آر-اس-اس' 98 rss_limit: 'محدودیت آر-اس-اس'
96 form_user: 99 form_user:
97 two_factor_description: "با فعال‌کردن تأیید ۲مرحله‌ای هر بار که اتصال تأییدنشده‌ای برقرار شد، به شما یک کد از راه ایمیل فرستاده می‌شود" 100 two_factor_description: "با فعال‌کردن تأیید ۲مرحله‌ای هر بار که اتصال تأییدنشده‌ای برقرار شد، به شما یک کد از راه ایمیل فرستاده می‌شود"
@@ -110,6 +113,7 @@ config:
110 # annotations: Remove ALL annotations 113 # annotations: Remove ALL annotations
111 # tags: Remove ALL tags 114 # tags: Remove ALL tags
112 # entries: Remove ALL entries 115 # entries: Remove ALL entries
116 # archived: Remove ALL archived entries
113 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE) 117 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
114 form_password: 118 form_password:
115 # description: "You can change your password here. Your new password should by at least 8 characters long." 119 # description: "You can change your password here. Your new password should by at least 8 characters long."
@@ -154,6 +158,7 @@ config:
154 # or: 'One rule OR another' 158 # or: 'One rule OR another'
155 # and: 'One rule AND another' 159 # and: 'One rule AND another'
156 # matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 160 # matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
161 # notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
157 162
158entry: 163entry:
159 page_titles: 164 page_titles:
@@ -185,6 +190,8 @@ entry:
185 unread_label: 'خوانده‌نشده' 190 unread_label: 'خوانده‌نشده'
186 preview_picture_label: 'دارای عکس پیش‌نمایش' 191 preview_picture_label: 'دارای عکس پیش‌نمایش'
187 preview_picture_help: 'پیش‌نمایش عکس' 192 preview_picture_help: 'پیش‌نمایش عکس'
193 # is_public_label: 'Has a public link'
194 # is_public_help: 'Public link'
188 language_label: 'زبان' 195 language_label: 'زبان'
189 # http_status_label: 'HTTP status' 196 # http_status_label: 'HTTP status'
190 reading_time: 197 reading_time:
@@ -223,6 +230,8 @@ entry:
223 original_article: 'اصلی' 230 original_article: 'اصلی'
224 annotations_on_the_entry: '{0} بدون حاشیه|{1} یک حاشیه|]1,Inf[ %nbحاشیه% annotations' 231 annotations_on_the_entry: '{0} بدون حاشیه|{1} یک حاشیه|]1,Inf[ %nbحاشیه% annotations'
225 created_at: 'زمان ساخت' 232 created_at: 'زمان ساخت'
233 # published_at: 'Publication date'
234 # published_by: 'Published by'
226 new: 235 new:
227 page_title: 'ذخیرهٔ مقالهٔ تازه' 236 page_title: 'ذخیرهٔ مقالهٔ تازه'
228 placeholder: 'http://website.com' 237 placeholder: 'http://website.com'
@@ -234,10 +243,12 @@ entry:
234 page_title: 'ویرایش مقاله' 243 page_title: 'ویرایش مقاله'
235 title_label: 'عنوان' 244 title_label: 'عنوان'
236 url_label: 'نشانی' 245 url_label: 'نشانی'
237 is_public_label: 'عمومی'
238 save_label: 'ذخیره' 246 save_label: 'ذخیره'
239 public: 247 public:
240 # shared_by_wallabag: "This article has been shared by <a href='%wallabag_instance%'>wallabag</a>" 248 # shared_by_wallabag: "This article has been shared by %username% with <a href='%wallabag_instance%'>wallabag</a>"
249 confirm:
250 # delete: "Are you sure you want to remove that article?"
251 # delete_tag: "Are you sure you want to remove that tag from that article?"
241 252
242about: 253about:
243 page_title: 'درباره' 254 page_title: 'درباره'
@@ -510,6 +521,28 @@ user:
510 # delete: Delete 521 # delete: Delete
511 # delete_confirm: Are you sure? 522 # delete_confirm: Are you sure?
512 # back_to_list: Back to list 523 # back_to_list: Back to list
524 search:
525 # placeholder: Filter by username or email
526
527site_credential:
528 # page_title: Site credentials management
529 # new_site_credential: Create a credential
530 # edit_site_credential: Edit an existing credential
531 # description: "Here you can manage all credentials for sites which required them (create, edit and delete), like a paywall, an authentication, etc."
532 # list:
533 # actions: Actions
534 # edit_action: Edit
535 # yes: Yes
536 # no: No
537 # create_new_one: Create a new credential
538 # form:
539 # username_label: 'Username'
540 # host_label: 'Host'
541 # password_label: 'Password'
542 # save: Save
543 # delete: Delete
544 # delete_confirm: Are you sure?
545 # back_to_list: Back to list
513 546
514error: 547error:
515 # page_title: An error occurred 548 # page_title: An error occurred
@@ -528,6 +561,7 @@ flashes:
528 # annotations_reset: Annotations reset 561 # annotations_reset: Annotations reset
529 # tags_reset: Tags reset 562 # tags_reset: Tags reset
530 # entries_reset: Entries reset 563 # entries_reset: Entries reset
564 # archived_reset: Archived entries deleted
531 entry: 565 entry:
532 notice: 566 notice:
533 entry_already_saved: 'این مقاله در تاریخ %date% ذخیره شده بود' 567 entry_already_saved: 'این مقاله در تاریخ %date% ذخیره شده بود'
@@ -562,3 +596,8 @@ flashes:
562 # added: 'User "%username%" added' 596 # added: 'User "%username%" added'
563 # updated: 'User "%username%" updated' 597 # updated: 'User "%username%" updated'
564 # deleted: 'User "%username%" deleted' 598 # deleted: 'User "%username%" deleted'
599 site_credential:
600 notice:
601 # added: 'Site credential for "%host%" added'
602 # updated: 'Site credential for "%host%" updated'
603 # deleted: 'Site credential for "%host%" deleted'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
index efddc46a..6eac4c36 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
@@ -32,6 +32,7 @@ menu:
32 save_link: "Sauvegarder un nouvel article" 32 save_link: "Sauvegarder un nouvel article"
33 back_to_unread: "Retour aux articles non lus" 33 back_to_unread: "Retour aux articles non lus"
34 users_management: "Gestion des utilisateurs" 34 users_management: "Gestion des utilisateurs"
35 site_credentials: 'Accès aux sites'
35 top: 36 top:
36 add_new_entry: "Sauvegarder un nouvel article" 37 add_new_entry: "Sauvegarder un nouvel article"
37 search: "Rechercher" 38 search: "Rechercher"
@@ -46,7 +47,7 @@ footer:
46 social: "Social" 47 social: "Social"
47 powered_by: "propulsé par" 48 powered_by: "propulsé par"
48 about: "À propos" 49 about: "À propos"
49 stats: Depuis le %user_creation%, vous avez lu %nb_archives% articles. Ce qui fait %per_day% par jour ! 50 stats: "Depuis le %user_creation%, vous avez lu %nb_archives% articles. Ce qui fait %per_day% par jour !"
50 51
51config: 52config:
52 page_title: "Configuration" 53 page_title: "Configuration"
@@ -71,27 +72,29 @@ config:
71 300_word: "Je lis environ 300 mots par minute" 72 300_word: "Je lis environ 300 mots par minute"
72 400_word: "Je lis environ 400 mots par minute" 73 400_word: "Je lis environ 400 mots par minute"
73 action_mark_as_read: 74 action_mark_as_read:
74 label: 'Où souhaitez-vous être redirigé après avoir marqué un article comme lu ?' 75 label: "Où souhaitez-vous être redirigé après avoir marqué un article comme lu ?"
75 redirect_homepage: "À la page d'accueil" 76 redirect_homepage: "À la page d’accueil"
76 redirect_current_page: 'À la page courante' 77 redirect_current_page: "À la page courante"
77 pocket_consumer_key_label: Clé d’authentification Pocket pour importer les données 78 pocket_consumer_key_label: "Clé d’authentification Pocket pour importer les données"
78 android_configuration: Configurez votre application Android 79 android_configuration: "Configurez votre application Android"
79 help_theme: "L'affichage de wallabag est personnalisable. C'est ici que vous choisissez le thème que vous préférez." 80 android_instruction: "Appuyez ici pour préremplir votre application Android"
80 help_items_per_page: "Vous pouvez définir le nombre d'articles affichés sur chaque page." 81 help_theme: "L’affichage de wallabag est personnalisable. C’est ici que vous choisissez le thème que vous préférez."
82 help_items_per_page: "Vous pouvez définir le nombre d’articles affichés sur chaque page."
81 help_reading_speed: "wallabag calcule une durée de lecture pour chaque article. Vous pouvez définir ici, grâce à cette liste déroulante, si vous lisez plus ou moins vite. wallabag recalculera la durée de lecture de chaque article." 83 help_reading_speed: "wallabag calcule une durée de lecture pour chaque article. Vous pouvez définir ici, grâce à cette liste déroulante, si vous lisez plus ou moins vite. wallabag recalculera la durée de lecture de chaque article."
82 help_language: "Vous pouvez définir la langue de l'interface de wallabag." 84 help_language: "Vous pouvez définir la langue de linterface de wallabag."
83 help_pocket_consumer_key: "Nécessaire pour l'import depuis Pocket. Vous pouvez le créer depuis votre compte Pocket." 85 help_pocket_consumer_key: "Nécessaire pour l’import depuis Pocket. Vous pouvez le créer depuis votre compte Pocket."
84 form_rss: 86 form_rss:
85 description: "Les flux RSS fournis par wallabag vous permettent de lire vos articles sauvegardés dans votre lecteur de flux préféré. Pour pouvoir les utiliser, vous devez d’abord créer un jeton." 87 description: "Les flux RSS fournis par wallabag vous permettent de lire vos articles sauvegardés dans votre lecteur de flux préféré. Pour pouvoir les utiliser, vous devez d’abord créer un jeton."
86 token_label: "Jeton RSS" 88 token_label: "Jeton RSS"
87 no_token: "Aucun jeton généré" 89 no_token: "Aucun jeton généré"
88 token_create: "Créez votre jeton" 90 token_create: "Créez votre jeton"
89 token_reset: "Réinitialisez votre jeton" 91 token_reset: "Réinitialisez votre jeton"
90 rss_links: "Adresse de vos flux RSS" 92 rss_links: "Adresses de vos flux RSS"
91 rss_link: 93 rss_link:
92 unread: "non lus" 94 unread: "Non lus"
93 starred: "favoris" 95 starred: "Favoris"
94 archive: "lus" 96 archive: "Lus"
97 all: "Tous"
95 rss_limit: "Nombre d’articles dans le flux" 98 rss_limit: "Nombre d’articles dans le flux"
96 form_user: 99 form_user:
97 two_factor_description: "Activer l’authentification double-facteur veut dire que vous allez recevoir un code par courriel à chaque nouvelle connexion non approuvée." 100 two_factor_description: "Activer l’authentification double-facteur veut dire que vous allez recevoir un code par courriel à chaque nouvelle connexion non approuvée."
@@ -100,17 +103,18 @@ config:
100 twoFactorAuthentication_label: "Double authentification" 103 twoFactorAuthentication_label: "Double authentification"
101 help_twoFactorAuthentication: "Si vous activez 2FA, à chaque tentative de connexion à wallabag, vous recevrez un code par email." 104 help_twoFactorAuthentication: "Si vous activez 2FA, à chaque tentative de connexion à wallabag, vous recevrez un code par email."
102 delete: 105 delete:
103 title: Supprimer mon compte (attention danger !) 106 title: "Supprimer mon compte (attention danger !)"
104 description: Si vous confirmez la suppression de votre compte, TOUS les articles, TOUS les tags, TOUTES les annotations et votre compte seront DÉFINITIVEMENT supprimé (c'est IRRÉVERSIBLE). Vous serez ensuite déconnecté. 107 description: "Si vous confirmez la suppression de votre compte, TOUS les articles, TOUS les tags, TOUTES les annotations et votre compte seront DÉFINITIVEMENT supprimé (c’est IRRÉVERSIBLE). Vous serez ensuite déconnecté."
105 confirm: Vous êtes vraiment sûr ? (C'EST IRRÉVERSIBLE) 108 confirm: "Vous êtes vraiment sûr ? (CEST IRRÉVERSIBLE)"
106 button: 'Supprimer mon compte' 109 button: "Supprimer mon compte"
107 reset: 110 reset:
108 title: Réinitialisation (attention danger !) 111 title: "Réinitialisation (attention danger !)"
109 description: En cliquant sur les boutons ci-dessous vous avez la possibilité de supprimer certaines informations de votre compte. Attention, ces actions sont IRRÉVERSIBLES ! 112 description: "En cliquant sur les boutons ci-dessous vous avez la possibilité de supprimer certaines informations de votre compte. Attention, ces actions sont IRRÉVERSIBLES !"
110 annotations: Supprimer TOUTES les annotations 113 annotations: "Supprimer TOUTES les annotations"
111 tags: Supprimer TOUS les tags 114 tags: "Supprimer TOUS les tags"
112 entries: Supprimer TOUS les articles 115 entries: "Supprimer TOUS les articles"
113 confirm: Êtes-vous vraiment vraiment sûr ? (C'EST IRRÉVERSIBLE) 116 archived: "Supprimer TOUS les articles archivés"
117 confirm: "Êtes-vous vraiment vraiment sûr ? (C’EST IRRÉVERSIBLE)"
114 form_password: 118 form_password:
115 description: "Vous pouvez changer ici votre mot de passe. Le mot de passe doit contenir au moins 8 caractères." 119 description: "Vous pouvez changer ici votre mot de passe. Le mot de passe doit contenir au moins 8 caractères."
116 old_password_label: "Mot de passe actuel" 120 old_password_label: "Mot de passe actuel"
@@ -154,6 +158,7 @@ config:
154 or: "Une règle OU l’autre" 158 or: "Une règle OU l’autre"
155 and: "Une règle ET l’autre" 159 and: "Une règle ET l’autre"
156 matches: "Teste si un <i>sujet</i> correspond à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title matches \"football\"</code>" 160 matches: "Teste si un <i>sujet</i> correspond à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title matches \"football\"</code>"
161 notmatches: "Teste si un <i>sujet</i> ne correspond pas à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title notmatches \"football\"</code>"
157 162
158entry: 163entry:
159 page_titles: 164 page_titles:
@@ -162,7 +167,7 @@ entry:
162 archived: "Articles lus" 167 archived: "Articles lus"
163 filtered: "Articles filtrés" 168 filtered: "Articles filtrés"
164 filtered_tags: "Articles filtrés par tags :" 169 filtered_tags: "Articles filtrés par tags :"
165 filtered_search: 'Articles filtrés par recherche :' 170 filtered_search: "Articles filtrés par recherche :"
166 untagged: "Article sans tag" 171 untagged: "Article sans tag"
167 list: 172 list:
168 number_on_the_page: "{0} Il n’y a pas d’article.|{1} Il y a un article.|]1,Inf[ Il y a %count% articles." 173 number_on_the_page: "{0} Il n’y a pas d’article.|{1} Il y a un article.|]1,Inf[ Il y a %count% articles."
@@ -185,8 +190,10 @@ entry:
185 unread_label: "Non lus" 190 unread_label: "Non lus"
186 preview_picture_label: "A une photo" 191 preview_picture_label: "A une photo"
187 preview_picture_help: "Photo" 192 preview_picture_help: "Photo"
193 is_public_label: 'A un lien public'
194 is_public_help: 'Lien public'
188 language_label: "Langue" 195 language_label: "Langue"
189 http_status_label: 'Statut HTTP' 196 http_status_label: "Statut HTTP"
190 reading_time: 197 reading_time:
191 label: "Durée de lecture en minutes" 198 label: "Durée de lecture en minutes"
192 from: "de" 199 from: "de"
@@ -223,6 +230,8 @@ entry:
223 original_article: "original" 230 original_article: "original"
224 annotations_on_the_entry: "{0} Aucune annotation|{1} Une annotation|]1,Inf[ %count% annotations" 231 annotations_on_the_entry: "{0} Aucune annotation|{1} Une annotation|]1,Inf[ %count% annotations"
225 created_at: "Date de création" 232 created_at: "Date de création"
233 published_at: "Date de publication"
234 published_by: "Publié par"
226 new: 235 new:
227 page_title: "Sauvegarder un nouvel article" 236 page_title: "Sauvegarder un nouvel article"
228 placeholder: "http://website.com" 237 placeholder: "http://website.com"
@@ -234,10 +243,12 @@ entry:
234 page_title: "Éditer un article" 243 page_title: "Éditer un article"
235 title_label: "Titre" 244 title_label: "Titre"
236 url_label: "Adresse" 245 url_label: "Adresse"
237 is_public_label: "Public"
238 save_label: "Enregistrer" 246 save_label: "Enregistrer"
239 public: 247 public:
240 shared_by_wallabag: "Cet article a été partagé par <a href=\"%wallabag_instance%\">wallabag</a>" 248 shared_by_wallabag: "Cet article a été partagé par %username% avec <a href=\"%wallabag_instance%\">wallabag</a>"
249 confirm:
250 delete: "Voulez-vous vraiment supprimer cet article ?"
251 delete_tag: "Voulez-vous vraiment supprimer ce tag de cet article ?"
241 252
242about: 253about:
243 page_title: "À propos" 254 page_title: "À propos"
@@ -295,32 +306,32 @@ howto:
295 bookmarklet: 306 bookmarklet:
296 description: "Glissez et déposez ce lien dans votre barre de favoris :" 307 description: "Glissez et déposez ce lien dans votre barre de favoris :"
297 shortcuts: 308 shortcuts:
298 page_description: Voici les raccourcis disponibles dans wallabag. 309 page_description: "Voici les raccourcis disponibles dans wallabag."
299 shortcut: Raccourci 310 shortcut: "Raccourci"
300 action: Action 311 action: "Action"
301 all_pages_title: Raccourcis disponibles dans toutes les pages 312 all_pages_title: "Raccourcis disponibles dans toutes les pages"
302 go_unread: Afficher les articles non lus 313 go_unread: "Afficher les articles non lus"
303 go_starred: Afficher les articles favoris 314 go_starred: "Afficher les articles favoris"
304 go_archive: Afficher les articles lus 315 go_archive: "Afficher les articles lus"
305 go_all: Afficher tous les articles 316 go_all: "Afficher tous les articles"
306 go_tags: Afficher les tags 317 go_tags: "Afficher les tags"
307 go_config: Aller à la configuration 318 go_config: "Aller à la configuration"
308 go_import: Aller aux imports 319 go_import: "Aller aux imports"
309 go_developers: Aller à la section Développeurs 320 go_developers: "Aller à la section Développeurs"
310 go_howto: Afficher l'aide (cette page !) 321 go_howto: "Afficher laide (cette page !)"
311 go_logout: Se déconnecter 322 go_logout: "Se déconnecter"
312 list_title: Raccourcis disponibles dans les pages de liste 323 list_title: "Raccourcis disponibles dans les pages de liste"
313 search: Afficher le formulaire de recherche 324 search: "Afficher le formulaire de recherche"
314 article_title: Raccourcis disponibles quand on affiche un article 325 article_title: "Raccourcis disponibles quand on affiche un article"
315 open_original: Ouvrir l'URL originale de l'article 326 open_original: "Ouvrir lURL originale de larticle"
316 toggle_favorite: Changer le statut Favori de l'article 327 toggle_favorite: "Changer le statut Favori de larticle"
317 toggle_archive: Changer le status Lu de l'article 328 toggle_archive: "Changer le status Lu de larticle"
318 delete: Supprimer l'article 329 delete: "Supprimer larticle"
319 material_title: Raccourcis disponibles avec le thème Material uniquement 330 material_title: "Raccourcis disponibles avec le thème Material uniquement"
320 add_link: Ajouter un nouvel article 331 add_link: "Ajouter un nouvel article"
321 hide_form: Masquer le formulaire courant (recherche ou nouvel article) 332 hide_form: "Masquer le formulaire courant (recherche ou nouvel article)"
322 arrows_navigation: Naviguer à travers les articles 333 arrows_navigation: "Naviguer à travers les articles"
323 open_article: Afficher l'article sélectionné 334 open_article: "Afficher larticle sélectionné"
324 335
325quickstart: 336quickstart:
326 page_title: "Pour bien débuter" 337 page_title: "Pour bien débuter"
@@ -382,8 +393,8 @@ tag:
382 number_on_the_page: "{0} Il n’y a pas de tag.|{1} Il y a un tag.|]1,Inf[ Il y a %count% tags." 393 number_on_the_page: "{0} Il n’y a pas de tag.|{1} Il y a un tag.|]1,Inf[ Il y a %count% tags."
383 see_untagged_entries: "Voir les articles sans tag" 394 see_untagged_entries: "Voir les articles sans tag"
384 new: 395 new:
385 add: 'Ajouter' 396 add: "Ajouter"
386 placeholder: 'Vous pouvez ajouter plusieurs tags, séparés par une virgule.' 397 placeholder: "Vous pouvez ajouter plusieurs tags, séparés par une virgule."
387 398
388import: 399import:
389 page_title: "Importer" 400 page_title: "Importer"
@@ -417,7 +428,7 @@ import:
417 how_to: "Choisissez le fichier de votre export Readability et cliquez sur le bouton ci-dessous pour l’importer." 428 how_to: "Choisissez le fichier de votre export Readability et cliquez sur le bouton ci-dessous pour l’importer."
418 worker: 429 worker:
419 enabled: "Les imports sont asynchrones. Une fois l’import commencé un worker externe traitera les messages un par un. Le service activé est :" 430 enabled: "Les imports sont asynchrones. Une fois l’import commencé un worker externe traitera les messages un par un. Le service activé est :"
420 download_images_warning: "Vous avez configuré le téléchagement des images pour vos articles. Combiné à l'import classique, cette opération peut être très très longue (voire échouer). Nous vous conseillons <strong>vivement</strong> d'activer les imports asynchrones." 431 download_images_warning: "Vous avez configuré le téléchagement des images pour vos articles. Combiné à l’import classique, cette opération peut être très très longue (voire échouer). Nous vous conseillons <strong>vivement</strong> d’activer les imports asynchrones."
421 firefox: 432 firefox:
422 page_title: "Import > Firefox" 433 page_title: "Import > Firefox"
423 description: "Cet outil va vous permettre d’importer tous vos marques-pages de Firefox. Ouvrez le panneau des marques-pages (Ctrl+Maj+O), puis dans « Importation et sauvegarde », choisissez « Sauvegarde… ». Vous allez récupérer un fichier .json. </p>" 434 description: "Cet outil va vous permettre d’importer tous vos marques-pages de Firefox. Ouvrez le panneau des marques-pages (Ctrl+Maj+O), puis dans « Importation et sauvegarde », choisissez « Sauvegarde… ». Vous allez récupérer un fichier .json. </p>"
@@ -486,16 +497,16 @@ developer:
486 back: "Retour" 497 back: "Retour"
487 498
488user: 499user:
489 page_title: Gestion des utilisateurs 500 page_title: "Gestion des utilisateurs"
490 new_user: Créer un nouvel utilisateur 501 new_user: "Créer un nouvel utilisateur"
491 edit_user: Éditer un utilisateur existant 502 edit_user: "Éditer un utilisateur existant"
492 description: Ici vous pouvez gérer vos utilisateurs (création, mise à jour et suppression) 503 description: "Ici vous pouvez gérer vos utilisateurs (création, mise à jour et suppression)"
493 list: 504 list:
494 actions: Actions 505 actions: "Actions"
495 edit_action: Éditer 506 edit_action: "Éditer"
496 yes: Oui 507 yes: "Oui"
497 no: Non 508 no: "Non"
498 create_new_one: Créer un nouvel utilisateur 509 create_new_one: "Créer un nouvel utilisateur"
499 form: 510 form:
500 username_label: "Nom d’utilisateur" 511 username_label: "Nom d’utilisateur"
501 name_label: "Nom" 512 name_label: "Nom"
@@ -508,11 +519,33 @@ user:
508 twofactor_label: "Double authentification" 519 twofactor_label: "Double authentification"
509 save: "Sauvegarder" 520 save: "Sauvegarder"
510 delete: "Supprimer" 521 delete: "Supprimer"
511 delete_confirm: "Voulez-vous vraiment ?" 522 delete_confirm: "Êtes-vous sûr ?"
523 back_to_list: "Revenir à la liste"
524 search:
525 placeholder: "Filtrer par nom d’utilisateur ou email"
526
527site_credential:
528 page_title: Gestion des accès aux sites
529 new_site_credential: Créer un accès à un site
530 edit_site_credential: Éditer l'accès d'un site
531 description: "Ici vous pouvez gérer les accès aux différents sites. Ces accès permettent de récupérer des contenus sur des sites qui requièrent une authentification ou un paywall"
532 list:
533 actions: Actions
534 edit_action: Éditer
535 yes: Oui
536 no: Non
537 create_new_one: Créer un nouvel accès à un site
538 form:
539 username_label: 'Identifiant'
540 host_label: 'Domaine'
541 password_label: 'Mot de passe'
542 save: "Sauvegarder"
543 delete: "Supprimer"
544 delete_confirm: "Êtes-vous sûr ?"
512 back_to_list: "Revenir à la liste" 545 back_to_list: "Revenir à la liste"
513 546
514error: 547error:
515 page_title: Une erreur est survenue 548 page_title: "Une erreur est survenue"
516 549
517flashes: 550flashes:
518 config: 551 config:
@@ -525,9 +558,10 @@ flashes:
525 tagging_rules_updated: "Règles mises à jour" 558 tagging_rules_updated: "Règles mises à jour"
526 tagging_rules_deleted: "Règle supprimée" 559 tagging_rules_deleted: "Règle supprimée"
527 rss_token_updated: "Jeton RSS mis à jour" 560 rss_token_updated: "Jeton RSS mis à jour"
528 annotations_reset: Annotations supprimées 561 annotations_reset: "Annotations supprimées"
529 tags_reset: Tags supprimés 562 tags_reset: "Tags supprimés"
530 entries_reset: Articles supprimés 563 entries_reset: "Articles supprimés"
564 archived_reset: "Articles archivés supprimés"
531 entry: 565 entry:
532 notice: 566 notice:
533 entry_already_saved: "Article déjà sauvegardé le %date%" 567 entry_already_saved: "Article déjà sauvegardé le %date%"
@@ -562,3 +596,8 @@ flashes:
562 added: 'Utilisateur "%username%" ajouté' 596 added: 'Utilisateur "%username%" ajouté'
563 updated: 'Utilisateur "%username%" mis à jour' 597 updated: 'Utilisateur "%username%" mis à jour'
564 deleted: 'Utilisateur "%username%" supprimé' 598 deleted: 'Utilisateur "%username%" supprimé'
599 site_credential:
600 notice:
601 added: 'Accès au site "%host%" ajouté'
602 updated: 'Accès au site "%host%" mis à jour'
603 deleted: 'Accès au site "%host%" supprimé'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
index 3cd3fd17..fa7ae0b2 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
@@ -91,9 +91,10 @@ config:
91 token_reset: 'Rigenera il tuo token' 91 token_reset: 'Rigenera il tuo token'
92 rss_links: 'Collegamenti RSS' 92 rss_links: 'Collegamenti RSS'
93 rss_link: 93 rss_link:
94 unread: 'non letti' 94 unread: 'Non letti'
95 starred: 'preferiti' 95 starred: 'Preferiti'
96 archive: 'archiviati' 96 archive: 'Archiviati'
97 # all: 'All'
97 rss_limit: 'Numero di elementi nel feed' 98 rss_limit: 'Numero di elementi nel feed'
98 form_user: 99 form_user:
99 two_factor_description: "Abilitando l'autenticazione a due fattori riceverai una e-mail con un codice per ogni nuova connesione non verificata" 100 two_factor_description: "Abilitando l'autenticazione a due fattori riceverai una e-mail con un codice per ogni nuova connesione non verificata"
@@ -112,6 +113,7 @@ config:
112 annotations: Rimuovi TUTTE le annotazioni 113 annotations: Rimuovi TUTTE le annotazioni
113 tags: Rimuovi TUTTE le etichette 114 tags: Rimuovi TUTTE le etichette
114 entries: Rimuovi TUTTI gli articoli 115 entries: Rimuovi TUTTI gli articoli
116 # archived: Remove ALL archived entries
115 confirm: Sei veramente sicuro? (NON PUOI TORNARE INDIETRO) 117 confirm: Sei veramente sicuro? (NON PUOI TORNARE INDIETRO)
116 form_password: 118 form_password:
117 description: "Qui puoi cambiare la tua password. La tua nuova password dovrebbe essere composta da almeno 8 caratteri." 119 description: "Qui puoi cambiare la tua password. La tua nuova password dovrebbe essere composta da almeno 8 caratteri."
@@ -156,6 +158,7 @@ config:
156 or: "Una regola O un'altra" 158 or: "Una regola O un'altra"
157 and: "Una regola E un'altra" 159 and: "Una regola E un'altra"
158 matches: 'Verifica che un <i>oggetto</i> risulti in una <i>ricerca</i> (case-insensitive).<br />Esempio: <code>titolo contiene "football"</code>' 160 matches: 'Verifica che un <i>oggetto</i> risulti in una <i>ricerca</i> (case-insensitive).<br />Esempio: <code>titolo contiene "football"</code>'
161 # notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
159 162
160entry: 163entry:
161 page_titles: 164 page_titles:
@@ -187,6 +190,8 @@ entry:
187 unread_label: 'Non letti' 190 unread_label: 'Non letti'
188 preview_picture_label: "Ha un'immagine di anteprima" 191 preview_picture_label: "Ha un'immagine di anteprima"
189 preview_picture_help: 'Immagine di anteprima' 192 preview_picture_help: 'Immagine di anteprima'
193 # is_public_label: 'Has a public link'
194 # is_public_help: 'Public link'
190 language_label: 'Lingua' 195 language_label: 'Lingua'
191 http_status_label: 'Stato HTTP' 196 http_status_label: 'Stato HTTP'
192 reading_time: 197 reading_time:
@@ -211,7 +216,7 @@ entry:
211 view_original_article: 'Contenuto originale' 216 view_original_article: 'Contenuto originale'
212 re_fetch_content: 'Ri-ottieni pagina' 217 re_fetch_content: 'Ri-ottieni pagina'
213 delete: 'Elimina' 218 delete: 'Elimina'
214 add_a_tag: 'Aggiungi un tag' 219 add_a_tag: 'Aggiungi un''etichetta'
215 share_content: 'Condividi' 220 share_content: 'Condividi'
216 share_email_label: 'E-mail' 221 share_email_label: 'E-mail'
217 public_link: 'Link pubblico' 222 public_link: 'Link pubblico'
@@ -222,7 +227,7 @@ entry:
222 label: 'Problemi?' 227 label: 'Problemi?'
223 description: 'Questo contenuto viene visualizzato male?' 228 description: 'Questo contenuto viene visualizzato male?'
224 edit_title: 'Modifica titolo' 229 edit_title: 'Modifica titolo'
225 original_article: 'originale' 230 original_article: 'Originale'
226 annotations_on_the_entry: '{0} Nessuna annotazione|{1} Una annotazione|]1,Inf[ %count% annotazioni' 231 annotations_on_the_entry: '{0} Nessuna annotazione|{1} Una annotazione|]1,Inf[ %count% annotazioni'
227 created_at: 'Data di creazione' 232 created_at: 'Data di creazione'
228 published_at: 'Data di pubblicazione' 233 published_at: 'Data di pubblicazione'
@@ -238,13 +243,15 @@ entry:
238 page_title: 'Modifica voce' 243 page_title: 'Modifica voce'
239 title_label: 'Titolo' 244 title_label: 'Titolo'
240 url_label: 'Url' 245 url_label: 'Url'
241 is_public_label: 'Pubblico'
242 save_label: 'Salva' 246 save_label: 'Salva'
243 public: 247 public:
244 shared_by_wallabag: "Questo articolo è stato condiviso da %username% con <a href='%wallabag_instance%'>wallabag</a>" 248 shared_by_wallabag: "Questo articolo è stato condiviso da %username% con <a href='%wallabag_instance%'>wallabag</a>"
249 confirm:
250 delete: "Vuoi veramente rimuovere quell'articolo?"
251 delete_tag: "Vuoi veramente rimuovere quell'etichetta da quell'articolo?"
245 252
246about: 253about:
247 page_title: 'About' 254 page_title: 'A proposito'
248 top_menu: 255 top_menu:
249 who_behind_wallabag: "Chi c'è dietro a wallabag" 256 who_behind_wallabag: "Chi c'è dietro a wallabag"
250 getting_help: 'Ottieni aiuto' 257 getting_help: 'Ottieni aiuto'
@@ -263,7 +270,7 @@ about:
263 bug_reports: 'Bug reports' 270 bug_reports: 'Bug reports'
264 support: '<a href="https://github.com/wallabag/wallabag/issues">su GitHub</a>' 271 support: '<a href="https://github.com/wallabag/wallabag/issues">su GitHub</a>'
265 helping: 272 helping:
266 description: 'wallabag è gratuito opensource. Puoi aiutarci:' 273 description: 'wallabag è gratuito ed OpenSource. Puoi aiutarci:'
267 by_contributing: 'per contribuire al progetto:' 274 by_contributing: 'per contribuire al progetto:'
268 by_contributing_2: 'un elenco delle attività richieste' 275 by_contributing_2: 'un elenco delle attività richieste'
269 by_paypal: 'via Paypal' 276 by_paypal: 'via Paypal'
@@ -331,7 +338,7 @@ quickstart:
331 more: 'Più…' 338 more: 'Più…'
332 intro: 339 intro:
333 title: 'Benvenuto su wallabag!' 340 title: 'Benvenuto su wallabag!'
334 paragraph_1: "Un tour in cui ti guideremo per scoprire e che ti mostrerà delle funzionalità che potrebbero interessarti." 341 paragraph_1: "Ti accompagneremo alla scoperta di wallabag e ti mostreremo delle funzionalità che potrebbero interessarti."
335 paragraph_2: 'Seguici!' 342 paragraph_2: 'Seguici!'
336 configure: 343 configure:
337 title: "Configura l'applicazione" 344 title: "Configura l'applicazione"
@@ -401,20 +408,20 @@ import:
401 save_label: 'Carica file' 408 save_label: 'Carica file'
402 pocket: 409 pocket:
403 page_title: 'Importa da > Pocket' 410 page_title: 'Importa da > Pocket'
404 description: "Questo importatore copierà tutti i tuoi dati da Pocket. Pocket non ci consente di ottenere contenuti dal loro servzio, così il contenuto leggibile di ogni articolo verrà ri-ottenuto da wallabag." 411 description: "Questo importatore copierà tutti i tuoi dati da Pocket. Pocket non ci consente di ottenere contenuti dal loro servizio, così il contenuto leggibile di ogni articolo verrà ri-ottenuto da wallabag."
405 config_missing: 412 config_missing:
406 description: "Importazione da Pocket non configurata." 413 description: "Importazione da Pocket non configurata."
407 admin_message: 'Devi definire %keyurls% una pocket_consumer_key %keyurle%.' 414 admin_message: 'Devi definire %keyurls% una pocket_consumer_key %keyurle%.'
408 user_message: 'Il tuo amministratore di server deve define una API Key per Pocket.' 415 user_message: 'Il tuo amministratore del server deve definire una API Key per Pocket.'
409 authorize_message: 'Puoi importare dati dal tuo account Pocket. Devi solo cliccare sul pulsante sottostante e autorizzare la connessione a getpocket.com.' 416 authorize_message: 'Puoi importare dati dal tuo account Pocket. Devi solo cliccare sul pulsante sottostante e autorizzare la connessione a getpocket.com.'
410 connect_to_pocket: 'Connetti a Pocket and importa i dati' 417 connect_to_pocket: 'Connetti a Pocket and importa i dati'
411 wallabag_v1: 418 wallabag_v1:
412 page_title: 'Importa da > Wallabag v1' 419 page_title: 'Importa da > Wallabag v1'
413 description: 'Questo importatore copierà tutti i tuoi dati da un wallabag v1. Nella tua pagina di configurazione, clicca su "JSON export" nella sezione "Esport i tuoi dati di wallabag". Otterrai un file "wallabag-export-1-xxxx-xx-xx.json".' 420 description: 'Questo importatore copierà tutti i tuoi dati da un wallabag v1. Nella tua pagina di configurazione, clicca su "JSON export" nella sezione "Esporta i tuoi dati di wallabag". Otterrai un file "wallabag-export-1-xxxx-xx-xx.json".'
414 how_to: 'Seleziona la tua esportazione di wallabag e clicca sul pulsante sottostante caricare il file e importare i dati.' 421 how_to: 'Seleziona la tua esportazione di wallabag e clicca sul pulsante sottostante caricare il file e importare i dati.'
415 wallabag_v2: 422 wallabag_v2:
416 page_title: 'Importa da > Wallabag v2' 423 page_title: 'Importa da > Wallabag v2'
417 description: 'Questo importatore copierà tutti i tuoi dati da un wallabag v2. Vai in "Tutti i contenuti", e, nella sidebar di esportazione, clicca su "JSON". Otterrai un file "Tutti i contenuti.json".' 424 description: 'Questo importatore copierà tutti i tuoi dati da un wallabag v2. Vai in "Tutti i contenuti", e, nella barra laterale di esportazione, clicca su "JSON". Otterrai un file "Tutti i contenuti.json'
418 readability: 425 readability:
419 page_title: 'Importa da > Readability' 426 page_title: 'Importa da > Readability'
420 description: 'Questo importatore copierà tutti i tuoi articoli da Readability. Nella pagina strumenti (https://www.readability.com/tools/), clicca su "Export your data" nella sezione "Data Export". Riceverai una E-mail per scaricare un file json (che tuttavia non termina con .json).' 427 description: 'Questo importatore copierà tutti i tuoi articoli da Readability. Nella pagina strumenti (https://www.readability.com/tools/), clicca su "Export your data" nella sezione "Data Export". Riceverai una E-mail per scaricare un file json (che tuttavia non termina con .json).'
@@ -514,6 +521,28 @@ user:
514 delete: Cancella 521 delete: Cancella
515 delete_confirm: Sei sicuro? 522 delete_confirm: Sei sicuro?
516 back_to_list: Torna alla lista 523 back_to_list: Torna alla lista
524 search:
525 # placeholder: Filter by username or email
526
527site_credential:
528 # page_title: Site credentials management
529 # new_site_credential: Create a credential
530 # edit_site_credential: Edit an existing credential
531 # description: "Here you can manage all credentials for sites which required them (create, edit and delete), like a paywall, an authentication, etc."
532 # list:
533 # actions: Actions
534 # edit_action: Edit
535 # yes: Yes
536 # no: No
537 # create_new_one: Create a new credential
538 # form:
539 # username_label: 'Username'
540 # host_label: 'Host'
541 # password_label: 'Password'
542 # save: Save
543 # delete: Delete
544 # delete_confirm: Are you sure?
545 # back_to_list: Back to list
517 546
518error: 547error:
519 page_title: Si è verificato un errore 548 page_title: Si è verificato un errore
@@ -532,6 +561,7 @@ flashes:
532 annotations_reset: Reset annotazioni 561 annotations_reset: Reset annotazioni
533 tags_reset: Reset etichette 562 tags_reset: Reset etichette
534 entries_reset: Reset articoli 563 entries_reset: Reset articoli
564 # archived_reset: Archived entries deleted
535 entry: 565 entry:
536 notice: 566 notice:
537 entry_already_saved: 'Contenuto già salvato in data %date%' 567 entry_already_saved: 'Contenuto già salvato in data %date%'
@@ -566,3 +596,8 @@ flashes:
566 added: 'Utente "%username%" aggiunto' 596 added: 'Utente "%username%" aggiunto'
567 updated: 'Utente "%username%" aggiornato' 597 updated: 'Utente "%username%" aggiornato'
568 deleted: 'Utente "%username%" eliminato' 598 deleted: 'Utente "%username%" eliminato'
599 site_credential:
600 notice:
601 # added: 'Site credential for "%host%" added'
602 # updated: 'Site credential for "%host%" updated'
603 # deleted: 'Site credential for "%host%" deleted'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
index 913e3bcb..be57e903 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
@@ -32,6 +32,7 @@ menu:
32 save_link: 'Enregistrar un novèl article' 32 save_link: 'Enregistrar un novèl article'
33 back_to_unread: 'Tornar als articles pas legits' 33 back_to_unread: 'Tornar als articles pas legits'
34 users_management: 'Gestion dels utilizaires' 34 users_management: 'Gestion dels utilizaires'
35 # site_credentials: 'Site credentials'
35 top: 36 top:
36 add_new_entry: 'Enregistrar un novèl article' 37 add_new_entry: 'Enregistrar un novèl article'
37 search: 'Cercar' 38 search: 'Cercar'
@@ -76,6 +77,7 @@ config:
76 redirect_current_page: 'A la pagina actuala' 77 redirect_current_page: 'A la pagina actuala'
77 pocket_consumer_key_label: Clau d'autentificacion Pocket per importar las donadas 78 pocket_consumer_key_label: Clau d'autentificacion Pocket per importar las donadas
78 android_configuration: Configuratz vòstra aplicacion Android 79 android_configuration: Configuratz vòstra aplicacion Android
80 # android_instruction: "Touch here to prefill your Android application"
79 help_theme: "wallabag es personalizable. Podètz causir vòstre tèma preferit aquí." 81 help_theme: "wallabag es personalizable. Podètz causir vòstre tèma preferit aquí."
80 help_items_per_page: "Podètz cambiar lo nombre d'articles afichats per pagina." 82 help_items_per_page: "Podètz cambiar lo nombre d'articles afichats per pagina."
81 help_reading_speed: "wallabag calcula lo temps de lectura per cada article. Podètz lo definir aquí, gràcias a aquesta lista, se sètz un legeire rapid o lent. wallabag tornarà calcular lo temps de lectura per cada article." 83 help_reading_speed: "wallabag calcula lo temps de lectura per cada article. Podètz lo definir aquí, gràcias a aquesta lista, se sètz un legeire rapid o lent. wallabag tornarà calcular lo temps de lectura per cada article."
@@ -87,11 +89,12 @@ config:
87 no_token: 'Pas cap de geton generat' 89 no_token: 'Pas cap de geton generat'
88 token_create: 'Creatz vòstre geton' 90 token_create: 'Creatz vòstre geton'
89 token_reset: 'Reïnicializatz vòstre geton' 91 token_reset: 'Reïnicializatz vòstre geton'
90 rss_links: 'URL de vòstres fluxes RSS' 92 rss_links: 'URLs de vòstres fluxes RSS'
91 rss_link: 93 rss_link:
92 unread: 'pas legits' 94 unread: 'Pas legits'
93 starred: 'favorits' 95 starred: 'Favorits'
94 archive: 'legits' 96 archive: 'Legits'
97 # all: 'All'
95 rss_limit: "Nombre d'articles dins un flux RSS" 98 rss_limit: "Nombre d'articles dins un flux RSS"
96 form_user: 99 form_user:
97 two_factor_description: "Activar l'autentificacion doble-factor vòl dire que recebretz un còdi per corrièl per cada novèla connexion pas aprovada." 100 two_factor_description: "Activar l'autentificacion doble-factor vòl dire que recebretz un còdi per corrièl per cada novèla connexion pas aprovada."
@@ -110,6 +113,7 @@ config:
110 annotations: Levar TOTAS las anotacions 113 annotations: Levar TOTAS las anotacions
111 tags: Levar TOTAS las etiquetas 114 tags: Levar TOTAS las etiquetas
112 entries: Levar TOTES los articles 115 entries: Levar TOTES los articles
116 archived: Levar TOTES los articles archivats
113 confirm: Sètz vertadièrament segur ? (ES IRREVERSIBLE) 117 confirm: Sètz vertadièrament segur ? (ES IRREVERSIBLE)
114 form_password: 118 form_password:
115 description: "Podètz cambiar vòstre senhal aquí. Vòstre senhal deu èsser long d'almens 8 caractèrs." 119 description: "Podètz cambiar vòstre senhal aquí. Vòstre senhal deu èsser long d'almens 8 caractèrs."
@@ -153,7 +157,8 @@ config:
153 not_equal_to: 'Diferent de…' 157 not_equal_to: 'Diferent de…'
154 or: "Una règla O l'autra" 158 or: "Una règla O l'autra"
155 and: "Una règla E l'autra" 159 and: "Una règla E l'autra"
156 matches: 'Teste se un <i>subjècte</i> correspond a una <i>recerca</i> (non sensibla a la cassa).<br />Exemple : <code>title matches \"football\"</code>' 160 matches: 'Teste se un <i>subjècte</i> correspond a una <i>recèrca</i> (non sensibla a la cassa).<br />Exemple : <code>title matches \"football\"</code>'
161 notmatches: 'Teste se <i>subjècte</i> correspond pas a una <i>recèrca</i> (sensibla a la cassa).<br />Example : <code>title notmatches "football"</code>'
157 162
158entry: 163entry:
159 page_titles: 164 page_titles:
@@ -183,8 +188,10 @@ entry:
183 archived_label: 'Legits' 188 archived_label: 'Legits'
184 starred_label: 'Favorits' 189 starred_label: 'Favorits'
185 unread_label: 'Pas legits' 190 unread_label: 'Pas legits'
186 preview_picture_label: 'A una fotò' 191 preview_picture_label: 'A un imatge'
187 preview_picture_help: 'Fotò' 192 preview_picture_help: 'Imatge'
193 # is_public_label: 'Has a public link'
194 # is_public_help: 'Public link'
188 language_label: 'Lenga' 195 language_label: 'Lenga'
189 http_status_label: 'Estatut HTTP' 196 http_status_label: 'Estatut HTTP'
190 reading_time: 197 reading_time:
@@ -223,6 +230,8 @@ entry:
223 original_article: 'original' 230 original_article: 'original'
224 annotations_on_the_entry: "{0} Pas cap d'anotacion|{1} Una anotacion|]1,Inf[ %count% anotacions" 231 annotations_on_the_entry: "{0} Pas cap d'anotacion|{1} Una anotacion|]1,Inf[ %count% anotacions"
225 created_at: 'Data de creacion' 232 created_at: 'Data de creacion'
233 published_at: 'Data de publicacion'
234 published_by: 'Publicat per'
226 new: 235 new:
227 page_title: 'Enregistrar un novèl article' 236 page_title: 'Enregistrar un novèl article'
228 placeholder: 'http://website.com' 237 placeholder: 'http://website.com'
@@ -234,10 +243,12 @@ entry:
234 page_title: 'Modificar un article' 243 page_title: 'Modificar un article'
235 title_label: 'Títol' 244 title_label: 'Títol'
236 url_label: 'Url' 245 url_label: 'Url'
237 is_public_label: 'Public'
238 save_label: 'Enregistrar' 246 save_label: 'Enregistrar'
239 public: 247 public:
240 shared_by_wallabag: "Aqueste article es estat partejat per <a href='%wallabag_instance%'>wallabag</a>" 248 shared_by_wallabag: "Aqueste article es estat partejat per <a href='%wallabag_instance%'>wallabag</a>"
249 confirm:
250 # delete: "Are you sure you want to remove that article?"
251 # delete_tag: "Are you sure you want to remove that tag from that article?"
241 252
242about: 253about:
243 page_title: 'A prepaus' 254 page_title: 'A prepaus'
@@ -341,8 +352,8 @@ quickstart:
341 new_user: 'Crear un novèl utilizaire' 352 new_user: 'Crear un novèl utilizaire'
342 analytics: 'Configurar las estadisticas' 353 analytics: 'Configurar las estadisticas'
343 sharing: 'Activar de paramètres de partatge' 354 sharing: 'Activar de paramètres de partatge'
344 export: 'Configurar los expòrt' 355 export: 'Configurar los expòrts'
345 import: 'Configurar los impòrt' 356 import: 'Configurar los impòrts'
346 first_steps: 357 first_steps:
347 title: 'Primièrs passes' 358 title: 'Primièrs passes'
348 description: "Ara wallabag es ben configurat, es lo moment d'archivar lo web. Podètz clicar sul signe + a man drecha amont per ajustar un ligam." 359 description: "Ara wallabag es ben configurat, es lo moment d'archivar lo web. Podètz clicar sul signe + a man drecha amont per ajustar un ligam."
@@ -458,7 +469,7 @@ developer:
458 action: 'Suprimir aqueste client' 469 action: 'Suprimir aqueste client'
459 client: 470 client:
460 page_title: 'Gestion dels clients API > Novèl client' 471 page_title: 'Gestion dels clients API > Novèl client'
461 page_description: "Anatz crear un novèl client. Mercés de cumplir l'url de redireccion cap a vòstra aplicacion." 472 page_description: "Anatz crear un novèl client. Mercés de garnir l'url de redireccion cap a vòstra aplicacion."
462 form: 473 form:
463 name_label: "Nom del client" 474 name_label: "Nom del client"
464 redirect_uris_label: 'URLs de redireccion' 475 redirect_uris_label: 'URLs de redireccion'
@@ -469,7 +480,7 @@ developer:
469 page_description: 'Vaquí los paramètres de vòstre client.' 480 page_description: 'Vaquí los paramètres de vòstre client.'
470 field_name: 'Nom del client' 481 field_name: 'Nom del client'
471 field_id: 'ID Client' 482 field_id: 'ID Client'
472 field_secret: 'Clau secreta' 483 field_secret: 'Clau secrèta'
473 back: 'Retour' 484 back: 'Retour'
474 read_howto: 'Legir "cossí crear ma primièra aplicacion"' 485 read_howto: 'Legir "cossí crear ma primièra aplicacion"'
475 howto: 486 howto:
@@ -510,6 +521,28 @@ user:
510 delete: 'Suprimir' 521 delete: 'Suprimir'
511 delete_confirm: 'Sètz segur ?' 522 delete_confirm: 'Sètz segur ?'
512 back_to_list: 'Tornar a la lista' 523 back_to_list: 'Tornar a la lista'
524 search:
525 placeholder: "Filtrar per nom d'utilizaire o corrièl"
526
527site_credential:
528 # page_title: Site credentials management
529 # new_site_credential: Create a credential
530 # edit_site_credential: Edit an existing credential
531 # description: "Here you can manage all credentials for sites which required them (create, edit and delete), like a paywall, an authentication, etc."
532 list:
533 actions: 'Accions'
534 edit_action: 'Modificar'
535 yes: 'Òc'
536 no: 'Non'
537 # create_new_one: Create a new credential
538 form:
539 # username_label: 'Username'
540 # host_label: 'Host'
541 # password_label: 'Password'
542 save: 'Enregistrar'
543 delete: 'Suprimir'
544 delete_confirm: 'Sètz segur ?'
545 back_to_list: 'Tornar a la lista'
513 546
514error: 547error:
515 page_title: Una error s'es produsida 548 page_title: Una error s'es produsida
@@ -528,6 +561,7 @@ flashes:
528 annotations_reset: Anotacions levadas 561 annotations_reset: Anotacions levadas
529 tags_reset: Etiquetas levadas 562 tags_reset: Etiquetas levadas
530 entries_reset: Articles levats 563 entries_reset: Articles levats
564 archived_reset: Articles archivat suprimits
531 entry: 565 entry:
532 notice: 566 notice:
533 entry_already_saved: 'Article ja salvargardat lo %date%' 567 entry_already_saved: 'Article ja salvargardat lo %date%'
@@ -562,3 +596,8 @@ flashes:
562 added: 'Utilizaire "%username%" ajustat' 596 added: 'Utilizaire "%username%" ajustat'
563 updated: 'Utilizaire "%username%" mes a jorn' 597 updated: 'Utilizaire "%username%" mes a jorn'
564 deleted: 'Utilizaire "%username%" suprimit' 598 deleted: 'Utilizaire "%username%" suprimit'
599 site_credential:
600 notice:
601 # added: 'Site credential for "%host%" added'
602 # updated: 'Site credential for "%host%" updated'
603 # deleted: 'Site credential for "%host%" deleted'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
index b990a6b9..00c559ed 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
@@ -32,6 +32,7 @@ menu:
32 save_link: 'Zapisz link' 32 save_link: 'Zapisz link'
33 back_to_unread: 'Powrót do nieprzeczytanych artykułów' 33 back_to_unread: 'Powrót do nieprzeczytanych artykułów'
34 users_management: 'Zarządzanie użytkownikami' 34 users_management: 'Zarządzanie użytkownikami'
35 # site_credentials: 'Site credentials'
35 top: 36 top:
36 add_new_entry: 'Dodaj nowy wpis' 37 add_new_entry: 'Dodaj nowy wpis'
37 search: 'Szukaj' 38 search: 'Szukaj'
@@ -76,6 +77,7 @@ config:
76 redirect_current_page: 'do bieżącej strony' 77 redirect_current_page: 'do bieżącej strony'
77 pocket_consumer_key_label: 'Klucz klienta Pocket do importu zawartości' 78 pocket_consumer_key_label: 'Klucz klienta Pocket do importu zawartości'
78 android_configuration: Skonfiguruj swoją androidową aplikację 79 android_configuration: Skonfiguruj swoją androidową aplikację
80 android_instruction: "Dotknij tutaj, aby wstępnie uzupełnij androidową aplikację"
79 help_theme: "Dopasuj wallabag do swoich potrzeb. Tutaj możesz wybrać preferowany przez ciebie motyw." 81 help_theme: "Dopasuj wallabag do swoich potrzeb. Tutaj możesz wybrać preferowany przez ciebie motyw."
80 help_items_per_page: "Możesz zmienić ilość artykułów wyświetlanych na każdej stronie." 82 help_items_per_page: "Możesz zmienić ilość artykułów wyświetlanych na każdej stronie."
81 help_reading_speed: "wallabag oblicza czas czytania każdego artykułu. Dzięki tej liście możesz określić swoje tempo. Wallabag przeliczy ponownie czas potrzebny, na przeczytanie każdego z artykułów." 83 help_reading_speed: "wallabag oblicza czas czytania każdego artykułu. Dzięki tej liście możesz określić swoje tempo. Wallabag przeliczy ponownie czas potrzebny, na przeczytanie każdego z artykułów."
@@ -89,9 +91,10 @@ config:
89 token_reset: 'Zresetuj swojego tokena' 91 token_reset: 'Zresetuj swojego tokena'
90 rss_links: 'RSS links' 92 rss_links: 'RSS links'
91 rss_link: 93 rss_link:
92 unread: 'nieprzeczytane' 94 unread: 'Nieprzeczytane'
93 starred: 'oznaczone gwiazdką' 95 starred: 'Oznaczone gwiazdką'
94 archive: 'archiwum' 96 archive: 'Archiwum'
97 # all: 'All'
95 rss_limit: 'Link do RSS' 98 rss_limit: 'Link do RSS'
96 form_user: 99 form_user:
97 two_factor_description: "Włączenie autoryzacji dwuetapowej oznacza, że będziesz otrzymywał maile z kodem przy każdym nowym, niezaufanym połączeniu" 100 two_factor_description: "Włączenie autoryzacji dwuetapowej oznacza, że będziesz otrzymywał maile z kodem przy każdym nowym, niezaufanym połączeniu"
@@ -110,6 +113,7 @@ config:
110 annotations: Usuń WSZYSTKIE adnotacje 113 annotations: Usuń WSZYSTKIE adnotacje
111 tags: Usuń WSZYSTKIE tagi 114 tags: Usuń WSZYSTKIE tagi
112 entries: usuń WSZYTSTKIE wpisy 115 entries: usuń WSZYTSTKIE wpisy
116 archived: usuń WSZYSTKIE zarchiwizowane wpisy
113 confirm: Jesteś pewien? (tej operacji NIE MOŻNA cofnąć) 117 confirm: Jesteś pewien? (tej operacji NIE MOŻNA cofnąć)
114 form_password: 118 form_password:
115 description: "Tutaj możesz zmienić swoje hasło. Twoje nowe hasło powinno mieć conajmniej 8 znaków." 119 description: "Tutaj możesz zmienić swoje hasło. Twoje nowe hasło powinno mieć conajmniej 8 znaków."
@@ -154,6 +158,7 @@ config:
154 or: 'Jedna reguła LUB inna' 158 or: 'Jedna reguła LUB inna'
155 and: 'Jedna reguła I inna' 159 and: 'Jedna reguła I inna'
156 matches: 'Sprawdź czy <i>temat</i> pasuje <i>szukaj</i> (duże lub małe litery).<br />Przykład: <code>tytuł zawiera "piłka nożna"</code>' 160 matches: 'Sprawdź czy <i>temat</i> pasuje <i>szukaj</i> (duże lub małe litery).<br />Przykład: <code>tytuł zawiera "piłka nożna"</code>'
161 notmatches: 'Sprawdź czy <i>temat</i> nie zawiera <i>szukaj</i> (duże lub małe litery).<br />Przykład: <code>tytuł nie zawiera "piłka nożna"</code>'
157 162
158entry: 163entry:
159 page_titles: 164 page_titles:
@@ -185,6 +190,8 @@ entry:
185 unread_label: 'Nieprzeczytane' 190 unread_label: 'Nieprzeczytane'
186 preview_picture_label: 'Posiada podgląd obrazu' 191 preview_picture_label: 'Posiada podgląd obrazu'
187 preview_picture_help: 'Podgląd obrazu' 192 preview_picture_help: 'Podgląd obrazu'
193 is_public_label: 'Posiada publiczny link'
194 is_public_help: 'Publiczny link'
188 language_label: 'Język' 195 language_label: 'Język'
189 http_status_label: 'Status HTTP' 196 http_status_label: 'Status HTTP'
190 reading_time: 197 reading_time:
@@ -223,6 +230,8 @@ entry:
223 original_article: 'oryginalny' 230 original_article: 'oryginalny'
224 annotations_on_the_entry: '{0} Nie ma adnotacji |{1} Jedna adnotacja |]1,Inf[ %count% adnotacji' 231 annotations_on_the_entry: '{0} Nie ma adnotacji |{1} Jedna adnotacja |]1,Inf[ %count% adnotacji'
225 created_at: 'Czas stworzenia' 232 created_at: 'Czas stworzenia'
233 published_at: 'Data publikacji'
234 published_by: 'Opublikowane przez'
226 new: 235 new:
227 page_title: 'Zapisz nowy wpis' 236 page_title: 'Zapisz nowy wpis'
228 placeholder: 'http://website.com' 237 placeholder: 'http://website.com'
@@ -234,10 +243,12 @@ entry:
234 page_title: 'Edytuj wpis' 243 page_title: 'Edytuj wpis'
235 title_label: 'Tytuł' 244 title_label: 'Tytuł'
236 url_label: 'Adres URL' 245 url_label: 'Adres URL'
237 is_public_label: 'Publiczny'
238 save_label: 'Zapisz' 246 save_label: 'Zapisz'
239 public: 247 public:
240 shared_by_wallabag: "Ten artykuł został udostępniony przez <a href='%wallabag_instance%'>wallabag</a>" 248 shared_by_wallabag: "Ten artykuł został udostępniony przez <a href='%wallabag_instance%'>wallabag</a>"
249 confirm:
250 delete: "Czy jesteś pewien, że chcesz usunąć ten artykuł?"
251 delete_tag: "Czy jesteś pewien, że chcesz usunąć ten tag, z tego artykułu?"
241 252
242about: 253about:
243 page_title: 'O nas' 254 page_title: 'O nas'
@@ -510,6 +521,28 @@ user:
510 delete: Usuń 521 delete: Usuń
511 delete_confirm: Jesteś pewien? 522 delete_confirm: Jesteś pewien?
512 back_to_list: Powrót do listy 523 back_to_list: Powrót do listy
524 search:
525 placeholder: Filtruj po nazwie użytkownika lub adresie e-mail
526
527site_credential:
528 # page_title: Site credentials management
529 # new_site_credential: Create a credential
530 # edit_site_credential: Edit an existing credential
531 # description: "Here you can manage all credentials for sites which required them (create, edit and delete), like a paywall, an authentication, etc."
532 list:
533 actions: Akcje
534 edit_action: Edytuj
535 yes: Tak
536 no: Nie
537 # create_new_one: Create a new credential
538 form:
539 # username_label: 'Username'
540 # host_label: 'Host'
541 # password_label: 'Password'
542 save: Zapisz
543 delete: Usuń
544 delete_confirm: Jesteś pewien?
545 back_to_list: Powrót do listy
513 546
514error: 547error:
515 page_title: Wystąpił błąd 548 page_title: Wystąpił błąd
@@ -528,6 +561,7 @@ flashes:
528 annotations_reset: Zresetuj adnotacje 561 annotations_reset: Zresetuj adnotacje
529 tags_reset: Zresetuj tagi 562 tags_reset: Zresetuj tagi
530 entries_reset: Zresetuj wpisy 563 entries_reset: Zresetuj wpisy
564 archived_reset: Zarchiwizowane wpisy usunięte
531 entry: 565 entry:
532 notice: 566 notice:
533 entry_already_saved: 'Wpis już został dodany %date%' 567 entry_already_saved: 'Wpis już został dodany %date%'
@@ -562,3 +596,8 @@ flashes:
562 added: 'Użytkownik "%username%" dodany' 596 added: 'Użytkownik "%username%" dodany'
563 updated: 'Użytkownik "%username%" zaktualizowany' 597 updated: 'Użytkownik "%username%" zaktualizowany'
564 deleted: 'Użytkownik "%username%" usunięty' 598 deleted: 'Użytkownik "%username%" usunięty'
599 site_credential:
600 notice:
601 # added: 'Site credential for "%host%" added'
602 # updated: 'Site credential for "%host%" updated'
603 # deleted: 'Site credential for "%host%" deleted'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
index 3b1f9cb6..4ab5f144 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
@@ -32,6 +32,7 @@ menu:
32 save_link: 'Salvar um link' 32 save_link: 'Salvar um link'
33 back_to_unread: 'Voltar para os artigos não lidos' 33 back_to_unread: 'Voltar para os artigos não lidos'
34 users_management: 'Gestão de Usuários' 34 users_management: 'Gestão de Usuários'
35 # site_credentials: 'Site credentials'
35 top: 36 top:
36 add_new_entry: 'Adicionar uma nova entrada' 37 add_new_entry: 'Adicionar uma nova entrada'
37 search: 'Pesquisa' 38 search: 'Pesquisa'
@@ -76,6 +77,7 @@ config:
76 # redirect_current_page: 'To the current page' 77 # redirect_current_page: 'To the current page'
77 pocket_consumer_key_label: 'Chave do consumidor do Pocket para importar conteúdo' 78 pocket_consumer_key_label: 'Chave do consumidor do Pocket para importar conteúdo'
78 # android_configuration: Configure your Android application 79 # android_configuration: Configure your Android application
80 # android_instruction: "Touch here to prefill your Android application"
79 # help_theme: "wallabag is customizable. You can choose your prefered theme here." 81 # help_theme: "wallabag is customizable. You can choose your prefered theme here."
80 # help_items_per_page: "You can change the number of articles displayed on each page." 82 # help_items_per_page: "You can change the number of articles displayed on each page."
81 # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article." 83 # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article."
@@ -89,9 +91,10 @@ config:
89 token_reset: 'Gerar novamente seu token' 91 token_reset: 'Gerar novamente seu token'
90 rss_links: 'Links RSS' 92 rss_links: 'Links RSS'
91 rss_link: 93 rss_link:
92 unread: 'não lido' 94 unread: 'Não lido'
93 starred: 'destacado' 95 starred: 'Destacado'
94 archive: 'arquivado' 96 archive: 'Arquivado'
97 # all: 'All'
95 rss_limit: 'Número de itens no feed' 98 rss_limit: 'Número de itens no feed'
96 form_user: 99 form_user:
97 two_factor_description: 'Habilitar autenticação de dois passos significa que você receberá um e-mail com um código a cada nova conexão desconhecida.' 100 two_factor_description: 'Habilitar autenticação de dois passos significa que você receberá um e-mail com um código a cada nova conexão desconhecida.'
@@ -110,6 +113,7 @@ config:
110 # annotations: Remove ALL annotations 113 # annotations: Remove ALL annotations
111 # tags: Remove ALL tags 114 # tags: Remove ALL tags
112 # entries: Remove ALL entries 115 # entries: Remove ALL entries
116 # archived: Remove ALL archived entries
113 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE) 117 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
114 form_password: 118 form_password:
115 # description: "You can change your password here. Your new password should by at least 8 characters long." 119 # description: "You can change your password here. Your new password should by at least 8 characters long."
@@ -154,6 +158,7 @@ config:
154 or: 'Uma regra OU outra' 158 or: 'Uma regra OU outra'
155 and: 'Uma regra E outra' 159 and: 'Uma regra E outra'
156 matches: 'Testa que um <i>assunto</i> corresponde a uma <i>pesquisa</i> (maiúscula ou minúscula).<br />Exemplo: <code>título corresponde a "futebol"</code>' 160 matches: 'Testa que um <i>assunto</i> corresponde a uma <i>pesquisa</i> (maiúscula ou minúscula).<br />Exemplo: <code>título corresponde a "futebol"</code>'
161 # notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
157 162
158entry: 163entry:
159 page_titles: 164 page_titles:
@@ -185,6 +190,8 @@ entry:
185 unread_label: 'Não Lido' 190 unread_label: 'Não Lido'
186 preview_picture_label: 'Possui uma imagem de preview' 191 preview_picture_label: 'Possui uma imagem de preview'
187 preview_picture_help: 'Imagem de preview' 192 preview_picture_help: 'Imagem de preview'
193 # is_public_label: 'Has a public link'
194 # is_public_help: 'Public link'
188 language_label: 'Idioma' 195 language_label: 'Idioma'
189 # http_status_label: 'HTTP status' 196 # http_status_label: 'HTTP status'
190 reading_time: 197 reading_time:
@@ -223,6 +230,8 @@ entry:
223 original_article: 'original' 230 original_article: 'original'
224 annotations_on_the_entry: '{0} Sem anotações|{1} Uma anotação|]1,Inf[ %nbAnnotations% anotações' 231 annotations_on_the_entry: '{0} Sem anotações|{1} Uma anotação|]1,Inf[ %nbAnnotations% anotações'
225 created_at: 'Data de criação' 232 created_at: 'Data de criação'
233 # published_at: 'Publication date'
234 # published_by: 'Published by'
226 new: 235 new:
227 page_title: 'Salvar nova entrada' 236 page_title: 'Salvar nova entrada'
228 placeholder: 'http://website.com' 237 placeholder: 'http://website.com'
@@ -234,10 +243,12 @@ entry:
234 page_title: 'Editar uma entrada' 243 page_title: 'Editar uma entrada'
235 title_label: 'Título' 244 title_label: 'Título'
236 url_label: 'Url' 245 url_label: 'Url'
237 is_public_label: 'Público'
238 save_label: 'Salvar' 246 save_label: 'Salvar'
239 public: 247 public:
240 shared_by_wallabag: "Este artigo foi compartilhado pelo <a href='%wallabag_instance%'>wallabag</a>" 248 shared_by_wallabag: "Este artigo foi compartilhado pelo <a href='%wallabag_instance%'>wallabag</a>"
249 confirm:
250 # delete: "Are you sure you want to remove that article?"
251 # delete_tag: "Are you sure you want to remove that tag from that article?"
241 252
242about: 253about:
243 page_title: 'Sobre' 254 page_title: 'Sobre'
@@ -510,6 +521,28 @@ user:
510 delete: 'Apagar' 521 delete: 'Apagar'
511 delete_confirm: 'Tem certeza?' 522 delete_confirm: 'Tem certeza?'
512 back_to_list: 'Voltar para a lista' 523 back_to_list: 'Voltar para a lista'
524 search:
525 # placeholder: Filter by username or email
526
527site_credential:
528 # page_title: Site credentials management
529 # new_site_credential: Create a credential
530 # edit_site_credential: Edit an existing credential
531 # description: "Here you can manage all credentials for sites which required them (create, edit and delete), like a paywall, an authentication, etc."
532 list:
533 actions: 'Ações'
534 edit_action: 'Editar'
535 yes: 'Sim'
536 no: 'Não'
537 # create_new_one: Create a new credential
538 form:
539 # username_label: 'Username'
540 # host_label: 'Host'
541 # password_label: 'Password'
542 save: 'Salvar'
543 delete: 'Apagar'
544 delete_confirm: 'Tem certeza?'
545 back_to_list: 'Voltar para a lista'
513 546
514error: 547error:
515 # page_title: An error occurred 548 # page_title: An error occurred
@@ -528,6 +561,7 @@ flashes:
528 # annotations_reset: Annotations reset 561 # annotations_reset: Annotations reset
529 # tags_reset: Tags reset 562 # tags_reset: Tags reset
530 # entries_reset: Entries reset 563 # entries_reset: Entries reset
564 # archived_reset: Archived entries deleted
531 entry: 565 entry:
532 notice: 566 notice:
533 entry_already_saved: 'Entrada já foi salva em %date%' 567 entry_already_saved: 'Entrada já foi salva em %date%'
@@ -562,3 +596,8 @@ flashes:
562 added: 'Usuário "%username%" adicionado' 596 added: 'Usuário "%username%" adicionado'
563 updated: 'Usuário "%username%" atualizado' 597 updated: 'Usuário "%username%" atualizado'
564 deleted: 'Usuário "%username%" removido' 598 deleted: 'Usuário "%username%" removido'
599 site_credential:
600 notice:
601 # added: 'Site credential for "%host%" added'
602 # updated: 'Site credential for "%host%" updated'
603 # deleted: 'Site credential for "%host%" deleted'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
index 728eed58..f16504ed 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
@@ -32,6 +32,7 @@ menu:
32 # save_link: 'Save a link' 32 # save_link: 'Save a link'
33 back_to_unread: 'Înapoi la articolele necitite' 33 back_to_unread: 'Înapoi la articolele necitite'
34 # users_management: 'Users management' 34 # users_management: 'Users management'
35 # site_credentials: 'Site credentials'
35 top: 36 top:
36 add_new_entry: 'Introdu un nou articol' 37 add_new_entry: 'Introdu un nou articol'
37 search: 'Căutare' 38 search: 'Căutare'
@@ -76,6 +77,7 @@ config:
76 # redirect_current_page: 'To the current page' 77 # redirect_current_page: 'To the current page'
77 pocket_consumer_key_label: Cheie consumator pentru importarea contentului din Pocket 78 pocket_consumer_key_label: Cheie consumator pentru importarea contentului din Pocket
78 # android_configuration: Configure your Android application 79 # android_configuration: Configure your Android application
80 # android_instruction: "Touch here to prefill your Android application"
79 # help_theme: "wallabag is customizable. You can choose your prefered theme here." 81 # help_theme: "wallabag is customizable. You can choose your prefered theme here."
80 # help_items_per_page: "You can change the number of articles displayed on each page." 82 # help_items_per_page: "You can change the number of articles displayed on each page."
81 # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article." 83 # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article."
@@ -89,9 +91,10 @@ config:
89 token_reset: 'Resetează-ți token-ul' 91 token_reset: 'Resetează-ți token-ul'
90 rss_links: 'Link-uri RSS' 92 rss_links: 'Link-uri RSS'
91 rss_link: 93 rss_link:
92 unread: 'unread' 94 unread: 'Unread'
93 starred: 'starred' 95 starred: 'Starred'
94 archive: 'archived' 96 archive: 'Archived'
97 # all: 'All'
95 rss_limit: 'Limită RSS' 98 rss_limit: 'Limită RSS'
96 form_user: 99 form_user:
97 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion" 100 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion"
@@ -110,6 +113,7 @@ config:
110 # annotations: Remove ALL annotations 113 # annotations: Remove ALL annotations
111 # tags: Remove ALL tags 114 # tags: Remove ALL tags
112 # entries: Remove ALL entries 115 # entries: Remove ALL entries
116 # archived: Remove ALL archived entries
113 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE) 117 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
114 form_password: 118 form_password:
115 # description: "You can change your password here. Your new password should by at least 8 characters long." 119 # description: "You can change your password here. Your new password should by at least 8 characters long."
@@ -154,6 +158,7 @@ config:
154 # or: 'One rule OR another' 158 # or: 'One rule OR another'
155 # and: 'One rule AND another' 159 # and: 'One rule AND another'
156 # matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 160 # matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
161 # notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
157 162
158entry: 163entry:
159 page_titles: 164 page_titles:
@@ -185,6 +190,8 @@ entry:
185 unread_label: 'Necitite' 190 unread_label: 'Necitite'
186 preview_picture_label: 'Are o imagine de previzualizare' 191 preview_picture_label: 'Are o imagine de previzualizare'
187 preview_picture_help: 'Previzualizare imagine' 192 preview_picture_help: 'Previzualizare imagine'
193 # is_public_label: 'Has a public link'
194 # is_public_help: 'Public link'
188 language_label: 'Limbă' 195 language_label: 'Limbă'
189 # http_status_label: 'HTTP status' 196 # http_status_label: 'HTTP status'
190 reading_time: 197 reading_time:
@@ -223,6 +230,8 @@ entry:
223 original_article: 'original' 230 original_article: 'original'
224 # annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations' 231 # annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations'
225 created_at: 'Data creării' 232 created_at: 'Data creării'
233 # published_at: 'Publication date'
234 # published_by: 'Published by'
226 new: 235 new:
227 page_title: 'Salvează un nou articol' 236 page_title: 'Salvează un nou articol'
228 placeholder: 'http://website.com' 237 placeholder: 'http://website.com'
@@ -234,10 +243,12 @@ entry:
234 # page_title: 'Edit an entry' 243 # page_title: 'Edit an entry'
235 # title_label: 'Title' 244 # title_label: 'Title'
236 url_label: 'Url' 245 url_label: 'Url'
237 # is_public_label: 'Public'
238 save_label: 'Salvează' 246 save_label: 'Salvează'
239 public: 247 public:
240 # shared_by_wallabag: "This article has been shared by <a href='%wallabag_instance%'>wallabag</a>" 248 # shared_by_wallabag: "This article has been shared by %username% with <a href='%wallabag_instance%'>wallabag</a>"
249 confirm:
250 # delete: "Are you sure you want to remove that article?"
251 # delete_tag: "Are you sure you want to remove that tag from that article?"
241 252
242about: 253about:
243 page_title: 'Despre' 254 page_title: 'Despre'
@@ -510,6 +521,28 @@ user:
510 # delete: Delete 521 # delete: Delete
511 # delete_confirm: Are you sure? 522 # delete_confirm: Are you sure?
512 # back_to_list: Back to list 523 # back_to_list: Back to list
524 search:
525 # placeholder: Filter by username or email
526
527site_credential:
528 # page_title: Site credentials management
529 # new_site_credential: Create a credential
530 # edit_site_credential: Edit an existing credential
531 # description: "Here you can manage all credentials for sites which required them (create, edit and delete), like a paywall, an authentication, etc."
532 # list:
533 # actions: Actions
534 # edit_action: Edit
535 # yes: Yes
536 # no: No
537 # create_new_one: Create a new credential
538 # form:
539 # username_label: 'Username'
540 # host_label: 'Host'
541 # password_label: 'Password'
542 # save: Save
543 # delete: Delete
544 # delete_confirm: Are you sure?
545 # back_to_list: Back to list
513 546
514error: 547error:
515 # page_title: An error occurred 548 # page_title: An error occurred
@@ -528,6 +561,7 @@ flashes:
528 # annotations_reset: Annotations reset 561 # annotations_reset: Annotations reset
529 # tags_reset: Tags reset 562 # tags_reset: Tags reset
530 # entries_reset: Entries reset 563 # entries_reset: Entries reset
564 # archived_reset: Archived entries deleted
531 entry: 565 entry:
532 notice: 566 notice:
533 # entry_already_saved: 'Entry already saved on %date%' 567 # entry_already_saved: 'Entry already saved on %date%'
@@ -562,3 +596,8 @@ flashes:
562 # added: 'User "%username%" added' 596 # added: 'User "%username%" added'
563 # updated: 'User "%username%" updated' 597 # updated: 'User "%username%" updated'
564 # deleted: 'User "%username%" deleted' 598 # deleted: 'User "%username%" deleted'
599 site_credential:
600 notice:
601 # added: 'Site credential for "%host%" added'
602 # updated: 'Site credential for "%host%" updated'
603 # deleted: 'Site credential for "%host%" deleted'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
index d3180f42..90a140cd 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
@@ -32,6 +32,7 @@ menu:
32 # save_link: 'Save a link' 32 # save_link: 'Save a link'
33 back_to_unread: 'Okunmayan makalelere geri dön' 33 back_to_unread: 'Okunmayan makalelere geri dön'
34 # users_management: 'Users management' 34 # users_management: 'Users management'
35 # site_credentials: 'Site credentials'
35 top: 36 top:
36 add_new_entry: 'Yeni bir makale ekle' 37 add_new_entry: 'Yeni bir makale ekle'
37 search: 'Ara' 38 search: 'Ara'
@@ -76,6 +77,7 @@ config:
76 # redirect_current_page: 'To the current page' 77 # redirect_current_page: 'To the current page'
77 # pocket_consumer_key_label: Consumer key for Pocket to import contents 78 # pocket_consumer_key_label: Consumer key for Pocket to import contents
78 # android_configuration: Configure your Android application 79 # android_configuration: Configure your Android application
80 # android_instruction: "Touch here to prefill your Android application"
79 # help_theme: "wallabag is customizable. You can choose your prefered theme here." 81 # help_theme: "wallabag is customizable. You can choose your prefered theme here."
80 # help_items_per_page: "You can change the number of articles displayed on each page." 82 # help_items_per_page: "You can change the number of articles displayed on each page."
81 # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article." 83 # help_reading_speed: "wallabag calculates a reading time for each article. You can define here, thanks to this list, if you are a fast or a slow reader. wallabag will recalculate the reading time for each article."
@@ -89,9 +91,10 @@ config:
89 token_reset: 'Belirteci (token) sıfırla' 91 token_reset: 'Belirteci (token) sıfırla'
90 rss_links: 'RSS akış bağlantıları' 92 rss_links: 'RSS akış bağlantıları'
91 rss_link: 93 rss_link:
92 unread: 'okunmayan' 94 unread: 'Okunmayan'
93 starred: 'favoriler' 95 starred: 'Favoriler'
94 archive: 'arşiv' 96 archive: 'Arşiv'
97 # all: 'All'
95 rss_limit: 'RSS içeriğinden talep edilecek makale limiti' 98 rss_limit: 'RSS içeriğinden talep edilecek makale limiti'
96 form_user: 99 form_user:
97 two_factor_description: "İki adımlı doğrulamayı aktifleştirdiğinizde, her yeni güvenilmeyen bağlantılarda size e-posta ile bir kod alacaksınız." 100 two_factor_description: "İki adımlı doğrulamayı aktifleştirdiğinizde, her yeni güvenilmeyen bağlantılarda size e-posta ile bir kod alacaksınız."
@@ -110,6 +113,7 @@ config:
110 # annotations: Remove ALL annotations 113 # annotations: Remove ALL annotations
111 # tags: Remove ALL tags 114 # tags: Remove ALL tags
112 # entries: Remove ALL entries 115 # entries: Remove ALL entries
116 # archived: Remove ALL archived entries
113 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE) 117 # confirm: Are you really really sure? (THIS CAN'T BE UNDONE)
114 form_password: 118 form_password:
115 # description: "You can change your password here. Your new password should by at least 8 characters long." 119 # description: "You can change your password here. Your new password should by at least 8 characters long."
@@ -154,6 +158,7 @@ config:
154 or: 'Bir kural veya birbaşkası' 158 or: 'Bir kural veya birbaşkası'
155 and: 'Bir kural ve diğeri' 159 and: 'Bir kural ve diğeri'
156 # matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 160 # matches: 'Tests that a <i>subject</i> is matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
161 # notmatches: 'Tests that a <i>subject</i> is not matches a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
157 162
158entry: 163entry:
159 page_titles: 164 page_titles:
@@ -223,6 +228,8 @@ entry:
223 original_article: 'orijinal' 228 original_article: 'orijinal'
224 # annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations' 229 # annotations_on_the_entry: '{0} No annotations|{1} One annotation|]1,Inf[ %count% annotations'
225 created_at: 'Oluşturulma tarihi' 230 created_at: 'Oluşturulma tarihi'
231 # published_at: 'Publication date'
232 # published_by: 'Published by'
226 new: 233 new:
227 page_title: 'Yeni makaleyi kaydet' 234 page_title: 'Yeni makaleyi kaydet'
228 placeholder: 'http://website.com' 235 placeholder: 'http://website.com'
@@ -234,10 +241,12 @@ entry:
234 page_title: 'Makaleyi düzenle' 241 page_title: 'Makaleyi düzenle'
235 title_label: 'Başlık' 242 title_label: 'Başlık'
236 url_label: 'Url' 243 url_label: 'Url'
237 is_public_label: 'Herkes tarafından erişime açık olsun mu?'
238 save_label: 'Kaydet' 244 save_label: 'Kaydet'
239 public: 245 public:
240 # shared_by_wallabag: "This article has been shared by <a href='%wallabag_instance%'>wallabag</a>" 246 # shared_by_wallabag: "This article has been shared by %username% with <a href='%wallabag_instance%'>wallabag</a>"
247 confirm:
248 # delete: "Are you sure you want to remove that article?"
249 # delete_tag: "Are you sure you want to remove that tag from that article?"
241 250
242about: 251about:
243 page_title: 'Hakkımızda' 252 page_title: 'Hakkımızda'
@@ -510,6 +519,8 @@ user:
510 # delete: Delete 519 # delete: Delete
511 # delete_confirm: Are you sure? 520 # delete_confirm: Are you sure?
512 # back_to_list: Back to list 521 # back_to_list: Back to list
522 search:
523 # placeholder: Filter by username or email
513 524
514error: 525error:
515 # page_title: An error occurred 526 # page_title: An error occurred
@@ -528,6 +539,7 @@ flashes:
528 # annotations_reset: Annotations reset 539 # annotations_reset: Annotations reset
529 # tags_reset: Tags reset 540 # tags_reset: Tags reset
530 # entries_reset: Entries reset 541 # entries_reset: Entries reset
542 # archived_reset: Archived entries deleted
531 entry: 543 entry:
532 notice: 544 notice:
533 entry_already_saved: 'Entry already saved on %date%' 545 entry_already_saved: 'Entry already saved on %date%'
@@ -562,3 +574,8 @@ flashes:
562 # added: 'User "%username%" added' 574 # added: 'User "%username%" added'
563 # updated: 'User "%username%" updated' 575 # updated: 'User "%username%" updated'
564 # deleted: 'User "%username%" deleted' 576 # deleted: 'User "%username%" deleted'
577 site_credential:
578 notice:
579 # added: 'Site credential for "%host%" added'
580 # updated: 'Site credential for "%host%" updated'
581 # deleted: 'Site credential for "%host%" deleted'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.da.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.da.yml
index 32a8b4a8..c6a84209 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.da.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.da.yml
@@ -4,3 +4,4 @@ validator:
4 # password_wrong_value: 'Wrong value for your current password' 4 # password_wrong_value: 'Wrong value for your current password'
5 # item_per_page_too_high: 'This will certainly kill the app' 5 # item_per_page_too_high: 'This will certainly kill the app'
6 # rss_limit_too_high: 'This will certainly kill the app' 6 # rss_limit_too_high: 'This will certainly kill the app'
7 # quote_length_too_high: 'The quote is too long. It should have {{ limit }} characters or less.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.de.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.de.yml
index 37b9888f..c74c00ca 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.de.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.de.yml
@@ -4,3 +4,4 @@ validator:
4 password_wrong_value: 'Falscher Wert für dein aktuelles Kennwort' 4 password_wrong_value: 'Falscher Wert für dein aktuelles Kennwort'
5 item_per_page_too_high: 'Dies wird die Anwendung möglicherweise beenden' 5 item_per_page_too_high: 'Dies wird die Anwendung möglicherweise beenden'
6 rss_limit_too_high: 'Dies wird die Anwendung möglicherweise beenden' 6 rss_limit_too_high: 'Dies wird die Anwendung möglicherweise beenden'
7 # quote_length_too_high: 'The quote is too long. It should have {{ limit }} characters or less.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.en.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.en.yml
index 29217497..8cc117fe 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.en.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.en.yml
@@ -4,3 +4,4 @@ validator:
4 password_wrong_value: 'Wrong value for your current password' 4 password_wrong_value: 'Wrong value for your current password'
5 item_per_page_too_high: 'This will certainly kill the app' 5 item_per_page_too_high: 'This will certainly kill the app'
6 rss_limit_too_high: 'This will certainly kill the app' 6 rss_limit_too_high: 'This will certainly kill the app'
7 quote_length_too_high: 'The quote is too long. It should have {{ limit }} characters or less.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.es.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.es.yml
index 57ddaa5a..97a8edfa 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.es.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.es.yml
@@ -4,3 +4,4 @@ validator:
4 password_wrong_value: 'Entrada equivocada para su contraseña actual' 4 password_wrong_value: 'Entrada equivocada para su contraseña actual'
5 item_per_page_too_high: 'Esto matará la aplicación' 5 item_per_page_too_high: 'Esto matará la aplicación'
6 rss_limit_too_high: 'Esto matará la aplicación' 6 rss_limit_too_high: 'Esto matará la aplicación'
7 # quote_length_too_high: 'The quote is too long. It should have {{ limit }} characters or less.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.fa.yml
index e0536d18..ef677525 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.fa.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.fa.yml
@@ -4,3 +4,4 @@ validator:
4 password_wrong_value: 'رمز فعلی را اشتباه وارد کرده‌اید' 4 password_wrong_value: 'رمز فعلی را اشتباه وارد کرده‌اید'
5 item_per_page_too_high: 'با این تعداد برنامه به فنا می‌رود' 5 item_per_page_too_high: 'با این تعداد برنامه به فنا می‌رود'
6 rss_limit_too_high: 'با این تعداد برنامه به فنا می‌رود' 6 rss_limit_too_high: 'با این تعداد برنامه به فنا می‌رود'
7 # quote_length_too_high: 'The quote is too long. It should have {{ limit }} characters or less.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.fr.yml
index 64574709..f31b4ed2 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.fr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.fr.yml
@@ -4,3 +4,4 @@ validator:
4 password_wrong_value: "Votre mot de passe actuel est faux" 4 password_wrong_value: "Votre mot de passe actuel est faux"
5 item_per_page_too_high: "Ça ne va pas plaire à l’application" 5 item_per_page_too_high: "Ça ne va pas plaire à l’application"
6 rss_limit_too_high: "Ça ne va pas plaire à l’application" 6 rss_limit_too_high: "Ça ne va pas plaire à l’application"
7 quote_length_too_high: "La citation est trop longue. Elle doit avoir au maximum {{ limit }} caractères."
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.it.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.it.yml
index d9beb54f..d949cc3b 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.it.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.it.yml
@@ -4,3 +4,4 @@ validator:
4 password_wrong_value: 'Valore inserito per la password corrente errato' 4 password_wrong_value: 'Valore inserito per la password corrente errato'
5 item_per_page_too_high: 'Questo valore è troppo alto' 5 item_per_page_too_high: 'Questo valore è troppo alto'
6 rss_limit_too_high: 'Questo valore è troppo alto' 6 rss_limit_too_high: 'Questo valore è troppo alto'
7 # quote_length_too_high: 'The quote is too long. It should have {{ limit }} characters or less.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.oc.yml
index f92c2708..fb4aa592 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.oc.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.oc.yml
@@ -4,3 +4,4 @@ validator:
4 password_wrong_value: 'Vòstre senhal actual es pas bon' 4 password_wrong_value: 'Vòstre senhal actual es pas bon'
5 item_per_page_too_high: "Aquò li agradarà pas a l'aplicacion" 5 item_per_page_too_high: "Aquò li agradarà pas a l'aplicacion"
6 rss_limit_too_high: "Aquò li agradarà pas a l'aplicacion" 6 rss_limit_too_high: "Aquò li agradarà pas a l'aplicacion"
7 # quote_length_too_high: 'The quote is too long. It should have {{ limit }} characters or less.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.pl.yml
index ffcd5e7f..e4165c14 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.pl.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.pl.yml
@@ -4,3 +4,4 @@ validator:
4 password_wrong_value: 'Twoje obecne hasło jest błędne' 4 password_wrong_value: 'Twoje obecne hasło jest błędne'
5 item_per_page_too_high: 'To może spowodować problemy z aplikacją' 5 item_per_page_too_high: 'To może spowodować problemy z aplikacją'
6 rss_limit_too_high: 'To może spowodować problemy z aplikacją' 6 rss_limit_too_high: 'To może spowodować problemy z aplikacją'
7 quote_length_too_high: 'Cytat jest zbyt długi. powinien mieć {{ limit }} znaków lub mniej.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.pt.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.pt.yml
index 4eddff10..a8c1f9de 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.pt.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.pt.yml
@@ -4,3 +4,4 @@ validator:
4 password_wrong_value: 'A senha atual informada está errada' 4 password_wrong_value: 'A senha atual informada está errada'
5 item_per_page_too_high: 'Certamente isso pode matar a aplicação' 5 item_per_page_too_high: 'Certamente isso pode matar a aplicação'
6 rss_limit_too_high: 'Certamente isso pode matar a aplicação' 6 rss_limit_too_high: 'Certamente isso pode matar a aplicação'
7 # quote_length_too_high: 'The quote is too long. It should have {{ limit }} characters or less.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.ro.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.ro.yml
index 59a8cdd8..6840cf11 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.ro.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.ro.yml
@@ -4,3 +4,4 @@ validator:
4 # password_wrong_value: 'Wrong value for your current password' 4 # password_wrong_value: 'Wrong value for your current password'
5 # item_per_page_too_high: 'This will certainly kill the app' 5 # item_per_page_too_high: 'This will certainly kill the app'
6 # rss_limit_too_high: 'This will certainly kill the app' 6 # rss_limit_too_high: 'This will certainly kill the app'
7 # quote_length_too_high: 'The quote is too long. It should have {{ limit }} characters or less.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.tr.yml
index 01388771..e1e7317f 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.tr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.tr.yml
@@ -4,3 +4,4 @@ validator:
4 # password_wrong_value: 'Wrong value for your current password' 4 # password_wrong_value: 'Wrong value for your current password'
5 # item_per_page_too_high: 'This will certainly kill the app' 5 # item_per_page_too_high: 'This will certainly kill the app'
6 # rss_limit_too_high: 'This will certainly kill the app' 6 # rss_limit_too_high: 'This will certainly kill the app'
7 # quote_length_too_high: 'The quote is too long. It should have {{ limit }} characters or less.'
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
index 3548f590..bcc57dac 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig
@@ -82,7 +82,7 @@
82 <fieldset class="w500p inline"> 82 <fieldset class="w500p inline">
83 <div class="row"> 83 <div class="row">
84 <h3>{{ 'config.form_settings.android_configuration'|trans }}</h3> 84 <h3>{{ 'config.form_settings.android_configuration'|trans }}</h3>
85 <a href="wallabag://{{ app.user.username }}@{{ wallabag_url }}" >Touch here to prefill your Android application</a> 85 <a href="wallabag://{{ app.user.username }}@{{ wallabag_url }}">{{ 'config.form_settings.android_instruction' | trans }}</a>
86 <br/> 86 <br/>
87 <img id="androidQrcode" /> 87 <img id="androidQrcode" />
88 <script> 88 <script>
@@ -106,7 +106,7 @@
106 106
107 <fieldset class="w500p inline"> 107 <fieldset class="w500p inline">
108 <div class="row"> 108 <div class="row">
109 <label>Rss token</label> 109 <label>{{ 'config.form_rss.token_label'|trans }}</label>
110 {% if rss.token %} 110 {% if rss.token %}
111 {{ rss.token }} 111 {{ rss.token }}
112 {% else %} 112 {% else %}
@@ -128,9 +128,10 @@
128 <div class="row"> 128 <div class="row">
129 <label>{{ 'config.form_rss.rss_links'|trans }}</label> 129 <label>{{ 'config.form_rss.rss_links'|trans }}</label>
130 <ul> 130 <ul>
131 <li><a href="{{ path('unread_rss', {'username': rss.username, 'token': rss.token}) }}">unread</a></li> 131 <li><a href="{{ path('unread_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.unread'|trans }}</a></li>
132 <li><a href="{{ path('starred_rss', {'username': rss.username, 'token': rss.token}) }}">fav</a></li> 132 <li><a href="{{ path('starred_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.starred'|trans }}</a></li>
133 <li><a href="{{ path('archive_rss', {'username': rss.username, 'token': rss.token}) }}">archives</a></li> 133 <li><a href="{{ path('archive_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.archive'|trans }}</a></li>
134 <li><a href="{{ path('all_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.all'|trans }}</a></li>
134 </ul> 135 </ul>
135 </div> 136 </div>
136 </fieldset> 137 </fieldset>
@@ -200,6 +201,11 @@
200 </a> 201 </a>
201 </li> 202 </li>
202 <li> 203 <li>
204 <a href="{{ path('config_reset', { type: 'archived'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
205 {{ 'config.reset.archived'|trans }}
206 </a>
207 </li>
208 <li>
203 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red"> 209 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
204 {{ 'config.reset.entries'|trans }} 210 {{ 'config.reset.entries'|trans }}
205 </a> 211 </a>
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..6424df8d 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
@@ -1,5 +1,12 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %} 1{% extends "WallabagCoreBundle::layout.html.twig" %}
2 2
3{% block head %}
4 {{ parent() }}
5 {% if tag is defined and app.user.config.rssToken %}
6 <link rel="alternate" type="application/rss+xml" href="{{ path('tag_rss', {'username': app.user.username, 'token': app.user.config.rssToken, 'slug': tag.slug}) }}" />
7 {% endif %}
8{% endblock %}
9
3{% block title %} 10{% block title %}
4 {% set filter = '' %} 11 {% set filter = '' %}
5 {% if tag is defined %} 12 {% if tag is defined %}
@@ -12,14 +19,17 @@
12{% endblock %} 19{% endblock %}
13 20
14{% block content %} 21{% block content %}
15 22 {% set currentRoute = app.request.attributes.get('_route') %}
16 {% set listMode = app.user.config.listMode %} 23 {% set listMode = app.user.config.listMode %}
17 <div class="results"> 24 <div class="results">
18 <div class="nb-results">{{ 'entry.list.number_on_the_page'|transchoice(entries.count) }}</div> 25 <div class="nb-results">{{ 'entry.list.number_on_the_page'|transchoice(entries.count) }}</div>
19 <div class="pagination"> 26 <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> 27 <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> 28 {% if app.user.config.rssToken %}
22 <i class="btn-clickable filter-btn material-icons md-36 js-filters-action">filter_list</i> 29 {% include "@WallabagCore/themes/common/Entry/_rss_link.html.twig" %}
30 {% endif %}
31 <i class="btn-clickable download-btn material-icons md-24 js-export-action">file_download</i>
32 <i class="btn-clickable filter-btn material-icons md-24 js-filters-action">filter_list</i>
23 {% if entries.getNbPages > 1 %} 33 {% if entries.getNbPages > 1 %}
24 {{ pagerfanta(entries, 'twitter_bootstrap_translated', {'proximity': 1}) }} 34 {{ pagerfanta(entries, 'twitter_bootstrap_translated', {'proximity': 1}) }}
25 {% endif %} 35 {% endif %}
@@ -47,10 +57,10 @@
47 </div> 57 </div>
48 58
49 <ul class="tools links"> 59 <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> 60 <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> 61 <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> 62 <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> 63 <li><a title="{{ 'entry.list.delete'|trans }}" class="tool icon" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" 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> 64 </ul>
55 {% if (entry.previewPicture is null or listMode == 1) %} 65 {% if (entry.previewPicture is null or listMode == 1) %}
56 <ul class="card-entry-tags"> 66 <ul class="card-entry-tags">
@@ -76,7 +86,6 @@
76 86
77 <!-- Export --> 87 <!-- Export -->
78 <aside id="download-form"> 88 <aside id="download-form">
79 {% set currentRoute = app.request.attributes.get('_route') %}
80 {% set currentTag = '' %} 89 {% set currentTag = '' %}
81 {% if tag is defined %} 90 {% if tag is defined %}
82 {% set currentTag = tag %} 91 {% set currentTag = tag %}
@@ -127,6 +136,11 @@
127 {{ form_widget(form.previewPicture) }} 136 {{ form_widget(form.previewPicture) }}
128 {{ form_label(form.previewPicture) }} 137 {{ form_label(form.previewPicture) }}
129 </div> 138 </div>
139
140 <div class="input-field">
141 {{ form_widget(form.isPublic) }}
142 {{ form_label(form.isPublic) }}
143 </div>
130 </div> 144 </div>
131 145
132 <div id="filter-language" class="filter-group"> 146 <div id="filter-language" class="filter-group">
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 a555691d..3d20a6bc 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
@@ -22,7 +22,7 @@
22 <li><a title="{{ markAsReadLabel|trans }}" class="tool icon icon-check {% if entry.isArchived == 0 %}archive-off{% else %}archive{% endif %} markasread" href="{{ path('archive_entry', { 'id': entry.id }) }}"><span>{{ markAsReadLabel|trans }}</span></a></li> 22 <li><a title="{{ markAsReadLabel|trans }}" class="tool icon icon-check {% if entry.isArchived == 0 %}archive-off{% else %}archive{% endif %} markasread" href="{{ path('archive_entry', { 'id': entry.id }) }}"><span>{{ markAsReadLabel|trans }}</span></a></li>
23 <li><a title="{{ 'entry.view.left_menu.set_as_starred'|trans }}" class="tool icon icon-star {% if entry.isStarred == 0 %}fav-off{% else %}fav{% endif %} favorite" href="{{ path('star_entry', { 'id': entry.id }) }}"><span>{{ 'entry.view.left_menu.set_as_starred'|trans }}</span></a></li> 23 <li><a title="{{ 'entry.view.left_menu.set_as_starred'|trans }}" class="tool icon icon-star {% if entry.isStarred == 0 %}fav-off{% else %}fav{% endif %} favorite" href="{{ path('star_entry', { 'id': entry.id }) }}"><span>{{ 'entry.view.left_menu.set_as_starred'|trans }}</span></a></li>
24 <li><a id="nav-btn-add-tag" class="tool icon icon-price-tags" title="{{ 'entry.view.left_menu.add_a_tag'|trans }}"><span>{{ 'entry.view.left_menu.add_a_tag'|trans }}</span></a></li> 24 <li><a id="nav-btn-add-tag" class="tool icon icon-price-tags" title="{{ 'entry.view.left_menu.add_a_tag'|trans }}"><span>{{ 'entry.view.left_menu.add_a_tag'|trans }}</span></a></li>
25 <li><a title="{{ 'entry.view.left_menu.delete'|trans }}" class="tool delete icon icon-trash" href="{{ path('delete_entry', { 'id': entry.id }) }}"><span>{{ 'entry.view.left_menu.delete'|trans }}</span></a></li> 25 <li><a title="{{ 'entry.view.left_menu.delete'|trans }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" class="tool delete icon icon-trash" href="{{ path('delete_entry', { 'id': entry.id }) }}"><span>{{ 'entry.view.left_menu.delete'|trans }}</span></a></li>
26 {% if craue_setting('share_public') %} 26 {% if craue_setting('share_public') %}
27 <li><a href="{{ path('share', {'id': entry.id }) }}" target="_blank" class="tool icon icon-eye" title="{{ 'entry.view.left_menu.public_link'|trans }}"><span>{{ 'entry.view.left_menu.public_link'|trans }}</span></a></li> 27 <li><a href="{{ path('share', {'id': entry.id }) }}" target="_blank" class="tool icon icon-eye" title="{{ 'entry.view.left_menu.public_link'|trans }}"><span>{{ 'entry.view.left_menu.public_link'|trans }}</span></a></li>
28 <li><a href="{{ path('delete_share', {'id': entry.id }) }}" class="tool icon icon-no-eye" title="{{ 'entry.view.left_menu.delete_public_link'|trans }}"><span>{{ 'entry.view.left_menu.delete_public_link'|trans }}</span></a></li> 28 <li><a href="{{ path('delete_share', {'id': entry.id }) }}" class="tool icon icon-no-eye" title="{{ 'entry.view.left_menu.delete_public_link'|trans }}"><span>{{ 'entry.view.left_menu.delete_public_link'|trans }}</span></a></li>
@@ -30,6 +30,7 @@
30 {% if craue_setting('share_twitter') %}<li><a href="https://twitter.com/home?status={{entry.title|url_encode}}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" class="tool twitter icon icon-twitter" title="Tweet"><span>Tweet</span></a></li>{% endif %} 30 {% if craue_setting('share_twitter') %}<li><a href="https://twitter.com/home?status={{entry.title|url_encode}}%20{{ entry.url|url_encode }}%20via%20@wallabagapp" target="_blank" class="tool twitter icon icon-twitter" title="Tweet"><span>Tweet</span></a></li>{% endif %}
31 {% if craue_setting('share_mail') %}<li><a href="mailto:?subject={{ entry.title|url_encode }}&amp;body={{ entry.url|url_encode }}%20via%20@wallabagapp" class="tool email icon icon-mail" title="Email"><span>Email</span></a></li>{% endif %} 31 {% if craue_setting('share_mail') %}<li><a href="mailto:?subject={{ entry.title|url_encode }}&amp;body={{ entry.url|url_encode }}%20via%20@wallabagapp" class="tool email icon icon-mail" title="Email"><span>Email</span></a></li>{% endif %}
32 {% if craue_setting('share_shaarli') %}<li><a href="{{ craue_setting('shaarli_url') }}/index.php?post={{ entry.url|url_encode }}&amp;title={{ entry.title|url_encode }}&amp;tags={{ entry.tags|join(',')|url_encode }}" target="_blank" class="tool icon-image icon-image--shaarli" title="shaarli"><span>shaarli</span></a></li>{% endif %} 32 {% if craue_setting('share_shaarli') %}<li><a href="{{ craue_setting('shaarli_url') }}/index.php?post={{ entry.url|url_encode }}&amp;title={{ entry.title|url_encode }}&amp;tags={{ entry.tags|join(',')|url_encode }}" target="_blank" class="tool icon-image icon-image--shaarli" title="shaarli"><span>shaarli</span></a></li>{% endif %}
33 {% if craue_setting('share_scuttle') %}<li><a href="{{ craue_setting('scuttle_url') }}/bookmarks.php?action=add&amp;address={{ entry.url|url_encode }}&amp;title={{ entry.title|url_encode }}&amp;tags={{ entry.tags|join(',')|url_encode }}" target="_blank" class="tool icon-image icon-image--scuttle" title="scuttle"><span>scuttle</span></a></li>{% endif %}
33 {% if craue_setting('share_diaspora') %}<li><a href="{{ craue_setting('diaspora_url') }}/bookmarklet?url={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}&notes=&v=1&noui=1&jump=doclose" target="_blank" class="tool diaspora icon-image icon-image--diaspora" title="diaspora"><span>diaspora</span></a></li>{% endif %} 34 {% if craue_setting('share_diaspora') %}<li><a href="{{ craue_setting('diaspora_url') }}/bookmarklet?url={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}&notes=&v=1&noui=1&jump=doclose" target="_blank" class="tool diaspora icon-image icon-image--diaspora" title="diaspora"><span>diaspora</span></a></li>{% endif %}
34 {% if craue_setting('share_unmark') %}<li><a href="{{ craue_setting('unmark_url') }}/mark/add?url={{ entry.url|url_encode }}&amp;title={{entry.title|url_encode}}&amp;v=6" target="_blank" class="tool unmark icon-image icon-image--unmark" title="unmark"><span>unmark.it</span></a></li>{% endif %} 35 {% if craue_setting('share_unmark') %}<li><a href="{{ craue_setting('unmark_url') }}/mark/add?url={{ entry.url|url_encode }}&amp;title={{entry.title|url_encode}}&amp;v=6" target="_blank" class="tool unmark icon-image icon-image--unmark" title="unmark"><span>unmark.it</span></a></li>{% endif %}
35 {% if craue_setting('carrot') %}<li><a href="https://secure.carrot.org/GiveAndGetBack.do?url={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}" class="tool carrot icon-image icon-image--carrot" target="_blank" title="carrot"><span>Carrot</span></a></li>{% endif %} 36 {% if craue_setting('carrot') %}<li><a href="https://secure.carrot.org/GiveAndGetBack.do?url={{ entry.url|url_encode }}&title={{ entry.title|url_encode }}" class="tool carrot icon-image icon-image--carrot" target="_blank" title="carrot"><span>Carrot</span></a></li>{% endif %}
@@ -43,9 +44,23 @@
43 44
44 <div id="article-informations"> 45 <div id="article-informations">
45 <i class="tool icon icon-calendar" title="{{ 'entry.view.created_at'|trans }}"> 46 <i class="tool icon icon-calendar" title="{{ 'entry.view.created_at'|trans }}">
46 {{ entry.createdAt|date('Y-m-d') }} 47 {{ entry.createdAt|date('Y-m-d H:i') }}
47 </i> 48 </i>
48 49
50 {% if entry.publishedAt is not null %}
51 <i class="tool icon icon-pencil2" title="{{ 'entry.view.published_at'|trans }}">
52 {{ entry.publishedAt|date('Y-m-d H:i') }}
53 </i>
54 {% endif %}
55
56 {% if entry.publishedBy is not empty %}
57 <i class="tool icon icon-users" title="{{ 'entry.view.published_by'|trans }}">
58 {% for author in entry.publishedBy %}
59 {{ author }}{% if not loop.last %}, {% endif %}
60 {% endfor %}
61 </i>
62 {% endif %}
63
49 <i class="tool icon icon-time"> 64 <i class="tool icon icon-time">
50 {% set readingTime = entry.readingTime / app.user.config.readingSpeed %} 65 {% set readingTime = entry.readingTime / app.user.config.readingSpeed %}
51 {% if readingTime > 0 %} 66 {% if readingTime > 0 %}
@@ -59,10 +74,16 @@
59 <aside class="tags"> 74 <aside class="tags">
60 <div class="card-entry-tags"> 75 <div class="card-entry-tags">
61 {% for tag in entry.tags %} 76 {% for tag in entry.tags %}
62 <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">
78 <i class="material-icons">label_outline</i>
79 <a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{ tag.label }}</a>
80 <a href="{{ path('remove_tag', { 'entry': entry.id, 'tag': tag.id }) }}" onclick="return confirm('{{ 'entry.confirm.delete_tag'|trans|escape('js') }}')" class="nostyle">
81 <i>✘</i>
82 </a>
83 </span>
63 {% endfor %} 84 {% endfor %}
64 </div> 85 </div>
65 <div class="input-field nav-panel-add-tag" style="display: none"> 86 <div class="input-field baggy-add-tag" style="display: none">
66 {{ render(controller( "WallabagCoreBundle:Tag:addTagForm", { 'id': entry.id } )) }} 87 {{ render(controller( "WallabagCoreBundle:Tag:addTagForm", { 'id': entry.id } )) }}
67 </div> 88 </div>
68 </aside> 89 </aside>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/SiteCredential/edit.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/SiteCredential/edit.html.twig
new file mode 100644
index 00000000..882be430
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/SiteCredential/edit.html.twig
@@ -0,0 +1,60 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{{ 'site_credential.page_title'|trans }}{% endblock %}
4
5{% block content %}
6
7 <div class="row">
8 <div class="col s12">
9 <div class="card-panel">
10 <div class="row">
11 <div class="input-field col s12">
12 <h4>{{ 'site_credential.edit_site_credential'|trans }}</h4>
13
14 <div id="set6" class="col s12">
15 {{ form_start(edit_form) }}
16 {{ form_errors(edit_form) }}
17
18 <div class="row">
19 <div class="input-field col s12">
20 {{ form_label(edit_form.host) }}
21 {{ form_errors(edit_form.host) }}
22 {{ form_widget(edit_form.host) }}
23 </div>
24 </div>
25
26 <div class="row">
27 <div class="input-field col s12">
28 {{ form_label(edit_form.username) }}
29 {{ form_errors(edit_form.username) }}
30 {{ form_widget(edit_form.username) }}
31 </div>
32 </div>
33
34 <div class="row">
35 <div class="input-field col s12">
36 {{ form_label(edit_form.password) }}
37 {{ form_errors(edit_form.password) }}
38 {{ form_widget(edit_form.password) }}
39 </div>
40 </div>
41
42 <br/>
43
44 {{ form_widget(edit_form.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
45 {{ form_widget(edit_form._token) }}
46 </form>
47 <p>
48 {{ form_start(delete_form) }}
49 <button onclick="return confirm('{{ 'site_credential.form.delete_confirm'|trans|escape('js') }}')" type="submit" class="btn waves-effect waves-light red">{{ 'site_credential.form.delete'|trans }}</button>
50 {{ form_end(delete_form) }}
51 </p>
52 <p><a class="waves-effect waves-light btn blue-grey" href="{{ path('site_credentials_index') }}">{{ 'site_credential.form.back_to_list'|trans }}</a></p>
53 </div>
54 </div>
55 </div>
56 </div>
57 </div>
58 </div>
59
60{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/SiteCredential/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/SiteCredential/index.html.twig
new file mode 100644
index 00000000..324854ad
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/SiteCredential/index.html.twig
@@ -0,0 +1,42 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{{ 'site_credential.page_title'|trans }}{% endblock %}
4
5{% block content %}
6
7 <div class="row">
8 <div class="col s12">
9 <div class="card-panel">
10 <div class="row">
11 <div class="input-field col s12">
12 <p class="help">{{ 'site_credential.description'|trans|raw }}</p>
13
14 <table class="bordered">
15 <thead>
16 <tr>
17 <th>{{ 'site_credential.form.host_label'|trans }}</th>
18 <th>{{ 'site_credential.list.actions'|trans }}</th>
19 </tr>
20 </thead>
21 <tbody>
22 {% for credential in credentials %}
23 <tr>
24 <td>{{ credential.host }}</td>
25 <td>
26 <a href="{{ path('site_credentials_edit', { 'id': credential.id }) }}">{{ 'site_credential.list.edit_action'|trans }}</a>
27 </td>
28 </tr>
29 {% endfor %}
30 </tbody>
31 </table>
32 <br />
33 <p>
34 <a href="{{ path('site_credentials_new') }}" class="waves-effect waves-light btn">{{ 'site_credential.list.create_new_one'|trans }}</a>
35 </p>
36 </div>
37 </div>
38 </div>
39 </div>
40 </div>
41
42{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/SiteCredential/new.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/SiteCredential/new.html.twig
new file mode 100644
index 00000000..3c008cde
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/SiteCredential/new.html.twig
@@ -0,0 +1,53 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{{ 'site_credential.page_title'|trans }}{% endblock %}
4
5{% block content %}
6
7 <div class="row">
8 <div class="col s12">
9 <div class="card-panel">
10 <div class="row">
11 <div class="input-field col s12">
12 <h4>{{ 'site_credential.new_site_credential'|trans }}</h4>
13
14 <div id="set6" class="col s12">
15 {{ form_start(form) }}
16 {{ form_errors(form) }}
17
18 <div class="row">
19 <div class="input-field col s12">
20 {{ form_label(form.host) }}
21 {{ form_errors(form.host) }}
22 {{ form_widget(form.host) }}
23 </div>
24 </div>
25
26 <div class="row">
27 <div class="input-field col s12">
28 {{ form_label(form.username) }}
29 {{ form_errors(form.username) }}
30 {{ form_widget(form.username) }}
31 </div>
32 </div>
33
34 <div class="row">
35 <div class="input-field col s12">
36 {{ form_label(form.password) }}
37 {{ form_errors(form.password) }}
38 {{ form_widget(form.password) }}
39 </div>
40 </div>
41
42 {{ form_widget(form.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
43 {{ form_rest(form) }}
44 </form>
45 <p><a class="waves-effect waves-light btn blue-grey" href="{{ path('site_credentials_index') }}">{{ 'site_credential.form.back_to_list'|trans }}</a></p>
46 </div>
47 </div>
48 </div>
49 </div>
50 </div>
51 </div>
52
53{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Tag/tags.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Tag/tags.html.twig
index 1e2c6b42..070d5629 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Tag/tags.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Tag/tags.html.twig
@@ -9,7 +9,12 @@
9 9
10 <ul> 10 <ul>
11 {% for tag in tags %} 11 {% for tag in tags %}
12 <li id="tag-{{ tag.id|e }}"><a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{tag.label}} ({{ tag.nbEntries | length }})</a></li> 12 <li id="tag-{{ tag.id|e }}">
13 <a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{tag.label}} ({{ tag.nbEntries }})</a>
14 <a rel="alternate" type="application/rss+xml" href="{{ path('tag_rss', {'username': app.user.username, 'token': app.user.config.rssToken, 'slug': tag.slug}) }}" class="right">
15 <i class="material-icons md-24">rss_feed</i>
16 </a>
17 </li>
13 {% endfor %} 18 {% endfor %}
14 </ul> 19 </ul>
15 20
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..17fa13bb 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 %}
@@ -36,6 +38,9 @@
36 {{ render(controller("WallabagCoreBundle:Entry:searchForm", {'currentRoute': app.request.attributes.get('_route')})) }} 38 {{ render(controller("WallabagCoreBundle:Entry:searchForm", {'currentRoute': app.request.attributes.get('_route')})) }}
37 </div> 39 </div>
38 </li> 40 </li>
41 {% if craue_setting('restricted_access') %}
42 <li class="menu site_credentials"><a href="{{ path('site_credentials_index') }}">{{ 'menu.left.site_credentials'|trans }}</a></li>
43 {% endif %}
39 <li class="menu config"><a href="{{ path('config') }}">{{ 'menu.left.config'|trans }}</a></li> 44 <li class="menu config"><a href="{{ path('config') }}">{{ 'menu.left.config'|trans }}</a></li>
40 {% if is_granted('ROLE_SUPER_ADMIN') %} 45 {% if is_granted('ROLE_SUPER_ADMIN') %}
41 <li class="menu users"><a href="{{ path('user_index') }}">{{ 'menu.left.users_management'|trans }}</a></li> 46 <li class="menu users"><a href="{{ path('user_index') }}">{{ 'menu.left.users_management'|trans }}</a></li>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/index.html.twig
index b3f0affb..528b055c 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/index.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/common/Developer/index.html.twig
@@ -33,7 +33,7 @@
33 <table class="striped"> 33 <table class="striped">
34 <tr> 34 <tr>
35 <td>{{ 'developer.existing_clients.field_id'|trans }}</td> 35 <td>{{ 'developer.existing_clients.field_id'|trans }}</td>
36 <td><strong><code>{{ client.id }}_{{ client.randomId }}</code></strong></td> 36 <td><strong><code>{{ client.clientId }}</code></strong></td>
37 </tr> 37 </tr>
38 <tr> 38 <tr>
39 <td>{{ 'developer.existing_clients.field_secret'|trans }}</td> 39 <td>{{ 'developer.existing_clients.field_secret'|trans }}</td>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_rss_link.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_rss_link.html.twig
new file mode 100644
index 00000000..2bf9b2bd
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_rss_link.html.twig
@@ -0,0 +1,6 @@
1{% if tag is defined %}
2 <a rel="alternate" type="application/rss+xml" href="{{ path('tag_rss', {'username': app.user.username, 'token': app.user.config.rssToken, 'slug': tag.slug}) }}" class="right"><i class="material-icons md-24">rss_feed</i></a>
3{% elseif currentRoute in ['unread', 'starred', 'archive', 'all'] %}
4 <a rel="alternate" type="application/rss+xml" href="{{ path(currentRoute ~ '_rss', {'username': app.user.username, 'token': app.user.config.rssToken}) }}" class="right"><i class="material-icons">rss_feed</i></a>
5{% endif %}
6
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/entries.xml.twig b/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/entries.xml.twig
index 12e8c79f..d70aa5dc 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/entries.xml.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/entries.xml.twig
@@ -1,8 +1,8 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/"> 2<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/">
3 <channel> 3 <channel>
4 <title>wallabag {{type}} feed</title> 4 <title>wallabag - {{ type }} feed</title>
5 <link>{{ url(type) }}</link> 5 <link>{{ url_html }}</link>
6 <link rel="self" href="{{ app.request.uri }}"/> 6 <link rel="self" href="{{ app.request.uri }}"/>
7 {% if entries.hasPreviousPage -%} 7 {% if entries.hasPreviousPage -%}
8 <link rel="previous" href="{{ url }}?page={{ entries.previousPage }}"/> 8 <link rel="previous" href="{{ url }}?page={{ entries.previousPage }}"/>
@@ -13,7 +13,7 @@
13 <link rel="last" href="{{ url }}?page={{ entries.nbPages }}"/> 13 <link rel="last" href="{{ url }}?page={{ entries.nbPages }}"/>
14 <pubDate>{{ "now"|date('D, d M Y H:i:s') }}</pubDate> 14 <pubDate>{{ "now"|date('D, d M Y H:i:s') }}</pubDate>
15 <generator>wallabag</generator> 15 <generator>wallabag</generator>
16 <description>wallabag {{type}} elements</description> 16 <description>wallabag {{ type }} elements</description>
17 17
18 {% for entry in entries %} 18 {% for entry in entries %}
19 19
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/share.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/share.html.twig
index 623cf1c4..a67807f9 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/share.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/share.html.twig
@@ -1,32 +1,6 @@
1<html> 1<html>
2 <head> 2 <head>
3 <title>{{ entry.title|e|raw }}</title> 3 <title>{{ entry.title|e|raw }}</title>
4 <style>
5 body {
6 margin: 10px;
7 font-family: 'Roboto',Verdana,Geneva,sans-serif;
8 font-size: 16px;
9 color: #000;
10 }
11 header {
12 text-align: center;
13 }
14
15 header h1 {
16 font-size: 1.3em;
17 }
18
19 a,
20 a:hover,
21 a:visited {
22 color: #000;
23 }
24
25 article {
26 margin: 0 auto;
27 width: 600px;
28 }
29 </style>
30 <meta property="og:title" content="{{ entry.title|e|raw }}" /> 4 <meta property="og:title" content="{{ entry.title|e|raw }}" />
31 <meta property="og:type" content="article" /> 5 <meta property="og:type" content="article" />
32 <meta property="og:url" content="{{ app.request.uri }}" /> 6 <meta property="og:url" content="{{ app.request.uri }}" />
@@ -40,12 +14,22 @@
40 <meta name="twitter:site" content="@wallabagapp" /> 14 <meta name="twitter:site" content="@wallabagapp" />
41 <meta name="twitter:title" content="{{ entry.title|e|raw }}" /> 15 <meta name="twitter:title" content="{{ entry.title|e|raw }}" />
42 <meta name="twitter:description" content="{{ entry.content|striptags|slice(0, 300)|raw }}&hellip;" /> 16 <meta name="twitter:description" content="{{ entry.content|striptags|slice(0, 300)|raw }}&hellip;" />
17 {% if app.debug %}
18 <script src="{{ asset('bundles/wallabagcore/public.dev.js') }}"></script>
19 {% else %}
20 <link rel="stylesheet" href="{{ asset('bundles/wallabagcore/public.css') }}">
21 {% endif %}
22
43 </head> 23 </head>
44 <body> 24 <body>
45 <header> 25 <header>
46 <h1>{{ entry.title|e|raw }}</h1> 26 <h1>{{ entry.title|e|raw }}</h1>
47 <div><a href="{{ entry.url|e }}" target="_blank" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|e|raw }}" class="tool">{{ entry.domainName|removeWww }}</a></div> 27 <div><a href="{{ entry.url|e }}" target="_blank" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|e|raw }}" class="tool">{{ entry.domainName|removeWww }}</a></div>
48 <div>{{ "entry.public.shared_by_wallabag"|trans({'%wallabag_instance%': url('homepage')})|raw }}</div> 28 <div>{{ "entry.public.shared_by_wallabag"|trans({'%wallabag_instance%': url('homepage'), '%username%': entry.user.username})|raw }}.</div>
29
30 {% if entry.previewPicture is not null %}
31 <div><img class="preview" src="{{ entry.previewPicture }}" alt="{{ entry.title|striptags|e('html_attr') }}" /></div>
32 {% endif %}
49 </header> 33 </header>
50 <article> 34 <article>
51 {{ entry.content | raw }} 35 {{ entry.content | raw }}
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 5d411fdd..a8143315 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>
@@ -66,19 +66,19 @@
66 </div> 66 </div>
67 </div> 67 </div>
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) }} 71 {{ form_label(form.config.action_mark_as_read) }}
72 {{ form_errors(form.config.action_mark_as_read) }} 72 {{ form_errors(form.config.action_mark_as_read) }}
73 {{ form_widget(form.config.action_mark_as_read) }} 73 {{ form_widget(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>
@@ -107,7 +107,7 @@
107 <div class="row"> 107 <div class="row">
108 <div class="input-field col s12"> 108 <div class="input-field col s12">
109 <h5>{{ 'config.form_settings.android_configuration'|trans }}</h5> 109 <h5>{{ 'config.form_settings.android_configuration'|trans }}</h5>
110 <a href="wallabag://{{ app.user.username }}@{{ wallabag_url }}" class="waves-effect waves-light btn hide-on-large-only">Touch here to prefill your Android application</a> 110 <a href="wallabag://{{ app.user.username }}@{{ wallabag_url }}" class="waves-effect waves-light btn hide-on-large-only">{{ 'config.form_settings.android_instruction' | trans }}</a>
111 <img id="androidQrcode" class="hide-on-med-and-down" /> 111 <img id="androidQrcode" class="hide-on-med-and-down" />
112 </div> 112 </div>
113 <script> 113 <script>
@@ -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,12 +151,13 @@
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>
159 <li><a href="{{ path('archive_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.archive'|trans }}</a></li> 159 <li><a href="{{ path('archive_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.archive'|trans }}</a></li>
160 <li><a href="{{ path('all_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.all'|trans }}</a></li>
160 </ul> 161 </ul>
161 </div> 162 </div>
162 </div> 163 </div>
@@ -229,6 +230,9 @@
229 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red"> 230 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
230 {{ 'config.reset.tags'|trans }} 231 {{ 'config.reset.tags'|trans }}
231 </a> 232 </a>
233 <a href="{{ path('config_reset', { type: 'archived'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
234 {{ 'config.reset.archived'|trans }}
235 </a>
232 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red"> 236 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
233 {{ 'config.reset.entries'|trans }} 237 {{ 'config.reset.entries'|trans }}
234 </a> 238 </a>
@@ -251,11 +255,11 @@
251 {{ form_start(form.pwd) }} 255 {{ form_start(form.pwd) }}
252 {{ form_errors(form.pwd) }} 256 {{ form_errors(form.pwd) }}
253 257
254 <div class="row"> 258 <div class="row">
255 <div class="input-field col s12"> 259 <div class="input-field col s12">
256 {{ 'config.form_password.description'|trans }} 260 {{ 'config.form_password.description'|trans }}
261 </div>
257 </div> 262 </div>
258 </div>
259 263
260 <div class="row"> 264 <div class="row">
261 <div class="input-field col s12"> 265 <div class="input-field col s12">
@@ -410,8 +414,8 @@
410 <tr> 414 <tr>
411 <td>domainName</td> 415 <td>domainName</td>
412 <td>{{ 'config.form_rules.faq.variable_description.domainName'|trans }}</td> 416 <td>{{ 'config.form_rules.faq.variable_description.domainName'|trans }}</td>
413 <td>matches</td> 417 <td>matches<br />notmaches</td>
414 <td>{{ 'config.form_rules.faq.operator_description.matches'|trans|raw }}</td> 418 <td>{{ 'config.form_rules.faq.operator_description.matches'|trans|raw }}<br />{{ 'config.form_rules.faq.operator_description.notmatches'|trans|raw }}</td>
415 </tr> 419 </tr>
416 </tbody> 420 </tbody>
417 </table> 421 </table>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_actions.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_actions.html.twig
index d278da1b..468338ac 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_actions.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_actions.html.twig
@@ -9,7 +9,7 @@
9 <li> 9 <li>
10 <a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}redo{% endif %}</i></a> 10 <a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}redo{% endif %}</i></a>
11 <a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool grey-text" href="{{ path('star_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_border{% else %}star{% endif %}</i></a> 11 <a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool grey-text" href="{{ path('star_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_border{% else %}star{% endif %}</i></a>
12 <a title="{{ 'entry.list.delete'|trans }}" class="tool grey-text delete" href="{{ path('delete_entry', { 'id': entry.id }) }}"><i class="material-icons">delete</i></a> 12 <a title="{{ 'entry.list.delete'|trans }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" class="tool grey-text delete" href="{{ path('delete_entry', { 'id': entry.id }) }}"><i class="material-icons">delete</i></a>
13 </li> 13 </li>
14 </ul> 14 </ul>
15</div> 15</div>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_list.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_list.html.twig
index 3ba6253a..b64e1436 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_list.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/_card_list.html.twig
@@ -1,18 +1,28 @@
1<div class="card"> 1<div class="card-stacked">
2 <div class="card-stacked"> 2 <div class="preview">{% if entry.previewPicture is not null %}<img src="{{ entry.previewPicture }}" />{% endif %}</div>
3 <div class="card-content"> 3 <div class="card-content">
4 <span class="card-title dot-ellipsis dot-resize-update"> 4 <span class="card-title dot-ellipsis dot-resize-update">
5 <a href="{{ path('view', { 'id': entry.id }) }}" title="{{ entry.title | striptags | e('html_attr') }}"> 5 <a href="{{ path('view', { 'id': entry.id }) }}" title="{{ entry.title | striptags | e('html_attr') }}">
6 {{ entry.title| striptags | truncate(120, true, '…') | raw }} 6 {{ entry.title| striptags | truncate(120, true, '…') | raw }}
7 </a> 7 </a>
8 </span> 8 </span>
9 <ul class="tools-list right"> 9
10 <li> 10 <div class="metadata">
11 <a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}redo{% endif %}</i></a> 11 <a href="{{ entry.url|e }}" class="grey-text domain" target="_blank" title="{{ entry.domainName|removeWww }}">
12 <a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool grey-text" href="{{ path('star_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_border{% else %}star{% endif %}</i></a> 12 <span>{{ entry.domainName|removeWww }}</span>
13 <a title="{{ 'entry.list.delete'|trans }}" class="tool grey-text delete" href="{{ path('delete_entry', { 'id': entry.id }) }}"><i class="material-icons">delete</i></a> 13 </a>
14 </li> 14 {% for tag in entry.tags | slice(0, 3) %}
15 </ul> 15 <span class="chip hide-on-med-and-down">
16 <a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{ tag.label }}</a>
17 </span>
18 {% endfor %}
16 </div> 19 </div>
17 </div> 20 </div>
21 <ul class="tools-list hide-on-small-only">
22 <li>
23 <a title="{{ 'entry.list.toogle_as_read'|trans }}" class="tool grey-text" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isArchived == 0 %}done{% else %}redo{% endif %}</i></a>
24 <a title="{{ 'entry.list.toogle_as_star'|trans }}" class="tool grey-text" href="{{ path('star_entry', { 'id': entry.id }) }}"><i class="material-icons">{% if entry.isStarred == 0 %}star_border{% else %}star{% endif %}</i></a>
25 <a title="{{ 'entry.list.delete'|trans }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" class="tool grey-text delete" href="{{ path('delete_entry', { 'id': entry.id }) }}"><i class="material-icons">delete</i></a>
26 </li>
27 </ul>
18</div> 28</div>
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/edit.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/edit.html.twig
index 1c5e2aab..b9537975 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/edit.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/edit.html.twig
@@ -27,11 +27,6 @@
27 {{ form_label(form.url) }} 27 {{ form_label(form.url) }}
28 {{ form_widget(form.url) }} 28 {{ form_widget(form.url) }}
29 </div> 29 </div>
30
31 <div class="input-field s12">
32 {{ form_widget(form.is_public) }}
33 {{ form_label(form.is_public) }}
34 </div>
35 <br> 30 <br>
36 31
37 {{ form_widget(form.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }} 32 {{ form_widget(form.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
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..0c4dc80b 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
@@ -1,9 +1,16 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %} 1{% extends "WallabagCoreBundle::layout.html.twig" %}
2 2
3{% block head %}
4 {{ parent() }}
5 {% if tag is defined and app.user.config.rssToken %}
6 <link rel="alternate" type="application/rss+xml" href="{{ path('tag_rss', {'username': app.user.username, 'token': app.user.config.rssToken, 'slug': tag.slug}) }}" />
7 {% endif %}
8{% endblock %}
9
3{% block title %} 10{% block title %}
4 {% set filter = '' %} 11 {% set filter = '' %}
5 {% if tag is defined %} 12 {% if tag is defined %}
6 {% set filter = tag %} 13 {% set filter = tag.slug %}
7 {% endif %} 14 {% endif %}
8 {% if searchTerm is defined and searchTerm is not empty %} 15 {% if searchTerm is defined and searchTerm is not empty %}
9 {% set filter = searchTerm %} 16 {% set filter = searchTerm %}
@@ -13,10 +20,14 @@
13 20
14{% block content %} 21{% block content %}
15 {% set listMode = app.user.config.listMode %} 22 {% set listMode = app.user.config.listMode %}
23 {% set currentRoute = app.request.attributes.get('_route') %}
16 <div class="results clearfix"> 24 <div class="results clearfix">
17 <div class="nb-results left"> 25 <div class="nb-results left">
18 {{ 'entry.list.number_on_the_page'|transchoice(entries.count) }} 26 {{ 'entry.list.number_on_the_page'|transchoice(entries.count) }}
19 <a href="{{ path('switch_view_mode') }}"><i class="material-icons">{% if listMode == 0 %}view_list{% else %}view_module{% endif %}</i></a> 27 <a href="{{ path('switch_view_mode') }}"><i class="material-icons">{% if listMode == 0 %}view_list{% else %}view_module{% endif %}</i></a>
28 {% if app.user.config.rssToken %}
29 {% include "@WallabagCore/themes/common/Entry/_rss_link.html.twig" %}
30 {% endif %}
20 </div> 31 </div>
21 {% if entries.getNbPages > 1 %} 32 {% if entries.getNbPages > 1 %}
22 {{ pagerfanta(entries, 'twitter_bootstrap_translated', {'proximity': 1}) }} 33 {{ pagerfanta(entries, 'twitter_bootstrap_translated', {'proximity': 1}) }}
@@ -24,9 +35,9 @@
24 </div> 35 </div>
25 36
26 <br /> 37 <br />
27 <ul class="row data"> 38 <ul class="{% if listMode == 1 %}collection{% else %}row data{% endif %}">
28 {% for entry in entries %} 39 {% for entry in entries %}
29 <li id="entry-{{ entry.id|e }}" class="col {% if listMode == 0 %}l3 m6{% endif %} s12"> 40 <li id="entry-{{ entry.id|e }}" class="col {% if listMode == 0 %}l3 m6{% else %}collection-item{% endif %} s12">
30 {% if listMode == 1 %} 41 {% if listMode == 1 %}
31 {% include "@WallabagCore/themes/material/Entry/_card_list.html.twig" with {'entry': entry} only %} 42 {% include "@WallabagCore/themes/material/Entry/_card_list.html.twig" with {'entry': entry} only %}
32 {% elseif entry.previewPicture is null %} 43 {% elseif entry.previewPicture is null %}
@@ -45,11 +56,10 @@
45 {% endif %} 56 {% endif %}
46 57
47 <!-- Export --> 58 <!-- Export -->
48 <div id="export" class="side-nav fixed right-aligned"> 59 <div id="export" class="side-nav right-aligned">
49 {% set currentRoute = app.request.attributes.get('_route') %}
50 {% set currentTag = '' %} 60 {% set currentTag = '' %}
51 {% if tag is defined %} 61 {% if tag is defined %}
52 {% set currentTag = tag %} 62 {% set currentTag = tag.slug %}
53 {% endif %} 63 {% endif %}
54 {% if currentRoute == 'homepage' %} 64 {% if currentRoute == 'homepage' %}
55 {% set currentRoute = 'unread' %} 65 {% set currentRoute = 'unread' %}
@@ -68,7 +78,7 @@
68 78
69 <!-- Filters --> 79 <!-- Filters -->
70 {% if form is not null %} 80 {% if form is not null %}
71 <div id="filters" class="side-nav fixed right-aligned"> 81 <div id="filters" class="side-nav right-aligned">
72 <form action="{{ path('all') }}"> 82 <form action="{{ path('all') }}">
73 83
74 <h4 class="center">{{ 'entry.filters.title'|trans }}</h4> 84 <h4 class="center">{{ 'entry.filters.title'|trans }}</h4>
@@ -103,6 +113,15 @@
103 </div> 113 </div>
104 114
105 <div class="col s12"> 115 <div class="col s12">
116 <label>{{ 'entry.filters.is_public_help'|trans }}</label>
117 </div>
118
119 <div class="input-field col s12 with-checkbox">
120 {{ form_widget(form.isPublic) }}
121 {{ form_label(form.isPublic) }}
122 </div>
123
124 <div class="col s12">
106 {{ form_label(form.language) }} 125 {{ form_label(form.language) }}
107 </div> 126 </div>
108 127
@@ -121,10 +140,12 @@
121 <div class="col s12"> 140 <div class="col s12">
122 {{ form_label(form.readingTime) }} 141 {{ form_label(form.readingTime) }}
123 </div> 142 </div>
143
124 <div class="input-field col s6"> 144 <div class="input-field col s6">
125 {{ form_widget(form.readingTime.left_number, {'type': 'number'}) }} 145 {{ form_widget(form.readingTime.left_number, {'type': 'number'}) }}
126 <label for="entry_filter_readingTime_left_number">{{ 'entry.filters.reading_time.from'|trans }}</label> 146 <label for="entry_filter_readingTime_left_number">{{ 'entry.filters.reading_time.from'|trans }}</label>
127 </div> 147 </div>
148
128 <div class="input-field col s6"> 149 <div class="input-field col s6">
129 {{ form_widget(form.readingTime.right_number, {'type': 'number'}) }} 150 {{ form_widget(form.readingTime.right_number, {'type': 'number'}) }}
130 <label for="entry_filter_readingTime_right_number">{{ 'entry.filters.reading_time.to'|trans }}</label> 151 <label for="entry_filter_readingTime_right_number">{{ 'entry.filters.reading_time.to'|trans }}</label>
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 af53084f..4cff7bf2 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
@@ -12,6 +12,11 @@
12 <div class="nav-wrapper cyan darken-1"> 12 <div class="nav-wrapper cyan darken-1">
13 <ul> 13 <ul>
14 <li> 14 <li>
15 <a href="#" data-activates="slide-out" class="button-collapse">
16 <i class="material-icons">menu</i>
17 </a>
18 </li>
19 <li>
15 <a class="waves-effect" href="{{ path('homepage') }}"> 20 <a class="waves-effect" href="{{ path('homepage') }}">
16 <i class="material-icons">exit_to_app</i> 21 <i class="material-icons">exit_to_app</i>
17 </a> 22 </a>
@@ -28,11 +33,6 @@
28 <i class="material-icons small">{% if entry.isStarred == 0 %}star_outline{% else %}star{% endif %}</i> 33 <i class="material-icons small">{% if entry.isStarred == 0 %}star_outline{% else %}star{% endif %}</i>
29 </a> 34 </a>
30 </li> 35 </li>
31 <li>
32 <a href="#" data-activates="slide-out" class="button-collapse right">
33 <i class="material-icons">menu</i>
34 </a>
35 </li>
36 </ul> 36 </ul>
37 </div> 37 </div>
38 </nav> 38 </nav>
@@ -82,7 +82,7 @@
82 <div class="collapsible-body"></div> 82 <div class="collapsible-body"></div>
83 </li> 83 </li>
84 <li class="bold border-bottom"> 84 <li class="bold border-bottom">
85 <a class="waves-effect collapsible-header delete" title="{{ 'entry.view.left_menu.delete'|trans }}" href="{{ path('delete_entry', { 'id': entry.id }) }}"> 85 <a class="waves-effect collapsible-header delete" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')" title="{{ 'entry.view.left_menu.delete'|trans }}" href="{{ path('delete_entry', { 'id': entry.id }) }}">
86 <i class="material-icons small">delete</i> 86 <i class="material-icons small">delete</i>
87 <span>{{ 'entry.view.left_menu.delete'|trans }}</span> 87 <span>{{ 'entry.view.left_menu.delete'|trans }}</span>
88 </a> 88 </a>
@@ -125,39 +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 %}
133 {% if craue_setting('share_scuttle') %}
134 <li>
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">
136 <span>scuttle</span>
137 </a>
138 </li>
139 {% endif %}
134 {% if craue_setting('share_diaspora') %} 140 {% if craue_setting('share_diaspora') %}
135 <li> 141 <li>
136 <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">
137 <i class="tool icon-image icon-image--diaspora" title="diaspora"></i>
138 <span>diaspora*</span> 143 <span>diaspora*</span>
139 </a> 144 </a>
140 </li> 145 </li>
141 {% endif %} 146 {% endif %}
142 {% if craue_setting('share_unmark') %} 147 {% if craue_setting('share_unmark') %}
143 <li> 148 <li>
144 <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">
145 <i class="tool icon-image icon-image--unmark" title="unmark"></i>
146 <span>unmark.it</span> 150 <span>unmark.it</span>
147 </a> 151 </a>
148 </li> 152 </li>
149 {% endif %} 153 {% endif %}
150 {% if craue_setting('carrot') %} 154 {% if craue_setting('carrot') %}
151 <li> 155 <li>
152 <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">
153 <i class="tool icon-image icon-image--carrot"></i>
154 <span>Carrot</span> 157 <span>Carrot</span>
155 </a> 158 </a>
156 </li> 159 </li>
157 {% endif %} 160 {% endif %}
158 {% if craue_setting('share_mail') %} 161 {% if craue_setting('share_mail') %}
159 <li> 162 <li>
160 <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>
161 <span>{{ 'entry.view.left_menu.share_email_label'|trans }}</span> 165 <span>{{ 'entry.view.left_menu.share_email_label'|trans }}</span>
162 </a> 166 </a>
163 </li> 167 </li>
@@ -212,32 +216,51 @@
212 <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>
213 </header> 217 </header>
214 <aside> 218 <aside>
215 <ul class="tools"> 219 <div class="tools">
216 <li> 220 <ul class="stats">
217 {% include "@WallabagCore/themes/material/Entry/_reading_time.html.twig" with {'entry': entry} only %} 221 <li>
218 </li> 222 {% include "@WallabagCore/themes/material/Entry/_reading_time.html.twig" with {'entry': entry} only %}
219 <li> 223 </li>
220 <i class="material-icons" title="{{ 'entry.view.created_at'|trans }}">today</i> 224 <li>
221 {{ entry.createdAt|date('Y-m-d') }} 225 <i class="material-icons" title="{{ 'entry.view.created_at'|trans }}">today</i>
222 </li> 226 {{ entry.createdAt|date('Y-m-d H:i') }}
223 <li> 227 </li>
224 <i class="material-icons link">link</i> 228 {% if entry.publishedAt is not null %}
225 <a href="{{ entry.url|e }}" target="_blank" title="{{ 'entry.view.original_article'|trans }} : {{ entry.title|striptags }}" class="tool"> 229 <li>
226 {{ entry.domainName|removeWww }} 230 <i class="material-icons" title="{{ 'entry.view.published_at'|trans }}">create</i>
227 </a> 231 {{ entry.publishedAt|date('Y-m-d H:i') }}
228 </li> 232 </li>
229 <li> 233 {% endif %}
230 <i class="material-icons link">comment</i> 234 {% if entry.publishedBy is not empty %}
231 {{ 'entry.view.annotations_on_the_entry'|transchoice(entry.annotations | length) }} 235 <li>
232 </li> 236 <i class="material-icons" title="{{ 'entry.view.published_by'|trans }}">person</i>
233 <li id="list"> 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">
234 {% for tag in entry.tags %} 254 {% for tag in entry.tags %}
235 <div class="chip"> 255 <li class="chip">
236 <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>
237 </div> 257 <a href="{{ path('remove_tag', { 'entry': entry.id, 'tag': tag.id }) }}" onclick="return confirm('{{ 'entry.confirm.delete_tag'|trans|escape('js') }}')">
258 <i class="material-icons vertical-align-middle">delete</i>
259 </a>
260 </li>
238 {% endfor %} 261 {% endfor %}
239 </li> 262 </ul>
240 </ul> 263 </div>
241 264
242 <div class="input-field nav-panel-add-tag" style="display: none"> 265 <div class="input-field nav-panel-add-tag" style="display: none">
243 {{ render(controller( "WallabagCoreBundle:Tag:addTagForm", { 'id': entry.id } )) }} 266 {{ render(controller( "WallabagCoreBundle:Tag:addTagForm", { 'id': entry.id } )) }}
@@ -259,7 +282,7 @@
259 <ul> 282 <ul>
260 <li><a class="btn-floating" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons">done</i></a></li> 283 <li><a class="btn-floating" href="{{ path('archive_entry', { 'id': entry.id }) }}"><i class="material-icons">done</i></a></li>
261 <li><a class="btn-floating" href="{{ path('star_entry', { 'id': entry.id }) }}"><i class="material-icons">star_outline</i></a></li> 284 <li><a class="btn-floating" href="{{ path('star_entry', { 'id': entry.id }) }}"><i class="material-icons">star_outline</i></a></li>
262 <li><a class="btn-floating" href="{{ path('delete_entry', { 'id': entry.id }) }}"><i class="material-icons">delete</i></a></li> 285 <li><a class="btn-floating" href="{{ path('delete_entry', { 'id': entry.id }) }}" onclick="return confirm('{{ 'entry.confirm.delete'|trans|escape('js') }}')"><i class="material-icons">delete</i></a></li>
263 </ul> 286 </ul>
264 </div> 287 </div>
265 </div> 288 </div>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/SiteCredential/edit.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/SiteCredential/edit.html.twig
new file mode 100644
index 00000000..882be430
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/SiteCredential/edit.html.twig
@@ -0,0 +1,60 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{{ 'site_credential.page_title'|trans }}{% endblock %}
4
5{% block content %}
6
7 <div class="row">
8 <div class="col s12">
9 <div class="card-panel">
10 <div class="row">
11 <div class="input-field col s12">
12 <h4>{{ 'site_credential.edit_site_credential'|trans }}</h4>
13
14 <div id="set6" class="col s12">
15 {{ form_start(edit_form) }}
16 {{ form_errors(edit_form) }}
17
18 <div class="row">
19 <div class="input-field col s12">
20 {{ form_label(edit_form.host) }}
21 {{ form_errors(edit_form.host) }}
22 {{ form_widget(edit_form.host) }}
23 </div>
24 </div>
25
26 <div class="row">
27 <div class="input-field col s12">
28 {{ form_label(edit_form.username) }}
29 {{ form_errors(edit_form.username) }}
30 {{ form_widget(edit_form.username) }}
31 </div>
32 </div>
33
34 <div class="row">
35 <div class="input-field col s12">
36 {{ form_label(edit_form.password) }}
37 {{ form_errors(edit_form.password) }}
38 {{ form_widget(edit_form.password) }}
39 </div>
40 </div>
41
42 <br/>
43
44 {{ form_widget(edit_form.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
45 {{ form_widget(edit_form._token) }}
46 </form>
47 <p>
48 {{ form_start(delete_form) }}
49 <button onclick="return confirm('{{ 'site_credential.form.delete_confirm'|trans|escape('js') }}')" type="submit" class="btn waves-effect waves-light red">{{ 'site_credential.form.delete'|trans }}</button>
50 {{ form_end(delete_form) }}
51 </p>
52 <p><a class="waves-effect waves-light btn blue-grey" href="{{ path('site_credentials_index') }}">{{ 'site_credential.form.back_to_list'|trans }}</a></p>
53 </div>
54 </div>
55 </div>
56 </div>
57 </div>
58 </div>
59
60{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/SiteCredential/index.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/SiteCredential/index.html.twig
new file mode 100644
index 00000000..324854ad
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/SiteCredential/index.html.twig
@@ -0,0 +1,42 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{{ 'site_credential.page_title'|trans }}{% endblock %}
4
5{% block content %}
6
7 <div class="row">
8 <div class="col s12">
9 <div class="card-panel">
10 <div class="row">
11 <div class="input-field col s12">
12 <p class="help">{{ 'site_credential.description'|trans|raw }}</p>
13
14 <table class="bordered">
15 <thead>
16 <tr>
17 <th>{{ 'site_credential.form.host_label'|trans }}</th>
18 <th>{{ 'site_credential.list.actions'|trans }}</th>
19 </tr>
20 </thead>
21 <tbody>
22 {% for credential in credentials %}
23 <tr>
24 <td>{{ credential.host }}</td>
25 <td>
26 <a href="{{ path('site_credentials_edit', { 'id': credential.id }) }}">{{ 'site_credential.list.edit_action'|trans }}</a>
27 </td>
28 </tr>
29 {% endfor %}
30 </tbody>
31 </table>
32 <br />
33 <p>
34 <a href="{{ path('site_credentials_new') }}" class="waves-effect waves-light btn">{{ 'site_credential.list.create_new_one'|trans }}</a>
35 </p>
36 </div>
37 </div>
38 </div>
39 </div>
40 </div>
41
42{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/SiteCredential/new.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/SiteCredential/new.html.twig
new file mode 100644
index 00000000..3c008cde
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/SiteCredential/new.html.twig
@@ -0,0 +1,53 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{{ 'site_credential.page_title'|trans }}{% endblock %}
4
5{% block content %}
6
7 <div class="row">
8 <div class="col s12">
9 <div class="card-panel">
10 <div class="row">
11 <div class="input-field col s12">
12 <h4>{{ 'site_credential.new_site_credential'|trans }}</h4>
13
14 <div id="set6" class="col s12">
15 {{ form_start(form) }}
16 {{ form_errors(form) }}
17
18 <div class="row">
19 <div class="input-field col s12">
20 {{ form_label(form.host) }}
21 {{ form_errors(form.host) }}
22 {{ form_widget(form.host) }}
23 </div>
24 </div>
25
26 <div class="row">
27 <div class="input-field col s12">
28 {{ form_label(form.username) }}
29 {{ form_errors(form.username) }}
30 {{ form_widget(form.username) }}
31 </div>
32 </div>
33
34 <div class="row">
35 <div class="input-field col s12">
36 {{ form_label(form.password) }}
37 {{ form_errors(form.password) }}
38 {{ form_widget(form.password) }}
39 </div>
40 </div>
41
42 {{ form_widget(form.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
43 {{ form_rest(form) }}
44 </form>
45 <p><a class="waves-effect waves-light btn blue-grey" href="{{ path('site_credentials_index') }}">{{ 'site_credential.form.back_to_list'|trans }}</a></p>
46 </div>
47 </div>
48 </div>
49 </div>
50 </div>
51 </div>
52
53{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Tag/tags.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Tag/tags.html.twig
index c83543ac..97ddedc9 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/Tag/tags.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Tag/tags.html.twig
@@ -14,6 +14,9 @@
14 {% for tag in tags %} 14 {% for tag in tags %}
15 <li title="{{tag.label}} ({{ tag.nbEntries }})" id="tag-{{ tag.id }}" class="col l2 m2 s5"> 15 <li title="{{tag.label}} ({{ tag.nbEntries }})" id="tag-{{ tag.id }}" class="col l2 m2 s5">
16 <a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{tag.label}} ({{ tag.nbEntries }})</a> 16 <a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{tag.label}} ({{ tag.nbEntries }})</a>
17 {% if app.user.config.rssToken %}
18 <a rel="alternate" type="application/rss+xml" href="{{ path('tag_rss', {'username': app.user.username, 'token': app.user.config.rssToken, 'slug': tag.slug}) }}" class="right"><i class="material-icons">rss_feed</i></a>
19 {% endif %}
17 </li> 20 </li>
18 {% endfor %} 21 {% endfor %}
19 </ul> 22 </ul>
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..60907e11 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 %}
@@ -64,6 +66,11 @@
64 <li class="bold {% if currentRoute == 'config' %}active{% endif %}"> 66 <li class="bold {% if currentRoute == 'config' %}active{% endif %}">
65 <a class="waves-effect" href="{{ path('config') }}">{{ 'menu.left.config'|trans }}</a> 67 <a class="waves-effect" href="{{ path('config') }}">{{ 'menu.left.config'|trans }}</a>
66 </li> 68 </li>
69 {% if craue_setting('restricted_access') %}
70 <li class="bold {% if currentRoute starts with 'site_credentials_' %}active{% endif %}">
71 <a class="waves-effect" href="{{ path('site_credentials_index') }}">{{ 'menu.left.site_credentials'|trans }}</a>
72 </li>
73 {% endif %}
67 {% if is_granted('ROLE_SUPER_ADMIN') %} 74 {% if is_granted('ROLE_SUPER_ADMIN') %}
68 <li class="bold {% if currentRoute starts with 'user_' %}active{% endif %}"> 75 <li class="bold {% if currentRoute starts with 'user_' %}active{% endif %}">
69 <a class="waves-effect" href="{{ path('user_index') }}">{{ 'menu.left.users_management'|trans }}</a> 76 <a class="waves-effect" href="{{ path('user_index') }}">{{ 'menu.left.users_management'|trans }}</a>
@@ -116,12 +123,12 @@
116 </ul> 123 </ul>
117 <div class="input-field nav-panel-search" style="display: none"> 124 <div class="input-field nav-panel-search" style="display: none">
118 {{ render(controller("WallabagCoreBundle:Entry:searchForm", {'currentRoute': app.request.attributes.get('_route')})) }} 125 {{ 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> 126 <label for="search"><i class="material-icons search">search</i></label>
120 <i class="material-icons close">clear</i> 127 <i class="material-icons close">clear</i>
121 </div> 128 </div>
122 <div class="input-field nav-panel-add" style="display: none"> 129 <div class="input-field nav-panel-add" style="display: none">
123 {{ render(controller("WallabagCoreBundle:Entry:addEntryForm")) }} 130 {{ render(controller("WallabagCoreBundle:Entry:addEntryForm")) }}
124 <label for="add" class="active"><i class="material-icons add">add</i></label> 131 <label for="add"><i class="material-icons add">add</i></label>
125 <i class="material-icons close">clear</i> 132 <i class="material-icons close">clear</i>
126 </div> 133 </div>
127 </div> 134 </div>
diff --git a/src/Wallabag/ImportBundle/Command/ImportCommand.php b/src/Wallabag/ImportBundle/Command/ImportCommand.php
index 28d01715..5f1ab0af 100644
--- a/src/Wallabag/ImportBundle/Command/ImportCommand.php
+++ b/src/Wallabag/ImportBundle/Command/ImportCommand.php
@@ -5,6 +5,7 @@ namespace Wallabag\ImportBundle\Command;
5use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; 5use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
6use Symfony\Component\Config\Definition\Exception\Exception; 6use Symfony\Component\Config\Definition\Exception\Exception;
7use Symfony\Component\Console\Input\InputArgument; 7use Symfony\Component\Console\Input\InputArgument;
8use Symfony\Component\Console\Input\InputOption;
8use Symfony\Component\Console\Input\InputInterface; 9use Symfony\Component\Console\Input\InputInterface;
9use Symfony\Component\Console\Output\OutputInterface; 10use Symfony\Component\Console\Output\OutputInterface;
10 11
@@ -15,10 +16,12 @@ class ImportCommand extends ContainerAwareCommand
15 $this 16 $this
16 ->setName('wallabag:import') 17 ->setName('wallabag:import')
17 ->setDescription('Import entries from a JSON export') 18 ->setDescription('Import entries from a JSON export')
18 ->addArgument('userId', InputArgument::REQUIRED, 'User ID to populate') 19 ->addArgument('username', InputArgument::REQUIRED, 'User to populate')
19 ->addArgument('filepath', InputArgument::REQUIRED, 'Path to the JSON file') 20 ->addArgument('filepath', InputArgument::REQUIRED, 'Path to the JSON file')
20 ->addOption('importer', null, InputArgument::OPTIONAL, 'The importer to use: v1, v2, instapaper, pinboard, readability, firefox or chrome', 'v1') 21 ->addOption('importer', null, InputOption::VALUE_OPTIONAL, 'The importer to use: v1, v2, instapaper, pinboard, readability, firefox or chrome', 'v1')
21 ->addOption('markAsRead', null, InputArgument::OPTIONAL, 'Mark all entries as read', false) 22 ->addOption('markAsRead', null, InputOption::VALUE_OPTIONAL, 'Mark all entries as read', false)
23 ->addOption('useUserId', null, InputOption::VALUE_NONE, 'Use user id instead of username to find account')
24 ->addOption('disableContentUpdate', null, InputOption::VALUE_NONE, 'Disable fetching updated content from URL')
22 ; 25 ;
23 } 26 }
24 27
@@ -34,10 +37,14 @@ class ImportCommand extends ContainerAwareCommand
34 // Turning off doctrine default logs queries for saving memory 37 // Turning off doctrine default logs queries for saving memory
35 $em->getConnection()->getConfiguration()->setSQLLogger(null); 38 $em->getConnection()->getConfiguration()->setSQLLogger(null);
36 39
37 $user = $em->getRepository('WallabagUserBundle:User')->findOneById($input->getArgument('userId')); 40 if ($input->getOption('useUserId')) {
41 $user = $em->getRepository('WallabagUserBundle:User')->findOneById($input->getArgument('username'));
42 } else {
43 $user = $em->getRepository('WallabagUserBundle:User')->findOneByUsername($input->getArgument('username'));
44 }
38 45
39 if (!is_object($user)) { 46 if (!is_object($user)) {
40 throw new Exception(sprintf('User with id "%s" not found', $input->getArgument('userId'))); 47 throw new Exception(sprintf('User "%s" not found', $input->getArgument('username')));
41 } 48 }
42 49
43 switch ($input->getOption('importer')) { 50 switch ($input->getOption('importer')) {
@@ -64,6 +71,7 @@ class ImportCommand extends ContainerAwareCommand
64 } 71 }
65 72
66 $import->setMarkAsRead($input->getOption('markAsRead')); 73 $import->setMarkAsRead($input->getOption('markAsRead'));
74 $import->setDisableContentUpdate($input->getOption('disableContentUpdate'));
67 $import->setUser($user); 75 $import->setUser($user);
68 76
69 $res = $import 77 $res = $import
diff --git a/src/Wallabag/ImportBundle/Import/AbstractImport.php b/src/Wallabag/ImportBundle/Import/AbstractImport.php
index 1d4a6e27..9b624296 100644
--- a/src/Wallabag/ImportBundle/Import/AbstractImport.php
+++ b/src/Wallabag/ImportBundle/Import/AbstractImport.php
@@ -8,6 +8,7 @@ use Doctrine\ORM\EntityManager;
8use Wallabag\CoreBundle\Helper\ContentProxy; 8use Wallabag\CoreBundle\Helper\ContentProxy;
9use Wallabag\CoreBundle\Entity\Entry; 9use Wallabag\CoreBundle\Entity\Entry;
10use Wallabag\CoreBundle\Entity\Tag; 10use Wallabag\CoreBundle\Entity\Tag;
11use Wallabag\CoreBundle\Helper\TagsAssigner;
11use Wallabag\UserBundle\Entity\User; 12use Wallabag\UserBundle\Entity\User;
12use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface; 13use OldSound\RabbitMqBundle\RabbitMq\ProducerInterface;
13use Symfony\Component\EventDispatcher\EventDispatcherInterface; 14use Symfony\Component\EventDispatcher\EventDispatcherInterface;
@@ -18,19 +19,22 @@ abstract class AbstractImport implements ImportInterface
18 protected $em; 19 protected $em;
19 protected $logger; 20 protected $logger;
20 protected $contentProxy; 21 protected $contentProxy;
22 protected $tagsAssigner;
21 protected $eventDispatcher; 23 protected $eventDispatcher;
22 protected $producer; 24 protected $producer;
23 protected $user; 25 protected $user;
24 protected $markAsRead; 26 protected $markAsRead;
27 protected $disableContentUpdate = false;
25 protected $skippedEntries = 0; 28 protected $skippedEntries = 0;
26 protected $importedEntries = 0; 29 protected $importedEntries = 0;
27 protected $queuedEntries = 0; 30 protected $queuedEntries = 0;
28 31
29 public function __construct(EntityManager $em, ContentProxy $contentProxy, EventDispatcherInterface $eventDispatcher) 32 public function __construct(EntityManager $em, ContentProxy $contentProxy, TagsAssigner $tagsAssigner, EventDispatcherInterface $eventDispatcher)
30 { 33 {
31 $this->em = $em; 34 $this->em = $em;
32 $this->logger = new NullLogger(); 35 $this->logger = new NullLogger();
33 $this->contentProxy = $contentProxy; 36 $this->contentProxy = $contentProxy;
37 $this->tagsAssigner = $tagsAssigner;
34 $this->eventDispatcher = $eventDispatcher; 38 $this->eventDispatcher = $eventDispatcher;
35 } 39 }
36 40
@@ -82,21 +86,34 @@ abstract class AbstractImport implements ImportInterface
82 } 86 }
83 87
84 /** 88 /**
89 * Set whether articles should be fetched for updated content.
90 *
91 * @param bool $disableContentUpdate
92 */
93 public function setDisableContentUpdate($disableContentUpdate)
94 {
95 $this->disableContentUpdate = $disableContentUpdate;
96
97 return $this;
98 }
99
100 /**
85 * Fetch content from the ContentProxy (using graby). 101 * Fetch content from the ContentProxy (using graby).
86 * If it fails return the given entry to be saved in all case (to avoid user to loose the content). 102 * If it fails return the given entry to be saved in all case (to avoid user to loose the content).
87 * 103 *
88 * @param Entry $entry Entry to update 104 * @param Entry $entry Entry to update
89 * @param string $url Url to grab content for 105 * @param string $url Url to grab content for
90 * @param array $content An array with AT LEAST keys title, html, url, language & content_type to skip the fetchContent from the url 106 * @param array $content An array with AT LEAST keys title, html, url, language & content_type to skip the fetchContent from the url
91 *
92 * @return Entry
93 */ 107 */
94 protected function fetchContent(Entry $entry, $url, array $content = []) 108 protected function fetchContent(Entry $entry, $url, array $content = [])
95 { 109 {
96 try { 110 try {
97 return $this->contentProxy->updateEntry($entry, $url, $content); 111 $this->contentProxy->updateEntry($entry, $url, $content, $this->disableContentUpdate);
98 } catch (\Exception $e) { 112 } catch (\Exception $e) {
99 return $entry; 113 $this->logger->error('Error trying to import an entry.', [
114 'entry_url' => $url,
115 'error_msg' => $e->getMessage(),
116 ]);
100 } 117 }
101 } 118 }
102 119
diff --git a/src/Wallabag/ImportBundle/Import/BrowserImport.php b/src/Wallabag/ImportBundle/Import/BrowserImport.php
index 8bf7d92e..71e65e59 100644
--- a/src/Wallabag/ImportBundle/Import/BrowserImport.php
+++ b/src/Wallabag/ImportBundle/Import/BrowserImport.php
@@ -4,7 +4,6 @@ namespace Wallabag\ImportBundle\Import;
4 4
5use Wallabag\CoreBundle\Entity\Entry; 5use Wallabag\CoreBundle\Entity\Entry;
6use Wallabag\UserBundle\Entity\User; 6use Wallabag\UserBundle\Entity\User;
7use Wallabag\CoreBundle\Helper\ContentProxy;
8use Wallabag\CoreBundle\Event\EntrySavedEvent; 7use Wallabag\CoreBundle\Event\EntrySavedEvent;
9 8
10abstract class BrowserImport extends AbstractImport 9abstract class BrowserImport extends AbstractImport
@@ -202,10 +201,10 @@ abstract class BrowserImport extends AbstractImport
202 $entry->setTitle($data['title']); 201 $entry->setTitle($data['title']);
203 202
204 // update entry with content (in case fetching failed, the given entry will be return) 203 // update entry with content (in case fetching failed, the given entry will be return)
205 $entry = $this->fetchContent($entry, $data['url'], $data); 204 $this->fetchContent($entry, $data['url'], $data);
206 205
207 if (array_key_exists('tags', $data)) { 206 if (array_key_exists('tags', $data)) {
208 $this->contentProxy->assignTagsToEntry( 207 $this->tagsAssigner->assignTagsToEntry(
209 $entry, 208 $entry,
210 $data['tags'] 209 $data['tags']
211 ); 210 );
diff --git a/src/Wallabag/ImportBundle/Import/InstapaperImport.php b/src/Wallabag/ImportBundle/Import/InstapaperImport.php
index 70a53f1a..3aa12f6f 100644
--- a/src/Wallabag/ImportBundle/Import/InstapaperImport.php
+++ b/src/Wallabag/ImportBundle/Import/InstapaperImport.php
@@ -68,6 +68,14 @@ class InstapaperImport extends AbstractImport
68 continue; 68 continue;
69 } 69 }
70 70
71 // last element in the csv is the folder where the content belong
72 // BUT it can also be the status (since status = folder in Instapaper)
73 // and we don't want archive, unread & starred to become a tag
74 $tags = null;
75 if (false === in_array($data[3], ['Archive', 'Unread', 'Starred'])) {
76 $tags = [$data[3]];
77 }
78
71 $entries[] = [ 79 $entries[] = [
72 'url' => $data[0], 80 'url' => $data[0],
73 'title' => $data[1], 81 'title' => $data[1],
@@ -75,6 +83,7 @@ class InstapaperImport extends AbstractImport
75 'is_archived' => $data[3] === 'Archive' || $data[3] === 'Starred', 83 'is_archived' => $data[3] === 'Archive' || $data[3] === 'Starred',
76 'is_starred' => $data[3] === 'Starred', 84 'is_starred' => $data[3] === 'Starred',
77 'html' => false, 85 'html' => false,
86 'tags' => $tags,
78 ]; 87 ];
79 } 88 }
80 fclose($handle); 89 fclose($handle);
@@ -116,7 +125,15 @@ class InstapaperImport extends AbstractImport
116 $entry->setTitle($importedEntry['title']); 125 $entry->setTitle($importedEntry['title']);
117 126
118 // update entry with content (in case fetching failed, the given entry will be return) 127 // update entry with content (in case fetching failed, the given entry will be return)
119 $entry = $this->fetchContent($entry, $importedEntry['url'], $importedEntry); 128 $this->fetchContent($entry, $importedEntry['url'], $importedEntry);
129
130 if (!empty($importedEntry['tags'])) {
131 $this->tagsAssigner->assignTagsToEntry(
132 $entry,
133 $importedEntry['tags'],
134 $this->em->getUnitOfWork()->getScheduledEntityInsertions()
135 );
136 }
120 137
121 $entry->setArchived($importedEntry['is_archived']); 138 $entry->setArchived($importedEntry['is_archived']);
122 $entry->setStarred($importedEntry['is_starred']); 139 $entry->setStarred($importedEntry['is_starred']);
diff --git a/src/Wallabag/ImportBundle/Import/PinboardImport.php b/src/Wallabag/ImportBundle/Import/PinboardImport.php
index d9865534..110b0464 100644
--- a/src/Wallabag/ImportBundle/Import/PinboardImport.php
+++ b/src/Wallabag/ImportBundle/Import/PinboardImport.php
@@ -109,10 +109,10 @@ class PinboardImport extends AbstractImport
109 $entry->setTitle($data['title']); 109 $entry->setTitle($data['title']);
110 110
111 // update entry with content (in case fetching failed, the given entry will be return) 111 // update entry with content (in case fetching failed, the given entry will be return)
112 $entry = $this->fetchContent($entry, $data['url'], $data); 112 $this->fetchContent($entry, $data['url'], $data);
113 113
114 if (!empty($data['tags'])) { 114 if (!empty($data['tags'])) {
115 $this->contentProxy->assignTagsToEntry( 115 $this->tagsAssigner->assignTagsToEntry(
116 $entry, 116 $entry,
117 $data['tags'], 117 $data['tags'],
118 $this->em->getUnitOfWork()->getScheduledEntityInsertions() 118 $this->em->getUnitOfWork()->getScheduledEntityInsertions()
diff --git a/src/Wallabag/ImportBundle/Import/PocketImport.php b/src/Wallabag/ImportBundle/Import/PocketImport.php
index 33093480..c1d5b6da 100644
--- a/src/Wallabag/ImportBundle/Import/PocketImport.php
+++ b/src/Wallabag/ImportBundle/Import/PocketImport.php
@@ -5,7 +5,6 @@ namespace Wallabag\ImportBundle\Import;
5use GuzzleHttp\Client; 5use GuzzleHttp\Client;
6use GuzzleHttp\Exception\RequestException; 6use GuzzleHttp\Exception\RequestException;
7use Wallabag\CoreBundle\Entity\Entry; 7use Wallabag\CoreBundle\Entity\Entry;
8use Wallabag\CoreBundle\Helper\ContentProxy;
9 8
10class PocketImport extends AbstractImport 9class PocketImport extends AbstractImport
11{ 10{
@@ -193,7 +192,7 @@ class PocketImport extends AbstractImport
193 $entry->setUrl($url); 192 $entry->setUrl($url);
194 193
195 // update entry with content (in case fetching failed, the given entry will be return) 194 // update entry with content (in case fetching failed, the given entry will be return)
196 $entry = $this->fetchContent($entry, $url); 195 $this->fetchContent($entry, $url);
197 196
198 // 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted 197 // 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted
199 $entry->setArchived($importedEntry['status'] == 1 || $this->markAsRead); 198 $entry->setArchived($importedEntry['status'] == 1 || $this->markAsRead);
@@ -216,7 +215,7 @@ class PocketImport extends AbstractImport
216 } 215 }
217 216
218 if (isset($importedEntry['tags']) && !empty($importedEntry['tags'])) { 217 if (isset($importedEntry['tags']) && !empty($importedEntry['tags'])) {
219 $this->contentProxy->assignTagsToEntry( 218 $this->tagsAssigner->assignTagsToEntry(
220 $entry, 219 $entry,
221 array_keys($importedEntry['tags']), 220 array_keys($importedEntry['tags']),
222 $this->em->getUnitOfWork()->getScheduledEntityInsertions() 221 $this->em->getUnitOfWork()->getScheduledEntityInsertions()
diff --git a/src/Wallabag/ImportBundle/Import/ReadabilityImport.php b/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
index de320d23..002b27f4 100644
--- a/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
+++ b/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
@@ -109,7 +109,7 @@ class ReadabilityImport extends AbstractImport
109 $entry->setTitle($data['title']); 109 $entry->setTitle($data['title']);
110 110
111 // update entry with content (in case fetching failed, the given entry will be return) 111 // update entry with content (in case fetching failed, the given entry will be return)
112 $entry = $this->fetchContent($entry, $data['url'], $data); 112 $this->fetchContent($entry, $data['url'], $data);
113 113
114 $entry->setArchived($data['is_archived']); 114 $entry->setArchived($data['is_archived']);
115 $entry->setStarred($data['is_starred']); 115 $entry->setStarred($data['is_starred']);
diff --git a/src/Wallabag/ImportBundle/Import/WallabagImport.php b/src/Wallabag/ImportBundle/Import/WallabagImport.php
index 702da057..c64ccd64 100644
--- a/src/Wallabag/ImportBundle/Import/WallabagImport.php
+++ b/src/Wallabag/ImportBundle/Import/WallabagImport.php
@@ -108,10 +108,10 @@ abstract class WallabagImport extends AbstractImport
108 $entry->setTitle($data['title']); 108 $entry->setTitle($data['title']);
109 109
110 // update entry with content (in case fetching failed, the given entry will be return) 110 // update entry with content (in case fetching failed, the given entry will be return)
111 $entry = $this->fetchContent($entry, $data['url'], $data); 111 $this->fetchContent($entry, $data['url'], $data);
112 112
113 if (array_key_exists('tags', $data)) { 113 if (array_key_exists('tags', $data)) {
114 $this->contentProxy->assignTagsToEntry( 114 $this->tagsAssigner->assignTagsToEntry(
115 $entry, 115 $entry,
116 $data['tags'], 116 $data['tags'],
117 $this->em->getUnitOfWork()->getScheduledEntityInsertions() 117 $this->em->getUnitOfWork()->getScheduledEntityInsertions()
diff --git a/src/Wallabag/ImportBundle/Import/WallabagV1Import.php b/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
index 59e3ce02..1f0df646 100644
--- a/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
+++ b/src/Wallabag/ImportBundle/Import/WallabagV1Import.php
@@ -4,6 +4,17 @@ namespace Wallabag\ImportBundle\Import;
4 4
5class WallabagV1Import extends WallabagImport 5class WallabagV1Import extends WallabagImport
6{ 6{
7 protected $fetchingErrorMessage;
8 protected $fetchingErrorMessageTitle;
9
10 public function __construct($em, $contentProxy, $tagsAssigner, $eventDispatcher, $fetchingErrorMessageTitle, $fetchingErrorMessage)
11 {
12 $this->fetchingErrorMessageTitle = $fetchingErrorMessageTitle;
13 $this->fetchingErrorMessage = $fetchingErrorMessage;
14
15 parent::__construct($em, $contentProxy, $tagsAssigner, $eventDispatcher);
16 }
17
7 /** 18 /**
8 * {@inheritdoc} 19 * {@inheritdoc}
9 */ 20 */
@@ -43,10 +54,11 @@ class WallabagV1Import extends WallabagImport
43 'created_at' => '', 54 'created_at' => '',
44 ]; 55 ];
45 56
46 // force content to be refreshed in case on bad fetch in the v1 installation 57 // In case of a bad fetch in v1, replace title and content with v2 error strings
58 // If fetching fails again, they will get this instead of the v1 strings
47 if (in_array($entry['title'], $this->untitled)) { 59 if (in_array($entry['title'], $this->untitled)) {
48 $data['title'] = ''; 60 $data['title'] = $this->fetchingErrorMessageTitle;
49 $data['html'] = ''; 61 $data['html'] = $this->fetchingErrorMessage;
50 } 62 }
51 63
52 if (array_key_exists('tags', $entry) && $entry['tags'] != '') { 64 if (array_key_exists('tags', $entry) && $entry['tags'] != '') {
diff --git a/src/Wallabag/ImportBundle/Import/WallabagV2Import.php b/src/Wallabag/ImportBundle/Import/WallabagV2Import.php
index d2a89d79..3e085ecf 100644
--- a/src/Wallabag/ImportBundle/Import/WallabagV2Import.php
+++ b/src/Wallabag/ImportBundle/Import/WallabagV2Import.php
@@ -36,8 +36,8 @@ class WallabagV2Import extends WallabagImport
36 return [ 36 return [
37 'html' => $entry['content'], 37 'html' => $entry['content'],
38 'content_type' => $entry['mimetype'], 38 'content_type' => $entry['mimetype'],
39 'is_archived' => (int) ($entry['is_archived'] || $this->markAsRead), 39 'is_archived' => (bool) ($entry['is_archived'] || $this->markAsRead),
40 'is_starred' => false, 40 'is_starred' => (bool) $entry['is_starred'],
41 ] + $entry; 41 ] + $entry;
42 } 42 }
43 43
diff --git a/src/Wallabag/ImportBundle/Resources/config/services.yml b/src/Wallabag/ImportBundle/Resources/config/services.yml
index c4fe3f92..b224a6a2 100644
--- a/src/Wallabag/ImportBundle/Resources/config/services.yml
+++ b/src/Wallabag/ImportBundle/Resources/config/services.yml
@@ -20,6 +20,7 @@ services:
20 arguments: 20 arguments:
21 - "@doctrine.orm.entity_manager" 21 - "@doctrine.orm.entity_manager"
22 - "@wallabag_core.content_proxy" 22 - "@wallabag_core.content_proxy"
23 - "@wallabag_core.tags_assigner"
23 - "@event_dispatcher" 24 - "@event_dispatcher"
24 calls: 25 calls:
25 - [ setClient, [ "@wallabag_import.pocket.client" ] ] 26 - [ setClient, [ "@wallabag_import.pocket.client" ] ]
@@ -32,7 +33,10 @@ services:
32 arguments: 33 arguments:
33 - "@doctrine.orm.entity_manager" 34 - "@doctrine.orm.entity_manager"
34 - "@wallabag_core.content_proxy" 35 - "@wallabag_core.content_proxy"
36 - "@wallabag_core.tags_assigner"
35 - "@event_dispatcher" 37 - "@event_dispatcher"
38 - "%wallabag_core.fetching_error_message_title%"
39 - "%wallabag_core.fetching_error_message%"
36 calls: 40 calls:
37 - [ setLogger, [ "@logger" ]] 41 - [ setLogger, [ "@logger" ]]
38 tags: 42 tags:
@@ -43,6 +47,7 @@ services:
43 arguments: 47 arguments:
44 - "@doctrine.orm.entity_manager" 48 - "@doctrine.orm.entity_manager"
45 - "@wallabag_core.content_proxy" 49 - "@wallabag_core.content_proxy"
50 - "@wallabag_core.tags_assigner"
46 - "@event_dispatcher" 51 - "@event_dispatcher"
47 calls: 52 calls:
48 - [ setLogger, [ "@logger" ]] 53 - [ setLogger, [ "@logger" ]]
@@ -54,6 +59,7 @@ services:
54 arguments: 59 arguments:
55 - "@doctrine.orm.entity_manager" 60 - "@doctrine.orm.entity_manager"
56 - "@wallabag_core.content_proxy" 61 - "@wallabag_core.content_proxy"
62 - "@wallabag_core.tags_assigner"
57 - "@event_dispatcher" 63 - "@event_dispatcher"
58 calls: 64 calls:
59 - [ setLogger, [ "@logger" ]] 65 - [ setLogger, [ "@logger" ]]
@@ -65,6 +71,7 @@ services:
65 arguments: 71 arguments:
66 - "@doctrine.orm.entity_manager" 72 - "@doctrine.orm.entity_manager"
67 - "@wallabag_core.content_proxy" 73 - "@wallabag_core.content_proxy"
74 - "@wallabag_core.tags_assigner"
68 - "@event_dispatcher" 75 - "@event_dispatcher"
69 calls: 76 calls:
70 - [ setLogger, [ "@logger" ]] 77 - [ setLogger, [ "@logger" ]]
@@ -76,6 +83,7 @@ services:
76 arguments: 83 arguments:
77 - "@doctrine.orm.entity_manager" 84 - "@doctrine.orm.entity_manager"
78 - "@wallabag_core.content_proxy" 85 - "@wallabag_core.content_proxy"
86 - "@wallabag_core.tags_assigner"
79 - "@event_dispatcher" 87 - "@event_dispatcher"
80 calls: 88 calls:
81 - [ setLogger, [ "@logger" ]] 89 - [ setLogger, [ "@logger" ]]
@@ -87,6 +95,7 @@ services:
87 arguments: 95 arguments:
88 - "@doctrine.orm.entity_manager" 96 - "@doctrine.orm.entity_manager"
89 - "@wallabag_core.content_proxy" 97 - "@wallabag_core.content_proxy"
98 - "@wallabag_core.tags_assigner"
90 - "@event_dispatcher" 99 - "@event_dispatcher"
91 calls: 100 calls:
92 - [ setLogger, [ "@logger" ]] 101 - [ setLogger, [ "@logger" ]]
@@ -97,6 +106,7 @@ services:
97 arguments: 106 arguments:
98 - "@doctrine.orm.entity_manager" 107 - "@doctrine.orm.entity_manager"
99 - "@wallabag_core.content_proxy" 108 - "@wallabag_core.content_proxy"
109 - "@wallabag_core.tags_assigner"
100 - "@event_dispatcher" 110 - "@event_dispatcher"
101 calls: 111 calls:
102 - [ setLogger, [ "@logger" ]] 112 - [ setLogger, [ "@logger" ]]
diff --git a/src/Wallabag/UserBundle/Controller/ManageController.php b/src/Wallabag/UserBundle/Controller/ManageController.php
index 92ee2b41..084f2c67 100644
--- a/src/Wallabag/UserBundle/Controller/ManageController.php
+++ b/src/Wallabag/UserBundle/Controller/ManageController.php
@@ -4,12 +4,15 @@ namespace Wallabag\UserBundle\Controller;
4 4
5use FOS\UserBundle\Event\UserEvent; 5use FOS\UserBundle\Event\UserEvent;
6use FOS\UserBundle\FOSUserEvents; 6use FOS\UserBundle\FOSUserEvents;
7use Pagerfanta\Adapter\DoctrineORMAdapter;
8use Pagerfanta\Exception\OutOfRangeCurrentPageException;
9use Pagerfanta\Pagerfanta;
7use Symfony\Component\HttpFoundation\Request; 10use Symfony\Component\HttpFoundation\Request;
8use Symfony\Bundle\FrameworkBundle\Controller\Controller; 11use Symfony\Bundle\FrameworkBundle\Controller\Controller;
9use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; 12use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
10use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 13use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
11use Wallabag\UserBundle\Entity\User; 14use Wallabag\UserBundle\Entity\User;
12use Wallabag\CoreBundle\Entity\Config; 15use Wallabag\UserBundle\Form\SearchUserType;
13 16
14/** 17/**
15 * User controller. 18 * User controller.
@@ -17,23 +20,6 @@ use Wallabag\CoreBundle\Entity\Config;
17class ManageController extends Controller 20class ManageController extends Controller
18{ 21{
19 /** 22 /**
20 * Lists all User entities.
21 *
22 * @Route("/", name="user_index")
23 * @Method("GET")
24 */
25 public function indexAction()
26 {
27 $em = $this->getDoctrine()->getManager();
28
29 $users = $em->getRepository('WallabagUserBundle:User')->findAll();
30
31 return $this->render('WallabagUserBundle:Manage:index.html.twig', array(
32 'users' => $users,
33 ));
34 }
35
36 /**
37 * Creates a new User entity. 23 * Creates a new User entity.
38 * 24 *
39 * @Route("/new", name="user_new") 25 * @Route("/new", name="user_new")
@@ -47,9 +33,7 @@ class ManageController extends Controller
47 // enable created user by default 33 // enable created user by default
48 $user->setEnabled(true); 34 $user->setEnabled(true);
49 35
50 $form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user, [ 36 $form = $this->createForm('Wallabag\UserBundle\Form\NewUserType', $user);
51 'validation_groups' => ['Profile'],
52 ]);
53 $form->handleRequest($request); 37 $form->handleRequest($request);
54 38
55 if ($form->isSubmitted() && $form->isValid()) { 39 if ($form->isSubmitted() && $form->isValid()) {
@@ -146,4 +130,49 @@ class ManageController extends Controller
146 ->getForm() 130 ->getForm()
147 ; 131 ;
148 } 132 }
133
134 /**
135 * @param Request $request
136 * @param int $page
137 *
138 * @Route("/list/{page}", name="user_index", defaults={"page" = 1})
139 *
140 * Default parameter for page is hardcoded (in duplication of the defaults from the Route)
141 * because this controller is also called inside the layout template without any page as argument
142 *
143 * @return \Symfony\Component\HttpFoundation\Response
144 */
145 public function searchFormAction(Request $request, $page = 1)
146 {
147 $em = $this->getDoctrine()->getManager();
148 $qb = $em->getRepository('WallabagUserBundle:User')->createQueryBuilder('u');
149
150 $form = $this->createForm(SearchUserType::class);
151 $form->handleRequest($request);
152
153 if ($form->isSubmitted() && $form->isValid()) {
154 $this->get('logger')->info('searching users');
155
156 $searchTerm = (isset($request->get('search_user')['term']) ? $request->get('search_user')['term'] : '');
157
158 $qb = $em->getRepository('WallabagUserBundle:User')->getQueryBuilderForSearch($searchTerm);
159 }
160
161 $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false);
162 $pagerFanta = new Pagerfanta($pagerAdapter);
163 $pagerFanta->setMaxPerPage(50);
164
165 try {
166 $pagerFanta->setCurrentPage($page);
167 } catch (OutOfRangeCurrentPageException $e) {
168 if ($page > 1) {
169 return $this->redirect($this->generateUrl('user_index', ['page' => $pagerFanta->getNbPages()]), 302);
170 }
171 }
172
173 return $this->render('WallabagUserBundle:Manage:index.html.twig', [
174 'searchForm' => $form->createView(),
175 'users' => $pagerFanta,
176 ]);
177 }
149} 178}
diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php
index 3a167de7..aba76ca7 100644
--- a/src/Wallabag/UserBundle/Entity/User.php
+++ b/src/Wallabag/UserBundle/Entity/User.php
@@ -4,11 +4,12 @@ namespace Wallabag\UserBundle\Entity;
4 4
5use Doctrine\Common\Collections\ArrayCollection; 5use Doctrine\Common\Collections\ArrayCollection;
6use Doctrine\ORM\Mapping as ORM; 6use Doctrine\ORM\Mapping as ORM;
7use JMS\Serializer\Annotation\Groups;
8use JMS\Serializer\Annotation\XmlRoot;
9use JMS\Serializer\Annotation\Accessor;
7use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface; 10use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface;
8use Scheb\TwoFactorBundle\Model\TrustedComputerInterface; 11use Scheb\TwoFactorBundle\Model\TrustedComputerInterface;
9use FOS\UserBundle\Model\User as BaseUser; 12use FOS\UserBundle\Model\User as BaseUser;
10use JMS\Serializer\Annotation\ExclusionPolicy;
11use JMS\Serializer\Annotation\Expose;
12use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; 13use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
13use Symfony\Component\Security\Core\User\UserInterface; 14use Symfony\Component\Security\Core\User\UserInterface;
14use Wallabag\ApiBundle\Entity\Client; 15use Wallabag\ApiBundle\Entity\Client;
@@ -18,23 +19,25 @@ use Wallabag\CoreBundle\Entity\Entry;
18/** 19/**
19 * User. 20 * User.
20 * 21 *
22 * @XmlRoot("user")
21 * @ORM\Entity(repositoryClass="Wallabag\UserBundle\Repository\UserRepository") 23 * @ORM\Entity(repositoryClass="Wallabag\UserBundle\Repository\UserRepository")
22 * @ORM\Table(name="`user`") 24 * @ORM\Table(name="`user`")
23 * @ORM\HasLifecycleCallbacks() 25 * @ORM\HasLifecycleCallbacks()
24 * @ExclusionPolicy("all")
25 * 26 *
26 * @UniqueEntity("email") 27 * @UniqueEntity("email")
27 * @UniqueEntity("username") 28 * @UniqueEntity("username")
28 */ 29 */
29class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface 30class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface
30{ 31{
32 /** @Serializer\XmlAttribute */
31 /** 33 /**
32 * @var int 34 * @var int
33 * 35 *
34 * @Expose
35 * @ORM\Column(name="id", type="integer") 36 * @ORM\Column(name="id", type="integer")
36 * @ORM\Id 37 * @ORM\Id
37 * @ORM\GeneratedValue(strategy="AUTO") 38 * @ORM\GeneratedValue(strategy="AUTO")
39 *
40 * @Groups({"user_api", "user_api_with_client"})
38 */ 41 */
39 protected $id; 42 protected $id;
40 43
@@ -42,20 +45,40 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
42 * @var string 45 * @var string
43 * 46 *
44 * @ORM\Column(name="name", type="text", nullable=true) 47 * @ORM\Column(name="name", type="text", nullable=true)
48 *
49 * @Groups({"user_api", "user_api_with_client"})
45 */ 50 */
46 protected $name; 51 protected $name;
47 52
48 /** 53 /**
49 * @var date 54 * @var string
55 *
56 * @Groups({"user_api", "user_api_with_client"})
57 */
58 protected $username;
59
60 /**
61 * @var string
62 *
63 * @Groups({"user_api", "user_api_with_client"})
64 */
65 protected $email;
66
67 /**
68 * @var \DateTime
50 * 69 *
51 * @ORM\Column(name="created_at", type="datetime") 70 * @ORM\Column(name="created_at", type="datetime")
71 *
72 * @Groups({"user_api", "user_api_with_client"})
52 */ 73 */
53 protected $createdAt; 74 protected $createdAt;
54 75
55 /** 76 /**
56 * @var date 77 * @var \DateTime
57 * 78 *
58 * @ORM\Column(name="updated_at", type="datetime") 79 * @ORM\Column(name="updated_at", type="datetime")
80 *
81 * @Groups({"user_api", "user_api_with_client"})
59 */ 82 */
60 protected $updatedAt; 83 protected $updatedAt;
61 84
@@ -75,7 +98,8 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
75 private $authCode; 98 private $authCode;
76 99
77 /** 100 /**
78 * @var bool Enabled yes/no 101 * @var bool
102 *
79 * @ORM\Column(type="boolean") 103 * @ORM\Column(type="boolean")
80 */ 104 */
81 private $twoFactorAuthentication = false; 105 private $twoFactorAuthentication = false;
@@ -86,10 +110,20 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
86 private $trusted; 110 private $trusted;
87 111
88 /** 112 /**
113 * @var ArrayCollection
114 *
89 * @ORM\OneToMany(targetEntity="Wallabag\ApiBundle\Entity\Client", mappedBy="user", cascade={"remove"}) 115 * @ORM\OneToMany(targetEntity="Wallabag\ApiBundle\Entity\Client", mappedBy="user", cascade={"remove"})
90 */ 116 */
91 protected $clients; 117 protected $clients;
92 118
119 /**
120 * @see getFirstClient() below
121 *
122 * @Groups({"user_api_with_client"})
123 * @Accessor(getter="getFirstClient")
124 */
125 protected $default_client;
126
93 public function __construct() 127 public function __construct()
94 { 128 {
95 parent::__construct(); 129 parent::__construct();
@@ -135,7 +169,7 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
135 } 169 }
136 170
137 /** 171 /**
138 * @return string 172 * @return \DateTime
139 */ 173 */
140 public function getCreatedAt() 174 public function getCreatedAt()
141 { 175 {
@@ -143,7 +177,7 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
143 } 177 }
144 178
145 /** 179 /**
146 * @return string 180 * @return \DateTime
147 */ 181 */
148 public function getUpdatedAt() 182 public function getUpdatedAt()
149 { 183 {
@@ -266,4 +300,16 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
266 { 300 {
267 return $this->clients; 301 return $this->clients;
268 } 302 }
303
304 /**
305 * Only used by the API when creating a new user it'll also return the first client (which was also created at the same time).
306 *
307 * @return Client
308 */
309 public function getFirstClient()
310 {
311 if (!empty($this->clients)) {
312 return $this->clients->first();
313 }
314 }
269} 315}
diff --git a/src/Wallabag/UserBundle/EventListener/AuthenticationFailureListener.php b/src/Wallabag/UserBundle/EventListener/AuthenticationFailureListener.php
new file mode 100644
index 00000000..10f13233
--- /dev/null
+++ b/src/Wallabag/UserBundle/EventListener/AuthenticationFailureListener.php
@@ -0,0 +1,40 @@
1<?php
2
3namespace Wallabag\UserBundle\EventListener;
4
5use Psr\Log\LoggerInterface;
6use Symfony\Component\EventDispatcher\EventSubscriberInterface;
7use Symfony\Component\HttpFoundation\RequestStack;
8use Symfony\Component\Security\Core\AuthenticationEvents;
9
10class AuthenticationFailureListener implements EventSubscriberInterface
11{
12 private $requestStack;
13 private $logger;
14
15 public function __construct(RequestStack $requestStack, LoggerInterface $logger)
16 {
17 $this->requestStack = $requestStack;
18 $this->logger = $logger;
19 }
20
21 /**
22 * {@inheritdoc}
23 */
24 public static function getSubscribedEvents()
25 {
26 return [
27 AuthenticationEvents::AUTHENTICATION_FAILURE => 'onAuthenticationFailure',
28 ];
29 }
30
31 /**
32 * On failure, add a custom error in log so server admin can configure fail2ban to block IP from people who try to login too much.
33 */
34 public function onAuthenticationFailure()
35 {
36 $request = $this->requestStack->getMasterRequest();
37
38 $this->logger->error('Authentication failure for user "'.$request->request->get('_username').'", from IP "'.$request->getClientIp().'", with UA: "'.$request->server->get('HTTP_USER_AGENT').'".');
39 }
40}
diff --git a/src/Wallabag/UserBundle/EventListener/CreateConfigListener.php b/src/Wallabag/UserBundle/EventListener/CreateConfigListener.php
index 0bdd1cae..e4d55c19 100644
--- a/src/Wallabag/UserBundle/EventListener/CreateConfigListener.php
+++ b/src/Wallabag/UserBundle/EventListener/CreateConfigListener.php
@@ -5,7 +5,6 @@ namespace Wallabag\UserBundle\EventListener;
5use Doctrine\ORM\EntityManager; 5use Doctrine\ORM\EntityManager;
6use FOS\UserBundle\Event\UserEvent; 6use FOS\UserBundle\Event\UserEvent;
7use FOS\UserBundle\FOSUserEvents; 7use FOS\UserBundle\FOSUserEvents;
8use Symfony\Component\EventDispatcher\EventDispatcherInterface;
9use Symfony\Component\EventDispatcher\EventSubscriberInterface; 8use Symfony\Component\EventDispatcher\EventSubscriberInterface;
10use Wallabag\CoreBundle\Entity\Config; 9use Wallabag\CoreBundle\Entity\Config;
11 10
@@ -47,7 +46,7 @@ class CreateConfigListener implements EventSubscriberInterface
47 ]; 46 ];
48 } 47 }
49 48
50 public function createConfig(UserEvent $event, $eventName = null, EventDispatcherInterface $eventDispatcher = null) 49 public function createConfig(UserEvent $event)
51 { 50 {
52 $config = new Config($event->getUser()); 51 $config = new Config($event->getUser());
53 $config->setTheme($this->theme); 52 $config->setTheme($this->theme);
diff --git a/src/Wallabag/UserBundle/Form/SearchUserType.php b/src/Wallabag/UserBundle/Form/SearchUserType.php
new file mode 100644
index 00000000..9ce46ee1
--- /dev/null
+++ b/src/Wallabag/UserBundle/Form/SearchUserType.php
@@ -0,0 +1,29 @@
1<?php
2
3namespace Wallabag\UserBundle\Form;
4
5use Symfony\Component\Form\AbstractType;
6use Symfony\Component\Form\Extension\Core\Type\TextType;
7use Symfony\Component\Form\FormBuilderInterface;
8use Symfony\Component\OptionsResolver\OptionsResolver;
9
10class SearchUserType extends AbstractType
11{
12 public function buildForm(FormBuilderInterface $builder, array $options)
13 {
14 $builder
15 ->setMethod('GET')
16 ->add('term', TextType::class, [
17 'required' => true,
18 'label' => 'user.new.form_search.term_label',
19 ])
20 ;
21 }
22
23 public function configureOptions(OptionsResolver $resolver)
24 {
25 $resolver->setDefaults([
26 'csrf_protection' => false,
27 ]);
28 }
29}
diff --git a/src/Wallabag/UserBundle/Repository/UserRepository.php b/src/Wallabag/UserBundle/Repository/UserRepository.php
index f913f52d..6adbe329 100644
--- a/src/Wallabag/UserBundle/Repository/UserRepository.php
+++ b/src/Wallabag/UserBundle/Repository/UserRepository.php
@@ -52,4 +52,17 @@ class UserRepository extends EntityRepository
52 ->getQuery() 52 ->getQuery()
53 ->getSingleScalarResult(); 53 ->getSingleScalarResult();
54 } 54 }
55
56 /**
57 * Retrieves users filtered with a search term.
58 *
59 * @param string $term
60 *
61 * @return QueryBuilder
62 */
63 public function getQueryBuilderForSearch($term)
64 {
65 return $this->createQueryBuilder('u')
66 ->andWhere('lower(u.username) LIKE lower(:term) OR lower(u.email) LIKE lower(:term) OR lower(u.name) LIKE lower(:term)')->setParameter('term', '%'.$term.'%');
67 }
55} 68}
diff --git a/src/Wallabag/UserBundle/Resources/config/services.yml b/src/Wallabag/UserBundle/Resources/config/services.yml
index 72f6f12c..d3925de3 100644
--- a/src/Wallabag/UserBundle/Resources/config/services.yml
+++ b/src/Wallabag/UserBundle/Resources/config/services.yml
@@ -7,7 +7,7 @@ services:
7 - "%scheb_two_factor.email.sender_email%" 7 - "%scheb_two_factor.email.sender_email%"
8 - "%scheb_two_factor.email.sender_name%" 8 - "%scheb_two_factor.email.sender_name%"
9 - '@=service(''craue_config'').get(''wallabag_support_url'')' 9 - '@=service(''craue_config'').get(''wallabag_support_url'')'
10 - '@=service(''craue_config'').get(''wallabag_url'')' 10 - '%domain_name%'
11 11
12 wallabag_user.password_resetting: 12 wallabag_user.password_resetting:
13 class: Wallabag\UserBundle\EventListener\PasswordResettingListener 13 class: Wallabag\UserBundle\EventListener\PasswordResettingListener
@@ -35,3 +35,11 @@ services:
35 - "%wallabag_core.list_mode%" 35 - "%wallabag_core.list_mode%"
36 tags: 36 tags:
37 - { name: kernel.event_subscriber } 37 - { name: kernel.event_subscriber }
38
39 wallabag_user.listener.authentication_failure_event_listener:
40 class: Wallabag\UserBundle\EventListener\AuthenticationFailureListener
41 arguments:
42 - "@request_stack"
43 - "@logger"
44 tags:
45 - { name: kernel.event_listener, event: security.authentication.failure, method: onAuthenticationFailure }
diff --git a/src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig b/src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig
index daba29e4..15002632 100644
--- a/src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig
+++ b/src/Wallabag/UserBundle/Resources/views/Manage/index.html.twig
@@ -7,37 +7,60 @@
7 <div class="row"> 7 <div class="row">
8 <div class="col s12"> 8 <div class="col s12">
9 <div class="card-panel"> 9 <div class="card-panel">
10 {% if users.getNbPages > 1 %}
11 {{ pagerfanta(users, 'twitter_bootstrap_translated', {'proximity': 1}) }}
12 {% endif %}
10 <div class="row"> 13 <div class="row">
11 <div class="input-field col s12"> 14 <div class="col s6">
12 <p class="help">{{ 'user.description'|trans|raw }}</p> 15 <p class="help">{{ 'user.description'|trans|raw }}</p>
16 </div>
17 <div class="col s6">
18 <div class="input-field">
19 <form name="search_users" method="GET" action="{{ path('user_index')}}">
20 {% if form_errors(searchForm) %}
21 <span class="black-text">{{ form_errors(searchForm) }}</span>
22 {% endif %}
23
24 {% if form_errors(searchForm.term) %}
25 <span class="black-text">{{ form_errors(searchForm.term) }}</span>
26 {% endif %}
13 27
14 <table class="bordered"> 28 {{ form_widget(searchForm.term, { 'attr': {'autocomplete': 'off', 'placeholder': 'user.search.placeholder'} }) }}
15 <thead> 29
16 <tr> 30 {{ form_rest(searchForm) }}
17 <th>{{ 'user.form.username_label'|trans }}</th> 31 </form>
18 <th>{{ 'user.form.email_label'|trans }}</th> 32 </div>
19 <th>{{ 'user.form.last_login_label'|trans }}</th>
20 <th>{{ 'user.list.actions'|trans }}</th>
21 </tr>
22 </thead>
23 <tbody>
24 {% for user in users %}
25 <tr>
26 <td>{{ user.username }}</td>
27 <td>{{ user.email }}</td>
28 <td>{% if user.lastLogin %}{{ user.lastLogin|date('Y-m-d H:i:s') }}{% endif %}</td>
29 <td>
30 <a href="{{ path('user_edit', { 'id': user.id }) }}">{{ 'user.list.edit_action'|trans }}</a>
31 </td>
32 </tr>
33 {% endfor %}
34 </tbody>
35 </table>
36 <br />
37 <p>
38 <a href="{{ path('user_new') }}" class="waves-effect waves-light btn">{{ 'user.list.create_new_one'|trans }}</a>
39 </p>
40 </div> 33 </div>
34
35 <table class="bordered">
36 <thead>
37 <tr>
38 <th>{{ 'user.form.username_label'|trans }}</th>
39 <th>{{ 'user.form.email_label'|trans }}</th>
40 <th>{{ 'user.form.last_login_label'|trans }}</th>
41 <th>{{ 'user.list.actions'|trans }}</th>
42 </tr>
43 </thead>
44 <tbody>
45 {% for user in users %}
46 <tr>
47 <td>{{ user.username }}</td>
48 <td>{{ user.email }}</td>
49 <td>{% if user.lastLogin %}{{ user.lastLogin|date('Y-m-d H:i:s') }}{% endif %}</td>
50 <td>
51 <a href="{{ path('user_edit', { 'id': user.id }) }}">{{ 'user.list.edit_action'|trans }}</a>
52 </td>
53 </tr>
54 {% endfor %}
55 </tbody>
56 </table>
57 <br />
58 <p>
59 <a href="{{ path('user_new') }}" class="waves-effect waves-light btn">{{ 'user.list.create_new_one'|trans }}</a>
60 </p>
61 {% if users.getNbPages > 1 %}
62 {{ pagerfanta(users, 'twitter_bootstrap_translated', {'proximity': 1}) }}
63 {% endif %}
41 </div> 64 </div>
42 </div> 65 </div>
43 </div> 66 </div>