aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/Wallabag
diff options
context:
space:
mode:
authorJérémy Benoist <j0k3r@users.noreply.github.com>2019-05-29 11:14:00 +0200
committerGitHub <noreply@github.com>2019-05-29 11:14:00 +0200
commit73ec68b1ffafb792265a3805833e5cd84c966aed (patch)
tree33c6040c050f85c537f8dbf5e91d8c281db092cd /src/Wallabag
parent2ba365c7c49556cd23b444dc3bb8d4a8cf08809d (diff)
parent2cbee36a0184786644470a84fdd8c720cfcac58e (diff)
downloadwallabag-73ec68b1ffafb792265a3805833e5cd84c966aed.tar.gz
wallabag-73ec68b1ffafb792265a3805833e5cd84c966aed.tar.zst
wallabag-73ec68b1ffafb792265a3805833e5cd84c966aed.zip
Merge pull request #3984 from wallabag/2.4
Merge 2.4 into master
Diffstat (limited to 'src/Wallabag')
-rw-r--r--src/Wallabag/AnnotationBundle/DataFixtures/AnnotationFixtures.php (renamed from src/Wallabag/AnnotationBundle/DataFixtures/ORM/LoadAnnotationData.php)17
-rw-r--r--src/Wallabag/ApiBundle/Controller/DeveloperController.php2
-rw-r--r--src/Wallabag/ApiBundle/Controller/EntryRestController.php106
-rw-r--r--src/Wallabag/ApiBundle/Controller/SearchRestController.php65
-rw-r--r--src/Wallabag/ApiBundle/Controller/WallabagRestController.php39
-rw-r--r--src/Wallabag/ApiBundle/Entity/AccessToken.php17
-rw-r--r--src/Wallabag/ApiBundle/Entity/AuthCode.php17
-rw-r--r--src/Wallabag/ApiBundle/Entity/RefreshToken.php17
-rw-r--r--src/Wallabag/ApiBundle/Form/Type/ClientType.php1
-rw-r--r--src/Wallabag/ApiBundle/Resources/config/routing_rest.yml5
-rw-r--r--src/Wallabag/CoreBundle/Command/GenerateUrlHashesCommand.php99
-rw-r--r--src/Wallabag/CoreBundle/Command/InstallCommand.php5
-rw-r--r--src/Wallabag/CoreBundle/Command/ShowUserCommand.php3
-rw-r--r--src/Wallabag/CoreBundle/Controller/ConfigController.php181
-rw-r--r--src/Wallabag/CoreBundle/Controller/EntryController.php126
-rw-r--r--src/Wallabag/CoreBundle/Controller/ExportController.php2
-rw-r--r--src/Wallabag/CoreBundle/Controller/FeedController.php (renamed from src/Wallabag/CoreBundle/Controller/RssController.php)98
-rw-r--r--src/Wallabag/CoreBundle/Controller/SiteCredentialController.php15
-rw-r--r--src/Wallabag/CoreBundle/Controller/StaticController.php2
-rw-r--r--src/Wallabag/CoreBundle/Controller/TagController.php53
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/ConfigFixtures.php (renamed from src/Wallabag/CoreBundle/DataFixtures/ORM/LoadConfigData.php)15
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/EntryFixtures.php (renamed from src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php)16
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/SettingFixtures.php (renamed from src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php)15
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/SiteCredentialFixtures.php (renamed from src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSiteCredentialData.php)15
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/TagFixtures.php (renamed from src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php)15
-rw-r--r--src/Wallabag/CoreBundle/DataFixtures/TaggingRuleFixtures.php (renamed from src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTaggingRuleData.php)14
-rw-r--r--src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php2
-rw-r--r--src/Wallabag/CoreBundle/Doctrine/DBAL/Driver/CustomPostgreSQLDriver.php25
-rw-r--r--src/Wallabag/CoreBundle/Doctrine/DBAL/Schema/CustomPostgreSqlSchemaManager.php38
-rw-r--r--src/Wallabag/CoreBundle/Doctrine/WallabagMigration.php2
-rw-r--r--src/Wallabag/CoreBundle/Entity/Config.php38
-rw-r--r--src/Wallabag/CoreBundle/Entity/Entry.php81
-rw-r--r--src/Wallabag/CoreBundle/Entity/SiteCredential.php17
-rw-r--r--src/Wallabag/CoreBundle/Entity/TaggingRule.php2
-rw-r--r--src/Wallabag/CoreBundle/Event/Listener/UserLocaleListener.php8
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/EditEntryType.php2
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/FeedType.php (renamed from src/Wallabag/CoreBundle/Form/Type/RssType.php)10
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/NewEntryType.php1
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/RenameTagType.php35
-rw-r--r--src/Wallabag/CoreBundle/Form/Type/UserInformationType.php9
-rw-r--r--src/Wallabag/CoreBundle/Helper/ContentProxy.php49
-rw-r--r--src/Wallabag/CoreBundle/Helper/DownloadImages.php68
-rw-r--r--src/Wallabag/CoreBundle/Helper/HttpClientFactory.php51
-rw-r--r--src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php2
-rw-r--r--src/Wallabag/CoreBundle/Helper/RuleBasedTagger.php1
-rw-r--r--src/Wallabag/CoreBundle/Helper/UrlHasher.php23
-rw-r--r--src/Wallabag/CoreBundle/ParamConverter/UsernameFeedTokenConverter.php (renamed from src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php)10
-rw-r--r--src/Wallabag/CoreBundle/Repository/EntryRepository.php99
-rw-r--r--src/Wallabag/CoreBundle/Resources/config/services.yml46
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.da.yml48
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.de.yml47
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.en.yml53
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.es.yml48
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml48
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml52
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.it.yml47
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml47
-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.yml47
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml49
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.th.yml47
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml47
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.da.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.de.yml3
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.en.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.es.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.fa.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.fr.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.it.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.oc.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.pl.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.pt.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.ro.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/translations/validators.tr.yml2
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/index.html.twig139
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/otp_app.html.twig55
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Entry/entries.html.twig17
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/baggy/Tag/tags.html.twig18
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_feed_link.html.twig11
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_rss_link.html.twig11
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/entries.xml.twig83
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/common/Static/quickstart.html.twig2
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Config/index.html.twig158
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Config/otp_app.html.twig63
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Entry/entries.html.twig14
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/Tag/tags.html.twig17
-rw-r--r--src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig11
-rw-r--r--src/Wallabag/CoreBundle/Tools/Utils.php2
-rw-r--r--src/Wallabag/CoreBundle/Twig/WallabagExtension.php10
-rw-r--r--src/Wallabag/ImportBundle/Controller/BrowserController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/ChromeController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/FirefoxController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/ImportController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/InstapaperController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/PinboardController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/PocketController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/ReadabilityController.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php2
-rw-r--r--src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php2
-rw-r--r--src/Wallabag/ImportBundle/Import/BrowserImport.php2
-rw-r--r--src/Wallabag/ImportBundle/Import/InstapaperImport.php9
-rw-r--r--src/Wallabag/ImportBundle/Import/PinboardImport.php2
-rw-r--r--src/Wallabag/ImportBundle/Import/PocketImport.php93
-rw-r--r--src/Wallabag/ImportBundle/Import/ReadabilityImport.php2
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagImport.php2
-rw-r--r--src/Wallabag/ImportBundle/Import/WallabagV2Import.php4
-rw-r--r--src/Wallabag/ImportBundle/Resources/config/services.yml16
-rw-r--r--src/Wallabag/UserBundle/Controller/ManageController.php45
-rw-r--r--src/Wallabag/UserBundle/DataFixtures/UserFixtures.php (renamed from src/Wallabag/UserBundle/DataFixtures/ORM/LoadUserData.php)15
-rw-r--r--src/Wallabag/UserBundle/Entity/User.php148
-rw-r--r--src/Wallabag/UserBundle/EventListener/CreateConfigListener.php13
-rw-r--r--src/Wallabag/UserBundle/Form/UserType.php9
-rw-r--r--src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php4
-rw-r--r--src/Wallabag/UserBundle/Repository/UserRepository.php8
-rw-r--r--src/Wallabag/UserBundle/Resources/config/services.yml3
-rw-r--r--src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig14
-rw-r--r--src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig11
-rw-r--r--src/Wallabag/UserBundle/Resources/views/Registration/register_content.html.twig1
-rw-r--r--src/Wallabag/UserBundle/Resources/views/layout.html.twig5
120 files changed, 2363 insertions, 952 deletions
diff --git a/src/Wallabag/AnnotationBundle/DataFixtures/ORM/LoadAnnotationData.php b/src/Wallabag/AnnotationBundle/DataFixtures/AnnotationFixtures.php
index 20e07fa3..ed46cea9 100644
--- a/src/Wallabag/AnnotationBundle/DataFixtures/ORM/LoadAnnotationData.php
+++ b/src/Wallabag/AnnotationBundle/DataFixtures/AnnotationFixtures.php
@@ -1,13 +1,15 @@
1<?php 1<?php
2 2
3namespace Wallabag\AnnotationBundle\DataFixtures\ORM; 3namespace Wallabag\AnnotationBundle\DataFixtures;
4 4
5use Doctrine\Common\DataFixtures\AbstractFixture; 5use Doctrine\Bundle\FixturesBundle\Fixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface; 6use Doctrine\Common\DataFixtures\DependentFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager; 7use Doctrine\Common\Persistence\ObjectManager;
8use Wallabag\AnnotationBundle\Entity\Annotation; 8use Wallabag\AnnotationBundle\Entity\Annotation;
9use Wallabag\CoreBundle\DataFixtures\EntryFixtures;
10use Wallabag\UserBundle\DataFixtures\UserFixtures;
9 11
10class LoadAnnotationData extends AbstractFixture implements OrderedFixtureInterface 12class AnnotationFixtures extends Fixture implements DependentFixtureInterface
11{ 13{
12 /** 14 /**
13 * {@inheritdoc} 15 * {@inheritdoc}
@@ -38,8 +40,11 @@ class LoadAnnotationData extends AbstractFixture implements OrderedFixtureInterf
38 /** 40 /**
39 * {@inheritdoc} 41 * {@inheritdoc}
40 */ 42 */
41 public function getOrder() 43 public function getDependencies()
42 { 44 {
43 return 35; 45 return [
46 EntryFixtures::class,
47 UserFixtures::class,
48 ];
44 } 49 }
45} 50}
diff --git a/src/Wallabag/ApiBundle/Controller/DeveloperController.php b/src/Wallabag/ApiBundle/Controller/DeveloperController.php
index c7178017..ae7e83da 100644
--- a/src/Wallabag/ApiBundle/Controller/DeveloperController.php
+++ b/src/Wallabag/ApiBundle/Controller/DeveloperController.php
@@ -2,9 +2,9 @@
2 2
3namespace Wallabag\ApiBundle\Controller; 3namespace Wallabag\ApiBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller; 5use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\Request; 6use Symfony\Component\HttpFoundation\Request;
7use Symfony\Component\Routing\Annotation\Route;
8use Wallabag\ApiBundle\Entity\Client; 8use Wallabag\ApiBundle\Entity\Client;
9use Wallabag\ApiBundle\Form\Type\ClientType; 9use Wallabag\ApiBundle\Form\Type\ClientType;
10 10
diff --git a/src/Wallabag/ApiBundle/Controller/EntryRestController.php b/src/Wallabag/ApiBundle/Controller/EntryRestController.php
index 33b75665..aaacdcdc 100644
--- a/src/Wallabag/ApiBundle/Controller/EntryRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/EntryRestController.php
@@ -4,18 +4,17 @@ namespace Wallabag\ApiBundle\Controller;
4 4
5use Hateoas\Configuration\Route; 5use Hateoas\Configuration\Route;
6use Hateoas\Representation\Factory\PagerfantaFactory; 6use Hateoas\Representation\Factory\PagerfantaFactory;
7use JMS\Serializer\SerializationContext;
8use Nelmio\ApiDocBundle\Annotation\ApiDoc; 7use Nelmio\ApiDocBundle\Annotation\ApiDoc;
9use Symfony\Component\HttpFoundation\JsonResponse; 8use Symfony\Component\HttpFoundation\JsonResponse;
10use Symfony\Component\HttpFoundation\Request; 9use Symfony\Component\HttpFoundation\Request;
11use Symfony\Component\HttpFoundation\Response; 10use Symfony\Component\HttpFoundation\Response;
12use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; 11use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
13use Symfony\Component\HttpKernel\Exception\HttpException; 12use Symfony\Component\HttpKernel\Exception\HttpException;
14use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
15use Wallabag\CoreBundle\Entity\Entry; 13use Wallabag\CoreBundle\Entity\Entry;
16use Wallabag\CoreBundle\Entity\Tag; 14use Wallabag\CoreBundle\Entity\Tag;
17use Wallabag\CoreBundle\Event\EntryDeletedEvent; 15use Wallabag\CoreBundle\Event\EntryDeletedEvent;
18use Wallabag\CoreBundle\Event\EntrySavedEvent; 16use Wallabag\CoreBundle\Event\EntrySavedEvent;
17use Wallabag\CoreBundle\Helper\UrlHasher;
19 18
20class EntryRestController extends WallabagRestController 19class EntryRestController extends WallabagRestController
21{ 20{
@@ -29,8 +28,10 @@ class EntryRestController extends WallabagRestController
29 * @ApiDoc( 28 * @ApiDoc(
30 * parameters={ 29 * parameters={
31 * {"name"="return_id", "dataType"="string", "required"=false, "format"="1 or 0", "description"="Set 1 if you want to retrieve ID in case entry(ies) exists, 0 by default"}, 30 * {"name"="return_id", "dataType"="string", "required"=false, "format"="1 or 0", "description"="Set 1 if you want to retrieve ID in case entry(ies) exists, 0 by default"},
32 * {"name"="url", "dataType"="string", "required"=true, "format"="An url", "description"="Url to check if it exists"}, 31 * {"name"="url", "dataType"="string", "required"=true, "format"="An url", "description"="DEPRECATED, use hashed_url instead"},
33 * {"name"="urls", "dataType"="string", "required"=false, "format"="An array of urls (?urls[]=http...&urls[]=http...)", "description"="Urls (as an array) to check if it exists"} 32 * {"name"="urls", "dataType"="string", "required"=false, "format"="An array of urls (?urls[]=http...&urls[]=http...)", "description"="DEPRECATED, use hashed_urls instead"},
33 * {"name"="hashed_url", "dataType"="string", "required"=false, "format"="A hashed url", "description"="Hashed url using SHA1 to check if it exists"},
34 * {"name"="hashed_urls", "dataType"="string", "required"=false, "format"="An array of hashed urls (?hashed_urls[]=xxx...&hashed_urls[]=xxx...)", "description"="An array of hashed urls using SHA1 to check if they exist"}
34 * } 35 * }
35 * ) 36 * )
36 * 37 *
@@ -39,38 +40,49 @@ class EntryRestController extends WallabagRestController
39 public function getEntriesExistsAction(Request $request) 40 public function getEntriesExistsAction(Request $request)
40 { 41 {
41 $this->validateAuthentication(); 42 $this->validateAuthentication();
43 $repo = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
42 44
43 $returnId = (null === $request->query->get('return_id')) ? false : (bool) $request->query->get('return_id'); 45 $returnId = (null === $request->query->get('return_id')) ? false : (bool) $request->query->get('return_id');
44 $urls = $request->query->get('urls', []);
45
46 // handle multiple urls first
47 if (!empty($urls)) {
48 $results = [];
49 foreach ($urls as $url) {
50 $res = $this->getDoctrine()
51 ->getRepository('WallabagCoreBundle:Entry')
52 ->findByUrlAndUserId($url, $this->getUser()->getId());
53
54 $results[$url] = $this->returnExistInformation($res, $returnId);
55 }
56 46
57 return $this->sendResponse($results); 47 $hashedUrls = $request->query->get('hashed_urls', []);
48 $hashedUrl = $request->query->get('hashed_url', '');
49 if (!empty($hashedUrl)) {
50 $hashedUrls[] = $hashedUrl;
58 } 51 }
59 52
60 // let's see if it is a simple url? 53 $urls = $request->query->get('urls', []);
61 $url = $request->query->get('url', ''); 54 $url = $request->query->get('url', '');
55 if (!empty($url)) {
56 $urls[] = $url;
57 }
58
59 $urlHashMap = [];
60 foreach ($urls as $urlToHash) {
61 $urlHash = UrlHasher::hashUrl($urlToHash);
62 $hashedUrls[] = $urlHash;
63 $urlHashMap[$urlHash] = $urlToHash;
64 }
62 65
63 if (empty($url)) { 66 if (empty($hashedUrls)) {
64 throw $this->createAccessDeniedException('URL is empty?, logged user id: ' . $this->getUser()->getId()); 67 throw $this->createAccessDeniedException('URL is empty?, logged user id: ' . $this->getUser()->getId());
65 } 68 }
66 69
67 $res = $this->getDoctrine() 70 $results = [];
68 ->getRepository('WallabagCoreBundle:Entry') 71 foreach ($hashedUrls as $hashedUrlToSearch) {
69 ->findByUrlAndUserId($url, $this->getUser()->getId()); 72 $res = $repo->findByHashedUrlAndUserId($hashedUrlToSearch, $this->getUser()->getId());
73
74 $results[$hashedUrlToSearch] = $this->returnExistInformation($res, $returnId);
75 }
70 76
71 $exists = $this->returnExistInformation($res, $returnId); 77 $results = $this->replaceUrlHashes($results, $urlHashMap);
72 78
73 return $this->sendResponse(['exists' => $exists]); 79 if (!empty($url) || !empty($hashedUrl)) {
80 $hu = array_keys($results)[0];
81
82 return $this->sendResponse(['exists' => $results[$hu]]);
83 }
84
85 return $this->sendResponse($results);
74 } 86 }
75 87
76 /** 88 /**
@@ -80,13 +92,14 @@ class EntryRestController extends WallabagRestController
80 * parameters={ 92 * parameters={
81 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by archived status."}, 93 * {"name"="archive", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by archived status."},
82 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by starred status."}, 94 * {"name"="starred", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by starred status."},
83 * {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated', default 'created'", "description"="sort entries by date."}, 95 * {"name"="sort", "dataType"="string", "required"=false, "format"="'created' or 'updated' or 'archived', default 'created'", "description"="sort entries by date."},
84 * {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."}, 96 * {"name"="order", "dataType"="string", "required"=false, "format"="'asc' or 'desc', default 'desc'", "description"="order of sort."},
85 * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."}, 97 * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
86 * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."}, 98 * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."},
87 * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."}, 99 * {"name"="tags", "dataType"="string", "required"=false, "format"="api,rest", "description"="a list of tags url encoded. Will returns entries that matches ALL tags."},
88 * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."}, 100 * {"name"="since", "dataType"="integer", "required"=false, "format"="default '0'", "description"="The timestamp since when you want entries updated."},
89 * {"name"="public", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by entries with a public link"}, 101 * {"name"="public", "dataType"="integer", "required"=false, "format"="1 or 0, all entries by default", "description"="filter by entries with a public link"},
102 * {"name"="detail", "dataType"="string", "required"=false, "format"="metadata or full, metadata by default", "description"="include content field if 'full'. 'full' by default for backward compatibility."},
90 * } 103 * }
91 * ) 104 * )
92 * 105 *
@@ -105,6 +118,7 @@ class EntryRestController extends WallabagRestController
105 $perPage = (int) $request->query->get('perPage', 30); 118 $perPage = (int) $request->query->get('perPage', 30);
106 $tags = \is_array($request->query->get('tags')) ? '' : (string) $request->query->get('tags', ''); 119 $tags = \is_array($request->query->get('tags')) ? '' : (string) $request->query->get('tags', '');
107 $since = $request->query->get('since', 0); 120 $since = $request->query->get('since', 0);
121 $detail = strtolower($request->query->get('detail', 'full'));
108 122
109 try { 123 try {
110 /** @var \Pagerfanta\Pagerfanta $pager */ 124 /** @var \Pagerfanta\Pagerfanta $pager */
@@ -116,7 +130,8 @@ class EntryRestController extends WallabagRestController
116 $sort, 130 $sort,
117 $order, 131 $order,
118 $since, 132 $since,
119 $tags 133 $tags,
134 $detail
120 ); 135 );
121 } catch (\Exception $e) { 136 } catch (\Exception $e) {
122 throw new BadRequestHttpException($e->getMessage()); 137 throw new BadRequestHttpException($e->getMessage());
@@ -140,8 +155,9 @@ class EntryRestController extends WallabagRestController
140 'perPage' => $perPage, 155 'perPage' => $perPage,
141 'tags' => $tags, 156 'tags' => $tags,
142 'since' => $since, 157 'since' => $since,
158 'detail' => $detail,
143 ], 159 ],
144 UrlGeneratorInterface::ABSOLUTE_URL 160 true
145 ) 161 )
146 ); 162 );
147 163
@@ -349,9 +365,7 @@ class EntryRestController extends WallabagRestController
349 'language' => !empty($data['language']) ? $data['language'] : $entry->getLanguage(), 365 'language' => !empty($data['language']) ? $data['language'] : $entry->getLanguage(),
350 'date' => !empty($data['publishedAt']) ? $data['publishedAt'] : $entry->getPublishedAt(), 366 'date' => !empty($data['publishedAt']) ? $data['publishedAt'] : $entry->getPublishedAt(),
351 // faking the open graph preview picture 367 // faking the open graph preview picture
352 'open_graph' => [ 368 'image' => !empty($data['picture']) ? $data['picture'] : $entry->getPreviewPicture(),
353 'og_image' => !empty($data['picture']) ? $data['picture'] : $entry->getPreviewPicture(),
354 ],
355 'authors' => \is_string($data['authors']) ? explode(',', $data['authors']) : $entry->getPublishedBy(), 369 'authors' => \is_string($data['authors']) ? explode(',', $data['authors']) : $entry->getPublishedBy(),
356 ] 370 ]
357 ); 371 );
@@ -363,7 +377,7 @@ class EntryRestController extends WallabagRestController
363 } 377 }
364 378
365 if (null !== $data['isArchived']) { 379 if (null !== $data['isArchived']) {
366 $entry->setArchived((bool) $data['isArchived']); 380 $entry->updateArchived((bool) $data['isArchived']);
367 } 381 }
368 382
369 if (null !== $data['isStarred']) { 383 if (null !== $data['isStarred']) {
@@ -479,7 +493,7 @@ class EntryRestController extends WallabagRestController
479 } 493 }
480 494
481 if (null !== $data['isArchived']) { 495 if (null !== $data['isArchived']) {
482 $entry->setArchived((bool) $data['isArchived']); 496 $entry->updateArchived((bool) $data['isArchived']);
483 } 497 }
484 498
485 if (null !== $data['isStarred']) { 499 if (null !== $data['isStarred']) {
@@ -787,21 +801,21 @@ class EntryRestController extends WallabagRestController
787 } 801 }
788 802
789 /** 803 /**
790 * Shortcut to send data serialized in json. 804 * Replace the hashedUrl keys in $results with the unhashed URL from the
791 * 805 * request, as recorded in $urlHashMap.
792 * @param mixed $data
793 *
794 * @return JsonResponse
795 */ 806 */
796 private function sendResponse($data) 807 private function replaceUrlHashes(array $results, array $urlHashMap)
797 { 808 {
798 // https://github.com/schmittjoh/JMSSerializerBundle/issues/293 809 $newResults = [];
799 $context = new SerializationContext(); 810 foreach ($results as $hash => $res) {
800 $context->setSerializeNull(true); 811 if (isset($urlHashMap[$hash])) {
801 812 $newResults[$urlHashMap[$hash]] = $res;
802 $json = $this->get('jms_serializer')->serialize($data, 'json', $context); 813 } else {
814 $newResults[$hash] = $res;
815 }
816 }
803 817
804 return (new JsonResponse())->setJson($json); 818 return $newResults;
805 } 819 }
806 820
807 /** 821 /**
@@ -832,8 +846,8 @@ class EntryRestController extends WallabagRestController
832 /** 846 /**
833 * Return information about the entry if it exist and depending on the id or not. 847 * Return information about the entry if it exist and depending on the id or not.
834 * 848 *
835 * @param Entry|null $entry 849 * @param Entry|bool|null $entry
836 * @param bool $returnId 850 * @param bool $returnId
837 * 851 *
838 * @return bool|int 852 * @return bool|int
839 */ 853 */
diff --git a/src/Wallabag/ApiBundle/Controller/SearchRestController.php b/src/Wallabag/ApiBundle/Controller/SearchRestController.php
new file mode 100644
index 00000000..d9f99844
--- /dev/null
+++ b/src/Wallabag/ApiBundle/Controller/SearchRestController.php
@@ -0,0 +1,65 @@
1<?php
2
3namespace Wallabag\ApiBundle\Controller;
4
5use Hateoas\Configuration\Route;
6use Hateoas\Representation\Factory\PagerfantaFactory;
7use Nelmio\ApiDocBundle\Annotation\ApiDoc;
8use Pagerfanta\Adapter\DoctrineORMAdapter;
9use Pagerfanta\Pagerfanta;
10use Symfony\Component\HttpFoundation\JsonResponse;
11use Symfony\Component\HttpFoundation\Request;
12
13class SearchRestController extends WallabagRestController
14{
15 /**
16 * Search all entries by term.
17 *
18 * @ApiDoc(
19 * parameters={
20 * {"name"="term", "dataType"="string", "required"=false, "format"="any", "description"="Any query term"},
21 * {"name"="page", "dataType"="integer", "required"=false, "format"="default '1'", "description"="what page you want."},
22 * {"name"="perPage", "dataType"="integer", "required"=false, "format"="default'30'", "description"="results per page."}
23 * }
24 * )
25 *
26 * @return JsonResponse
27 */
28 public function getSearchAction(Request $request)
29 {
30 $this->validateAuthentication();
31
32 $term = $request->query->get('term');
33 $page = (int) $request->query->get('page', 1);
34 $perPage = (int) $request->query->get('perPage', 30);
35
36 $qb = $this->get('wallabag_core.entry_repository')
37 ->getBuilderForSearchByUser(
38 $this->getUser()->getId(),
39 $term,
40 null
41 );
42
43 $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false);
44 $pager = new Pagerfanta($pagerAdapter);
45
46 $pager->setMaxPerPage($perPage);
47 $pager->setCurrentPage($page);
48
49 $pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
50 $paginatedCollection = $pagerfantaFactory->createRepresentation(
51 $pager,
52 new Route(
53 'api_get_search',
54 [
55 'term' => $term,
56 'page' => $page,
57 'perPage' => $perPage,
58 ],
59 true
60 )
61 );
62
63 return $this->sendResponse($paginatedCollection);
64 }
65}
diff --git a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
index 7d8cfbba..f18b0910 100644
--- a/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
+++ b/src/Wallabag/ApiBundle/Controller/WallabagRestController.php
@@ -3,6 +3,7 @@
3namespace Wallabag\ApiBundle\Controller; 3namespace Wallabag\ApiBundle\Controller;
4 4
5use FOS\RestBundle\Controller\FOSRestController; 5use FOS\RestBundle\Controller\FOSRestController;
6use JMS\Serializer\SerializationContext;
6use Nelmio\ApiDocBundle\Annotation\ApiDoc; 7use Nelmio\ApiDocBundle\Annotation\ApiDoc;
7use Symfony\Component\HttpFoundation\JsonResponse; 8use Symfony\Component\HttpFoundation\JsonResponse;
8use Symfony\Component\Security\Core\Exception\AccessDeniedException; 9use Symfony\Component\Security\Core\Exception\AccessDeniedException;
@@ -14,6 +15,8 @@ class WallabagRestController extends FOSRestController
14 * 15 *
15 * @ApiDoc() 16 * @ApiDoc()
16 * 17 *
18 * @deprecated Should use info endpoint instead
19 *
17 * @return JsonResponse 20 * @return JsonResponse
18 */ 21 */
19 public function getVersionAction() 22 public function getVersionAction()
@@ -24,6 +27,24 @@ class WallabagRestController extends FOSRestController
24 return (new JsonResponse())->setJson($json); 27 return (new JsonResponse())->setJson($json);
25 } 28 }
26 29
30 /**
31 * Retrieve information about the wallabag instance.
32 *
33 * @ApiDoc()
34 *
35 * @return JsonResponse
36 */
37 public function getInfoAction()
38 {
39 $info = [
40 'appname' => 'wallabag',
41 'version' => $this->container->getParameter('wallabag_core.version'),
42 'allowed_registration' => $this->container->getParameter('wallabag_user.registration_enabled'),
43 ];
44
45 return (new JsonResponse())->setJson($this->get('jms_serializer')->serialize($info, 'json'));
46 }
47
27 protected function validateAuthentication() 48 protected function validateAuthentication()
28 { 49 {
29 if (false === $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) { 50 if (false === $this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
@@ -44,4 +65,22 @@ class WallabagRestController extends FOSRestController
44 throw $this->createAccessDeniedException('Access forbidden. Entry user id: ' . $requestUserId . ', logged user id: ' . $user->getId()); 65 throw $this->createAccessDeniedException('Access forbidden. Entry user id: ' . $requestUserId . ', logged user id: ' . $user->getId());
45 } 66 }
46 } 67 }
68
69 /**
70 * Shortcut to send data serialized in json.
71 *
72 * @param mixed $data
73 *
74 * @return JsonResponse
75 */
76 protected function sendResponse($data)
77 {
78 // https://github.com/schmittjoh/JMSSerializerBundle/issues/293
79 $context = new SerializationContext();
80 $context->setSerializeNull(true);
81
82 $json = $this->get('jms_serializer')->serialize($data, 'json', $context);
83
84 return (new JsonResponse())->setJson($json);
85 }
47} 86}
diff --git a/src/Wallabag/ApiBundle/Entity/AccessToken.php b/src/Wallabag/ApiBundle/Entity/AccessToken.php
index c09a0c80..98e0af3e 100644
--- a/src/Wallabag/ApiBundle/Entity/AccessToken.php
+++ b/src/Wallabag/ApiBundle/Entity/AccessToken.php
@@ -8,6 +8,22 @@ use FOS\OAuthServerBundle\Entity\AccessToken as BaseAccessToken;
8/** 8/**
9 * @ORM\Table("oauth2_access_tokens") 9 * @ORM\Table("oauth2_access_tokens")
10 * @ORM\Entity 10 * @ORM\Entity
11 * @ORM\AttributeOverrides({
12 * @ORM\AttributeOverride(name="token",
13 * column=@ORM\Column(
14 * name = "token",
15 * type = "string",
16 * length = 191
17 * )
18 * ),
19 * @ORM\AttributeOverride(name="scope",
20 * column=@ORM\Column(
21 * name = "scope",
22 * type = "string",
23 * length = 191
24 * )
25 * )
26 * })
11 */ 27 */
12class AccessToken extends BaseAccessToken 28class AccessToken extends BaseAccessToken
13{ 29{
@@ -26,6 +42,7 @@ class AccessToken extends BaseAccessToken
26 42
27 /** 43 /**
28 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User") 44 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User")
45 * @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
29 */ 46 */
30 protected $user; 47 protected $user;
31} 48}
diff --git a/src/Wallabag/ApiBundle/Entity/AuthCode.php b/src/Wallabag/ApiBundle/Entity/AuthCode.php
index 4d4b09fe..7c9c8539 100644
--- a/src/Wallabag/ApiBundle/Entity/AuthCode.php
+++ b/src/Wallabag/ApiBundle/Entity/AuthCode.php
@@ -8,6 +8,22 @@ use FOS\OAuthServerBundle\Entity\AuthCode as BaseAuthCode;
8/** 8/**
9 * @ORM\Table("oauth2_auth_codes") 9 * @ORM\Table("oauth2_auth_codes")
10 * @ORM\Entity 10 * @ORM\Entity
11 * @ORM\AttributeOverrides({
12 * @ORM\AttributeOverride(name="token",
13 * column=@ORM\Column(
14 * name = "token",
15 * type = "string",
16 * length = 191
17 * )
18 * ),
19 * @ORM\AttributeOverride(name="scope",
20 * column=@ORM\Column(
21 * name = "scope",
22 * type = "string",
23 * length = 191
24 * )
25 * )
26 * })
11 */ 27 */
12class AuthCode extends BaseAuthCode 28class AuthCode extends BaseAuthCode
13{ 29{
@@ -26,6 +42,7 @@ class AuthCode extends BaseAuthCode
26 42
27 /** 43 /**
28 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User") 44 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User")
45 * @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
29 */ 46 */
30 protected $user; 47 protected $user;
31} 48}
diff --git a/src/Wallabag/ApiBundle/Entity/RefreshToken.php b/src/Wallabag/ApiBundle/Entity/RefreshToken.php
index 822a02d8..55a507e1 100644
--- a/src/Wallabag/ApiBundle/Entity/RefreshToken.php
+++ b/src/Wallabag/ApiBundle/Entity/RefreshToken.php
@@ -8,6 +8,22 @@ use FOS\OAuthServerBundle\Entity\RefreshToken as BaseRefreshToken;
8/** 8/**
9 * @ORM\Table("oauth2_refresh_tokens") 9 * @ORM\Table("oauth2_refresh_tokens")
10 * @ORM\Entity 10 * @ORM\Entity
11 * @ORM\AttributeOverrides({
12 * @ORM\AttributeOverride(name="token",
13 * column=@ORM\Column(
14 * name = "token",
15 * type = "string",
16 * length = 191
17 * )
18 * ),
19 * @ORM\AttributeOverride(name="scope",
20 * column=@ORM\Column(
21 * name = "scope",
22 * type = "string",
23 * length = 191
24 * )
25 * )
26 * })
11 */ 27 */
12class RefreshToken extends BaseRefreshToken 28class RefreshToken extends BaseRefreshToken
13{ 29{
@@ -26,6 +42,7 @@ class RefreshToken extends BaseRefreshToken
26 42
27 /** 43 /**
28 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User") 44 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User")
45 * @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="CASCADE")
29 */ 46 */
30 protected $user; 47 protected $user;
31} 48}
diff --git a/src/Wallabag/ApiBundle/Form/Type/ClientType.php b/src/Wallabag/ApiBundle/Form/Type/ClientType.php
index fc22538f..14dc5c44 100644
--- a/src/Wallabag/ApiBundle/Form/Type/ClientType.php
+++ b/src/Wallabag/ApiBundle/Form/Type/ClientType.php
@@ -20,6 +20,7 @@ class ClientType extends AbstractType
20 'required' => false, 20 'required' => false,
21 'label' => 'developer.client.form.redirect_uris_label', 21 'label' => 'developer.client.form.redirect_uris_label',
22 'property_path' => 'redirectUris', 22 'property_path' => 'redirectUris',
23 'default_protocol' => null,
23 ]) 24 ])
24 ->add('save', SubmitType::class, ['label' => 'developer.client.form.save_label']) 25 ->add('save', SubmitType::class, ['label' => 'developer.client.form.save_label'])
25 ; 26 ;
diff --git a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
index c0283e71..06e62c37 100644
--- a/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
+++ b/src/Wallabag/ApiBundle/Resources/config/routing_rest.yml
@@ -3,6 +3,11 @@ entry:
3 resource: "WallabagApiBundle:EntryRest" 3 resource: "WallabagApiBundle:EntryRest"
4 name_prefix: api_ 4 name_prefix: api_
5 5
6search:
7 type: rest
8 resource: "WallabagApiBundle:SearchRest"
9 name_prefix: api_
10
6tag: 11tag:
7 type: rest 12 type: rest
8 resource: "WallabagApiBundle:TagRest" 13 resource: "WallabagApiBundle:TagRest"
diff --git a/src/Wallabag/CoreBundle/Command/GenerateUrlHashesCommand.php b/src/Wallabag/CoreBundle/Command/GenerateUrlHashesCommand.php
new file mode 100644
index 00000000..8f2bff11
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Command/GenerateUrlHashesCommand.php
@@ -0,0 +1,99 @@
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\Helper\UrlHasher;
11use Wallabag\UserBundle\Entity\User;
12
13class GenerateUrlHashesCommand extends ContainerAwareCommand
14{
15 /** @var OutputInterface */
16 protected $output;
17
18 protected function configure()
19 {
20 $this
21 ->setName('wallabag:generate-hashed-urls')
22 ->setDescription('Generates hashed urls for each entry')
23 ->setHelp('This command helps you to generates hashes of the url of each entry, to check through API if an URL is already saved')
24 ->addArgument('username', InputArgument::OPTIONAL, 'User to process entries');
25 }
26
27 protected function execute(InputInterface $input, OutputInterface $output)
28 {
29 $this->output = $output;
30
31 $username = (string) $input->getArgument('username');
32
33 if ($username) {
34 try {
35 $user = $this->getUser($username);
36 $this->generateHashedUrls($user);
37 } catch (NoResultException $e) {
38 $output->writeln(sprintf('<error>User "%s" not found.</error>', $username));
39
40 return 1;
41 }
42 } else {
43 $users = $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findAll();
44
45 $output->writeln(sprintf('Generating hashed urls for "%d" users', \count($users)));
46
47 foreach ($users as $user) {
48 $output->writeln(sprintf('Processing user: %s', $user->getUsername()));
49 $this->generateHashedUrls($user);
50 }
51 $output->writeln('Finished generated hashed urls');
52 }
53
54 return 0;
55 }
56
57 /**
58 * @param User $user
59 */
60 private function generateHashedUrls(User $user)
61 {
62 $em = $this->getContainer()->get('doctrine.orm.entity_manager');
63 $repo = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry');
64
65 $entries = $repo->findByUser($user->getId());
66
67 $i = 1;
68 foreach ($entries as $entry) {
69 $entry->setHashedUrl(UrlHasher::hashUrl($entry->getUrl()));
70 $em->persist($entry);
71
72 if (0 === ($i % 20)) {
73 $em->flush();
74 }
75 ++$i;
76 }
77
78 $em->flush();
79
80 $this->output->writeln(sprintf('Generated hashed urls for user: %s', $user->getUserName()));
81 }
82
83 /**
84 * Fetches a user from its username.
85 *
86 * @param string $username
87 *
88 * @return User
89 */
90 private function getUser($username)
91 {
92 return $this->getDoctrine()->getRepository('WallabagUserBundle:User')->findOneByUserName($username);
93 }
94
95 private function getDoctrine()
96 {
97 return $this->getContainer()->get('doctrine');
98 }
99}
diff --git a/src/Wallabag/CoreBundle/Command/InstallCommand.php b/src/Wallabag/CoreBundle/Command/InstallCommand.php
index 3c76545c..c58ae2b5 100644
--- a/src/Wallabag/CoreBundle/Command/InstallCommand.php
+++ b/src/Wallabag/CoreBundle/Command/InstallCommand.php
@@ -94,8 +94,9 @@ class InstallCommand extends ContainerAwareCommand
94 $status = '<info>OK!</info>'; 94 $status = '<info>OK!</info>';
95 $help = ''; 95 $help = '';
96 96
97 $conn = $this->getContainer()->get('doctrine')->getManager()->getConnection();
98
97 try { 99 try {
98 $conn = $this->getContainer()->get('doctrine')->getManager()->getConnection();
99 $conn->connect(); 100 $conn->connect();
100 } catch (\Exception $e) { 101 } catch (\Exception $e) {
101 if (false === strpos($e->getMessage(), 'Unknown database') 102 if (false === strpos($e->getMessage(), 'Unknown database')
@@ -253,7 +254,7 @@ class InstallCommand extends ContainerAwareCommand
253 $question->setHidden(true); 254 $question->setHidden(true);
254 $user->setPlainPassword($this->io->askQuestion($question)); 255 $user->setPlainPassword($this->io->askQuestion($question));
255 256
256 $user->setEmail($this->io->ask('Email', '')); 257 $user->setEmail($this->io->ask('Email', 'wallabag@wallabag.io'));
257 258
258 $user->setEnabled(true); 259 $user->setEnabled(true);
259 $user->addRole('ROLE_SUPER_ADMIN'); 260 $user->addRole('ROLE_SUPER_ADMIN');
diff --git a/src/Wallabag/CoreBundle/Command/ShowUserCommand.php b/src/Wallabag/CoreBundle/Command/ShowUserCommand.php
index a0184267..c95efbf3 100644
--- a/src/Wallabag/CoreBundle/Command/ShowUserCommand.php
+++ b/src/Wallabag/CoreBundle/Command/ShowUserCommand.php
@@ -57,7 +57,8 @@ class ShowUserCommand extends ContainerAwareCommand
57 sprintf('Display name: %s', $user->getName()), 57 sprintf('Display name: %s', $user->getName()),
58 sprintf('Creation date: %s', $user->getCreatedAt()->format('Y-m-d H:i:s')), 58 sprintf('Creation date: %s', $user->getCreatedAt()->format('Y-m-d H:i:s')),
59 sprintf('Last login: %s', null !== $user->getLastLogin() ? $user->getLastLogin()->format('Y-m-d H:i:s') : 'never'), 59 sprintf('Last login: %s', null !== $user->getLastLogin() ? $user->getLastLogin()->format('Y-m-d H:i:s') : 'never'),
60 sprintf('2FA activated: %s', $user->isTwoFactorAuthentication() ? 'yes' : 'no'), 60 sprintf('2FA (email) activated: %s', $user->isEmailTwoFactor() ? 'yes' : 'no'),
61 sprintf('2FA (OTP) activated: %s', $user->isGoogleAuthenticatorEnabled() ? 'yes' : 'no'),
61 ]); 62 ]);
62 } 63 }
63 64
diff --git a/src/Wallabag/CoreBundle/Controller/ConfigController.php b/src/Wallabag/CoreBundle/Controller/ConfigController.php
index b999c539..3b281d48 100644
--- a/src/Wallabag/CoreBundle/Controller/ConfigController.php
+++ b/src/Wallabag/CoreBundle/Controller/ConfigController.php
@@ -2,17 +2,19 @@
2 2
3namespace Wallabag\CoreBundle\Controller; 3namespace Wallabag\CoreBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 5use PragmaRX\Recovery\Recovery as BackupCodes;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller; 6use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\JsonResponse; 7use Symfony\Component\HttpFoundation\JsonResponse;
8use Symfony\Component\HttpFoundation\RedirectResponse; 8use Symfony\Component\HttpFoundation\RedirectResponse;
9use Symfony\Component\HttpFoundation\Request; 9use Symfony\Component\HttpFoundation\Request;
10use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; 10use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
11use Symfony\Component\Routing\Annotation\Route;
12use Symfony\Component\Validator\Constraints\Locale as LocaleConstraint;
11use Wallabag\CoreBundle\Entity\Config; 13use Wallabag\CoreBundle\Entity\Config;
12use Wallabag\CoreBundle\Entity\TaggingRule; 14use Wallabag\CoreBundle\Entity\TaggingRule;
13use Wallabag\CoreBundle\Form\Type\ChangePasswordType; 15use Wallabag\CoreBundle\Form\Type\ChangePasswordType;
14use Wallabag\CoreBundle\Form\Type\ConfigType; 16use Wallabag\CoreBundle\Form\Type\ConfigType;
15use Wallabag\CoreBundle\Form\Type\RssType; 17use Wallabag\CoreBundle\Form\Type\FeedType;
16use Wallabag\CoreBundle\Form\Type\TaggingRuleType; 18use Wallabag\CoreBundle\Form\Type\TaggingRuleType;
17use Wallabag\CoreBundle\Form\Type\UserInformationType; 19use Wallabag\CoreBundle\Form\Type\UserInformationType;
18use Wallabag\CoreBundle\Tools\Utils; 20use Wallabag\CoreBundle\Tools\Utils;
@@ -45,7 +47,7 @@ class ConfigController extends Controller
45 $activeTheme = $this->get('liip_theme.active_theme'); 47 $activeTheme = $this->get('liip_theme.active_theme');
46 $activeTheme->setName($config->getTheme()); 48 $activeTheme->setName($config->getTheme());
47 49
48 $this->get('session')->getFlashBag()->add( 50 $this->addFlash(
49 'notice', 51 'notice',
50 'flashes.config.notice.config_saved' 52 'flashes.config.notice.config_saved'
51 ); 53 );
@@ -67,7 +69,7 @@ class ConfigController extends Controller
67 $userManager->updateUser($user, true); 69 $userManager->updateUser($user, true);
68 } 70 }
69 71
70 $this->get('session')->getFlashBag()->add('notice', $message); 72 $this->addFlash('notice', $message);
71 73
72 return $this->redirect($this->generateUrl('config') . '#set4'); 74 return $this->redirect($this->generateUrl('config') . '#set4');
73 } 75 }
@@ -82,7 +84,7 @@ class ConfigController extends Controller
82 if ($userForm->isSubmitted() && $userForm->isValid()) { 84 if ($userForm->isSubmitted() && $userForm->isValid()) {
83 $userManager->updateUser($user, true); 85 $userManager->updateUser($user, true);
84 86
85 $this->get('session')->getFlashBag()->add( 87 $this->addFlash(
86 'notice', 88 'notice',
87 'flashes.config.notice.user_updated' 89 'flashes.config.notice.user_updated'
88 ); 90 );
@@ -90,17 +92,17 @@ class ConfigController extends Controller
90 return $this->redirect($this->generateUrl('config') . '#set3'); 92 return $this->redirect($this->generateUrl('config') . '#set3');
91 } 93 }
92 94
93 // handle rss information 95 // handle feed information
94 $rssForm = $this->createForm(RssType::class, $config, ['action' => $this->generateUrl('config') . '#set2']); 96 $feedForm = $this->createForm(FeedType::class, $config, ['action' => $this->generateUrl('config') . '#set2']);
95 $rssForm->handleRequest($request); 97 $feedForm->handleRequest($request);
96 98
97 if ($rssForm->isSubmitted() && $rssForm->isValid()) { 99 if ($feedForm->isSubmitted() && $feedForm->isValid()) {
98 $em->persist($config); 100 $em->persist($config);
99 $em->flush(); 101 $em->flush();
100 102
101 $this->get('session')->getFlashBag()->add( 103 $this->addFlash(
102 'notice', 104 'notice',
103 'flashes.config.notice.rss_updated' 105 'flashes.config.notice.feed_updated'
104 ); 106 );
105 107
106 return $this->redirect($this->generateUrl('config') . '#set2'); 108 return $this->redirect($this->generateUrl('config') . '#set2');
@@ -130,7 +132,7 @@ class ConfigController extends Controller
130 $em->persist($taggingRule); 132 $em->persist($taggingRule);
131 $em->flush(); 133 $em->flush();
132 134
133 $this->get('session')->getFlashBag()->add( 135 $this->addFlash(
134 'notice', 136 'notice',
135 'flashes.config.notice.tagging_rules_updated' 137 'flashes.config.notice.tagging_rules_updated'
136 ); 138 );
@@ -141,23 +143,135 @@ class ConfigController extends Controller
141 return $this->render('WallabagCoreBundle:Config:index.html.twig', [ 143 return $this->render('WallabagCoreBundle:Config:index.html.twig', [
142 'form' => [ 144 'form' => [
143 'config' => $configForm->createView(), 145 'config' => $configForm->createView(),
144 'rss' => $rssForm->createView(), 146 'feed' => $feedForm->createView(),
145 'pwd' => $pwdForm->createView(), 147 'pwd' => $pwdForm->createView(),
146 'user' => $userForm->createView(), 148 'user' => $userForm->createView(),
147 'new_tagging_rule' => $newTaggingRule->createView(), 149 'new_tagging_rule' => $newTaggingRule->createView(),
148 ], 150 ],
149 'rss' => [ 151 'feed' => [
150 'username' => $user->getUsername(), 152 'username' => $user->getUsername(),
151 'token' => $config->getRssToken(), 153 'token' => $config->getFeedToken(),
152 ], 154 ],
153 'twofactor_auth' => $this->getParameter('twofactor_auth'), 155 'twofactor_auth' => $this->getParameter('twofactor_auth'),
154 'wallabag_url' => $this->getParameter('domain_name'), 156 'wallabag_url' => $this->getParameter('domain_name'),
155 'enabled_users' => $this->get('wallabag_user.user_repository') 157 'enabled_users' => $this->get('wallabag_user.user_repository')->getSumEnabledUsers(),
156 ->getSumEnabledUsers(),
157 ]); 158 ]);
158 } 159 }
159 160
160 /** 161 /**
162 * Enable 2FA using email.
163 *
164 * @Route("/config/otp/email", name="config_otp_email")
165 */
166 public function otpEmailAction()
167 {
168 if (!$this->getParameter('twofactor_auth')) {
169 return $this->createNotFoundException('two_factor not enabled');
170 }
171
172 $user = $this->getUser();
173
174 $user->setGoogleAuthenticatorSecret(null);
175 $user->setBackupCodes(null);
176 $user->setEmailTwoFactor(true);
177
178 $this->container->get('fos_user.user_manager')->updateUser($user, true);
179
180 $this->addFlash(
181 'notice',
182 'flashes.config.notice.otp_enabled'
183 );
184
185 return $this->redirect($this->generateUrl('config') . '#set3');
186 }
187
188 /**
189 * Enable 2FA using OTP app, user will need to confirm the generated code from the app.
190 *
191 * @Route("/config/otp/app", name="config_otp_app")
192 */
193 public function otpAppAction()
194 {
195 if (!$this->getParameter('twofactor_auth')) {
196 return $this->createNotFoundException('two_factor not enabled');
197 }
198
199 $user = $this->getUser();
200 $secret = $this->get('scheb_two_factor.security.google_authenticator')->generateSecret();
201
202 $user->setGoogleAuthenticatorSecret($secret);
203 $user->setEmailTwoFactor(false);
204
205 $backupCodes = (new BackupCodes())->toArray();
206 $backupCodesHashed = array_map(
207 function ($backupCode) {
208 return password_hash($backupCode, PASSWORD_DEFAULT);
209 },
210 $backupCodes
211 );
212
213 $user->setBackupCodes($backupCodesHashed);
214
215 $this->container->get('fos_user.user_manager')->updateUser($user, true);
216
217 return $this->render('WallabagCoreBundle:Config:otp_app.html.twig', [
218 'backupCodes' => $backupCodes,
219 'qr_code' => $this->get('scheb_two_factor.security.google_authenticator')->getQRContent($user),
220 ]);
221 }
222
223 /**
224 * Cancelling 2FA using OTP app.
225 *
226 * @Route("/config/otp/app/cancel", name="config_otp_app_cancel")
227 */
228 public function otpAppCancelAction()
229 {
230 if (!$this->getParameter('twofactor_auth')) {
231 return $this->createNotFoundException('two_factor not enabled');
232 }
233
234 $user = $this->getUser();
235 $user->setGoogleAuthenticatorSecret(null);
236 $user->setBackupCodes(null);
237
238 $this->container->get('fos_user.user_manager')->updateUser($user, true);
239
240 return $this->redirect($this->generateUrl('config') . '#set3');
241 }
242
243 /**
244 * Validate OTP code.
245 *
246 * @param Request $request
247 *
248 * @Route("/config/otp/app/check", name="config_otp_app_check")
249 */
250 public function otpAppCheckAction(Request $request)
251 {
252 $isValid = $this->get('scheb_two_factor.security.google_authenticator')->checkCode(
253 $this->getUser(),
254 $request->get('_auth_code')
255 );
256
257 if (true === $isValid) {
258 $this->addFlash(
259 'notice',
260 'flashes.config.notice.otp_enabled'
261 );
262
263 return $this->redirect($this->generateUrl('config') . '#set3');
264 }
265
266 $this->addFlash(
267 'two_factor',
268 'scheb_two_factor.code_invalid'
269 );
270
271 return $this->redirect($this->generateUrl('config_otp_app'));
272 }
273
274 /**
161 * @param Request $request 275 * @param Request $request
162 * 276 *
163 * @Route("/generate-token", name="generate_token") 277 * @Route("/generate-token", name="generate_token")
@@ -167,19 +281,19 @@ class ConfigController extends Controller
167 public function generateTokenAction(Request $request) 281 public function generateTokenAction(Request $request)
168 { 282 {
169 $config = $this->getConfig(); 283 $config = $this->getConfig();
170 $config->setRssToken(Utils::generateToken()); 284 $config->setFeedToken(Utils::generateToken());
171 285
172 $em = $this->getDoctrine()->getManager(); 286 $em = $this->getDoctrine()->getManager();
173 $em->persist($config); 287 $em->persist($config);
174 $em->flush(); 288 $em->flush();
175 289
176 if ($request->isXmlHttpRequest()) { 290 if ($request->isXmlHttpRequest()) {
177 return new JsonResponse(['token' => $config->getRssToken()]); 291 return new JsonResponse(['token' => $config->getFeedToken()]);
178 } 292 }
179 293
180 $this->get('session')->getFlashBag()->add( 294 $this->addFlash(
181 'notice', 295 'notice',
182 'flashes.config.notice.rss_token_updated' 296 'flashes.config.notice.feed_token_updated'
183 ); 297 );
184 298
185 return $this->redirect($this->generateUrl('config') . '#set2'); 299 return $this->redirect($this->generateUrl('config') . '#set2');
@@ -202,7 +316,7 @@ class ConfigController extends Controller
202 $em->remove($rule); 316 $em->remove($rule);
203 $em->flush(); 317 $em->flush();
204 318
205 $this->get('session')->getFlashBag()->add( 319 $this->addFlash(
206 'notice', 320 'notice',
207 'flashes.config.notice.tagging_rules_deleted' 321 'flashes.config.notice.tagging_rules_deleted'
208 ); 322 );
@@ -268,7 +382,7 @@ class ConfigController extends Controller
268 break; 382 break;
269 } 383 }
270 384
271 $this->get('session')->getFlashBag()->add( 385 $this->addFlash(
272 'notice', 386 'notice',
273 'flashes.config.notice.' . $type . '_reset' 387 'flashes.config.notice.' . $type . '_reset'
274 ); 388 );
@@ -330,6 +444,27 @@ class ConfigController extends Controller
330 } 444 }
331 445
332 /** 446 /**
447 * Change the locale for the current user.
448 *
449 * @param Request $request
450 * @param string $language
451 *
452 * @Route("/locale/{language}", name="changeLocale")
453 *
454 * @return \Symfony\Component\HttpFoundation\RedirectResponse
455 */
456 public function setLocaleAction(Request $request, $language = null)
457 {
458 $errors = $this->get('validator')->validate($language, (new LocaleConstraint()));
459
460 if (0 === \count($errors)) {
461 $request->getSession()->set('_locale', $language);
462 }
463
464 return $this->redirect($request->headers->get('referer', $this->generateUrl('homepage')));
465 }
466
467 /**
333 * Remove all tags for given tags and a given user and cleanup orphan tags. 468 * Remove all tags for given tags and a given user and cleanup orphan tags.
334 * 469 *
335 * @param array $tags 470 * @param array $tags
diff --git a/src/Wallabag/CoreBundle/Controller/EntryController.php b/src/Wallabag/CoreBundle/Controller/EntryController.php
index b7fdea27..5c8ecb40 100644
--- a/src/Wallabag/CoreBundle/Controller/EntryController.php
+++ b/src/Wallabag/CoreBundle/Controller/EntryController.php
@@ -2,12 +2,13 @@
2 2
3namespace Wallabag\CoreBundle\Controller; 3namespace Wallabag\CoreBundle\Controller;
4 4
5use Doctrine\ORM\NoResultException;
5use Pagerfanta\Adapter\DoctrineORMAdapter; 6use Pagerfanta\Adapter\DoctrineORMAdapter;
6use Pagerfanta\Exception\OutOfRangeCurrentPageException; 7use Pagerfanta\Exception\OutOfRangeCurrentPageException;
7use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache; 8use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
8use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
9use Symfony\Bundle\FrameworkBundle\Controller\Controller; 9use Symfony\Bundle\FrameworkBundle\Controller\Controller;
10use Symfony\Component\HttpFoundation\Request; 10use Symfony\Component\HttpFoundation\Request;
11use Symfony\Component\Routing\Annotation\Route;
11use Symfony\Component\Routing\Generator\UrlGeneratorInterface; 12use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
12use Wallabag\CoreBundle\Entity\Entry; 13use Wallabag\CoreBundle\Entity\Entry;
13use Wallabag\CoreBundle\Event\EntryDeletedEvent; 14use Wallabag\CoreBundle\Event\EntryDeletedEvent;
@@ -233,6 +234,46 @@ class EntryController extends Controller
233 } 234 }
234 235
235 /** 236 /**
237 * Shows untagged articles for current user.
238 *
239 * @param Request $request
240 * @param int $page
241 *
242 * @Route("/untagged/list/{page}", name="untagged", defaults={"page" = "1"})
243 *
244 * @return \Symfony\Component\HttpFoundation\Response
245 */
246 public function showUntaggedEntriesAction(Request $request, $page)
247 {
248 return $this->showEntries('untagged', $request, $page);
249 }
250
251 /**
252 * Shows random entry depending on the given type.
253 *
254 * @param string $type
255 *
256 * @Route("/{type}/random", name="random_entry", requirements={"type": "unread|starred|archive|untagged|all"})
257 *
258 * @return \Symfony\Component\HttpFoundation\RedirectResponse
259 */
260 public function redirectRandomEntryAction($type = 'all')
261 {
262 try {
263 $entry = $this->get('wallabag_core.entry_repository')
264 ->getRandomEntry($this->getUser()->getId(), $type);
265 } catch (NoResultException $e) {
266 $bag = $this->get('session')->getFlashBag();
267 $bag->clear();
268 $bag->add('notice', 'flashes.entry.notice.no_random_entry');
269
270 return $this->redirect($this->generateUrl($type));
271 }
272
273 return $this->redirect($this->generateUrl('view', ['id' => $entry->getId()]));
274 }
275
276 /**
236 * Shows entry content. 277 * Shows entry content.
237 * 278 *
238 * @param Entry $entry 279 * @param Entry $entry
@@ -466,54 +507,6 @@ class EntryController extends Controller
466 } 507 }
467 508
468 /** 509 /**
469 * Shows untagged articles for current user.
470 *
471 * @param Request $request
472 * @param int $page
473 *
474 * @Route("/untagged/list/{page}", name="untagged", defaults={"page" = "1"})
475 *
476 * @return \Symfony\Component\HttpFoundation\Response
477 */
478 public function showUntaggedEntriesAction(Request $request, $page)
479 {
480 return $this->showEntries('untagged', $request, $page);
481 }
482
483 /**
484 * Fetch content and update entry.
485 * In case it fails, $entry->getContent will return an error message.
486 *
487 * @param Entry $entry
488 * @param string $prefixMessage Should be the translation key: entry_saved or entry_reloaded
489 */
490 private function updateEntry(Entry $entry, $prefixMessage = 'entry_saved')
491 {
492 $message = 'flashes.entry.notice.' . $prefixMessage;
493
494 try {
495 $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl());
496 } catch (\Exception $e) {
497 $this->get('logger')->error('Error while saving an entry', [
498 'exception' => $e,
499 'entry' => $entry,
500 ]);
501
502 $message = 'flashes.entry.notice.' . $prefixMessage . '_failed';
503 }
504
505 if (empty($entry->getDomainName())) {
506 $this->get('wallabag_core.content_proxy')->setEntryDomainName($entry);
507 }
508
509 if (empty($entry->getTitle())) {
510 $this->get('wallabag_core.content_proxy')->setDefaultEntryTitle($entry);
511 }
512
513 $this->get('session')->getFlashBag()->add('notice', $message);
514 }
515
516 /**
517 * Global method to retrieve entries depending on the given type 510 * Global method to retrieve entries depending on the given type
518 * It returns the response to be send. 511 * It returns the response to be send.
519 * 512 *
@@ -532,11 +525,9 @@ class EntryController extends Controller
532 switch ($type) { 525 switch ($type) {
533 case 'search': 526 case 'search':
534 $qb = $repository->getBuilderForSearchByUser($this->getUser()->getId(), $searchTerm, $currentRoute); 527 $qb = $repository->getBuilderForSearchByUser($this->getUser()->getId(), $searchTerm, $currentRoute);
535
536 break; 528 break;
537 case 'untagged': 529 case 'untagged':
538 $qb = $repository->getBuilderForUntaggedByUser($this->getUser()->getId()); 530 $qb = $repository->getBuilderForUntaggedByUser($this->getUser()->getId());
539
540 break; 531 break;
541 case 'starred': 532 case 'starred':
542 $qb = $repository->getBuilderForStarredByUser($this->getUser()->getId()); 533 $qb = $repository->getBuilderForStarredByUser($this->getUser()->getId());
@@ -588,6 +579,39 @@ class EntryController extends Controller
588 } 579 }
589 580
590 /** 581 /**
582 * Fetch content and update entry.
583 * In case it fails, $entry->getContent will return an error message.
584 *
585 * @param Entry $entry
586 * @param string $prefixMessage Should be the translation key: entry_saved or entry_reloaded
587 */
588 private function updateEntry(Entry $entry, $prefixMessage = 'entry_saved')
589 {
590 $message = 'flashes.entry.notice.' . $prefixMessage;
591
592 try {
593 $this->get('wallabag_core.content_proxy')->updateEntry($entry, $entry->getUrl());
594 } catch (\Exception $e) {
595 $this->get('logger')->error('Error while saving an entry', [
596 'exception' => $e,
597 'entry' => $entry,
598 ]);
599
600 $message = 'flashes.entry.notice.' . $prefixMessage . '_failed';
601 }
602
603 if (empty($entry->getDomainName())) {
604 $this->get('wallabag_core.content_proxy')->setEntryDomainName($entry);
605 }
606
607 if (empty($entry->getTitle())) {
608 $this->get('wallabag_core.content_proxy')->setDefaultEntryTitle($entry);
609 }
610
611 $this->get('session')->getFlashBag()->add('notice', $message);
612 }
613
614 /**
591 * Check if the logged user can manage the given entry. 615 * Check if the logged user can manage the given entry.
592 * 616 *
593 * @param Entry $entry 617 * @param Entry $entry
diff --git a/src/Wallabag/CoreBundle/Controller/ExportController.php b/src/Wallabag/CoreBundle/Controller/ExportController.php
index 9e9dbe49..9ff35ff5 100644
--- a/src/Wallabag/CoreBundle/Controller/ExportController.php
+++ b/src/Wallabag/CoreBundle/Controller/ExportController.php
@@ -2,10 +2,10 @@
2 2
3namespace Wallabag\CoreBundle\Controller; 3namespace Wallabag\CoreBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller; 5use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\Request; 6use Symfony\Component\HttpFoundation\Request;
8use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 7use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
8use Symfony\Component\Routing\Annotation\Route;
9use Wallabag\CoreBundle\Entity\Entry; 9use Wallabag\CoreBundle\Entity\Entry;
10 10
11/** 11/**
diff --git a/src/Wallabag/CoreBundle/Controller/RssController.php b/src/Wallabag/CoreBundle/Controller/FeedController.php
index 848bb814..8d422a90 100644
--- a/src/Wallabag/CoreBundle/Controller/RssController.php
+++ b/src/Wallabag/CoreBundle/Controller/FeedController.php
@@ -7,86 +7,97 @@ use Pagerfanta\Adapter\DoctrineORMAdapter;
7use Pagerfanta\Exception\OutOfRangeCurrentPageException; 7use Pagerfanta\Exception\OutOfRangeCurrentPageException;
8use Pagerfanta\Pagerfanta; 8use Pagerfanta\Pagerfanta;
9use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; 9use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
10use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
11use Symfony\Bundle\FrameworkBundle\Controller\Controller; 10use Symfony\Bundle\FrameworkBundle\Controller\Controller;
12use Symfony\Component\HttpFoundation\Request;
13use Symfony\Component\HttpFoundation\Response; 11use Symfony\Component\HttpFoundation\Response;
12use Symfony\Component\Routing\Annotation\Route;
14use Symfony\Component\Routing\Generator\UrlGeneratorInterface; 13use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
15use Wallabag\CoreBundle\Entity\Tag; 14use Wallabag\CoreBundle\Entity\Tag;
16use Wallabag\UserBundle\Entity\User; 15use Wallabag\UserBundle\Entity\User;
17 16
18class RssController extends Controller 17class FeedController extends Controller
19{ 18{
20 /** 19 /**
21 * Shows unread entries for current user. 20 * Shows unread entries for current user.
22 * 21 *
23 * @Route("/{username}/{token}/unread.xml", name="unread_rss", defaults={"_format"="xml"}) 22 * @Route("/feed/{username}/{token}/unread/{page}", name="unread_feed", defaults={"page"=1, "_format"="xml"})
24 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") 23 *
24 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
25 *
26 * @param User $user
27 * @param $page
25 * 28 *
26 * @return \Symfony\Component\HttpFoundation\Response 29 * @return \Symfony\Component\HttpFoundation\Response
27 */ 30 */
28 public function showUnreadRSSAction(Request $request, User $user) 31 public function showUnreadFeedAction(User $user, $page)
29 { 32 {
30 return $this->showEntries('unread', $user, $request->query->get('page', 1)); 33 return $this->showEntries('unread', $user, $page);
31 } 34 }
32 35
33 /** 36 /**
34 * Shows read entries for current user. 37 * Shows read entries for current user.
35 * 38 *
36 * @Route("/{username}/{token}/archive.xml", name="archive_rss", defaults={"_format"="xml"}) 39 * @Route("/feed/{username}/{token}/archive/{page}", name="archive_feed", defaults={"page"=1, "_format"="xml"})
37 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") 40 *
41 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
42 *
43 * @param User $user
44 * @param $page
38 * 45 *
39 * @return \Symfony\Component\HttpFoundation\Response 46 * @return \Symfony\Component\HttpFoundation\Response
40 */ 47 */
41 public function showArchiveRSSAction(Request $request, User $user) 48 public function showArchiveFeedAction(User $user, $page)
42 { 49 {
43 return $this->showEntries('archive', $user, $request->query->get('page', 1)); 50 return $this->showEntries('archive', $user, $page);
44 } 51 }
45 52
46 /** 53 /**
47 * Shows starred entries for current user. 54 * Shows starred entries for current user.
48 * 55 *
49 * @Route("/{username}/{token}/starred.xml", name="starred_rss", defaults={"_format"="xml"}) 56 * @Route("/feed/{username}/{token}/starred/{page}", name="starred_feed", defaults={"page"=1, "_format"="xml"})
50 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") 57 *
58 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
59 *
60 * @param User $user
61 * @param $page
51 * 62 *
52 * @return \Symfony\Component\HttpFoundation\Response 63 * @return \Symfony\Component\HttpFoundation\Response
53 */ 64 */
54 public function showStarredRSSAction(Request $request, User $user) 65 public function showStarredFeedAction(User $user, $page)
55 { 66 {
56 return $this->showEntries('starred', $user, $request->query->get('page', 1)); 67 return $this->showEntries('starred', $user, $page);
57 } 68 }
58 69
59 /** 70 /**
60 * Shows all entries for current user. 71 * Shows all entries for current user.
61 * 72 *
62 * @Route("/{username}/{token}/all.xml", name="all_rss", defaults={"_format"="xml"}) 73 * @Route("/feed/{username}/{token}/all/{page}", name="all_feed", defaults={"page"=1, "_format"="xml"})
63 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") 74 *
75 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
64 * 76 *
65 * @return \Symfony\Component\HttpFoundation\Response 77 * @return \Symfony\Component\HttpFoundation\Response
66 */ 78 */
67 public function showAllRSSAction(Request $request, User $user) 79 public function showAllFeedAction(User $user, $page)
68 { 80 {
69 return $this->showEntries('all', $user, $request->query->get('page', 1)); 81 return $this->showEntries('all', $user, $page);
70 } 82 }
71 83
72 /** 84 /**
73 * Shows entries associated to a tag for current user. 85 * Shows entries associated to a tag for current user.
74 * 86 *
75 * @Route("/{username}/{token}/tags/{slug}.xml", name="tag_rss", defaults={"_format"="xml"}) 87 * @Route("/feed/{username}/{token}/tags/{slug}/{page}", name="tag_feed", defaults={"page"=1, "_format"="xml"})
76 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_rsstoken_converter") 88 *
89 * @ParamConverter("user", class="WallabagUserBundle:User", converter="username_feed_token_converter")
77 * @ParamConverter("tag", options={"mapping": {"slug": "slug"}}) 90 * @ParamConverter("tag", options={"mapping": {"slug": "slug"}})
78 * 91 *
79 * @return \Symfony\Component\HttpFoundation\Response 92 * @return \Symfony\Component\HttpFoundation\Response
80 */ 93 */
81 public function showTagsAction(Request $request, User $user, Tag $tag) 94 public function showTagsFeedAction(User $user, Tag $tag, $page)
82 { 95 {
83 $page = $request->query->get('page', 1);
84
85 $url = $this->generateUrl( 96 $url = $this->generateUrl(
86 'tag_rss', 97 'tag_feed',
87 [ 98 [
88 'username' => $user->getUsername(), 99 'username' => $user->getUsername(),
89 'token' => $user->getConfig()->getRssToken(), 100 'token' => $user->getConfig()->getFeedToken(),
90 'slug' => $tag->getSlug(), 101 'slug' => $tag->getSlug(),
91 ], 102 ],
92 UrlGeneratorInterface::ABSOLUTE_URL 103 UrlGeneratorInterface::ABSOLUTE_URL
@@ -119,12 +130,15 @@ class RssController extends Controller
119 return $this->render( 130 return $this->render(
120 '@WallabagCore/themes/common/Entry/entries.xml.twig', 131 '@WallabagCore/themes/common/Entry/entries.xml.twig',
121 [ 132 [
122 'url_html' => $this->generateUrl('tag_entries', ['slug' => $tag->getSlug()], UrlGeneratorInterface::ABSOLUTE_URL), 133 'type' => 'tag',
123 'type' => 'tag (' . $tag->getLabel() . ')',
124 'url' => $url, 134 'url' => $url,
125 'entries' => $entries, 135 'entries' => $entries,
136 'user' => $user->getUsername(),
137 'domainName' => $this->getParameter('domain_name'),
138 'version' => $this->getParameter('wallabag_core.version'),
139 'tag' => $tag->getSlug(),
126 ], 140 ],
127 new Response('', 200, ['Content-Type' => 'application/rss+xml']) 141 new Response('', 200, ['Content-Type' => 'application/atom+xml'])
128 ); 142 );
129 } 143 }
130 144
@@ -162,14 +176,14 @@ class RssController extends Controller
162 $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); 176 $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false);
163 $entries = new Pagerfanta($pagerAdapter); 177 $entries = new Pagerfanta($pagerAdapter);
164 178
165 $perPage = $user->getConfig()->getRssLimit() ?: $this->getParameter('wallabag_core.rss_limit'); 179 $perPage = $user->getConfig()->getFeedLimit() ?: $this->getParameter('wallabag_core.Feed_limit');
166 $entries->setMaxPerPage($perPage); 180 $entries->setMaxPerPage($perPage);
167 181
168 $url = $this->generateUrl( 182 $url = $this->generateUrl(
169 $type . '_rss', 183 $type . '_feed',
170 [ 184 [
171 'username' => $user->getUsername(), 185 'username' => $user->getUsername(),
172 'token' => $user->getConfig()->getRssToken(), 186 'token' => $user->getConfig()->getFeedToken(),
173 ], 187 ],
174 UrlGeneratorInterface::ABSOLUTE_URL 188 UrlGeneratorInterface::ABSOLUTE_URL
175 ); 189 );
@@ -178,19 +192,19 @@ class RssController extends Controller
178 $entries->setCurrentPage((int) $page); 192 $entries->setCurrentPage((int) $page);
179 } catch (OutOfRangeCurrentPageException $e) { 193 } catch (OutOfRangeCurrentPageException $e) {
180 if ($page > 1) { 194 if ($page > 1) {
181 return $this->redirect($url . '?page=' . $entries->getNbPages(), 302); 195 return $this->redirect($url . '/' . $entries->getNbPages());
182 } 196 }
183 } 197 }
184 198
185 return $this->render( 199 return $this->render('@WallabagCore/themes/common/Entry/entries.xml.twig', [
186 '@WallabagCore/themes/common/Entry/entries.xml.twig', 200 'type' => $type,
187 [ 201 'url' => $url,
188 'url_html' => $this->generateUrl($type, [], UrlGeneratorInterface::ABSOLUTE_URL), 202 'entries' => $entries,
189 'type' => $type, 203 'user' => $user->getUsername(),
190 'url' => $url, 204 'domainName' => $this->getParameter('domain_name'),
191 'entries' => $entries, 205 'version' => $this->getParameter('wallabag_core.version'),
192 ], 206 ],
193 new Response('', 200, ['Content-Type' => 'application/rss+xml']) 207 new Response('', 200, ['Content-Type' => 'application/atom+xml'])
194 ); 208 );
195 } 209 }
196} 210}
diff --git a/src/Wallabag/CoreBundle/Controller/SiteCredentialController.php b/src/Wallabag/CoreBundle/Controller/SiteCredentialController.php
index 548de744..51bc1d94 100644
--- a/src/Wallabag/CoreBundle/Controller/SiteCredentialController.php
+++ b/src/Wallabag/CoreBundle/Controller/SiteCredentialController.php
@@ -2,10 +2,9 @@
2 2
3namespace Wallabag\CoreBundle\Controller; 3namespace Wallabag\CoreBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
6use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
7use Symfony\Bundle\FrameworkBundle\Controller\Controller; 5use Symfony\Bundle\FrameworkBundle\Controller\Controller;
8use Symfony\Component\HttpFoundation\Request; 6use Symfony\Component\HttpFoundation\Request;
7use Symfony\Component\Routing\Annotation\Route;
9use Wallabag\CoreBundle\Entity\SiteCredential; 8use Wallabag\CoreBundle\Entity\SiteCredential;
10use Wallabag\UserBundle\Entity\User; 9use Wallabag\UserBundle\Entity\User;
11 10
@@ -19,8 +18,7 @@ class SiteCredentialController extends Controller
19 /** 18 /**
20 * Lists all User entities. 19 * Lists all User entities.
21 * 20 *
22 * @Route("/", name="site_credentials_index") 21 * @Route("/", name="site_credentials_index", methods={"GET"})
23 * @Method("GET")
24 */ 22 */
25 public function indexAction() 23 public function indexAction()
26 { 24 {
@@ -36,8 +34,7 @@ class SiteCredentialController extends Controller
36 /** 34 /**
37 * Creates a new site credential entity. 35 * Creates a new site credential entity.
38 * 36 *
39 * @Route("/new", name="site_credentials_new") 37 * @Route("/new", name="site_credentials_new", methods={"GET", "POST"})
40 * @Method({"GET", "POST"})
41 * 38 *
42 * @param Request $request 39 * @param Request $request
43 * 40 *
@@ -77,8 +74,7 @@ class SiteCredentialController extends Controller
77 /** 74 /**
78 * Displays a form to edit an existing site credential entity. 75 * Displays a form to edit an existing site credential entity.
79 * 76 *
80 * @Route("/{id}/edit", name="site_credentials_edit") 77 * @Route("/{id}/edit", name="site_credentials_edit", methods={"GET", "POST"})
81 * @Method({"GET", "POST"})
82 * 78 *
83 * @param Request $request 79 * @param Request $request
84 * @param SiteCredential $siteCredential 80 * @param SiteCredential $siteCredential
@@ -121,8 +117,7 @@ class SiteCredentialController extends Controller
121 /** 117 /**
122 * Deletes a site credential entity. 118 * Deletes a site credential entity.
123 * 119 *
124 * @Route("/{id}", name="site_credentials_delete") 120 * @Route("/{id}", name="site_credentials_delete", methods={"DELETE"})
125 * @Method("DELETE")
126 * 121 *
127 * @param Request $request 122 * @param Request $request
128 * @param SiteCredential $siteCredential 123 * @param SiteCredential $siteCredential
diff --git a/src/Wallabag/CoreBundle/Controller/StaticController.php b/src/Wallabag/CoreBundle/Controller/StaticController.php
index 318af303..fa760c14 100644
--- a/src/Wallabag/CoreBundle/Controller/StaticController.php
+++ b/src/Wallabag/CoreBundle/Controller/StaticController.php
@@ -2,8 +2,8 @@
2 2
3namespace Wallabag\CoreBundle\Controller; 3namespace Wallabag\CoreBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller; 5use Symfony\Bundle\FrameworkBundle\Controller\Controller;
6use Symfony\Component\Routing\Annotation\Route;
7 7
8class StaticController extends Controller 8class StaticController extends Controller
9{ 9{
diff --git a/src/Wallabag/CoreBundle/Controller/TagController.php b/src/Wallabag/CoreBundle/Controller/TagController.php
index b6d28e59..d0155c60 100644
--- a/src/Wallabag/CoreBundle/Controller/TagController.php
+++ b/src/Wallabag/CoreBundle/Controller/TagController.php
@@ -5,12 +5,13 @@ namespace Wallabag\CoreBundle\Controller;
5use Pagerfanta\Adapter\ArrayAdapter; 5use Pagerfanta\Adapter\ArrayAdapter;
6use Pagerfanta\Exception\OutOfRangeCurrentPageException; 6use Pagerfanta\Exception\OutOfRangeCurrentPageException;
7use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; 7use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
8use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
9use Symfony\Bundle\FrameworkBundle\Controller\Controller; 8use Symfony\Bundle\FrameworkBundle\Controller\Controller;
10use Symfony\Component\HttpFoundation\Request; 9use Symfony\Component\HttpFoundation\Request;
10use Symfony\Component\Routing\Annotation\Route;
11use Wallabag\CoreBundle\Entity\Entry; 11use Wallabag\CoreBundle\Entity\Entry;
12use Wallabag\CoreBundle\Entity\Tag; 12use Wallabag\CoreBundle\Entity\Tag;
13use Wallabag\CoreBundle\Form\Type\NewTagType; 13use Wallabag\CoreBundle\Form\Type\NewTagType;
14use Wallabag\CoreBundle\Form\Type\RenameTagType;
14 15
15class TagController extends Controller 16class TagController extends Controller
16{ 17{
@@ -87,8 +88,14 @@ class TagController extends Controller
87 $tags = $this->get('wallabag_core.tag_repository') 88 $tags = $this->get('wallabag_core.tag_repository')
88 ->findAllFlatTagsWithNbEntries($this->getUser()->getId()); 89 ->findAllFlatTagsWithNbEntries($this->getUser()->getId());
89 90
91 $renameForms = [];
92 foreach ($tags as $tag) {
93 $renameForms[$tag['id']] = $this->createForm(RenameTagType::class, new Tag())->createView();
94 }
95
90 return $this->render('WallabagCoreBundle:Tag:tags.html.twig', [ 96 return $this->render('WallabagCoreBundle:Tag:tags.html.twig', [
91 'tags' => $tags, 97 'tags' => $tags,
98 'renameForms' => $renameForms,
92 ]); 99 ]);
93 } 100 }
94 101
@@ -130,4 +137,48 @@ class TagController extends Controller
130 'tag' => $tag, 137 'tag' => $tag,
131 ]); 138 ]);
132 } 139 }
140
141 /**
142 * Rename a given tag with a new label
143 * Create a new tag with the new name and drop the old one.
144 *
145 * @param Tag $tag
146 * @param Request $request
147 *
148 * @Route("/tag/rename/{slug}", name="tag_rename")
149 * @ParamConverter("tag", options={"mapping": {"slug": "slug"}})
150 *
151 * @return \Symfony\Component\HttpFoundation\Response
152 */
153 public function renameTagAction(Tag $tag, Request $request)
154 {
155 $form = $this->createForm(RenameTagType::class, new Tag());
156 $form->handleRequest($request);
157
158 if ($form->isSubmitted() && $form->isValid()) {
159 $entries = $this->get('wallabag_core.entry_repository')->findAllByTagId(
160 $this->getUser()->getId(),
161 $tag->getId()
162 );
163 foreach ($entries as $entry) {
164 $this->get('wallabag_core.tags_assigner')->assignTagsToEntry(
165 $entry,
166 $form->get('label')->getData()
167 );
168 $entry->removeTag($tag);
169 }
170
171 $em = $this->getDoctrine()->getManager();
172 $em->flush();
173 }
174
175 $this->get('session')->getFlashBag()->add(
176 'notice',
177 'flashes.tag.notice.tag_renamed'
178 );
179
180 $redirectUrl = $this->get('wallabag_core.helper.redirect')->to($request->headers->get('referer'), '', true);
181
182 return $this->redirect($redirectUrl);
183 }
133} 184}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadConfigData.php b/src/Wallabag/CoreBundle/DataFixtures/ConfigFixtures.php
index 3d4d5def..c54e9f2c 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadConfigData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/ConfigFixtures.php
@@ -1,13 +1,14 @@
1<?php 1<?php
2 2
3namespace Wallabag\CoreBundle\DataFixtures\ORM; 3namespace Wallabag\CoreBundle\DataFixtures;
4 4
5use Doctrine\Common\DataFixtures\AbstractFixture; 5use Doctrine\Bundle\FixturesBundle\Fixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface; 6use Doctrine\Common\DataFixtures\DependentFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager; 7use Doctrine\Common\Persistence\ObjectManager;
8use Wallabag\CoreBundle\Entity\Config; 8use Wallabag\CoreBundle\Entity\Config;
9use Wallabag\UserBundle\DataFixtures\UserFixtures;
9 10
10class LoadConfigData extends AbstractFixture implements OrderedFixtureInterface 11class ConfigFixtures extends Fixture implements DependentFixtureInterface
11{ 12{
12 /** 13 /**
13 * {@inheritdoc} 14 * {@inheritdoc}
@@ -60,8 +61,10 @@ class LoadConfigData extends AbstractFixture implements OrderedFixtureInterface
60 /** 61 /**
61 * {@inheritdoc} 62 * {@inheritdoc}
62 */ 63 */
63 public function getOrder() 64 public function getDependencies()
64 { 65 {
65 return 20; 66 return [
67 UserFixtures::class,
68 ];
66 } 69 }
67} 70}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php b/src/Wallabag/CoreBundle/DataFixtures/EntryFixtures.php
index 8e7a1d2a..024fcfdc 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadEntryData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/EntryFixtures.php
@@ -1,13 +1,14 @@
1<?php 1<?php
2 2
3namespace Wallabag\CoreBundle\DataFixtures\ORM; 3namespace Wallabag\CoreBundle\DataFixtures;
4 4
5use Doctrine\Common\DataFixtures\AbstractFixture; 5use Doctrine\Bundle\FixturesBundle\Fixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface; 6use Doctrine\Common\DataFixtures\DependentFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager; 7use Doctrine\Common\Persistence\ObjectManager;
8use Wallabag\CoreBundle\Entity\Entry; 8use Wallabag\CoreBundle\Entity\Entry;
9use Wallabag\UserBundle\DataFixtures\UserFixtures;
9 10
10class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface 11class EntryFixtures extends Fixture implements DependentFixtureInterface
11{ 12{
12 /** 13 /**
13 * {@inheritdoc} 14 * {@inheritdoc}
@@ -127,8 +128,11 @@ class LoadEntryData extends AbstractFixture implements OrderedFixtureInterface
127 /** 128 /**
128 * {@inheritdoc} 129 * {@inheritdoc}
129 */ 130 */
130 public function getOrder() 131 public function getDependencies()
131 { 132 {
132 return 30; 133 return [
134 UserFixtures::class,
135 TagFixtures::class,
136 ];
133 } 137 }
134} 138}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php b/src/Wallabag/CoreBundle/DataFixtures/SettingFixtures.php
index 3fe88e7f..cc7d1f59 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSettingData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/SettingFixtures.php
@@ -1,15 +1,14 @@
1<?php 1<?php
2 2
3namespace Wallabag\CoreBundle\DataFixtures\ORM; 3namespace Wallabag\CoreBundle\DataFixtures;
4 4
5use Craue\ConfigBundle\Entity\Setting; 5use Craue\ConfigBundle\Entity\Setting;
6use Doctrine\Common\DataFixtures\AbstractFixture; 6use Doctrine\Bundle\FixturesBundle\Fixture;
7use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
8use Doctrine\Common\Persistence\ObjectManager; 7use Doctrine\Common\Persistence\ObjectManager;
9use Symfony\Component\DependencyInjection\ContainerAwareInterface; 8use Symfony\Component\DependencyInjection\ContainerAwareInterface;
10use Symfony\Component\DependencyInjection\ContainerInterface; 9use Symfony\Component\DependencyInjection\ContainerInterface;
11 10
12class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface 11class SettingFixtures extends Fixture implements ContainerAwareInterface
13{ 12{
14 /** 13 /**
15 * @var ContainerInterface 14 * @var ContainerInterface
@@ -36,12 +35,4 @@ class LoadSettingData extends AbstractFixture implements OrderedFixtureInterface
36 35
37 $manager->flush(); 36 $manager->flush();
38 } 37 }
39
40 /**
41 * {@inheritdoc}
42 */
43 public function getOrder()
44 {
45 return 29;
46 }
47} 38}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSiteCredentialData.php b/src/Wallabag/CoreBundle/DataFixtures/SiteCredentialFixtures.php
index faf29da6..9a7d116f 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadSiteCredentialData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/SiteCredentialFixtures.php
@@ -1,15 +1,16 @@
1<?php 1<?php
2 2
3namespace Wallabag\CoreBundle\DataFixtures\ORM; 3namespace Wallabag\CoreBundle\DataFixtures;
4 4
5use Doctrine\Common\DataFixtures\AbstractFixture; 5use Doctrine\Bundle\FixturesBundle\Fixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface; 6use Doctrine\Common\DataFixtures\DependentFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager; 7use Doctrine\Common\Persistence\ObjectManager;
8use Symfony\Component\DependencyInjection\ContainerAwareInterface; 8use Symfony\Component\DependencyInjection\ContainerAwareInterface;
9use Symfony\Component\DependencyInjection\ContainerInterface; 9use Symfony\Component\DependencyInjection\ContainerInterface;
10use Wallabag\CoreBundle\Entity\SiteCredential; 10use Wallabag\CoreBundle\Entity\SiteCredential;
11use Wallabag\UserBundle\DataFixtures\UserFixtures;
11 12
12class LoadSiteCredentialData extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface 13class SiteCredentialFixtures extends Fixture implements DependentFixtureInterface, ContainerAwareInterface
13{ 14{
14 /** 15 /**
15 * @var ContainerInterface 16 * @var ContainerInterface
@@ -46,8 +47,10 @@ class LoadSiteCredentialData extends AbstractFixture implements OrderedFixtureIn
46 /** 47 /**
47 * {@inheritdoc} 48 * {@inheritdoc}
48 */ 49 */
49 public function getOrder() 50 public function getDependencies()
50 { 51 {
51 return 50; 52 return [
53 UserFixtures::class,
54 ];
52 } 55 }
53} 56}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php b/src/Wallabag/CoreBundle/DataFixtures/TagFixtures.php
index 485445c1..58a0d799 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTagData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/TagFixtures.php
@@ -1,13 +1,12 @@
1<?php 1<?php
2 2
3namespace Wallabag\CoreBundle\DataFixtures\ORM; 3namespace Wallabag\CoreBundle\DataFixtures;
4 4
5use Doctrine\Common\DataFixtures\AbstractFixture; 5use Doctrine\Bundle\FixturesBundle\Fixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager; 6use Doctrine\Common\Persistence\ObjectManager;
8use Wallabag\CoreBundle\Entity\Tag; 7use Wallabag\CoreBundle\Entity\Tag;
9 8
10class LoadTagData extends AbstractFixture implements OrderedFixtureInterface 9class TagFixtures extends Fixture
11{ 10{
12 /** 11 /**
13 * {@inheritdoc} 12 * {@inheritdoc}
@@ -33,12 +32,4 @@ class LoadTagData extends AbstractFixture implements OrderedFixtureInterface
33 32
34 $manager->flush(); 33 $manager->flush();
35 } 34 }
36
37 /**
38 * {@inheritdoc}
39 */
40 public function getOrder()
41 {
42 return 25;
43 }
44} 35}
diff --git a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTaggingRuleData.php b/src/Wallabag/CoreBundle/DataFixtures/TaggingRuleFixtures.php
index 55abd63c..78ff314a 100644
--- a/src/Wallabag/CoreBundle/DataFixtures/ORM/LoadTaggingRuleData.php
+++ b/src/Wallabag/CoreBundle/DataFixtures/TaggingRuleFixtures.php
@@ -1,13 +1,13 @@
1<?php 1<?php
2 2
3namespace Wallabag\CoreBundle\DataFixtures\ORM; 3namespace Wallabag\CoreBundle\DataFixtures;
4 4
5use Doctrine\Common\DataFixtures\AbstractFixture; 5use Doctrine\Bundle\FixturesBundle\Fixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface; 6use Doctrine\Common\DataFixtures\DependentFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager; 7use Doctrine\Common\Persistence\ObjectManager;
8use Wallabag\CoreBundle\Entity\TaggingRule; 8use Wallabag\CoreBundle\Entity\TaggingRule;
9 9
10class LoadTaggingRuleData extends AbstractFixture implements OrderedFixtureInterface 10class TaggingRuleFixtures extends Fixture implements DependentFixtureInterface
11{ 11{
12 /** 12 /**
13 * {@inheritdoc} 13 * {@inheritdoc}
@@ -49,8 +49,10 @@ class LoadTaggingRuleData extends AbstractFixture implements OrderedFixtureInter
49 /** 49 /**
50 * {@inheritdoc} 50 * {@inheritdoc}
51 */ 51 */
52 public function getOrder() 52 public function getDependencies()
53 { 53 {
54 return 40; 54 return [
55 ConfigFixtures::class,
56 ];
55 } 57 }
56} 58}
diff --git a/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php b/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php
index a3ef2b53..e9a1e9e0 100644
--- a/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php
+++ b/src/Wallabag/CoreBundle/DependencyInjection/WallabagCoreExtension.php
@@ -18,7 +18,7 @@ class WallabagCoreExtension extends Extension
18 $container->setParameter('wallabag_core.items_on_page', $config['items_on_page']); 18 $container->setParameter('wallabag_core.items_on_page', $config['items_on_page']);
19 $container->setParameter('wallabag_core.theme', $config['theme']); 19 $container->setParameter('wallabag_core.theme', $config['theme']);
20 $container->setParameter('wallabag_core.language', $config['language']); 20 $container->setParameter('wallabag_core.language', $config['language']);
21 $container->setParameter('wallabag_core.rss_limit', $config['rss_limit']); 21 $container->setParameter('wallabag_core.feed_limit', $config['rss_limit']);
22 $container->setParameter('wallabag_core.reading_speed', $config['reading_speed']); 22 $container->setParameter('wallabag_core.reading_speed', $config['reading_speed']);
23 $container->setParameter('wallabag_core.version', $config['version']); 23 $container->setParameter('wallabag_core.version', $config['version']);
24 $container->setParameter('wallabag_core.paypal_url', $config['paypal_url']); 24 $container->setParameter('wallabag_core.paypal_url', $config['paypal_url']);
diff --git a/src/Wallabag/CoreBundle/Doctrine/DBAL/Driver/CustomPostgreSQLDriver.php b/src/Wallabag/CoreBundle/Doctrine/DBAL/Driver/CustomPostgreSQLDriver.php
deleted file mode 100644
index eb5b203f..00000000
--- a/src/Wallabag/CoreBundle/Doctrine/DBAL/Driver/CustomPostgreSQLDriver.php
+++ /dev/null
@@ -1,25 +0,0 @@
1<?php
2
3namespace Wallabag\CoreBundle\Doctrine\DBAL\Driver;
4
5use Doctrine\DBAL\Connection;
6use Doctrine\DBAL\Driver\PDOPgSql\Driver;
7use Wallabag\CoreBundle\Doctrine\DBAL\Schema\CustomPostgreSqlSchemaManager;
8
9/**
10 * This custom driver allow to use a different schema manager
11 * So we can fix the PostgreSQL 10 problem.
12 *
13 * @see https://github.com/wallabag/wallabag/issues/3479
14 * @see https://github.com/doctrine/dbal/issues/2868
15 */
16class CustomPostgreSQLDriver extends Driver
17{
18 /**
19 * {@inheritdoc}
20 */
21 public function getSchemaManager(Connection $conn)
22 {
23 return new CustomPostgreSqlSchemaManager($conn);
24 }
25}
diff --git a/src/Wallabag/CoreBundle/Doctrine/DBAL/Schema/CustomPostgreSqlSchemaManager.php b/src/Wallabag/CoreBundle/Doctrine/DBAL/Schema/CustomPostgreSqlSchemaManager.php
deleted file mode 100644
index 439ae17d..00000000
--- a/src/Wallabag/CoreBundle/Doctrine/DBAL/Schema/CustomPostgreSqlSchemaManager.php
+++ /dev/null
@@ -1,38 +0,0 @@
1<?php
2
3namespace Wallabag\CoreBundle\Doctrine\DBAL\Schema;
4
5use Doctrine\DBAL\Schema\PostgreSqlSchemaManager;
6use Doctrine\DBAL\Schema\Sequence;
7
8/**
9 * This custom schema manager fix the PostgreSQL 10 problem.
10 *
11 * @see https://github.com/wallabag/wallabag/issues/3479
12 * @see https://github.com/doctrine/dbal/issues/2868
13 */
14class CustomPostgreSqlSchemaManager extends PostgreSqlSchemaManager
15{
16 /**
17 * {@inheritdoc}
18 */
19 protected function _getPortableSequenceDefinition($sequence)
20 {
21 $sequenceName = $sequence['relname'];
22 if ('public' !== $sequence['schemaname']) {
23 $sequenceName = $sequence['schemaname'] . '.' . $sequence['relname'];
24 }
25
26 $query = 'SELECT min_value, increment_by FROM ' . $this->_platform->quoteIdentifier($sequenceName);
27
28 // the `method_exists` is only to avoid test to fail:
29 // DAMA\DoctrineTestBundle\Doctrine\DBAL\StaticConnection doesn't support the `getServerVersion`
30 if (method_exists($this->_conn->getWrappedConnection(), 'getServerVersion') && (float) ($this->_conn->getWrappedConnection()->getServerVersion()) >= 10) {
31 $query = "SELECT min_value, increment_by FROM pg_sequences WHERE schemaname = 'public' AND sequencename = " . $this->_conn->quote($sequenceName);
32 }
33
34 $data = $this->_conn->fetchAll($query);
35
36 return new Sequence($sequenceName, $data[0]['increment_by'], $data[0]['min_value']);
37 }
38}
diff --git a/src/Wallabag/CoreBundle/Doctrine/WallabagMigration.php b/src/Wallabag/CoreBundle/Doctrine/WallabagMigration.php
index 7aa2409a..4a3fef3b 100644
--- a/src/Wallabag/CoreBundle/Doctrine/WallabagMigration.php
+++ b/src/Wallabag/CoreBundle/Doctrine/WallabagMigration.php
@@ -2,8 +2,8 @@
2 2
3namespace Wallabag\CoreBundle\Doctrine; 3namespace Wallabag\CoreBundle\Doctrine;
4 4
5use Doctrine\DBAL\Migrations\AbstractMigration;
6use Doctrine\DBAL\Schema\Schema; 5use Doctrine\DBAL\Schema\Schema;
6use Doctrine\Migrations\AbstractMigration;
7use Symfony\Component\DependencyInjection\ContainerAwareInterface; 7use Symfony\Component\DependencyInjection\ContainerAwareInterface;
8use Symfony\Component\DependencyInjection\ContainerInterface; 8use Symfony\Component\DependencyInjection\ContainerInterface;
9 9
diff --git a/src/Wallabag/CoreBundle/Entity/Config.php b/src/Wallabag/CoreBundle/Entity/Config.php
index b902ae2c..c6e65d66 100644
--- a/src/Wallabag/CoreBundle/Entity/Config.php
+++ b/src/Wallabag/CoreBundle/Entity/Config.php
@@ -60,21 +60,21 @@ class Config
60 /** 60 /**
61 * @var string 61 * @var string
62 * 62 *
63 * @ORM\Column(name="rss_token", type="string", nullable=true) 63 * @ORM\Column(name="feed_token", type="string", nullable=true)
64 */ 64 */
65 private $rssToken; 65 private $feedToken;
66 66
67 /** 67 /**
68 * @var int 68 * @var int
69 * 69 *
70 * @ORM\Column(name="rss_limit", type="integer", nullable=true) 70 * @ORM\Column(name="feed_limit", type="integer", nullable=true)
71 * @Assert\Range( 71 * @Assert\Range(
72 * min = 1, 72 * min = 1,
73 * max = 100000, 73 * max = 100000,
74 * maxMessage = "validator.rss_limit_too_high" 74 * maxMessage = "validator.feed_limit_too_high"
75 * ) 75 * )
76 */ 76 */
77 private $rssLimit; 77 private $feedLimit;
78 78
79 /** 79 /**
80 * @var float 80 * @var float
@@ -231,51 +231,51 @@ class Config
231 } 231 }
232 232
233 /** 233 /**
234 * Set rssToken. 234 * Set feed Token.
235 * 235 *
236 * @param string $rssToken 236 * @param string $feedToken
237 * 237 *
238 * @return Config 238 * @return Config
239 */ 239 */
240 public function setRssToken($rssToken) 240 public function setFeedToken($feedToken)
241 { 241 {
242 $this->rssToken = $rssToken; 242 $this->feedToken = $feedToken;
243 243
244 return $this; 244 return $this;
245 } 245 }
246 246
247 /** 247 /**
248 * Get rssToken. 248 * Get feedToken.
249 * 249 *
250 * @return string 250 * @return string
251 */ 251 */
252 public function getRssToken() 252 public function getFeedToken()
253 { 253 {
254 return $this->rssToken; 254 return $this->feedToken;
255 } 255 }
256 256
257 /** 257 /**
258 * Set rssLimit. 258 * Set Feed Limit.
259 * 259 *
260 * @param int $rssLimit 260 * @param int $feedLimit
261 * 261 *
262 * @return Config 262 * @return Config
263 */ 263 */
264 public function setRssLimit($rssLimit) 264 public function setFeedLimit($feedLimit)
265 { 265 {
266 $this->rssLimit = $rssLimit; 266 $this->feedLimit = $feedLimit;
267 267
268 return $this; 268 return $this;
269 } 269 }
270 270
271 /** 271 /**
272 * Get rssLimit. 272 * Get Feed Limit.
273 * 273 *
274 * @return int 274 * @return int
275 */ 275 */
276 public function getRssLimit() 276 public function getFeedLimit()
277 { 277 {
278 return $this->rssLimit; 278 return $this->feedLimit;
279 } 279 }
280 280
281 /** 281 /**
diff --git a/src/Wallabag/CoreBundle/Entity/Entry.php b/src/Wallabag/CoreBundle/Entity/Entry.php
index 2b1f2e05..1b4367fd 100644
--- a/src/Wallabag/CoreBundle/Entity/Entry.php
+++ b/src/Wallabag/CoreBundle/Entity/Entry.php
@@ -13,6 +13,7 @@ use JMS\Serializer\Annotation\XmlRoot;
13use Symfony\Component\Validator\Constraints as Assert; 13use Symfony\Component\Validator\Constraints as Assert;
14use Wallabag\AnnotationBundle\Entity\Annotation; 14use Wallabag\AnnotationBundle\Entity\Annotation;
15use Wallabag\CoreBundle\Helper\EntityTimestampsTrait; 15use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
16use Wallabag\CoreBundle\Helper\UrlHasher;
16use Wallabag\UserBundle\Entity\User; 17use Wallabag\UserBundle\Entity\User;
17 18
18/** 19/**
@@ -25,7 +26,8 @@ use Wallabag\UserBundle\Entity\User;
25 * options={"collate"="utf8mb4_unicode_ci", "charset"="utf8mb4"}, 26 * options={"collate"="utf8mb4_unicode_ci", "charset"="utf8mb4"},
26 * indexes={ 27 * indexes={
27 * @ORM\Index(name="created_at", columns={"created_at"}), 28 * @ORM\Index(name="created_at", columns={"created_at"}),
28 * @ORM\Index(name="uid", columns={"uid"}) 29 * @ORM\Index(name="uid", columns={"uid"}),
30 * @ORM\Index(name="hashed_url_user_id", columns={"user_id", "hashed_url"}, options={"lengths"={null, 40}})
29 * } 31 * }
30 * ) 32 * )
31 * @ORM\HasLifecycleCallbacks() 33 * @ORM\HasLifecycleCallbacks()
@@ -76,6 +78,13 @@ class Entry
76 private $url; 78 private $url;
77 79
78 /** 80 /**
81 * @var string
82 *
83 * @ORM\Column(name="hashed_url", type="string", length=40, nullable=true)
84 */
85 private $hashedUrl;
86
87 /**
79 * @var bool 88 * @var bool
80 * 89 *
81 * @Exclude 90 * @Exclude
@@ -87,6 +96,15 @@ class Entry
87 private $isArchived = false; 96 private $isArchived = false;
88 97
89 /** 98 /**
99 * @var \DateTime
100 *
101 * @ORM\Column(name="archived_at", type="datetime", nullable=true)
102 *
103 * @Groups({"entries_for_user", "export_all"})
104 */
105 private $archivedAt = null;
106
107 /**
90 * @var bool 108 * @var bool
91 * 109 *
92 * @Exclude 110 * @Exclude
@@ -307,6 +325,7 @@ class Entry
307 public function setUrl($url) 325 public function setUrl($url)
308 { 326 {
309 $this->url = $url; 327 $this->url = $url;
328 $this->hashedUrl = UrlHasher::hashUrl($url);
310 329
311 return $this; 330 return $this;
312 } 331 }
@@ -336,6 +355,44 @@ class Entry
336 } 355 }
337 356
338 /** 357 /**
358 * update isArchived and archive_at fields.
359 *
360 * @param bool $isArchived
361 *
362 * @return Entry
363 */
364 public function updateArchived($isArchived = false)
365 {
366 $this->setArchived($isArchived);
367 $this->setArchivedAt(null);
368 if ($this->isArchived()) {
369 $this->setArchivedAt(new \DateTime());
370 }
371
372 return $this;
373 }
374
375 /**
376 * @return \DateTime|null
377 */
378 public function getArchivedAt()
379 {
380 return $this->archivedAt;
381 }
382
383 /**
384 * @param \DateTime|null $archivedAt
385 *
386 * @return Entry
387 */
388 public function setArchivedAt($archivedAt = null)
389 {
390 $this->archivedAt = $archivedAt;
391
392 return $this;
393 }
394
395 /**
339 * Get isArchived. 396 * Get isArchived.
340 * 397 *
341 * @return bool 398 * @return bool
@@ -357,7 +414,7 @@ class Entry
357 414
358 public function toggleArchive() 415 public function toggleArchive()
359 { 416 {
360 $this->isArchived = $this->isArchived() ^ 1; 417 $this->updateArchived($this->isArchived() ^ 1);
361 418
362 return $this; 419 return $this;
363 } 420 }
@@ -864,4 +921,24 @@ class Entry
864 { 921 {
865 return $this->originUrl; 922 return $this->originUrl;
866 } 923 }
924
925 /**
926 * @return string
927 */
928 public function getHashedUrl()
929 {
930 return $this->hashedUrl;
931 }
932
933 /**
934 * @param mixed $hashedUrl
935 *
936 * @return Entry
937 */
938 public function setHashedUrl($hashedUrl)
939 {
940 $this->hashedUrl = $hashedUrl;
941
942 return $this;
943 }
867} 944}
diff --git a/src/Wallabag/CoreBundle/Entity/SiteCredential.php b/src/Wallabag/CoreBundle/Entity/SiteCredential.php
index ac714359..dee48fd5 100644
--- a/src/Wallabag/CoreBundle/Entity/SiteCredential.php
+++ b/src/Wallabag/CoreBundle/Entity/SiteCredential.php
@@ -60,6 +60,13 @@ class SiteCredential
60 private $createdAt; 60 private $createdAt;
61 61
62 /** 62 /**
63 * @var \DateTime
64 *
65 * @ORM\Column(name="updated_at", type="datetime")
66 */
67 private $updatedAt;
68
69 /**
63 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="siteCredentials") 70 * @ORM\ManyToOne(targetEntity="Wallabag\UserBundle\Entity\User", inversedBy="siteCredentials")
64 */ 71 */
65 private $user; 72 private $user;
@@ -179,6 +186,16 @@ class SiteCredential
179 } 186 }
180 187
181 /** 188 /**
189 * Get updatedAt.
190 *
191 * @return \DateTime
192 */
193 public function getUpdatedAt()
194 {
195 return $this->updatedAt;
196 }
197
198 /**
182 * @return User 199 * @return User
183 */ 200 */
184 public function getUser() 201 public function getUser()
diff --git a/src/Wallabag/CoreBundle/Entity/TaggingRule.php b/src/Wallabag/CoreBundle/Entity/TaggingRule.php
index 84e11e26..c1be3165 100644
--- a/src/Wallabag/CoreBundle/Entity/TaggingRule.php
+++ b/src/Wallabag/CoreBundle/Entity/TaggingRule.php
@@ -3,7 +3,7 @@
3namespace Wallabag\CoreBundle\Entity; 3namespace Wallabag\CoreBundle\Entity;
4 4
5use Doctrine\ORM\Mapping as ORM; 5use Doctrine\ORM\Mapping as ORM;
6use KPhoen\RulerZBundle\Validator\Constraints as RulerZAssert; 6use Symfony\Bridge\RulerZ\Validator\Constraints as RulerZAssert;
7use Symfony\Component\Validator\Constraints as Assert; 7use Symfony\Component\Validator\Constraints as Assert;
8 8
9/** 9/**
diff --git a/src/Wallabag/CoreBundle/Event/Listener/UserLocaleListener.php b/src/Wallabag/CoreBundle/Event/Listener/UserLocaleListener.php
index 367cdfb0..dc1db5c7 100644
--- a/src/Wallabag/CoreBundle/Event/Listener/UserLocaleListener.php
+++ b/src/Wallabag/CoreBundle/Event/Listener/UserLocaleListener.php
@@ -6,8 +6,10 @@ use Symfony\Component\HttpFoundation\Session\Session;
6use Symfony\Component\Security\Http\Event\InteractiveLoginEvent; 6use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
7 7
8/** 8/**
9 * Stores the locale of the user in the session after the 9 * Stores the locale of the user in the session after the login.
10 * login. This can be used by the LocaleListener afterwards. 10 * If no locale are defined (if user doesn't change it from the login screen), override it with the user's config one.
11 *
12 * This can be used by the LocaleListener afterwards.
11 * 13 *
12 * @see http://symfony.com/doc/master/cookbook/session/locale_sticky_session.html 14 * @see http://symfony.com/doc/master/cookbook/session/locale_sticky_session.html
13 */ 15 */
@@ -30,7 +32,7 @@ class UserLocaleListener
30 { 32 {
31 $user = $event->getAuthenticationToken()->getUser(); 33 $user = $event->getAuthenticationToken()->getUser();
32 34
33 if (null !== $user->getConfig()->getLanguage()) { 35 if (null !== $user->getConfig()->getLanguage() && null === $this->session->get('_locale')) {
34 $this->session->set('_locale', $user->getConfig()->getLanguage()); 36 $this->session->set('_locale', $user->getConfig()->getLanguage());
35 } 37 }
36 } 38 }
diff --git a/src/Wallabag/CoreBundle/Form/Type/EditEntryType.php b/src/Wallabag/CoreBundle/Form/Type/EditEntryType.php
index 08355928..2fc4c204 100644
--- a/src/Wallabag/CoreBundle/Form/Type/EditEntryType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/EditEntryType.php
@@ -22,11 +22,13 @@ class EditEntryType extends AbstractType
22 'disabled' => true, 22 'disabled' => true,
23 'required' => false, 23 'required' => false,
24 'label' => 'entry.edit.url_label', 24 'label' => 'entry.edit.url_label',
25 'default_protocol' => null,
25 ]) 26 ])
26 ->add('origin_url', UrlType::class, [ 27 ->add('origin_url', UrlType::class, [
27 'required' => false, 28 'required' => false,
28 'property_path' => 'originUrl', 29 'property_path' => 'originUrl',
29 'label' => 'entry.edit.origin_url_label', 30 'label' => 'entry.edit.origin_url_label',
31 'default_protocol' => null,
30 ]) 32 ])
31 ->add('save', SubmitType::class, [ 33 ->add('save', SubmitType::class, [
32 'label' => 'entry.edit.save_label', 34 'label' => 'entry.edit.save_label',
diff --git a/src/Wallabag/CoreBundle/Form/Type/RssType.php b/src/Wallabag/CoreBundle/Form/Type/FeedType.php
index 49b31c1e..9b34daf4 100644
--- a/src/Wallabag/CoreBundle/Form/Type/RssType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/FeedType.php
@@ -7,14 +7,14 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType;
7use Symfony\Component\Form\FormBuilderInterface; 7use Symfony\Component\Form\FormBuilderInterface;
8use Symfony\Component\OptionsResolver\OptionsResolver; 8use Symfony\Component\OptionsResolver\OptionsResolver;
9 9
10class RssType extends AbstractType 10class FeedType extends AbstractType
11{ 11{
12 public function buildForm(FormBuilderInterface $builder, array $options) 12 public function buildForm(FormBuilderInterface $builder, array $options)
13 { 13 {
14 $builder 14 $builder
15 ->add('rss_limit', null, [ 15 ->add('feed_limit', null, [
16 'label' => 'config.form_rss.rss_limit', 16 'label' => 'config.form_feed.feed_limit',
17 'property_path' => 'rssLimit', 17 'property_path' => 'feedLimit',
18 ]) 18 ])
19 ->add('save', SubmitType::class, [ 19 ->add('save', SubmitType::class, [
20 'label' => 'config.form.save', 20 'label' => 'config.form.save',
@@ -31,6 +31,6 @@ class RssType extends AbstractType
31 31
32 public function getBlockPrefix() 32 public function getBlockPrefix()
33 { 33 {
34 return 'rss_config'; 34 return 'feed_config';
35 } 35 }
36} 36}
diff --git a/src/Wallabag/CoreBundle/Form/Type/NewEntryType.php b/src/Wallabag/CoreBundle/Form/Type/NewEntryType.php
index 7d74fee3..7af1e589 100644
--- a/src/Wallabag/CoreBundle/Form/Type/NewEntryType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/NewEntryType.php
@@ -15,6 +15,7 @@ class NewEntryType extends AbstractType
15 ->add('url', UrlType::class, [ 15 ->add('url', UrlType::class, [
16 'required' => true, 16 'required' => true,
17 'label' => 'entry.new.form_new.url_label', 17 'label' => 'entry.new.form_new.url_label',
18 'default_protocol' => null,
18 ]) 19 ])
19 ; 20 ;
20 } 21 }
diff --git a/src/Wallabag/CoreBundle/Form/Type/RenameTagType.php b/src/Wallabag/CoreBundle/Form/Type/RenameTagType.php
new file mode 100644
index 00000000..e6270048
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Form/Type/RenameTagType.php
@@ -0,0 +1,35 @@
1<?php
2
3namespace Wallabag\CoreBundle\Form\Type;
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 RenameTagType extends AbstractType
11{
12 public function buildForm(FormBuilderInterface $builder, array $options)
13 {
14 $builder
15 ->add('label', TextType::class, [
16 'required' => true,
17 'attr' => [
18 'placeholder' => 'tag.rename.placeholder',
19 ],
20 ])
21 ;
22 }
23
24 public function configureOptions(OptionsResolver $resolver)
25 {
26 $resolver->setDefaults([
27 'data_class' => 'Wallabag\CoreBundle\Entity\Tag',
28 ]);
29 }
30
31 public function getBlockPrefix()
32 {
33 return 'tag';
34 }
35}
diff --git a/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php b/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php
index 07c99949..6e4c9154 100644
--- a/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php
+++ b/src/Wallabag/CoreBundle/Form/Type/UserInformationType.php
@@ -21,9 +21,14 @@ class UserInformationType extends AbstractType
21 ->add('email', EmailType::class, [ 21 ->add('email', EmailType::class, [
22 'label' => 'config.form_user.email_label', 22 'label' => 'config.form_user.email_label',
23 ]) 23 ])
24 ->add('twoFactorAuthentication', CheckboxType::class, [ 24 ->add('emailTwoFactor', CheckboxType::class, [
25 'required' => false, 25 'required' => false,
26 'label' => 'config.form_user.twoFactorAuthentication_label', 26 'label' => 'config.form_user.emailTwoFactor_label',
27 ])
28 ->add('googleTwoFactor', CheckboxType::class, [
29 'required' => false,
30 'label' => 'config.form_user.googleTwoFactor_label',
31 'mapped' => false,
27 ]) 32 ])
28 ->add('save', SubmitType::class, [ 33 ->add('save', SubmitType::class, [
29 'label' => 'config.form.save', 34 'label' => 'config.form.save',
diff --git a/src/Wallabag/CoreBundle/Helper/ContentProxy.php b/src/Wallabag/CoreBundle/Helper/ContentProxy.php
index bc257ffb..c6fa0d98 100644
--- a/src/Wallabag/CoreBundle/Helper/ContentProxy.php
+++ b/src/Wallabag/CoreBundle/Helper/ContentProxy.php
@@ -12,8 +12,8 @@ use Wallabag\CoreBundle\Entity\Entry;
12use Wallabag\CoreBundle\Tools\Utils; 12use Wallabag\CoreBundle\Tools\Utils;
13 13
14/** 14/**
15 * This kind of proxy class take care of getting the content from an url 15 * This kind of proxy class takes care of getting the content from an url
16 * and update the entry with what it found. 16 * and updates the entry with what it found.
17 */ 17 */
18class ContentProxy 18class ContentProxy
19{ 19{
@@ -54,7 +54,11 @@ class ContentProxy
54 54
55 if ((empty($content) || false === $this->validateContent($content)) && false === $disableContentUpdate) { 55 if ((empty($content) || false === $this->validateContent($content)) && false === $disableContentUpdate) {
56 $fetchedContent = $this->graby->fetchContent($url); 56 $fetchedContent = $this->graby->fetchContent($url);
57 $fetchedContent['title'] = $this->sanitizeContentTitle($fetchedContent['title'], $fetchedContent['content_type']); 57
58 $fetchedContent['title'] = $this->sanitizeContentTitle(
59 $fetchedContent['title'],
60 isset($fetchedContent['headers']['content-type']) ? $fetchedContent['headers']['content-type'] : ''
61 );
58 62
59 // when content is imported, we have information in $content 63 // when content is imported, we have information in $content
60 // in case fetching content goes bad, we'll keep the imported information instead of overriding them 64 // in case fetching content goes bad, we'll keep the imported information instead of overriding them
@@ -188,8 +192,8 @@ class ContentProxy
188 /** 192 /**
189 * Try to sanitize the title of the fetched content from wrong character encodings and invalid UTF-8 character. 193 * Try to sanitize the title of the fetched content from wrong character encodings and invalid UTF-8 character.
190 * 194 *
191 * @param $title 195 * @param string $title
192 * @param $contentType 196 * @param string $contentType
193 * 197 *
194 * @return string 198 * @return string
195 */ 199 */
@@ -253,16 +257,14 @@ class ContentProxy
253 257
254 if (!empty($content['title'])) { 258 if (!empty($content['title'])) {
255 $entry->setTitle($content['title']); 259 $entry->setTitle($content['title']);
256 } elseif (!empty($content['open_graph']['og_title'])) {
257 $entry->setTitle($content['open_graph']['og_title']);
258 } 260 }
259 261
260 if (empty($content['html'])) { 262 if (empty($content['html'])) {
261 $content['html'] = $this->fetchingErrorMessage; 263 $content['html'] = $this->fetchingErrorMessage;
262 264
263 if (!empty($content['open_graph']['og_description'])) { 265 if (!empty($content['description'])) {
264 $content['html'] .= '<p><i>But we found a short description: </i></p>'; 266 $content['html'] .= '<p><i>But we found a short description: </i></p>';
265 $content['html'] .= $content['open_graph']['og_description']; 267 $content['html'] .= $content['description'];
266 } 268 }
267 } 269 }
268 270
@@ -277,8 +279,8 @@ class ContentProxy
277 $entry->setPublishedBy($content['authors']); 279 $entry->setPublishedBy($content['authors']);
278 } 280 }
279 281
280 if (!empty($content['all_headers']) && $this->storeArticleHeaders) { 282 if (!empty($content['headers'])) {
281 $entry->setHeaders($content['all_headers']); 283 $entry->setHeaders($content['headers']);
282 } 284 }
283 285
284 if (!empty($content['date'])) { 286 if (!empty($content['date'])) {
@@ -289,17 +291,30 @@ class ContentProxy
289 $this->updateLanguage($entry, $content['language']); 291 $this->updateLanguage($entry, $content['language']);
290 } 292 }
291 293
292 if (!empty($content['open_graph']['og_image'])) { 294 $previewPictureUrl = '';
293 $this->updatePreviewPicture($entry, $content['open_graph']['og_image']); 295 if (!empty($content['image'])) {
296 $previewPictureUrl = $content['image'];
294 } 297 }
295 298
296 // if content is an image, define it as a preview too 299 // if content is an image, define it as a preview too
297 if (!empty($content['content_type']) && \in_array($this->mimeGuesser->guess($content['content_type']), ['jpeg', 'jpg', 'gif', 'png'], true)) { 300 if (!empty($content['headers']['content-type']) && \in_array($this->mimeGuesser->guess($content['headers']['content-type']), ['jpeg', 'jpg', 'gif', 'png'], true)) {
298 $this->updatePreviewPicture($entry, $content['url']); 301 $previewPictureUrl = $content['url'];
302 } elseif (empty($previewPictureUrl)) {
303 $this->logger->debug('Extracting images from content to provide a default preview picture');
304 $imagesUrls = DownloadImages::extractImagesUrlsFromHtml($content['html']);
305 $this->logger->debug(\count($imagesUrls) . ' pictures found');
306
307 if (!empty($imagesUrls)) {
308 $previewPictureUrl = $imagesUrls[0];
309 }
310 }
311
312 if (!empty($content['headers']['content-type'])) {
313 $entry->setMimetype($content['headers']['content-type']);
299 } 314 }
300 315
301 if (!empty($content['content_type'])) { 316 if (!empty($previewPictureUrl)) {
302 $entry->setMimetype($content['content_type']); 317 $this->updatePreviewPicture($entry, $previewPictureUrl);
303 } 318 }
304 319
305 try { 320 try {
diff --git a/src/Wallabag/CoreBundle/Helper/DownloadImages.php b/src/Wallabag/CoreBundle/Helper/DownloadImages.php
index cc3dcfce..7a39a2e4 100644
--- a/src/Wallabag/CoreBundle/Helper/DownloadImages.php
+++ b/src/Wallabag/CoreBundle/Helper/DownloadImages.php
@@ -2,8 +2,13 @@
2 2
3namespace Wallabag\CoreBundle\Helper; 3namespace Wallabag\CoreBundle\Helper;
4 4
5use GuzzleHttp\Client; 5use Http\Client\Common\HttpMethodsClient;
6use GuzzleHttp\Message\Response; 6use Http\Client\Common\Plugin\ErrorPlugin;
7use Http\Client\Common\PluginClient;
8use Http\Client\HttpClient;
9use Http\Discovery\MessageFactoryDiscovery;
10use Http\Message\MessageFactory;
11use Psr\Http\Message\ResponseInterface;
7use Psr\Log\LoggerInterface; 12use Psr\Log\LoggerInterface;
8use Symfony\Component\DomCrawler\Crawler; 13use Symfony\Component\DomCrawler\Crawler;
9use Symfony\Component\Finder\Finder; 14use Symfony\Component\Finder\Finder;
@@ -19,9 +24,9 @@ class DownloadImages
19 private $mimeGuesser; 24 private $mimeGuesser;
20 private $wallabagUrl; 25 private $wallabagUrl;
21 26
22 public function __construct(Client $client, $baseFolder, $wallabagUrl, LoggerInterface $logger) 27 public function __construct(HttpClient $client, $baseFolder, $wallabagUrl, LoggerInterface $logger, MessageFactory $messageFactory = null)
23 { 28 {
24 $this->client = $client; 29 $this->client = new HttpMethodsClient(new PluginClient($client, [new ErrorPlugin()]), $messageFactory ?: MessageFactoryDiscovery::find());
25 $this->baseFolder = $baseFolder; 30 $this->baseFolder = $baseFolder;
26 $this->wallabagUrl = rtrim($wallabagUrl, '/'); 31 $this->wallabagUrl = rtrim($wallabagUrl, '/');
27 $this->logger = $logger; 32 $this->logger = $logger;
@@ -31,23 +36,36 @@ class DownloadImages
31 } 36 }
32 37
33 /** 38 /**
34 * Process the html and extract image from it, save them to local and return the updated html. 39 * Process the html and extract images URLs from it.
35 * 40 *
36 * @param int $entryId ID of the entry
37 * @param string $html 41 * @param string $html
38 * @param string $url Used as a base path for relative image and folder
39 * 42 *
40 * @return string 43 * @return string[]
41 */ 44 */
42 public function processHtml($entryId, $html, $url) 45 public static function extractImagesUrlsFromHtml($html)
43 { 46 {
44 $crawler = new Crawler($html); 47 $crawler = new Crawler($html);
45 $imagesCrawler = $crawler 48 $imagesCrawler = $crawler
46 ->filterXpath('//img'); 49 ->filterXpath('//img');
47 $imagesUrls = $imagesCrawler 50 $imagesUrls = $imagesCrawler
48 ->extract(['src']); 51 ->extract(['src']);
49 $imagesSrcsetUrls = $this->getSrcsetUrls($imagesCrawler); 52 $imagesSrcsetUrls = self::getSrcsetUrls($imagesCrawler);
50 $imagesUrls = array_unique(array_merge($imagesUrls, $imagesSrcsetUrls)); 53
54 return array_unique(array_merge($imagesUrls, $imagesSrcsetUrls));
55 }
56
57 /**
58 * Process the html and extract image from it, save them to local and return the updated html.
59 *
60 * @param int $entryId ID of the entry
61 * @param string $html
62 * @param string $url Used as a base path for relative image and folder
63 *
64 * @return string
65 */
66 public function processHtml($entryId, $html, $url)
67 {
68 $imagesUrls = self::extractImagesUrlsFromHtml($html);
51 69
52 $relativePath = $this->getRelativePath($entryId); 70 $relativePath = $this->getRelativePath($entryId);
53 71
@@ -122,7 +140,7 @@ class DownloadImages
122 $localPath = $folderPath . '/' . $hashImage . '.' . $ext; 140 $localPath = $folderPath . '/' . $hashImage . '.' . $ext;
123 141
124 try { 142 try {
125 $im = imagecreatefromstring($res->getBody()); 143 $im = imagecreatefromstring((string) $res->getBody());
126 } catch (\Exception $e) { 144 } catch (\Exception $e) {
127 $im = false; 145 $im = false;
128 } 146 }
@@ -135,7 +153,21 @@ class DownloadImages
135 153
136 switch ($ext) { 154 switch ($ext) {
137 case 'gif': 155 case 'gif':
138 imagegif($im, $localPath); 156 // use Imagick if available to keep GIF animation
157 if (class_exists('\\Imagick')) {
158 try {
159 $imagick = new \Imagick();
160 $imagick->readImageBlob($res->getBody());
161 $imagick->setImageFormat('gif');
162 $imagick->writeImages($localPath, true);
163 } catch (\Exception $e) {
164 // if Imagick fail, fallback to the default solution
165 imagegif($im, $localPath);
166 }
167 } else {
168 imagegif($im, $localPath);
169 }
170
139 $this->logger->debug('DownloadImages: Re-creating gif'); 171 $this->logger->debug('DownloadImages: Re-creating gif');
140 break; 172 break;
141 case 'jpeg': 173 case 'jpeg':
@@ -185,7 +217,7 @@ class DownloadImages
185 * 217 *
186 * @return array An array of urls 218 * @return array An array of urls
187 */ 219 */
188 private function getSrcsetUrls(Crawler $imagesCrawler) 220 private static function getSrcsetUrls(Crawler $imagesCrawler)
189 { 221 {
190 $urls = []; 222 $urls = [];
191 $iterator = $imagesCrawler 223 $iterator = $imagesCrawler
@@ -279,14 +311,14 @@ class DownloadImages
279 /** 311 /**
280 * Retrieve and validate the extension from the response of the url of the image. 312 * Retrieve and validate the extension from the response of the url of the image.
281 * 313 *
282 * @param Response $res Guzzle Response 314 * @param ResponseInterface $res Http Response
283 * @param string $imagePath Path from the src image from the content (used for log only) 315 * @param string $imagePath Path from the src image from the content (used for log only)
284 * 316 *
285 * @return string|false Extension name or false if validation failed 317 * @return string|false Extension name or false if validation failed
286 */ 318 */
287 private function getExtensionFromResponse(Response $res, $imagePath) 319 private function getExtensionFromResponse(ResponseInterface $res, $imagePath)
288 { 320 {
289 $ext = $this->mimeGuesser->guess($res->getHeader('content-type')); 321 $ext = $this->mimeGuesser->guess(current($res->getHeader('content-type')));
290 $this->logger->debug('DownloadImages: Checking extension', ['ext' => $ext, 'header' => $res->getHeader('content-type')]); 322 $this->logger->debug('DownloadImages: Checking extension', ['ext' => $ext, 'header' => $res->getHeader('content-type')]);
291 323
292 // ok header doesn't have the extension, try a different way 324 // ok header doesn't have the extension, try a different way
diff --git a/src/Wallabag/CoreBundle/Helper/HttpClientFactory.php b/src/Wallabag/CoreBundle/Helper/HttpClientFactory.php
index 4602a684..b8e95381 100644
--- a/src/Wallabag/CoreBundle/Helper/HttpClientFactory.php
+++ b/src/Wallabag/CoreBundle/Helper/HttpClientFactory.php
@@ -2,16 +2,18 @@
2 2
3namespace Wallabag\CoreBundle\Helper; 3namespace Wallabag\CoreBundle\Helper;
4 4
5use Graby\Ring\Client\SafeCurlHandler; 5use GuzzleHttp\Client as GuzzleClient;
6use GuzzleHttp\Client;
7use GuzzleHttp\Cookie\CookieJar; 6use GuzzleHttp\Cookie\CookieJar;
8use GuzzleHttp\Event\SubscriberInterface; 7use GuzzleHttp\Event\SubscriberInterface;
8use Http\Adapter\Guzzle5\Client as GuzzleAdapter;
9use Http\Client\HttpClient;
10use Http\HttplugBundle\ClientFactory\ClientFactory;
9use Psr\Log\LoggerInterface; 11use Psr\Log\LoggerInterface;
10 12
11/** 13/**
12 * Builds and configures the Guzzle HTTP client. 14 * Builds and configures the HTTP client.
13 */ 15 */
14class HttpClientFactory 16class HttpClientFactory implements ClientFactory
15{ 17{
16 /** @var [\GuzzleHttp\Event\SubscriberInterface] */ 18 /** @var [\GuzzleHttp\Event\SubscriberInterface] */
17 private $subscribers = []; 19 private $subscribers = [];
@@ -37,35 +39,42 @@ class HttpClientFactory
37 } 39 }
38 40
39 /** 41 /**
40 * @return \GuzzleHttp\Client|null 42 * Adds a subscriber to the HTTP client.
43 *
44 * @param SubscriberInterface $subscriber
45 */
46 public function addSubscriber(SubscriberInterface $subscriber)
47 {
48 $this->subscribers[] = $subscriber;
49 }
50
51 /**
52 * Input an array of configuration to be able to create a HttpClient.
53 *
54 * @param array $config
55 *
56 * @return HttpClient
41 */ 57 */
42 public function buildHttpClient() 58 public function createClient(array $config = [])
43 { 59 {
44 $this->logger->log('debug', 'Restricted access config enabled?', ['enabled' => (int) $this->restrictedAccess]); 60 $this->logger->log('debug', 'Restricted access config enabled?', ['enabled' => (int) $this->restrictedAccess]);
45 61
46 if (0 === (int) $this->restrictedAccess) { 62 if (0 === (int) $this->restrictedAccess) {
47 return; 63 return new GuzzleAdapter(new GuzzleClient($config));
48 } 64 }
49 65
50 // we clear the cookie to avoid websites who use cookies for analytics 66 // we clear the cookie to avoid websites who use cookies for analytics
51 $this->cookieJar->clear(); 67 $this->cookieJar->clear();
52 // need to set the (shared) cookie jar 68 if (!isset($config['defaults']['cookies'])) {
53 $client = new Client(['handler' => new SafeCurlHandler(), 'defaults' => ['cookies' => $this->cookieJar]]); 69 // need to set the (shared) cookie jar
70 $config['defaults']['cookies'] = $this->cookieJar;
71 }
54 72
73 $guzzle = new GuzzleClient($config);
55 foreach ($this->subscribers as $subscriber) { 74 foreach ($this->subscribers as $subscriber) {
56 $client->getEmitter()->attach($subscriber); 75 $guzzle->getEmitter()->attach($subscriber);
57 } 76 }
58 77
59 return $client; 78 return new GuzzleAdapter($guzzle);
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 } 79 }
71} 80}
diff --git a/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php b/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php
index 183d394a..04abc6d0 100644
--- a/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php
+++ b/src/Wallabag/CoreBundle/Helper/PreparePagerForEntries.php
@@ -21,7 +21,7 @@ class PreparePagerForEntries
21 21
22 /** 22 /**
23 * @param AdapterInterface $adapter 23 * @param AdapterInterface $adapter
24 * @param User $user If user isn't logged in, we can force it (like for rss) 24 * @param User $user If user isn't logged in, we can force it (like for feed)
25 * 25 *
26 * @return Pagerfanta|null 26 * @return Pagerfanta|null
27 */ 27 */
diff --git a/src/Wallabag/CoreBundle/Helper/RuleBasedTagger.php b/src/Wallabag/CoreBundle/Helper/RuleBasedTagger.php
index 63f65067..fbdf2ac7 100644
--- a/src/Wallabag/CoreBundle/Helper/RuleBasedTagger.php
+++ b/src/Wallabag/CoreBundle/Helper/RuleBasedTagger.php
@@ -6,6 +6,7 @@ use Psr\Log\LoggerInterface;
6use RulerZ\RulerZ; 6use RulerZ\RulerZ;
7use Wallabag\CoreBundle\Entity\Entry; 7use Wallabag\CoreBundle\Entity\Entry;
8use Wallabag\CoreBundle\Entity\Tag; 8use Wallabag\CoreBundle\Entity\Tag;
9use Wallabag\CoreBundle\Entity\TaggingRule;
9use Wallabag\CoreBundle\Repository\EntryRepository; 10use Wallabag\CoreBundle\Repository\EntryRepository;
10use Wallabag\CoreBundle\Repository\TagRepository; 11use Wallabag\CoreBundle\Repository\TagRepository;
11use Wallabag\UserBundle\Entity\User; 12use Wallabag\UserBundle\Entity\User;
diff --git a/src/Wallabag/CoreBundle/Helper/UrlHasher.php b/src/Wallabag/CoreBundle/Helper/UrlHasher.php
new file mode 100644
index 00000000..d123eaba
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Helper/UrlHasher.php
@@ -0,0 +1,23 @@
1<?php
2
3namespace Wallabag\CoreBundle\Helper;
4
5/**
6 * Hash URLs for privacy and performance.
7 */
8class UrlHasher
9{
10 /**
11 * Hash the given url using the given algorithm.
12 * Hashed url are faster to be retrieved in the database than the real url.
13 *
14 * @param string $url
15 * @param string $algorithm
16 *
17 * @return string
18 */
19 public static function hashUrl(string $url, $algorithm = 'sha1')
20 {
21 return hash($algorithm, urldecode($url));
22 }
23}
diff --git a/src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php b/src/Wallabag/CoreBundle/ParamConverter/UsernameFeedTokenConverter.php
index 4a2fcab5..e220abfc 100644
--- a/src/Wallabag/CoreBundle/ParamConverter/UsernameRssTokenConverter.php
+++ b/src/Wallabag/CoreBundle/ParamConverter/UsernameFeedTokenConverter.php
@@ -10,12 +10,12 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
10use Wallabag\UserBundle\Entity\User; 10use Wallabag\UserBundle\Entity\User;
11 11
12/** 12/**
13 * ParamConverter used in the RSS controller to retrieve the right user according to 13 * ParamConverter used in the Feed controller to retrieve the right user according to
14 * username & token given in the url. 14 * username & token given in the url.
15 * 15 *
16 * @see http://stfalcon.com/en/blog/post/symfony2-custom-paramconverter 16 * @see http://stfalcon.com/en/blog/post/symfony2-custom-paramconverter
17 */ 17 */
18class UsernameRssTokenConverter implements ParamConverterInterface 18class UsernameFeedTokenConverter implements ParamConverterInterface
19{ 19{
20 private $registry; 20 private $registry;
21 21
@@ -67,7 +67,7 @@ class UsernameRssTokenConverter implements ParamConverterInterface
67 public function apply(Request $request, ParamConverter $configuration) 67 public function apply(Request $request, ParamConverter $configuration)
68 { 68 {
69 $username = $request->attributes->get('username'); 69 $username = $request->attributes->get('username');
70 $rssToken = $request->attributes->get('token'); 70 $feedToken = $request->attributes->get('token');
71 71
72 if (!$request->attributes->has('username') || !$request->attributes->has('token')) { 72 if (!$request->attributes->has('username') || !$request->attributes->has('token')) {
73 return false; 73 return false;
@@ -78,8 +78,8 @@ class UsernameRssTokenConverter implements ParamConverterInterface
78 78
79 $userRepository = $em->getRepository($configuration->getClass()); 79 $userRepository = $em->getRepository($configuration->getClass());
80 80
81 // Try to find user by its username and config rss_token 81 // Try to find user by its username and config feed_token
82 $user = $userRepository->findOneByUsernameAndRsstoken($username, $rssToken); 82 $user = $userRepository->findOneByUsernameAndFeedtoken($username, $feedToken);
83 83
84 if (null === $user || !($user instanceof User)) { 84 if (null === $user || !($user instanceof User)) {
85 throw new NotFoundHttpException(sprintf('%s not found.', $configuration->getClass())); 85 throw new NotFoundHttpException(sprintf('%s not found.', $configuration->getClass()));
diff --git a/src/Wallabag/CoreBundle/Repository/EntryRepository.php b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
index cebce714..880e7c65 100644
--- a/src/Wallabag/CoreBundle/Repository/EntryRepository.php
+++ b/src/Wallabag/CoreBundle/Repository/EntryRepository.php
@@ -3,11 +3,13 @@
3namespace Wallabag\CoreBundle\Repository; 3namespace Wallabag\CoreBundle\Repository;
4 4
5use Doctrine\ORM\EntityRepository; 5use Doctrine\ORM\EntityRepository;
6use Doctrine\ORM\NoResultException;
6use Doctrine\ORM\QueryBuilder; 7use Doctrine\ORM\QueryBuilder;
7use Pagerfanta\Adapter\DoctrineORMAdapter; 8use Pagerfanta\Adapter\DoctrineORMAdapter;
8use Pagerfanta\Pagerfanta; 9use Pagerfanta\Pagerfanta;
9use Wallabag\CoreBundle\Entity\Entry; 10use Wallabag\CoreBundle\Entity\Entry;
10use Wallabag\CoreBundle\Entity\Tag; 11use Wallabag\CoreBundle\Entity\Tag;
12use Wallabag\CoreBundle\Helper\UrlHasher;
11 13
12class EntryRepository extends EntityRepository 14class EntryRepository extends EntityRepository
13{ 15{
@@ -50,7 +52,7 @@ class EntryRepository extends EntityRepository
50 public function getBuilderForArchiveByUser($userId) 52 public function getBuilderForArchiveByUser($userId)
51 { 53 {
52 return $this 54 return $this
53 ->getSortedQueryBuilderByUser($userId) 55 ->getSortedQueryBuilderByUser($userId, 'archivedAt', 'desc')
54 ->andWhere('e.isArchived = true') 56 ->andWhere('e.isArchived = true')
55 ; 57 ;
56 } 58 }
@@ -110,8 +112,7 @@ class EntryRepository extends EntityRepository
110 */ 112 */
111 public function getBuilderForUntaggedByUser($userId) 113 public function getBuilderForUntaggedByUser($userId)
112 { 114 {
113 return $this 115 return $this->sortQueryBuilder($this->getRawBuilderForUntaggedByUser($userId));
114 ->sortQueryBuilder($this->getRawBuilderForUntaggedByUser($userId));
115 } 116 }
116 117
117 /** 118 /**
@@ -139,15 +140,30 @@ class EntryRepository extends EntityRepository
139 * @param string $order 140 * @param string $order
140 * @param int $since 141 * @param int $since
141 * @param string $tags 142 * @param string $tags
143 * @param string $detail 'metadata' or 'full'. Include content field if 'full'
144 *
145 * @todo Breaking change: replace default detail=full by detail=metadata in a future version
142 * 146 *
143 * @return Pagerfanta 147 * @return Pagerfanta
144 */ 148 */
145 public function findEntries($userId, $isArchived = null, $isStarred = null, $isPublic = null, $sort = 'created', $order = 'asc', $since = 0, $tags = '') 149 public function findEntries($userId, $isArchived = null, $isStarred = null, $isPublic = null, $sort = 'created', $order = 'asc', $since = 0, $tags = '', $detail = 'full')
146 { 150 {
151 if (!\in_array(strtolower($detail), ['full', 'metadata'], true)) {
152 throw new \Exception('Detail "' . $detail . '" parameter is wrong, allowed: full or metadata');
153 }
154
147 $qb = $this->createQueryBuilder('e') 155 $qb = $this->createQueryBuilder('e')
148 ->leftJoin('e.tags', 't') 156 ->leftJoin('e.tags', 't')
149 ->where('e.user = :userId')->setParameter('userId', $userId); 157 ->where('e.user = :userId')->setParameter('userId', $userId);
150 158
159 if ('metadata' === $detail) {
160 $fieldNames = $this->getClassMetadata()->getFieldNames();
161 $fields = array_filter($fieldNames, function ($k) {
162 return 'content' !== $k;
163 });
164 $qb->select(sprintf('partial e.{%s}', implode(',', $fields)));
165 }
166
151 if (null !== $isArchived) { 167 if (null !== $isArchived) {
152 $qb->andWhere('e.isArchived = :isArchived')->setParameter('isArchived', (bool) $isArchived); 168 $qb->andWhere('e.isArchived = :isArchived')->setParameter('isArchived', (bool) $isArchived);
153 } 169 }
@@ -193,6 +209,8 @@ class EntryRepository extends EntityRepository
193 $qb->orderBy('e.id', $order); 209 $qb->orderBy('e.id', $order);
194 } elseif ('updated' === $sort) { 210 } elseif ('updated' === $sort) {
195 $qb->orderBy('e.updatedAt', $order); 211 $qb->orderBy('e.updatedAt', $order);
212 } elseif ('archived' === $sort) {
213 $qb->orderBy('e.archivedAt', $order);
196 } 214 }
197 215
198 $pagerAdapter = new DoctrineORMAdapter($qb, true, false); 216 $pagerAdapter = new DoctrineORMAdapter($qb, true, false);
@@ -324,15 +342,32 @@ class EntryRepository extends EntityRepository
324 * Find an entry by its url and its owner. 342 * Find an entry by its url and its owner.
325 * If it exists, return the entry otherwise return false. 343 * If it exists, return the entry otherwise return false.
326 * 344 *
327 * @param $url 345 * @param string $url
328 * @param $userId 346 * @param int $userId
329 * 347 *
330 * @return Entry|bool 348 * @return Entry|bool
331 */ 349 */
332 public function findByUrlAndUserId($url, $userId) 350 public function findByUrlAndUserId($url, $userId)
333 { 351 {
352 return $this->findByHashedUrlAndUserId(
353 UrlHasher::hashUrl($url),
354 $userId
355 );
356 }
357
358 /**
359 * Find an entry by its hashed url and its owner.
360 * If it exists, return the entry otherwise return false.
361 *
362 * @param string $hashedUrl Url hashed using sha1
363 * @param int $userId
364 *
365 * @return Entry|bool
366 */
367 public function findByHashedUrlAndUserId($hashedUrl, $userId)
368 {
334 $res = $this->createQueryBuilder('e') 369 $res = $this->createQueryBuilder('e')
335 ->where('e.url = :url')->setParameter('url', urldecode($url)) 370 ->where('e.hashedUrl = :hashed_url')->setParameter('hashed_url', $hashedUrl)
336 ->andWhere('e.user = :user_id')->setParameter('user_id', $userId) 371 ->andWhere('e.user = :user_id')->setParameter('user_id', $userId)
337 ->getQuery() 372 ->getQuery()
338 ->getResult(); 373 ->getResult();
@@ -416,8 +451,8 @@ class EntryRepository extends EntityRepository
416 /** 451 /**
417 * Find all entries by url and owner. 452 * Find all entries by url and owner.
418 * 453 *
419 * @param $url 454 * @param string $url
420 * @param $userId 455 * @param int $userId
421 * 456 *
422 * @return array 457 * @return array
423 */ 458 */
@@ -431,6 +466,49 @@ class EntryRepository extends EntityRepository
431 } 466 }
432 467
433 /** 468 /**
469 * Returns a random entry, filtering by status.
470 *
471 * @param int $userId
472 * @param string $type Can be unread, archive, starred, etc
473 *
474 * @throws NoResultException
475 *
476 * @return Entry
477 */
478 public function getRandomEntry($userId, $type = '')
479 {
480 $qb = $this->getQueryBuilderByUser($userId)
481 ->select('e.id');
482
483 switch ($type) {
484 case 'unread':
485 $qb->andWhere('e.isArchived = false');
486 break;
487 case 'archive':
488 $qb->andWhere('e.isArchived = true');
489 break;
490 case 'starred':
491 $qb->andWhere('e.isStarred = true');
492 break;
493 case 'untagged':
494 $qb->leftJoin('e.tags', 't');
495 $qb->andWhere('t.id is null');
496 break;
497 }
498
499 $ids = $qb->getQuery()->getArrayResult();
500
501 if (empty($ids)) {
502 throw new NoResultException();
503 }
504
505 // random select one in the list
506 $randomId = $ids[mt_rand(0, \count($ids) - 1)]['id'];
507
508 return $this->find($randomId);
509 }
510
511 /**
434 * Return a query builder to be used by other getBuilderFor* method. 512 * Return a query builder to be used by other getBuilderFor* method.
435 * 513 *
436 * @param int $userId 514 * @param int $userId
@@ -468,7 +546,6 @@ class EntryRepository extends EntityRepository
468 */ 546 */
469 private function sortQueryBuilder(QueryBuilder $qb, $sortBy = 'createdAt', $direction = 'desc') 547 private function sortQueryBuilder(QueryBuilder $qb, $sortBy = 'createdAt', $direction = 'desc')
470 { 548 {
471 return $qb 549 return $qb->orderBy(sprintf('e.%s', $sortBy), $direction);
472 ->orderBy(sprintf('e.%s', $sortBy), $direction);
473 } 550 }
474} 551}
diff --git a/src/Wallabag/CoreBundle/Resources/config/services.yml b/src/Wallabag/CoreBundle/Resources/config/services.yml
index 85306276..31986951 100644
--- a/src/Wallabag/CoreBundle/Resources/config/services.yml
+++ b/src/Wallabag/CoreBundle/Resources/config/services.yml
@@ -22,10 +22,10 @@ services:
22 tags: 22 tags:
23 - { name: form.type } 23 - { name: form.type }
24 24
25 wallabag_core.param_converter.username_rsstoken_converter: 25 wallabag_core.param_converter.username_feed_token_converter:
26 class: Wallabag\CoreBundle\ParamConverter\UsernameRssTokenConverter 26 class: Wallabag\CoreBundle\ParamConverter\UsernameFeedTokenConverter
27 tags: 27 tags:
28 - { name: request.param_converter, converter: username_rsstoken_converter } 28 - { name: request.param_converter, converter: username_feed_token_converter }
29 arguments: 29 arguments:
30 - "@doctrine" 30 - "@doctrine"
31 31
@@ -42,7 +42,7 @@ services:
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 error_message_title: '%wallabag_core.fetching_error_message_title%'
45 - "@wallabag_core.guzzle.http_client" 45 - "@wallabag_core.http_client"
46 - "@wallabag_core.graby.config_builder" 46 - "@wallabag_core.graby.config_builder"
47 calls: 47 calls:
48 - [ setLogger, [ "@logger" ] ] 48 - [ setLogger, [ "@logger" ] ]
@@ -55,9 +55,8 @@ services:
55 - {} 55 - {}
56 - "@logger" 56 - "@logger"
57 57
58 wallabag_core.guzzle.http_client: 58 wallabag_core.http_client:
59 class: GuzzleHttp\ClientInterface 59 alias: 'httplug.client.wallabag_core'
60 factory: ["@wallabag_core.guzzle.http_client_factory", buildHttpClient]
61 60
62 wallabag_core.guzzle_authenticator.config_builder: 61 wallabag_core.guzzle_authenticator.config_builder:
63 class: Wallabag\CoreBundle\GuzzleSiteAuthenticator\GrabySiteConfigBuilder 62 class: Wallabag\CoreBundle\GuzzleSiteAuthenticator\GrabySiteConfigBuilder
@@ -73,7 +72,7 @@ services:
73 bd_guzzle_site_authenticator.site_config_builder: 72 bd_guzzle_site_authenticator.site_config_builder:
74 alias: wallabag_core.guzzle_authenticator.config_builder 73 alias: wallabag_core.guzzle_authenticator.config_builder
75 74
76 wallabag_core.guzzle.http_client_factory: 75 wallabag_core.http_client_factory:
77 class: Wallabag\CoreBundle\Helper\HttpClientFactory 76 class: Wallabag\CoreBundle\Helper\HttpClientFactory
78 arguments: 77 arguments:
79 - "@wallabag_core.guzzle.cookie_jar" 78 - "@wallabag_core.guzzle.cookie_jar"
@@ -181,6 +180,7 @@ services:
181 180
182 wallabag_core.exception_controller: 181 wallabag_core.exception_controller:
183 class: Wallabag\CoreBundle\Controller\ExceptionController 182 class: Wallabag\CoreBundle\Controller\ExceptionController
183 public: true
184 arguments: 184 arguments:
185 - '@twig' 185 - '@twig'
186 - '%kernel.debug%' 186 - '%kernel.debug%'
@@ -211,10 +211,38 @@ services:
211 - "@logger" 211 - "@logger"
212 212
213 wallabag_core.entry.download_images.client: 213 wallabag_core.entry.download_images.client:
214 class: GuzzleHttp\Client 214 alias: 'httplug.client.wallabag_core.entry.download_images'
215 215
216 wallabag_core.helper.crypto_proxy: 216 wallabag_core.helper.crypto_proxy:
217 class: Wallabag\CoreBundle\Helper\CryptoProxy 217 class: Wallabag\CoreBundle\Helper\CryptoProxy
218 arguments: 218 arguments:
219 - "%wallabag_core.site_credentials.encryption_key_path%" 219 - "%wallabag_core.site_credentials.encryption_key_path%"
220 - "@logger" 220 - "@logger"
221
222 wallabag_core.command.clean_duplicates:
223 class: Wallabag\CoreBundle\Command\CleanDuplicatesCommand
224 tags: ['console.command']
225
226 wallabag_core.command.export:
227 class: Wallabag\CoreBundle\Command\ExportCommand
228 tags: ['console.command']
229
230 wallabag_core.command.install:
231 class: Wallabag\CoreBundle\Command\InstallCommand
232 tags: ['console.command']
233
234 wallabag_core.command.list_user:
235 class: Wallabag\CoreBundle\Command\ListUserCommand
236 tags: ['console.command']
237
238 wallabag_core.command.reload_entry:
239 class: Wallabag\CoreBundle\Command\ReloadEntryCommand
240 tags: ['console.command']
241
242 wallabag_core.command.show_user:
243 class: Wallabag\CoreBundle\Command\ShowUserCommand
244 tags: ['console.command']
245
246 wallabag_core.command.tag_all:
247 class: Wallabag\CoreBundle\Command\TagAllCommand
248 tags: ['console.command']
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
index 6f842534..e04c2ff1 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.da.yml
@@ -37,6 +37,7 @@ menu:
37 add_new_entry: 'Tilføj ny artikel' 37 add_new_entry: 'Tilføj ny artikel'
38 search: 'Søg' 38 search: 'Søg'
39 filter_entries: 'Filtrer artikler' 39 filter_entries: 'Filtrer artikler'
40 # random_entry: Jump to a random entry from that list
40 # export: 'Export' 41 # export: 'Export'
41 search_form: 42 search_form:
42 input_label: 'Indtast søgning' 43 input_label: 'Indtast søgning'
@@ -53,11 +54,12 @@ config:
53 page_title: 'Opsætning' 54 page_title: 'Opsætning'
54 tab_menu: 55 tab_menu:
55 settings: 'Indstillinger' 56 settings: 'Indstillinger'
56 rss: 'RSS' 57 feed: 'RSS'
57 user_info: 'Brugeroplysninger' 58 user_info: 'Brugeroplysninger'
58 password: 'Adgangskode' 59 password: 'Adgangskode'
59 # rules: 'Tagging rules' 60 # rules: 'Tagging rules'
60 new_user: 'Tilføj bruger' 61 new_user: 'Tilføj bruger'
62 # reset: 'Reset area'
61 form: 63 form:
62 save: 'Gem' 64 save: 'Gem'
63 form_settings: 65 form_settings:
@@ -83,25 +85,33 @@ config:
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." 85 # 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."
84 # help_language: "You can change the language of wallabag interface." 86 # help_language: "You can change the language of wallabag interface."
85 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account." 87 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
86 form_rss: 88 form_feed:
87 description: 'RSS-feeds fra wallabag gør det muligt at læse de artikler, der gemmes i wallabag, med din RSS-læser. Det kræver, at du genererer et token først.' 89 description: 'RSS-feeds fra wallabag gør det muligt at læse de artikler, der gemmes i wallabag, med din RSS-læser. Det kræver, at du genererer et token først.'
88 token_label: 'RSS-Token' 90 token_label: 'RSS-Token'
89 no_token: 'Intet token' 91 no_token: 'Intet token'
90 token_create: 'Opret token' 92 token_create: 'Opret token'
91 token_reset: 'Nulstil token' 93 token_reset: 'Nulstil token'
92 rss_links: 'RSS-Links' 94 feed_links: 'RSS-Links'
93 rss_link: 95 feed_link:
94 unread: 'Ulæst' 96 unread: 'Ulæst'
95 starred: 'Favoritter' 97 starred: 'Favoritter'
96 archive: 'Arkiv' 98 archive: 'Arkiv'
97 # all: 'All' 99 # all: 'All'
98 # rss_limit: 'Number of items in the feed' 100 # feed_limit: 'Number of items in the feed'
99 form_user: 101 form_user:
100 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion" 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
101 name_label: 'Navn' 103 name_label: 'Navn'
102 email_label: 'Emailadresse' 104 email_label: 'Emailadresse'
103 # twoFactorAuthentication_label: 'Two factor authentication' 105 two_factor:
104 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 106 # emailTwoFactor_label: 'Using email (receive a code by email)'
107 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
108 # table_method: Method
109 # table_state: State
110 # table_action: Action
111 # state_enabled: Enabled
112 # state_disabled: Disabled
113 # action_email: Use email
114 # action_app: Use OTP App
105 delete: 115 delete:
106 # title: Delete my account (a.k.a danger zone) 116 # title: Delete my account (a.k.a danger zone)
107 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. 117 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@@ -159,6 +169,15 @@ config:
159 # and: 'One rule AND another' 169 # and: 'One rule AND another'
160 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 170 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
161 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 171 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
172 otp:
173 # page_title: Two-factor authentication
174 # app:
175 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
176 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
177 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
178 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
179 # cancel: Cancel
180 # enable: Enable
162 181
163entry: 182entry:
164 # default_title: 'Title of the entry' 183 # default_title: 'Title of the entry'
@@ -353,7 +372,7 @@ quickstart:
353 # title: 'Configure the application' 372 # title: 'Configure the application'
354 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' 373 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
355 # language: 'Change language and design' 374 # language: 'Change language and design'
356 # rss: 'Enable RSS feeds' 375 # feed: 'Enable RSS feeds'
357 # tagging_rules: 'Write rules to automatically tag your articles' 376 # tagging_rules: 'Write rules to automatically tag your articles'
358 # admin: 377 # admin:
359 # title: 'Administration' 378 # title: 'Administration'
@@ -404,6 +423,8 @@ tag:
404 new: 423 new:
405 # add: 'Add' 424 # add: 'Add'
406 # placeholder: 'You can add several tags, separated by a comma.' 425 # placeholder: 'You can add several tags, separated by a comma.'
426 rename:
427 # placeholder: 'You can update tag name.'
407 428
408# export: 429# export:
409# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>' 430# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@@ -529,7 +550,8 @@ user:
529 email_label: 'Emailadresse' 550 email_label: 'Emailadresse'
530 # enabled_label: 'Enabled' 551 # enabled_label: 'Enabled'
531 # last_login_label: 'Last login' 552 # last_login_label: 'Last login'
532 # twofactor_label: Two factor authentication 553 # twofactor_email_label: Two factor authentication by email
554 # twofactor_google_label: Two factor authentication by OTP app
533 # save: Save 555 # save: Save
534 # delete: Delete 556 # delete: Delete
535 # delete_confirm: Are you sure? 557 # delete_confirm: Are you sure?
@@ -567,10 +589,10 @@ flashes:
567 password_updated: 'Adgangskode opdateret' 589 password_updated: 'Adgangskode opdateret'
568 # password_not_updated_demo: "In demonstration mode, you can't change password for this user." 590 # password_not_updated_demo: "In demonstration mode, you can't change password for this user."
569 user_updated: 'Oplysninger opdateret' 591 user_updated: 'Oplysninger opdateret'
570 rss_updated: 'RSS-oplysninger opdateret' 592 feed_updated: 'RSS-oplysninger opdateret'
571 # tagging_rules_updated: 'Tagging rules updated' 593 # tagging_rules_updated: 'Tagging rules updated'
572 # tagging_rules_deleted: 'Tagging rule deleted' 594 # tagging_rules_deleted: 'Tagging rule deleted'
573 # rss_token_updated: 'RSS token updated' 595 # feed_token_updated: 'RSS token updated'
574 # annotations_reset: Annotations reset 596 # annotations_reset: Annotations reset
575 # tags_reset: Tags reset 597 # tags_reset: Tags reset
576 # entries_reset: Entries reset 598 # entries_reset: Entries reset
@@ -588,9 +610,11 @@ flashes:
588 entry_starred: 'Artikel markeret som favorit' 610 entry_starred: 'Artikel markeret som favorit'
589 entry_unstarred: 'Artikel ikke længere markeret som favorit' 611 entry_unstarred: 'Artikel ikke længere markeret som favorit'
590 entry_deleted: 'Artikel slettet' 612 entry_deleted: 'Artikel slettet'
613 # no_random_entry: 'No article with these criterias was found'
591 tag: 614 tag:
592 notice: 615 notice:
593 # tag_added: 'Tag added' 616 # tag_added: 'Tag added'
617 # tag_renamed: 'Tag renamed'
594 import: 618 import:
595 notice: 619 notice:
596 # failed: 'Import failed, please try again.' 620 # failed: 'Import failed, please try again.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
index 874908b9..5a9668a9 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.de.yml
@@ -37,6 +37,7 @@ menu:
37 add_new_entry: 'Neuen Artikel hinzufügen' 37 add_new_entry: 'Neuen Artikel hinzufügen'
38 search: 'Suche' 38 search: 'Suche'
39 filter_entries: 'Artikel filtern' 39 filter_entries: 'Artikel filtern'
40 # random_entry: Jump to a random entry from that list
40 export: 'Exportieren' 41 export: 'Exportieren'
41 search_form: 42 search_form:
42 input_label: 'Suchbegriff hier eingeben' 43 input_label: 'Suchbegriff hier eingeben'
@@ -53,11 +54,12 @@ config:
53 page_title: 'Einstellungen' 54 page_title: 'Einstellungen'
54 tab_menu: 55 tab_menu:
55 settings: 'Einstellungen' 56 settings: 'Einstellungen'
56 rss: 'RSS' 57 feed: 'RSS'
57 user_info: 'Benutzerinformation' 58 user_info: 'Benutzerinformation'
58 password: 'Kennwort' 59 password: 'Kennwort'
59 rules: 'Tagging-Regeln' 60 rules: 'Tagging-Regeln'
60 new_user: 'Benutzer hinzufügen' 61 new_user: 'Benutzer hinzufügen'
62 reset: 'Zurücksetzen'
61 form: 63 form:
62 save: 'Speichern' 64 save: 'Speichern'
63 form_settings: 65 form_settings:
@@ -83,25 +85,33 @@ config:
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." 85 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."
84 help_language: "Du kannst die Sprache der wallabag-Oberfläche ändern." 86 help_language: "Du kannst die Sprache der wallabag-Oberfläche ändern."
85 help_pocket_consumer_key: "Nötig für den Pocket-Import. Du kannst ihn in deinem Pocket account einrichten." 87 help_pocket_consumer_key: "Nötig für den Pocket-Import. Du kannst ihn in deinem Pocket account einrichten."
86 form_rss: 88 form_feed:
87 description: 'Die RSS-Feeds von wallabag erlauben es dir, deine gespeicherten Artikel mit deinem bevorzugten RSS-Reader zu lesen. Vorher musst du jedoch einen Token erstellen.' 89 description: 'Die RSS-Feeds von wallabag erlauben es dir, deine gespeicherten Artikel mit deinem bevorzugten RSS-Reader zu lesen. Vorher musst du jedoch einen Token erstellen.'
88 token_label: 'RSS-Token' 90 token_label: 'RSS-Token'
89 no_token: 'Kein Token' 91 no_token: 'Kein Token'
90 token_create: 'Token erstellen' 92 token_create: 'Token erstellen'
91 token_reset: 'Token zurücksetzen' 93 token_reset: 'Token zurücksetzen'
92 rss_links: 'RSS-Links' 94 feed_links: 'RSS-Links'
93 rss_link: 95 feed_link:
94 unread: 'Ungelesene' 96 unread: 'Ungelesene'
95 starred: 'Favoriten' 97 starred: 'Favoriten'
96 archive: 'Archivierte' 98 archive: 'Archivierte'
97 all: 'Alle' 99 all: 'Alle'
98 rss_limit: 'Anzahl der Einträge pro Feed' 100 feed_limit: 'Anzahl der Einträge pro Feed'
99 form_user: 101 form_user:
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" 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
101 name_label: 'Name' 103 name_label: 'Name'
102 email_label: 'E-Mail-Adresse' 104 email_label: 'E-Mail-Adresse'
103 twoFactorAuthentication_label: 'Zwei-Faktor-Authentifizierung' 105 two_factor:
104 help_twoFactorAuthentication: "Wenn du 2FA aktivierst, wirst du bei jedem Login einen Code per E-Mail bekommen." 106 # emailTwoFactor_label: 'Using email (receive a code by email)'
107 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
108 # table_method: Method
109 # table_state: State
110 # table_action: Action
111 # state_enabled: Enabled
112 # state_disabled: Disabled
113 # action_email: Use email
114 # action_app: Use OTP App
105 delete: 115 delete:
106 title: 'Lösche mein Konto (a.k.a Gefahrenzone)' 116 title: 'Lösche mein Konto (a.k.a Gefahrenzone)'
107 description: 'Wenn du dein Konto löschst, werden ALL deine Artikel, ALL deine Tags, ALL deine Anmerkungen und dein Konto dauerhaft gelöscht (kann NICHT RÜCKGÄNGIG gemacht werden). Du wirst anschließend ausgeloggt.' 117 description: 'Wenn du dein Konto löschst, werden ALL deine Artikel, ALL deine Tags, ALL deine Anmerkungen und dein Konto dauerhaft gelöscht (kann NICHT RÜCKGÄNGIG gemacht werden). Du wirst anschließend ausgeloggt.'
@@ -353,7 +363,7 @@ quickstart:
353 title: 'Anwendung konfigurieren' 363 title: 'Anwendung konfigurieren'
354 description: 'Um die Applikation für dich anzupassen, schau in die Konfiguration von wallabag.' 364 description: 'Um die Applikation für dich anzupassen, schau in die Konfiguration von wallabag.'
355 language: 'Sprache und Design ändern' 365 language: 'Sprache und Design ändern'
356 rss: 'RSS-Feeds aktivieren' 366 feed: 'RSS-Feeds aktivieren'
357 tagging_rules: 'Schreibe Regeln, um deine Beiträge automatisch zu taggen (verschlagworten)' 367 tagging_rules: 'Schreibe Regeln, um deine Beiträge automatisch zu taggen (verschlagworten)'
358 admin: 368 admin:
359 title: 'Administration' 369 title: 'Administration'
@@ -404,6 +414,8 @@ tag:
404 new: 414 new:
405 add: 'Hinzufügen' 415 add: 'Hinzufügen'
406 placeholder: 'Du kannst verschiedene Tags, getrennt von einem Komma, hinzufügen.' 416 placeholder: 'Du kannst verschiedene Tags, getrennt von einem Komma, hinzufügen.'
417 rename:
418 # placeholder: 'You can update tag name.'
407 419
408export: 420export:
409 footer_template: '<div style="text-align:center;"><p>Generiert von wallabag mit Hilfe von %method%</p><p>Bitte öffne <a href="https://github.com/wallabag/wallabag/issues">ein Ticket</a> wenn du ein Problem mit der Darstellung von diesem E-Book auf deinem Gerät hast.</p></div>' 421 footer_template: '<div style="text-align:center;"><p>Generiert von wallabag mit Hilfe von %method%</p><p>Bitte öffne <a href="https://github.com/wallabag/wallabag/issues">ein Ticket</a> wenn du ein Problem mit der Darstellung von diesem E-Book auf deinem Gerät hast.</p></div>'
@@ -529,7 +541,8 @@ user:
529 email_label: 'E-Mail-Adresse' 541 email_label: 'E-Mail-Adresse'
530 enabled_label: 'Aktiviert' 542 enabled_label: 'Aktiviert'
531 last_login_label: 'Letzter Login' 543 last_login_label: 'Letzter Login'
532 twofactor_label: 'Zwei-Faktor-Authentifizierung' 544 # twofactor_email_label: Two factor authentication by email
545 # twofactor_google_label: Two factor authentication by OTP app
533 save: 'Speichern' 546 save: 'Speichern'
534 delete: 'Löschen' 547 delete: 'Löschen'
535 delete_confirm: 'Bist du sicher?' 548 delete_confirm: 'Bist du sicher?'
@@ -567,14 +580,14 @@ flashes:
567 password_updated: 'Kennwort aktualisiert' 580 password_updated: 'Kennwort aktualisiert'
568 password_not_updated_demo: 'Im Testmodus kannst du das Kennwort nicht ändern.' 581 password_not_updated_demo: 'Im Testmodus kannst du das Kennwort nicht ändern.'
569 user_updated: 'Information aktualisiert' 582 user_updated: 'Information aktualisiert'
570 rss_updated: 'RSS-Informationen aktualisiert' 583 feed_updated: 'RSS-Informationen aktualisiert'
571 tagging_rules_updated: 'Tagging-Regeln aktualisiert' 584 tagging_rules_updated: 'Tagging-Regeln aktualisiert'
572 tagging_rules_deleted: 'Tagging-Regel gelöscht' 585 tagging_rules_deleted: 'Tagging-Regel gelöscht'
573 rss_token_updated: 'RSS-Token aktualisiert' 586 feed_token_updated: 'RSS-Token aktualisiert'
574 annotations_reset: 'Anmerkungen zurücksetzen' 587 annotations_reset: Anmerkungen zurücksetzen
575 tags_reset: 'Tags zurücksetzen' 588 tags_reset: Tags zurücksetzen
576 entries_reset: 'Einträge zurücksetzen' 589 entries_reset: Einträge zurücksetzen
577 archived_reset: 'Archiverte Einträge zurücksetzen' 590 archived_reset: Archiverte Einträge zurücksetzen
578 entry: 591 entry:
579 notice: 592 notice:
580 entry_already_saved: 'Eintrag bereits am %date% gespeichert' 593 entry_already_saved: 'Eintrag bereits am %date% gespeichert'
@@ -588,9 +601,11 @@ flashes:
588 entry_starred: 'Eintrag favorisiert' 601 entry_starred: 'Eintrag favorisiert'
589 entry_unstarred: 'Eintrag defavorisiert' 602 entry_unstarred: 'Eintrag defavorisiert'
590 entry_deleted: 'Eintrag gelöscht' 603 entry_deleted: 'Eintrag gelöscht'
604 # no_random_entry: 'No article with these criterias was found'
591 tag: 605 tag:
592 notice: 606 notice:
593 tag_added: 'Tag hinzugefügt' 607 tag_added: 'Tag hinzugefügt'
608 #tag_renamed: 'Tag renamed'
594 import: 609 import:
595 notice: 610 notice:
596 failed: 'Import fehlgeschlagen, bitte erneut probieren.' 611 failed: 'Import fehlgeschlagen, bitte erneut probieren.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
index 598ad58d..e2994f53 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.en.yml
@@ -37,6 +37,7 @@ menu:
37 add_new_entry: 'Add a new entry' 37 add_new_entry: 'Add a new entry'
38 search: 'Search' 38 search: 'Search'
39 filter_entries: 'Filter entries' 39 filter_entries: 'Filter entries'
40 random_entry: Jump to a random entry from that list
40 export: 'Export' 41 export: 'Export'
41 search_form: 42 search_form:
42 input_label: 'Enter your search here' 43 input_label: 'Enter your search here'
@@ -53,11 +54,12 @@ config:
53 page_title: 'Config' 54 page_title: 'Config'
54 tab_menu: 55 tab_menu:
55 settings: 'Settings' 56 settings: 'Settings'
56 rss: 'RSS' 57 feed: 'Feeds'
57 user_info: 'User information' 58 user_info: 'User information'
58 password: 'Password' 59 password: 'Password'
59 rules: 'Tagging rules' 60 rules: 'Tagging rules'
60 new_user: 'Add a user' 61 new_user: 'Add a user'
62 reset: 'Reset area'
61 form: 63 form:
62 save: 'Save' 64 save: 'Save'
63 form_settings: 65 form_settings:
@@ -83,25 +85,33 @@ config:
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." 85 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."
84 help_language: "You can change the language of wallabag interface." 86 help_language: "You can change the language of wallabag interface."
85 help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account." 87 help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
86 form_rss: 88 form_feed:
87 description: 'RSS feeds provided by wallabag allow you to read your saved articles with your favourite RSS reader. You need to generate a token first.' 89 description: 'Atom feeds provided by wallabag allow you to read your saved articles with your favourite Atom reader. You need to generate a token first.'
88 token_label: 'RSS token' 90 token_label: 'Feed token'
89 no_token: 'No token' 91 no_token: 'No token'
90 token_create: 'Create your token' 92 token_create: 'Create your token'
91 token_reset: 'Regenerate your token' 93 token_reset: 'Regenerate your token'
92 rss_links: 'RSS links' 94 feed_links: 'Feed links'
93 rss_link: 95 feed_link:
94 unread: 'Unread' 96 unread: 'Unread'
95 starred: 'Starred' 97 starred: 'Starred'
96 archive: 'Archived' 98 archive: 'Archived'
97 all: 'All' 99 all: 'All'
98 rss_limit: 'Number of items in the feed' 100 feed_limit: 'Number of items in the feed'
99 form_user: 101 form_user:
100 two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connection." 102 two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
101 name_label: 'Name' 103 name_label: 'Name'
102 email_label: 'Email' 104 email_label: 'Email'
103 twoFactorAuthentication_label: 'Two factor authentication' 105 two_factor:
104 help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 106 emailTwoFactor_label: 'Using email (receive a code by email)'
107 googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
108 table_method: Method
109 table_state: State
110 table_action: Action
111 state_enabled: Enabled
112 state_disabled: Disabled
113 action_email: Use email
114 action_app: Use OTP App
105 delete: 115 delete:
106 title: Delete my account (a.k.a danger zone) 116 title: Delete my account (a.k.a danger zone)
107 description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. 117 description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@@ -159,6 +169,15 @@ config:
159 and: 'One rule AND another' 169 and: 'One rule AND another'
160 matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 170 matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
161 notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 171 notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
172 otp:
173 page_title: Two-factor authentication
174 app:
175 two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
176 two_factor_code_description_2: 'You can scan that QR Code with your app:'
177 two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
178 two_factor_code_description_4: 'Test an OTP code from your configured app:'
179 cancel: Cancel
180 enable: Enable
162 181
163entry: 182entry:
164 default_title: 'Title of the entry' 183 default_title: 'Title of the entry'
@@ -353,7 +372,7 @@ quickstart:
353 title: 'Configure the application' 372 title: 'Configure the application'
354 description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' 373 description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
355 language: 'Change language and design' 374 language: 'Change language and design'
356 rss: 'Enable RSS feeds' 375 feed: 'Enable feeds'
357 tagging_rules: 'Write rules to automatically tag your articles' 376 tagging_rules: 'Write rules to automatically tag your articles'
358 admin: 377 admin:
359 title: 'Administration' 378 title: 'Administration'
@@ -404,6 +423,8 @@ tag:
404 new: 423 new:
405 add: 'Add' 424 add: 'Add'
406 placeholder: 'You can add several tags, separated by a comma.' 425 placeholder: 'You can add several tags, separated by a comma.'
426 rename:
427 placeholder: 'You can update tag name.'
407 428
408export: 429export:
409 footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>' 430 footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@@ -529,7 +550,8 @@ user:
529 email_label: 'Email' 550 email_label: 'Email'
530 enabled_label: 'Enabled' 551 enabled_label: 'Enabled'
531 last_login_label: 'Last login' 552 last_login_label: 'Last login'
532 twofactor_label: Two factor authentication 553 twofactor_email_label: Two factor authentication by email
554 twofactor_google_label: Two factor authentication by OTP app
533 save: Save 555 save: Save
534 delete: Delete 556 delete: Delete
535 delete_confirm: Are you sure? 557 delete_confirm: Are you sure?
@@ -567,14 +589,15 @@ flashes:
567 password_updated: 'Password updated' 589 password_updated: 'Password updated'
568 password_not_updated_demo: "In demonstration mode, you can't change password for this user." 590 password_not_updated_demo: "In demonstration mode, you can't change password for this user."
569 user_updated: 'Information updated' 591 user_updated: 'Information updated'
570 rss_updated: 'RSS information updated' 592 feed_updated: 'Feed information updated'
571 tagging_rules_updated: 'Tagging rules updated' 593 tagging_rules_updated: 'Tagging rules updated'
572 tagging_rules_deleted: 'Tagging rule deleted' 594 tagging_rules_deleted: 'Tagging rule deleted'
573 rss_token_updated: 'RSS token updated' 595 feed_token_updated: 'Feed token updated'
574 annotations_reset: Annotations reset 596 annotations_reset: Annotations reset
575 tags_reset: Tags reset 597 tags_reset: Tags reset
576 entries_reset: Entries reset 598 entries_reset: Entries reset
577 archived_reset: Archived entries deleted 599 archived_reset: Archived entries deleted
600 otp_enabled: Two-factor authentication enabled
578 entry: 601 entry:
579 notice: 602 notice:
580 entry_already_saved: 'Entry already saved on %date%' 603 entry_already_saved: 'Entry already saved on %date%'
@@ -588,9 +611,11 @@ flashes:
588 entry_starred: 'Entry starred' 611 entry_starred: 'Entry starred'
589 entry_unstarred: 'Entry unstarred' 612 entry_unstarred: 'Entry unstarred'
590 entry_deleted: 'Entry deleted' 613 entry_deleted: 'Entry deleted'
614 no_random_entry: 'No article with these criterias was found'
591 tag: 615 tag:
592 notice: 616 notice:
593 tag_added: 'Tag added' 617 tag_added: 'Tag added'
618 tag_renamed: 'Tag renamed'
594 import: 619 import:
595 notice: 620 notice:
596 failed: 'Import failed, please try again.' 621 failed: 'Import failed, please try again.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
index f8aa4109..d1ccfc81 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.es.yml
@@ -37,6 +37,7 @@ menu:
37 add_new_entry: 'Añadir un nuevo artículo' 37 add_new_entry: 'Añadir un nuevo artículo'
38 search: 'Buscar' 38 search: 'Buscar'
39 filter_entries: 'Filtrar los artículos' 39 filter_entries: 'Filtrar los artículos'
40 # random_entry: Jump to a random entry from that list
40 export: 'Exportar' 41 export: 'Exportar'
41 search_form: 42 search_form:
42 input_label: 'Introduzca su búsqueda aquí' 43 input_label: 'Introduzca su búsqueda aquí'
@@ -53,11 +54,12 @@ config:
53 page_title: 'Configuración' 54 page_title: 'Configuración'
54 tab_menu: 55 tab_menu:
55 settings: 'Configuración' 56 settings: 'Configuración'
56 rss: 'RSS' 57 feed: 'RSS'
57 user_info: 'Información de usuario' 58 user_info: 'Información de usuario'
58 password: 'Contraseña' 59 password: 'Contraseña'
59 rules: 'Reglas de etiquetado automáticas' 60 rules: 'Reglas de etiquetado automáticas'
60 new_user: 'Añadir un usuario' 61 new_user: 'Añadir un usuario'
62 reset: 'Reiniciar mi cuenta'
61 form: 63 form:
62 save: 'Guardar' 64 save: 'Guardar'
63 form_settings: 65 form_settings:
@@ -83,25 +85,33 @@ config:
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." 85 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."
84 help_language: "Puedes cambiar el idioma de la interfaz de wallabag." 86 help_language: "Puedes cambiar el idioma de la interfaz de wallabag."
85 help_pocket_consumer_key: "Requerido para la importación desde Pocket. Puedes crearla en tu cuenta de Pocket." 87 help_pocket_consumer_key: "Requerido para la importación desde Pocket. Puedes crearla en tu cuenta de Pocket."
86 form_rss: 88 form_feed:
87 description: 'Los feeds RSS de wallabag permiten leer los artículos guardados con su lector RSS favorito. Primero necesitas generar un token.' 89 description: 'Los feeds RSS de wallabag permiten leer los artículos guardados con su lector RSS favorito. Primero necesitas generar un token.'
88 token_label: 'Token RSS' 90 token_label: 'Token RSS'
89 no_token: 'Sin token' 91 no_token: 'Sin token'
90 token_create: 'Crear token' 92 token_create: 'Crear token'
91 token_reset: 'Reiniciar token' 93 token_reset: 'Reiniciar token'
92 rss_links: 'URLs de feeds RSS' 94 feed_links: 'URLs de feeds RSS'
93 rss_link: 95 feed_link:
94 unread: 'sin leer' 96 unread: 'sin leer'
95 starred: 'favoritos' 97 starred: 'favoritos'
96 archive: 'archivados' 98 archive: 'archivados'
97 # all: 'All' 99 # all: 'All'
98 rss_limit: 'Límite de artículos en feed RSS' 100 feed_limit: 'Límite de artículos en feed RSS'
99 form_user: 101 form_user:
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." 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
101 name_label: 'Nombre' 103 name_label: 'Nombre'
102 email_label: 'Dirección de e-mail' 104 email_label: 'Dirección de e-mail'
103 twoFactorAuthentication_label: 'Autenticación en dos pasos' 105 two_factor:
104 help_twoFactorAuthentication: "Si activas la autenticación en dos pasos, cada vez que quieras iniciar sesión en wallabag recibirás un código por e-mail." 106 # emailTwoFactor_label: 'Using email (receive a code by email)'
107 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
108 # table_method: Method
109 # table_state: State
110 # table_action: Action
111 # state_enabled: Enabled
112 # state_disabled: Disabled
113 # action_email: Use email
114 # action_app: Use OTP App
105 delete: 115 delete:
106 title: Eliminar mi cuenta (Zona peligrosa) 116 title: Eliminar mi cuenta (Zona peligrosa)
107 description: Si eliminas tu cuenta, TODOS tus artículos, TODAS tus etiquetas, TODAS tus anotaciones y tu cuenta serán eliminadas de forma PERMANENTE (no se puede deshacer). Después serás desconectado. 117 description: Si eliminas tu cuenta, TODOS tus artículos, TODAS tus etiquetas, TODAS tus anotaciones y tu cuenta serán eliminadas de forma PERMANENTE (no se puede deshacer). Después serás desconectado.
@@ -159,6 +169,15 @@ config:
159 and: 'Una regla Y la otra' 169 and: 'Una regla Y la otra'
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>' 170 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> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 171 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
172 otp:
173 # page_title: Two-factor authentication
174 # app:
175 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
176 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
177 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
178 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
179 # cancel: Cancel
180 # enable: Enable
162 181
163entry: 182entry:
164 default_title: 'Título del artículo' 183 default_title: 'Título del artículo'
@@ -353,7 +372,7 @@ quickstart:
353 title: 'Configure la aplicación' 372 title: 'Configure la aplicación'
354 description: 'Para que la aplicación se ajuste a tus necesidades, echa un vistazo a la configuración de wallabag.' 373 description: 'Para que la aplicación se ajuste a tus necesidades, echa un vistazo a la configuración de wallabag.'
355 language: 'Cambie el idioma y el diseño' 374 language: 'Cambie el idioma y el diseño'
356 rss: 'Activar los feeds RSS' 375 feed: 'Activar los feeds RSS'
357 tagging_rules: 'Escribe reglas para etiquetar automáticamente tus artículos' 376 tagging_rules: 'Escribe reglas para etiquetar automáticamente tus artículos'
358 admin: 377 admin:
359 title: 'Administración' 378 title: 'Administración'
@@ -404,6 +423,8 @@ tag:
404 new: 423 new:
405 add: 'Añadir' 424 add: 'Añadir'
406 placeholder: 'Puedes añadir varias etiquetas, separadas por una coma.' 425 placeholder: 'Puedes añadir varias etiquetas, separadas por una coma.'
426 rename:
427 # placeholder: 'You can update tag name.'
407 428
408# export: 429# export:
409# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>' 430# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@@ -529,7 +550,8 @@ user:
529 email_label: 'E-mail' 550 email_label: 'E-mail'
530 enabled_label: 'Activado' 551 enabled_label: 'Activado'
531 last_login_label: 'Último inicio de sesión' 552 last_login_label: 'Último inicio de sesión'
532 twofactor_label: Autenticación en dos pasos 553 # twofactor_email_label: Two factor authentication by email
554 # twofactor_google_label: Two factor authentication by OTP app
533 save: Guardar 555 save: Guardar
534 delete: Eliminar 556 delete: Eliminar
535 delete_confirm: ¿Estás seguro? 557 delete_confirm: ¿Estás seguro?
@@ -567,10 +589,10 @@ flashes:
567 password_updated: 'Contraseña actualizada' 589 password_updated: 'Contraseña actualizada'
568 password_not_updated_demo: "En el modo demo, no puede cambiar la contraseña del usuario." 590 password_not_updated_demo: "En el modo demo, no puede cambiar la contraseña del usuario."
569 user_updated: 'Información actualizada' 591 user_updated: 'Información actualizada'
570 rss_updated: 'Configuración RSS actualizada' 592 feed_updated: 'Configuración RSS actualizada'
571 tagging_rules_updated: 'Regla de etiquetado actualizada' 593 tagging_rules_updated: 'Regla de etiquetado actualizada'
572 tagging_rules_deleted: 'Regla de etiquetado eliminada' 594 tagging_rules_deleted: 'Regla de etiquetado eliminada'
573 rss_token_updated: 'Token RSS actualizado' 595 feed_token_updated: 'Token RSS actualizado'
574 annotations_reset: Anotaciones reiniciadas 596 annotations_reset: Anotaciones reiniciadas
575 tags_reset: Etiquetas reiniciadas 597 tags_reset: Etiquetas reiniciadas
576 entries_reset: Artículos reiniciados 598 entries_reset: Artículos reiniciados
@@ -588,9 +610,11 @@ flashes:
588 entry_starred: 'Artículo marcado como favorito' 610 entry_starred: 'Artículo marcado como favorito'
589 entry_unstarred: 'Artículo desmarcado como favorito' 611 entry_unstarred: 'Artículo desmarcado como favorito'
590 entry_deleted: 'Artículo eliminado' 612 entry_deleted: 'Artículo eliminado'
613 # no_random_entry: 'No article with these criterias was found'
591 tag: 614 tag:
592 notice: 615 notice:
593 tag_added: 'Etiqueta añadida' 616 tag_added: 'Etiqueta añadida'
617 # tag_renamed: 'Tag renamed'
594 import: 618 import:
595 notice: 619 notice:
596 failed: 'Importación fallida, por favor, inténtelo de nuevo.' 620 failed: 'Importación fallida, por favor, inténtelo de nuevo.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
index 785e39ee..e5d36bd3 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fa.yml
@@ -37,6 +37,7 @@ menu:
37 add_new_entry: 'افزودن مقالهٔ تازه' 37 add_new_entry: 'افزودن مقالهٔ تازه'
38 search: 'جستجو' 38 search: 'جستجو'
39 filter_entries: 'فیلترکردن مقاله‌ها' 39 filter_entries: 'فیلترکردن مقاله‌ها'
40 # random_entry: Jump to a random entry from that list
40 export: 'برون‌بری' 41 export: 'برون‌بری'
41 search_form: 42 search_form:
42 input_label: 'جستجوی خود را این‌جا بنویسید:' 43 input_label: 'جستجوی خود را این‌جا بنویسید:'
@@ -53,11 +54,12 @@ config:
53 page_title: 'پیکربندی' 54 page_title: 'پیکربندی'
54 tab_menu: 55 tab_menu:
55 settings: 'تنظیمات' 56 settings: 'تنظیمات'
56 rss: 'آر-اس-اس' 57 feed: 'آر-اس-اس'
57 user_info: 'اطلاعات کاربر' 58 user_info: 'اطلاعات کاربر'
58 password: 'رمز' 59 password: 'رمز'
59 rules: 'برچسب‌گذاری خودکار' 60 rules: 'برچسب‌گذاری خودکار'
60 new_user: 'افزودن کاربر' 61 new_user: 'افزودن کاربر'
62 # reset: 'Reset area'
61 form: 63 form:
62 save: 'ذخیره' 64 save: 'ذخیره'
63 form_settings: 65 form_settings:
@@ -83,25 +85,33 @@ config:
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." 85 # 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."
84 # help_language: "You can change the language of wallabag interface." 86 # help_language: "You can change the language of wallabag interface."
85 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account." 87 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
86 form_rss: 88 form_feed:
87 description: 'با خوراک آر-اس-اس که wallabag در اختیارتان می‌گذارد، می‌توانید مقاله‌های ذخیره‌شده را در نرم‌افزار آر-اس-اس دلخواه خود بخوانید. برای این کار نخست باید یک کد بسازید.' 89 description: 'با خوراک آر-اس-اس که wallabag در اختیارتان می‌گذارد، می‌توانید مقاله‌های ذخیره‌شده را در نرم‌افزار آر-اس-اس دلخواه خود بخوانید. برای این کار نخست باید یک کد بسازید.'
88 token_label: 'کد آر-اس-اس' 90 token_label: 'کد آر-اس-اس'
89 no_token: 'بدون کد' 91 no_token: 'بدون کد'
90 token_create: 'کد خود را بسازید' 92 token_create: 'کد خود را بسازید'
91 token_reset: 'بازنشانی کد' 93 token_reset: 'بازنشانی کد'
92 rss_links: 'پیوند آر-اس-اس' 94 feed_links: 'پیوند آر-اس-اس'
93 rss_link: 95 feed_link:
94 unread: 'خوانده‌نشده' 96 unread: 'خوانده‌نشده'
95 starred: 'برگزیده' 97 starred: 'برگزیده'
96 archive: 'بایگانی' 98 archive: 'بایگانی'
97 # all: 'All' 99 # all: 'All'
98 rss_limit: 'محدودیت آر-اس-اس' 100 feed_limit: 'محدودیت آر-اس-اس'
99 form_user: 101 form_user:
100 two_factor_description: "با فعال‌کردن تأیید ۲مرحله‌ای هر بار که اتصال تأییدنشده‌ای برقرار شد، به شما یک کد از راه ایمیل فرستاده می‌شود" 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
101 name_label: 'نام' 103 name_label: 'نام'
102 email_label: 'نشانی ایمیل' 104 email_label: 'نشانی ایمیل'
103 twoFactorAuthentication_label: 'تأیید ۲مرحله‌ای' 105 two_factor:
104 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 106 # emailTwoFactor_label: 'Using email (receive a code by email)'
107 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
108 # table_method: Method
109 # table_state: State
110 # table_action: Action
111 # state_enabled: Enabled
112 # state_disabled: Disabled
113 # action_email: Use email
114 # action_app: Use OTP App
105 delete: 115 delete:
106 # title: Delete my account (a.k.a danger zone) 116 # title: Delete my account (a.k.a danger zone)
107 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. 117 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@@ -159,6 +169,15 @@ config:
159 # and: 'One rule AND another' 169 # and: 'One rule AND another'
160 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 170 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
161 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 171 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
172 otp:
173 # page_title: Two-factor authentication
174 # app:
175 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
176 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
177 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
178 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
179 # cancel: Cancel
180 # enable: Enable
162 181
163entry: 182entry:
164 # default_title: 'Title of the entry' 183 # default_title: 'Title of the entry'
@@ -353,7 +372,7 @@ quickstart:
353 title: 'برنامه را تنظیم کنید' 372 title: 'برنامه را تنظیم کنید'
354 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' 373 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
355 language: 'زبان و نمای برنامه را تغییر دهید' 374 language: 'زبان و نمای برنامه را تغییر دهید'
356 rss: 'خوراک آر-اس-اس را فعال کنید' 375 feed: 'خوراک آر-اس-اس را فعال کنید'
357 tagging_rules: 'قانون‌های برچسب‌گذاری خودکار مقاله‌هایتان را تعریف کنید' 376 tagging_rules: 'قانون‌های برچسب‌گذاری خودکار مقاله‌هایتان را تعریف کنید'
358 admin: 377 admin:
359 title: 'مدیریت' 378 title: 'مدیریت'
@@ -404,6 +423,8 @@ tag:
404 new: 423 new:
405 # add: 'Add' 424 # add: 'Add'
406 # placeholder: 'You can add several tags, separated by a comma.' 425 # placeholder: 'You can add several tags, separated by a comma.'
426 rename:
427 # placeholder: 'You can update tag name.'
407 428
408# export: 429# export:
409# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>' 430# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@@ -529,7 +550,8 @@ user:
529 email_label: 'نشانی ایمیل' 550 email_label: 'نشانی ایمیل'
530 # enabled_label: 'Enabled' 551 # enabled_label: 'Enabled'
531 # last_login_label: 'Last login' 552 # last_login_label: 'Last login'
532 # twofactor_label: Two factor authentication 553 # twofactor_email_label: Two factor authentication by email
554 # twofactor_google_label: Two factor authentication by OTP app
533 # save: Save 555 # save: Save
534 # delete: Delete 556 # delete: Delete
535 # delete_confirm: Are you sure? 557 # delete_confirm: Are you sure?
@@ -567,10 +589,10 @@ flashes:
567 password_updated: 'رمز به‌روز شد' 589 password_updated: 'رمز به‌روز شد'
568 password_not_updated_demo: "در حالت نمایشی نمی‌توانید رمز کاربر را عوض کنید." 590 password_not_updated_demo: "در حالت نمایشی نمی‌توانید رمز کاربر را عوض کنید."
569 user_updated: 'اطلاعات به‌روز شد' 591 user_updated: 'اطلاعات به‌روز شد'
570 rss_updated: 'اطلاعات آر-اس-اس به‌روز شد' 592 feed_updated: 'اطلاعات آر-اس-اس به‌روز شد'
571 tagging_rules_updated: 'برچسب‌گذاری خودکار به‌روز شد' 593 tagging_rules_updated: 'برچسب‌گذاری خودکار به‌روز شد'
572 tagging_rules_deleted: 'قانون برچسب‌گذاری پاک شد' 594 tagging_rules_deleted: 'قانون برچسب‌گذاری پاک شد'
573 rss_token_updated: 'کد آر-اس-اس به‌روز شد' 595 feed_token_updated: 'کد آر-اس-اس به‌روز شد'
574 # annotations_reset: Annotations reset 596 # annotations_reset: Annotations reset
575 # tags_reset: Tags reset 597 # tags_reset: Tags reset
576 # entries_reset: Entries reset 598 # entries_reset: Entries reset
@@ -588,9 +610,11 @@ flashes:
588 entry_starred: 'مقاله برگزیده شد' 610 entry_starred: 'مقاله برگزیده شد'
589 entry_unstarred: 'مقاله نابرگزیده شد' 611 entry_unstarred: 'مقاله نابرگزیده شد'
590 entry_deleted: 'مقاله پاک شد' 612 entry_deleted: 'مقاله پاک شد'
613 # no_random_entry: 'No article with these criterias was found'
591 tag: 614 tag:
592 notice: 615 notice:
593 tag_added: 'برچسب افزوده شد' 616 tag_added: 'برچسب افزوده شد'
617 # tag_renamed: 'Tag renamed'
594 import: 618 import:
595 notice: 619 notice:
596 failed: 'درون‌ریزی شکست خورد. لطفاً دوباره تلاش کنید.' 620 failed: 'درون‌ریزی شکست خورد. لطفاً دوباره تلاش کنید.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
index b2fa1c50..0b1853a4 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.fr.yml
@@ -37,6 +37,7 @@ menu:
37 add_new_entry: "Sauvegarder un nouvel article" 37 add_new_entry: "Sauvegarder un nouvel article"
38 search: "Rechercher" 38 search: "Rechercher"
39 filter_entries: "Filtrer les articles" 39 filter_entries: "Filtrer les articles"
40 random_entry: Aller à un article aléatoire de cette liste
40 export: "Exporter" 41 export: "Exporter"
41 search_form: 42 search_form:
42 input_label: "Saisissez votre terme de recherche" 43 input_label: "Saisissez votre terme de recherche"
@@ -53,11 +54,12 @@ config:
53 page_title: "Configuration" 54 page_title: "Configuration"
54 tab_menu: 55 tab_menu:
55 settings: "Paramètres" 56 settings: "Paramètres"
56 rss: "RSS" 57 feed: "Flux"
57 user_info: "Mon compte" 58 user_info: "Mon compte"
58 password: "Mot de passe" 59 password: "Mot de passe"
59 rules: "Règles de tag automatiques" 60 rules: "Règles de tag automatiques"
60 new_user: "Créer un compte" 61 new_user: "Créer un compte"
62 reset: "Réinitialisation"
61 form: 63 form:
62 save: "Enregistrer" 64 save: "Enregistrer"
63 form_settings: 65 form_settings:
@@ -83,25 +85,33 @@ config:
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." 85 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."
84 help_language: "Vous pouvez définir la langue de l’interface de wallabag." 86 help_language: "Vous pouvez définir la langue de l’interface de wallabag."
85 help_pocket_consumer_key: "Nécessaire pour l’import depuis Pocket. Vous pouvez le créer depuis votre compte Pocket." 87 help_pocket_consumer_key: "Nécessaire pour l’import depuis Pocket. Vous pouvez le créer depuis votre compte Pocket."
86 form_rss: 88 form_feed:
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." 89 description: "Les flux Atom 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."
88 token_label: "Jeton RSS" 90 token_label: "Jeton de flux"
89 no_token: "Aucun jeton généré" 91 no_token: "Aucun jeton généré"
90 token_create: "Créez votre jeton" 92 token_create: "Créez votre jeton"
91 token_reset: "Réinitialisez votre jeton" 93 token_reset: "Réinitialisez votre jeton"
92 rss_links: "Adresses de vos flux RSS" 94 feed_links: "Adresses de vos flux"
93 rss_link: 95 feed_link:
94 unread: "Non lus" 96 unread: "Non lus"
95 starred: "Favoris" 97 starred: "Favoris"
96 archive: "Lus" 98 archive: "Lus"
97 all: "Tous" 99 all: "Tous"
98 rss_limit: "Nombre d’articles dans le flux" 100 feed_limit: "Nombre d’articles dans le flux"
99 form_user: 101 form_user:
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." 102 two_factor_description: "Activer l’authentification double-facteur veut dire que vous allez recevoir un code par courriel OU que vous devriez utiliser une application de mot de passe à usage unique (comme Google Authenticator, Authy or FreeOTP) pour obtenir un code temporaire à chaque nouvelle connexion non approuvée. Vous ne pouvez pas choisir les deux options."
101 name_label: "Nom" 103 name_label: "Nom"
102 email_label: "Adresse courriel" 104 email_label: "Adresse courriel"
103 twoFactorAuthentication_label: "Double authentification" 105 two_factor:
104 help_twoFactorAuthentication: "Si vous activez 2FA, à chaque tentative de connexion à wallabag, vous recevrez un code par email." 106 emailTwoFactor_label: 'En utlisant l’email (recevez un code par email)'
107 googleTwoFactor_label: 'En utilisant une application de mot de passe à usage unique (ouvrez l’app, comme Google Authenticator, Authy or FreeOTP, pour obtenir un mot de passe à usage unique)'
108 table_method: Méthode
109 table_state: État
110 table_action: Action
111 state_enabled: Activé
112 state_disabled: Désactivé
113 action_email: Utiliser l'email
114 action_app: Utiliser une app OTP
105 delete: 115 delete:
106 title: "Supprimer mon compte (attention danger !)" 116 title: "Supprimer mon compte (attention danger !)"
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é." 117 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é."
@@ -159,6 +169,15 @@ config:
159 and: "Une règle ET l’autre" 169 and: "Une règle ET l’autre"
160 matches: "Teste si un <i>sujet</i> correspond à une <i>recherche</i> (non sensible à la casse).<br />Exemple : <code>title matches \"football\"</code>" 170 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>" 171 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>"
172 otp:
173 page_title: Authentification double-facteur
174 app:
175 two_factor_code_description_1: Vous venez d’activer l’authentification double-facteur, ouvrez votre application OTP pour configurer la génération du mot de passe à usage unique. Ces informations disparaîtront après un rechargement de la page.
176 two_factor_code_description_2: 'Vous pouvez scanner le QR code avec votre application :'
177 two_factor_code_description_3: 'N’oubliez pas de sauvegarder ces codes de secours dans un endroit sûr, vous pourrez les utiliser si vous ne pouvez plus accéder à votre application OTP :'
178 two_factor_code_description_4: 'Testez un code généré par votre application OTP :'
179 cancel: Annuler
180 enable: Activer
162 181
163entry: 182entry:
164 default_title: "Titre de l’article" 183 default_title: "Titre de l’article"
@@ -353,7 +372,7 @@ quickstart:
353 title: "Configurez l’application" 372 title: "Configurez l’application"
354 description: "Pour voir une application qui vous correspond, allez voir du côté de la configuration de wallabag." 373 description: "Pour voir une application qui vous correspond, allez voir du côté de la configuration de wallabag."
355 language: "Changez la langue et le design de l’application" 374 language: "Changez la langue et le design de l’application"
356 rss: "Activez les flux RSS" 375 feed: "Activez les flux Atom"
357 tagging_rules: "Écrivez des règles pour classer automatiquement vos articles" 376 tagging_rules: "Écrivez des règles pour classer automatiquement vos articles"
358 admin: 377 admin:
359 title: "Administration" 378 title: "Administration"
@@ -404,6 +423,8 @@ tag:
404 new: 423 new:
405 add: "Ajouter" 424 add: "Ajouter"
406 placeholder: "Vous pouvez ajouter plusieurs tags, séparés par une virgule." 425 placeholder: "Vous pouvez ajouter plusieurs tags, séparés par une virgule."
426 rename:
427 placeholder: 'Vous pouvez changer le nom de votre tag.'
407 428
408export: 429export:
409 footer_template: '<div style="text-align:center;"><p>Généré par wallabag with %method%</p><p>Merci d''ouvrir <a href="https://github.com/wallabag/wallabag/issues">un ticket</a> si vous rencontrez des soucis d''affichage avec ce document sur votre support.</p></div>' 430 footer_template: '<div style="text-align:center;"><p>Généré par wallabag with %method%</p><p>Merci d''ouvrir <a href="https://github.com/wallabag/wallabag/issues">un ticket</a> si vous rencontrez des soucis d''affichage avec ce document sur votre support.</p></div>'
@@ -530,6 +551,8 @@ user:
530 enabled_label: "Activé" 551 enabled_label: "Activé"
531 last_login_label: "Dernière connexion" 552 last_login_label: "Dernière connexion"
532 twofactor_label: "Double authentification" 553 twofactor_label: "Double authentification"
554 twofactor_email_label: Double authentification par email
555 twofactor_google_label: Double authentification par OTP app
533 save: "Sauvegarder" 556 save: "Sauvegarder"
534 delete: "Supprimer" 557 delete: "Supprimer"
535 delete_confirm: "Êtes-vous sûr ?" 558 delete_confirm: "Êtes-vous sûr ?"
@@ -567,14 +590,15 @@ flashes:
567 password_updated: "Votre mot de passe a bien été mis à jour" 590 password_updated: "Votre mot de passe a bien été mis à jour"
568 password_not_updated_demo: "En démo, vous ne pouvez pas changer le mot de passe de cet utilisateur." 591 password_not_updated_demo: "En démo, vous ne pouvez pas changer le mot de passe de cet utilisateur."
569 user_updated: "Vos informations personnelles ont bien été mises à jour" 592 user_updated: "Vos informations personnelles ont bien été mises à jour"
570 rss_updated: "La configuration des flux RSS a bien été mise à jour" 593 feed_updated: "La configuration des flux a bien été mise à jour"
571 tagging_rules_updated: "Règles mises à jour" 594 tagging_rules_updated: "Règles mises à jour"
572 tagging_rules_deleted: "Règle supprimée" 595 tagging_rules_deleted: "Règle supprimée"
573 rss_token_updated: "Jeton RSS mis à jour" 596 feed_token_updated: "Jeton des flux mis à jour"
574 annotations_reset: "Annotations supprimées" 597 annotations_reset: "Annotations supprimées"
575 tags_reset: "Tags supprimés" 598 tags_reset: "Tags supprimés"
576 entries_reset: "Articles supprimés" 599 entries_reset: "Articles supprimés"
577 archived_reset: "Articles archivés supprimés" 600 archived_reset: "Articles archivés supprimés"
601 otp_enabled: "Authentification à double-facteur activée"
578 entry: 602 entry:
579 notice: 603 notice:
580 entry_already_saved: "Article déjà sauvegardé le %date%" 604 entry_already_saved: "Article déjà sauvegardé le %date%"
@@ -588,9 +612,11 @@ flashes:
588 entry_starred: "Article ajouté dans les favoris" 612 entry_starred: "Article ajouté dans les favoris"
589 entry_unstarred: "Article retiré des favoris" 613 entry_unstarred: "Article retiré des favoris"
590 entry_deleted: "Article supprimé" 614 entry_deleted: "Article supprimé"
615 no_random_entry: "Aucun article correspond aux critères n'a été trouvé"
591 tag: 616 tag:
592 notice: 617 notice:
593 tag_added: "Tag ajouté" 618 tag_added: "Tag ajouté"
619 tag_renamed: "Tag renommé"
594 import: 620 import:
595 notice: 621 notice:
596 failed: "L’import a échoué, veuillez ré-essayer" 622 failed: "L’import a échoué, veuillez ré-essayer"
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
index ecaa3b60..0474d2bc 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.it.yml
@@ -37,6 +37,7 @@ menu:
37 add_new_entry: 'Aggiungi un nuovo contenuto' 37 add_new_entry: 'Aggiungi un nuovo contenuto'
38 search: 'Cerca' 38 search: 'Cerca'
39 filter_entries: 'Filtra contenuti' 39 filter_entries: 'Filtra contenuti'
40 # random_entry: Jump to a random entry from that list
40 export: 'Esporta' 41 export: 'Esporta'
41 search_form: 42 search_form:
42 input_label: 'Inserisci qui la tua ricerca' 43 input_label: 'Inserisci qui la tua ricerca'
@@ -53,11 +54,12 @@ config:
53 page_title: 'Configurazione' 54 page_title: 'Configurazione'
54 tab_menu: 55 tab_menu:
55 settings: 'Impostazioni' 56 settings: 'Impostazioni'
56 rss: 'RSS' 57 feed: 'RSS'
57 user_info: 'Informazioni utente' 58 user_info: 'Informazioni utente'
58 password: 'Password' 59 password: 'Password'
59 rules: 'Regole di etichettatura' 60 rules: 'Regole di etichettatura'
60 new_user: 'Aggiungi utente' 61 new_user: 'Aggiungi utente'
62 reset: 'Area di reset'
61 form: 63 form:
62 save: 'Salva' 64 save: 'Salva'
63 form_settings: 65 form_settings:
@@ -83,25 +85,32 @@ config:
83 help_reading_speed: "wallabag calcola un tempo di lettura per ogni articolo. Puoi definire qui, grazie a questa lista, se sei un lettore lento o veloce. wallabag ricalcolerà la velocità di lettura per ogni articolo." 85 help_reading_speed: "wallabag calcola un tempo di lettura per ogni articolo. Puoi definire qui, grazie a questa lista, se sei un lettore lento o veloce. wallabag ricalcolerà la velocità di lettura per ogni articolo."
84 help_language: "Puoi cambiare la lingua dell'interfaccia di wallabag." 86 help_language: "Puoi cambiare la lingua dell'interfaccia di wallabag."
85 help_pocket_consumer_key: "Richiesta per importare da Pocket. La puoi creare nel tuo account Pocket." 87 help_pocket_consumer_key: "Richiesta per importare da Pocket. La puoi creare nel tuo account Pocket."
86 form_rss: 88 form_feed:
87 description: 'I feed RSS generati da wallabag ti permettono di leggere i tuoi contenuti salvati con il tuo lettore di RSS preferito. Prima, devi generare un token.' 89 description: 'I feed RSS generati da wallabag ti permettono di leggere i tuoi contenuti salvati con il tuo lettore di RSS preferito. Prima, devi generare un token.'
88 token_label: 'Token RSS' 90 token_label: 'Token RSS'
89 no_token: 'Nessun token' 91 no_token: 'Nessun token'
90 token_create: 'Crea il tuo token' 92 token_create: 'Crea il tuo token'
91 token_reset: 'Rigenera il tuo token' 93 token_reset: 'Rigenera il tuo token'
92 rss_links: 'Collegamenti RSS' 94 feed_links: 'Collegamenti RSS'
93 rss_link: 95 feed_link:
94 unread: 'Non letti' 96 unread: 'Non letti'
95 starred: 'Preferiti' 97 starred: 'Preferiti'
96 archive: 'Archiviati' 98 archive: 'Archiviati'
97 # all: 'All' 99 # all: 'All'
98 rss_limit: 'Numero di elementi nel feed' 100 feed_limit: 'Numero di elementi nel feed'
99 form_user: 101 form_user:
100 two_factor_description: "Abilitando l'autenticazione a due fattori riceverai una e-mail con un codice per ogni nuova connesione non verificata" 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
101 name_label: 'Nome' 103 name_label: 'Nome'
102 email_label: 'E-mail' 104 email_label: 'E-mail'
103 twoFactorAuthentication_label: 'Autenticazione a due fattori' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
104 help_twoFactorAuthentication: "Se abiliti l'autenticazione a due fattori, ogni volta che vorrai connetterti a wallabag, riceverai un codice via E-mail." 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
107 # table_method: Method
108 # table_state: State
109 # table_action: Action
110 # state_enabled: Enabled
111 # state_disabled: Disabled
112 # action_email: Use email
113 # action_app: Use OTP App
105 delete: 114 delete:
106 title: Cancella il mio account (zona pericolosa) 115 title: Cancella il mio account (zona pericolosa)
107 description: Rimuovendo il tuo account, TUTTI i tuoi articoli, TUTTE le tue etichette, TUTTE le tue annotazioni ed il tuo account verranno rimossi PERMANENTEMENTE (impossibile da ANNULLARE). Verrai poi disconnesso. 116 description: Rimuovendo il tuo account, TUTTI i tuoi articoli, TUTTE le tue etichette, TUTTE le tue annotazioni ed il tuo account verranno rimossi PERMANENTEMENTE (impossibile da ANNULLARE). Verrai poi disconnesso.
@@ -159,6 +168,15 @@ config:
159 and: "Una regola E un'altra" 168 and: "Una regola E un'altra"
160 matches: 'Verifica che un <i>oggetto</i> risulti in una <i>ricerca</i> (case-insensitive).<br />Esempio: <code>titolo contiene "football"</code>' 169 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> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 170 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
171 otp:
172 # page_title: Two-factor authentication
173 # app:
174 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
175 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
176 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
177 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
178 # cancel: Cancel
179 # enable: Enable
162 180
163entry: 181entry:
164 default_title: "Titolo del contenuto" 182 default_title: "Titolo del contenuto"
@@ -353,7 +371,7 @@ quickstart:
353 title: "Configura l'applicazione" 371 title: "Configura l'applicazione"
354 description: "Per avere un'applicazione che ti soddisfi, dai un'occhiata alla configurazione di wallabag." 372 description: "Per avere un'applicazione che ti soddisfi, dai un'occhiata alla configurazione di wallabag."
355 language: 'Cambia lingua e design' 373 language: 'Cambia lingua e design'
356 rss: 'Abilita i feed RSS' 374 feed: 'Abilita i feed RSS'
357 tagging_rules: 'Scrivi delle regole per taggare automaticamente i contenuti' 375 tagging_rules: 'Scrivi delle regole per taggare automaticamente i contenuti'
358 admin: 376 admin:
359 title: 'Amministrazione' 377 title: 'Amministrazione'
@@ -404,6 +422,8 @@ tag:
404 new: 422 new:
405 add: 'Aggiungi' 423 add: 'Aggiungi'
406 placeholder: 'Puoi aggiungere varie etichette, separate da una virgola.' 424 placeholder: 'Puoi aggiungere varie etichette, separate da una virgola.'
425 rename:
426 # placeholder: 'You can update tag name.'
407 427
408# export: 428# export:
409# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>' 429# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@@ -529,7 +549,8 @@ user:
529 email_label: 'E-mail' 549 email_label: 'E-mail'
530 enabled_label: 'Abilitato' 550 enabled_label: 'Abilitato'
531 last_login_label: 'Ultima connessione' 551 last_login_label: 'Ultima connessione'
532 twofactor_label: Autenticazione a due fattori 552 # twofactor_email_label: Two factor authentication by email
553 # twofactor_google_label: Two factor authentication by OTP app
533 save: Salva 554 save: Salva
534 delete: Cancella 555 delete: Cancella
535 delete_confirm: Sei sicuro? 556 delete_confirm: Sei sicuro?
@@ -567,10 +588,10 @@ flashes:
567 password_updated: 'Password aggiornata' 588 password_updated: 'Password aggiornata'
568 password_not_updated_demo: "In modalità demo, non puoi cambiare la password dell'utente." 589 password_not_updated_demo: "In modalità demo, non puoi cambiare la password dell'utente."
569 user_updated: 'Informazioni aggiornate' 590 user_updated: 'Informazioni aggiornate'
570 rss_updated: 'Informazioni RSS aggiornate' 591 feed_updated: 'Informazioni RSS aggiornate'
571 tagging_rules_updated: 'Regole di etichettatura aggiornate' 592 tagging_rules_updated: 'Regole di etichettatura aggiornate'
572 tagging_rules_deleted: 'Regola di etichettatura eliminate' 593 tagging_rules_deleted: 'Regola di etichettatura eliminate'
573 rss_token_updated: 'RSS token aggiornato' 594 feed_token_updated: 'RSS token aggiornato'
574 annotations_reset: Reset annotazioni 595 annotations_reset: Reset annotazioni
575 tags_reset: Reset etichette 596 tags_reset: Reset etichette
576 entries_reset: Reset articoli 597 entries_reset: Reset articoli
@@ -588,9 +609,11 @@ flashes:
588 entry_starred: 'Contenuto segnato come preferito' 609 entry_starred: 'Contenuto segnato come preferito'
589 entry_unstarred: 'Contenuto rimosso dai preferiti' 610 entry_unstarred: 'Contenuto rimosso dai preferiti'
590 entry_deleted: 'Contenuto eliminato' 611 entry_deleted: 'Contenuto eliminato'
612 # no_random_entry: 'No article with these criterias was found'
591 tag: 613 tag:
592 notice: 614 notice:
593 tag_added: 'Etichetta aggiunta' 615 tag_added: 'Etichetta aggiunta'
616 # tag_renamed: 'Tag renamed'
594 import: 617 import:
595 notice: 618 notice:
596 failed: 'Importazione fallita, riprova.' 619 failed: 'Importazione fallita, riprova.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
index 848c88d2..e761832e 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.oc.yml
@@ -37,6 +37,7 @@ menu:
37 add_new_entry: 'Enregistrar un novèl article' 37 add_new_entry: 'Enregistrar un novèl article'
38 search: 'Cercar' 38 search: 'Cercar'
39 filter_entries: 'Filtrar los articles' 39 filter_entries: 'Filtrar los articles'
40 # random_entry: Jump to a random entry from that list
40 export: 'Exportar' 41 export: 'Exportar'
41 search_form: 42 search_form:
42 input_label: 'Picatz vòstre mot-clau a cercar aquí' 43 input_label: 'Picatz vòstre mot-clau a cercar aquí'
@@ -53,11 +54,12 @@ config:
53 page_title: 'Configuracion' 54 page_title: 'Configuracion'
54 tab_menu: 55 tab_menu:
55 settings: 'Paramètres' 56 settings: 'Paramètres'
56 rss: 'RSS' 57 feed: 'RSS'
57 user_info: 'Mon compte' 58 user_info: 'Mon compte'
58 password: 'Senhal' 59 password: 'Senhal'
59 rules: "Règlas d'etiquetas automaticas" 60 rules: "Règlas d'etiquetas automaticas"
60 new_user: 'Crear un compte' 61 new_user: 'Crear un compte'
62 reset: 'Zòna de reïnicializacion'
61 form: 63 form:
62 save: 'Enregistrar' 64 save: 'Enregistrar'
63 form_settings: 65 form_settings:
@@ -83,25 +85,32 @@ config:
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." 85 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."
84 help_language: "Podètz cambiar la lenga de l'interfàcia de wallabag." 86 help_language: "Podètz cambiar la lenga de l'interfàcia de wallabag."
85 help_pocket_consumer_key: "Requesida per l'importacion de Pocket. Podètz la crear dins vòstre compte Pocket." 87 help_pocket_consumer_key: "Requesida per l'importacion de Pocket. Podètz la crear dins vòstre compte Pocket."
86 form_rss: 88 form_feed:
87 description: "Los fluxes RSS fornits per wallabag vos permeton de legir vòstres articles salvagardats dins vòstre lector de fluxes preferit. Per los poder emplegar, vos cal, d'en primièr crear un geton." 89 description: "Los fluxes RSS fornits per wallabag vos permeton de legir vòstres articles salvagardats dins vòstre lector de fluxes preferit. Per los poder emplegar, vos cal, d'en primièr crear un geton."
88 token_label: 'Geton RSS' 90 token_label: 'Geton RSS'
89 no_token: 'Pas cap de geton generat' 91 no_token: 'Pas cap de geton generat'
90 token_create: 'Creatz vòstre geton' 92 token_create: 'Creatz vòstre geton'
91 token_reset: 'Reïnicializatz vòstre geton' 93 token_reset: 'Reïnicializatz vòstre geton'
92 rss_links: 'URLs de vòstres fluxes RSS' 94 feed_links: 'URLs de vòstres fluxes RSS'
93 rss_link: 95 feed_link:
94 unread: 'Pas legits' 96 unread: 'Pas legits'
95 starred: 'Favorits' 97 starred: 'Favorits'
96 archive: 'Legits' 98 archive: 'Legits'
97 all: 'Totes' 99 all: 'Totes'
98 rss_limit: "Nombre d'articles dins un flux RSS" 100 feed_limit: "Nombre d'articles dins un flux"
99 form_user: 101 form_user:
100 two_factor_description: "Activar l'autentificacion en dos temps vòl dire que recebretz un còdi per corrièl per cada novèla connexion pas aprovada." 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
101 name_label: 'Nom' 103 name_label: 'Nom'
102 email_label: 'Adreça de corrièl' 104 email_label: 'Adreça de corrièl'
103 twoFactorAuthentication_label: 'Dobla autentificacion' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
104 help_twoFactorAuthentication: "S'avètz activat l'autentificacion en dos temps, cada còp que volètz vos connectar a wallabag, recebretz un còdi per corrièl." 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
107 # table_method: Method
108 # table_state: State
109 # table_action: Action
110 # state_enabled: Enabled
111 # state_disabled: Disabled
112 # action_email: Use email
113 # action_app: Use OTP App
105 delete: 114 delete:
106 title: Suprimir mon compte (Mèfi zòna perilhosa) 115 title: Suprimir mon compte (Mèfi zòna perilhosa)
107 description: Se confirmatz la supression de vòstre compte, TOTES vòstres articles, TOTAS vòstras etiquetas, TOTAS vòstras anotacions e vòstre compte seràn suprimits per totjorn. E aquò es IRREVERSIBLE. Puèi seretz desconnectat. 116 description: Se confirmatz la supression de vòstre compte, TOTES vòstres articles, TOTAS vòstras etiquetas, TOTAS vòstras anotacions e vòstre compte seràn suprimits per totjorn. E aquò es IRREVERSIBLE. Puèi seretz desconnectat.
@@ -159,6 +168,15 @@ config:
159 and: "Una règla E l'autra" 168 and: "Una règla E l'autra"
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>' 169 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>' 170 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>'
171 otp:
172 # page_title: Two-factor authentication
173 # app:
174 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
175 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
176 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
177 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
178 # cancel: Cancel
179 # enable: Enable
162 180
163entry: 181entry:
164 default_title: "Títol de l'article" 182 default_title: "Títol de l'article"
@@ -353,7 +371,7 @@ quickstart:
353 title: "Configuratz l'aplicacion" 371 title: "Configuratz l'aplicacion"
354 description: "Per fin d'aver una aplicacion que vos va ben, anatz veire la configuracion de wallabag." 372 description: "Per fin d'aver una aplicacion que vos va ben, anatz veire la configuracion de wallabag."
355 language: "Cambiatz la lenga e l'estil de l'aplicacion" 373 language: "Cambiatz la lenga e l'estil de l'aplicacion"
356 rss: 'Activatz los fluxes RSS' 374 feed: 'Activatz los fluxes RSS'
357 tagging_rules: 'Escrivètz de règlas per classar automaticament vòstres articles' 375 tagging_rules: 'Escrivètz de règlas per classar automaticament vòstres articles'
358 admin: 376 admin:
359 title: 'Administracion' 377 title: 'Administracion'
@@ -404,6 +422,8 @@ tag:
404 new: 422 new:
405 add: 'Ajustar' 423 add: 'Ajustar'
406 placeholder: "Podètz ajustar mai qu'una etiqueta, separadas per de virgula." 424 placeholder: "Podètz ajustar mai qu'una etiqueta, separadas per de virgula."
425 rename:
426 # placeholder: 'You can update tag name.'
407 427
408export: 428export:
409 footer_template: '<div style="text-align:center;"><p>Produch per wallabag amb %method%</p><p>Mercés de dobrir <a href="https://github.com/wallabag/wallabag/issues">una sollicitacion</a> s’avètz de problèmas amb l’afichatge d’aqueste E-Book sus vòstre periferic.</p></div>' 429 footer_template: '<div style="text-align:center;"><p>Produch per wallabag amb %method%</p><p>Mercés de dobrir <a href="https://github.com/wallabag/wallabag/issues">una sollicitacion</a> s’avètz de problèmas amb l’afichatge d’aqueste E-Book sus vòstre periferic.</p></div>'
@@ -529,7 +549,8 @@ user:
529 email_label: 'Adreça de corrièl' 549 email_label: 'Adreça de corrièl'
530 enabled_label: 'Actiu' 550 enabled_label: 'Actiu'
531 last_login_label: 'Darrièra connexion' 551 last_login_label: 'Darrièra connexion'
532 twofactor_label: 'Autentificacion doble-factor' 552 # twofactor_email_label: Two factor authentication by email
553 # twofactor_google_label: Two factor authentication by OTP app
533 save: 'Enregistrar' 554 save: 'Enregistrar'
534 delete: 'Suprimir' 555 delete: 'Suprimir'
535 delete_confirm: 'Sètz segur ?' 556 delete_confirm: 'Sètz segur ?'
@@ -567,10 +588,10 @@ flashes:
567 password_updated: 'Vòstre senhal es ben estat mes a jorn' 588 password_updated: 'Vòstre senhal es ben estat mes a jorn'
568 password_not_updated_demo: "En demostracion, podètz pas cambiar lo senhal d'aqueste utilizaire." 589 password_not_updated_demo: "En demostracion, podètz pas cambiar lo senhal d'aqueste utilizaire."
569 user_updated: 'Vòstres informacions personnelas son ben estadas mesas a jorn' 590 user_updated: 'Vòstres informacions personnelas son ben estadas mesas a jorn'
570 rss_updated: 'La configuracion dels fluxes RSS es ben estada mesa a jorn' 591 feed_updated: 'La configuracion dels fluxes RSS es ben estada mesa a jorn'
571 tagging_rules_updated: 'Règlas misa a jorn' 592 tagging_rules_updated: 'Règlas misa a jorn'
572 tagging_rules_deleted: 'Règla suprimida' 593 tagging_rules_deleted: 'Règla suprimida'
573 rss_token_updated: 'Geton RSS mes a jorn' 594 feed_token_updated: 'Geton RSS mes a jorn'
574 annotations_reset: Anotacions levadas 595 annotations_reset: Anotacions levadas
575 tags_reset: Etiquetas levadas 596 tags_reset: Etiquetas levadas
576 entries_reset: Articles levats 597 entries_reset: Articles levats
@@ -588,9 +609,11 @@ flashes:
588 entry_starred: 'Article ajustat dins los favorits' 609 entry_starred: 'Article ajustat dins los favorits'
589 entry_unstarred: 'Article quitat dels favorits' 610 entry_unstarred: 'Article quitat dels favorits'
590 entry_deleted: 'Article suprimit' 611 entry_deleted: 'Article suprimit'
612 # no_random_entry: 'No article with these criterias was found'
591 tag: 613 tag:
592 notice: 614 notice:
593 tag_added: 'Etiqueta ajustada' 615 tag_added: 'Etiqueta ajustada'
616 # tag_renamed: 'Tag renamed'
594 import: 617 import:
595 notice: 618 notice:
596 failed: "L'importacion a fracassat, mercés de tornar ensajar." 619 failed: "L'importacion a fracassat, mercés de tornar ensajar."
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
index a0032fe8..f3d506e5 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pl.yml
@@ -37,6 +37,7 @@ menu:
37 add_new_entry: 'Dodaj nowy wpis' 37 add_new_entry: 'Dodaj nowy wpis'
38 search: 'Szukaj' 38 search: 'Szukaj'
39 filter_entries: 'Filtruj wpisy' 39 filter_entries: 'Filtruj wpisy'
40 # random_entry: Jump to a random entry from that list
40 export: 'Eksportuj' 41 export: 'Eksportuj'
41 search_form: 42 search_form:
42 input_label: 'Wpisz swoje zapytanie tutaj' 43 input_label: 'Wpisz swoje zapytanie tutaj'
@@ -53,11 +54,12 @@ config:
53 page_title: 'Konfiguracja' 54 page_title: 'Konfiguracja'
54 tab_menu: 55 tab_menu:
55 settings: 'Ustawienia' 56 settings: 'Ustawienia'
56 rss: 'Kanał RSS' 57 feed: 'Kanał RSS'
57 user_info: 'Informacje o użytkowniku' 58 user_info: 'Informacje o użytkowniku'
58 password: 'Hasło' 59 password: 'Hasło'
59 rules: 'Zasady tagowania' 60 rules: 'Zasady tagowania'
60 new_user: 'Dodaj użytkownika' 61 new_user: 'Dodaj użytkownika'
62 reset: 'Reset'
61 form: 63 form:
62 save: 'Zapisz' 64 save: 'Zapisz'
63 form_settings: 65 form_settings:
@@ -83,25 +85,32 @@ config:
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." 85 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."
84 help_language: "Możesz zmienić język interfejsu wallabag." 86 help_language: "Możesz zmienić język interfejsu wallabag."
85 help_pocket_consumer_key: "Wymagane dla importu z Pocket. Możesz go stworzyć na swoim koncie Pocket." 87 help_pocket_consumer_key: "Wymagane dla importu z Pocket. Możesz go stworzyć na swoim koncie Pocket."
86 form_rss: 88 form_feed:
87 description: 'Kanały RSS prowadzone przez wallabag pozwalają Ci na czytanie twoich zapisanych artykułów w twoim ulubionym czytniku RSS. Musisz najpierw wynegenerować tokena.‌' 89 description: 'Kanały RSS prowadzone przez wallabag pozwalają Ci na czytanie twoich zapisanych artykułów w twoim ulubionym czytniku RSS. Musisz najpierw wynegenerować tokena.‌'
88 token_label: 'Token RSS' 90 token_label: 'Token RSS'
89 no_token: 'Brak tokena' 91 no_token: 'Brak tokena'
90 token_create: 'Stwórz tokena' 92 token_create: 'Stwórz tokena'
91 token_reset: 'Zresetuj swojego tokena' 93 token_reset: 'Zresetuj swojego tokena'
92 rss_links: 'RSS links' 94 feed_links: 'RSS links'
93 rss_link: 95 feed_link:
94 unread: 'Nieprzeczytane' 96 unread: 'Nieprzeczytane'
95 starred: 'Oznaczone gwiazdką' 97 starred: 'Oznaczone gwiazdką'
96 archive: 'Archiwum' 98 archive: 'Archiwum'
97 all: 'Wszystkie' 99 all: 'Wszystkie'
98 rss_limit: 'Link do RSS' 100 feed_limit: 'Link do RSS'
99 form_user: 101 form_user:
100 two_factor_description: "Włączenie autoryzacji dwuetapowej oznacza, że będziesz otrzymywał maile z kodem przy każdym nowym, niezaufanym połączeniu" 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
101 name_label: 'Nazwa' 103 name_label: 'Nazwa'
102 email_label: 'Adres email' 104 email_label: 'Adres email'
103 twoFactorAuthentication_label: 'Autoryzacja dwuetapowa' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
104 help_twoFactorAuthentication: "Jeżeli włączysz autoryzację dwuetapową. Za każdym razem, kiedy będziesz chciał się zalogować, dostaniesz kod na swój e-mail." 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
107 # table_method: Method
108 # table_state: State
109 # table_action: Action
110 # state_enabled: Enabled
111 # state_disabled: Disabled
112 # action_email: Use email
113 # action_app: Use OTP App
105 delete: 114 delete:
106 title: Usuń moje konto (niebezpieczna strefa !) 115 title: Usuń moje konto (niebezpieczna strefa !)
107 description: Jeżeli usuniesz swoje konto, wszystkie twoje artykuły, tagi, adnotacje, oraz konto zostaną trwale usunięte (operacja jest NIEODWRACALNA). Następnie zostaniesz wylogowany. 116 description: Jeżeli usuniesz swoje konto, wszystkie twoje artykuły, tagi, adnotacje, oraz konto zostaną trwale usunięte (operacja jest NIEODWRACALNA). Następnie zostaniesz wylogowany.
@@ -159,6 +168,15 @@ config:
159 and: 'Jedna reguła I inna' 168 and: 'Jedna reguła I inna'
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>' 169 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>' 170 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>'
171 otp:
172 # page_title: Two-factor authentication
173 # app:
174 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
175 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
176 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
177 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
178 # cancel: Cancel
179 # enable: Enable
162 180
163entry: 181entry:
164 default_title: 'Tytuł wpisu' 182 default_title: 'Tytuł wpisu'
@@ -353,7 +371,7 @@ quickstart:
353 title: 'Konfiguruj aplikację' 371 title: 'Konfiguruj aplikację'
354 description: 'W celu dopasowania aplikacji do swoich upodobań, zobacz konfigurację aplikacji' 372 description: 'W celu dopasowania aplikacji do swoich upodobań, zobacz konfigurację aplikacji'
355 language: 'Zmień język i wygląd' 373 language: 'Zmień język i wygląd'
356 rss: 'Włącz kanały RSS' 374 feed: 'Włącz kanały RSS'
357 tagging_rules: 'Napisz reguły pozwalające na automatyczne otagowanie twoich artykułów' 375 tagging_rules: 'Napisz reguły pozwalające na automatyczne otagowanie twoich artykułów'
358 admin: 376 admin:
359 title: 'Administracja' 377 title: 'Administracja'
@@ -404,6 +422,8 @@ tag:
404 new: 422 new:
405 add: 'Dodaj' 423 add: 'Dodaj'
406 placeholder: 'Możesz dodać kilka tagów, oddzielając je przecinkami.' 424 placeholder: 'Możesz dodać kilka tagów, oddzielając je przecinkami.'
425 rename:
426 placeholder: 'Możesz zaktualizować nazwę taga.'
407 427
408export: 428export:
409 footer_template: '<div style="text-align:center;"><p>Stworzone przez wallabag z %method%</p><p>Proszę zgłoś <a href="https://github.com/wallabag/wallabag/issues">sprawę</a>, jeżeli masz problem z wyświetleniem tego e-booka na swoim urządzeniu.</p></div>' 429 footer_template: '<div style="text-align:center;"><p>Stworzone przez wallabag z %method%</p><p>Proszę zgłoś <a href="https://github.com/wallabag/wallabag/issues">sprawę</a>, jeżeli masz problem z wyświetleniem tego e-booka na swoim urządzeniu.</p></div>'
@@ -529,7 +549,8 @@ user:
529 email_label: 'Adres email' 549 email_label: 'Adres email'
530 enabled_label: 'Włączony' 550 enabled_label: 'Włączony'
531 last_login_label: 'Ostatnie logowanie' 551 last_login_label: 'Ostatnie logowanie'
532 twofactor_label: Autoryzacja dwuetapowa 552 # twofactor_email_label: Two factor authentication by email
553 # twofactor_google_label: Two factor authentication by OTP app
533 save: Zapisz 554 save: Zapisz
534 delete: Usuń 555 delete: Usuń
535 delete_confirm: Jesteś pewien? 556 delete_confirm: Jesteś pewien?
@@ -567,10 +588,10 @@ flashes:
567 password_updated: 'Hasło zaktualizowane' 588 password_updated: 'Hasło zaktualizowane'
568 password_not_updated_demo: "In demonstration mode, you can't change password for this user." 589 password_not_updated_demo: "In demonstration mode, you can't change password for this user."
569 user_updated: 'Informacje zaktualizowane' 590 user_updated: 'Informacje zaktualizowane'
570 rss_updated: 'Informacje RSS zaktualizowane' 591 feed_updated: 'Informacje RSS zaktualizowane'
571 tagging_rules_updated: 'Reguły tagowania zaktualizowane' 592 tagging_rules_updated: 'Reguły tagowania zaktualizowane'
572 tagging_rules_deleted: 'Reguła tagowania usunięta' 593 tagging_rules_deleted: 'Reguła tagowania usunięta'
573 rss_token_updated: 'Token kanału RSS zaktualizowany' 594 feed_token_updated: 'Token kanału RSS zaktualizowany'
574 annotations_reset: Zresetuj adnotacje 595 annotations_reset: Zresetuj adnotacje
575 tags_reset: Zresetuj tagi 596 tags_reset: Zresetuj tagi
576 entries_reset: Zresetuj wpisy 597 entries_reset: Zresetuj wpisy
@@ -588,9 +609,11 @@ flashes:
588 entry_starred: 'Wpis oznaczony gwiazdką' 609 entry_starred: 'Wpis oznaczony gwiazdką'
589 entry_unstarred: 'Wpis odznaczony gwiazdką' 610 entry_unstarred: 'Wpis odznaczony gwiazdką'
590 entry_deleted: 'Wpis usunięty' 611 entry_deleted: 'Wpis usunięty'
612 # no_random_entry: 'No article with these criterias was found'
591 tag: 613 tag:
592 notice: 614 notice:
593 tag_added: 'Tag dodany' 615 tag_added: 'Tag dodany'
616 tag_renamed: 'Nazwa taga zmieniona'
594 import: 617 import:
595 notice: 618 notice:
596 failed: 'Nieudany import, prosimy spróbować ponownie.' 619 failed: 'Nieudany import, prosimy spróbować ponownie.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
index 292fad61..6ddc1fc1 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.pt.yml
@@ -37,6 +37,7 @@ menu:
37 add_new_entry: 'Adicionar uma nova entrada' 37 add_new_entry: 'Adicionar uma nova entrada'
38 search: 'Pesquisa' 38 search: 'Pesquisa'
39 filter_entries: 'Filtrar entradas' 39 filter_entries: 'Filtrar entradas'
40 # random_entry: Jump to a random entry from that list
40 export: 'Exportar' 41 export: 'Exportar'
41 search_form: 42 search_form:
42 input_label: 'Digite aqui sua pesquisa' 43 input_label: 'Digite aqui sua pesquisa'
@@ -53,11 +54,12 @@ config:
53 page_title: 'Config' 54 page_title: 'Config'
54 tab_menu: 55 tab_menu:
55 settings: 'Configurações' 56 settings: 'Configurações'
56 rss: 'RSS' 57 feed: 'RSS'
57 user_info: 'Informação do Usuário' 58 user_info: 'Informação do Usuário'
58 password: 'Senha' 59 password: 'Senha'
59 rules: 'Regras de tags' 60 rules: 'Regras de tags'
60 new_user: 'Adicionar um usuário' 61 new_user: 'Adicionar um usuário'
62 # reset: 'Reset area'
61 form: 63 form:
62 save: 'Salvar' 64 save: 'Salvar'
63 form_settings: 65 form_settings:
@@ -83,25 +85,32 @@ config:
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." 85 # 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."
84 # help_language: "You can change the language of wallabag interface." 86 # help_language: "You can change the language of wallabag interface."
85 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account." 87 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
86 form_rss: 88 form_feed:
87 description: 'Feeds RSS providos pelo wallabag permitem que você leia seus artigos salvos em seu leitor de RSS favorito. Você precisa gerar um token primeiro.' 89 description: 'Feeds RSS providos pelo wallabag permitem que você leia seus artigos salvos em seu leitor de RSS favorito. Você precisa gerar um token primeiro.'
88 token_label: 'Token RSS' 90 token_label: 'Token RSS'
89 no_token: 'Nenhum Token' 91 no_token: 'Nenhum Token'
90 token_create: 'Criar seu token' 92 token_create: 'Criar seu token'
91 token_reset: 'Gerar novamente seu token' 93 token_reset: 'Gerar novamente seu token'
92 rss_links: 'Links RSS' 94 feed_links: 'Links RSS'
93 rss_link: 95 feed_link:
94 unread: 'Não lido' 96 unread: 'Não lido'
95 starred: 'Destacado' 97 starred: 'Destacado'
96 archive: 'Arquivado' 98 archive: 'Arquivado'
97 # all: 'All' 99 # all: 'All'
98 rss_limit: 'Número de itens no feed' 100 feed_limit: 'Número de itens no feed'
99 form_user: 101 form_user:
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.' 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
101 name_label: 'Nome' 103 name_label: 'Nome'
102 email_label: 'E-mail' 104 email_label: 'E-mail'
103 twoFactorAuthentication_label: 'Autenticação de dois passos' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
104 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
107 # table_method: Method
108 # table_state: State
109 # table_action: Action
110 # state_enabled: Enabled
111 # state_disabled: Disabled
112 # action_email: Use email
113 # action_app: Use OTP App
105 delete: 114 delete:
106 # title: Delete my account (a.k.a danger zone) 115 # title: Delete my account (a.k.a danger zone)
107 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. 116 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@@ -159,6 +168,15 @@ config:
159 and: 'Uma regra E outra' 168 and: 'Uma regra E outra'
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>' 169 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> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 170 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
171 otp:
172 # page_title: Two-factor authentication
173 # app:
174 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
175 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
176 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
177 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
178 # cancel: Cancel
179 # enable: Enable
162 180
163entry: 181entry:
164 default_title: 'Título da entrada' 182 default_title: 'Título da entrada'
@@ -353,7 +371,7 @@ quickstart:
353 title: 'Configurar a aplicação' 371 title: 'Configurar a aplicação'
354 description: 'Para ter uma aplicação que atende você, dê uma olhada na configuração do wallabag.' 372 description: 'Para ter uma aplicação que atende você, dê uma olhada na configuração do wallabag.'
355 language: 'Alterar idioma e design' 373 language: 'Alterar idioma e design'
356 rss: 'Habilitar feeds RSS' 374 feed: 'Habilitar feeds RSS'
357 tagging_rules: 'Escrever regras para acrescentar tags automaticamente em seus artigos' 375 tagging_rules: 'Escrever regras para acrescentar tags automaticamente em seus artigos'
358 admin: 376 admin:
359 title: 'Administração' 377 title: 'Administração'
@@ -404,6 +422,8 @@ tag:
404 new: 422 new:
405 # add: 'Add' 423 # add: 'Add'
406 # placeholder: 'You can add several tags, separated by a comma.' 424 # placeholder: 'You can add several tags, separated by a comma.'
425 rename:
426 # placeholder: 'You can update tag name.'
407 427
408# export: 428# export:
409# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>' 429# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@@ -529,7 +549,8 @@ user:
529 email_label: 'E-mail' 549 email_label: 'E-mail'
530 enabled_label: 'Habilitado' 550 enabled_label: 'Habilitado'
531 last_login_label: 'Último login' 551 last_login_label: 'Último login'
532 twofactor_label: 'Autenticação de dois passos' 552 # twofactor_email_label: Two factor authentication by email
553 # twofactor_google_label: Two factor authentication by OTP app
533 save: 'Salvar' 554 save: 'Salvar'
534 delete: 'Apagar' 555 delete: 'Apagar'
535 delete_confirm: 'Tem certeza?' 556 delete_confirm: 'Tem certeza?'
@@ -567,10 +588,10 @@ flashes:
567 password_updated: 'Senha atualizada' 588 password_updated: 'Senha atualizada'
568 password_not_updated_demo: 'Em modo de demonstração, você não pode alterar a senha deste usuário.' 589 password_not_updated_demo: 'Em modo de demonstração, você não pode alterar a senha deste usuário.'
569 # user_updated: 'Information updated' 590 # user_updated: 'Information updated'
570 rss_updated: 'Informação de RSS atualizada' 591 feed_updated: 'Informação de RSS atualizada'
571 tagging_rules_updated: 'Regras de tags atualizadas' 592 tagging_rules_updated: 'Regras de tags atualizadas'
572 tagging_rules_deleted: 'Regra de tag apagada' 593 tagging_rules_deleted: 'Regra de tag apagada'
573 rss_token_updated: 'Token RSS atualizado' 594 feed_token_updated: 'Token RSS atualizado'
574 # annotations_reset: Annotations reset 595 # annotations_reset: Annotations reset
575 # tags_reset: Tags reset 596 # tags_reset: Tags reset
576 # entries_reset: Entries reset 597 # entries_reset: Entries reset
@@ -588,9 +609,11 @@ flashes:
588 entry_starred: 'Entrada destacada' 609 entry_starred: 'Entrada destacada'
589 entry_unstarred: 'Entrada não destacada' 610 entry_unstarred: 'Entrada não destacada'
590 entry_deleted: 'Entrada apagada' 611 entry_deleted: 'Entrada apagada'
612 # no_random_entry: 'No article with these criterias was found'
591 tag: 613 tag:
592 notice: 614 notice:
593 tag_added: 'Tag adicionada' 615 tag_added: 'Tag adicionada'
616 # tag_renamed: 'Tag renamed'
594 import: 617 import:
595 notice: 618 notice:
596 failed: 'Importação falhou, por favor tente novamente.' 619 failed: 'Importação falhou, por favor tente novamente.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
index 9e8d68b3..8c0791f0 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ro.yml
@@ -37,6 +37,7 @@ menu:
37 add_new_entry: 'Introdu un nou articol' 37 add_new_entry: 'Introdu un nou articol'
38 search: 'Căutare' 38 search: 'Căutare'
39 filter_entries: 'Filtrează articolele' 39 filter_entries: 'Filtrează articolele'
40 # random_entry: Jump to a random entry from that list
40 # export: 'Export' 41 # export: 'Export'
41 search_form: 42 search_form:
42 input_label: 'Introdu căutarea ta' 43 input_label: 'Introdu căutarea ta'
@@ -53,11 +54,12 @@ config:
53 page_title: 'Configurație' 54 page_title: 'Configurație'
54 tab_menu: 55 tab_menu:
55 settings: 'Setări' 56 settings: 'Setări'
56 rss: 'RSS' 57 feed: 'RSS'
57 user_info: 'Informații despre utilizator' 58 user_info: 'Informații despre utilizator'
58 password: 'Parolă' 59 password: 'Parolă'
59 # rules: 'Tagging rules' 60 # rules: 'Tagging rules'
60 new_user: 'Crează un utilizator' 61 new_user: 'Crează un utilizator'
62 # reset: 'Reset area'
61 form: 63 form:
62 save: 'Salvează' 64 save: 'Salvează'
63 form_settings: 65 form_settings:
@@ -83,25 +85,32 @@ config:
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." 85 # 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."
84 # help_language: "You can change the language of wallabag interface." 86 # help_language: "You can change the language of wallabag interface."
85 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account." 87 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
86 form_rss: 88 form_feed:
87 description: 'Feed-urile RSS oferite de wallabag îți permit să-ți citești articolele salvate în reader-ul tău preferat RSS.' 89 description: 'Feed-urile RSS oferite de wallabag îți permit să-ți citești articolele salvate în reader-ul tău preferat RSS.'
88 token_label: 'RSS-Token' 90 token_label: 'RSS-Token'
89 no_token: 'Fără token' 91 no_token: 'Fără token'
90 token_create: 'Crează-ți token' 92 token_create: 'Crează-ți token'
91 token_reset: 'Resetează-ți token-ul' 93 token_reset: 'Resetează-ți token-ul'
92 rss_links: 'Link-uri RSS' 94 feed_links: 'Link-uri RSS'
93 rss_link: 95 feed_link:
94 unread: 'Unread' 96 unread: 'Unread'
95 starred: 'Starred' 97 starred: 'Starred'
96 archive: 'Archived' 98 archive: 'Archived'
97 # all: 'All' 99 # all: 'All'
98 rss_limit: 'Limită RSS' 100 feed_limit: 'Limită RSS'
99 form_user: 101 form_user:
100 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code on every new untrusted connexion" 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
101 name_label: 'Nume' 103 name_label: 'Nume'
102 email_label: 'E-mail' 104 email_label: 'E-mail'
103 # twoFactorAuthentication_label: 'Two factor authentication' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
104 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
107 # table_method: Method
108 # table_state: State
109 # table_action: Action
110 # state_enabled: Enabled
111 # state_disabled: Disabled
112 # action_email: Use email
113 # action_app: Use OTP App
105 delete: 114 delete:
106 # title: Delete my account (a.k.a danger zone) 115 # title: Delete my account (a.k.a danger zone)
107 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. 116 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@@ -159,6 +168,15 @@ config:
159 # and: 'One rule AND another' 168 # and: 'One rule AND another'
160 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 169 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
161 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 170 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
171 otp:
172 # page_title: Two-factor authentication
173 # app:
174 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
175 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
176 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
177 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
178 # cancel: Cancel
179 # enable: Enable
162 180
163entry: 181entry:
164 # default_title: 'Title of the entry' 182 # default_title: 'Title of the entry'
@@ -353,7 +371,7 @@ quickstart:
353 # title: 'Configure the application' 371 # title: 'Configure the application'
354 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' 372 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
355 # language: 'Change language and design' 373 # language: 'Change language and design'
356 # rss: 'Enable RSS feeds' 374 # feed: 'Enable RSS feeds'
357 # tagging_rules: 'Write rules to automatically tag your articles' 375 # tagging_rules: 'Write rules to automatically tag your articles'
358 # admin: 376 # admin:
359 # title: 'Administration' 377 # title: 'Administration'
@@ -404,6 +422,8 @@ tag:
404 new: 422 new:
405 # add: 'Add' 423 # add: 'Add'
406 # placeholder: 'You can add several tags, separated by a comma.' 424 # placeholder: 'You can add several tags, separated by a comma.'
425 rename:
426 # placeholder: 'You can update tag name.'
407 427
408# export: 428# export:
409# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>' 429# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@@ -529,7 +549,8 @@ user:
529 email_label: 'E-mail' 549 email_label: 'E-mail'
530 # enabled_label: 'Enabled' 550 # enabled_label: 'Enabled'
531 # last_login_label: 'Last login' 551 # last_login_label: 'Last login'
532 # twofactor_label: Two factor authentication 552 # twofactor_email_label: Two factor authentication by email
553 # twofactor_google_label: Two factor authentication by OTP app
533 # save: Save 554 # save: Save
534 # delete: Delete 555 # delete: Delete
535 # delete_confirm: Are you sure? 556 # delete_confirm: Are you sure?
@@ -567,10 +588,10 @@ flashes:
567 password_updated: 'Parolă actualizată' 588 password_updated: 'Parolă actualizată'
568 password_not_updated_demo: "In demonstration mode, you can't change password for this user." 589 password_not_updated_demo: "In demonstration mode, you can't change password for this user."
569 user_updated: 'Informație actualizată' 590 user_updated: 'Informație actualizată'
570 rss_updated: 'Informație RSS actualizată' 591 feed_updated: 'Informație RSS actualizată'
571 # tagging_rules_updated: 'Tagging rules updated' 592 # tagging_rules_updated: 'Tagging rules updated'
572 # tagging_rules_deleted: 'Tagging rule deleted' 593 # tagging_rules_deleted: 'Tagging rule deleted'
573 # rss_token_updated: 'RSS token updated' 594 # feed_token_updated: 'RSS token updated'
574 # annotations_reset: Annotations reset 595 # annotations_reset: Annotations reset
575 # tags_reset: Tags reset 596 # tags_reset: Tags reset
576 # entries_reset: Entries reset 597 # entries_reset: Entries reset
@@ -588,9 +609,11 @@ flashes:
588 entry_starred: 'Articol adăugat la favorite' 609 entry_starred: 'Articol adăugat la favorite'
589 entry_unstarred: 'Articol șters de la favorite' 610 entry_unstarred: 'Articol șters de la favorite'
590 entry_deleted: 'Articol șters' 611 entry_deleted: 'Articol șters'
612 # no_random_entry: 'No article with these criterias was found'
591 tag: 613 tag:
592 notice: 614 notice:
593 # tag_added: 'Tag added' 615 # tag_added: 'Tag added'
616 # tag_renamed: 'Tag renamed'
594 import: 617 import:
595 notice: 618 notice:
596 # failed: 'Import failed, please try again.' 619 # failed: 'Import failed, please try again.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml
index 48753b55..2ee2d83a 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.ru.yml
@@ -36,6 +36,7 @@ menu:
36 add_new_entry: 'Добавить новую запись' 36 add_new_entry: 'Добавить новую запись'
37 search: 'Поиск' 37 search: 'Поиск'
38 filter_entries: 'Фильтр записей' 38 filter_entries: 'Фильтр записей'
39 # random_entry: Jump to a random entry from that list
39 export: 'Экспорт' 40 export: 'Экспорт'
40 search_form: 41 search_form:
41 input_label: 'Введите текст для поиска' 42 input_label: 'Введите текст для поиска'
@@ -52,11 +53,12 @@ config:
52 page_title: 'Настройки' 53 page_title: 'Настройки'
53 tab_menu: 54 tab_menu:
54 settings: 'Настройки' 55 settings: 'Настройки'
55 rss: 'RSS' 56 feed: 'RSS'
56 user_info: 'Информация о пользователе' 57 user_info: 'Информация о пользователе'
57 password: 'Пароль' 58 password: 'Пароль'
58 rules: 'Правила настройки простановки тегов' 59 rules: 'Правила настройки простановки тегов'
59 new_user: 'Добавить пользователя' 60 new_user: 'Добавить пользователя'
61 reset: 'Сброс данных'
60 form: 62 form:
61 save: 'Сохранить' 63 save: 'Сохранить'
62 form_settings: 64 form_settings:
@@ -81,24 +83,31 @@ config:
81 help_reading_speed: "wallabag посчитает сколько времени занимает чтение каждой записи. Вы можете определить здесь, как быстро вы читаете. wallabag пересчитает время чтения для каждой записи." 83 help_reading_speed: "wallabag посчитает сколько времени занимает чтение каждой записи. Вы можете определить здесь, как быстро вы читаете. wallabag пересчитает время чтения для каждой записи."
82 help_language: "Вы можете изменить язык интерфейса wallabag." 84 help_language: "Вы можете изменить язык интерфейса wallabag."
83 help_pocket_consumer_key: "Обязательно для импорта из Pocket. Вы можете создать это в Вашем аккаунте на Pocket." 85 help_pocket_consumer_key: "Обязательно для импорта из Pocket. Вы можете создать это в Вашем аккаунте на Pocket."
84 form_rss: 86 form_feed:
85 description: 'RSS фид созданный с помощью wallabag позволяет читать Ваши записи через Ваш любимый RSS агрегатор. Для начала Вам потребуется создать ключ.' 87 description: 'RSS фид созданный с помощью wallabag позволяет читать Ваши записи через Ваш любимый RSS агрегатор. Для начала Вам потребуется создать ключ.'
86 token_label: 'RSS ключ' 88 token_label: 'RSS ключ'
87 no_token: 'Ключ не задан' 89 no_token: 'Ключ не задан'
88 token_create: 'Создать ключ' 90 token_create: 'Создать ключ'
89 token_reset: 'Пересоздать ключ' 91 token_reset: 'Пересоздать ключ'
90 rss_links: 'ссылка на RSS' 92 feed_links: 'ссылка на RSS'
91 rss_link: 93 feed_link:
92 unread: 'непрочитанные' 94 unread: 'непрочитанные'
93 starred: 'помеченные' 95 starred: 'помеченные'
94 archive: 'архивные' 96 archive: 'архивные'
95 rss_limit: 'Количество записей в фиде' 97 feed_limit: 'Количество записей в фиде'
96 form_user: 98 form_user:
97 two_factor_description: "Включить двухфакторную аутентификацию, Вы получите сообщение на указанный email с кодом, при каждом новом непроверенном подключении." 99 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
98 name_label: 'Имя' 100 name_label: 'Имя'
99 email_label: 'Email' 101 email_label: 'Email'
100 twoFactorAuthentication_label: 'Двухфакторная аутентификация' 102 # emailTwoFactor_label: 'Using email (receive a code by email)'
101 help_twoFactorAuthentication: "Если Вы включите двухфакторную аутентификацию, то Вы будете получать код на указанный ранее email, каждый раз при входе в wallabag." 103 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
104 # table_method: Method
105 # table_state: State
106 # table_action: Action
107 # state_enabled: Enabled
108 # state_disabled: Disabled
109 # action_email: Use email
110 # action_app: Use OTP App
102 delete: 111 delete:
103 title: "Удалить мой аккаунт (или опасная зона)" 112 title: "Удалить мой аккаунт (или опасная зона)"
104 description: "Если Вы удалите ваш аккаунт, ВСЕ ваши записи, теги и другие данные, будут БЕЗВОЗВРАТНО удалены (операция не может быть отменена после). Затем Вы выйдете из системы." 113 description: "Если Вы удалите ваш аккаунт, ВСЕ ваши записи, теги и другие данные, будут БЕЗВОЗВРАТНО удалены (операция не может быть отменена после). Затем Вы выйдете из системы."
@@ -154,6 +163,15 @@ config:
154 or: 'Одно правило ИЛИ другое' 163 or: 'Одно правило ИЛИ другое'
155 and: 'Одно правило И другое' 164 and: 'Одно правило И другое'
156 matches: 'Тесты, в которых <i> тема </i> соответствует <i> поиску </i> (без учета регистра). Пример: <code> title matches "футбол" </code>' 165 matches: 'Тесты, в которых <i> тема </i> соответствует <i> поиску </i> (без учета регистра). Пример: <code> title matches "футбол" </code>'
166 otp:
167 # page_title: Two-factor authentication
168 # app:
169 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
170 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
171 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
172 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
173 # cancel: Cancel
174 # enable: Enable
157 175
158entry: 176entry:
159 default_title: 'Название записи' 177 default_title: 'Название записи'
@@ -341,7 +359,7 @@ quickstart:
341 title: 'Настроить приложение' 359 title: 'Настроить приложение'
342 description: 'Чтобы иметь приложение, которое вам подходит, ознакомьтесь с конфигурацией wallabag.' 360 description: 'Чтобы иметь приложение, которое вам подходит, ознакомьтесь с конфигурацией wallabag.'
343 language: 'Выбрать язык и дизайн' 361 language: 'Выбрать язык и дизайн'
344 rss: 'Включить RSS фид' 362 feed: 'Включить RSS фид'
345 tagging_rules: 'Создать правило для автоматической установки тегов' 363 tagging_rules: 'Создать правило для автоматической установки тегов'
346 admin: 364 admin:
347 title: 'Администрирование' 365 title: 'Администрирование'
@@ -392,6 +410,8 @@ tag:
392 new: 410 new:
393 add: 'Добавить' 411 add: 'Добавить'
394 placeholder: 'Вы можете добавить несколько тегов, разделенных запятой.' 412 placeholder: 'Вы можете добавить несколько тегов, разделенных запятой.'
413 rename:
414 # placeholder: 'You can update tag name.'
395 415
396# export: 416# export:
397# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>' 417# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@@ -517,7 +537,8 @@ user:
517 email_label: 'Email' 537 email_label: 'Email'
518 enabled_label: 'Включить' 538 enabled_label: 'Включить'
519 last_login_label: 'Последний вход' 539 last_login_label: 'Последний вход'
520 twofactor_label: "Двухфакторная аутентификация" 540 # twofactor_email_label: Two factor authentication by email
541 # twofactor_google_label: Two factor authentication by OTP app
521 save: "Сохранить" 542 save: "Сохранить"
522 delete: "Удалить" 543 delete: "Удалить"
523 delete_confirm: "Вы уверены?" 544 delete_confirm: "Вы уверены?"
@@ -533,10 +554,10 @@ flashes:
533 password_updated: 'Пароль обновлен' 554 password_updated: 'Пароль обновлен'
534 password_not_updated_demo: "В режиме демонстрации нельзя изменять пароль для этого пользователя." 555 password_not_updated_demo: "В режиме демонстрации нельзя изменять пароль для этого пользователя."
535 user_updated: 'Информация обновлена' 556 user_updated: 'Информация обновлена'
536 rss_updated: 'RSS информация обновлена' 557 feed_updated: 'RSS информация обновлена'
537 tagging_rules_updated: 'Правила тегировния обновлены' 558 tagging_rules_updated: 'Правила тегировния обновлены'
538 tagging_rules_deleted: 'Правила тегировния удалены' 559 tagging_rules_deleted: 'Правила тегировния удалены'
539 rss_token_updated: 'RSS ключ обновлен' 560 feed_token_updated: 'RSS ключ обновлен'
540 annotations_reset: "Аннотации сброшены" 561 annotations_reset: "Аннотации сброшены"
541 tags_reset: "Теги сброшены" 562 tags_reset: "Теги сброшены"
542 entries_reset: "Записи сброшены" 563 entries_reset: "Записи сброшены"
@@ -553,9 +574,11 @@ flashes:
553 entry_starred: 'Запись помечена звездочкой' 574 entry_starred: 'Запись помечена звездочкой'
554 entry_unstarred: 'Пометка звездочкой у записи убрана' 575 entry_unstarred: 'Пометка звездочкой у записи убрана'
555 entry_deleted: 'Запись удалена' 576 entry_deleted: 'Запись удалена'
577 # no_random_entry: 'No article with these criterias was found'
556 tag: 578 tag:
557 notice: 579 notice:
558 tag_added: 'Тег добавлен' 580 tag_added: 'Тег добавлен'
581 # tag_renamed: 'Tag renamed'
559 import: 582 import:
560 notice: 583 notice:
561 failed: 'Во время импорта произошла ошибка, повторите попытку.' 584 failed: 'Во время импорта произошла ошибка, повторите попытку.'
@@ -573,4 +596,4 @@ flashes:
573 notice: 596 notice:
574 added: 'Пользователь "%username%" добавлен' 597 added: 'Пользователь "%username%" добавлен'
575 updated: 'Пользователь "%username%" обновлен' 598 updated: 'Пользователь "%username%" обновлен'
576 deleted: 'Пользователь "%username%" удален' \ No newline at end of file 599 deleted: 'Пользователь "%username%" удален'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml
index cb3b0f23..967ae427 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.th.yml
@@ -37,6 +37,7 @@ menu:
37 add_new_entry: 'เพิ่มรายการใหม่' 37 add_new_entry: 'เพิ่มรายการใหม่'
38 search: 'ค้นหา' 38 search: 'ค้นหา'
39 filter_entries: 'ตัวกรองรายการ' 39 filter_entries: 'ตัวกรองรายการ'
40 # random_entry: Jump to a random entry from that list
40 export: 'นำข้อมูลออก' 41 export: 'นำข้อมูลออก'
41 search_form: 42 search_form:
42 input_label: 'ค้นหาที่นี้' 43 input_label: 'ค้นหาที่นี้'
@@ -53,11 +54,12 @@ config:
53 page_title: 'กำหนดค่า' 54 page_title: 'กำหนดค่า'
54 tab_menu: 55 tab_menu:
55 settings: 'ตั้งค่า' 56 settings: 'ตั้งค่า'
56 rss: 'RSS' 57 feed: 'RSS'
57 user_info: 'ข้อมูลผู้ใช้' 58 user_info: 'ข้อมูลผู้ใช้'
58 password: 'รหัสผ่าน' 59 password: 'รหัสผ่าน'
59 rules: 'การแท็กข้อบังคับ' 60 rules: 'การแท็กข้อบังคับ'
60 new_user: 'เพิ่มผู้ใช้' 61 new_user: 'เพิ่มผู้ใช้'
62 reset: 'รีเซ็ตพื้นที่ '
61 form: 63 form:
62 save: 'บันทึก' 64 save: 'บันทึก'
63 form_settings: 65 form_settings:
@@ -83,25 +85,32 @@ config:
83 help_reading_speed: "wallabag จะคำนวณเวลาการอ่านในแต่ละรายการซึ่งคุณสามารถกำหนดได้ที่นี้,ต้องขอบคุณรายการนี้,หากคุณเป็นนักอ่านที่เร็วหรือช้า wallabag จะทำการคำนวณเวลาที่อ่านใหม่ในแต่ละรายการ" 85 help_reading_speed: "wallabag จะคำนวณเวลาการอ่านในแต่ละรายการซึ่งคุณสามารถกำหนดได้ที่นี้,ต้องขอบคุณรายการนี้,หากคุณเป็นนักอ่านที่เร็วหรือช้า wallabag จะทำการคำนวณเวลาที่อ่านใหม่ในแต่ละรายการ"
84 help_language: "คุณสามารถเปลี่ยภาษาของ wallabag interface ได้" 86 help_language: "คุณสามารถเปลี่ยภาษาของ wallabag interface ได้"
85 help_pocket_consumer_key: "การ้องขอการเก็บการนำข้อมูลเข้า คุณสามารถสร้างบัญชีการเก็บของคุณ" 87 help_pocket_consumer_key: "การ้องขอการเก็บการนำข้อมูลเข้า คุณสามารถสร้างบัญชีการเก็บของคุณ"
86 form_rss: 88 form_feed:
87 description: 'RSS จะเก็บเงื่อนไขโดย wallabag ต้องยอมรับการอ่านรายการของคุณกับผู้อ่านที่ชอบ RSS คุณต้องทำเครื่องหมายก่อน' 89 description: 'RSS จะเก็บเงื่อนไขโดย wallabag ต้องยอมรับการอ่านรายการของคุณกับผู้อ่านที่ชอบ RSS คุณต้องทำเครื่องหมายก่อน'
88 token_label: 'เครื่องหมาย RSS' 90 token_label: 'เครื่องหมาย RSS'
89 no_token: 'ไม่มีเครื่องหมาย' 91 no_token: 'ไม่มีเครื่องหมาย'
90 token_create: 'สร้างเครื่องหมาย' 92 token_create: 'สร้างเครื่องหมาย'
91 token_reset: 'ทำเครื่องหมาย' 93 token_reset: 'ทำเครื่องหมาย'
92 rss_links: 'ลิงค์ RSS' 94 feed_links: 'ลิงค์ RSS'
93 rss_link: 95 feed_link:
94 unread: 'ยังไมได้่อ่าน' 96 unread: 'ยังไมได้่อ่าน'
95 starred: 'ทำการแสดง' 97 starred: 'ทำการแสดง'
96 archive: 'เอกสาร' 98 archive: 'เอกสาร'
97 all: 'ทั้งหมด' 99 all: 'ทั้งหมด'
98 rss_limit: 'จำนวนไอเทมที่เก็บ' 100 feed_limit: 'จำนวนไอเทมที่เก็บ'
99 form_user: 101 form_user:
100 two_factor_description: "การเปิดใช้งาน two factor authentication คือคุณจะต้องได้รับอีเมลกับ code ที่ยังไม่ตรวจสอบในการเชื่อมต่อ" 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
101 name_label: 'ชื่อ' 103 name_label: 'ชื่อ'
102 email_label: 'อีเมล' 104 email_label: 'อีเมล'
103 twoFactorAuthentication_label: 'Two factor authentication' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
104 help_twoFactorAuthentication: "ถ้าคุณเปิด 2FA, ในแต่ละช่วงเวลาที่คุณต้องการลงชื่อเข้าใช wallabag, คุณจะต้องได้รับ code จากอีเมล" 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
107 # table_method: Method
108 # table_state: State
109 # table_action: Action
110 # state_enabled: Enabled
111 # state_disabled: Disabled
112 # action_email: Use email
113 # action_app: Use OTP App
105 delete: 114 delete:
106 title: ลบบัญชีของฉัน (โซนที่เป็นภัย!) 115 title: ลบบัญชีของฉัน (โซนที่เป็นภัย!)
107 description: ถ้าคุณลบบัญชีของคุณIf , รายการทั้งหมดของคุณ, แท็กทั้งหมดของคุณ, หมายเหตุทั้งหมดของคุณและบัญชีของคุณจะถูกลบอย่างถาวร (มันไม่สามารถยกเลิกได้) คุณจะต้องลงชื่อออก 116 description: ถ้าคุณลบบัญชีของคุณIf , รายการทั้งหมดของคุณ, แท็กทั้งหมดของคุณ, หมายเหตุทั้งหมดของคุณและบัญชีของคุณจะถูกลบอย่างถาวร (มันไม่สามารถยกเลิกได้) คุณจะต้องลงชื่อออก
@@ -159,6 +168,15 @@ config:
159 and: 'หนึ่งข้อบังคับและอื่นๆ' 168 and: 'หนึ่งข้อบังคับและอื่นๆ'
160 matches: 'ทดสอบว่า <i>เรื่อง</i> นี้ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อที่ตรงกับ "football"</code>' 169 matches: 'ทดสอบว่า <i>เรื่อง</i> นี้ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อที่ตรงกับ "football"</code>'
161 notmatches: 'ทดสอบว่า <i>เรื่อง</i> นี้ไม่ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อทีไม่ตรงกับ "football"</code>' 170 notmatches: 'ทดสอบว่า <i>เรื่อง</i> นี้ไม่ตรงกับ <i>การต้นหา</i> (กรณีไม่ทราบ).<br />ตัวอย่าง: <code>หัวข้อทีไม่ตรงกับ "football"</code>'
171 otp:
172 # page_title: Two-factor authentication
173 # app:
174 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
175 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
176 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
177 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
178 # cancel: Cancel
179 # enable: Enable
162 180
163entry: 181entry:
164 default_title: 'หัวข้อรายการ' 182 default_title: 'หัวข้อรายการ'
@@ -351,7 +369,7 @@ quickstart:
351 title: 'กำหนดค่าแอพพลิเคชั่น' 369 title: 'กำหนดค่าแอพพลิเคชั่น'
352 description: 'ภายใน order จะมี application suit ของคุณ, จะมองหาองค์ประกอบของ wallabag' 370 description: 'ภายใน order จะมี application suit ของคุณ, จะมองหาองค์ประกอบของ wallabag'
353 language: 'เปลี่ยนภาษาและออกแบบ' 371 language: 'เปลี่ยนภาษาและออกแบบ'
354 rss: 'เปิดใช้ RSS' 372 feed: 'เปิดใช้ RSS'
355 tagging_rules: 'เขียนข้อบังคับการแท็กอัตโนมัติของบทความของคุณ' 373 tagging_rules: 'เขียนข้อบังคับการแท็กอัตโนมัติของบทความของคุณ'
356 admin: 374 admin:
357 title: 'ผู้ดูแลระบบ' 375 title: 'ผู้ดูแลระบบ'
@@ -402,6 +420,8 @@ tag:
402 new: 420 new:
403 add: 'เพิ่ม' 421 add: 'เพิ่ม'
404 placeholder: 'คุณสามารถเพิ่มได้หลายแท็ก, จากการแบ่งโดย comma' 422 placeholder: 'คุณสามารถเพิ่มได้หลายแท็ก, จากการแบ่งโดย comma'
423 rename:
424 # placeholder: 'You can update tag name.'
405 425
406export: 426export:
407 footer_template: '<div style="text-align:center;"><p>ผลิตโดย wallabag กับ %method%</p><p>ให้ทำการเปิด <a href="https://github.com/wallabag/wallabag/issues">ฉบับนี้</a> ถ้าคุณมีข้อบกพร่องif you have trouble with the display of this E-Book on your device.</p></div>' 427 footer_template: '<div style="text-align:center;"><p>ผลิตโดย wallabag กับ %method%</p><p>ให้ทำการเปิด <a href="https://github.com/wallabag/wallabag/issues">ฉบับนี้</a> ถ้าคุณมีข้อบกพร่องif you have trouble with the display of this E-Book on your device.</p></div>'
@@ -527,7 +547,8 @@ user:
527 email_label: 'อีเมล' 547 email_label: 'อีเมล'
528 enabled_label: 'เปิดใช้งาน' 548 enabled_label: 'เปิดใช้งาน'
529 last_login_label: 'ลงชื้อเข้าใช้ครั้งสุดท้าย' 549 last_login_label: 'ลงชื้อเข้าใช้ครั้งสุดท้าย'
530 twofactor_label: Two factor authentication 550 # twofactor_email_label: Two factor authentication by email
551 # twofactor_google_label: Two factor authentication by OTP app
531 save: บันทึก 552 save: บันทึก
532 delete: ลบ 553 delete: ลบ
533 delete_confirm: ตุณแน่ใจหรือไม่? 554 delete_confirm: ตุณแน่ใจหรือไม่?
@@ -565,10 +586,10 @@ flashes:
565 password_updated: 'อัปเดตรหัสผ่าน' 586 password_updated: 'อัปเดตรหัสผ่าน'
566 password_not_updated_demo: "In demonstration mode, you can't change password for this user." 587 password_not_updated_demo: "In demonstration mode, you can't change password for this user."
567 user_updated: 'อัปเดตข้อมูล' 588 user_updated: 'อัปเดตข้อมูล'
568 rss_updated: 'อัปเดตข้อมูล RSS' 589 feed_updated: 'อัปเดตข้อมูล RSS'
569 tagging_rules_updated: 'อัปเดตการแท็กข้อบังคับ' 590 tagging_rules_updated: 'อัปเดตการแท็กข้อบังคับ'
570 tagging_rules_deleted: 'การลบข้อบังคับของแท็ก' 591 tagging_rules_deleted: 'การลบข้อบังคับของแท็ก'
571 rss_token_updated: 'อัปเดตเครื่องหมาย RSS ' 592 feed_token_updated: 'อัปเดตเครื่องหมาย RSS '
572 annotations_reset: รีเซ็ตหมายเหตุ 593 annotations_reset: รีเซ็ตหมายเหตุ
573 tags_reset: รีเซ็ตแท็ก 594 tags_reset: รีเซ็ตแท็ก
574 entries_reset: รีเซ็ตรายการ 595 entries_reset: รีเซ็ตรายการ
@@ -586,9 +607,11 @@ flashes:
586 entry_starred: 'รายการที่แสดง' 607 entry_starred: 'รายการที่แสดง'
587 entry_unstarred: 'รายการที่ไม่ได้แสดง' 608 entry_unstarred: 'รายการที่ไม่ได้แสดง'
588 entry_deleted: 'รายการที่ถูกลบ' 609 entry_deleted: 'รายการที่ถูกลบ'
610 # no_random_entry: 'No article with these criterias was found'
589 tag: 611 tag:
590 notice: 612 notice:
591 tag_added: 'แท็กที่เพิ่ม' 613 tag_added: 'แท็กที่เพิ่ม'
614 # tag_renamed: 'Tag renamed'
592 import: 615 import:
593 notice: 616 notice:
594 failed: 'นำข้อมูลเข้าล้มเหลว, ลองใหม่อีกครั้ง' 617 failed: 'นำข้อมูลเข้าล้มเหลว, ลองใหม่อีกครั้ง'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
index e2156d47..2f86f25d 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/messages.tr.yml
@@ -37,6 +37,7 @@ menu:
37 add_new_entry: 'Yeni bir makale ekle' 37 add_new_entry: 'Yeni bir makale ekle'
38 search: 'Ara' 38 search: 'Ara'
39 filter_entries: 'Filtrele' 39 filter_entries: 'Filtrele'
40 # random_entry: Jump to a random entry from that list
40 export: 'Dışa Aktar' 41 export: 'Dışa Aktar'
41 search_form: 42 search_form:
42 input_label: 'Aramak istediğiniz herhangi bir şey yazın' 43 input_label: 'Aramak istediğiniz herhangi bir şey yazın'
@@ -53,11 +54,12 @@ config:
53 page_title: 'Yapılandırma' 54 page_title: 'Yapılandırma'
54 tab_menu: 55 tab_menu:
55 settings: 'Ayarlar' 56 settings: 'Ayarlar'
56 rss: 'RSS' 57 feed: 'RSS'
57 user_info: 'Kullanıcı bilgileri' 58 user_info: 'Kullanıcı bilgileri'
58 password: 'Şifre' 59 password: 'Şifre'
59 rules: 'Etiketleme kuralları' 60 rules: 'Etiketleme kuralları'
60 new_user: 'Bir kullanıcı ekle' 61 new_user: 'Bir kullanıcı ekle'
62 # reset: 'Reset area'
61 form: 63 form:
62 save: 'Kaydet' 64 save: 'Kaydet'
63 form_settings: 65 form_settings:
@@ -83,25 +85,32 @@ config:
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." 85 # 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."
84 # help_language: "You can change the language of wallabag interface." 86 # help_language: "You can change the language of wallabag interface."
85 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account." 87 # help_pocket_consumer_key: "Required for Pocket import. You can create it in your Pocket account."
86 form_rss: 88 form_feed:
87 description: 'wallabag RSS akışı kaydetmiş olduğunuz makalelerini favori RSS okuyucunuzda görüntülemenizi sağlar. Bunu yapabilmek için öncelikle belirteç (token) oluşturmalısınız.' 89 description: 'wallabag RSS akışı kaydetmiş olduğunuz makalelerini favori RSS okuyucunuzda görüntülemenizi sağlar. Bunu yapabilmek için öncelikle belirteç (token) oluşturmalısınız.'
88 token_label: 'RSS belirteci (token)' 90 token_label: 'RSS belirteci (token)'
89 no_token: 'Belirteç (token) yok' 91 no_token: 'Belirteç (token) yok'
90 token_create: 'Yeni belirteç (token) oluştur' 92 token_create: 'Yeni belirteç (token) oluştur'
91 token_reset: 'Belirteci (token) sıfırla' 93 token_reset: 'Belirteci (token) sıfırla'
92 rss_links: 'RSS akış bağlantıları' 94 feed_links: 'RSS akış bağlantıları'
93 rss_link: 95 feed_link:
94 unread: 'Okunmayan' 96 unread: 'Okunmayan'
95 starred: 'Favoriler' 97 starred: 'Favoriler'
96 archive: 'Arşiv' 98 archive: 'Arşiv'
97 # all: 'All' 99 # all: 'All'
98 rss_limit: 'RSS içeriğinden talep edilecek makale limiti' 100 feed_limit: 'RSS içeriğinden talep edilecek makale limiti'
99 form_user: 101 form_user:
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." 102 # two_factor_description: "Enabling two factor authentication means you'll receive an email with a code OR need to use an OTP app (like Google Authenticator, Authy or FreeOTP) to get a one time code on every new untrusted connection. You can't choose both option."
101 name_label: 'İsim' 103 name_label: 'İsim'
102 email_label: 'E-posta' 104 email_label: 'E-posta'
103 twoFactorAuthentication_label: 'İki adımlı doğrulama' 105 # emailTwoFactor_label: 'Using email (receive a code by email)'
104 # help_twoFactorAuthentication: "If you enable 2FA, each time you want to login to wallabag, you'll receive a code by email." 106 # googleTwoFactor_label: 'Using an OTP app (open the app, like Google Authenticator, Authy or FreeOTP, to get a one time code)'
107 # table_method: Method
108 # table_state: State
109 # table_action: Action
110 # state_enabled: Enabled
111 # state_disabled: Disabled
112 # action_email: Use email
113 # action_app: Use OTP App
105 delete: 114 delete:
106 # title: Delete my account (a.k.a danger zone) 115 # title: Delete my account (a.k.a danger zone)
107 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out. 116 # description: If you remove your account, ALL your articles, ALL your tags, ALL your annotations and your account will be PERMANENTLY removed (it can't be UNDONE). You'll then be logged out.
@@ -159,6 +168,15 @@ config:
159 and: 'Bir kural ve diğeri' 168 and: 'Bir kural ve diğeri'
160 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>' 169 # matches: 'Tests that a <i>subject</i> matches a <i>search</i> (case-insensitive).<br />Example: <code>title matches "football"</code>'
161 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>' 170 # notmatches: 'Tests that a <i>subject</i> doesn''t match match a <i>search</i> (case-insensitive).<br />Example: <code>title notmatches "football"</code>'
171 otp:
172 # page_title: Two-factor authentication
173 # app:
174 # two_factor_code_description_1: You just enabled the OTP two factor authentication, open your OTP app and use that code to get a one time password. It'll disapear after a page reload.
175 # two_factor_code_description_2: 'You can scan that QR Code with your app:'
176 # two_factor_code_description_3: 'Also, save these backup codes in a safe place, you can use them in case you lose access to your OTP app:'
177 # two_factor_code_description_4: 'Test an OTP code from your configured app:'
178 # cancel: Cancel
179 # enable: Enable
162 180
163entry: 181entry:
164 default_title: 'Makalenin başlığı' 182 default_title: 'Makalenin başlığı'
@@ -351,7 +369,7 @@ quickstart:
351 title: 'Uygulamayı Yapılandırma' 369 title: 'Uygulamayı Yapılandırma'
352 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.' 370 # description: 'In order to have an application which suits you, have a look into the configuration of wallabag.'
353 language: 'Dili ve tasarımı değiştirme' 371 language: 'Dili ve tasarımı değiştirme'
354 rss: 'RSS akışını aktifleştirme' 372 feed: 'RSS akışını aktifleştirme'
355 # tagging_rules: 'Write rules to automatically tag your articles' 373 # tagging_rules: 'Write rules to automatically tag your articles'
356 admin: 374 admin:
357 # title: 'Administration' 375 # title: 'Administration'
@@ -402,6 +420,8 @@ tag:
402 new: 420 new:
403 # add: 'Add' 421 # add: 'Add'
404 # placeholder: 'You can add several tags, separated by a comma.' 422 # placeholder: 'You can add several tags, separated by a comma.'
423 rename:
424 # placeholder: 'You can update tag name.'
405 425
406# export: 426# export:
407# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>' 427# footer_template: '<div style="text-align:center;"><p>Produced by wallabag with %method%</p><p>Please open <a href="https://github.com/wallabag/wallabag/issues">an issue</a> if you have trouble with the display of this E-Book on your device.</p></div>'
@@ -527,7 +547,8 @@ user:
527 email_label: 'E-posta' 547 email_label: 'E-posta'
528 # enabled_label: 'Enabled' 548 # enabled_label: 'Enabled'
529 # last_login_label: 'Last login' 549 # last_login_label: 'Last login'
530 # twofactor_label: Two factor authentication 550 # twofactor_email_label: Two factor authentication by email
551 # twofactor_google_label: Two factor authentication by OTP app
531 # save: Save 552 # save: Save
532 # delete: Delete 553 # delete: Delete
533 # delete_confirm: Are you sure? 554 # delete_confirm: Are you sure?
@@ -545,10 +566,10 @@ flashes:
545 password_updated: 'Şifre güncellendi' 566 password_updated: 'Şifre güncellendi'
546 password_not_updated_demo: "In demonstration mode, you can't change password for this user." 567 password_not_updated_demo: "In demonstration mode, you can't change password for this user."
547 user_updated: 'Bilgiler güncellendi' 568 user_updated: 'Bilgiler güncellendi'
548 rss_updated: 'RSS bilgiler güncellendi' 569 feed_updated: 'RSS bilgiler güncellendi'
549 tagging_rules_updated: 'Tagging rules updated' 570 tagging_rules_updated: 'Tagging rules updated'
550 tagging_rules_deleted: 'Tagging rule deleted' 571 tagging_rules_deleted: 'Tagging rule deleted'
551 rss_token_updated: 'RSS token updated' 572 feed_token_updated: 'RSS token updated'
552 # annotations_reset: Annotations reset 573 # annotations_reset: Annotations reset
553 # tags_reset: Tags reset 574 # tags_reset: Tags reset
554 # entries_reset: Entries reset 575 # entries_reset: Entries reset
@@ -566,9 +587,11 @@ flashes:
566 entry_starred: 'Makale favorilere eklendi' 587 entry_starred: 'Makale favorilere eklendi'
567 entry_unstarred: 'Makale favorilerden çıkartıldı' 588 entry_unstarred: 'Makale favorilerden çıkartıldı'
568 entry_deleted: 'Makale silindi' 589 entry_deleted: 'Makale silindi'
590 # no_random_entry: 'No article with these criterias was found'
569 tag: 591 tag:
570 notice: 592 notice:
571 tag_added: 'Etiket eklendi' 593 tag_added: 'Etiket eklendi'
594 # tag_renamed: 'Tag renamed'
572 import: 595 import:
573 notice: 596 notice:
574 # failed: 'Import failed, please try again.' 597 # failed: 'Import failed, please try again.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.da.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.da.yml
index c6a84209..c0438978 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.da.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.da.yml
@@ -3,5 +3,5 @@ validator:
3 password_too_short: 'Adgangskoden skal være mindst 8 tegn' 3 password_too_short: 'Adgangskoden skal være mindst 8 tegn'
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 # feed_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.' 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 907b67a5..4c675ef4 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.de.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.de.yml
@@ -3,6 +3,5 @@ validator:
3 password_too_short: 'Kennwort-Mindestlänge von acht Zeichen nicht erfüllt' 3 password_too_short: 'Kennwort-Mindestlänge von acht Zeichen nicht erfüllt'
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 feed_limit_too_high: 'Dies wird die Anwendung möglicherweise beenden'
7 quote_length_too_high: 'Das Zitat ist zu lang. Es sollte nicht mehr als {{ limit }} Zeichen enthalten.' 7 quote_length_too_high: 'Das Zitat ist zu lang. Es sollte nicht mehr als {{ limit }} Zeichen enthalten.'
8
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.en.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.en.yml
index 8cc117fe..89d4c68a 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.en.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.en.yml
@@ -3,5 +3,5 @@ validator:
3 password_too_short: 'Password should by at least 8 chars long' 3 password_too_short: 'Password should by at least 8 chars long'
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 feed_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.' 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 97a8edfa..ba34ee76 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.es.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.es.yml
@@ -3,5 +3,5 @@ validator:
3 password_too_short: 'La contraseña debe tener al menos 8 carácteres' 3 password_too_short: 'La contraseña debe tener al menos 8 carácteres'
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 feed_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.' 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 ef677525..9b1a4af2 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.fa.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.fa.yml
@@ -3,5 +3,5 @@ validator:
3 password_too_short: 'رمز شما باید ۸ حرف یا بیشتر باشد' 3 password_too_short: 'رمز شما باید ۸ حرف یا بیشتر باشد'
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 feed_limit_too_high: 'با این تعداد برنامه به فنا می‌رود'
7 # quote_length_too_high: 'The quote is too long. It should have {{ limit }} characters or less.' 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 f31b4ed2..92f69aa0 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.fr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.fr.yml
@@ -3,5 +3,5 @@ validator:
3 password_too_short: "Le mot de passe doit contenir au moins 8 caractères" 3 password_too_short: "Le mot de passe doit contenir au moins 8 caractères"
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 feed_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." 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 d949cc3b..b20d6f51 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.it.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.it.yml
@@ -3,5 +3,5 @@ validator:
3 password_too_short: 'La password deve essere lunga almeno 8 caratteri' 3 password_too_short: 'La password deve essere lunga almeno 8 caratteri'
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 feed_limit_too_high: 'Questo valore è troppo alto'
7 # quote_length_too_high: 'The quote is too long. It should have {{ limit }} characters or less.' 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 87f00f10..cb57844f 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.oc.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.oc.yml
@@ -3,5 +3,5 @@ validator:
3 password_too_short: 'Lo senhal deu aver almens 8 caractèrs' 3 password_too_short: 'Lo senhal deu aver almens 8 caractèrs'
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 feed_limit_too_high: "Aquò li agradarà pas a l'aplicacion"
7 quote_length_too_high: 'Aquesta citacion es tròpa longa. Cal que faga {{ limit }} caractèrs o mens.' 7 quote_length_too_high: 'Aquesta citacion es tròpa longa. Cal que faga {{ limit }} caractèrs o mens.'
diff --git a/src/Wallabag/CoreBundle/Resources/translations/validators.pl.yml b/src/Wallabag/CoreBundle/Resources/translations/validators.pl.yml
index e4165c14..94757cc5 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.pl.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.pl.yml
@@ -3,5 +3,5 @@ validator:
3 password_too_short: 'Hasło powinno mieć minimum 8 znaków długości' 3 password_too_short: 'Hasło powinno mieć minimum 8 znaków długości'
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 feed_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.' 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 a8c1f9de..df2f3f35 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.pt.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.pt.yml
@@ -3,5 +3,5 @@ validator:
3 password_too_short: 'A senha deve ter pelo menos 8 caracteres' 3 password_too_short: 'A senha deve ter pelo menos 8 caracteres'
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 feed_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.' 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 6840cf11..e5c8a72f 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.ro.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.ro.yml
@@ -3,5 +3,5 @@ validator:
3 password_too_short: 'Parola ar trebui să conțină cel puțin 8 caractere' 3 password_too_short: 'Parola ar trebui să conțină cel puțin 8 caractere'
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 # feed_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.' 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 e1e7317f..881ffd3b 100644
--- a/src/Wallabag/CoreBundle/Resources/translations/validators.tr.yml
+++ b/src/Wallabag/CoreBundle/Resources/translations/validators.tr.yml
@@ -3,5 +3,5 @@ validator:
3 # password_too_short: 'Password should by at least 8 chars long' 3 # password_too_short: 'Password should by at least 8 chars long'
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 # feed_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.' 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 bcc57dac..4ef6ab3c 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
@@ -86,8 +86,7 @@
86 <br/> 86 <br/>
87 <img id="androidQrcode" /> 87 <img id="androidQrcode" />
88 <script> 88 <script>
89 const imgBase64 = jrQrcode.getQrBase64('wallabag://{{ app.user.username }}@{{ wallabag_url }}'); 89 document.getElementById('androidQrcode').src = jrQrcode.getQrBase64('wallabag://{{ app.user.username }}@{{ wallabag_url }}');
90 document.getElementById('androidQrcode').src = imgBase64;
91 </script> 90 </script>
92 </div> 91 </div>
93 </fieldset> 92 </fieldset>
@@ -95,43 +94,43 @@
95 {{ form_rest(form.config) }} 94 {{ form_rest(form.config) }}
96 </form> 95 </form>
97 96
98 <h2>{{ 'config.tab_menu.rss'|trans }}</h2> 97 <h2>{{ 'config.tab_menu.feed'|trans }}</h2>
99 98
100 {{ form_start(form.rss) }} 99 {{ form_start(form.feed) }}
101 {{ form_errors(form.rss) }} 100 {{ form_errors(form.feed) }}
102 101
103 <div class="row"> 102 <div class="row">
104 {{ 'config.form_rss.description'|trans }} 103 {{ 'config.form_feed.description'|trans }}
105 </div> 104 </div>
106 105
107 <fieldset class="w500p inline"> 106 <fieldset class="w500p inline">
108 <div class="row"> 107 <div class="row">
109 <label>{{ 'config.form_rss.token_label'|trans }}</label> 108 <label>{{ 'config.form_feed.token_label'|trans }}</label>
110 {% if rss.token %} 109 {% if feed.token %}
111 {{ rss.token }} 110 {{ feed.token }}
112 {% else %} 111 {% else %}
113 <em>{{ 'config.form_rss.no_token'|trans }}</em> 112 <em>{{ 'config.form_feed.no_token'|trans }}</em>
114 {% endif %} 113 {% endif %}
115 114
116 <a href="{{ path('generate_token') }}"> 115 <a href="{{ path('generate_token') }}">
117 {% if rss.token %} 116 {% if feed.token %}
118 {{ 'config.form_rss.token_reset'|trans }} 117 {{ 'config.form_feed.token_reset'|trans }}
119 {% else %} 118 {% else %}
120 {{ 'config.form_rss.token_create'|trans }} 119 {{ 'config.form_feed.token_create'|trans }}
121 {% endif %} 120 {% endif %}
122 </a> 121 </a>
123 </div> 122 </div>
124 </fieldset> 123 </fieldset>
125 124
126 {% if rss.token %} 125 {% if feed.token %}
127 <fieldset class="w500p inline"> 126 <fieldset class="w500p inline">
128 <div class="row"> 127 <div class="row">
129 <label>{{ 'config.form_rss.rss_links'|trans }}</label> 128 <label>{{ 'config.form_feed.feed_links'|trans }}</label>
130 <ul> 129 <ul>
131 <li><a href="{{ path('unread_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.unread'|trans }}</a></li> 130 <li><a href="{{ path('unread_feed', {'username': feed.username, 'token': feed.token}) }}">{{ 'config.form_feed.feed_link.unread'|trans }}</a></li>
132 <li><a href="{{ path('starred_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.starred'|trans }}</a></li> 131 <li><a href="{{ path('starred_feed', {'username': feed.username, 'token': feed.token}) }}">{{ 'config.form_feed.feed_link.starred'|trans }}</a></li>
133 <li><a href="{{ path('archive_rss', {'username': rss.username, 'token': rss.token}) }}">{{ 'config.form_rss.rss_link.archive'|trans }}</a></li> 132 <li><a href="{{ path('archive_feed', {'username': feed.username, 'token': feed.token}) }}">{{ 'config.form_feed.feed_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> 133 <li><a href="{{ path('all_feed', {'username': feed.username, 'token': feed.token}) }}">{{ 'config.form_feed.feed_link.all'|trans }}</a></li>
135 </ul> 134 </ul>
136 </div> 135 </div>
137 </fieldset> 136 </fieldset>
@@ -139,13 +138,13 @@
139 138
140 <fieldset class="w500p inline"> 139 <fieldset class="w500p inline">
141 <div class="row"> 140 <div class="row">
142 {{ form_label(form.rss.rss_limit) }} 141 {{ form_label(form.feed.feed_limit) }}
143 {{ form_errors(form.rss.rss_limit) }} 142 {{ form_errors(form.feed.feed_limit) }}
144 {{ form_widget(form.rss.rss_limit) }} 143 {{ form_widget(form.feed.feed_limit) }}
145 </div> 144 </div>
146 </fieldset> 145 </fieldset>
147 146
148 {{ form_rest(form.rss) }} 147 {{ form_rest(form.feed) }}
149 </form> 148 </form>
150 149
151 <h2>{{ 'config.tab_menu.user_info'|trans }}</h2> 150 <h2>{{ 'config.tab_menu.user_info'|trans }}</h2>
@@ -169,52 +168,41 @@
169 </div> 168 </div>
170 </fieldset> 169 </fieldset>
171 170
171 {{ form_widget(form.user.save) }}
172
172 {% if twofactor_auth %} 173 {% if twofactor_auth %}
174 <h5>{{ 'config.otp.page_title'|trans }}</h5>
175
173 <div class="row"> 176 <div class="row">
174 {{ 'config.form_user.two_factor_description'|trans }} 177 {{ 'config.form_user.two_factor_description'|trans }}
175 </div> 178 </div>
176 179
177 <fieldset class="w500p inline"> 180 <table>
178 <div class="row"> 181 <thead>
179 {{ form_label(form.user.twoFactorAuthentication) }} 182 <tr>
180 {{ form_errors(form.user.twoFactorAuthentication) }} 183 <th>{{ 'config.form_user.two_factor.table_method'|trans }}</th>
181 {{ form_widget(form.user.twoFactorAuthentication) }} 184 <th>{{ 'config.form_user.two_factor.table_state'|trans }}</th>
182 </div> 185 <th>{{ 'config.form_user.two_factor.table_action'|trans }}</th>
183 <a href="#" title="{{ 'config.form_user.help_twoFactorAuthentication'|trans }}"> 186 </tr>
184 <i class="material-icons">live_help</i> 187 </thead>
185 </a>
186 </fieldset>
187 {% endif %}
188 188
189 <h2>{{ 'config.reset.title'|trans }}</h2> 189 <tbody>
190 <fieldset class="w500p inline"> 190 <tr>
191 <p>{{ 'config.reset.description'|trans }}</p> 191 <td>{{ 'config.form_user.two_factor.emailTwoFactor_label'|trans }}</td>
192 <ul> 192 <td>{% if app.user.isEmailTwoFactor %}<b>{{ 'config.form_user.two_factor.state_enabled'|trans }}</b>{% else %}{{ 'config.form_user.two_factor.state_disabled'|trans }}{% endif %}</td>
193 <li> 193 <td><a href="{{ path('config_otp_email') }}" class="waves-effect waves-light btn{% if app.user.isEmailTwoFactor %} disabled{% endif %}">{{ 'config.form_user.two_factor.action_email'|trans }}</a></td>
194 <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red"> 194 </tr>
195 {{ 'config.reset.annotations'|trans }} 195 <tr>
196 </a> 196 <td>{{ 'config.form_user.two_factor.googleTwoFactor_label'|trans }}</td>
197 </li> 197 <td>{% if app.user.isGoogleTwoFactor %}<b>{{ 'config.form_user.two_factor.state_enabled'|trans }}</b>{% else %}{{ 'config.form_user.two_factor.state_disabled'|trans }}{% endif %}</td>
198 <li> 198 <td><a href="{{ path('config_otp_app') }}" class="waves-effect waves-light btn{% if app.user.isGoogleTwoFactor %} disabled{% endif %}">{{ 'config.form_user.two_factor.action_app'|trans }}</a></td>
199 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red"> 199 </tr>
200 {{ 'config.reset.tags'|trans }} 200 </tbody>
201 </a> 201 </table>
202 </li> 202
203 <li> 203 {% endif %}
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>
209 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
210 {{ 'config.reset.entries'|trans }}
211 </a>
212 </li>
213 </ul>
214 </fieldset>
215 204
216 {{ form_widget(form.user._token) }} 205 {{ form_widget(form.user._token) }}
217 {{ form_widget(form.user.save) }}
218 </form> 206 </form>
219 207
220 {% if enabled_users > 1 %} 208 {% if enabled_users > 1 %}
@@ -277,7 +265,7 @@
277 {% endfor %} 265 {% endfor %}
278 </ul> 266 </ul>
279 267
280 {{ form_start(form.new_tagging_rule) }} 268 {{ form_start(form.new_tagging_rule) }}
281 {{ form_errors(form.new_tagging_rule) }} 269 {{ form_errors(form.new_tagging_rule) }}
282 270
283 <fieldset class="w500p inline"> 271 <fieldset class="w500p inline">
@@ -382,4 +370,31 @@
382 </table> 370 </table>
383 </div> 371 </div>
384 </div> 372 </div>
373
374 <h2>{{ 'config.reset.title'|trans }}</h2>
375 <fieldset class="w500p inline">
376 <p>{{ 'config.reset.description'|trans }}</p>
377 <ul>
378 <li>
379 <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
380 {{ 'config.reset.annotations'|trans }}
381 </a>
382 </li>
383 <li>
384 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
385 {{ 'config.reset.tags'|trans }}
386 </a>
387 </li>
388 <li>
389 <a href="{{ path('config_reset', { type: 'archived'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
390 {{ 'config.reset.archived'|trans }}
391 </a>
392 </li>
393 <li>
394 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
395 {{ 'config.reset.entries'|trans }}
396 </a>
397 </li>
398 </ul>
399 </fieldset>
385{% endblock %} 400{% endblock %}
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/otp_app.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/otp_app.html.twig
new file mode 100644
index 00000000..0919646e
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/baggy/Config/otp_app.html.twig
@@ -0,0 +1,55 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{{ 'config.page_title'|trans }} > {{ 'config.otp.page_title'|trans }}{% endblock %}
4
5{% block content %}
6 <h5>{{ 'config.otp.page_title'|trans }}</h5>
7
8 <ol>
9 <li>
10 <p>{{ 'config.otp.app.two_factor_code_description_1'|trans }}</p>
11 <p>{{ 'config.otp.app.two_factor_code_description_2'|trans }}</p>
12
13 <p>
14 <img id="2faQrcode" class="hide-on-med-and-down" />
15 <script>
16 document.getElementById('2faQrcode').src = jrQrcode.getQrBase64('{{ qr_code }}');
17 </script>
18 </p>
19 </li>
20 <li>
21 <p>{{ 'config.otp.app.two_factor_code_description_3'|trans }}</p>
22
23 <p><strong>{{ backupCodes|join("\n")|nl2br }}</strong></p>
24 </li>
25 <li>
26 <p>{{ 'config.otp.app.two_factor_code_description_4'|trans }}</p>
27
28 {% for flashMessage in app.session.flashbag.get("two_factor") %}
29 <div class="card-panel red darken-1 black-text">
30 {{ flashMessage|trans }}
31 </div>
32 {% endfor %}
33
34 <form class="form" action="{{ path("config_otp_app_check") }}" method="post">
35 <div class="card-content">
36 <div class="row">
37 <div class="input-field col s12">
38 <label for="_auth_code">{{ "scheb_two_factor.auth_code"|trans }}</label>
39 <input id="_auth_code" type="text" autocomplete="off" name="_auth_code" />
40 </div>
41 </div>
42 </div>
43 <div class="card-action">
44 <a href="{{ path('config_otp_app_cancel') }}" class="waves-effect waves-light grey btn">
45 {{ 'config.otp.app.cancel'|trans }}
46 </a>
47 <button class="btn waves-effect waves-light" type="submit" name="send">
48 {{ 'config.otp.app.enable'|trans }}
49 <i class="material-icons right">send</i>
50 </button>
51 </div>
52 </form>
53 </li>
54 </ol>
55{% endblock %}
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 832112be..6c5d2601 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
@@ -2,8 +2,8 @@
2 2
3{% block head %} 3{% block head %}
4 {{ parent() }} 4 {{ parent() }}
5 {% if tag is defined and app.user.config.rssToken %} 5 {% if tag is defined and app.user.config.feedToken %}
6 <link rel="alternate" type="application/rss+xml" href="{{ path('tag_rss', {'username': app.user.username, 'token': app.user.config.rssToken, 'slug': tag.slug}) }}" /> 6 <link rel="alternate" type="application/atom+xml" href="{{ path('tag_feed', {'username': app.user.username, 'token': app.user.config.feedToken, 'slug': tag.slug}) }}" />
7 {% endif %} 7 {% endif %}
8{% endblock %} 8{% endblock %}
9 9
@@ -20,13 +20,19 @@
20 20
21{% block content %} 21{% block content %}
22 {% set currentRoute = app.request.attributes.get('_route') %} 22 {% set currentRoute = app.request.attributes.get('_route') %}
23 {% if currentRoute == 'homepage' %}
24 {% set currentRoute = 'unread' %}
25 {% endif %}
23 {% set listMode = app.user.config.listMode %} 26 {% set listMode = app.user.config.listMode %}
24 <div class="results"> 27 <div class="results">
25 <div class="nb-results">{{ 'entry.list.number_on_the_page'|transchoice(entries.count) }}</div> 28 <div class="nb-results">{{ 'entry.list.number_on_the_page'|transchoice(entries.count) }}</div>
26 <div class="pagination"> 29 <div class="pagination">
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> 30 <a href="{{ path('switch_view_mode') }}"><i class="listMode-btn material-icons md-24">{% if listMode == 0 %}list{% else %}view_module{% endif %}</i></a>
28 {% if app.user.config.rssToken %} 31 {% if app.user.config.feedToken %}
29 {% include "@WallabagCore/themes/common/Entry/_rss_link.html.twig" %} 32 {% include "@WallabagCore/themes/common/Entry/_feed_link.html.twig" %}
33 {% endif %}
34 {% if currentRoute in ['unread', 'starred', 'archive', 'untagged', 'all'] %}
35 <a href="{{ path('random_entry', { 'type': currentRoute }) }}"><i class="btn-clickable material-icons md-24 js-random-action">casino</i></a>
30 {% endif %} 36 {% endif %}
31 <i class="btn-clickable download-btn material-icons md-24 js-export-action">file_download</i> 37 <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> 38 <i class="btn-clickable filter-btn material-icons md-24 js-filters-action">filter_list</i>
@@ -90,9 +96,6 @@
90 {% if tag is defined %} 96 {% if tag is defined %}
91 {% set currentTag = tag %} 97 {% set currentTag = tag %}
92 {% endif %} 98 {% endif %}
93 {% if currentRoute == 'homepage' %}
94 {% set currentRoute = 'unread' %}
95 {% endif %}
96 <h2>{{ 'entry.list.export_title'|trans }}</h2> 99 <h2>{{ 'entry.list.export_title'|trans }}</h2>
97 <a href="javascript: void(null);" id="download-form-close" class="close-button--popup close-button">&times;</a> 100 <a href="javascript: void(null);" id="download-form-close" class="close-button--popup close-button">&times;</a>
98 <ul> 101 <ul>
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 070d5629..ae8403bd 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
@@ -10,10 +10,22 @@
10 <ul> 10 <ul>
11 {% for tag in tags %} 11 {% for tag in tags %}
12 <li id="tag-{{ tag.id|e }}"> 12 <li id="tag-{{ tag.id|e }}">
13 <a href="{{ path('tag_entries', {'slug': tag.slug}) }}">{{tag.label}} ({{ tag.nbEntries }})</a> 13 <a href="{{ path('tag_entries', {'slug': tag.slug}) }}" data-handle="tag-link">{{ tag.label }}&nbsp;({{ 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"> 14
15 <i class="material-icons md-24">rss_feed</i> 15 {% if renameForms is defined and renameForms[tag.id] is defined %}
16 <form class="card-tag-form hidden" data-handle="tag-rename-form" action="{{ path('tag_rename', {'slug': tag.slug})}}" method="POST">
17 {{ form_widget(renameForms[tag.id].label, {'attr': {'value': tag.label}}) }}
18 {{ form_rest(renameForms[tag.id]) }}
19 </form>
20 <a class="card-tag-rename" data-handler="tag-rename" href="javascript:void(0);">
21 <i class="material-icons">mode_edit</i>
16 </a> 22 </a>
23 {% endif %}
24 {% if app.user.config.feedToken %}
25 <a rel="alternate" type="application/atom+xml" href="{{ path('tag_feed', {'username': app.user.username, 'token': app.user.config.feedToken, 'slug': tag.slug}) }}" class="right">
26 <i class="material-icons md-24">rss_feed</i>
27 </a>
28 {% endif %}
17 </li> 29 </li>
18 {% endfor %} 30 {% endfor %}
19 </ul> 31 </ul>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_feed_link.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_feed_link.html.twig
new file mode 100644
index 00000000..6df4c160
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_feed_link.html.twig
@@ -0,0 +1,11 @@
1{% if tag is defined %}
2 <a rel="alternate" type="application/atom+xml" href="{{ path('tag_feed', {'username': app.user.username, 'token': app.user.config.feedToken, 'slug': tag.slug}) }}" class="right"><i class="material-icons md-24">rss_feed</i></a>
3{% elseif currentRoute in ['homepage', 'unread', 'starred', 'archive', 'all'] %}
4 {% set feedRoute = currentRoute %}
5 {% if currentRoute == 'homepage' %}
6 {% set feedRoute = 'unread' %}
7 {% endif %}
8 {% set feedRoute = feedRoute ~ '_feed' %}
9
10 <a rel="alternate" type="application/atom+xml" href="{{ path(feedRoute, {'username': app.user.username, 'token': app.user.config.feedToken}) }}" class="right"><i class="material-icons">rss_feed</i></a>
11{% endif %}
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
deleted file mode 100644
index eb26054c..00000000
--- a/src/Wallabag/CoreBundle/Resources/views/themes/common/Entry/_rss_link.html.twig
+++ /dev/null
@@ -1,11 +0,0 @@
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 ['homepage', 'unread', 'starred', 'archive', 'all'] %}
4 {% set rssRoute = currentRoute %}
5 {% if currentRoute == 'homepage' %}
6 {% set rssRoute = 'unread' %}
7 {% endif %}
8 {% set rssRoute = rssRoute ~ '_rss' %}
9
10 <a rel="alternate" type="application/rss+xml" href="{{ path(rssRoute, {'username': app.user.username, 'token': app.user.config.rssToken}) }}" class="right"><i class="material-icons">rss_feed</i></a>
11{% endif %}
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 25d09ec3..cf6f6571 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,34 +1,53 @@
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<feed xmlns="http://www.w3.org/2005/Atom">
3 <channel> 3 {% if type != 'tag' %}
4 <title>wallabag - {{ type }} feed</title> 4 <title>wallabag — {{type}} feed</title>
5 <link>{{ url_html }}</link> 5 <subtitle type="html">Atom feed for {{ type }} entries</subtitle>
6 <link rel="self" href="{{ app.request.uri }}"/> 6 <id>wallabag:{{ domainName | removeScheme | removeWww }}:{{ user }}:{{ type }}</id>
7 {% if entries.hasPreviousPage -%} 7 <link rel="alternate" type="text/html" href="{{ url(type) }}"/>
8 <link rel="previous" href="{{ url }}?page={{ entries.previousPage }}"/> 8 {% else %}
9 {% endif -%} 9 <id>wallabag:{{ domainName | removeScheme | removeWww }}:{{ user }}:{{ type }}:{{ tag }}</id>
10 {% if entries.hasNextPage -%} 10 <link rel="alternate" type="text/html" href="{{ url('tag_entries', {'slug': tag}) }}"/>
11 <link rel="next" href="{{ url }}?page={{ entries.nextPage }}"/> 11 <title>wallabag — {{type}} {{ tag }} feed</title>
12 {% endif -%} 12 <subtitle type="html">Atom feed for entries tagged with {{ tag }}</subtitle>
13 <link rel="last" href="{{ url }}?page={{ entries.nbPages }}"/> 13 {% endif %}
14 <pubDate>{{ "now"|date(constant('DATE_RSS')) }}</pubDate> 14 {% if entries | length > 0 %}
15 <generator>wallabag</generator> 15 <updated>{{ (entries | first).createdAt | date('c') }}</updated> {# Indicates the last time the feed was modified in a significant way. #}
16 <description>wallabag {{ type }} elements</description> 16 {% endif %}
17 17 <link rel="self" type="application/atom+xml" href="{{ app.request.uri }}"/>
18 {% for entry in entries %} 18 {% if entries.hasPreviousPage %}
19 19 <link rel="previous" href="{{ url }}/{{ entries.previousPage }}"/>
20 <item> 20 {% endif -%}
21 <title><![CDATA[{{ entry.title|e }}]]></title> 21 {% if entries.hasNextPage %}
22 <source url="{{ url('view', { 'id': entry.id }) }}">wallabag</source> 22 <link rel="next" href="{{ url }}/{{ entries.nextPage }}"/>
23 <link>{{ entry.url }}</link> 23 {% endif -%}
24 <guid>{{ entry.url }}</guid> 24 <link rel="last" href="{{ url }}/{{ entries.nbPages }}"/>
25 <pubDate>{{ entry.createdAt|date(constant('DATE_RSS')) }}</pubDate> 25 <generator uri="https://wallabag.org" version="{{ version }}">wallabag</generator>
26 <description> 26 <author>
27 <![CDATA[{%- if entry.readingTime > 0 -%}{{ 'entry.list.reading_time_minutes'|trans({'%readingTime%': entry.readingTime}) }}{%- else -%}{{ 'entry.list.reading_time_less_one_minute'|trans|raw }}{%- endif %}{{ entry.content|raw -}}]]> 27 <name>{{ user }}</name>
28 </description> 28 </author>
29 </item> 29 <icon>{{ asset('favicon.ico') }}</icon>
30 30 <logo>{{ asset('bundles/wallabagcore/themes/_global/img/logo-square.png') }}</logo>
31 {% for entry in entries %}
32 <entry>
33 <title><![CDATA[{{ entry.title|e }}]]></title>
34 <link rel="alternate" type="text/html"
35 href="{{ url('view', {'id': entry.id}) }}"/>
36 <link rel="via">{{ entry.url }}</link>
37 <id>wallabag:{{ domainName | removeScheme | removeWww }}:{{ user }}:entry:{{ entry.id }}</id>
38 <updated>{{ entry.updatedAt|date('c') }}</updated>
39 <published>{{ entry.createdAt|date('c') }}</published>
40 {% for tag in entry.tags %}
41 <category term="{{ tag.slug }}" label="{{ tag.label }}" />
31 {% endfor %} 42 {% endfor %}
32 43 {% for author in entry.publishedBy %}
33 </channel> 44 <author>
34</rss> 45 <name>{{ author }}</name>
46 </author>
47 {% endfor %}
48 <content type="html" {% if entry.language %}xml:lang="{{ entry.language[:2] }}"{% endif %}>
49 <![CDATA[{%- if entry.readingTime > 0 -%}{{ 'entry.list.reading_time_minutes'|trans({'%readingTime%': entry.readingTime}) }}{%- else -%}{{ 'entry.list.reading_time_less_one_minute'|trans|raw }}{%- endif %}{{ entry.content|raw -}}]]>
50 </content>
51 </entry>
52 {% endfor %}
53</feed>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/common/Static/quickstart.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/common/Static/quickstart.html.twig
index 4580813c..521b3eea 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/common/Static/quickstart.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/common/Static/quickstart.html.twig
@@ -21,7 +21,7 @@
21 <div class="card-action"> 21 <div class="card-action">
22 <ul> 22 <ul>
23 <li><a href="{{ path('config') }}">{{ 'quickstart.configure.language'|trans }}</a></li> 23 <li><a href="{{ path('config') }}">{{ 'quickstart.configure.language'|trans }}</a></li>
24 <li><a href="{{ path('config') }}#set2">{{ 'quickstart.configure.rss'|trans }}</a></li> 24 <li><a href="{{ path('config') }}#set2">{{ 'quickstart.configure.feed'|trans }}</a></li>
25 <li><a href="{{ path('config') }}#set5">{{ 'quickstart.more'|trans }}</a></li> 25 <li><a href="{{ path('config') }}#set5">{{ 'quickstart.more'|trans }}</a></li>
26 </ul> 26 </ul>
27 </div> 27 </div>
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 f896fe2d..818fc4e7 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
@@ -12,10 +12,11 @@
12 <div class="div_tabs col s12"> 12 <div class="div_tabs col s12">
13 <ul class="tabs"> 13 <ul class="tabs">
14 <li class="tab col s12 m6 l3"><a class="active" href="#set1">{{ 'config.tab_menu.settings'|trans }}</a></li> 14 <li class="tab col s12 m6 l3"><a class="active" href="#set1">{{ 'config.tab_menu.settings'|trans }}</a></li>
15 <li class="tab col s12 m6 l3"><a href="#set2">{{ 'config.tab_menu.rss'|trans }}</a></li> 15 <li class="tab col s12 m6 l3"><a href="#set2">{{ 'config.tab_menu.feed'|trans }}</a></li>
16 <li class="tab col s12 m6 l3"><a href="#set3">{{ 'config.tab_menu.user_info'|trans }}</a></li> 16 <li class="tab col s12 m6 l3"><a href="#set3">{{ 'config.tab_menu.user_info'|trans }}</a></li>
17 <li class="tab col s12 m6 l3"><a href="#set4">{{ 'config.tab_menu.password'|trans }}</a></li> 17 <li class="tab col s12 m6 l3"><a href="#set4">{{ 'config.tab_menu.password'|trans }}</a></li>
18 <li class="tab col s12 m6 l3"><a href="#set5">{{ 'config.tab_menu.rules'|trans }}</a></li> 18 <li class="tab col s12 m6 l3"><a href="#set5">{{ 'config.tab_menu.rules'|trans }}</a></li>
19 <li class="tab col s12 m6 l3"><a href="#set6">{{ 'config.tab_menu.reset'|trans }}</a></li>
19 </ul> 20 </ul>
20 </div> 21 </div>
21 22
@@ -111,8 +112,7 @@
111 <img id="androidQrcode" class="hide-on-med-and-down" /> 112 <img id="androidQrcode" class="hide-on-med-and-down" />
112 </div> 113 </div>
113 <script> 114 <script>
114 const imgBase64 = jrQrcode.getQrBase64('wallabag://{{ app.user.username }}@{{ wallabag_url }}'); 115 document.getElementById('androidQrcode').src = jrQrcode.getQrBase64('wallabag://{{ app.user.username }}@{{ wallabag_url }}');
115 document.getElementById('androidQrcode').src = imgBase64;
116 </script> 116 </script>
117 </div> 117 </div>
118 118
@@ -122,42 +122,42 @@
122 </div> 122 </div>
123 123
124 <div id="set2" class="col s12"> 124 <div id="set2" class="col s12">
125 {{ form_start(form.rss) }} 125 {{ form_start(form.feed) }}
126 {{ form_errors(form.rss) }} 126 {{ form_errors(form.feed) }}
127 127
128 <div class="row"> 128 <div class="row">
129 <div class="input-field col s12"> 129 <div class="input-field col s12">
130 {{ 'config.form_rss.description'|trans }} 130 {{ 'config.form_feed.description'|trans }}
131 </div> 131 </div>
132 </div> 132 </div>
133 133
134 <div class="row"> 134 <div class="row">
135 <div class="col s12"> 135 <div class="col s12">
136 <h6 class="grey-text">{{ 'config.form_rss.token_label'|trans }}</h6> 136 <h6 class="grey-text">{{ 'config.form_feed.token_label'|trans }}</h6>
137 <div> 137 <div>
138 {% if rss.token %} 138 {% if feed.token %}
139 {{ rss.token }} 139 {{ feed.token }}
140 {% else %} 140 {% else %}
141 <em>{{ 'config.form_rss.no_token'|trans }}</em> 141 <em>{{ 'config.form_feed.no_token'|trans }}</em>
142 {% endif %} 142 {% endif %}
143 – <a href="{{ path('generate_token') }}"> 143 – <a href="{{ path('generate_token') }}">
144 {% if rss.token %} 144 {% if feed.token %}
145 {{ 'config.form_rss.token_reset'|trans }} 145 {{ 'config.form_feed.token_reset'|trans }}
146 {% else %} 146 {% else %}
147 {{ 'config.form_rss.token_create'|trans }} 147 {{ 'config.form_feed.token_create'|trans }}
148 {% endif %}</a> 148 {% endif %}</a>
149 </div> 149 </div>
150 </div> 150 </div>
151 </div> 151 </div>
152 {% if rss.token %} 152 {% if feed.token %}
153 <div class="row"> 153 <div class="row">
154 <div class="col s12"> 154 <div class="col s12">
155 <h6 class="grey-text">{{ 'config.form_rss.rss_links'|trans }}</h6> 155 <h6 class="grey-text">{{ 'config.form_feed.feed_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_feed', {'username': feed.username, 'token': feed.token}) }}">{{ 'config.form_feed.feed_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_feed', {'username': feed.username, 'token': feed.token}) }}">{{ 'config.form_feed.feed_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_feed', {'username': feed.username, 'token': feed.token}) }}">{{ 'config.form_feed.feed_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 <li><a href="{{ path('all_feed', {'username': feed.username, 'token': feed.token}) }}">{{ 'config.form_feed.feed_link.all'|trans }}</a></li>
161 </ul> 161 </ul>
162 </div> 162 </div>
163 </div> 163 </div>
@@ -165,14 +165,14 @@
165 165
166 <div class="row"> 166 <div class="row">
167 <div class="input-field col s12"> 167 <div class="input-field col s12">
168 {{ form_label(form.rss.rss_limit) }} 168 {{ form_label(form.feed.feed_limit) }}
169 {{ form_errors(form.rss.rss_limit) }} 169 {{ form_errors(form.feed.feed_limit) }}
170 {{ form_widget(form.rss.rss_limit) }} 170 {{ form_widget(form.feed.feed_limit) }}
171 </div> 171 </div>
172 </div> 172 </div>
173 173
174 {{ form_widget(form.rss.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }} 174 {{ form_widget(form.feed.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
175 {{ form_rest(form.rss) }} 175 {{ form_rest(form.feed) }}
176 </form> 176 </form>
177 </div> 177 </div>
178 178
@@ -196,59 +196,42 @@
196 </div> 196 </div>
197 </div> 197 </div>
198 198
199 {% if twofactor_auth %} 199 {{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
200 <div class="row">
201 <div class="input-field col s11">
202 {{ 'config.form_user.two_factor_description'|trans }}
203
204 <br />
205 200
206 {{ form_widget(form.user.twoFactorAuthentication) }} 201 {% if twofactor_auth %}
207 {{ form_label(form.user.twoFactorAuthentication) }} 202 <br/>
208 {{ form_errors(form.user.twoFactorAuthentication) }} 203 <br/>
209 </div> 204 <div class="row">
210 <div class="input-field col s1"> 205 <h5>{{ 'config.otp.page_title'|trans }}</h5>
211 <a href="#" class="tooltipped" data-position="left" data-delay="50" data-tooltip="{{ 'config.form_user.help_twoFactorAuthentication'|trans }}"> 206
212 <i class="material-icons">live_help</i> 207 <p>{{ 'config.form_user.two_factor_description'|trans }}</p>
213 </a> 208
209 <table>
210 <thead>
211 <tr>
212 <th>{{ 'config.form_user.two_factor.table_method'|trans }}</th>
213 <th>{{ 'config.form_user.two_factor.table_state'|trans }}</th>
214 <th>{{ 'config.form_user.two_factor.table_action'|trans }}</th>
215 </tr>
216 </thead>
217
218 <tbody>
219 <tr>
220 <td>{{ 'config.form_user.two_factor.emailTwoFactor_label'|trans }}</td>
221 <td>{% if app.user.isEmailTwoFactor %}<b>{{ 'config.form_user.two_factor.state_enabled'|trans }}</b>{% else %}{{ 'config.form_user.two_factor.state_disabled'|trans }}{% endif %}</td>
222 <td><a href="{{ path('config_otp_email') }}" class="waves-effect waves-light btn{% if app.user.isEmailTwoFactor %} disabled{% endif %}">{{ 'config.form_user.two_factor.action_email'|trans }}</a></td>
223 </tr>
224 <tr>
225 <td>{{ 'config.form_user.two_factor.googleTwoFactor_label'|trans }}</td>
226 <td>{% if app.user.isGoogleTwoFactor %}<b>{{ 'config.form_user.two_factor.state_enabled'|trans }}</b>{% else %}{{ 'config.form_user.two_factor.state_disabled'|trans }}{% endif %}</td>
227 <td><a href="{{ path('config_otp_app') }}" class="waves-effect waves-light btn{% if app.user.isGoogleTwoFactor %} disabled{% endif %}">{{ 'config.form_user.two_factor.action_app'|trans }}</a></td>
228 </tr>
229 </tbody>
230 </table>
214 </div> 231 </div>
215 </div>
216 {% endif %} 232 {% endif %}
217
218 {{ form_widget(form.user.save, {'attr': {'class': 'btn waves-effect waves-light'}}) }}
219 {{ form_widget(form.user._token) }} 233 {{ form_widget(form.user._token) }}
220 </form> 234 </form>
221
222 <br /><hr /><br />
223
224 <div class="row">
225 <h5>{{ 'config.reset.title'|trans }}</h5>
226 <p>{{ 'config.reset.description'|trans }}</p>
227 <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
228 {{ 'config.reset.annotations'|trans }}
229 </a>
230 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
231 {{ 'config.reset.tags'|trans }}
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>
236 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
237 {{ 'config.reset.entries'|trans }}
238 </a>
239 </div>
240
241 {% if enabled_users > 1 %}
242 <br /><hr /><br />
243
244 <div class="row">
245 <h5>{{ 'config.form_user.delete.title'|trans }}</h5>
246 <p>{{ 'config.form_user.delete.description'|trans }}</p>
247 <a href="{{ path('delete_account') }}" onclick="return confirm('{{ 'config.form_user.delete.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red delete-account">
248 {{ 'config.form_user.delete.button'|trans }}
249 </a>
250 </div>
251 {% endif %}
252 </div> 235 </div>
253 236
254 <div id="set4" class="col s12"> 237 <div id="set4" class="col s12">
@@ -422,6 +405,37 @@
422 </div> 405 </div>
423 </div> 406 </div>
424 </div> 407 </div>
408
409 <div id="set6" class="col s12">
410 <div class="row">
411 <h5>{{ 'config.reset.title'|trans }}</h5>
412 <p>{{ 'config.reset.description'|trans }}</p>
413 <a href="{{ path('config_reset', { type: 'annotations'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
414 {{ 'config.reset.annotations'|trans }}
415 </a>
416 <a href="{{ path('config_reset', { type: 'tags'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
417 {{ 'config.reset.tags'|trans }}
418 </a>
419 <a href="{{ path('config_reset', { type: 'archived'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
420 {{ 'config.reset.archived'|trans }}
421 </a>
422 <a href="{{ path('config_reset', { type: 'entries'}) }}" onclick="return confirm('{{ 'config.reset.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red">
423 {{ 'config.reset.entries'|trans }}
424 </a>
425 </div>
426
427 {% if enabled_users > 1 %}
428 <br /><hr /><br />
429
430 <div class="row">
431 <h5>{{ 'config.form_user.delete.title'|trans }}</h5>
432 <p>{{ 'config.form_user.delete.description'|trans }}</p>
433 <a href="{{ path('delete_account') }}" onclick="return confirm('{{ 'config.form_user.delete.confirm'|trans|escape('js') }}')" class="waves-effect waves-light btn red delete-account">
434 {{ 'config.form_user.delete.button'|trans }}
435 </a>
436 </div>
437 {% endif %}
438 </div>
425 </div> 439 </div>
426 440
427 </div> 441 </div>
diff --git a/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/otp_app.html.twig b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/otp_app.html.twig
new file mode 100644
index 00000000..7875d787
--- /dev/null
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/Config/otp_app.html.twig
@@ -0,0 +1,63 @@
1{% extends "WallabagCoreBundle::layout.html.twig" %}
2
3{% block title %}{{ 'config.page_title'|trans }} > {{ 'config.otp.page_title'|trans }}{% endblock %}
4
5{% block content %}
6 <div class="row">
7 <div class="col s12">
8 <div class="card-panel settings">
9 <div class="row">
10 <h5>{{ 'config.otp.page_title'|trans }}</h5>
11
12 <ol>
13 <li>
14 <p>{{ 'config.otp.app.two_factor_code_description_1'|trans }}</p>
15 <p>{{ 'config.otp.app.two_factor_code_description_2'|trans }}</p>
16
17 <p>
18 <img id="2faQrcode" class="hide-on-med-and-down" />
19 <script>
20 document.getElementById('2faQrcode').src = jrQrcode.getQrBase64('{{ qr_code }}');
21 </script>
22 </p>
23 </li>
24 <li>
25 <p>{{ 'config.otp.app.two_factor_code_description_3'|trans }}</p>
26
27 <p><strong>{{ backupCodes|join("\n")|nl2br }}</strong></p>
28 </li>
29 <li>
30 <p>{{ 'config.otp.app.two_factor_code_description_4'|trans }}</p>
31
32 {% for flashMessage in app.session.flashbag.get("two_factor") %}
33 <div class="card-panel red darken-1 black-text">
34 {{ flashMessage|trans }}
35 </div>
36 {% endfor %}
37
38 <form class="form" action="{{ path("config_otp_app_check") }}" method="post">
39 <div class="card-content">
40 <div class="row">
41 <div class="input-field col s12">
42 <label for="_auth_code">{{ "scheb_two_factor.auth_code"|trans }}</label>
43 <input id="_auth_code" type="text" autocomplete="off" name="_auth_code" />
44 </div>
45 </div>
46 </div>
47 <div class="card-action">
48 <a href="{{ path('config_otp_app_cancel') }}" class="waves-effect waves-light grey btn">
49 {{ 'config.otp.app.cancel'|trans }}
50 </a>
51 <button class="btn waves-effect waves-light" type="submit" name="send">
52 {{ 'config.otp.app.enable'|trans }}
53 <i class="material-icons right">send</i>
54 </button>
55 </div>
56 </form>
57 </li>
58 </ol>
59 </div>
60 </div>
61 </div>
62 </div>
63{% endblock %}
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 742dd330..476d7403 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
@@ -2,8 +2,8 @@
2 2
3{% block head %} 3{% block head %}
4 {{ parent() }} 4 {{ parent() }}
5 {% if tag is defined and app.user.config.rssToken %} 5 {% if tag is defined and app.user.config.feedToken %}
6 <link rel="alternate" type="application/rss+xml" href="{{ path('tag_rss', {'username': app.user.username, 'token': app.user.config.rssToken, 'slug': tag.slug}) }}" /> 6 <link rel="alternate" type="application/atom+xml" href="{{ path('tag_feed', {'username': app.user.username, 'token': app.user.config.feedToken, 'slug': tag.slug}) }}" />
7 {% endif %} 7 {% endif %}
8{% endblock %} 8{% endblock %}
9 9
@@ -21,12 +21,15 @@
21{% block content %} 21{% block content %}
22 {% set listMode = app.user.config.listMode %} 22 {% set listMode = app.user.config.listMode %}
23 {% set currentRoute = app.request.attributes.get('_route') %} 23 {% set currentRoute = app.request.attributes.get('_route') %}
24 {% if currentRoute == 'homepage' %}
25 {% set currentRoute = 'unread' %}
26 {% endif %}
24 <div class="results"> 27 <div class="results">
25 <div class="nb-results"> 28 <div class="nb-results">
26 {{ 'entry.list.number_on_the_page'|transchoice(entries.count) }} 29 {{ 'entry.list.number_on_the_page'|transchoice(entries.count) }}
27 <a href="{{ path('switch_view_mode') }}"><i class="material-icons">{% if listMode == 0 %}view_list{% else %}view_module{% endif %}</i></a> 30 <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 %} 31 {% if app.user.config.feedToken %}
29 {% include "@WallabagCore/themes/common/Entry/_rss_link.html.twig" %} 32 {% include "@WallabagCore/themes/common/Entry/_feed_link.html.twig" %}
30 {% endif %} 33 {% endif %}
31 </div> 34 </div>
32 {% if entries.getNbPages > 1 %} 35 {% if entries.getNbPages > 1 %}
@@ -60,9 +63,6 @@
60 {% if tag is defined %} 63 {% if tag is defined %}
61 {% set currentTag = tag.slug %} 64 {% set currentTag = tag.slug %}
62 {% endif %} 65 {% endif %}
63 {% if currentRoute == 'homepage' %}
64 {% set currentRoute = 'unread' %}
65 {% endif %}
66 <h4 class="center">{{ 'entry.list.export_title'|trans }}</h4> 66 <h4 class="center">{{ 'entry.list.export_title'|trans }}</h4>
67 <ul> 67 <ul>
68 {% if craue_setting('export_epub') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'epub', 'tag' : currentTag }) }}">EPUB</a></li>{% endif %} 68 {% if craue_setting('export_epub') %}<li class="bold"><a class="waves-effect" href="{{ path('export_entries', { 'category': currentRoute, 'format': 'epub', 'tag' : currentTag }) }}">EPUB</a></li>{% endif %}
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 c15b5146..79907bbb 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
@@ -13,9 +13,20 @@
13 <ul class="card-tag-labels"> 13 <ul class="card-tag-labels">
14 {% for tag in tags %} 14 {% for tag in tags %}
15 <li title="{{tag.label}} ({{ tag.nbEntries }})" id="tag-{{ tag.id }}"> 15 <li title="{{tag.label}} ({{ tag.nbEntries }})" id="tag-{{ tag.id }}">
16 <a href="{{ path('tag_entries', {'slug': tag.slug}) }}" class="card-tag-link">{{tag.label}} ({{ tag.nbEntries }})</a> 16 <a href="{{ path('tag_entries', {'slug': tag.slug}) }}" class="card-tag-link" data-handle="tag-link">
17 {% if app.user.config.rssToken %} 17 {{ tag.label }}&nbsp;({{ tag.nbEntries }})
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="card-tag-rss"><i class="material-icons">rss_feed</i></a> 18 </a>
19 {% if renameForms is defined and renameForms[tag.id] is defined %}
20 <form class="card-tag-form hidden" data-handle="tag-rename-form" action="{{ path('tag_rename', {'slug': tag.slug})}}" method="POST">
21 {{ form_widget(renameForms[tag.id].label, {'attr': {'value': tag.label}}) }}
22 {{ form_rest(renameForms[tag.id]) }}
23 </form>
24 <a class="card-tag-rename" data-handler="tag-rename" href="javascript:void(0);">
25 <i class="material-icons">mode_edit</i>
26 </a>
27 {% endif %}
28 {% if app.user.config.feedToken %}
29 <a rel="alternate" type="application/atom+xml" href="{{ path('tag_feed', {'username': app.user.username, 'token': app.user.config.feedToken, 'slug': tag.slug}) }}" class="card-tag-rss"><i class="material-icons">rss_feed</i></a>
19 {% endif %} 30 {% endif %}
20 </li> 31 </li>
21 {% endfor %} 32 {% endfor %}
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 052a8c01..b9c45567 100644
--- a/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig
+++ b/src/Wallabag/CoreBundle/Resources/views/themes/material/layout.html.twig
@@ -46,6 +46,8 @@
46 {% set activeRoute = 'starred' %} 46 {% set activeRoute = 'starred' %}
47 {% elseif currentRoute == 'unread' or currentRoute == 'homepage' or currentRouteFromQueryParams == 'unread' %} 47 {% elseif currentRoute == 'unread' or currentRoute == 'homepage' or currentRouteFromQueryParams == 'unread' %}
48 {% set activeRoute = 'unread' %} 48 {% set activeRoute = 'unread' %}
49 {% elseif currentRoute == 'untagged' %}
50 {% set activeRoute = 'untagged' %}
49 {% endif %} 51 {% endif %}
50 52
51 <li class="bold {% if activeRoute == 'unread' %}active{% endif %}"> 53 <li class="bold {% if activeRoute == 'unread' %}active{% endif %}">
@@ -113,6 +115,13 @@
113 <i class="material-icons">search</i> 115 <i class="material-icons">search</i>
114 </a> 116 </a>
115 </li> 117 </li>
118 {% if activeRoute %}
119 <li id="button_random">
120 <a class="waves-effect tooltipped js-random-action" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.random_entry'|trans }}" href="{{ path('random_entry', { 'type': activeRoute }) }}">
121 <i class="material-icons">casino</i>
122 </a>
123 </li>
124 {% endif %}
116 <li id="button_filters"> 125 <li id="button_filters">
117 <a class="nav-panel-menu button-collapse-right tooltipped js-filters-action" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.filter_entries'|trans }}" href="#" data-activates="filters"> 126 <a class="nav-panel-menu button-collapse-right tooltipped js-filters-action" data-position="bottom" data-delay="50" data-tooltip="{{ 'menu.top.filter_entries'|trans }}" href="#" data-activates="filters">
118 <i class="material-icons">filter_list</i> 127 <i class="material-icons">filter_list</i>
@@ -125,7 +134,7 @@
125 </li> 134 </li>
126 </ul> 135 </ul>
127 </div> 136 </div>
128 {{ render(controller("WallabagCoreBundle:Entry:searchForm", {'currentRoute': app.request.attributes.get('_route')})) }} 137 {{ render(controller("WallabagCoreBundle:Entry:searchForm", {'currentRoute': currentRoute})) }}
129 {{ render(controller("WallabagCoreBundle:Entry:addEntryForm")) }} 138 {{ render(controller("WallabagCoreBundle:Entry:addEntryForm")) }}
130 </div> 139 </div>
131 </nav> 140 </nav>
diff --git a/src/Wallabag/CoreBundle/Tools/Utils.php b/src/Wallabag/CoreBundle/Tools/Utils.php
index e56e251e..b7ad7966 100644
--- a/src/Wallabag/CoreBundle/Tools/Utils.php
+++ b/src/Wallabag/CoreBundle/Tools/Utils.php
@@ -5,7 +5,7 @@ namespace Wallabag\CoreBundle\Tools;
5class Utils 5class Utils
6{ 6{
7 /** 7 /**
8 * Generate a token used for RSS. 8 * Generate a token used for Feeds.
9 * 9 *
10 * @param int $length Length of the token 10 * @param int $length Length of the token
11 * 11 *
diff --git a/src/Wallabag/CoreBundle/Twig/WallabagExtension.php b/src/Wallabag/CoreBundle/Twig/WallabagExtension.php
index 00b1e595..536185d4 100644
--- a/src/Wallabag/CoreBundle/Twig/WallabagExtension.php
+++ b/src/Wallabag/CoreBundle/Twig/WallabagExtension.php
@@ -28,6 +28,7 @@ class WallabagExtension extends \Twig_Extension implements \Twig_Extension_Globa
28 { 28 {
29 return [ 29 return [
30 new \Twig_SimpleFilter('removeWww', [$this, 'removeWww']), 30 new \Twig_SimpleFilter('removeWww', [$this, 'removeWww']),
31 new \Twig_SimpleFilter('removeScheme', [$this, 'removeScheme']),
31 new \Twig_SimpleFilter('removeSchemeAndWww', [$this, 'removeSchemeAndWww']), 32 new \Twig_SimpleFilter('removeSchemeAndWww', [$this, 'removeSchemeAndWww']),
32 ]; 33 ];
33 } 34 }
@@ -46,11 +47,14 @@ class WallabagExtension extends \Twig_Extension implements \Twig_Extension_Globa
46 return preg_replace('/^www\./i', '', $url); 47 return preg_replace('/^www\./i', '', $url);
47 } 48 }
48 49
50 public function removeScheme($url)
51 {
52 return preg_replace('#^https?://#i', '', $url);
53 }
54
49 public function removeSchemeAndWww($url) 55 public function removeSchemeAndWww($url)
50 { 56 {
51 return $this->removeWww( 57 return $this->removeWww($this->removeScheme($url));
52 preg_replace('@^https?://@i', '', $url)
53 );
54 } 58 }
55 59
56 /** 60 /**
diff --git a/src/Wallabag/ImportBundle/Controller/BrowserController.php b/src/Wallabag/ImportBundle/Controller/BrowserController.php
index 6418925c..58d2a730 100644
--- a/src/Wallabag/ImportBundle/Controller/BrowserController.php
+++ b/src/Wallabag/ImportBundle/Controller/BrowserController.php
@@ -2,10 +2,10 @@
2 2
3namespace Wallabag\ImportBundle\Controller; 3namespace Wallabag\ImportBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller; 5use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\Request; 6use Symfony\Component\HttpFoundation\Request;
8use Symfony\Component\HttpFoundation\Response; 7use Symfony\Component\HttpFoundation\Response;
8use Symfony\Component\Routing\Annotation\Route;
9use Wallabag\ImportBundle\Form\Type\UploadImportType; 9use Wallabag\ImportBundle\Form\Type\UploadImportType;
10 10
11abstract class BrowserController extends Controller 11abstract class BrowserController extends Controller
diff --git a/src/Wallabag/ImportBundle/Controller/ChromeController.php b/src/Wallabag/ImportBundle/Controller/ChromeController.php
index 0cb418a1..6628cdb0 100644
--- a/src/Wallabag/ImportBundle/Controller/ChromeController.php
+++ b/src/Wallabag/ImportBundle/Controller/ChromeController.php
@@ -2,8 +2,8 @@
2 2
3namespace Wallabag\ImportBundle\Controller; 3namespace Wallabag\ImportBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Component\HttpFoundation\Request; 5use Symfony\Component\HttpFoundation\Request;
6use Symfony\Component\Routing\Annotation\Route;
7 7
8class ChromeController extends BrowserController 8class ChromeController extends BrowserController
9{ 9{
diff --git a/src/Wallabag/ImportBundle/Controller/FirefoxController.php b/src/Wallabag/ImportBundle/Controller/FirefoxController.php
index 88697f9d..dce8455f 100644
--- a/src/Wallabag/ImportBundle/Controller/FirefoxController.php
+++ b/src/Wallabag/ImportBundle/Controller/FirefoxController.php
@@ -2,8 +2,8 @@
2 2
3namespace Wallabag\ImportBundle\Controller; 3namespace Wallabag\ImportBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Component\HttpFoundation\Request; 5use Symfony\Component\HttpFoundation\Request;
6use Symfony\Component\Routing\Annotation\Route;
7 7
8class FirefoxController extends BrowserController 8class FirefoxController extends BrowserController
9{ 9{
diff --git a/src/Wallabag/ImportBundle/Controller/ImportController.php b/src/Wallabag/ImportBundle/Controller/ImportController.php
index 7e4fd174..fbd7434e 100644
--- a/src/Wallabag/ImportBundle/Controller/ImportController.php
+++ b/src/Wallabag/ImportBundle/Controller/ImportController.php
@@ -2,8 +2,8 @@
2 2
3namespace Wallabag\ImportBundle\Controller; 3namespace Wallabag\ImportBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller; 5use Symfony\Bundle\FrameworkBundle\Controller\Controller;
6use Symfony\Component\Routing\Annotation\Route;
7 7
8class ImportController extends Controller 8class ImportController extends Controller
9{ 9{
diff --git a/src/Wallabag/ImportBundle/Controller/InstapaperController.php b/src/Wallabag/ImportBundle/Controller/InstapaperController.php
index f184baf9..faed3b72 100644
--- a/src/Wallabag/ImportBundle/Controller/InstapaperController.php
+++ b/src/Wallabag/ImportBundle/Controller/InstapaperController.php
@@ -2,9 +2,9 @@
2 2
3namespace Wallabag\ImportBundle\Controller; 3namespace Wallabag\ImportBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller; 5use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\Request; 6use Symfony\Component\HttpFoundation\Request;
7use Symfony\Component\Routing\Annotation\Route;
8use Wallabag\ImportBundle\Form\Type\UploadImportType; 8use Wallabag\ImportBundle\Form\Type\UploadImportType;
9 9
10class InstapaperController extends Controller 10class InstapaperController extends Controller
diff --git a/src/Wallabag/ImportBundle/Controller/PinboardController.php b/src/Wallabag/ImportBundle/Controller/PinboardController.php
index 6f54c69a..cc6fae79 100644
--- a/src/Wallabag/ImportBundle/Controller/PinboardController.php
+++ b/src/Wallabag/ImportBundle/Controller/PinboardController.php
@@ -2,9 +2,9 @@
2 2
3namespace Wallabag\ImportBundle\Controller; 3namespace Wallabag\ImportBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller; 5use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\Request; 6use Symfony\Component\HttpFoundation\Request;
7use Symfony\Component\Routing\Annotation\Route;
8use Wallabag\ImportBundle\Form\Type\UploadImportType; 8use Wallabag\ImportBundle\Form\Type\UploadImportType;
9 9
10class PinboardController extends Controller 10class PinboardController extends Controller
diff --git a/src/Wallabag/ImportBundle/Controller/PocketController.php b/src/Wallabag/ImportBundle/Controller/PocketController.php
index 9f28819a..71ceb427 100644
--- a/src/Wallabag/ImportBundle/Controller/PocketController.php
+++ b/src/Wallabag/ImportBundle/Controller/PocketController.php
@@ -2,10 +2,10 @@
2 2
3namespace Wallabag\ImportBundle\Controller; 3namespace Wallabag\ImportBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller; 5use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\Form\Extension\Core\Type\CheckboxType; 6use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
8use Symfony\Component\HttpFoundation\Request; 7use Symfony\Component\HttpFoundation\Request;
8use Symfony\Component\Routing\Annotation\Route;
9use Symfony\Component\Routing\Generator\UrlGeneratorInterface; 9use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
10 10
11class PocketController extends Controller 11class PocketController extends Controller
diff --git a/src/Wallabag/ImportBundle/Controller/ReadabilityController.php b/src/Wallabag/ImportBundle/Controller/ReadabilityController.php
index 729a97a3..b120ef96 100644
--- a/src/Wallabag/ImportBundle/Controller/ReadabilityController.php
+++ b/src/Wallabag/ImportBundle/Controller/ReadabilityController.php
@@ -2,9 +2,9 @@
2 2
3namespace Wallabag\ImportBundle\Controller; 3namespace Wallabag\ImportBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Bundle\FrameworkBundle\Controller\Controller; 5use Symfony\Bundle\FrameworkBundle\Controller\Controller;
7use Symfony\Component\HttpFoundation\Request; 6use Symfony\Component\HttpFoundation\Request;
7use Symfony\Component\Routing\Annotation\Route;
8use Wallabag\ImportBundle\Form\Type\UploadImportType; 8use Wallabag\ImportBundle\Form\Type\UploadImportType;
9 9
10class ReadabilityController extends Controller 10class ReadabilityController extends Controller
diff --git a/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php b/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php
index d700d8a8..e1c35343 100644
--- a/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php
+++ b/src/Wallabag/ImportBundle/Controller/WallabagV1Controller.php
@@ -2,8 +2,8 @@
2 2
3namespace Wallabag\ImportBundle\Controller; 3namespace Wallabag\ImportBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Component\HttpFoundation\Request; 5use Symfony\Component\HttpFoundation\Request;
6use Symfony\Component\Routing\Annotation\Route;
7 7
8class WallabagV1Controller extends WallabagController 8class WallabagV1Controller extends WallabagController
9{ 9{
diff --git a/src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php b/src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php
index ab26400c..c4116c1d 100644
--- a/src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php
+++ b/src/Wallabag/ImportBundle/Controller/WallabagV2Controller.php
@@ -2,8 +2,8 @@
2 2
3namespace Wallabag\ImportBundle\Controller; 3namespace Wallabag\ImportBundle\Controller;
4 4
5use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
6use Symfony\Component\HttpFoundation\Request; 5use Symfony\Component\HttpFoundation\Request;
6use Symfony\Component\Routing\Annotation\Route;
7 7
8class WallabagV2Controller extends WallabagController 8class WallabagV2Controller extends WallabagController
9{ 9{
diff --git a/src/Wallabag/ImportBundle/Import/BrowserImport.php b/src/Wallabag/ImportBundle/Import/BrowserImport.php
index 811e3fb8..3987e80f 100644
--- a/src/Wallabag/ImportBundle/Import/BrowserImport.php
+++ b/src/Wallabag/ImportBundle/Import/BrowserImport.php
@@ -133,7 +133,7 @@ abstract class BrowserImport extends AbstractImport
133 ); 133 );
134 } 134 }
135 135
136 $entry->setArchived($data['is_archived']); 136 $entry->updateArchived($data['is_archived']);
137 137
138 if (!empty($data['created_at'])) { 138 if (!empty($data['created_at'])) {
139 $dt = new \DateTime(); 139 $dt = new \DateTime();
diff --git a/src/Wallabag/ImportBundle/Import/InstapaperImport.php b/src/Wallabag/ImportBundle/Import/InstapaperImport.php
index 5a18c7c0..f7bee9ef 100644
--- a/src/Wallabag/ImportBundle/Import/InstapaperImport.php
+++ b/src/Wallabag/ImportBundle/Import/InstapaperImport.php
@@ -62,7 +62,7 @@ class InstapaperImport extends AbstractImport
62 } 62 }
63 63
64 $entries = []; 64 $entries = [];
65 $handle = fopen($this->filepath, 'rb'); 65 $handle = fopen($this->filepath, 'r');
66 while (false !== ($data = fgetcsv($handle, 10240))) { 66 while (false !== ($data = fgetcsv($handle, 10240))) {
67 if ('URL' === $data[0]) { 67 if ('URL' === $data[0]) {
68 continue; 68 continue;
@@ -79,7 +79,6 @@ class InstapaperImport extends AbstractImport
79 $entries[] = [ 79 $entries[] = [
80 'url' => $data[0], 80 'url' => $data[0],
81 'title' => $data[1], 81 'title' => $data[1],
82 'status' => $data[3],
83 'is_archived' => 'Archive' === $data[3] || 'Starred' === $data[3], 82 'is_archived' => 'Archive' === $data[3] || 'Starred' === $data[3],
84 'is_starred' => 'Starred' === $data[3], 83 'is_starred' => 'Starred' === $data[3],
85 'html' => false, 84 'html' => false,
@@ -94,6 +93,10 @@ class InstapaperImport extends AbstractImport
94 return false; 93 return false;
95 } 94 }
96 95
96 // most recent articles are first, which means we should create them at the end so they will show up first
97 // as Instapaper doesn't export the creation date of the article
98 $entries = array_reverse($entries);
99
97 if ($this->producer) { 100 if ($this->producer) {
98 $this->parseEntriesForProducer($entries); 101 $this->parseEntriesForProducer($entries);
99 102
@@ -147,7 +150,7 @@ class InstapaperImport extends AbstractImport
147 ); 150 );
148 } 151 }
149 152
150 $entry->setArchived($importedEntry['is_archived']); 153 $entry->updateArchived($importedEntry['is_archived']);
151 $entry->setStarred($importedEntry['is_starred']); 154 $entry->setStarred($importedEntry['is_starred']);
152 155
153 $this->em->persist($entry); 156 $this->em->persist($entry);
diff --git a/src/Wallabag/ImportBundle/Import/PinboardImport.php b/src/Wallabag/ImportBundle/Import/PinboardImport.php
index 995d1f2c..202eb1b3 100644
--- a/src/Wallabag/ImportBundle/Import/PinboardImport.php
+++ b/src/Wallabag/ImportBundle/Import/PinboardImport.php
@@ -131,7 +131,7 @@ class PinboardImport extends AbstractImport
131 ); 131 );
132 } 132 }
133 133
134 $entry->setArchived($data['is_archived']); 134 $entry->updateArchived($data['is_archived']);
135 $entry->setStarred($data['is_starred']); 135 $entry->setStarred($data['is_starred']);
136 $entry->setCreatedAt(new \DateTime($data['created_at'])); 136 $entry->setCreatedAt(new \DateTime($data['created_at']));
137 137
diff --git a/src/Wallabag/ImportBundle/Import/PocketImport.php b/src/Wallabag/ImportBundle/Import/PocketImport.php
index 5737928d..746120af 100644
--- a/src/Wallabag/ImportBundle/Import/PocketImport.php
+++ b/src/Wallabag/ImportBundle/Import/PocketImport.php
@@ -2,13 +2,22 @@
2 2
3namespace Wallabag\ImportBundle\Import; 3namespace Wallabag\ImportBundle\Import;
4 4
5use GuzzleHttp\Client; 5use Http\Client\Common\HttpMethodsClient;
6use GuzzleHttp\Exception\RequestException; 6use Http\Client\Common\Plugin\ErrorPlugin;
7use Http\Client\Common\PluginClient;
8use Http\Client\Exception\RequestException;
9use Http\Client\HttpClient;
10use Http\Discovery\MessageFactoryDiscovery;
11use Http\Message\MessageFactory;
12use Psr\Http\Message\ResponseInterface;
7use Wallabag\CoreBundle\Entity\Entry; 13use Wallabag\CoreBundle\Entity\Entry;
8 14
9class PocketImport extends AbstractImport 15class PocketImport extends AbstractImport
10{ 16{
11 const NB_ELEMENTS = 5000; 17 const NB_ELEMENTS = 5000;
18 /**
19 * @var HttpMethodsClient
20 */
12 private $client; 21 private $client;
13 private $accessToken; 22 private $accessToken;
14 23
@@ -55,24 +64,18 @@ class PocketImport extends AbstractImport
55 */ 64 */
56 public function getRequestToken($redirectUri) 65 public function getRequestToken($redirectUri)
57 { 66 {
58 $request = $this->client->createRequest('POST', 'https://getpocket.com/v3/oauth/request',
59 [
60 'body' => json_encode([
61 'consumer_key' => $this->user->getConfig()->getPocketConsumerKey(),
62 'redirect_uri' => $redirectUri,
63 ]),
64 ]
65 );
66
67 try { 67 try {
68 $response = $this->client->send($request); 68 $response = $this->client->post('https://getpocket.com/v3/oauth/request', [], json_encode([
69 'consumer_key' => $this->user->getConfig()->getPocketConsumerKey(),
70 'redirect_uri' => $redirectUri,
71 ]));
69 } catch (RequestException $e) { 72 } catch (RequestException $e) {
70 $this->logger->error(sprintf('PocketImport: Failed to request token: %s', $e->getMessage()), ['exception' => $e]); 73 $this->logger->error(sprintf('PocketImport: Failed to request token: %s', $e->getMessage()), ['exception' => $e]);
71 74
72 return false; 75 return false;
73 } 76 }
74 77
75 return $response->json()['code']; 78 return $this->jsonDecode($response)['code'];
76 } 79 }
77 80
78 /** 81 /**
@@ -85,24 +88,18 @@ class PocketImport extends AbstractImport
85 */ 88 */
86 public function authorize($code) 89 public function authorize($code)
87 { 90 {
88 $request = $this->client->createRequest('POST', 'https://getpocket.com/v3/oauth/authorize',
89 [
90 'body' => json_encode([
91 'consumer_key' => $this->user->getConfig()->getPocketConsumerKey(),
92 'code' => $code,
93 ]),
94 ]
95 );
96
97 try { 91 try {
98 $response = $this->client->send($request); 92 $response = $this->client->post('https://getpocket.com/v3/oauth/authorize', [], json_encode([
93 'consumer_key' => $this->user->getConfig()->getPocketConsumerKey(),
94 'code' => $code,
95 ]));
99 } catch (RequestException $e) { 96 } catch (RequestException $e) {
100 $this->logger->error(sprintf('PocketImport: Failed to authorize client: %s', $e->getMessage()), ['exception' => $e]); 97 $this->logger->error(sprintf('PocketImport: Failed to authorize client: %s', $e->getMessage()), ['exception' => $e]);
101 98
102 return false; 99 return false;
103 } 100 }
104 101
105 $this->accessToken = $response->json()['access_token']; 102 $this->accessToken = $this->jsonDecode($response)['access_token'];
106 103
107 return true; 104 return true;
108 } 105 }
@@ -114,29 +111,23 @@ class PocketImport extends AbstractImport
114 { 111 {
115 static $run = 0; 112 static $run = 0;
116 113
117 $request = $this->client->createRequest('POST', 'https://getpocket.com/v3/get',
118 [
119 'body' => json_encode([
120 'consumer_key' => $this->user->getConfig()->getPocketConsumerKey(),
121 'access_token' => $this->accessToken,
122 'detailType' => 'complete',
123 'state' => 'all',
124 'sort' => 'newest',
125 'count' => self::NB_ELEMENTS,
126 'offset' => $offset,
127 ]),
128 ]
129 );
130
131 try { 114 try {
132 $response = $this->client->send($request); 115 $response = $this->client->post('https://getpocket.com/v3/get', [], json_encode([
116 'consumer_key' => $this->user->getConfig()->getPocketConsumerKey(),
117 'access_token' => $this->accessToken,
118 'detailType' => 'complete',
119 'state' => 'all',
120 'sort' => 'newest',
121 'count' => self::NB_ELEMENTS,
122 'offset' => $offset,
123 ]));
133 } catch (RequestException $e) { 124 } catch (RequestException $e) {
134 $this->logger->error(sprintf('PocketImport: Failed to import: %s', $e->getMessage()), ['exception' => $e]); 125 $this->logger->error(sprintf('PocketImport: Failed to import: %s', $e->getMessage()), ['exception' => $e]);
135 126
136 return false; 127 return false;
137 } 128 }
138 129
139 $entries = $response->json(); 130 $entries = $this->jsonDecode($response);
140 131
141 if ($this->producer) { 132 if ($this->producer) {
142 $this->parseEntriesForProducer($entries['list']); 133 $this->parseEntriesForProducer($entries['list']);
@@ -159,13 +150,14 @@ class PocketImport extends AbstractImport
159 } 150 }
160 151
161 /** 152 /**
162 * Set the Guzzle client. 153 * Set the Http client.
163 * 154 *
164 * @param Client $client 155 * @param HttpClient $client
156 * @param MessageFactory|null $messageFactory
165 */ 157 */
166 public function setClient(Client $client) 158 public function setClient(HttpClient $client, MessageFactory $messageFactory = null)
167 { 159 {
168 $this->client = $client; 160 $this->client = new HttpMethodsClient(new PluginClient($client, [new ErrorPlugin()]), $messageFactory ?: MessageFactoryDiscovery::find());
169 } 161 }
170 162
171 /** 163 /**
@@ -206,7 +198,7 @@ class PocketImport extends AbstractImport
206 $this->fetchContent($entry, $url); 198 $this->fetchContent($entry, $url);
207 199
208 // 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted 200 // 0, 1, 2 - 1 if the item is archived - 2 if the item should be deleted
209 $entry->setArchived(1 === (int) $importedEntry['status'] || $this->markAsRead); 201 $entry->updateArchived(1 === (int) $importedEntry['status'] || $this->markAsRead);
210 202
211 // 0 or 1 - 1 if the item is starred 203 // 0 or 1 - 1 if the item is starred
212 $entry->setStarred(1 === (int) $importedEntry['favorite']); 204 $entry->setStarred(1 === (int) $importedEntry['favorite']);
@@ -252,4 +244,15 @@ class PocketImport extends AbstractImport
252 244
253 return $importedEntry; 245 return $importedEntry;
254 } 246 }
247
248 protected function jsonDecode(ResponseInterface $response)
249 {
250 $data = json_decode((string) $response->getBody(), true);
251
252 if (JSON_ERROR_NONE !== json_last_error()) {
253 throw new \InvalidArgumentException('Unable to parse JSON data: ' . json_last_error_msg());
254 }
255
256 return $data;
257 }
255} 258}
diff --git a/src/Wallabag/ImportBundle/Import/ReadabilityImport.php b/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
index a5f3798e..c5abf189 100644
--- a/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
+++ b/src/Wallabag/ImportBundle/Import/ReadabilityImport.php
@@ -123,7 +123,7 @@ class ReadabilityImport extends AbstractImport
123 // update entry with content (in case fetching failed, the given entry will be return) 123 // update entry with content (in case fetching failed, the given entry will be return)
124 $this->fetchContent($entry, $data['url'], $data); 124 $this->fetchContent($entry, $data['url'], $data);
125 125
126 $entry->setArchived($data['is_archived']); 126 $entry->updateArchived($data['is_archived']);
127 $entry->setStarred($data['is_starred']); 127 $entry->setStarred($data['is_starred']);
128 $entry->setCreatedAt(new \DateTime($data['created_at'])); 128 $entry->setCreatedAt(new \DateTime($data['created_at']));
129 129
diff --git a/src/Wallabag/ImportBundle/Import/WallabagImport.php b/src/Wallabag/ImportBundle/Import/WallabagImport.php
index 58b6a970..75a28fbf 100644
--- a/src/Wallabag/ImportBundle/Import/WallabagImport.php
+++ b/src/Wallabag/ImportBundle/Import/WallabagImport.php
@@ -134,7 +134,7 @@ abstract class WallabagImport extends AbstractImport
134 $entry->setPreviewPicture($importedEntry['preview_picture']); 134 $entry->setPreviewPicture($importedEntry['preview_picture']);
135 } 135 }
136 136
137 $entry->setArchived($data['is_archived']); 137 $entry->updateArchived($data['is_archived']);
138 $entry->setStarred($data['is_starred']); 138 $entry->setStarred($data['is_starred']);
139 139
140 if (!empty($data['created_at'])) { 140 if (!empty($data['created_at'])) {
diff --git a/src/Wallabag/ImportBundle/Import/WallabagV2Import.php b/src/Wallabag/ImportBundle/Import/WallabagV2Import.php
index 3e085ecf..2ba26003 100644
--- a/src/Wallabag/ImportBundle/Import/WallabagV2Import.php
+++ b/src/Wallabag/ImportBundle/Import/WallabagV2Import.php
@@ -35,7 +35,9 @@ class WallabagV2Import extends WallabagImport
35 { 35 {
36 return [ 36 return [
37 'html' => $entry['content'], 37 'html' => $entry['content'],
38 'content_type' => $entry['mimetype'], 38 'headers' => [
39 'content-type' => $entry['mimetype'],
40 ],
39 'is_archived' => (bool) ($entry['is_archived'] || $this->markAsRead), 41 'is_archived' => (bool) ($entry['is_archived'] || $this->markAsRead),
40 'is_starred' => (bool) $entry['is_starred'], 42 'is_starred' => (bool) $entry['is_starred'],
41 ] + $entry; 43 ] + $entry;
diff --git a/src/Wallabag/ImportBundle/Resources/config/services.yml b/src/Wallabag/ImportBundle/Resources/config/services.yml
index b224a6a2..973c0d03 100644
--- a/src/Wallabag/ImportBundle/Resources/config/services.yml
+++ b/src/Wallabag/ImportBundle/Resources/config/services.yml
@@ -7,13 +7,7 @@ services:
7 class: Wallabag\ImportBundle\Import\ImportChain 7 class: Wallabag\ImportBundle\Import\ImportChain
8 8
9 wallabag_import.pocket.client: 9 wallabag_import.pocket.client:
10 class: GuzzleHttp\Client 10 alias: 'httplug.client.wallabag_import.pocket.client'
11 arguments:
12 -
13 defaults:
14 headers:
15 content-type: "application/json"
16 X-Accept: "application/json"
17 11
18 wallabag_import.pocket.import: 12 wallabag_import.pocket.import:
19 class: Wallabag\ImportBundle\Import\PocketImport 13 class: Wallabag\ImportBundle\Import\PocketImport
@@ -112,3 +106,11 @@ services:
112 - [ setLogger, [ "@logger" ]] 106 - [ setLogger, [ "@logger" ]]
113 tags: 107 tags:
114 - { name: wallabag_import.import, alias: chrome } 108 - { name: wallabag_import.import, alias: chrome }
109
110 wallabag_import.command.import:
111 class: Wallabag\ImportBundle\Command\ImportCommand
112 tags: ['console.command']
113
114 wallabag_import.command.redis_worker:
115 class: Wallabag\ImportBundle\Command\RedisWorkerCommand
116 tags: ['console.command']
diff --git a/src/Wallabag/UserBundle/Controller/ManageController.php b/src/Wallabag/UserBundle/Controller/ManageController.php
index f3de656f..63a06206 100644
--- a/src/Wallabag/UserBundle/Controller/ManageController.php
+++ b/src/Wallabag/UserBundle/Controller/ManageController.php
@@ -7,10 +7,9 @@ use FOS\UserBundle\FOSUserEvents;
7use Pagerfanta\Adapter\DoctrineORMAdapter; 7use Pagerfanta\Adapter\DoctrineORMAdapter;
8use Pagerfanta\Exception\OutOfRangeCurrentPageException; 8use Pagerfanta\Exception\OutOfRangeCurrentPageException;
9use Pagerfanta\Pagerfanta; 9use Pagerfanta\Pagerfanta;
10use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
11use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
12use Symfony\Bundle\FrameworkBundle\Controller\Controller; 10use Symfony\Bundle\FrameworkBundle\Controller\Controller;
13use Symfony\Component\HttpFoundation\Request; 11use Symfony\Component\HttpFoundation\Request;
12use Symfony\Component\Routing\Annotation\Route;
14use Wallabag\UserBundle\Entity\User; 13use Wallabag\UserBundle\Entity\User;
15use Wallabag\UserBundle\Form\SearchUserType; 14use Wallabag\UserBundle\Form\SearchUserType;
16 15
@@ -22,8 +21,7 @@ class ManageController extends Controller
22 /** 21 /**
23 * Creates a new User entity. 22 * Creates a new User entity.
24 * 23 *
25 * @Route("/new", name="user_new") 24 * @Route("/new", name="user_new", methods={"GET", "POST"})
26 * @Method({"GET", "POST"})
27 */ 25 */
28 public function newAction(Request $request) 26 public function newAction(Request $request)
29 { 27 {
@@ -60,19 +58,33 @@ class ManageController extends Controller
60 /** 58 /**
61 * Displays a form to edit an existing User entity. 59 * Displays a form to edit an existing User entity.
62 * 60 *
63 * @Route("/{id}/edit", name="user_edit") 61 * @Route("/{id}/edit", name="user_edit", methods={"GET", "POST"})
64 * @Method({"GET", "POST"})
65 */ 62 */
66 public function editAction(Request $request, User $user) 63 public function editAction(Request $request, User $user)
67 { 64 {
65 $userManager = $this->container->get('fos_user.user_manager');
66
68 $deleteForm = $this->createDeleteForm($user); 67 $deleteForm = $this->createDeleteForm($user);
69 $editForm = $this->createForm('Wallabag\UserBundle\Form\UserType', $user); 68 $form = $this->createForm('Wallabag\UserBundle\Form\UserType', $user);
70 $editForm->handleRequest($request); 69 $form->handleRequest($request);
71 70
72 if ($editForm->isSubmitted() && $editForm->isValid()) { 71 // `googleTwoFactor` isn't a field within the User entity, we need to define it's value in a different way
73 $em = $this->getDoctrine()->getManager(); 72 if ($this->getParameter('twofactor_auth') && true === $user->isGoogleAuthenticatorEnabled() && false === $form->isSubmitted()) {
74 $em->persist($user); 73 $form->get('googleTwoFactor')->setData(true);
75 $em->flush(); 74 }
75
76 if ($form->isSubmitted() && $form->isValid()) {
77 // handle creation / reset of the OTP secret if checkbox changed from the previous state
78 if ($this->getParameter('twofactor_auth')) {
79 if (true === $form->get('googleTwoFactor')->getData() && false === $user->isGoogleAuthenticatorEnabled()) {
80 $user->setGoogleAuthenticatorSecret($this->get('scheb_two_factor.security.google_authenticator')->generateSecret());
81 $user->setEmailTwoFactor(false);
82 } elseif (false === $form->get('googleTwoFactor')->getData() && true === $user->isGoogleAuthenticatorEnabled()) {
83 $user->setGoogleAuthenticatorSecret(null);
84 }
85 }
86
87 $userManager->updateUser($user);
76 88
77 $this->get('session')->getFlashBag()->add( 89 $this->get('session')->getFlashBag()->add(
78 'notice', 90 'notice',
@@ -84,7 +96,7 @@ class ManageController extends Controller
84 96
85 return $this->render('WallabagUserBundle:Manage:edit.html.twig', [ 97 return $this->render('WallabagUserBundle:Manage:edit.html.twig', [
86 'user' => $user, 98 'user' => $user,
87 'edit_form' => $editForm->createView(), 99 'edit_form' => $form->createView(),
88 'delete_form' => $deleteForm->createView(), 100 'delete_form' => $deleteForm->createView(),
89 'twofactor_auth' => $this->getParameter('twofactor_auth'), 101 'twofactor_auth' => $this->getParameter('twofactor_auth'),
90 ]); 102 ]);
@@ -93,8 +105,7 @@ class ManageController extends Controller
93 /** 105 /**
94 * Deletes a User entity. 106 * Deletes a User entity.
95 * 107 *
96 * @Route("/{id}", name="user_delete") 108 * @Route("/{id}", name="user_delete", methods={"DELETE"})
97 * @Method("DELETE")
98 */ 109 */
99 public function deleteAction(Request $request, User $user) 110 public function deleteAction(Request $request, User $user)
100 { 111 {
@@ -135,8 +146,6 @@ class ManageController extends Controller
135 $form->handleRequest($request); 146 $form->handleRequest($request);
136 147
137 if ($form->isSubmitted() && $form->isValid()) { 148 if ($form->isSubmitted() && $form->isValid()) {
138 $this->get('logger')->info('searching users');
139
140 $searchTerm = (isset($request->get('search_user')['term']) ? $request->get('search_user')['term'] : ''); 149 $searchTerm = (isset($request->get('search_user')['term']) ? $request->get('search_user')['term'] : '');
141 150
142 $qb = $em->getRepository('WallabagUserBundle:User')->getQueryBuilderForSearch($searchTerm); 151 $qb = $em->getRepository('WallabagUserBundle:User')->getQueryBuilderForSearch($searchTerm);
@@ -161,7 +170,7 @@ class ManageController extends Controller
161 } 170 }
162 171
163 /** 172 /**
164 * Creates a form to delete a User entity. 173 * Create a form to delete a User entity.
165 * 174 *
166 * @param User $user The User entity 175 * @param User $user The User entity
167 * 176 *
diff --git a/src/Wallabag/UserBundle/DataFixtures/ORM/LoadUserData.php b/src/Wallabag/UserBundle/DataFixtures/UserFixtures.php
index 26dbda3b..1e375e09 100644
--- a/src/Wallabag/UserBundle/DataFixtures/ORM/LoadUserData.php
+++ b/src/Wallabag/UserBundle/DataFixtures/UserFixtures.php
@@ -1,13 +1,12 @@
1<?php 1<?php
2 2
3namespace Wallabag\UserBundle\DataFixtures\ORM; 3namespace Wallabag\UserBundle\DataFixtures;
4 4
5use Doctrine\Common\DataFixtures\AbstractFixture; 5use Doctrine\Bundle\FixturesBundle\Fixture;
6use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
7use Doctrine\Common\Persistence\ObjectManager; 6use Doctrine\Common\Persistence\ObjectManager;
8use Wallabag\UserBundle\Entity\User; 7use Wallabag\UserBundle\Entity\User;
9 8
10class LoadUserData extends AbstractFixture implements OrderedFixtureInterface 9class UserFixtures extends Fixture
11{ 10{
12 /** 11 /**
13 * {@inheritdoc} 12 * {@inheritdoc}
@@ -50,12 +49,4 @@ class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
50 49
51 $manager->flush(); 50 $manager->flush();
52 } 51 }
53
54 /**
55 * {@inheritdoc}
56 */
57 public function getOrder()
58 {
59 return 10;
60 }
61} 52}
diff --git a/src/Wallabag/UserBundle/Entity/User.php b/src/Wallabag/UserBundle/Entity/User.php
index 48446e3c..43fa6a80 100644
--- a/src/Wallabag/UserBundle/Entity/User.php
+++ b/src/Wallabag/UserBundle/Entity/User.php
@@ -8,8 +8,9 @@ use FOS\UserBundle\Model\User as BaseUser;
8use JMS\Serializer\Annotation\Accessor; 8use JMS\Serializer\Annotation\Accessor;
9use JMS\Serializer\Annotation\Groups; 9use JMS\Serializer\Annotation\Groups;
10use JMS\Serializer\Annotation\XmlRoot; 10use JMS\Serializer\Annotation\XmlRoot;
11use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface; 11use Scheb\TwoFactorBundle\Model\BackupCodeInterface;
12use Scheb\TwoFactorBundle\Model\TrustedComputerInterface; 12use Scheb\TwoFactorBundle\Model\Email\TwoFactorInterface as EmailTwoFactorInterface;
13use Scheb\TwoFactorBundle\Model\Google\TwoFactorInterface as GoogleTwoFactorInterface;
13use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; 14use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
14use Symfony\Component\Security\Core\User\UserInterface; 15use Symfony\Component\Security\Core\User\UserInterface;
15use Wallabag\ApiBundle\Entity\Client; 16use Wallabag\ApiBundle\Entity\Client;
@@ -28,7 +29,7 @@ use Wallabag\CoreBundle\Helper\EntityTimestampsTrait;
28 * @UniqueEntity("email") 29 * @UniqueEntity("email")
29 * @UniqueEntity("username") 30 * @UniqueEntity("username")
30 */ 31 */
31class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterface 32class User extends BaseUser implements EmailTwoFactorInterface, GoogleTwoFactorInterface, BackupCodeInterface
32{ 33{
33 use EntityTimestampsTrait; 34 use EntityTimestampsTrait;
34 35
@@ -123,16 +124,21 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
123 private $authCode; 124 private $authCode;
124 125
125 /** 126 /**
126 * @var bool 127 * @ORM\Column(name="googleAuthenticatorSecret", type="string", nullable=true)
127 *
128 * @ORM\Column(type="boolean")
129 */ 128 */
130 private $twoFactorAuthentication = false; 129 private $googleAuthenticatorSecret;
131 130
132 /** 131 /**
133 * @ORM\Column(type="json_array", nullable=true) 132 * @ORM\Column(type="json_array", nullable=true)
134 */ 133 */
135 private $trusted; 134 private $backupCodes;
135
136 /**
137 * @var bool
138 *
139 * @ORM\Column(type="boolean")
140 */
141 private $emailTwoFactor = false;
136 142
137 public function __construct() 143 public function __construct()
138 { 144 {
@@ -233,49 +239,119 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
233 /** 239 /**
234 * @return bool 240 * @return bool
235 */ 241 */
236 public function isTwoFactorAuthentication() 242 public function isEmailTwoFactor()
237 { 243 {
238 return $this->twoFactorAuthentication; 244 return $this->emailTwoFactor;
239 } 245 }
240 246
241 /** 247 /**
242 * @param bool $twoFactorAuthentication 248 * @param bool $emailTwoFactor
243 */ 249 */
244 public function setTwoFactorAuthentication($twoFactorAuthentication) 250 public function setEmailTwoFactor($emailTwoFactor)
245 { 251 {
246 $this->twoFactorAuthentication = $twoFactorAuthentication; 252 $this->emailTwoFactor = $emailTwoFactor;
247 } 253 }
248 254
249 public function isEmailAuthEnabled() 255 /**
256 * Used in the user config form to be "like" the email option.
257 */
258 public function isGoogleTwoFactor()
259 {
260 return $this->isGoogleAuthenticatorEnabled();
261 }
262
263 /**
264 * {@inheritdoc}
265 */
266 public function isEmailAuthEnabled(): bool
250 { 267 {
251 return $this->twoFactorAuthentication; 268 return $this->emailTwoFactor;
252 } 269 }
253 270
254 public function getEmailAuthCode() 271 /**
272 * {@inheritdoc}
273 */
274 public function getEmailAuthCode(): string
255 { 275 {
256 return $this->authCode; 276 return $this->authCode;
257 } 277 }
258 278
259 public function setEmailAuthCode($authCode) 279 /**
280 * {@inheritdoc}
281 */
282 public function setEmailAuthCode(string $authCode): void
260 { 283 {
261 $this->authCode = $authCode; 284 $this->authCode = $authCode;
262 } 285 }
263 286
264 public function addTrustedComputer($token, \DateTime $validUntil) 287 /**
288 * {@inheritdoc}
289 */
290 public function getEmailAuthRecipient(): string
265 { 291 {
266 $this->trusted[$token] = $validUntil->format('r'); 292 return $this->email;
267 } 293 }
268 294
269 public function isTrustedComputer($token) 295 /**
296 * {@inheritdoc}
297 */
298 public function isGoogleAuthenticatorEnabled(): bool
270 { 299 {
271 if (isset($this->trusted[$token])) { 300 return $this->googleAuthenticatorSecret ? true : false;
272 $now = new \DateTime(); 301 }
273 $validUntil = new \DateTime($this->trusted[$token]);
274 302
275 return $now < $validUntil; 303 /**
276 } 304 * {@inheritdoc}
305 */
306 public function getGoogleAuthenticatorUsername(): string
307 {
308 return $this->username;
309 }
277 310
278 return false; 311 /**
312 * {@inheritdoc}
313 */
314 public function getGoogleAuthenticatorSecret(): string
315 {
316 return $this->googleAuthenticatorSecret;
317 }
318
319 /**
320 * {@inheritdoc}
321 */
322 public function setGoogleAuthenticatorSecret(?string $googleAuthenticatorSecret): void
323 {
324 $this->googleAuthenticatorSecret = $googleAuthenticatorSecret;
325 }
326
327 public function setBackupCodes(array $codes = null)
328 {
329 $this->backupCodes = $codes;
330 }
331
332 public function getBackupCodes()
333 {
334 return $this->backupCodes;
335 }
336
337 /**
338 * {@inheritdoc}
339 */
340 public function isBackupCode(string $code): bool
341 {
342 return false === $this->findBackupCode($code) ? false : true;
343 }
344
345 /**
346 * {@inheritdoc}
347 */
348 public function invalidateBackupCode(string $code): void
349 {
350 $key = $this->findBackupCode($code);
351
352 if (false !== $key) {
353 unset($this->backupCodes[$key]);
354 }
279 } 355 }
280 356
281 /** 357 /**
@@ -309,4 +385,24 @@ class User extends BaseUser implements TwoFactorInterface, TrustedComputerInterf
309 return $this->clients->first(); 385 return $this->clients->first();
310 } 386 }
311 } 387 }
388
389 /**
390 * Try to find a backup code from the list of backup codes of the current user.
391 *
392 * @param string $code Given code from the user
393 *
394 * @return string|false
395 */
396 private function findBackupCode(string $code)
397 {
398 foreach ($this->backupCodes as $key => $backupCode) {
399 // backup code are hashed using `password_hash`
400 // see ConfigController->otpAppAction
401 if (password_verify($code, $backupCode)) {
402 return $key;
403 }
404 }
405
406 return false;
407 }
312} 408}
diff --git a/src/Wallabag/UserBundle/EventListener/CreateConfigListener.php b/src/Wallabag/UserBundle/EventListener/CreateConfigListener.php
index e4d55c19..81954213 100644
--- a/src/Wallabag/UserBundle/EventListener/CreateConfigListener.php
+++ b/src/Wallabag/UserBundle/EventListener/CreateConfigListener.php
@@ -6,6 +6,7 @@ use 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\EventSubscriberInterface; 8use Symfony\Component\EventDispatcher\EventSubscriberInterface;
9use Symfony\Component\HttpFoundation\Session\Session;
9use Wallabag\CoreBundle\Entity\Config; 10use Wallabag\CoreBundle\Entity\Config;
10 11
11/** 12/**
@@ -17,22 +18,24 @@ class CreateConfigListener implements EventSubscriberInterface
17 private $em; 18 private $em;
18 private $theme; 19 private $theme;
19 private $itemsOnPage; 20 private $itemsOnPage;
20 private $rssLimit; 21 private $feedLimit;
21 private $language; 22 private $language;
22 private $readingSpeed; 23 private $readingSpeed;
23 private $actionMarkAsRead; 24 private $actionMarkAsRead;
24 private $listMode; 25 private $listMode;
26 private $session;
25 27
26 public function __construct(EntityManager $em, $theme, $itemsOnPage, $rssLimit, $language, $readingSpeed, $actionMarkAsRead, $listMode) 28 public function __construct(EntityManager $em, $theme, $itemsOnPage, $feedLimit, $language, $readingSpeed, $actionMarkAsRead, $listMode, Session $session)
27 { 29 {
28 $this->em = $em; 30 $this->em = $em;
29 $this->theme = $theme; 31 $this->theme = $theme;
30 $this->itemsOnPage = $itemsOnPage; 32 $this->itemsOnPage = $itemsOnPage;
31 $this->rssLimit = $rssLimit; 33 $this->feedLimit = $feedLimit;
32 $this->language = $language; 34 $this->language = $language;
33 $this->readingSpeed = $readingSpeed; 35 $this->readingSpeed = $readingSpeed;
34 $this->actionMarkAsRead = $actionMarkAsRead; 36 $this->actionMarkAsRead = $actionMarkAsRead;
35 $this->listMode = $listMode; 37 $this->listMode = $listMode;
38 $this->session = $session;
36 } 39 }
37 40
38 public static function getSubscribedEvents() 41 public static function getSubscribedEvents()
@@ -51,8 +54,8 @@ class CreateConfigListener implements EventSubscriberInterface
51 $config = new Config($event->getUser()); 54 $config = new Config($event->getUser());
52 $config->setTheme($this->theme); 55 $config->setTheme($this->theme);
53 $config->setItemsPerPage($this->itemsOnPage); 56 $config->setItemsPerPage($this->itemsOnPage);
54 $config->setRssLimit($this->rssLimit); 57 $config->setFeedLimit($this->feedLimit);
55 $config->setLanguage($this->language); 58 $config->setLanguage($this->session->get('_locale', $this->language));
56 $config->setReadingSpeed($this->readingSpeed); 59 $config->setReadingSpeed($this->readingSpeed);
57 $config->setActionMarkAsRead($this->actionMarkAsRead); 60 $config->setActionMarkAsRead($this->actionMarkAsRead);
58 $config->setListMode($this->listMode); 61 $config->setListMode($this->listMode);
diff --git a/src/Wallabag/UserBundle/Form/UserType.php b/src/Wallabag/UserBundle/Form/UserType.php
index 56fea640..026db9a2 100644
--- a/src/Wallabag/UserBundle/Form/UserType.php
+++ b/src/Wallabag/UserBundle/Form/UserType.php
@@ -35,9 +35,14 @@ class UserType extends AbstractType
35 'required' => false, 35 'required' => false,
36 'label' => 'user.form.enabled_label', 36 'label' => 'user.form.enabled_label',
37 ]) 37 ])
38 ->add('twoFactorAuthentication', CheckboxType::class, [ 38 ->add('emailTwoFactor', CheckboxType::class, [
39 'required' => false, 39 'required' => false,
40 'label' => 'user.form.twofactor_label', 40 'label' => 'user.form.twofactor_email_label',
41 ])
42 ->add('googleTwoFactor', CheckboxType::class, [
43 'required' => false,
44 'label' => 'user.form.twofactor_google_label',
45 'mapped' => false,
41 ]) 46 ])
42 ->add('save', SubmitType::class, [ 47 ->add('save', SubmitType::class, [
43 'label' => 'user.form.save', 48 'label' => 'user.form.save',
diff --git a/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php b/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php
index aed805c9..2797efde 100644
--- a/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php
+++ b/src/Wallabag/UserBundle/Mailer/AuthCodeMailer.php
@@ -78,7 +78,7 @@ class AuthCodeMailer implements AuthCodeMailerInterface
78 * 78 *
79 * @param TwoFactorInterface $user 79 * @param TwoFactorInterface $user
80 */ 80 */
81 public function sendAuthCode(TwoFactorInterface $user) 81 public function sendAuthCode(TwoFactorInterface $user): void
82 { 82 {
83 $template = $this->twig->loadTemplate('WallabagUserBundle:TwoFactor:email_auth_code.html.twig'); 83 $template = $this->twig->loadTemplate('WallabagUserBundle:TwoFactor:email_auth_code.html.twig');
84 84
@@ -97,7 +97,7 @@ class AuthCodeMailer implements AuthCodeMailerInterface
97 97
98 $message = new \Swift_Message(); 98 $message = new \Swift_Message();
99 $message 99 $message
100 ->setTo($user->getEmail()) 100 ->setTo($user->getEmailAuthRecipient())
101 ->setFrom($this->senderEmail, $this->senderName) 101 ->setFrom($this->senderEmail, $this->senderName)
102 ->setSubject($subject) 102 ->setSubject($subject)
103 ->setBody($bodyText, 'text/plain') 103 ->setBody($bodyText, 'text/plain')
diff --git a/src/Wallabag/UserBundle/Repository/UserRepository.php b/src/Wallabag/UserBundle/Repository/UserRepository.php
index be693d3b..4abd55f1 100644
--- a/src/Wallabag/UserBundle/Repository/UserRepository.php
+++ b/src/Wallabag/UserBundle/Repository/UserRepository.php
@@ -9,18 +9,18 @@ use Wallabag\UserBundle\Entity\User;
9class UserRepository extends EntityRepository 9class UserRepository extends EntityRepository
10{ 10{
11 /** 11 /**
12 * Find a user by its username and rss roken. 12 * Find a user by its username and Feed token.
13 * 13 *
14 * @param string $username 14 * @param string $username
15 * @param string $rssToken 15 * @param string $feedToken
16 * 16 *
17 * @return User|null 17 * @return User|null
18 */ 18 */
19 public function findOneByUsernameAndRsstoken($username, $rssToken) 19 public function findOneByUsernameAndFeedtoken($username, $feedToken)
20 { 20 {
21 return $this->createQueryBuilder('u') 21 return $this->createQueryBuilder('u')
22 ->leftJoin('u.config', 'c') 22 ->leftJoin('u.config', 'c')
23 ->where('c.rssToken = :rss_token')->setParameter('rss_token', $rssToken) 23 ->where('c.feedToken = :feed_token')->setParameter('feed_token', $feedToken)
24 ->andWhere('u.username = :username')->setParameter('username', $username) 24 ->andWhere('u.username = :username')->setParameter('username', $username)
25 ->getQuery() 25 ->getQuery()
26 ->getOneOrNullResult(); 26 ->getOneOrNullResult();
diff --git a/src/Wallabag/UserBundle/Resources/config/services.yml b/src/Wallabag/UserBundle/Resources/config/services.yml
index d3925de3..2dcf3011 100644
--- a/src/Wallabag/UserBundle/Resources/config/services.yml
+++ b/src/Wallabag/UserBundle/Resources/config/services.yml
@@ -28,11 +28,12 @@ services:
28 - "@doctrine.orm.entity_manager" 28 - "@doctrine.orm.entity_manager"
29 - "%wallabag_core.theme%" 29 - "%wallabag_core.theme%"
30 - "%wallabag_core.items_on_page%" 30 - "%wallabag_core.items_on_page%"
31 - "%wallabag_core.rss_limit%" 31 - "%wallabag_core.feed_limit%"
32 - "%wallabag_core.language%" 32 - "%wallabag_core.language%"
33 - "%wallabag_core.reading_speed%" 33 - "%wallabag_core.reading_speed%"
34 - "%wallabag_core.action_mark_as_read%" 34 - "%wallabag_core.action_mark_as_read%"
35 - "%wallabag_core.list_mode%" 35 - "%wallabag_core.list_mode%"
36 - "@session"
36 tags: 37 tags:
37 - { name: kernel.event_subscriber } 38 - { name: kernel.event_subscriber }
38 39
diff --git a/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig b/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig
index c8471bdd..47a5cb78 100644
--- a/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig
+++ b/src/Wallabag/UserBundle/Resources/views/Authentication/form.html.twig
@@ -1,7 +1,8 @@
1{# Override `vendor/scheb/two-factor-bundle/Resources/views/Authentication/form.html.twig` #}
1{% extends "WallabagUserBundle::layout.html.twig" %} 2{% extends "WallabagUserBundle::layout.html.twig" %}
2 3
3{% block fos_user_content %} 4{% block fos_user_content %}
4<form class="form" action="" method="post"> 5<form class="form" action="{{ path("2fa_login_check") }}" method="post">
5 <div class="card-content"> 6 <div class="card-content">
6 <div class="row"> 7 <div class="row">
7 8
@@ -9,14 +10,19 @@
9 <p class="error">{{ flashMessage|trans }}</p> 10 <p class="error">{{ flashMessage|trans }}</p>
10 {% endfor %} 11 {% endfor %}
11 12
13 {# Authentication errors #}
14 {% if authenticationError %}
15 <p class="error">{{ authenticationError|trans(authenticationErrorData) }}</p>
16 {% endif %}
17
12 <div class="input-field col s12"> 18 <div class="input-field col s12">
13 <label for="_auth_code">{{ "scheb_two_factor.auth_code"|trans }}</label> 19 <label for="_auth_code">{{ "scheb_two_factor.auth_code"|trans }}</label>
14 <input id="_auth_code" type="text" autocomplete="off" name="_auth_code" /> 20 <input id="_auth_code" type="text" autocomplete="off" name="{{ authCodeParameterName }}" />
15 </div> 21 </div>
16 22
17 {% if useTrustedOption %} 23 {% if displayTrustedOption %}
18 <div class="input-field col s12"> 24 <div class="input-field col s12">
19 <input id="_trusted" type="checkbox" name="_trusted" /> 25 <input id="_trusted" type="checkbox" name="{{ trustedParameterName }}" />
20 <label for="_trusted">{{ "scheb_two_factor.trusted"|trans }}</label> 26 <label for="_trusted">{{ "scheb_two_factor.trusted"|trans }}</label>
21 </div> 27 </div>
22 {% endif %} 28 {% endif %}
diff --git a/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig b/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig
index 3ffd15f5..2de8f3a5 100644
--- a/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig
+++ b/src/Wallabag/UserBundle/Resources/views/Manage/edit.html.twig
@@ -50,9 +50,14 @@
50 {% if twofactor_auth %} 50 {% if twofactor_auth %}
51 <div class="row"> 51 <div class="row">
52 <div class="input-field col s12"> 52 <div class="input-field col s12">
53 {{ form_widget(edit_form.twoFactorAuthentication) }} 53 {{ form_widget(edit_form.emailTwoFactor) }}
54 {{ form_label(edit_form.twoFactorAuthentication) }} 54 {{ form_label(edit_form.emailTwoFactor) }}
55 {{ form_errors(edit_form.twoFactorAuthentication) }} 55 {{ form_errors(edit_form.emailTwoFactor) }}
56 </div>
57 <div class="input-field col s12">
58 {{ form_widget(edit_form.googleTwoFactor) }}
59 {{ form_label(edit_form.googleTwoFactor) }}
60 {{ form_errors(edit_form.googleTwoFactor) }}
56 </div> 61 </div>
57 </div> 62 </div>
58 {% endif %} 63 {% endif %}
diff --git a/src/Wallabag/UserBundle/Resources/views/Registration/register_content.html.twig b/src/Wallabag/UserBundle/Resources/views/Registration/register_content.html.twig
index d0a85fc7..85cd4f0d 100644
--- a/src/Wallabag/UserBundle/Resources/views/Registration/register_content.html.twig
+++ b/src/Wallabag/UserBundle/Resources/views/Registration/register_content.html.twig
@@ -3,7 +3,6 @@
3{{ form_start(form, {'method': 'post', 'action': path('fos_user_registration_register'), 'attr': {'class': 'fos_user_registration_register'}}) }} 3{{ form_start(form, {'method': 'post', 'action': path('fos_user_registration_register'), 'attr': {'class': 'fos_user_registration_register'}}) }}
4 <div class="card-content"> 4 <div class="card-content">
5 <div class="row"> 5 <div class="row">
6
7 {{ form_widget(form._token) }} 6 {{ form_widget(form._token) }}
8 7
9 {% for flashMessage in app.session.flashbag.get('notice') %} 8 {% for flashMessage in app.session.flashbag.get('notice') %}
diff --git a/src/Wallabag/UserBundle/Resources/views/layout.html.twig b/src/Wallabag/UserBundle/Resources/views/layout.html.twig
index f97e9870..a47b31d0 100644
--- a/src/Wallabag/UserBundle/Resources/views/layout.html.twig
+++ b/src/Wallabag/UserBundle/Resources/views/layout.html.twig
@@ -15,6 +15,11 @@
15 {% block fos_user_content %} 15 {% block fos_user_content %}
16 {% endblock fos_user_content %} 16 {% endblock fos_user_content %}
17 </div> 17 </div>
18 <div class="center">
19 <a href="{{ path('changeLocale', {'language': 'de'}) }}">Deutsch</a> –
20 <a href="{{ path('changeLocale', {'language': 'en'}) }}">English</a> –
21 <a href="{{ path('changeLocale', {'language': 'fr'}) }}">Français</a>
22 </div>
18 </div> 23 </div>
19</main> 24</main>
20{% endblock %} 25{% endblock %}